From 82ad4dc13ece3d9db6b64f704304140beb1f4776 Mon Sep 17 00:00:00 2001 From: Pratik Date: Fri, 3 Jan 2025 15:15:45 +0530 Subject: [PATCH] feat: add markdown editor toolbar --- wiki/public/js/editor.js | 143 +++++++++++++++++- wiki/public/js/render_wiki.js | 62 ++++---- wiki/public/scss/edit_wiki.scss | 56 ++++++- wiki/public/scss/wiki.scss | 18 ++- .../doctype/wiki_page/templates/editor.html | 34 ++++- 5 files changed, 264 insertions(+), 49 deletions(-) diff --git a/wiki/public/js/editor.js b/wiki/public/js/editor.js index bfba69fa..5e93cbdc 100644 --- a/wiki/public/js/editor.js +++ b/wiki/public/js/editor.js @@ -1,6 +1,6 @@ import * as Ace from "ace-builds"; -import "ace-builds/src-noconflict/theme-tomorrow_night"; import "ace-builds/src-noconflict/mode-markdown"; +import "ace-builds/src-noconflict/theme-tomorrow_night"; const editorContainer = document.getElementById("wiki-editor"); const previewContainer = $("#preview-container"); @@ -8,10 +8,10 @@ const previewToggleBtn = $("#toggle-btn"); const wikiTitleInput = $(".wiki-title-input"); const editWikiBtn = $(".edit-wiki-btn, .sidebar-edit-mode-btn"); const saveWikiPageBtn = document.querySelector( - '[data-wiki-button="saveWikiPage"]', + '[data-wiki-button="saveWikiPage"]' ); const draftWikiPageBtn = document.querySelector( - '[data-wiki-button="draftWikiPage"]', + '[data-wiki-button="draftWikiPage"]' ); let showPreview = false; @@ -125,7 +125,7 @@ editorContainer.addEventListener( e.preventDefault(); e.stopPropagation(); }, - 500, + 500 ); editorContainer.addEventListener("drop", function (e) { @@ -162,8 +162,141 @@ editorContainer.addEventListener("drop", function (e) { } editor.session.insert( editor.getCursorPosition(), - `![](${encodeURI(file_doc.file_url)})`, + `![](${encodeURI(file_doc.file_url)})` ); }, }); }); + +function insertMarkdown(type) { + const selection = editor.getSelectedText(); + let insertion = ""; + + switch (type) { + case "bold": + insertion = `**${selection || "bold text"}**`; + break; + case "italic": + insertion = `*${selection || "italic text"}*`; + break; + case "heading": + insertion = `\n# ${selection || "Heading"}`; + break; + case "quote": + insertion = `\n> ${selection || "Quote"}`; + break; + case "olist": + insertion = `\n1. ${selection || "List item"}`; + break; + case "ulist": + insertion = `\n* ${selection || "List item"}`; + break; + case "link": + insertion = `[${selection || "link text"}](url)`; + break; + case "image": + new frappe.ui.FileUploader({ + dialog_title: __("Insert Image in Markdown"), + doctype: this.doctype, + docname: this.docname, + frm: this.frm, + folder: "Home/Attachments", + allow_multiple: false, + restrictions: { + allowed_file_types: ["image/*"], + }, + on_success: (file_doc) => { + if (this.frm && !this.frm.is_new()) { + this.frm.attachments.attachment_uploaded(file_doc); + } + editor.session.insert( + editor.getCursorPosition(), + `\n![](${encodeURI(file_doc.file_url)})` + ); + }, + }); + break; + case "table": + insertion = `${selection}\n| Header 1 | Header 2 |\n| -------- | -------- |\n| Row 1 | Row 1 |\n| Row 2 | Row 2 |`; + break; + } + + editor.insert(insertion); + editor.focus(); +} + +const mdeBoldBtn = document.querySelector('[data-mde-button="bold"]'); +const mdeItalicBtn = document.querySelector('[data-mde-button="italic"]'); +const mdeHeadingBtn = document.querySelector('[data-mde-button="heading"]'); +const mdeQuoteBtn = document.querySelector('[data-mde-button="quote"]'); +const mdeOlistBtn = document.querySelector('[data-mde-button="olist"]'); +const mdeUlistBtn = document.querySelector('[data-mde-button="ulist"]'); +const mdeLinkBtn = document.querySelector('[data-mde-button="link"]'); +const mdeImageBtn = document.querySelector('[data-mde-button="image"]'); +const mdeTableBtn = document.querySelector('[data-mde-button="table"]'); + +mdeBoldBtn.addEventListener("click", () => insertMarkdown("bold")); +mdeItalicBtn.addEventListener("click", () => insertMarkdown("italic")); +mdeHeadingBtn.addEventListener("click", () => insertMarkdown("heading")); +mdeQuoteBtn.addEventListener("click", () => insertMarkdown("quote")); +mdeOlistBtn.addEventListener("click", () => insertMarkdown("olist")); +mdeUlistBtn.addEventListener("click", () => insertMarkdown("ulist")); +mdeLinkBtn.addEventListener("click", () => insertMarkdown("link")); +mdeImageBtn.addEventListener("click", () => insertMarkdown("image")); +mdeTableBtn.addEventListener("click", () => insertMarkdown("table")); + +editor.commands.addCommand({ + name: "bold", + bindKey: { win: "Ctrl-B", mac: "Command-B" }, + exec: () => insertMarkdown("bold"), + readOnly: false, +}); + +editor.commands.addCommand({ + name: "italic", + bindKey: { win: "Ctrl-I", mac: "Command-I" }, + exec: () => insertMarkdown("italic"), + readOnly: false, +}); + +editor.commands.addCommand({ + name: "heading", + bindKey: { win: "Ctrl-H", mac: "Command-H" }, + exec: () => insertMarkdown("heading"), + readOnly: false, +}); + +editor.commands.addCommand({ + name: "quote", + bindKey: { win: "Ctrl-Shift-.", mac: "Command-Shift-." }, + exec: () => insertMarkdown("quote"), + readOnly: false, +}); + +editor.commands.addCommand({ + name: "orderedList", + bindKey: { win: "Ctrl-Shift-7", mac: "Command-Shift-7" }, + exec: () => insertMarkdown("olist"), + readOnly: false, +}); + +editor.commands.addCommand({ + name: "unorderedList", + bindKey: { win: "Ctrl-Shift-8", mac: "Command-Shift-8" }, + exec: () => insertMarkdown("ulist"), + readOnly: false, +}); + +editor.commands.addCommand({ + name: "link", + bindKey: { win: "Ctrl-K", mac: "Command-K" }, + exec: () => insertMarkdown("link"), + readOnly: false, +}); + +editor.commands.addCommand({ + name: "image", + bindKey: { win: "Ctrl-P", mac: "Command-P" }, + exec: () => insertMarkdown("image"), + readOnly: false, +}); diff --git a/wiki/public/js/render_wiki.js b/wiki/public/js/render_wiki.js index ac6d5f70..f32aac0f 100644 --- a/wiki/public/js/render_wiki.js +++ b/wiki/public/js/render_wiki.js @@ -121,16 +121,14 @@ window.RenderWiki = class RenderWiki extends Wiki { if (urlParams.get("editWiki") && $(".wiki-options").length) { toggleEditor(); - $("html").css({ overflow: "hidden" }); } else if (urlParams.get("newWiki")) { toggleEditor(); - $("html").css({ overflow: "hidden" }); if ( !$( `.doc-sidebar .sidebar-group[data-title="${urlParams.get( - "newWiki", - )}"] .add-sidebar-page`, + "newWiki" + )}"] .add-sidebar-page` ).length ) { this.add_wiki_sidebar(urlParams.get("newWiki")); @@ -138,17 +136,17 @@ window.RenderWiki = class RenderWiki extends Wiki { $( $( `.sidebar-items > .list-unstyled .h6:contains(${urlParams.get( - "newWiki", - )}) + .add-sidebar-page`, - )[0], + "newWiki" + )}) + .add-sidebar-page` + )[0] ).trigger("click"); } else $( $( `.sidebar-items > .list-unstyled .h6:contains(${urlParams.get( - "newWiki", - )}) + .add-sidebar-page`, - )[1], + "newWiki" + )}) + .add-sidebar-page` + )[1] ).trigger("click"); } $(".wiki-footer, .wiki-page-meta").toggleClass("hide"); @@ -171,7 +169,7 @@ window.RenderWiki = class RenderWiki extends Wiki { { scrollTop: offset, }, - 100, + 100 ); }); }); @@ -260,7 +258,7 @@ window.RenderWiki = class RenderWiki extends Wiki { $(".edit-wiki-btn, .sidebar-edit-mode-btn").on("click", function () { if (frappe.session.user === "Guest") window.location.assign( - `/login?redirect-to=${window.location.pathname}`, + `/login?redirect-to=${window.location.pathname}` ); else { const urlParams = new URLSearchParams(window.location.search); @@ -285,7 +283,7 @@ window.RenderWiki = class RenderWiki extends Wiki { const groupName = $(".sidebar-item.active").data("group-name"); $(".edit-wiki-btn").trigger("click"); $( - `.doc-sidebar .add-sidebar-page[data-group-name="${groupName}"]`, + `.doc-sidebar .add-sidebar-page[data-group-name="${groupName}"]` ).trigger("click"); }); @@ -310,7 +308,7 @@ window.RenderWiki = class RenderWiki extends Wiki { if (newWikiPage.data("group-name") !== groupName) { // when new item is created in a different group as earlier newSidebarItem.appendTo( - $(this).parent().parent().children(".list-unstyled"), + $(this).parent().parent().children(".list-unstyled") ); if (urlParams.get("newWiki") !== groupName) set_search_params("newWiki", groupName); @@ -327,11 +325,11 @@ window.RenderWiki = class RenderWiki extends Wiki { } else { // fresh new item active_items = $( - ".sidebar-item.active, .sidebar-item.active .active", + ".sidebar-item.active, .sidebar-item.active .active" ).removeClass("active"); newSidebarItem.appendTo( - $(this).parent().parent().children(".list-unstyled"), + $(this).parent().parent().children(".list-unstyled") ); if (!$(".wiki-editor").is(":visible")) toggleEditor(); if (urlParams.get("newWiki") !== groupName) @@ -340,7 +338,7 @@ window.RenderWiki = class RenderWiki extends Wiki { $(this).parent().parent().each(setSortable); e.stopPropagation(); - }, + } ); } @@ -369,7 +367,7 @@ window.RenderWiki = class RenderWiki extends Wiki { title: __("Delete Wiki Page"), indicator: "red", message: __( - `Are you sure you want to delete the Wiki Page ${title}?`, + `Are you sure you want to delete the Wiki Page ${title}?` ), primary_action: { label: "Yes", @@ -395,7 +393,7 @@ window.RenderWiki = class RenderWiki extends Wiki { }, }, }); - }, + } ); } @@ -410,7 +408,7 @@ window.RenderWiki = class RenderWiki extends Wiki { $(".revision-content").html(), $(".from-markdown .wiki-content") .html() - .replaceAll(/
/g, ""), + .replaceAll(/
/g, "") ); $(".previous-revision").removeClass("hide"); } else { @@ -452,11 +450,12 @@ window.RenderWiki = class RenderWiki extends Wiki { if (previousRevision.content) $(".revision-content")[0].innerHTML = HtmlDiff.execute( previousRevision.content, - currentRevision.content, + currentRevision.content ); else $(".revision-content")[0].innerHTML = currentRevision.content; - $(".revision-time")[0].innerHTML = - `${currentRevision.author} edited ${currentRevision.revision_time}`; + $( + ".revision-time" + )[0].innerHTML = `${currentRevision.author} edited ${currentRevision.revision_time}`; currentRevisionIndex++; addHljsClass(); }); @@ -473,10 +472,11 @@ window.RenderWiki = class RenderWiki extends Wiki { $(".previous-revision").removeClass("hide"); $(".revision-content")[0].innerHTML = HtmlDiff.execute( nextRevision.content, - currentRevision.content, + currentRevision.content ); - $(".revision-time")[0].innerHTML = - `${currentRevision.author} edited ${currentRevision.revision_time}`; + $( + ".revision-time" + )[0].innerHTML = `${currentRevision.author} edited ${currentRevision.revision_time}`; currentRevisionIndex--; addHljsClass(); }); @@ -505,7 +505,7 @@ window.RenderWiki = class RenderWiki extends Wiki { $(".doc-sidebar .sidebar-items") .children(".list-unstyled") .not(".hidden") - .first(), + .first() ); $(".web-sidebar ul").each(setSortable); @@ -537,7 +537,7 @@ window.RenderWiki = class RenderWiki extends Wiki { $(this) .parent() .append( - $(`