new.html 16 KB

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