2020-03-10 22:11:32 +00:00
|
|
|
// class MarkdownView {
|
|
|
|
// constructor(target, content) {
|
|
|
|
// this.textarea = target.appendChild(document.createElement("textarea"))
|
|
|
|
// this.textarea.value = content
|
|
|
|
// }
|
|
|
|
|
|
|
|
// get content() { return this.textarea.value }
|
|
|
|
// focus() { this.textarea.focus() }
|
|
|
|
// destroy() { this.textarea.remove() }
|
|
|
|
// }
|
|
|
|
|
2021-01-07 23:33:35 +00:00
|
|
|
import { EditorView } from "prosemirror-view";
|
|
|
|
import { EditorState, TextSelection } from "prosemirror-state";
|
|
|
|
import { exampleSetup } from "prosemirror-example-setup";
|
2021-01-04 16:19:24 +00:00
|
|
|
import { keymap } from "prosemirror-keymap";
|
|
|
|
|
2021-01-07 23:33:35 +00:00
|
|
|
import { writeAsMarkdownParser } from "./markdownParser";
|
|
|
|
import { writeAsMarkdownSerializer } from "./markdownSerializer";
|
|
|
|
import { writeFreelySchema } from "./schema";
|
|
|
|
import { getMenu } from "./menu";
|
2020-03-10 22:11:32 +00:00
|
|
|
|
2021-01-07 23:33:35 +00:00
|
|
|
let $title = document.querySelector("#title");
|
|
|
|
let $content = document.querySelector("#content");
|
2020-09-09 21:46:47 +00:00
|
|
|
|
2021-01-07 23:33:35 +00:00
|
|
|
// Bugs:
|
2021-03-03 19:29:23 +00:00
|
|
|
// 1. When there's just an empty line and a hard break is inserted with shift-enter then two enters are inserted
|
2021-01-07 23:33:35 +00:00
|
|
|
// which do not show up in the markdown ( maybe bc. they are training enters )
|
2021-01-04 16:19:24 +00:00
|
|
|
|
2021-01-07 23:33:35 +00:00
|
|
|
class ProseMirrorView {
|
|
|
|
constructor(target, content) {
|
2021-01-08 00:41:36 +00:00
|
|
|
let typingTimer;
|
2021-01-07 23:33:35 +00:00
|
|
|
let localDraft = localStorage.getItem(window.draftKey);
|
|
|
|
if (localDraft != null) {
|
|
|
|
content = localDraft;
|
2020-09-09 14:02:00 +00:00
|
|
|
}
|
2021-01-07 23:33:35 +00:00
|
|
|
if (content.indexOf("# ") === 0) {
|
|
|
|
let eol = content.indexOf("\n");
|
|
|
|
let title = content.substring("# ".length, eol);
|
|
|
|
content = content.substring(eol + "\n\n".length);
|
|
|
|
$title.value = title;
|
2020-09-09 14:02:00 +00:00
|
|
|
}
|
2020-03-10 22:11:32 +00:00
|
|
|
|
2021-01-07 23:33:35 +00:00
|
|
|
const doc = writeAsMarkdownParser.parse(
|
|
|
|
// Replace all "solo" \n's with \\\n for correct markdown parsing
|
2021-03-03 19:29:23 +00:00
|
|
|
// Can't use lookahead or lookbehind because it's not supported on Safari
|
|
|
|
content.replaceAll(/([^]{0,1})(\n)([^]{0,1})/g, (match, p1, p2, p3) => {
|
|
|
|
return p1 !== "\n" && p3 !== "\n" ? p1 + "\\\n" + p3 : match;
|
|
|
|
})
|
2021-01-07 23:33:35 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
this.view = new EditorView(target, {
|
|
|
|
state: EditorState.create({
|
|
|
|
doc,
|
|
|
|
plugins: [
|
|
|
|
keymap({
|
|
|
|
"Mod-Enter": () => {
|
|
|
|
document.getElementById("publish").click();
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
"Mod-k": () => {
|
2021-03-03 19:29:23 +00:00
|
|
|
const linkButton = document.querySelector(
|
|
|
|
".ProseMirror-icon[title='Add or remove link']"
|
|
|
|
);
|
|
|
|
linkButton.dispatchEvent(new Event("mousedown"));
|
2021-01-07 23:33:35 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
...exampleSetup({
|
|
|
|
schema: writeFreelySchema,
|
|
|
|
menuContent: getMenu(),
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
dispatchTransaction(transaction) {
|
2021-01-08 00:41:36 +00:00
|
|
|
let newState = this.state.apply(transaction);
|
2021-01-07 23:33:35 +00:00
|
|
|
const newContent = writeAsMarkdownSerializer
|
2021-01-08 00:41:36 +00:00
|
|
|
.serialize(newState.doc)
|
2021-01-07 23:33:35 +00:00
|
|
|
// Replace all \\\ns ( not followed by a \n ) with \n
|
2021-03-03 19:29:23 +00:00
|
|
|
.replaceAll(/(\\\n)(\n{0,1})/g, (match, p1, p2) =>
|
|
|
|
p2 !== "\n" ? "\n" + p2 : match
|
|
|
|
);
|
2021-01-07 23:33:35 +00:00
|
|
|
$content.value = newContent;
|
|
|
|
let draft = "";
|
|
|
|
if ($title.value != null && $title.value !== "") {
|
|
|
|
draft = "# " + $title.value + "\n\n";
|
|
|
|
}
|
|
|
|
draft += newContent;
|
2021-01-08 00:41:36 +00:00
|
|
|
clearTimeout(typingTimer);
|
2021-03-03 19:29:23 +00:00
|
|
|
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
2021-01-07 23:33:35 +00:00
|
|
|
this.updateState(newState);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
// Editor is focused to the last position. This is a workaround for a bug:
|
|
|
|
// 1. 1 type something in an existing entry
|
|
|
|
// 2. reload - works fine, the draft is reloaded
|
|
|
|
// 3. reload again - the draft is somehow removed from localStorage and the original content is loaded
|
|
|
|
// When the editor is focused the content is re-saved to localStorage
|
|
|
|
|
|
|
|
// This is also useful for editing, so it's not a bad thing even
|
|
|
|
const lastPosition = this.view.state.doc.content.size;
|
|
|
|
const selection = TextSelection.create(this.view.state.doc, lastPosition);
|
|
|
|
this.view.dispatch(this.view.state.tr.setSelection(selection));
|
|
|
|
this.view.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
get content() {
|
|
|
|
return defaultMarkdownSerializer.serialize(this.view.state.doc);
|
|
|
|
}
|
|
|
|
focus() {
|
|
|
|
this.view.focus();
|
|
|
|
}
|
|
|
|
destroy() {
|
|
|
|
this.view.destroy();
|
|
|
|
}
|
|
|
|
}
|
2020-03-10 22:11:32 +00:00
|
|
|
|
2021-01-07 23:33:35 +00:00
|
|
|
let place = document.querySelector("#editor");
|
|
|
|
let view = new ProseMirrorView(place, $content.value);
|