import React = require("react"); import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec"; const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; // :: Object // [Specs](#model.NodeSpec) for the nodes defined in this schema. export const nodes: { [index: string]: NodeSpec } = { // :: NodeSpec The top level document node. doc: { content: "block+" }, footnote: { group: "inline", content: "inline*", inline: true, attrs: { visibility: { default: false } }, // This makes the view treat the node as a leaf, even though it // technically has content atom: true, toDOM: () => ["footnote", 0], parseDOM: [{ tag: "footnote" }] }, paragraph: ParagraphNodeSpec, // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. blockquote: { content: "block*", group: "block", defining: true, parseDOM: [{ tag: "blockquote" }], toDOM() { return blockquoteDOM; } }, // blockquote: { // ...ParagraphNodeSpec, // defining: true, // parseDOM: [{ // tag: "blockquote", getAttrs(dom: any) { // return getParagraphNodeAttrs(dom); // } // }], // toDOM(node: any) { // const dom = toParagraphDOM(node); // (dom as any)[0] = 'blockquote'; // return dom; // }, // }, // :: NodeSpec A horizontal rule (`
`). horizontal_rule: { group: "block", parseDOM: [{ tag: "hr" }], toDOM() { return hrDOM; } }, // :: NodeSpec A heading textblock, with a `level` attribute that // should hold the number 1 to 6. Parsed and serialized as `

` to // `

` elements. heading: { ...ParagraphNodeSpec, attrs: { ...ParagraphNodeSpec.attrs, level: { default: 1 }, }, 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) { const dom = toParagraphDOM(node) as any; const level = node.attrs.level || 1; dom[0] = 'h' + level; return dom; }, getAttrs(dom: any) { const attrs = getParagraphNodeAttrs(dom) as any; const level = Number(dom.nodeName.substring(1)) || 1; attrs.level = level; return attrs; } }, // :: NodeSpec A code listing. Disallows marks or non-text inline // nodes by default. Represented as a `
` element with a
    // `` element inside of it.
    code_block: {
        content: "inline*",
        marks: "_",
        group: "block",
        code: true,
        defining: true,
        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
        toDOM() { return preDOM; }
    },

    // :: NodeSpec The text node.
    text: {
        group: "inline"
    },

    dashComment: {
        attrs: {
            docid: { default: "" },
        },
        inline: true,
        group: "inline",
        toDOM(node) {
            const attrs = { style: `width: 40px` };
            return ["span", { ...node.attrs, ...attrs }, "←"];
        },
    },

    summary: {
        inline: true,
        attrs: {
            visibility: { default: false },
            text: { default: undefined },
            textslice: { default: undefined },
        },
        group: "inline",
        toDOM(node) {
            const attrs = { style: `width: 40px` };
            return ["span", { ...node.attrs, ...attrs }];
        },
    },

    // :: NodeSpec An inline image (``) node. Supports `src`,
    // `alt`, and `href` attributes. The latter two default to the empty
    // string.
    image: {
        inline: true,
        attrs: {
            src: {},
            agnostic: { default: null },
            width: { default: 100 },
            alt: { default: null },
            title: { default: null },
            float: { default: "left" },
            location: { default: "add:right" },
            docid: { default: "" }
        },
        group: "inline",
        draggable: true,
        parseDOM: [{
            tag: "img[src]", getAttrs(dom: any) {
                return {
                    src: dom.getAttribute("src"),
                    title: dom.getAttribute("title"),
                    alt: dom.getAttribute("alt"),
                    width: Math.min(100, Number(dom.getAttribute("width"))),
                };
            }
        }],
        // TODO if we don't define toDom, dragging the image crashes. Why?
        toDOM(node) {
            const attrs = { style: `width: ${node.attrs.width}` };
            return ["img", { ...node.attrs, ...attrs }];
        }
    },

    dashDoc: {
        inline: true,
        attrs: {
            width: { default: 200 },
            height: { default: 100 },
            title: { default: null },
            float: { default: "right" },
            location: { default: "add:right" },
            hidden: { default: false },
            fieldKey: { default: "" },
            docid: { default: "" },
            alias: { default: "" }
        },
        group: "inline",
        draggable: false,
        toDOM(node) {
            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
            return ["div", { ...node.attrs, ...attrs }];
        }
    },

    dashField: {
        inline: true,
        attrs: {
            fieldKey: { default: "" },
            docid: { default: "" },
            hideKey: { default: false }
        },
        group: "inline",
        draggable: false,
        toDOM(node) {
            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
            return ["div", { ...node.attrs, ...attrs }];
        }
    },

    video: {
        inline: true,
        attrs: {
            src: {},
            width: { default: "100px" },
            alt: { default: null },
            title: { default: null }
        },
        group: "inline",
        draggable: true,
        parseDOM: [{
            tag: "video[src]", getAttrs(dom: any) {
                return {
                    src: dom.getAttribute("src"),
                    title: dom.getAttribute("title"),
                    alt: dom.getAttribute("alt"),
                    width: Math.min(100, Number(dom.getAttribute("width"))),
                };
            }
        }],
        toDOM(node) {
            const attrs = { style: `width: ${node.attrs.width}` };
            return ["video", { ...node.attrs, ...attrs }];
        }
    },

    // :: NodeSpec A hard line break, represented in the DOM as `
`. hard_break: { inline: true, group: "inline", selectable: false, parseDOM: [{ tag: "br" }], toDOM() { return brDOM; } }, ordered_list: { ...orderedList, content: 'list_item+', group: 'block', attrs: { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" },// "decimal", "multi", "bullet" fontColor: { default: "inherit" }, fontSize: { default: undefined }, fontFamily: { default: undefined }, visibility: { default: true }, indent: { default: undefined } }, parseDOM: [ { tag: "ul", getAttrs(dom: any) { return { bulletStyle: dom.getAttribute("data-bulletStyle"), mapStyle: dom.getAttribute("data-mapStyle"), fontColor: dom.style.color, fontSize: dom.style["font-size"], fontFamily: dom.style["font-family"], indent: dom.style["margin-left"] }; } }, { style: 'list-style-type=disc', getAttrs(dom: any) { return { mapStyle: "bullet" }; } }, { tag: "ol", getAttrs(dom: any) { return { bulletStyle: dom.getAttribute("data-bulletStyle"), mapStyle: dom.getAttribute("data-mapStyle"), fontColor: dom.style.color, fontSize: dom.style["font-size"], fontFamily: dom.style["font-family"], indent: dom.style["margin-left"] }; } }], toDOM(node: Node) { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : ""; const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : ""; const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : ""; const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ""; if (node.attrs.mapStyle === "bullet") { return ['ul', { "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle, style: `${fsize} ${ffam} ${fcol} ${marg}` }, 0]; } return node.attrs.visibility ? ['ol', { class: `${map}-ol`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle, style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}` }, 0] : ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; } }, list_item: { ...listItem, attrs: { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet" visibility: { default: true } }, content: 'paragraph+ | (paragraph ordered_list)', parseDOM: [{ tag: "li", getAttrs(dom: any) { return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") }; } }], toDOM(node: any) { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; return ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, node.attrs.visibility ? 0 : ["span", { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== "bullet" ? "inline-block" : "list-item"}; text-overflow: ellipsis; white-space: pre` }, `${node.firstChild?.textContent}...`]]; } }, };