]> BookStack Code Mirror - bookstack/commitdiff
Added jsdoc types for prosemirror
authorDan Brown <redacted>
Sun, 16 Jan 2022 15:21:57 +0000 (15:21 +0000)
committerDan Brown <redacted>
Sun, 16 Jan 2022 15:21:57 +0000 (15:21 +0000)
Also added link markdown handling when target is set.

TODO
resources/js/editor/commands.js
resources/js/editor/markdown-parser.js
resources/js/editor/markdown-serializer.js
resources/js/editor/menu/item-anchor-button.js
resources/js/editor/notes.md [deleted file]
resources/js/editor/schema.js
resources/js/editor/types.js [new file with mode: 0644]
resources/js/editor/util.js

diff --git a/TODO b/TODO
index ca678d1dc8c2f9e8456d2f12aa65d37656a47882..ce7cdbf076e12b0ad279ff5cfb015bcdd40bdc08 100644 (file)
--- a/TODO
+++ b/TODO
   - 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
index 1817bd2a9d7ca4f4f731863ac073c67d07ad1c58..bd71ceba310349a42da8f42f0cedf3e98c964940 100644 (file)
@@ -1,3 +1,8 @@
+/**
+ * @param {String} attrName
+ * @param {String} attrValue
+ * @return {PmCommandHandler}
+ */
 export function setBlockAttr(attrName, attrValue) {
     return function (state, dispatch) {
         const ref = state.selection;
@@ -37,6 +42,10 @@ export function setBlockAttr(attrName, attrValue) {
     }
 }
 
+/**
+ * @param {PmNodeType} blockType
+ * @return {PmCommandHandler}
+ */
 export function insertBlockBefore(blockType) {
     return function (state, dispatch) {
         const startPosition = state.selection.$from.before(1);
@@ -49,6 +58,9 @@ export function insertBlockBefore(blockType) {
     }
 }
 
+/**
+ * @return {PmCommandHandler}
+ */
 export function removeMarks() {
     return function (state, dispatch) {
         if (dispatch) {
index b198ffad43f27b433ce4affb74529fc7972d993b..d60ddd55401f8972bb86719ffd2951bf3f71be73 100644 (file)
@@ -48,6 +48,10 @@ parser.tokenHandlers.html_inline = function(state, tok, tokens, i) {
     }
 }
 
+/**
+ * @param {String} html
+ * @return {PmMark[]}
+ */
 function extractMarksFromHtml(html) {
     const contentDoc = htmlToDoc('<p>' + (html || '') + '</p>');
     const marks = contentDoc?.content?.content?.[0]?.content?.content?.[0]?.marks;
index 5e1dfb33bdfa5672633d788c761a6f2d548f1c08..2edc1ef27606ea4547cdd0979115ce031f9d60ca 100644 (file)
@@ -1,14 +1,45 @@
-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;">',
@@ -44,9 +75,12 @@ marks.background_color = {
     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');
@@ -57,7 +91,7 @@ function writeNodeAsHtml(state, node) {
 // 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 {
index f419314275464cd0d01a65b24140568ed73fa4c6..d95ac2e78f339a9c604e82f9e1ec938399d17ae9 100644 (file)
@@ -6,7 +6,11 @@ import schema from "../schema";
 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();
@@ -20,6 +24,11 @@ function getMarkAttribute(markType, attribute) {
     };
 }
 
+/**
+ * @param {(function(FormData))} submitter
+ * @param {Function} closer
+ * @return {DialogBox}
+ */
 function getLinkDialog(submitter, closer) {
     return new DialogBox([
         new DialogForm([
@@ -64,6 +73,12 @@ function applyLink(formData, state, dispatch) {
     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);
@@ -77,6 +92,9 @@ function onPress(state, dispatch, view, e) {
     document.body.appendChild(dom);
 }
 
+/**
+ * @return {MenuItem}
+ */
 function anchorButtonItem() {
     return new MenuItem({
         title: "Insert/Edit Anchor Link",
diff --git a/resources/js/editor/notes.md b/resources/js/editor/notes.md
deleted file mode 100644 (file)
index c458855..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-- 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
index 6aa230b218fe458b6ab80437b8d4585857bd51c7..e0cd2522248a40ace21229054047fdf601b2e3ae 100644 (file)
@@ -3,9 +3,10 @@ import {Schema} from "prosemirror-model";
 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
diff --git a/resources/js/editor/types.js b/resources/js/editor/types.js
new file mode 100644 (file)
index 0000000..b676eb1
--- /dev/null
@@ -0,0 +1,106 @@
+/**
+ * @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
index 3c9cffde5085d12ca894d328c29993bfa6e85b2c..f94aa10fc93a908c69b04b13d014736e2a9bb546 100644 (file)
@@ -1,13 +1,20 @@
 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();