diff --git a/packages/core/src/extensions/Blocks/PreviousBlockTypePlugin.ts b/packages/core/src/extensions/Blocks/PreviousBlockTypePlugin.ts index 1db218a4e3..e8d1403b14 100644 --- a/packages/core/src/extensions/Blocks/PreviousBlockTypePlugin.ts +++ b/packages/core/src/extensions/Blocks/PreviousBlockTypePlugin.ts @@ -5,14 +5,9 @@ import { } from "@tiptap/core"; import { Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; -import BlockAttributes from "./BlockAttributes"; const PLUGIN_KEY = new PluginKey(`previous-blocks`); -// Inserts "prev-" string into an HTML attribute name with a "data-" prefix, e.g. "data-depth" -> "data-prev-depth". -// Assumes "data-" prefix is in the attribute name. -const insertPrev = (attr: string) => attr.slice(0, 5) + "prev-" + attr.slice(5); - /** * This plugin tracks transformation of Block node attributes, so we can support CSS transitions. * @@ -75,7 +70,6 @@ export const PreviousBlockTypePlugin = () => { changes.forEach(() => { const oldNodes = findChildren(oldState.doc, (node) => node.attrs.id); - const oldNodesById = new Map( oldNodes.map((node) => [node.node.attrs.id, node]) ); @@ -84,28 +78,38 @@ export const PreviousBlockTypePlugin = () => { for (let node of newNodes) { const oldNode = oldNodesById.get(node.node.attrs.id); - if (oldNode) { + const oldContentNode = oldNode?.node.firstChild; + const newContentNode = node.node.firstChild; + if (oldNode && oldContentNode && newContentNode) { const newAttrs = { - listType: node.node.attrs.listType, - blockColor: node.node.attrs.blockColor, - blockStyle: node.node.attrs.blockStyle, - headingType: node.node.attrs.headingType, + // listType: node.node.attrs.listType, + level: newContentNode.attrs.level, + type: newContentNode.type.name, depth: newState.doc.resolve(node.pos).depth, }; const oldAttrs = { - listType: oldNode.node.attrs.listType, - blockColor: oldNode.node.attrs.blockColor, - blockStyle: oldNode.node.attrs.blockStyle, - headingType: oldNode.node.attrs.headingType, + // listType: oldNode.node.attrs.listType, + level: oldContentNode.attrs.level, + type: oldContentNode.type.name, depth: oldState.doc.resolve(oldNode.pos).depth, }; if ( JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs) // TODO: faster deep equal? ) { - (oldAttrs as any).depthChange = oldAttrs.depth - newAttrs.depth; + (oldAttrs as any)["depth-change"] = + oldAttrs.depth - newAttrs.depth; prev.prevBlockAttrs[node.node.attrs.id] = oldAttrs; + + // for debugging: + // console.log( + // "previousBlockTypePlugin changes detected, oldAttrs", + // oldAttrs, + // "new", + // newAttrs + // ); + prev.needsUpdate = true; } } @@ -135,9 +139,15 @@ export const PreviousBlockTypePlugin = () => { const decorationAttributes: any = {}; for (let [nodeAttr, val] of Object.entries(prevAttrs)) { - decorationAttributes[insertPrev(BlockAttributes[nodeAttr])] = - val || "none"; + decorationAttributes["data-prev-" + nodeAttr] = val || "none"; } + + // for debugging: + // console.log( + // "previousBlockTypePlugin committing decorations", + // decorationAttributes + // ); + const decoration = Decoration.node(pos, pos + node.nodeSize, { ...decorationAttributes, }); diff --git a/packages/core/src/extensions/Blocks/nodes/Block.module.css b/packages/core/src/extensions/Blocks/nodes/Block.module.css index 140c8711f4..a2a4ca0b09 100644 --- a/packages/core/src/extensions/Blocks/nodes/Block.module.css +++ b/packages/core/src/extensions/Blocks/nodes/Block.module.css @@ -23,11 +23,22 @@ BASIC STYLES } .blockContent { + font-size: 1em; padding: 3px 0; transition: font-size 0.2s; /* display: inline-block; */ } +/* reset styles, they will be set on blockContent */ +.blockContent p, +.blockContent h1, +.blockContent h2, +.blockContent h3 { + margin: 0; + padding: 0; + font-size: inherit; +} + /* NESTED BLOCKS */ @@ -54,7 +65,7 @@ NESTED BLOCKS height: 0; } -/* NESTED BLOCK ANIMATIONS */ +/* NESTED BLOCK ANIMATIONS (change in indent) */ [data-prev-depth-change="1"] { --x: 1; @@ -97,31 +108,39 @@ NESTED BLOCKS } /* HEADINGS*/ -.blockOuter[data-prev-heading-type="1"] > .block > div:first-child, -.blockOuter[data-heading-type="1"]:not([data-prev-heading-type]) - > .block - > div:first-child { - font-size: 3em; - font-weight: bold; +[data-level="1"] { + --level: 3em; +} +[data-level="2"] { + --level: 2em; +} +[data-level="3"] { + --level: 1.3em; } -.blockOuter[data-prev-heading-type="2"] > .block > div:first-child, -.blockOuter[data-heading-type="2"]:not([data-prev-heading-type]) - > .block - > div:first-child { - font-size: 2em; +[data-prev-level="1"] { + --prev-level: 3em; +} +[data-prev-level="2"] { + --prev-level: 2em; +} +[data-prev-level="3"] { + --prev-level: 1.3em; +} + +.blockOuter[data-prev-type="headingContent"] > .block > .blockContent { + font-size: var(--prev-level); font-weight: bold; } -.blockOuter[data-prev-heading-type="3"] > .block > div:first-child, -.blockOuter[data-heading-type="3"]:not([data-prev-heading-type]) +.blockOuter:not([data-prev-type]) > .block - > div:first-child { - font-size: 1.3em; + > .blockContent[data-content-type="headingContent"] { + font-size: var(--level); font-weight: bold; } -/* LISTS */ +/* LISTS (TODO) */ .block > div:first-child::before { content: ""; @@ -191,12 +210,12 @@ NESTED BLOCKS /* PLACEHOLDERS*/ -.blockContent > div { +.blockContent > :first-child { display: inline; } -.blockContent.isEmpty div::before, -.blockContent.isFilter div::before { +.blockContent.isEmpty > :first-child:before, +.blockContent.isFilter > :first-child:before { /*float: left; */ content: ""; color: #aeb8c2; @@ -209,18 +228,19 @@ NESTED BLOCKS /* TODO: would be nicer if defined from code */ -.blockContent.isEmpty.hasAnchor div::before { +.blockContent.isEmpty.hasAnchor > :first-child:before { content: "Enter text or type '/' for commands"; } -.blockContent.isFilter.hasAnchor div::before { +.blockContent.isFilter.hasAnchor > :first-child:before { content: "Type to filter"; } -[data-heading-type] > .blockContent.isEmpty div::before { +.blockContent[data-content-type="headingContent"].isEmpty + > :first-child::before { content: "Heading"; } -[data-list-type] > .blockContent.isEmpty div::before { +.blockContent[data-content-type="listContent"] > :first-child div::before { content: "List"; -} \ No newline at end of file +} diff --git a/packages/core/src/extensions/Blocks/nodes/Block.ts b/packages/core/src/extensions/Blocks/nodes/Block.ts index b5601f2cd8..ad3f9a2a5d 100644 --- a/packages/core/src/extensions/Blocks/nodes/Block.ts +++ b/packages/core/src/extensions/Blocks/nodes/Block.ts @@ -1,12 +1,12 @@ import { mergeAttributes, Node } from "@tiptap/core"; -import { TextSelection } from "prosemirror-state"; import { Slice } from "prosemirror-model"; -import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin"; -import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos"; +import { TextSelection } from "prosemirror-state"; import BlockAttributes from "../BlockAttributes"; +import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos"; +import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin"; +import styles from "./Block.module.css"; import { HeadingContentAttributes } from "./HeadingContent"; import { ListItemContentAttributes } from "./ListItemContent"; -import styles from "./Block.module.css"; export interface IBlock { HTMLAttributes: Record; @@ -109,6 +109,7 @@ export const Block = Node.create({ [ "div", mergeAttributes(attrs, { + // TODO: maybe remove html attributes from inner block class: styles.block, "data-node-type": this.name, }), @@ -134,7 +135,9 @@ export const Block = Node.create({ (posInBlock) => ({ state }) => { const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) return false; + if (blockInfo === undefined) { + return false; + } const { startPos, endPos } = blockInfo; @@ -166,7 +169,9 @@ export const Block = Node.create({ state.doc, posBetweenBlocks + 1 ); - if (nextBlockInfo === undefined) return false; + if (nextBlockInfo === undefined) { + return false; + } const { node, contentNode, startPos, endPos, depth } = nextBlockInfo; @@ -186,13 +191,17 @@ export const Block = Node.create({ let prevBlockEndPos = posBetweenBlocks - 1; let prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); - if (prevBlockInfo === undefined) return false; + if (prevBlockInfo === undefined) { + return false; + } // Finds the nearest previous block, prioritizing higher nesting levels. while (prevBlockInfo.numChildBlocks > 0) { prevBlockEndPos--; prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); - if (prevBlockInfo === undefined) return false; + if (prevBlockInfo === undefined) { + return false; + } } // Deletes next block and adds its text content to the nearest previous block. @@ -212,7 +221,9 @@ export const Block = Node.create({ (posInBlock, keepType) => ({ state }) => { const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) return false; + if (blockInfo === undefined) { + return false; + } const { startPos, endPos, depth } = blockInfo; @@ -259,7 +270,9 @@ export const Block = Node.create({ (posInBlock, type, attributes) => ({ state }) => { const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) return false; + if (blockInfo === undefined) { + return false; + } const { startPos, endPos } = blockInfo; @@ -278,7 +291,9 @@ export const Block = Node.create({ (posInBlock, type, attributes) => ({ state, chain }) => { const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) return false; + if (blockInfo === undefined) { + return false; + } const { node, startPos, endPos } = blockInfo; diff --git a/packages/core/src/extensions/Blocks/nodes/HeadingContent.ts b/packages/core/src/extensions/Blocks/nodes/HeadingContent.ts index 32f2500768..c3be474d8f 100644 --- a/packages/core/src/extensions/Blocks/nodes/HeadingContent.ts +++ b/packages/core/src/extensions/Blocks/nodes/HeadingContent.ts @@ -1,4 +1,4 @@ -import { InputRule, Node, NodeViewRendererProps } from "@tiptap/core"; +import { InputRule, mergeAttributes, Node } from "@tiptap/core"; import styles from "./Block.module.css"; export type HeadingContentAttributes = { @@ -10,28 +10,18 @@ export const HeadingContent = Node.create({ group: "blockContent", content: "inline*", - addNodeView() { - return (props: NodeViewRendererProps) => { - const element = document.createElement("div"); - element.setAttribute("data-node-type", "block-content"); - element.setAttribute("data-content-type", this.name); - element.className = styles.blockContent; - - const editableElement = document.createElement( - "h" + props.HTMLAttributes["level"] - ); - element.appendChild(editableElement); - - return { - dom: element, - contentDOM: editableElement, - }; - }; - }, - addAttributes() { return { - level: { default: "1" }, + level: { + default: "1", + // instead of "level" attributes, use "data-level" + parseHTML: (element) => element.getAttribute("data-level"), + renderHTML: (attributes) => { + return { + "data-level": attributes.level, + }; + }, + }, }; }, @@ -71,15 +61,15 @@ export const HeadingContent = Node.create({ ]; }, - renderHTML({ HTMLAttributes }) { + renderHTML({ node, HTMLAttributes }) { return [ "div", - { - "data-node-type": "block-content", + mergeAttributes(HTMLAttributes, { + "data-node-type": "block-content", // TODO: only for testing? if so, rename to data-test-*? "data-content-type": this.name, class: styles.blockContent, - }, - ["h" + HTMLAttributes["level"], 0], + }), + ["h" + node.attrs.level, 0], ]; }, }); diff --git a/packages/core/src/extensions/Blocks/nodes/TextContent.ts b/packages/core/src/extensions/Blocks/nodes/TextContent.ts index 3bb7468bda..1b5034e61a 100644 --- a/packages/core/src/extensions/Blocks/nodes/TextContent.ts +++ b/packages/core/src/extensions/Blocks/nodes/TextContent.ts @@ -1,4 +1,4 @@ -import { Node, NodeViewRendererProps } from "@tiptap/core"; +import { Node } from "@tiptap/core"; import styles from "./Block.module.css"; export const TextContent = Node.create({ @@ -6,23 +6,6 @@ export const TextContent = Node.create({ group: "blockContent", content: "inline*", - addNodeView() { - return (_props: NodeViewRendererProps) => { - const element = document.createElement("div"); - element.setAttribute("data-node-type", "block-content"); - element.setAttribute("data-content-type", this.name); - element.className = styles.blockContent; - - const editableElement = document.createElement("p"); - element.appendChild(editableElement); - - return { - dom: element, - contentDOM: editableElement, - }; - }; - }, - parseHTML() { return [ { @@ -36,7 +19,7 @@ export const TextContent = Node.create({ return [ "div", { - "data-node-type": "block-content", + "data-node-type": "block-content", // TODO: only for testing? if so, rename to data-test-*? "data-content-type": this.name, class: styles.blockContent, },