Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10433467
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Subscribers
None
View Options
diff --git a/prose/markdownParser.js b/prose/markdownParser.js
index cbc9b5e..bd81590 100644
--- a/prose/markdownParser.js
+++ b/prose/markdownParser.js
@@ -1,57 +1,57 @@
import { MarkdownParser } from "prosemirror-markdown";
import markdownit from "markdown-it";
import { writeFreelySchema } from "./schema";
-export const writeAsMarkdownParser = new MarkdownParser(
+export const writeFreelyMarkdownParser = new MarkdownParser(
writeFreelySchema,
markdownit("commonmark", { html: true }),
{
// blockquote: { block: "blockquote" },
paragraph: { block: "paragraph" },
list_item: { block: "list_item" },
bullet_list: { block: "bullet_list" },
ordered_list: {
block: "ordered_list",
getAttrs: (tok) => ({ order: +tok.attrGet("start") || 1 }),
},
heading: {
block: "heading",
getAttrs: (tok) => ({ level: +tok.tag.slice(1) }),
},
code_block: { block: "code_block", noCloseToken: true },
fence: {
block: "code_block",
getAttrs: (tok) => ({ params: tok.info || "" }),
noCloseToken: true,
},
// hr: { node: "horizontal_rule" },
image: {
node: "image",
getAttrs: (tok) => ({
src: tok.attrGet("src"),
title: tok.attrGet("title") || null,
alt: (tok.children !== null && typeof tok.children[0] !== 'undefined' ? tok.children[0].content : null),
}),
},
hardbreak: { node: "hard_break" },
em: { mark: "em" },
strong: { mark: "strong" },
link: {
mark: "link",
getAttrs: (tok) => ({
href: tok.attrGet("href"),
title: tok.attrGet("title") || null,
}),
},
code_inline: { mark: "code", noCloseToken: true },
html_block: {
node: "readmore",
getAttrs(token) {
// TODO: Give different attributes depending on the token content
return {};
},
},
}
);
diff --git a/prose/markdownSerializer.js b/prose/markdownSerializer.js
index cadfcac..d14deab 100644
--- a/prose/markdownSerializer.js
+++ b/prose/markdownSerializer.js
@@ -1,124 +1,124 @@
import { MarkdownSerializer } from "prosemirror-markdown";
function backticksFor(node, side) {
const ticks = /`+/g;
let m;
let len = 0;
if (node.isText)
while ((m = ticks.exec(node.text))) len = Math.max(len, m[0].length);
let result = len > 0 && side > 0 ? " `" : "`";
for (let i = 0; i < len; i++) result += "`";
if (len > 0 && side < 0) result += " ";
return result;
}
function isPlainURL(link, parent, index, side) {
if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) return false;
const content = parent.child(index + (side < 0 ? -1 : 0));
if (
!content.isText ||
content.text != link.attrs.href ||
content.marks[content.marks.length - 1] != link
)
return false;
if (index == (side < 0 ? 1 : parent.childCount - 1)) return true;
const next = parent.child(index + (side < 0 ? -2 : 1));
return !link.isInSet(next.marks);
}
-export const writeAsMarkdownSerializer = new MarkdownSerializer(
+export const writeFreelyMarkdownSerializer = new MarkdownSerializer(
{
readmore(state, node) {
state.write("<!--more-->\n");
state.closeBlock(node);
},
// blockquote(state, node) {
// state.wrapBlock("> ", undefined, node, () => state.renderContent(node));
// },
code_block(state, node) {
state.write(`\`\`\`${node.attrs.params || ""}\n`);
state.text(node.textContent, false);
state.ensureNewLine();
state.write("```");
state.closeBlock(node);
},
heading(state, node) {
state.write(`${state.repeat("#", node.attrs.level)} `);
state.renderInline(node);
state.closeBlock(node);
},
bullet_list(state, node) {
node.attrs.tight = true;
state.renderList(node, " ", () => `${node.attrs.bullet || "*"} `);
},
ordered_list(state, node) {
const start = node.attrs.order || 1;
const maxW = String(start + node.childCount - 1).length;
const space = state.repeat(" ", maxW + 2);
state.renderList(node, space, (i) => {
const nStr = String(start + i);
return `${state.repeat(" ", maxW - nStr.length) + nStr}. `;
});
},
list_item(state, node) {
state.renderContent(node);
},
paragraph(state, node) {
state.renderInline(node);
state.closeBlock(node);
},
image(state, node) {
state.write(
`![${state.esc(node.attrs.alt || "")}](${state.esc(node.attrs.src)}${
node.attrs.title ? ` ${state.quote(node.attrs.title)}` : ""
})`
);
},
hard_break(state, node, parent, index) {
for (let i = index + 1; i < parent.childCount; i += 1)
if (parent.child(i).type !== node.type) {
state.write("\\\n");
return;
}
},
text(state, node) {
state.text(node.text || "");
},
},
{
em: {
open: "*",
close: "*",
mixable: true,
expelEnclosingWhitespace: true,
},
strong: {
open: "**",
close: "**",
mixable: true,
expelEnclosingWhitespace: true,
},
link: {
open(_state, mark, parent, index) {
return isPlainURL(mark, parent, index, 1) ? "<" : "[";
},
close(state, mark, parent, index) {
return isPlainURL(mark, parent, index, -1)
? ">"
: `](${state.esc(mark.attrs.href)}${
mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ""
})`;
},
},
code: {
open(_state, _mark, parent, index) {
return backticksFor(parent.child(index), -1);
},
close(_state, _mark, parent, index) {
return backticksFor(parent.child(index - 1), 1);
},
escape: false,
},
}
);
diff --git a/prose/prose.js b/prose/prose.js
index 5a6b618..7c361a0 100644
--- a/prose/prose.js
+++ b/prose/prose.js
@@ -1,118 +1,118 @@
// 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() }
// }
import { EditorView } from "prosemirror-view";
import { EditorState, TextSelection } from "prosemirror-state";
import { exampleSetup } from "prosemirror-example-setup";
import { keymap } from "prosemirror-keymap";
-import { writeAsMarkdownParser } from "./markdownParser";
-import { writeAsMarkdownSerializer } from "./markdownSerializer";
+import { writeFreelyMarkdownParser } from "./markdownParser";
+import { writeFreelyMarkdownSerializer } from "./markdownSerializer";
import { writeFreelySchema } from "./schema";
import { getMenu } from "./menu";
let $title = document.querySelector("#title");
let $content = document.querySelector("#content");
// Bugs:
// 1. When there's just an empty line and a hard break is inserted with shift-enter then two enters are inserted
// which do not show up in the markdown ( maybe bc. they are training enters )
class ProseMirrorView {
constructor(target, content) {
let typingTimer;
let localDraft = localStorage.getItem(window.draftKey);
if (localDraft != null) {
content = localDraft;
}
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;
}
- const doc = writeAsMarkdownParser.parse(
+ const doc = writeFreelyMarkdownParser.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.replace(/([^]{0,1})(\n)([^]{0,1})/g, (match, p1, p2, p3) => {
return p1 !== "\n" && p3 !== "\n" ? p1 + "\\\n" + p3 : match;
})
);
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"));
return true;
},
}),
...exampleSetup({
schema: writeFreelySchema,
menuContent: getMenu(),
}),
],
}),
dispatchTransaction(transaction) {
let newState = this.state.apply(transaction);
- const newContent = writeAsMarkdownSerializer
+ const newContent = writeFreelyMarkdownSerializer
.serialize(newState.doc)
// Replace all \\\ns ( not followed by a \n ) with \n
.replace(/(\\\n)(\n{0,1})/g, (match, p1, p2) =>
p2 !== "\n" ? "\n" + p2 : match
);
$content.value = newContent;
let draft = "";
if ($title.value != null && $title.value !== "") {
draft = "# " + $title.value + "\n\n";
}
draft += newContent;
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
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();
}
}
let place = document.querySelector("#editor");
let view = new ProseMirrorView(place, $content.value);
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Jan 20, 3:48 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3136011
Attached To
rWF WriteFreely
Event Timeline
Log In to Comment