new.html 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. <link rel="stylesheet" href="mde/script/mde.css">
  2. <script src="mde/script/mde.js"></script>
  3. <style>
  4. .selectable{
  5. cursor: pointer;
  6. }
  7. .selectable:hover{
  8. background-color: rgba(0, 0, 0, 0.1);
  9. }
  10. #mediaContent{
  11. max-height: 50vh;
  12. overflow-y: auto;
  13. }
  14. .editor-preview img {
  15. max-width: 100%;
  16. height: auto;
  17. }
  18. .CodeMirror {
  19. resize: vertical; /* Allows vertical resizing */
  20. overflow: auto; /* Ensures scrollbars appear if content overflows */
  21. min-height: 200px; /* Optional: Set a minimum height */
  22. }
  23. .file-item.selected {
  24. background-color: rgba(0, 0, 0, 0.1) !important;
  25. }
  26. .folder-item.selected {
  27. background-color: rgba(0, 0, 0, 0.1) !important;
  28. }
  29. </style>
  30. <br>
  31. <div class="ui container" style="margin-top: 20px;">
  32. <h2 class="ui header">New Post</h2>
  33. <!-- Load draft message -->
  34. <div id="loadDraftMessage" class="ui hidden message">
  35. <div class="header">Saved Draft Found</div>
  36. <p>A saved draft was found. Would you like to load it?</p>
  37. <button class="ui green basic button" id="loadDraftButton">Load Draft</button>
  38. <button class="ui red basic button" id="discardDraftButton">Discard</button>
  39. </div>
  40. <!-- Post Editor -->
  41. <div class="ui form">
  42. <div class="field">
  43. <label for="title">Title</label>
  44. <input type="text" id="title" placeholder="Enter title here">
  45. </div>
  46. <div class="field">
  47. <label for="editor">Post Content</label>
  48. <textarea id="editor"></textarea>
  49. </div>
  50. <button class="ui basic button" id="saveButton"><i class="ui green upload icon"></i>Publish</button>
  51. <button class="ui blue basic button" id="saveDraftButton"><i class="ui save icon"></i>Save as Draft</button>
  52. </div>
  53. </div>
  54. <!-- Media Selector -->
  55. <div id="postengine_media_selector" class="ui modal">
  56. <i class="close icon"></i>
  57. <div class="content">
  58. <div class="ui fluid input">
  59. <button class="ui basic circular icon button" id="backButton">
  60. <i class="arrow left icon"></i>
  61. </button>
  62. <button class="ui basic circular icon button" id="parentButton" style="margin-left: 0.4em;">
  63. <i class="arrow up icon"></i>
  64. </button>
  65. <input type="text" placeholder="/" id="locationBar" style="margin-left: 0.4em;" value="/">
  66. </div>
  67. <div id="mediaContent" class="ui segments" style="margin-top: 1em;">
  68. <div class="ui basic segment" style="pointer-events: none; user-select: none; opacity: 0.5;">
  69. <i class="ui green circle check icon"></i> Loading media files...
  70. </div>
  71. </div>
  72. <div id="limited_access_warning" class="ui hidden red message" style="display:none;">
  73. This folder is not accessible by guest visitors but only logged-in users.
  74. </div>
  75. </div>
  76. <div class="actions">
  77. <div class="ui black deny button">
  78. Cancel
  79. </div>
  80. <button class="ui basic button" id="importButton">
  81. <i class="green download icon"></i> Import Selected
  82. </button>
  83. </div>
  84. </div>
  85. <script>
  86. var currentDir = "/img"; // Initialize current path
  87. var pathHistory = [currentDir]; // Initialize path history
  88. $(document).ready(function() {
  89. // Initialize SimpleMDE
  90. var simplemde = new SimpleMDE({
  91. element: document.getElementById("editor"),
  92. toolbar: [
  93. "bold",
  94. "italic",
  95. "heading",
  96. "|",
  97. "quote",
  98. "unordered-list",
  99. "ordered-list",
  100. "|",
  101. "link",
  102. "image",
  103. {
  104. name: "mediaSelect",
  105. action: function customFunction(editor) {
  106. openMediaSelector(editor);
  107. },
  108. className: "fa fa-folder", // Font Awesome icon
  109. title: "Select Media"
  110. },
  111. "|",
  112. "preview",
  113. ]
  114. });
  115. /*
  116. Media Selector
  117. This function will open the media selector modal when the media button is clicked.
  118. */
  119. function fetchDirectoryContents(path) {
  120. $.ajax({
  121. url: `/api/fs/list?dir=${encodeURIComponent(path)}`,
  122. method: 'GET',
  123. success: function(data) {
  124. if (data.error == undefined){
  125. pathHistory.push(currentDir);
  126. if (path.endsWith("/")) {
  127. currentDir = path.slice(0, -1); // Remove trailing slash if present
  128. } else {
  129. currentDir = path; // Update current directory
  130. }
  131. $("#locationBar").val(currentDir); // Update location bar
  132. renderDirectoryContents(data);
  133. } else {
  134. alert("Failed to fetch directory contents: " + data.error);
  135. }
  136. },
  137. error: function() {
  138. alert("Error fetching directory contents.");
  139. }
  140. });
  141. }
  142. function renderDirectoryContents(contents) {
  143. const container = $("#mediaContent");
  144. container.empty();
  145. let folderElements = ``;
  146. let fileElements = ``;
  147. let selectableFileCounter = 0;
  148. contents.forEach(item => {
  149. if (item.IsDir) {
  150. folderElements += (`
  151. <div class="ui segment folder-item selectable" data-path="${currentDir + "/" + item.Filename}">
  152. <i class="yellow folder icon"></i> ${item.Filename}
  153. </div>
  154. `);
  155. selectableFileCounter++;
  156. } else if (!item.IsDir && isWebSafeFile(item.Filename)) {
  157. let fileIcon = getFileTypeIcons(item.Filename);
  158. fileElements += (`
  159. <div class="ui segment file-item selectable" data-path="${currentDir + "/" + item.Filename}" data-type="${getFileType(item.Filename)}">
  160. ${fileIcon} ${item.Filename}
  161. </div>
  162. `);
  163. selectableFileCounter++;
  164. }
  165. });
  166. container.append(folderElements);
  167. container.append(fileElements);
  168. if (selectableFileCounter == 0) {
  169. container.append("<div class='ui basic segment' style='pointer-events: none; user-select: none; opacity: 0.5;'><i class='ui green circle check icon'></i> No usable files / folders found</div>");
  170. }
  171. // Add click handlers for folders and files
  172. $(".folder-item").on('dblclick', function() {
  173. const path = $(this).data('path');
  174. fetchDirectoryContents(path);
  175. });
  176. $(".folder-item").on("click", function(event) {
  177. const path = $(this).data('path');
  178. if (!event.ctrlKey) {
  179. // Highlight the selected folder and remove previous selection if ctrl is not held
  180. $(".folder-item.selected").removeClass("selected");
  181. }
  182. $(this).toggleClass("selected"); // Toggle selection for the clicked item
  183. });
  184. $(".file-item").on('dblclick', function() {
  185. $("#postengine_media_selector").modal("hide");
  186. const path = $(this).data('path');
  187. const type = $(this).data('type');
  188. addMediaFileToEditor(path, type);
  189. });
  190. $(".file-item").on("click", function(event) {
  191. const path = $(this).data('path');
  192. const type = $(this).data('type');
  193. if (!event.ctrlKey) {
  194. // Highlight the selected file and remove previous selection if ctrl is not held
  195. $(".file-item.selected").removeClass("selected");
  196. }
  197. $(this).toggleClass("selected"); // Toggle selection for the clicked item
  198. });
  199. //Show warning if the directory starts with /admin or /store
  200. if (currentDir.startsWith("/admin") || currentDir.startsWith("/store")) {
  201. $("#limited_access_warning").show();
  202. } else {
  203. $("#limited_access_warning").hide();
  204. }
  205. }
  206. // Bind event to the back button
  207. $('#backButton').on('click', function() {
  208. if (pathHistory.length > 1) {
  209. pathHistory.pop(); // Remove the current directory
  210. const previousPath = pathHistory.pop();
  211. fetchDirectoryContents(previousPath);
  212. }
  213. });
  214. // Bind event to the parent button
  215. $('#parentButton').on('click', function() {
  216. if (currentDir == "/") {
  217. msgbox("Already at the root directory", 3000);
  218. return;
  219. }
  220. let parentPath = currentDir.split("/");
  221. parentPath.pop(); // Remove the last element (current directory)
  222. parentPath = parentPath.join("/"); // Join the remaining elements to form the parent path
  223. if (parentPath == ""){
  224. parentPath = "/"; // Set to root if empty
  225. }
  226. fetchDirectoryContents(parentPath);
  227. });
  228. // Bind event to the import button
  229. $('#importButton').on('click', function() {
  230. const selectedFolders = $(".folder-item.selected");
  231. const selectedFiles = $(".file-item.selected");
  232. if (selectedFiles.length === 0 && selectedFolders.length === 0) {
  233. alert("No files selected for import.");
  234. return;
  235. }
  236. selectedFolders.each(function() {
  237. const path = $(this).data('path');
  238. const type = "link";
  239. addMediaFileToEditor(path, type);
  240. });
  241. selectedFiles.each(function() {
  242. const path = $(this).data('path');
  243. const type = $(this).data('type');
  244. addMediaFileToEditor(path, type);
  245. });
  246. $("#postengine_media_selector").modal("hide");
  247. });
  248. function isWebSafeFile(fileName) {
  249. const webSafeExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'webm', 'mp3', 'ogg'];
  250. const extension = fileName.split('.').pop().toLowerCase();
  251. return webSafeExtensions.includes(extension);
  252. }
  253. function getFileType(fileName) {
  254. const extension = fileName.split('.').pop().toLowerCase();
  255. if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension)) {
  256. return 'image';
  257. } else if (extension === 'webm') {
  258. return 'video';
  259. } else if (['mp3', 'ogg'].includes(extension)) {
  260. return 'audio';
  261. } else {
  262. return 'unknown';
  263. }
  264. }
  265. function openMediaSelector(editor){
  266. // Reset history
  267. pathHistory = []; // Reset path history
  268. fetchDirectoryContents(currentDir);
  269. $("#postengine_media_selector").modal("show");
  270. }
  271. function addMediaFileToEditor(mediaLink, mediaType){
  272. if (mediaType === 'image') {
  273. simplemde.codemirror.replaceSelection(`![Image](${mediaLink})`);
  274. } else if (mediaType === 'video') {
  275. let mimeType = mediaLink.split('.').pop().toLowerCase() === 'webm' ? 'video/webm' : 'video/mp4';
  276. simplemde.codemirror.replaceSelection(`<video style="background:black;" width="720" height="480" controls><source src="${mediaLink}" type="${mimeType}"></video>`);
  277. } else if (mediaType === 'audio') {
  278. simplemde.codemirror.replaceSelection(`<audio style="min-width: 512px;" controls><source src="${mediaLink}" type="audio/mpeg">Your browser does not support the audio element.</audio>`);
  279. } else if (mediaType === 'link') {
  280. let folderName = mediaLink.split("/").pop(); // Extract folder name from path
  281. simplemde.codemirror.replaceSelection(`[${folderName}](${mediaLink})`);
  282. } else {
  283. alert("Unsupported media type!");
  284. return;
  285. }
  286. }
  287. function getFileTypeIcons(fileName) {
  288. const extension = fileName.split('.').pop().toLowerCase();
  289. if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension)) {
  290. return '<i class="blue file image icon"></i>';
  291. } else if (extension === 'webm') {
  292. return '<i class="violet file video icon"></i>';
  293. } else if (['mp3', 'ogg'].includes(extension)) {
  294. return '<i class="green file audio icon"></i>';
  295. } else {
  296. return '<i class="file icon"></i>';
  297. }
  298. }
  299. // Initialize with root directory
  300. fetchDirectoryContents(currentDir);
  301. // Save button functionality
  302. $('#saveButton').on('click', function() {
  303. const title = $('#title').val();
  304. const tags = $('#tags').val();
  305. const content = simplemde.value();
  306. console.log("Title:", title);
  307. console.log("Tags:", tags);
  308. console.log("Content:", content);
  309. // Add your save logic here
  310. alert("Content saved successfully!");
  311. });
  312. // Save Draft button functionality
  313. $('#saveDraftButton').on('click', function() {
  314. const title = $('#title').val();
  315. const content = simplemde.value();
  316. if (title.trim() === "" && content.trim() === "") {
  317. alert("Cannot save an empty draft.");
  318. return;
  319. }
  320. localStorage.setItem('draftTitle', title);
  321. localStorage.setItem('draftContent', content);
  322. msgbox("Draft saved successfully!");
  323. });
  324. // Discard Draft button functionality
  325. $('#discardDraftButton').on('click', function() {
  326. if (confirm("Are you sure you want to discard the draft? This action cannot be undone.")) {
  327. localStorage.removeItem('draftTitle');
  328. localStorage.removeItem('draftContent');
  329. $('#title').val('');
  330. simplemde.value('');
  331. msgbox("Draft discarded successfully!");
  332. $('#loadDraftMessage').addClass('hidden');
  333. }
  334. });
  335. // Load draft from localStorage on page load
  336. const savedTitle = localStorage.getItem('draftTitle');
  337. const savedContent = localStorage.getItem('draftContent');
  338. if (savedTitle || savedContent) {
  339. $('#loadDraftMessage').removeClass('hidden');
  340. $('#loadDraftButton').on('click', function() {
  341. $('#title').val(savedTitle || '');
  342. simplemde.value(savedContent || '');
  343. $('#loadDraftMessage').addClass('hidden');
  344. });
  345. }
  346. });
  347. </script>