Added jsdoc types for prosemirror

Also added link markdown handling when target is set.
This commit is contained in:
Dan Brown 2022-01-16 15:21:57 +00:00
parent 89194a3f85
commit 7622106665
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
9 changed files with 198 additions and 17 deletions

8
TODO
View file

@ -29,4 +29,10 @@
- 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.
- 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.

View 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) {

View 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;

View 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 {

View 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",

View file

@ -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.
-

View 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;
export default schema;

View file

@ -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
*/

View 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();