writefreely/prose/prose.js

119 lines
4.1 KiB
JavaScript
Raw Normal View History

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";
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:
// 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-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
// 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": () => {
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
.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);
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);