writefreely/prose/prose.js

202 lines
5.5 KiB
JavaScript

// 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 {Schema} from "prosemirror-model"
import {EditorView} from "prosemirror-view"
import {EditorState, Plugin} from "prosemirror-state"
import {defaultMarkdownParser,
defaultMarkdownSerializer} from "prosemirror-markdown"
import {exampleSetup} from "prosemirror-example-setup"
// TODO: maybe don't need to use our own schema but waiting to figure out
// line break issues
const schema = new Schema({
nodes: {
doc: {
content: "block+"
},
paragraph: {
content: "inline*",
group: "block",
parseDOM: [{tag: "p"}],
toDOM() { return ["p", 0] }
},
blockquote: {
content: "block+",
group: "block",
parseDOM: [{tag: "blockquote"}],
toDOM() { return ["blockquote", 0] }
},
horizontal_rule: {
group: "block",
parseDOM: [{tag: "hr"}],
toDOM() { return ["div", ["hr"]] }
},
heading: {
attrs: {level: {default: 1}},
content: "inline*",
group: "block",
defining: true,
parseDOM: [{tag: "h1", attrs: {level: 1}},
{tag: "h2", attrs: {level: 2}},
{tag: "h3", attrs: {level: 3}},
{tag: "h4", attrs: {level: 4}},
{tag: "h5", attrs: {level: 5}},
{tag: "h6", attrs: {level: 6}}],
toDOM(node) { return ["h" + node.attrs.level, 0] }
},
code_block: {
content: "text*",
group: "block",
code: true,
defining: true,
marks: "",
attrs: {params: {default: ""}},
parseDOM: [{tag: "pre", preserveWhitespace: "full", getAttrs: node => (
{params: node.getAttribute("data-params") || ""}
)}],
toDOM(node) { return ["pre", node.attrs.params ? {"data-params": node.attrs.params} : {}, ["code", 0]] }
},
ordered_list: {
content: "list_item+",
group: "block",
attrs: {order: {default: 1}, tight: {default: false}},
parseDOM: [{tag: "ol", getAttrs(dom) {
return {order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1,
tight: dom.hasAttribute("data-tight")}
}}],
toDOM(node) {
return ["ol", {start: node.attrs.order == 1 ? null : node.attrs.order,
"data-tight": node.attrs.tight ? "true" : null}, 0]
}
},
bullet_list: {
content: "list_item+",
group: "block",
attrs: {tight: {default: false}},
parseDOM: [{tag: "ul", getAttrs: dom => ({tight: dom.hasAttribute("data-tight")})}],
toDOM(node) { return ["ul", {"data-tight": node.attrs.tight ? "true" : null}, 0] }
},
list_item: {
content: "paragraph block*",
defining: true,
parseDOM: [{tag: "li"}],
toDOM() { return ["li", 0] }
},
text: {
group: "inline"
},
image: {
inline: true,
attrs: {
src: {},
alt: {default: null},
title: {default: null}
},
group: "inline",
draggable: true,
parseDOM: [{tag: "img[src]", getAttrs(dom) {
return {
src: dom.getAttribute("src"),
title: dom.getAttribute("title"),
alt: dom.getAttribute("alt")
}
}}],
toDOM(node) { return ["img", node.attrs] }
},
hard_break: {
inline: true,
group: "inline",
selectable: false,
parseDOM: [{tag: "br"}],
toDOM() { return ["br"] }
}
},
marks: {
em: {
parseDOM: [{tag: "i"}, {tag: "em"},
{style: "font-style", getAttrs: value => value == "italic" && null}],
toDOM() { return ["em"] }
},
strong: {
parseDOM: [{tag: "b"}, {tag: "strong"},
{style: "font-weight", getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null}],
toDOM() { return ["strong"] }
},
link: {
attrs: {
href: {},
title: {default: null}
},
inclusive: false,
parseDOM: [{tag: "a[href]", getAttrs(dom) {
return {href: dom.getAttribute("href"), title: dom.getAttribute("title")}
}}],
toDOM(node) { return ["a", node.attrs] }
},
code: {
parseDOM: [{tag: "code"}],
toDOM() { return ["code"] }
}
}
})
class ProseMirrorView {
constructor(target, content) {
this.view = new EditorView(target, {
state: EditorState.create({
doc: defaultMarkdownParser.parse(content),
plugins: exampleSetup({schema})
}), dispatchTransaction(transaction) {
document.querySelector('#content').innerText = defaultMarkdownSerializer.serialize(transaction.doc)
let newState = this.state.apply(transaction)
this.updateState(newState)
}
})
}
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, document.querySelector('#content').value)
// document.querySelectorAll("input[type=radio]").forEach(button => {
// button.addEventListener("change", () => {
// if (!button.checked) return
// let View = button.value == "markdown" ? MarkdownView : ProseMirrorView
// if (view instanceof View) return
// let content = view.content
// view.destroy()
// view = new View(place, content)
// view.focus()
// })
// })