mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
Revert "Merge pull request #8493 from 01zulfi/editor/collapsible-headings"
This reverts commit7241dce7a6, reversing changes made toae5b422965.
This commit is contained in:
@@ -704,10 +704,8 @@ function EditorChrome(props: PropsWithChildren<EditorProps>) {
|
||||
);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (!isMobile() && !isTablet()) {
|
||||
editor.style.marginLeft = `-${negativeSpace}px`;
|
||||
editor.style.marginRight = `-${negativeSpace}px`;
|
||||
}
|
||||
editor.style.marginLeft = `-${negativeSpace}px`;
|
||||
editor.style.marginRight = `-${negativeSpace}px`;
|
||||
editor.style.paddingLeft = `${negativeSpace}px`;
|
||||
editor.style.paddingRight = `${negativeSpace}px`;
|
||||
});
|
||||
|
||||
@@ -16,11 +16,13 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { getParentAttributes } from "../../utils/prosemirror.js";
|
||||
import {
|
||||
getParentAttributes,
|
||||
isClickWithinBounds
|
||||
} from "../../utils/prosemirror.js";
|
||||
import { InputRule, Node, mergeAttributes } from "@tiptap/core";
|
||||
InputRule,
|
||||
Node,
|
||||
findParentNodeClosestToPos,
|
||||
mergeAttributes
|
||||
} from "@tiptap/core";
|
||||
import { Paragraph } from "../paragraph/index.js";
|
||||
import { Heading } from "../heading/index.js";
|
||||
import { TextSelection } from "@tiptap/pm/state";
|
||||
@@ -233,9 +235,37 @@ export const Callout = Node.create({
|
||||
|
||||
const pos = typeof getPos === "function" ? getPos() : 0;
|
||||
if (typeof pos !== "number") return;
|
||||
|
||||
const resolvedPos = editor.state.doc.resolve(pos);
|
||||
if (isClickWithinBounds(e, resolvedPos, "right")) {
|
||||
|
||||
const { x, y, width } = e.target.getBoundingClientRect();
|
||||
|
||||
const clientX =
|
||||
e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
|
||||
|
||||
const clientY =
|
||||
e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
|
||||
|
||||
const hitArea = { width: 40, height: 40 };
|
||||
|
||||
const isRtl =
|
||||
e.target.dir === "rtl" ||
|
||||
findParentNodeClosestToPos(
|
||||
resolvedPos,
|
||||
(node) => !!node.attrs.textDirection
|
||||
)?.node.attrs.textDirection === "rtl";
|
||||
|
||||
let xEnd = clientX <= x + width;
|
||||
let xStart = clientX >= x + width - hitArea.width;
|
||||
|
||||
const yStart = clientY >= y;
|
||||
const yEnd = clientY <= y + hitArea.height;
|
||||
|
||||
if (isRtl) {
|
||||
xStart = clientX >= x;
|
||||
xEnd = clientX <= x + hitArea.width;
|
||||
}
|
||||
|
||||
if (xStart && xEnd && yStart && yEnd) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
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>"`;
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
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 { Heading } from "../heading.js";
|
||||
|
||||
test("collapse heading", () => {
|
||||
const { editor } = createEditor({
|
||||
extensions: {
|
||||
heading: Heading.configure({ levels: [1, 2, 3, 4, 5, 6] })
|
||||
},
|
||||
initialContent: `
|
||||
<h1>Main Heading</h1>
|
||||
<p>paragraph.</p>
|
||||
<h2>Subheading</h2>
|
||||
<p>subheading paragraph</p>
|
||||
<h1>Main heading 2</h1>
|
||||
<p>paragraph another</p>
|
||||
`
|
||||
});
|
||||
|
||||
const headingPos = 0;
|
||||
|
||||
editor.commands.command(({ tr }) => {
|
||||
tr.setNodeAttribute(headingPos, "collapsed", true);
|
||||
return true;
|
||||
});
|
||||
|
||||
expect(editor.getHTML()).toMatchSnapshot("heading collapsed");
|
||||
|
||||
editor.commands.command(({ tr }) => {
|
||||
tr.setNodeAttribute(headingPos, "collapsed", false);
|
||||
return true;
|
||||
});
|
||||
|
||||
expect(editor.getHTML()).toMatchSnapshot("heading uncollapsed");
|
||||
});
|
||||
@@ -18,31 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { tiptapKeys } from "@notesnook/common";
|
||||
import {
|
||||
textblockTypeInputRule,
|
||||
findParentNodeClosestToPos
|
||||
} from "@tiptap/core";
|
||||
import { textblockTypeInputRule } from "@tiptap/core";
|
||||
import { Heading as TiptapHeading } from "@tiptap/extension-heading";
|
||||
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||
import { isClickWithinBounds } from "../../utils/prosemirror.js";
|
||||
|
||||
const HEADING_REGEX = /^(#{1,6})\s$/;
|
||||
export const Heading = TiptapHeading.extend({
|
||||
addAttributes() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
collapsed: {
|
||||
default: false,
|
||||
keepOnSplit: false,
|
||||
parseHTML: (element) => element.dataset.collapsed === "true",
|
||||
renderHTML: (attributes) => ({
|
||||
"data-collapsed": attributes.collapsed === true
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
@@ -61,16 +41,6 @@ export const Heading = TiptapHeading.extend({
|
||||
textAlign,
|
||||
textDirection
|
||||
});
|
||||
},
|
||||
toggleHeadingCollapse:
|
||||
(pos: number) =>
|
||||
({ tr }: { tr: any }) => {
|
||||
const node = tr.doc.nodeAt(pos);
|
||||
if (node && node.type === this.type) {
|
||||
tr.setNodeAttribute(pos, "collapsed", !node.attrs.collapsed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -101,114 +71,5 @@ export const Heading = TiptapHeading.extend({
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey("collapsibleHeadings"),
|
||||
props: {
|
||||
decorations: (state) => {
|
||||
const decorations: Decoration[] = [];
|
||||
const { doc } = state;
|
||||
|
||||
doc.descendants((node, pos) => {
|
||||
if (node.type.name === "heading" && node.attrs.collapsed) {
|
||||
const headingLevel = node.attrs.level;
|
||||
let nextPos = pos + node.nodeSize;
|
||||
|
||||
while (nextPos < doc.content.size) {
|
||||
const nextNode = doc.nodeAt(nextPos);
|
||||
if (!nextNode) break;
|
||||
if (
|
||||
nextNode.type.name === "heading" &&
|
||||
nextNode.attrs.level <= headingLevel
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
decorations.push(
|
||||
Decoration.node(nextPos, nextPos + nextNode.nodeSize, {
|
||||
style: "display: none;"
|
||||
})
|
||||
);
|
||||
|
||||
nextPos += nextNode.nodeSize;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return DecorationSet.create(doc, decorations);
|
||||
}
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ({ node, getPos, editor, HTMLAttributes }) => {
|
||||
const heading = document.createElement(`h${node.attrs.level}`);
|
||||
|
||||
for (const attr in HTMLAttributes) {
|
||||
heading.setAttribute(attr, HTMLAttributes[attr]);
|
||||
}
|
||||
|
||||
if (node.attrs.collapsed) heading.dataset.collapsed = "true";
|
||||
else delete heading.dataset.collapsed;
|
||||
|
||||
function onClick(e: MouseEvent | TouchEvent) {
|
||||
if (e instanceof MouseEvent && e.button !== 0) return;
|
||||
if (!(e.target instanceof HTMLHeadingElement)) return;
|
||||
|
||||
const pos = typeof getPos === "function" ? getPos() : 0;
|
||||
if (typeof pos !== "number") return;
|
||||
|
||||
const resolvedPos = editor.state.doc.resolve(pos);
|
||||
const calloutAncestor = findParentNodeClosestToPos(
|
||||
resolvedPos,
|
||||
(node) => node.type.name === "callout"
|
||||
);
|
||||
if (calloutAncestor) return;
|
||||
|
||||
if (isClickWithinBounds(e, resolvedPos, "left")) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
editor.commands.command(({ tr }) => {
|
||||
const currentNode = tr.doc.nodeAt(pos);
|
||||
if (currentNode) {
|
||||
tr.setNodeAttribute(
|
||||
pos,
|
||||
"collapsed",
|
||||
!currentNode.attrs.collapsed
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
heading.onmousedown = onClick;
|
||||
heading.ontouchstart = onClick;
|
||||
|
||||
return {
|
||||
dom: heading,
|
||||
contentDOM: heading,
|
||||
update: (updatedNode) => {
|
||||
if (updatedNode.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (updatedNode.attrs.level !== node.attrs.level) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (updatedNode.attrs.collapsed) heading.dataset.collapsed = "true";
|
||||
else delete heading.dataset.collapsed;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,11 +17,12 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import {
|
||||
findParentNodeOfTypeClosestToPos,
|
||||
isClickWithinBounds
|
||||
} from "../../utils/prosemirror.js";
|
||||
Node,
|
||||
mergeAttributes,
|
||||
findParentNodeClosestToPos
|
||||
} from "@tiptap/core";
|
||||
import { findParentNodeOfTypeClosestToPos } from "../../utils/prosemirror.js";
|
||||
import { OutlineList } from "../outline-list/outline-list.js";
|
||||
import { keybindings, tiptapKeys } from "@notesnook/common";
|
||||
|
||||
@@ -128,9 +129,36 @@ export const OutlineListItem = Node.create<ListItemOptions>({
|
||||
|
||||
const pos = typeof getPos === "function" ? getPos() : 0;
|
||||
if (typeof pos !== "number") return;
|
||||
|
||||
const resolvedPos = editor.state.doc.resolve(pos);
|
||||
if (isClickWithinBounds(e, resolvedPos, "left")) {
|
||||
|
||||
const { x, y, right } = li.getBoundingClientRect();
|
||||
|
||||
const clientX =
|
||||
e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
|
||||
|
||||
const clientY =
|
||||
e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
|
||||
|
||||
const hitArea = { width: 40, height: 40 };
|
||||
|
||||
const isRtl =
|
||||
e.target.dir === "rtl" ||
|
||||
findParentNodeClosestToPos(
|
||||
resolvedPos,
|
||||
(node) => !!node.attrs.textDirection
|
||||
)?.node.attrs.textDirection === "rtl";
|
||||
|
||||
let xStart = clientX >= x - hitArea.width;
|
||||
let xEnd = clientX <= x;
|
||||
const yStart = clientY >= y;
|
||||
const yEnd = clientY <= y + hitArea.height;
|
||||
|
||||
if (isRtl) {
|
||||
xEnd = clientX <= right + hitArea.width;
|
||||
xStart = clientX >= right;
|
||||
}
|
||||
|
||||
if (xStart && xEnd && yStart && yEnd) {
|
||||
e.preventDefault();
|
||||
editor.commands.command(({ tr }) => {
|
||||
tr.setNodeAttribute(
|
||||
|
||||
@@ -345,52 +345,3 @@ export function getDeletedNodes(
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
export function isClickWithinBounds(
|
||||
e: MouseEvent | TouchEvent,
|
||||
pos: ResolvedPos,
|
||||
hitPosition: "left" | "right",
|
||||
hitArea: { width: number; height: number } = { width: 40, height: 40 }
|
||||
) {
|
||||
const { target } = e;
|
||||
if (!(target instanceof HTMLElement)) return false;
|
||||
|
||||
const { x, y, right, width } = target.getBoundingClientRect();
|
||||
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
|
||||
const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
|
||||
const isRtl =
|
||||
target.dir === "rtl" ||
|
||||
findParentNodeClosestToPos(pos, (node) => !!node.attrs.textDirection)?.node
|
||||
.attrs.textDirection === "rtl";
|
||||
|
||||
switch (hitPosition) {
|
||||
case "left": {
|
||||
let xStart = clientX >= x - hitArea.width;
|
||||
let xEnd = clientX <= x;
|
||||
const yStart = clientY >= y;
|
||||
const yEnd = clientY <= y + hitArea.height;
|
||||
|
||||
if (isRtl) {
|
||||
xEnd = clientX <= x + hitArea.width;
|
||||
xStart = clientX >= right;
|
||||
}
|
||||
|
||||
return xStart && xEnd && yStart && yEnd;
|
||||
}
|
||||
case "right": {
|
||||
let xEnd = clientX <= x + width;
|
||||
let xStart = clientX >= x + width - hitArea.width;
|
||||
const yStart = clientY >= y;
|
||||
const yEnd = clientY <= y + hitArea.height;
|
||||
|
||||
if (isRtl) {
|
||||
xStart = clientX >= x;
|
||||
xEnd = clientX <= x + hitArea.width;
|
||||
}
|
||||
|
||||
return xStart && xEnd && yStart && yEnd;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -800,70 +800,6 @@ del.diffdel {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ProseMirror h1,
|
||||
.ProseMirror h2,
|
||||
.ProseMirror h3,
|
||||
.ProseMirror h4,
|
||||
.ProseMirror h5,
|
||||
.ProseMirror h6 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ProseMirror h1::before,
|
||||
.ProseMirror h2::before,
|
||||
.ProseMirror h3::before,
|
||||
.ProseMirror h4::before,
|
||||
.ProseMirror h5::before,
|
||||
.ProseMirror h6::before {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
content: "";
|
||||
background-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
||||
background-color: var(--icon);
|
||||
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9IiM4ODg4ODgiIGQ9Ik03LjQxIDguNThMMTIgMTMuMTdsNC41OS00LjU5TDE4IDEwbC02IDZsLTYtNmwxLjQxLTEuNDJaIi8+PC9zdmc+)
|
||||
no-repeat 50% 50%;
|
||||
mask-size: cover;
|
||||
border: 1px solid var(--background);
|
||||
|
||||
transform: rotate(0);
|
||||
transition: transform 250ms ease, opacity 200ms ease;
|
||||
left: -22px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ProseMirror h1[data-collapsed="true"]::before,
|
||||
.ProseMirror h2[data-collapsed="true"]::before,
|
||||
.ProseMirror h3[data-collapsed="true"]::before,
|
||||
.ProseMirror h4[data-collapsed="true"]::before,
|
||||
.ProseMirror h5[data-collapsed="true"]::before,
|
||||
.ProseMirror h6[data-collapsed="true"]::before {
|
||||
transform: rotate(-90deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ProseMirror h1:hover::before,
|
||||
.ProseMirror h2:hover::before,
|
||||
.ProseMirror h3:hover::before,
|
||||
.ProseMirror h4:hover::before,
|
||||
.ProseMirror h5:hover::before,
|
||||
.ProseMirror h6:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ProseMirror div.callout h1::before,
|
||||
.ProseMirror div.callout h2::before,
|
||||
.ProseMirror div.callout h3::before,
|
||||
.ProseMirror div.callout h4::before,
|
||||
.ProseMirror div.callout h5::before,
|
||||
.ProseMirror div.callout h6::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* simplebar */
|
||||
.simplebar-track {
|
||||
height: 9px !important;
|
||||
|
||||
Reference in New Issue
Block a user