123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- <link rel="stylesheet" href="mde/script/mde.css">
- <script src="mde/script/mde.js"></script>
- <style>
- .selectable{
- cursor: pointer;
- }
- .selectable:hover{
- background-color: rgba(0, 0, 0, 0.1);
- }
- #mediaContent{
- max-height: 50vh;
- overflow-y: auto;
- }
- .editor-preview img {
- max-width: 100%;
- height: auto;
- }
- .CodeMirror,
- .CodeMirror-scroll {
- height: 520px !important;
- min-height: 520px !important;
- max-height: 520px !important;
- }
- .file-item.selected {
- background-color: rgba(0, 0, 0, 0.1) !important;
- }
- .folder-item.selected {
- background-color: rgba(0, 0, 0, 0.1) !important;
- }
- </style>
- <br>
- <script>
- if ($(".postengine_edit_media_selector").length > 1) {
- $(".postengine_edit_media_selector").first().remove();
- }
- </script>
- <div class="ui container" style="margin-top: 20px;">
- <div class="ui header">
- Edit Post
- </div>
- <!-- Post Editor -->
- <div class="ui form">
- <div class="disabled field">
- <label for="title">Title</label>
- <input type="text" id="title" placeholder="Enter title here" readonly="readonly">
- </div>
-
- <div class="field">
- <label for="editor">Post Content</label>
- <textarea id="editor"></textarea>
- </div>
- <button class="ui basic button" id="saveButton"><i class="ui green save icon"></i> Update</button>
- <button class="ui basic button" id="cancelButton" onclick="handleBackToPostList();">
- <i class="ui red cancel icon"></i> Discard Changes
- </button>
- </div>
- </div>
- <!-- Media Selector -->
- <div class="ui modal postengine_edit_media_selector">
- <i class="close icon"></i>
- <div class="content">
- <div class="ui fluid input">
- <button class="ui basic circular icon button" id="backButton">
- <i class="arrow left icon"></i>
- </button>
- <button class="ui basic circular icon button" id="parentButton" style="margin-left: 0.4em;">
- <i class="arrow up icon"></i>
- </button>
- <input type="text" placeholder="/" id="locationBar" style="margin-left: 0.4em;" value="/">
- </div>
- <div id="mediaContent" class="ui segments" style="margin-top: 1em;">
- <div class="ui basic segment" style="pointer-events: none; user-select: none; opacity: 0.5;">
- <i class="ui green circle check icon"></i> Loading media files...
- </div>
- </div>
- <div id="limited_access_warning" class="ui hidden red message" style="display:none;">
- This folder is not accessible by guest visitors but only logged-in users.
- </div>
- </div>
- <div class="actions">
- <div class="ui black deny button">
- Cancel
- </div>
- <button class="ui basic button" id="importButton">
- <i class="green download icon"></i> Import Selected
- </button>
- </div>
- </div>
- <br><br><br>
- <script>
- var currentDir = "/"; // Initialize current path
- var pathHistory = []; // Initialize path history
- var unsafeCharacters = /[<>:"/\\|?*\x00-\x1F]/; // Regex for unsafe characters
- if (typeof(autosaveInterval) != "undefined"){
- clearInterval(autosaveInterval); // Clear any existing autosave interval
- }
- //Load the post content if editingPost is not empty
- var editingPostFilename = editingPost;
- if (loadPostContent == ""){
- alert("No post selected for editing.");
- switchToTab("allposts");
- }else{
- loadPostContent(editingPost); // Load the content of the post into the editor
- }
- //Generate the title from the filename
- var editingPostTitle = editingPost.split("_").slice(1).join("_").split(".")[0]; // Extract the post title from the filename
- if (editingPostTitle.endsWith(".md")) {
- editingPostTitle = editingPostTitle.slice(0, -3); // Remove the ".md" suffix
- }
- $("#title").val(editingPostTitle);
- function handleBackToPostList(){
- if (confirm("Are you sure you want to discard the editing content?")) {
- switchToTab("allposts"); // Switch to the all posts tab
- }
- }
- // Initialize SimpleMDE
- var simplemde = new SimpleMDE({
- element: document.getElementById("editor"),
- toolbar: [
- "bold",
- "italic",
- "heading",
- "|",
- "quote",
- "unordered-list",
- "ordered-list",
- "|",
- "link",
- "image",
- {
- name: "mediaSelect",
- action: function customFunction(editor) {
- openMediaSelector(editor);
- },
- className: "fa fa-folder", // Font Awesome icon
- title: "Select Media"
- },
- "|",
- "preview",
- ]
- });
- // Allow vertical resize for the editor
- document.getElementById("editor").style.resize = "vertical";
- /*
- Post Loader
- */
- function loadPostContent(filename) {
- let filepath = "/site/posts/" + filename;
- $.get("/api/fs/download?file=" + filepath, function(data) {
- if (data.error == undefined){
- simplemde.value(data); // Load the content into the editor
- } else {
- alert("Failed to load post content: " + data.error);
- }
- }).fail(function() {
- alert("Error loading post content.");
- });
- }
- /*
- Media Selector
- This function will open the media selector modal when the media button is clicked.
- */
- function fetchDirectoryContents(path) {
- $.ajax({
- url: `/api/fs/list?dir=${encodeURIComponent(path)}`,
- method: 'GET',
- success: function(data) {
- if (data.error == undefined){
- pathHistory.push(currentDir);
- if (path.endsWith("/")) {
- currentDir = path.slice(0, -1); // Remove trailing slash if present
- } else {
- currentDir = path; // Update current directory
- }
- $("#locationBar").val(currentDir); // Update location bar
- renderDirectoryContents(data);
- } else {
- alert("Failed to fetch directory contents: " + data.error);
- }
- },
- error: function() {
- alert("Error fetching directory contents.");
- }
- });
- }
- function renderDirectoryContents(contents) {
- const container = $("#mediaContent");
- container.empty();
- let folderElements = ``;
- let fileElements = ``;
- let selectableFileCounter = 0;
- contents.forEach(item => {
- if (item.IsDir) {
- folderElements += (`
- <div class="ui segment folder-item selectable" data-path="${currentDir + "/" + item.Filename}">
- <i class="yellow folder icon"></i> ${item.Filename}
- </div>
- `);
- selectableFileCounter++;
- } else if (!item.IsDir && isWebSafeFile(item.Filename)) {
- let fileIcon = getFileTypeIcons(item.Filename);
- fileElements += (`
- <div class="ui segment file-item selectable" data-path="${currentDir + "/" + item.Filename}" data-type="${getFileType(item.Filename)}">
- ${fileIcon} ${item.Filename}
- </div>
- `);
- selectableFileCounter++;
- }
- });
- container.append(folderElements);
- container.append(fileElements);
- if (selectableFileCounter == 0) {
- 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>");
- }
- // Add click handlers for folders and files
- $(".folder-item").on('dblclick', function() {
- const path = $(this).data('path');
- fetchDirectoryContents(path);
- });
- $(".folder-item").on("click", function(event) {
- const path = $(this).data('path');
- if (!event.ctrlKey) {
- // Highlight the selected folder and remove previous selection if ctrl is not held
- $(".folder-item.selected").removeClass("selected");
- }
- $(this).toggleClass("selected"); // Toggle selection for the clicked item
- });
- $(".file-item").on('dblclick', function() {
- $(".postengine_edit_media_selector").modal("hide");
- const path = $(this).data('path');
- const type = $(this).data('type');
- addMediaFileToEditor(path, type);
- });
- $(".file-item").on("click", function(event) {
- const path = $(this).data('path');
- const type = $(this).data('type');
- if (!event.ctrlKey) {
- // Highlight the selected file and remove previous selection if ctrl is not held
- $(".file-item.selected").removeClass("selected");
- }
- $(this).toggleClass("selected"); // Toggle selection for the clicked item
- });
- //Show warning if the directory starts with /admin or /store
- if (currentDir.startsWith("/admin") || currentDir.startsWith("/store")) {
- $("#limited_access_warning").show();
- } else {
- $("#limited_access_warning").hide();
- }
- }
- // Bind event to the back button
- $('#backButton').on('click', function() {
- if (pathHistory.length > 1) {
- pathHistory.pop(); // Remove the current directory
- const previousPath = pathHistory.pop();
- fetchDirectoryContents(previousPath);
- }
- });
- // Bind event to the parent button
- $('#parentButton').on('click', function() {
- if (currentDir == "/") {
- msgbox("Already at the root directory", 3000);
- return;
- }
- let parentPath = currentDir.split("/");
- parentPath.pop(); // Remove the last element (current directory)
- parentPath = parentPath.join("/"); // Join the remaining elements to form the parent path
- if (parentPath == ""){
- parentPath = "/"; // Set to root if empty
- }
- fetchDirectoryContents(parentPath);
- });
- // Bind event to the import button
- $('#importButton').on('click', function() {
- const selectedFolders = $(".folder-item.selected");
- const selectedFiles = $(".file-item.selected");
- if (selectedFiles.length === 0 && selectedFolders.length === 0) {
- alert("No files selected for import.");
- return;
- }
- selectedFolders.each(function() {
- const path = $(this).data('path');
- const type = "link";
- addMediaFileToEditor(path, type);
- });
- selectedFiles.each(function() {
- const path = $(this).data('path');
- const type = $(this).data('type');
- addMediaFileToEditor(path, type);
- });
- $(".postengine_edit_media_selector").modal("hide");
- });
- function isWebSafeFile(fileName) {
- const webSafeExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'webm', 'mp3', 'ogg'];
- const extension = fileName.split('.').pop().toLowerCase();
- return webSafeExtensions.includes(extension);
- }
- function getFileType(fileName) {
- const extension = fileName.split('.').pop().toLowerCase();
- if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension)) {
- return 'image';
- } else if (extension === 'webm') {
- return 'video';
- } else if (['mp3', 'ogg'].includes(extension)) {
- return 'audio';
- } else {
- return 'unknown';
- }
- }
- function openMediaSelector(editor){
- // Reset history
- pathHistory = []; // Reset path history
- fetchDirectoryContents(currentDir);
- $(".postengine_edit_media_selector").modal("show");
- }
- function addMediaFileToEditor(mediaLink, mediaType){
- if (mediaType === 'image') {
- simplemde.codemirror.replaceSelection(``);
- } else if (mediaType === 'video') {
- let mimeType = mediaLink.split('.').pop().toLowerCase() === 'webm' ? 'video/webm' : 'video/mp4';
- simplemde.codemirror.replaceSelection(`<video style="background:black;" width="720" height="480" controls><source src="${mediaLink}" type="${mimeType}"></video>`);
- } else if (mediaType === 'audio') {
- simplemde.codemirror.replaceSelection(`<audio style="min-width: 512px;" controls><source src="${mediaLink}" type="audio/mpeg">Your browser does not support the audio element.</audio>`);
- } else if (mediaType === 'link') {
- let folderName = mediaLink.split("/").pop(); // Extract folder name from path
- simplemde.codemirror.replaceSelection(`[${folderName}](${mediaLink})`);
- } else {
- alert("Unsupported media type!");
- return;
- }
- }
- function getFileTypeIcons(fileName) {
- const extension = fileName.split('.').pop().toLowerCase();
- if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension)) {
- return '<i class="blue file image icon"></i>';
- } else if (extension === 'webm') {
- return '<i class="violet file video icon"></i>';
- } else if (['mp3', 'ogg'].includes(extension)) {
- return '<i class="green file audio icon"></i>';
- } else {
- return '<i class="file icon"></i>';
- }
- }
-
- // Save button functionality
- $('#saveButton').on('click', function() {
- const title = $('#title').val();
- const content = simplemde.value();
- console.log("Title:", title);
- console.log("Content:", content);
- createNewPost(title, content);
- });
- /*
- Post Create Function
- */
- // Function to create a new post
- function createNewPost(title, content="# Hello World\n"){
- if (title.trim() === "" && content.trim() === "") {
- alert("Cannot create an empty post.");
- return;
- }
- $("#saveButton").addClass("loading disabled");
- //Create the markdown file at the /blog/posts folder
- const blob = new Blob([content], { type: 'text/plain' });
- let storeFilename = editingPostFilename; //Overwrite on top of the original one
- const file = new File([blob], storeFilename);
- handleFile(file, "/site/posts", function(){
- //Update the post index
- updatePostIndex(function(){
- $("#confirmNewPostBtn").removeClass("loading disabled");
- msgbox("Post created successfully!", 3000);
- switchToTab("allposts"); // Switch to the all posts tab
- });
- //Clear the draft
- localStorage.removeItem('draftTitle');
- localStorage.removeItem('draftContent');
- });
- }
- // Error handler for AJAX requests
- function errorHandler(event) {
- msgbox("Failed to create post: " + event.target.responseText, 3000);
- $("#saveButton").removeClass("loading disabled");
- }
- // Function to handle file upload
- function handleFile(file, dir=currentPath, callback=undefined) {
- // Perform actions with the selected file
- var formdata = new FormData();
- formdata.append("file1", file);
- var ajax = new XMLHttpRequest();
- ajax.addEventListener("load", function(event){
- let responseText = event.target.responseText;
- try{
- responseText = JSON.parse(responseText);
- if (responseText.error != undefined){
- alert(responseText.error);
- }
- }catch(ex){
- }
- if (callback != undefined){
- callback();
- }
- }, false); // doesnt appear to ever get called even upon success
- ajax.addEventListener("error", errorHandler, false);
- //ajax.addEventListener("abort", abortHandler, false);
- ajax.open("POST", "/upload?dir=" + dir);
- ajax.send(formdata);
- }
- $(document).ready(function() {
- // Initialize with root directory
- fetchDirectoryContents(currentDir);
- });
- </script>
|