|
|
|
/*
|
|
|
|
* 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 = "<i class=\"" + buttons[i]['icon'] + " fa-fw\"></i>";
|
|
|
|
}
|
|
|
|
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: '<i class="fas fa-sync fa-spin"></i> 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();
|
|
|
|
var append = "";
|
|
|
|
if (noteid != "") {
|
|
|
|
note = NOTES.get(noteid);
|
|
|
|
if (note.getSyncStatus() != "LOCAL_ONLY") {
|
|
|
|
note.setSyncStatus("LOCAL_EDITED");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var tempuuid = Math.random() * 100000000000000000;
|
|
|
|
append = " <!-- MobileNewNoteSaveTempID:" + tempuuid + " -->";
|
|
|
|
}
|
|
|
|
note.setText($("#note").val() + append);
|
|
|
|
note.setModified();
|
|
|
|
NOTES.set(note);
|
|
|
|
NOTES.syncAll(function () {
|
|
|
|
app.toast.create({
|
|
|
|
text: '<i class="fas fa-check"></i> Note saved.',
|
|
|
|
closeTimeout: 3000
|
|
|
|
}).open();
|
|
|
|
$("#orignote").val(note.content);
|
|
|
|
if (append != "") {
|
|
|
|
for (n in NOTES.notes) {
|
|
|
|
if (NOTES.notes[n].getText().endsWith(append)) {
|
|
|
|
$("#note").data("noteid", NOTES.notes[n].noteid);
|
|
|
|
NOTES.notes[n].setText(NOTES.notes[n].getText().replace(append, ""));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
save_in_progress = false;
|
|
|
|
if (typeof callback == "function") {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
}, function () {
|
|
|
|
app.toast.create({
|
|
|
|
text: '<i class="fas fa-save"></i> Note saved locally.',
|
|
|
|
closeTimeout: 3000
|
|
|
|
}).open();
|
|
|
|
$("#orignote").val(note.content);
|
|
|
|
if (append != "") {
|
|
|
|
for (n in NOTES.notes) {
|
|
|
|
if (NOTES.notes[n].getText().endsWith(append)) {
|
|
|
|
$("#note").data("noteid", NOTES.notes[n].noteid);
|
|
|
|
NOTES.notes[n].setText(NOTES.notes[n].getText().replace(append, ""));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|