mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
editor: unhide children when collapsed heading is changed to non-heading node (#8946)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
@@ -3,3 +3,13 @@
|
||||
exports[`collapse heading > heading collapsed 1`] = `"<h1 data-collapsed="true">Main Heading</h1><p>paragraph.</p><h2>Subheading</h2><p>subheading paragraph</p><h1>Main heading 2</h1><p>paragraph another</p>"`;
|
||||
|
||||
exports[`collapse heading > heading uncollapsed 1`] = `"<h1>Main Heading</h1><p>paragraph.</p><h2>Subheading</h2><p>subheading paragraph</p><h1>Main heading 2</h1><p>paragraph another</p>"`;
|
||||
|
||||
exports[`replacing collapsed heading with another heading level should not unhide content 1`] = `"<h2 data-collapsed="true">A collapsed heading</h2><p data-hidden="true">Hidden paragraph</p>"`;
|
||||
|
||||
exports[`replacing collapsed heading with another node (blockquote) should unhide content 1`] = `"<blockquote><h1 data-collapsed="true">A collpased heading</h1></blockquote><p>Hidden paragraph</p>"`;
|
||||
|
||||
exports[`replacing collapsed heading with another node (bulletList) should unhide content 1`] = `"<ul><li><p>A collpased heading</p></li></ul><p>Hidden paragraph</p>"`;
|
||||
|
||||
exports[`replacing collapsed heading with another node (codeBlock) should unhide content 1`] = `"<pre><code>A collpased heading</code></pre><p>Hidden paragraph</p>"`;
|
||||
|
||||
exports[`replacing collapsed heading with another node (paragraph) should unhide content 1`] = `"<p>A collpased heading</p><p>Hidden paragraph</p>"`;
|
||||
|
||||
@@ -18,8 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { test, expect } from "vitest";
|
||||
import { createEditor } from "../../../../test-utils/index.js";
|
||||
import { createEditor, h } from "../../../../test-utils/index.js";
|
||||
import { Heading } from "../heading.js";
|
||||
import { Editor } from "@tiptap/core";
|
||||
|
||||
test("collapse heading", () => {
|
||||
const { editor } = createEditor({
|
||||
@@ -52,3 +53,59 @@ test("collapse heading", () => {
|
||||
|
||||
expect(editor.getHTML()).toMatchSnapshot("heading uncollapsed");
|
||||
});
|
||||
|
||||
test("replacing collapsed heading with another heading level should not unhide content", () => {
|
||||
const el = h("div", [
|
||||
h("h1", ["A collapsed heading"], { "data-collapsed": "true" }),
|
||||
h("p", ["Hidden paragraph"], { "data-hidden": "true" })
|
||||
]);
|
||||
const { editor } = createEditor({
|
||||
extensions: {
|
||||
heading: Heading.configure({ levels: [1, 2, 3, 4, 5, 6] })
|
||||
},
|
||||
initialContent: el.outerHTML
|
||||
});
|
||||
|
||||
editor.commands.setTextSelection(0);
|
||||
editor.commands.setHeading({ level: 2 });
|
||||
|
||||
expect(editor.getHTML()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
const nodes: { name: string; setNode: (editor: Editor) => void }[] = [
|
||||
{
|
||||
name: "paragraph",
|
||||
setNode: (editor) => editor.commands.setParagraph()
|
||||
},
|
||||
{
|
||||
name: "codeBlock",
|
||||
setNode: (editor) => editor.commands.setCodeBlock()
|
||||
},
|
||||
{
|
||||
name: "bulletList",
|
||||
setNode: (editor) => editor.commands.toggleList("bulletList", "listItem")
|
||||
},
|
||||
{
|
||||
name: "blockquote",
|
||||
setNode: (editor) => editor.commands.toggleBlockquote()
|
||||
}
|
||||
];
|
||||
for (const { name, setNode } of nodes) {
|
||||
test(`replacing collapsed heading with another node (${name}) should unhide content`, () => {
|
||||
const el = h("div", [
|
||||
h("h1", ["A collpased heading"], { "data-collapsed": "true" }),
|
||||
h("p", ["Hidden paragraph"], { "data-hidden": "true" })
|
||||
]);
|
||||
const { editor } = createEditor({
|
||||
extensions: {
|
||||
heading: Heading.configure({ levels: [1, 2, 3, 4, 5, 6] })
|
||||
},
|
||||
initialContent: el.outerHTML
|
||||
});
|
||||
|
||||
editor.commands.setTextSelection(0);
|
||||
setNode(editor);
|
||||
|
||||
expect(editor.getHTML()).toMatchSnapshot();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ export const Heading = TiptapHeading.extend({
|
||||
const headingLevel = currentNode.attrs.level;
|
||||
|
||||
tr.setNodeAttribute(pos, "collapsed", shouldCollapse);
|
||||
toggleNodesUnderHeading(tr, pos, headingLevel, shouldCollapse);
|
||||
toggleNodesUnderPos(tr, pos, headingLevel, shouldCollapse);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -255,17 +255,17 @@ export const Heading = TiptapHeading.extend({
|
||||
}
|
||||
});
|
||||
|
||||
function toggleNodesUnderHeading(
|
||||
function toggleNodesUnderPos(
|
||||
tr: Transaction,
|
||||
headingPos: number,
|
||||
pos: number,
|
||||
headingLevel: number,
|
||||
isCollapsing: boolean
|
||||
) {
|
||||
const { doc } = tr;
|
||||
const headingNode = doc.nodeAt(headingPos);
|
||||
if (!headingNode || headingNode.type.name !== "heading") return;
|
||||
const node = doc.nodeAt(pos);
|
||||
if (!node) return;
|
||||
|
||||
let nextPos = headingPos + headingNode.nodeSize;
|
||||
let nextPos = pos + node.nodeSize;
|
||||
const cursorPos = tr.selection.from;
|
||||
let shouldMoveCursor = false;
|
||||
let insideCollapsedHeading = false;
|
||||
@@ -320,8 +320,8 @@ function toggleNodesUnderHeading(
|
||||
}
|
||||
|
||||
if (shouldMoveCursor) {
|
||||
const headingEndPos = headingPos + headingNode.nodeSize - 1;
|
||||
tr.setSelection(Selection.near(tr.doc.resolve(headingEndPos)));
|
||||
const endPos = pos + node.nodeSize - 1;
|
||||
tr.setSelection(Selection.near(tr.doc.resolve(endPos)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,26 +366,27 @@ const headingUpdatePlugin = new Plugin({
|
||||
let modified = false;
|
||||
|
||||
newDoc.descendants((newNode, pos) => {
|
||||
if (newNode.type.name === "heading") {
|
||||
if (pos >= oldDoc.content.size) return;
|
||||
if (pos >= oldDoc.content.size) return;
|
||||
|
||||
const oldNode = oldDoc.nodeAt(pos);
|
||||
if (
|
||||
oldNode &&
|
||||
oldNode.type.name === "heading" &&
|
||||
oldNode.attrs.level !== newNode.attrs.level
|
||||
) {
|
||||
/**
|
||||
* if the level of a collapsed heading is changed,
|
||||
* we need to reset visibility of all the nodes under it as there
|
||||
* might be a heading of same or higher level previously
|
||||
* hidden under this heading
|
||||
*/
|
||||
if (newNode.attrs.collapsed) {
|
||||
toggleNodesUnderHeading(tr, pos, oldNode.attrs.level, false);
|
||||
toggleNodesUnderHeading(tr, pos, newNode.attrs.level, true);
|
||||
modified = true;
|
||||
}
|
||||
const oldNode = oldDoc.nodeAt(pos);
|
||||
if (
|
||||
oldNode &&
|
||||
oldNode.type.name === "heading" &&
|
||||
oldNode.attrs.level !== newNode.attrs.level
|
||||
) {
|
||||
/**
|
||||
* if the level of a collapsed heading is changed,
|
||||
* we need to reset visibility of all the nodes under it as there
|
||||
* might be a heading of same or higher level previously
|
||||
* hidden under this heading
|
||||
*/
|
||||
if (newNode.type.name === "heading" && newNode.attrs.collapsed) {
|
||||
toggleNodesUnderPos(tr, pos, oldNode.attrs.level, false);
|
||||
toggleNodesUnderPos(tr, pos, newNode.attrs.level, true);
|
||||
modified = true;
|
||||
} else if (newNode.type.name !== "heading" && oldNode.attrs.collapsed) {
|
||||
toggleNodesUnderPos(tr, pos, oldNode.attrs.level, false);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user