diff --git a/src/public/app/dialogs/note_revisions.js b/src/public/app/dialogs/note_revisions.js deleted file mode 100644 index 520a1cc99..000000000 --- a/src/public/app/dialogs/note_revisions.js +++ /dev/null @@ -1,232 +0,0 @@ -import utils from '../services/utils.js'; -import server from '../services/server.js'; -import toastService from "../services/toast.js"; -import appContext from "../services/app_context.js"; -import libraryLoader from "../services/library_loader.js"; -import openService from "../services/open.js"; -import protectedSessionHolder from "../services/protected_session_holder.js"; - -const $dialog = $("#note-revisions-dialog"); -const $list = $("#note-revision-list"); -const $listDropdown = $("#note-revision-list-dropdown"); -const $content = $("#note-revision-content"); -const $title = $("#note-revision-title"); -const $titleButtons = $("#note-revision-title-buttons"); -const $eraseAllRevisionsButton = $("#note-revisions-erase-all-revisions-button"); - -$listDropdown.dropdown(); - -$listDropdown.parent().on('hide.bs.dropdown', e => { - // prevent closing dropdown by clicking outside - if (e.clickEvent) { - e.preventDefault(); - } -}); - -let revisionItems = []; -let note; -let noteRevisionId; - -export async function showCurrentNoteRevisions() { - await showNoteRevisionsDialog(appContext.tabManager.getActiveContextNoteId()); -} - -export async function showNoteRevisionsDialog(noteId, noteRevisionId) { - utils.openDialog($dialog); - - await loadNoteRevisions(noteId, noteRevisionId); -} - -async function loadNoteRevisions(noteId, noteRevId) { - $list.empty(); - $content.empty(); - $titleButtons.empty(); - - note = appContext.tabManager.getActiveContextNote(); - revisionItems = await server.get(`notes/${noteId}/revisions`); - - for (const item of revisionItems) { - $list.append( - $('') - .text(item.dateLastEdited.substr(0, 16) + ` (${item.contentLength} bytes)`) - .attr('data-note-revision-id', item.noteRevisionId) - .attr('title', 'This revision was last edited on ' + item.dateLastEdited) - ); - } - - $listDropdown.dropdown('show'); - - noteRevisionId = noteRevId; - - if (revisionItems.length > 0) { - if (!noteRevisionId) { - noteRevisionId = revisionItems[0].noteRevisionId; - } - } else { - $title.text("No revisions for this note yet..."); - noteRevisionId = null; - } - - $eraseAllRevisionsButton.toggle(revisionItems.length > 0); -} - -$dialog.on('shown.bs.modal', () => { - $list.find(`[data-note-revision-id="${noteRevisionId}"]`) - .trigger('focus'); -}); - -async function setContentPane() { - const noteRevisionId = $list.find(".active").attr('data-note-revision-id'); - - const revisionItem = revisionItems.find(r => r.noteRevisionId === noteRevisionId); - - $titleButtons.empty(); - $content.empty(); - - $title.html(revisionItem.title); - - const $restoreRevisionButton = $(''); - - $restoreRevisionButton.on('click', async () => { - const confirmDialog = await import('../dialogs/confirm.js'); - const text = 'Do you want to restore this revision? This will overwrite current title/content of the note with this revision.'; - - if (await confirmDialog.confirm(text)) { - await server.put(`notes/${revisionItem.noteId}/restore-revision/${revisionItem.noteRevisionId}`); - - $dialog.modal('hide'); - - toastService.showMessage('Note revision has been restored.'); - } - }); - - const $eraseRevisionButton = $(''); - - $eraseRevisionButton.on('click', async () => { - const confirmDialog = await import('../dialogs/confirm.js'); - const text = 'Do you want to delete this revision? This action will delete revision title and content, but still preserve revision metadata.'; - - if (await confirmDialog.confirm(text)) { - await server.remove(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`); - - loadNoteRevisions(revisionItem.noteId); - - toastService.showMessage('Note revision has been deleted.'); - } - }); - - if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) { - $titleButtons - .append($restoreRevisionButton) - .append('   '); - } - - $titleButtons - .append($eraseRevisionButton) - .append('   '); - - const $downloadButton = $(''); - - $downloadButton.on('click', () => openService.downloadNoteRevision(revisionItem.noteId, revisionItem.noteRevisionId)); - - if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) { - $titleButtons.append($downloadButton); - } - - const fullNoteRevision = await server.get(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`); - - if (revisionItem.type === 'text') { - $content.html(fullNoteRevision.content); - - if ($content.find('span.math-tex').length > 0) { - await libraryLoader.requireLibrary(libraryLoader.KATEX); - - renderMathInElement($content[0], {trust: true}); - } - } - else if (revisionItem.type === 'code') { - $content.html($("
").text(fullNoteRevision.content));
-    }
-    else if (revisionItem.type === 'image') {
-        $content.html($("")
-            // reason why we put this inline as base64 is that we do not want to let user to copy this
-            // as a URL to be used in a note. Instead if they copy and paste it into a note, it will be a uploaded as a new note
-            .attr("src", `data:${note.mime};base64,` + fullNoteRevision.content)
-            .css("max-width", "100%")
-            .css("max-height", "100%"));
-    }
-    else if (revisionItem.type === 'file') {
-        const $table = $("")
-            .append($("").append(
-                $("").append(
-                $("").append(
-                $('
").text("MIME: "), - $("").text(revisionItem.mime) - )) - .append($("
").text("File size:"), - $("").text(revisionItem.contentLength + " bytes") - )); - - if (fullNoteRevision.content) { - $table.append($("
').append( - $('
').text("Preview:"), - $('
')
-                        .text(fullNoteRevision.content)
-                )
-            ));
-        }
-
-        $content.html($table);
-    }
-    else if (revisionItem.type === 'canvas') {
-        /**
-         * FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
-         *        REMOVE external dependency!!!! This is defined in the svg in defs.style
-         */
-        const content = fullNoteRevision.content;
-
-        try {
-            const data = JSON.parse(content)
-            const svg = data.svg || "no svg present."
-
-            /**
-             * maxWidth: 100% use full width of container but do not enlarge!
-             * height:auto to ensure that height scales with width
-             */
-            const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
-            $content.html($('
').append($svgHtml)); - } catch(err) { - console.error("error parsing fullNoteRevision.content as JSON", fullNoteRevision.content, err); - $content.html($("
").text("Error parsing content. Please check console.error() for more details.")); - } - } - else { - $content.text("Preview isn't available for this note type."); - } -} - -$eraseAllRevisionsButton.on('click', async () => { - const confirmDialog = await import('../dialogs/confirm.js'); - const text = 'Do you want to delete all revisions of this note? This action will erase revision title and content, but still preserve revision metadata.'; - - if (await confirmDialog.confirm(text)) { - await server.remove(`notes/${note.noteId}/revisions`); - - $dialog.modal('hide'); - - toastService.showMessage('Note revisions has been deleted.'); - } -}); - -$list.on('click', '.dropdown-item', e => { - e.preventDefault(); - return false; -}); - -$list.on('focus', '.dropdown-item', e => { - $list.find('.dropdown-item').each((i, el) => { - $(el).toggleClass('active', el === e.target); - }); - - setContentPane(); -}); diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 163c0aeee..f44d292e5 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -69,6 +69,7 @@ import ImportDialog from "../widgets/dialogs/import.js"; import ExportDialog from "../widgets/dialogs/export.js"; import MarkdownImportDialog from "../widgets/dialogs/markdown_import.js"; import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js"; +import NoteRevisionsDialog from "../widgets/dialogs/note_revisions.js"; export default class DesktopLayout { constructor(customWidgets) { @@ -212,6 +213,7 @@ export default class DesktopLayout { .child(new ImportDialog()) .child(new ExportDialog()) .child(new MarkdownImportDialog()) - .child(new ProtectedSessionPasswordDialog()); + .child(new ProtectedSessionPasswordDialog()) + .child(new NoteRevisionsDialog()); } } diff --git a/src/public/app/services/entrypoints.js b/src/public/app/services/entrypoints.js index a794232b7..44033f766 100644 --- a/src/public/app/services/entrypoints.js +++ b/src/public/app/services/entrypoints.js @@ -5,7 +5,6 @@ import server from "./server.js"; import appContext from "./app_context.js"; import Component from "../widgets/component.js"; import toastService from "./toast.js"; -import noteCreateService from "./note_create.js"; import ws from "./ws.js"; import bundleService from "./bundle.js"; @@ -19,18 +18,6 @@ export default class Entrypoints extends Component { jQuery.hotkeys.options.filterContentEditable = false; jQuery.hotkeys.options.filterTextInputs = false; } - - $(document).on('click', "a[data-action='note-revision']", async event => { - const linkEl = $(event.target); - const noteId = linkEl.attr('data-note-path'); - const noteRevisionId = linkEl.attr('data-note-revision-id'); - - const attributesDialog = await import("../dialogs/note_revisions.js"); - - attributesDialog.showNoteRevisionsDialog(noteId, noteRevisionId); - - return false; - }); } openDevToolsCommand() { diff --git a/src/public/app/services/note_tooltip.js b/src/public/app/services/note_tooltip.js index c3c974c27..77ec358d6 100644 --- a/src/public/app/services/note_tooltip.js +++ b/src/public/app/services/note_tooltip.js @@ -22,8 +22,7 @@ async function mouseEnterHandler() { const $link = $(this); if ($link.hasClass("no-tooltip-preview") - || $link.hasClass("disabled") - || $link.attr("data-action") === 'note-revision') { + || $link.hasClass("disabled")) { return; } diff --git a/src/public/app/services/root_command_executor.js b/src/public/app/services/root_command_executor.js index 4a275c278..bbaacc67b 100644 --- a/src/public/app/services/root_command_executor.js +++ b/src/public/app/services/root_command_executor.js @@ -8,14 +8,6 @@ import options from "./options.js"; import froca from "./froca.js"; export default class RootCommandExecutor extends Component { - showNoteRevisionsCommand() { - import("../dialogs/note_revisions.js").then(d => d.showCurrentNoteRevisions()); - } - - pasteMarkdownIntoTextCommand() { - import("../widgets/dialogs/markdown_import.js").then(d => d.importMarkdownInline()); - } - editReadOnlyNoteCommand() { const noteContext = appContext.tabManager.getActiveContext(); noteContext.readOnlyTemporarilyDisabled = true; diff --git a/src/public/app/widgets/dialogs/markdown_import.js b/src/public/app/widgets/dialogs/markdown_import.js index d6b514136..bdba6796d 100644 --- a/src/public/app/widgets/dialogs/markdown_import.js +++ b/src/public/app/widgets/dialogs/markdown_import.js @@ -64,6 +64,10 @@ export default class MarkdownImportDialog extends BasicWidget { toastService.showMessage("Markdown content has been imported into the document."); } + async pasteMarkdownIntoTextEvent() { + await this.importMarkdownInlineEvent(); // BC with keyboard shortcuts command + } + async importMarkdownInlineEvent() { if (appContext.tabManager.getActiveContextNoteType() !== 'text') { return; diff --git a/src/public/app/widgets/dialogs/note_revisions.js b/src/public/app/widgets/dialogs/note_revisions.js new file mode 100644 index 000000000..cda66558d --- /dev/null +++ b/src/public/app/widgets/dialogs/note_revisions.js @@ -0,0 +1,299 @@ +import utils from '../../services/utils.js'; +import server from '../../services/server.js'; +import toastService from "../../services/toast.js"; +import appContext from "../../services/app_context.js"; +import libraryLoader from "../../services/library_loader.js"; +import openService from "../../services/open.js"; +import protectedSessionHolder from "../../services/protected_session_holder.js"; +import BasicWidget from "../basic_widget.js"; + +const TPL = ` +`; + +export default class NoteRevisionsDialog extends BasicWidget { + constructor() { + super(); + + this.revisionItems = []; + this.note = null; + this.noteRevisionId = null; + } + + doRender() { + this.$widget = $(TPL); + this.$list = this.$widget.find(".note-revision-list"); + this.$listDropdown = this.$widget.find(".note-revision-list-dropdown"); + this.$content = this.$widget.find(".note-revision-content"); + this.$title = this.$widget.find(".note-revision-title"); + this.$titleButtons = this.$widget.find(".note-revision-title-buttons"); + this.$eraseAllRevisionsButton = this.$widget.find(".note-revisions-erase-all-revisions-button"); + + this.$listDropdown.dropdown(); + + this.$listDropdown.parent().on('hide.bs.dropdown', e => { + // prevent closing dropdown by clicking outside + if (e.clickEvent) { + e.preventDefault(); + } + }); + + this.$widget.on('shown.bs.modal', () => { + this.$list.find(`[data-note-revision-id="${this.noteRevisionId}"]`) + .trigger('focus'); + }); + + this.$eraseAllRevisionsButton.on('click', async () => { + const confirmDialog = await import('../../dialogs/confirm.js'); + const text = 'Do you want to delete all revisions of this note? This action will erase revision title and content, but still preserve revision metadata.'; + + if (await confirmDialog.confirm(text)) { + await server.remove(`notes/${this.note.noteId}/revisions`); + + this.$widget.modal('hide'); + + toastService.showMessage('Note revisions has been deleted.'); + } + }); + + this.$list.on('click', '.dropdown-item', e => { + e.preventDefault(); + return false; + }); + + this.$list.on('focus', '.dropdown-item', e => { + this.$list.find('.dropdown-item').each((i, el) => { + $(el).toggleClass('active', el === e.target); + }); + + this.setContentPane(); + }); + } + + async showNoteRevisionsEvent({noteId = appContext.tabManager.getActiveContextNoteId()}) { + utils.openDialog(this.$widget); + + await this.loadNoteRevisions(noteId); + } + + async loadNoteRevisions(noteId) { + this.$list.empty(); + this.$content.empty(); + this.$titleButtons.empty(); + + this.note = appContext.tabManager.getActiveContextNote(); + this.revisionItems = await server.get(`notes/${noteId}/revisions`); + + for (const item of this.revisionItems) { + this.$list.append( + $('') + .text(item.dateLastEdited.substr(0, 16) + ` (${item.contentLength} bytes)`) + .attr('data-note-revision-id', item.noteRevisionId) + .attr('title', 'This revision was last edited on ' + item.dateLastEdited) + ); + } + + this.$listDropdown.dropdown('show'); + + if (this.revisionItems.length > 0) { + if (!this.noteRevisionId) { + this.noteRevisionId = this.revisionItems[0].noteRevisionId; + } + } else { + this.$title.text("No revisions for this note yet..."); + this.noteRevisionId = null; + } + + this.$eraseAllRevisionsButton.toggle(this.revisionItems.length > 0); + } + + async setContentPane() { + const noteRevisionId = this.$list.find(".active").attr('data-note-revision-id'); + + const revisionItem = this.revisionItems.find(r => r.noteRevisionId === noteRevisionId); + + this.$titleButtons.empty(); + this.$content.empty(); + + this.$title.html(revisionItem.title); + + const $restoreRevisionButton = $(''); + + $restoreRevisionButton.on('click', async () => { + const confirmDialog = await import('../../dialogs/confirm.js'); + const text = 'Do you want to restore this revision? This will overwrite current title/content of the note with this revision.'; + + if (await confirmDialog.confirm(text)) { + await server.put(`notes/${revisionItem.noteId}/restore-revision/${revisionItem.noteRevisionId}`); + + this.$widget.modal('hide'); + + toastService.showMessage('Note revision has been restored.'); + } + }); + + const $eraseRevisionButton = $(''); + + $eraseRevisionButton.on('click', async () => { + const confirmDialog = await import('../../dialogs/confirm.js'); + const text = 'Do you want to delete this revision? This action will delete revision title and content, but still preserve revision metadata.'; + + if (await confirmDialog.confirm(text)) { + await server.remove(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`); + + this.loadNoteRevisions(revisionItem.noteId); + + toastService.showMessage('Note revision has been deleted.'); + } + }); + + if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) { + this.$titleButtons + .append($restoreRevisionButton) + .append('   '); + } + + this.$titleButtons + .append($eraseRevisionButton) + .append('   '); + + const $downloadButton = $(''); + + $downloadButton.on('click', () => openService.downloadNoteRevision(revisionItem.noteId, revisionItem.noteRevisionId)); + + if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) { + this.$titleButtons.append($downloadButton); + } + + const fullNoteRevision = await server.get(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`); + + if (revisionItem.type === 'text') { + this.$content.html(fullNoteRevision.content); + + if (this.$content.find('span.math-tex').length > 0) { + await libraryLoader.requireLibrary(libraryLoader.KATEX); + + renderMathInElement($content[0], {trust: true}); + } + } + else if (revisionItem.type === 'code') { + this.$content.html($("
").text(fullNoteRevision.content));
+        }
+        else if (revisionItem.type === 'image') {
+            this.$content.html($("")
+                // reason why we put this inline as base64 is that we do not want to let user to copy this
+                // as a URL to be used in a note. Instead if they copy and paste it into a note, it will be a uploaded as a new note
+                .attr("src", `data:${note.mime};base64,` + fullNoteRevision.content)
+                .css("max-width", "100%")
+                .css("max-height", "100%"));
+        }
+        else if (revisionItem.type === 'file') {
+            const $table = $("")
+                .append($("").append(
+                    $("").append(
+                    $("").append(
+                    $('
").text("MIME: "), + $("").text(revisionItem.mime) + )) + .append($("
").text("File size:"), + $("").text(revisionItem.contentLength + " bytes") + )); + + if (fullNoteRevision.content) { + $table.append($("
').append( + $('
').text("Preview:"), + $('
')
+                            .text(fullNoteRevision.content)
+                    )
+                ));
+            }
+
+            this.$content.html($table);
+        }
+        else if (revisionItem.type === 'canvas') {
+            /**
+             * FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
+             *        REMOVE external dependency!!!! This is defined in the svg in defs.style
+             */
+            const content = fullNoteRevision.content;
+
+            try {
+                const data = JSON.parse(content)
+                const svg = data.svg || "no svg present."
+
+                /**
+                 * maxWidth: 100% use full width of container but do not enlarge!
+                 * height:auto to ensure that height scales with width
+                 */
+                const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
+                this.$content.html($('
').append($svgHtml)); + } catch(err) { + console.error("error parsing fullNoteRevision.content as JSON", fullNoteRevision.content, err); + this.$content.html($("
").text("Error parsing content. Please check console.error() for more details.")); + } + } + else { + this.$content.text("Preview isn't available for this note type."); + } + } +} diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs index 51525ace4..f96ccdd22 100644 --- a/src/views/desktop.ejs +++ b/src/views/desktop.ejs @@ -17,7 +17,6 @@ -<%- include('dialogs/note_revisions.ejs') %> <%- include('dialogs/options.ejs') %> <%- include('dialogs/info.ejs') %> <%- include('dialogs/prompt.ejs') %> diff --git a/src/views/dialogs/note_revisions.ejs b/src/views/dialogs/note_revisions.ejs deleted file mode 100644 index 9462a7d9c..000000000 --- a/src/views/dialogs/note_revisions.ejs +++ /dev/null @@ -1,63 +0,0 @@ -