Also added link markdown handling when target is set.
- If no marks, clear the block type if text type?
- Remove links button? (Action already in place if link href is empty).
- Links - Limit target attribute options and validate URL.
-- Links - Integrate entity picker.
\ No newline at end of file
+- Links - Integrate entity picker.
+
+### Notes
+
+- Use NodeViews for embedded content (Code, Drawings) where control is needed.
+- Probably still easiest to have seperate (codemirror) MD editor. Can alter display output via NodeViews to make MD like
+ but its tricky since editing the markdown content would change the block definition/type while editing.
\ No newline at end of file
+/**
+ * @param {String} attrName
+ * @param {String} attrValue
+ * @return {PmCommandHandler}
+ */
export function setBlockAttr(attrName, attrValue) {
return function (state, dispatch) {
const ref = state.selection;
}
}
+/**
+ * @param {PmNodeType} blockType
+ * @return {PmCommandHandler}
+ */
export function insertBlockBefore(blockType) {
return function (state, dispatch) {
const startPosition = state.selection.$from.before(1);
}
}
+/**
+ * @return {PmCommandHandler}
+ */
export function removeMarks() {
return function (state, dispatch) {
if (dispatch) {
}
}
+/**
+ * @param {String} html
+ * @return {PmMark[]}
+ */
function extractMarksFromHtml(html) {
const contentDoc = htmlToDoc('<p>' + (html || '') + '</p>');
const marks = contentDoc?.content?.content?.[0]?.content?.content?.[0]?.marks;
-import {MarkdownSerializer, defaultMarkdownSerializer} from "prosemirror-markdown";
+import {MarkdownSerializer, defaultMarkdownSerializer, MarkdownSerializerState} from "prosemirror-markdown";
import {docToHtml} from "./util";
const nodes = defaultMarkdownSerializer.nodes;
const marks = defaultMarkdownSerializer.marks;
-nodes.callout = function(state, node) {
+nodes.callout = function (state, node) {
writeNodeAsHtml(state, node);
};
+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)
+}
+
+marks.link = {
+ open(state, mark, parent, index) {
+ const attrs = mark.attrs;
+ if (attrs.target) {
+ return `<a href="${attrs.target}" ${attrs.title ? `title="${attrs.title}"` : ''} target="${attrs.target}">`
+ }
+ return isPlainURL(mark, parent, index, 1) ? "<" : "["
+ },
+ close(state, mark, parent, index) {
+ if (mark.attrs.target) {
+ return `</a>`;
+ }
+ return isPlainURL(mark, parent, index, -1) ? ">"
+ : "](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")"
+ }
+};
marks.underline = {
open: '<span style="text-decoration: underline;">',
close: '</span>',
};
-
+/**
+ * @param {MarkdownSerializerState} state
+ * @param node
+ */
function writeNodeAsHtml(state, node) {
- const html = docToHtml({ content: [node] });
+ const html = docToHtml({content: [node]});
state.write(html);
state.ensureNewLine();
state.write('\n');
// or element that cannot be represented in commonmark without losing
// formatting or content.
for (const [nodeType, serializerFunction] of Object.entries(nodes)) {
- nodes[nodeType] = function(state, node, parent, index) {
+ nodes[nodeType] = function (state, node, parent, index) {
if (node.attrs.align) {
writeNodeAsHtml(state, node);
} else {
import {MenuItem} from "./menu";
import {icons} from "./icons";
-
+/**
+ * @param {PmMarkType} markType
+ * @param {String} attribute
+ * @return {(function(PmEditorState): (string|null))}
+ */
function getMarkAttribute(markType, attribute) {
return function (state) {
const marks = state.selection.$head.marks();
};
}
+/**
+ * @param {(function(FormData))} submitter
+ * @param {Function} closer
+ * @return {DialogBox}
+ */
function getLinkDialog(submitter, closer) {
return new DialogBox([
new DialogForm([
return true;
}
+/**
+ * @param {PmEditorState} state
+ * @param {PmDispatchFunction} dispatch
+ * @param {PmView} view
+ * @param {Event} e
+ */
function onPress(state, dispatch, view, e) {
const dialog = getLinkDialog((data) => {
applyLink(data, state, dispatch);
document.body.appendChild(dom);
}
+/**
+ * @return {MenuItem}
+ */
function anchorButtonItem() {
return new MenuItem({
title: "Insert/Edit Anchor Link",
+++ /dev/null
-
-
-
-- Use NodeViews for embedded content (Code, Drawings) where control is needed.
-- Probably still easiest to have seperate (codemirror) MD editor. Can alter display output via NodeViews to make MD like
- but its tricky since editing the markdown content would change the block definition/type while editing.
--
\ No newline at end of file
import nodes from "./schema-nodes";
import marks from "./schema-marks";
-const index = new Schema({
+/** @var {PmSchema} schema */
+const schema = new Schema({
nodes,
marks,
});
-export default index;
\ No newline at end of file
+export default schema;
\ No newline at end of file
--- /dev/null
+/**
+ * @typedef {Object} PmEditorState
+ * @property {PmNode} doc
+ * @property {PmSelection} selection
+ * @property {PmMark[]|null} storedMarks
+ * @property {PmSchema} schema
+ * @property {PmTransaction} tr
+ */
+
+/**
+ * @typedef {Object} PmNode
+ * @property {PmNodeType} type
+ * @property {Object} attrs
+ * @property {PmFragment} content
+ * @property {PmMark[]} marks
+ * @property {String|null} text
+ * @property {Number} nodeSize
+ * @property {Number} childCount
+ */
+
+/**
+ * @typedef {Object} PmNodeType
+ */
+
+/**
+ * @typedef {Object} PmMark
+ * @property {PmMarkType} type
+ * @property {Object} attrs
+ */
+
+/**
+ * @typedef {Object} PmMarkType
+ * @property {String} name
+ * @property {PmSchema} schema
+ * @property {PmMarkSpec} spec
+ */
+
+/**
+ * @typedef {Object} PmMarkSpec
+ */
+
+/**
+ * @typedef {Object} PmSchema
+ * @property {PmSchema} schema
+ * @property {Object<PmNodeType>} nodes
+ * @property {Object<PmMarkType>} marks
+ * @property {PmNodeType} topNodeType
+ * @property {Object} cached
+ */
+
+/**
+ * @typedef {Object} PmSelection
+ * @property {PmSelectionRange[]} ranges
+ * @property {PmResolvedPos} $anchor
+ * @property {PmResolvedPos} $head
+ * @property {Number} anchor
+ * @property {Number} head
+ * @property {Number} from
+ * @property {Number} to
+ * @property {PmResolvedPos} $from
+ * @property {PmResolvedPos} $to
+ * @property {Boolean} empty
+ */
+
+/**
+ * @typedef {Object} PmResolvedPos
+ * @property {Number} pos
+ * @property {Number} depth
+ * @property {Number} parentOffset
+ * @property {PmNode} parent
+ * @property {PmNode} doc
+ */
+
+/**
+ * @typedef {Object} PmSelectionRange
+ */
+
+/**
+ * @typedef {Object} PmTransaction
+ * @property {Number} time
+ * @property {PmMark[]|null} storedMarks
+ * @property {PmSelection} selection
+ */
+
+/**
+ * @typedef {Object} PmFragment
+ */
+
+/**
+ * @typedef {Function} PmCommandHandler
+ * @param {PmEditorState} state
+ * @param {PmDispatchFunction} dispatch
+ */
+
+/**
+ * @typedef {Function} PmDispatchFunction
+ * @param {PmTransaction} tr
+ */
+
+/**
+ * @typedef {Object} PmView
+ * @param {PmEditorState} state
+ * @param {Element} dom
+ * @param {Boolean} editable
+ * @param {Boolean} composing
+ */
\ No newline at end of file
import schema from "./schema";
import {DOMParser, DOMSerializer} from "prosemirror-model";
-
+/**
+ * @param {String} html
+ * @return {PmNode}
+ */
export function htmlToDoc(html) {
const renderDoc = document.implementation.createHTMLDocument();
renderDoc.body.innerHTML = html;
return DOMParser.fromSchema(schema).parse(renderDoc.body);
}
+/**
+ * @param {PmNode} doc
+ * @return {string}
+ */
export function docToHtml(doc) {
const fragment = DOMSerializer.fromSchema(schema).serializeFragment(doc.content);
const renderDoc = document.implementation.createHTMLDocument();