Added jsdoc types for prosemirror
Also added link markdown handling when target is set.
This commit is contained in:
parent
89194a3f85
commit
7622106665
9 changed files with 198 additions and 17 deletions
8
TODO
8
TODO
|
@ -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.
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
-
|
|
@ -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;
|
106
resources/js/editor/types.js
Normal file
106
resources/js/editor/types.js
Normal 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
|
||||
*/
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue