/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ class MarkdownEditor { constructor(textarea, toolbar, buttons) { this.textarea = textarea; this.toolbar = toolbar; if (typeof buttons != "undefined") { for (var i = 0; i < buttons.length; i++) { var div = document.createElement('div'); if (buttons[i]['spacer']) { continue; // skip spacers div.className = "toolbar-spacer"; div.innerHTML = "|"; } else { div.className = "link toolbar-button"; if (typeof buttons[i]['linestart'] == "string") { div.setAttribute("data-linestart", buttons[i]['linestart']); div.setAttribute("data-padspace", buttons[i]['padspace'] ? 1 : 0); div.setAttribute("data-maximum", buttons[i]['maximum']); } else { div.setAttribute("data-before", buttons[i]['before']); div.setAttribute("data-after", buttons[i]['after']); } div.innerHTML = ""; } toolbar.appendChild(div); } } var btns = toolbar.getElementsByClassName("toolbar-button"); var editorobj = this; for (var i = 0; i < btns.length; i++) { btns[i].addEventListener('click', function () { if (editorobj.textarea.value.length == editorobj.getCursorPosition()[0] && editorobj.textarea.value.length == editorobj.getCursorPosition()[1]) { //return; } if (typeof this.dataset.linestart == "string") { editorobj.formatline(this.dataset.linestart, this.dataset.padspace, this.dataset.maximum); } else { editorobj.format(this.dataset.before, this.dataset.after); } editorobj.textarea.focus(); }, false); } } formatline(start, padspace, maximum) { var text = this.getText(); var cursor = this.getCursorPosition(); var startofline = cursor[0]; while (startofline - 1 > 0 && text[startofline - 1] != "\n") { startofline--; } console.log("startofline", startofline, text[startofline - 1]); // Check if the line is already formatted and count how many times var falsestart = startofline; var count = 0; while (falsestart != text.length && text.slice(falsestart, falsestart + start.length) == start) { falsestart += start.length; count++; } console.log("falsestart", falsestart); // Check if we're allowed to add another formatting mark if (count > 0) { if (maximum <= count) { return; } } // If it's the first one and a space is needed after if (count == 0 && padspace == 1) { start += " "; } text = [text.slice(0, startofline), start, text.slice(startofline)].join(''); this.textarea.value = text; this.textarea.selectionStart = cursor[0] + start.length; this.textarea.selectionEnd = cursor[0] + start.length; } format(before, after) { var text = this.getText(); var cursor = this.getCursorPosition(); var origcursor = this.getCursorPosition(); if (cursor[0] == cursor[1]) { // Walk backwards and forwards to get the whole word while (cursor[0] - 1 >= 0 && text[cursor[0] - 1] != " " && text[cursor[0] - 1] != "\n") { cursor[0]--; } while (cursor[1] < text.length && text[cursor[1]] != " " && text[cursor[1]] != "\n") { cursor[1]++; } } var subject = text.substring(cursor[0], cursor[1]); if (subject.startsWith(before) && subject.endsWith(after)) { // Don't do anything return; } if (subject == "") { subject = " "; origcursor[0]++; origcursor[1]++; } subject = before + subject + after; text = [text.slice(0, cursor[0]), subject, text.slice(cursor[1])].join(''); this.textarea.value = text; this.textarea.selectionStart = origcursor[0] + before.length; this.textarea.selectionEnd = origcursor[1] + before.length; } getCursorPosition() { var start = this.textarea.selectionStart; var end = this.textarea.selectionEnd; console.log([start, end], this.textarea.value.length); return [start, end]; } /** * Get the textarea value, with an extra newline if needed. * @returns string */ getText() { var text = this.textarea.value; // if (text[text.length - 1] != "\n") { // text += "\n"; // } return text; } } var editor; function initEditor() { editor = new MarkdownEditor( document.getElementById('note'), document.getElementById("editor-toolbar"), [ { label: "Bold", icon: "fas fa-bold", before: "**", after: "**" }, { label: "Italic", icon: "fas fa-italic", before: "*", after: "*" }, { label: "Heading", icon: "fas fa-heading", linestart: "#", padspace: true, maximum: 6 }, { label: "Strikethrough", icon: "fas fa-strikethrough", before: "~~", after: "~~" }, { spacer: true }, { label: "Quote", icon: "fas fa-quote-left", linestart: "> ", padspace: false, maximum: 6 }, { label: "Checklist", icon: "fas fa-tasks", linestart: "- [ ]", padspace: true, maximum: 1 }, { label: "List", icon: "fas fa-list-ul", linestart: "-", padspace: true, maximum: 1 } ]); document.getElementById('note').addEventListener('keydown', function (event) { if (event.ctrlKey || event.metaKey) { switch (String.fromCharCode(event.which).toLowerCase()) { case 's': event.preventDefault(); savenote(); break; } } }); } function getMarkdown() { return document.getElementById('note').value; } var save_in_progress = false; function savenote(callback) { if (save_in_progress) { console.log("Warning: save already in progress, doing nothing."); return; } app.toast.create({ text: '   Saving...', closeTimeout: 2 * 60 * 1000 // two whole minutes should be enough for *any* connection. }).open(); save_in_progress = true; var noteid = $("#note").data("noteid"); var note = new Note(); if (noteid != "") { note = NOTES.get(noteid); if (note.getSyncStatus() != "LOCAL_ONLY") { note.setSyncStatus("LOCAL_EDITED"); } } note.setText($("#note").val()); note.setModified(); NOTES.set(note); NOTES.syncAll(function () { app.toast.create({ text: '   Note saved.', closeTimeout: 3000 }).open(); $("#orignote").val(note.content); save_in_progress = false; if (typeof callback == "function") { callback(); } }, function () { app.toast.create({ text: '   Note saved locally.', closeTimeout: 3000 }).open(); $("#orignote").val(note.content); save_in_progress = false; if (typeof callback == "function") { callback(); } }); } function exiteditor() { if ($("#note").val() == "" || $("#note").val() === $("#orignote").val()) { router.back({force: true, ignoreCache: true, reload: true}); } else { savenote(function () { router.back({force: true, ignoreCache: true, reload: true}); }); } }