Skip to content

Commit f0ca7ca

Browse files
authored
Merge pull request #309 from pratikb64/add-markdown-editor
feat: add markdown editor toolbar
2 parents d4a22f8 + 82ad4dc commit f0ca7ca

File tree

5 files changed

+264
-49
lines changed

5 files changed

+264
-49
lines changed

wiki/public/js/editor.js

+138-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import * as Ace from "ace-builds";
2-
import "ace-builds/src-noconflict/theme-tomorrow_night";
32
import "ace-builds/src-noconflict/mode-markdown";
3+
import "ace-builds/src-noconflict/theme-tomorrow_night";
44

55
const editorContainer = document.getElementById("wiki-editor");
66
const previewContainer = $("#preview-container");
77
const previewToggleBtn = $("#toggle-btn");
88
const wikiTitleInput = $(".wiki-title-input");
99
const editWikiBtn = $(".edit-wiki-btn, .sidebar-edit-mode-btn");
1010
const saveWikiPageBtn = document.querySelector(
11-
'[data-wiki-button="saveWikiPage"]',
11+
'[data-wiki-button="saveWikiPage"]'
1212
);
1313
const draftWikiPageBtn = document.querySelector(
14-
'[data-wiki-button="draftWikiPage"]',
14+
'[data-wiki-button="draftWikiPage"]'
1515
);
1616
let showPreview = false;
1717

@@ -125,7 +125,7 @@ editorContainer.addEventListener(
125125
e.preventDefault();
126126
e.stopPropagation();
127127
},
128-
500,
128+
500
129129
);
130130

131131
editorContainer.addEventListener("drop", function (e) {
@@ -162,8 +162,141 @@ editorContainer.addEventListener("drop", function (e) {
162162
}
163163
editor.session.insert(
164164
editor.getCursorPosition(),
165-
`![](${encodeURI(file_doc.file_url)})`,
165+
`![](${encodeURI(file_doc.file_url)})`
166166
);
167167
},
168168
});
169169
});
170+
171+
function insertMarkdown(type) {
172+
const selection = editor.getSelectedText();
173+
let insertion = "";
174+
175+
switch (type) {
176+
case "bold":
177+
insertion = `**${selection || "bold text"}**`;
178+
break;
179+
case "italic":
180+
insertion = `*${selection || "italic text"}*`;
181+
break;
182+
case "heading":
183+
insertion = `\n# ${selection || "Heading"}`;
184+
break;
185+
case "quote":
186+
insertion = `\n> ${selection || "Quote"}`;
187+
break;
188+
case "olist":
189+
insertion = `\n1. ${selection || "List item"}`;
190+
break;
191+
case "ulist":
192+
insertion = `\n* ${selection || "List item"}`;
193+
break;
194+
case "link":
195+
insertion = `[${selection || "link text"}](url)`;
196+
break;
197+
case "image":
198+
new frappe.ui.FileUploader({
199+
dialog_title: __("Insert Image in Markdown"),
200+
doctype: this.doctype,
201+
docname: this.docname,
202+
frm: this.frm,
203+
folder: "Home/Attachments",
204+
allow_multiple: false,
205+
restrictions: {
206+
allowed_file_types: ["image/*"],
207+
},
208+
on_success: (file_doc) => {
209+
if (this.frm && !this.frm.is_new()) {
210+
this.frm.attachments.attachment_uploaded(file_doc);
211+
}
212+
editor.session.insert(
213+
editor.getCursorPosition(),
214+
`\n![](${encodeURI(file_doc.file_url)})`
215+
);
216+
},
217+
});
218+
break;
219+
case "table":
220+
insertion = `${selection}\n| Header 1 | Header 2 |\n| -------- | -------- |\n| Row 1 | Row 1 |\n| Row 2 | Row 2 |`;
221+
break;
222+
}
223+
224+
editor.insert(insertion);
225+
editor.focus();
226+
}
227+
228+
const mdeBoldBtn = document.querySelector('[data-mde-button="bold"]');
229+
const mdeItalicBtn = document.querySelector('[data-mde-button="italic"]');
230+
const mdeHeadingBtn = document.querySelector('[data-mde-button="heading"]');
231+
const mdeQuoteBtn = document.querySelector('[data-mde-button="quote"]');
232+
const mdeOlistBtn = document.querySelector('[data-mde-button="olist"]');
233+
const mdeUlistBtn = document.querySelector('[data-mde-button="ulist"]');
234+
const mdeLinkBtn = document.querySelector('[data-mde-button="link"]');
235+
const mdeImageBtn = document.querySelector('[data-mde-button="image"]');
236+
const mdeTableBtn = document.querySelector('[data-mde-button="table"]');
237+
238+
mdeBoldBtn.addEventListener("click", () => insertMarkdown("bold"));
239+
mdeItalicBtn.addEventListener("click", () => insertMarkdown("italic"));
240+
mdeHeadingBtn.addEventListener("click", () => insertMarkdown("heading"));
241+
mdeQuoteBtn.addEventListener("click", () => insertMarkdown("quote"));
242+
mdeOlistBtn.addEventListener("click", () => insertMarkdown("olist"));
243+
mdeUlistBtn.addEventListener("click", () => insertMarkdown("ulist"));
244+
mdeLinkBtn.addEventListener("click", () => insertMarkdown("link"));
245+
mdeImageBtn.addEventListener("click", () => insertMarkdown("image"));
246+
mdeTableBtn.addEventListener("click", () => insertMarkdown("table"));
247+
248+
editor.commands.addCommand({
249+
name: "bold",
250+
bindKey: { win: "Ctrl-B", mac: "Command-B" },
251+
exec: () => insertMarkdown("bold"),
252+
readOnly: false,
253+
});
254+
255+
editor.commands.addCommand({
256+
name: "italic",
257+
bindKey: { win: "Ctrl-I", mac: "Command-I" },
258+
exec: () => insertMarkdown("italic"),
259+
readOnly: false,
260+
});
261+
262+
editor.commands.addCommand({
263+
name: "heading",
264+
bindKey: { win: "Ctrl-H", mac: "Command-H" },
265+
exec: () => insertMarkdown("heading"),
266+
readOnly: false,
267+
});
268+
269+
editor.commands.addCommand({
270+
name: "quote",
271+
bindKey: { win: "Ctrl-Shift-.", mac: "Command-Shift-." },
272+
exec: () => insertMarkdown("quote"),
273+
readOnly: false,
274+
});
275+
276+
editor.commands.addCommand({
277+
name: "orderedList",
278+
bindKey: { win: "Ctrl-Shift-7", mac: "Command-Shift-7" },
279+
exec: () => insertMarkdown("olist"),
280+
readOnly: false,
281+
});
282+
283+
editor.commands.addCommand({
284+
name: "unorderedList",
285+
bindKey: { win: "Ctrl-Shift-8", mac: "Command-Shift-8" },
286+
exec: () => insertMarkdown("ulist"),
287+
readOnly: false,
288+
});
289+
290+
editor.commands.addCommand({
291+
name: "link",
292+
bindKey: { win: "Ctrl-K", mac: "Command-K" },
293+
exec: () => insertMarkdown("link"),
294+
readOnly: false,
295+
});
296+
297+
editor.commands.addCommand({
298+
name: "image",
299+
bindKey: { win: "Ctrl-P", mac: "Command-P" },
300+
exec: () => insertMarkdown("image"),
301+
readOnly: false,
302+
});

wiki/public/js/render_wiki.js

+31-31
Original file line numberDiff line numberDiff line change
@@ -121,34 +121,32 @@ window.RenderWiki = class RenderWiki extends Wiki {
121121

122122
if (urlParams.get("editWiki") && $(".wiki-options").length) {
123123
toggleEditor();
124-
$("html").css({ overflow: "hidden" });
125124
} else if (urlParams.get("newWiki")) {
126125
toggleEditor();
127-
$("html").css({ overflow: "hidden" });
128126

129127
if (
130128
!$(
131129
`.doc-sidebar .sidebar-group[data-title="${urlParams.get(
132-
"newWiki",
133-
)}"] .add-sidebar-page`,
130+
"newWiki"
131+
)}"] .add-sidebar-page`
134132
).length
135133
) {
136134
this.add_wiki_sidebar(urlParams.get("newWiki"));
137135

138136
$(
139137
$(
140138
`.sidebar-items > .list-unstyled .h6:contains(${urlParams.get(
141-
"newWiki",
142-
)}) + .add-sidebar-page`,
143-
)[0],
139+
"newWiki"
140+
)}) + .add-sidebar-page`
141+
)[0]
144142
).trigger("click");
145143
} else
146144
$(
147145
$(
148146
`.sidebar-items > .list-unstyled .h6:contains(${urlParams.get(
149-
"newWiki",
150-
)}) + .add-sidebar-page`,
151-
)[1],
147+
"newWiki"
148+
)}) + .add-sidebar-page`
149+
)[1]
152150
).trigger("click");
153151
}
154152
$(".wiki-footer, .wiki-page-meta").toggleClass("hide");
@@ -171,7 +169,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
171169
{
172170
scrollTop: offset,
173171
},
174-
100,
172+
100
175173
);
176174
});
177175
});
@@ -260,7 +258,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
260258
$(".edit-wiki-btn, .sidebar-edit-mode-btn").on("click", function () {
261259
if (frappe.session.user === "Guest")
262260
window.location.assign(
263-
`/login?redirect-to=${window.location.pathname}`,
261+
`/login?redirect-to=${window.location.pathname}`
264262
);
265263
else {
266264
const urlParams = new URLSearchParams(window.location.search);
@@ -285,7 +283,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
285283
const groupName = $(".sidebar-item.active").data("group-name");
286284
$(".edit-wiki-btn").trigger("click");
287285
$(
288-
`.doc-sidebar .add-sidebar-page[data-group-name="${groupName}"]`,
286+
`.doc-sidebar .add-sidebar-page[data-group-name="${groupName}"]`
289287
).trigger("click");
290288
});
291289

@@ -310,7 +308,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
310308
if (newWikiPage.data("group-name") !== groupName) {
311309
// when new item is created in a different group as earlier
312310
newSidebarItem.appendTo(
313-
$(this).parent().parent().children(".list-unstyled"),
311+
$(this).parent().parent().children(".list-unstyled")
314312
);
315313
if (urlParams.get("newWiki") !== groupName)
316314
set_search_params("newWiki", groupName);
@@ -327,11 +325,11 @@ window.RenderWiki = class RenderWiki extends Wiki {
327325
} else {
328326
// fresh new item
329327
active_items = $(
330-
".sidebar-item.active, .sidebar-item.active .active",
328+
".sidebar-item.active, .sidebar-item.active .active"
331329
).removeClass("active");
332330

333331
newSidebarItem.appendTo(
334-
$(this).parent().parent().children(".list-unstyled"),
332+
$(this).parent().parent().children(".list-unstyled")
335333
);
336334
if (!$(".wiki-editor").is(":visible")) toggleEditor();
337335
if (urlParams.get("newWiki") !== groupName)
@@ -340,7 +338,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
340338

341339
$(this).parent().parent().each(setSortable);
342340
e.stopPropagation();
343-
},
341+
}
344342
);
345343
}
346344

@@ -369,7 +367,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
369367
title: __("Delete Wiki Page"),
370368
indicator: "red",
371369
message: __(
372-
`Are you sure you want to <b>delete</b> the Wiki Page <b>${title}</b>?`,
370+
`Are you sure you want to <b>delete</b> the Wiki Page <b>${title}</b>?`
373371
),
374372
primary_action: {
375373
label: "Yes",
@@ -395,7 +393,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
395393
},
396394
},
397395
});
398-
},
396+
}
399397
);
400398
}
401399

@@ -410,7 +408,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
410408
$(".revision-content").html(),
411409
$(".from-markdown .wiki-content")
412410
.html()
413-
.replaceAll(/<br class="ProseMirror-trailingBreak">/g, ""),
411+
.replaceAll(/<br class="ProseMirror-trailingBreak">/g, "")
414412
);
415413
$(".previous-revision").removeClass("hide");
416414
} else {
@@ -452,11 +450,12 @@ window.RenderWiki = class RenderWiki extends Wiki {
452450
if (previousRevision.content)
453451
$(".revision-content")[0].innerHTML = HtmlDiff.execute(
454452
previousRevision.content,
455-
currentRevision.content,
453+
currentRevision.content
456454
);
457455
else $(".revision-content")[0].innerHTML = currentRevision.content;
458-
$(".revision-time")[0].innerHTML =
459-
`${currentRevision.author} edited ${currentRevision.revision_time}`;
456+
$(
457+
".revision-time"
458+
)[0].innerHTML = `${currentRevision.author} edited ${currentRevision.revision_time}`;
460459
currentRevisionIndex++;
461460
addHljsClass();
462461
});
@@ -473,10 +472,11 @@ window.RenderWiki = class RenderWiki extends Wiki {
473472
$(".previous-revision").removeClass("hide");
474473
$(".revision-content")[0].innerHTML = HtmlDiff.execute(
475474
nextRevision.content,
476-
currentRevision.content,
475+
currentRevision.content
477476
);
478-
$(".revision-time")[0].innerHTML =
479-
`${currentRevision.author} edited ${currentRevision.revision_time}`;
477+
$(
478+
".revision-time"
479+
)[0].innerHTML = `${currentRevision.author} edited ${currentRevision.revision_time}`;
480480
currentRevisionIndex--;
481481
addHljsClass();
482482
});
@@ -505,7 +505,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
505505
$(".doc-sidebar .sidebar-items")
506506
.children(".list-unstyled")
507507
.not(".hidden")
508-
.first(),
508+
.first()
509509
);
510510

511511
$(".web-sidebar ul").each(setSortable);
@@ -537,7 +537,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
537537
$(this)
538538
.parent()
539539
.append(
540-
$(`<ul class="list-unstyled" style="min-height:20px;"> </ul`),
540+
$(`<ul class="list-unstyled" style="min-height:20px;"> </ul`)
541541
);
542542
}
543543
});
@@ -568,7 +568,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
568568
// fixes html tags when they are sliced
569569
return new DOMParser().parseFromString(
570570
content.slice(start, end),
571-
"text/html",
571+
"text/html"
572572
).body.innerHTML;
573573
}
574574

@@ -630,7 +630,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
630630
$dropdown_menu.addClass("show");
631631
dropdownItems = $dropdown_menu.find(".dropdown-item");
632632
});
633-
}, 500),
633+
}, 500)
634634
);
635635

636636
$("#dropdownMenuSearch, .mobile-search-icon").on("click", () => {
@@ -726,7 +726,7 @@ window.RenderWiki = class RenderWiki extends Wiki {
726726
$(".update-page-settings-button").on("click", function () {
727727
const name = $('[name="wiki-page-name"]').val();
728728
const hideOnSidebar = $('input[name="pageHideOnSidebar"]').prop(
729-
"checked",
729+
"checked"
730730
);
731731
const route =
732732
$(".wiki-space-route-block").text().trim() +

0 commit comments

Comments
 (0)