Merge branch 'beta' into rn-81

This commit is contained in:
Ammar Ahmed
2025-11-25 11:22:26 +05:00
13 changed files with 287 additions and 190 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.3.5",
"version": "3.3.6-beta.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.3.5",
"version": "3.3.6-beta.0",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.5",
"version": "3.3.6-beta.0",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.3.5",
"version": "3.3.6-beta.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.3.5",
"version": "3.3.6-beta.0",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.3.5",
"version": "3.3.6-beta.0",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -30,13 +30,18 @@ export async function handleDrop(
item:
| ItemReference
| Context
| { type: "trash" | "notebooks" | "favorites" | undefined }
| { type: "trash" | "notebooks" | "favorites" | "archive" | undefined }
) {
if (!item.type) return;
const noteIds = getDragData(dataTransfer, "note");
const notebookIds = getDragData(dataTransfer, "notebook");
const { setColor, favorite, delete: trashNotes } = useNoteStore.getState();
const {
setColor,
favorite,
delete: trashNotes,
archive
} = useNoteStore.getState();
switch (item.type) {
case "notebook":
if (noteIds.length > 0) {
@@ -83,5 +88,8 @@ export async function handleDrop(
await useNoteStore.getState().refresh();
}
break;
case "archive":
archive(true, ...noteIds);
break;
}
}

View File

@@ -118,7 +118,7 @@ function countCharacters(text: string) {
function countParagraphs(fragment: Fragment) {
let count = 0;
fragment.nodesBetween(0, fragment.size, (node) => {
if (node.type.name === "paragraph") {
if (node.type.name === "paragraph" && node.content.size > 0) {
count++;
}
return true;
@@ -737,7 +737,7 @@ function toIEditor(editor: Editor): IEditor {
function getSelectedParagraphs(editor: Editor, selection: Selection): number {
let count = 0;
editor.state.doc.nodesBetween(selection.from, selection.to, (node) => {
if (node.type.name === "paragraph") {
if (node.type.name === "paragraph" && node.content.size > 0) {
count++;
}
return true;

View File

@@ -506,6 +506,8 @@ function RouteItem({
? "trash"
: item.path === "/favorites"
? "favorites"
: item.path == "/archive"
? "archive"
: undefined
});
}}

View File

@@ -91,18 +91,7 @@ const features: Record<FeatureKeys, Feature> = {
)
}
]
: [
{
title: "Notesnook Circle",
subtitle:
"Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom. As a member you get exclusive discounts and offers from our partners. Check it out in Settings > Notesnook Circle."
},
{
title: "Collapsible headings",
subtitle:
"You can now collapse and expand headings in your notes. This makes it easier to manage large notes and focus on specific sections."
}
],
: [],
cta: {
title: strings.gotIt(),
icon: Checkmark,

View File

@@ -666,6 +666,15 @@ class UserManager {
usesFallback: await this.usesFallbackPWHash(old_password)
});
// retrieve user keys before deriving a new encryption key
const oldUserKeys = {
attachmentsKey: await this.getAttachmentsKey(),
monographPasswordsKey: await this.getMonographPasswordsKey(),
inboxKeys: (await this.hasInboxKeys())
? await this.getInboxKeys()
: undefined
} as const;
await this.db.storage().deriveCryptoKey({
password: new_password,
salt
@@ -678,27 +687,33 @@ class UserManager {
const userEncryptionKey = await this.getEncryptionKey();
if (userEncryptionKey) {
const updateUserPayload: Partial<User> = {};
const attachmentsKey = await this.getAttachmentsKey();
if (attachmentsKey) {
if (oldUserKeys.attachmentsKey) {
user.attachmentsKey = await this.db
.storage()
.encrypt(userEncryptionKey, JSON.stringify(attachmentsKey));
.encrypt(
userEncryptionKey,
JSON.stringify(oldUserKeys.attachmentsKey)
);
updateUserPayload.attachmentsKey = user.attachmentsKey;
}
const monographPasswordsKey = await this.getMonographPasswordsKey();
if (monographPasswordsKey) {
if (oldUserKeys.monographPasswordsKey) {
user.monographPasswordsKey = await this.db
.storage()
.encrypt(userEncryptionKey, JSON.stringify(monographPasswordsKey));
.encrypt(
userEncryptionKey,
JSON.stringify(oldUserKeys.monographPasswordsKey)
);
updateUserPayload.monographPasswordsKey = user.monographPasswordsKey;
}
const inboxKeys = await this.getInboxKeys();
if (inboxKeys) {
if (oldUserKeys.inboxKeys) {
user.inboxKeys = {
public: inboxKeys.publicKey,
public: oldUserKeys.inboxKeys.publicKey,
private: await this.db
.storage()
.encrypt(userEncryptionKey, JSON.stringify(inboxKeys.privateKey))
.encrypt(
userEncryptionKey,
JSON.stringify(oldUserKeys.inboxKeys.privateKey)
)
};
updateUserPayload.inboxKeys = user.inboxKeys;
}

View File

@@ -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>"`;

View File

@@ -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();
});
}

View File

@@ -23,10 +23,8 @@ import {
textblockTypeInputRule
} from "@tiptap/core";
import { Heading as TiptapHeading } from "@tiptap/extension-heading";
import { isClickWithinBounds } from "../../utils/prosemirror.js";
import { Plugin, PluginKey, Selection, Transaction } from "@tiptap/pm/state";
import { Node } from "@tiptap/pm/model";
import { useToolbarStore } from "../../toolbar/stores/toolbar-store.js";
const COLLAPSIBLE_BLOCK_TYPES = [
"paragraph",
@@ -168,6 +166,14 @@ export const Heading = TiptapHeading.extend({
addNodeView() {
return ({ node, getPos, editor, HTMLAttributes }) => {
const heading = document.createElement(`h${node.attrs.level}`);
const contentWrapper = document.createElement("div");
const icon = document.createElement("span");
// providing a minWidth so that empty headings show the blinking cursor
contentWrapper.style.minWidth = "1px";
icon.className = "heading-collapse-icon";
icon.contentEditable = "false";
for (const attr in HTMLAttributes) {
heading.setAttribute(attr, HTMLAttributes[attr]);
@@ -176,50 +182,46 @@ export const Heading = TiptapHeading.extend({
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;
function onIconClick(e: MouseEvent | TouchEvent) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
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;
const forbiddenParents = ["callout"];
if (
isClickWithinBounds(
e,
resolvedPos,
useToolbarStore.getState().isMobile ? "right" : "left"
findParentNodeClosestToPos(resolvedPos, (node) =>
forbiddenParents.includes(node.type.name)
)
) {
e.preventDefault();
e.stopImmediatePropagation();
editor.commands.command(({ tr }) => {
const currentNode = tr.doc.nodeAt(pos);
if (currentNode && currentNode.type.name === "heading") {
const shouldCollapse = !currentNode.attrs.collapsed;
const headingLevel = currentNode.attrs.level;
tr.setNodeAttribute(pos, "collapsed", shouldCollapse);
toggleNodesUnderHeading(tr, pos, headingLevel, shouldCollapse);
}
return true;
});
return;
}
editor.commands.command(({ tr }) => {
const currentNode = tr.doc.nodeAt(pos);
if (currentNode && currentNode.type.name === "heading") {
const shouldCollapse = !currentNode.attrs.collapsed;
const headingLevel = currentNode.attrs.level;
tr.setNodeAttribute(pos, "collapsed", shouldCollapse);
toggleNodesUnderPos(tr, pos, headingLevel, shouldCollapse);
}
return true;
});
}
heading.onmousedown = onClick;
heading.ontouchstart = onClick;
icon.onmousedown = onIconClick;
icon.ontouchend = onIconClick;
heading.appendChild(contentWrapper);
heading.appendChild(icon);
return {
dom: heading,
contentDOM: heading,
contentDOM: contentWrapper,
update: (updatedNode) => {
if (updatedNode.type !== this.type) {
return false;
@@ -253,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;
@@ -318,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)));
}
}
@@ -364,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;
}
}
});

View File

@@ -316,6 +316,31 @@ img.ProseMirror-separator {
.ProseMirror table p {
margin: 0;
}
.ProseMirror td > h1:first-child,
.ProseMirror td > h2:first-child,
.ProseMirror td > h3:first-child,
.ProseMirror td > h4:first-child,
.ProseMirror td > h5:first-child,
.ProseMirror td > h6:first-child {
margin-top: 0;
}
.ProseMirror td > ol,
.ProseMirror td > ul {
padding-left: 20px;
margin-top: 0;
}
.ProseMirror td > blockquote {
margin-left: 0;
margin-top: 0;
}
.ProseMirror td > blockquote > :first-child {
margin-top: 0;
}
/*
.resize-cursor {
@@ -739,6 +764,10 @@ p > *::selection {
mask-size: cover;
}
.simple-checklist > li.checked p {
opacity: 0.8;
}
/* Callout */
.ProseMirror div.callout {
padding: 15px;
@@ -866,150 +895,133 @@ del.diffdel {
text-decoration: none;
}
.ProseMirror h1,
.ProseMirror h2,
.ProseMirror h3,
.ProseMirror h4,
.ProseMirror h5,
.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;
.ProseMirror h1 .heading-collapse-icon,
.ProseMirror h2 .heading-collapse-icon,
.ProseMirror h3 .heading-collapse-icon,
.ProseMirror h4 .heading-collapse-icon,
.ProseMirror h5 .heading-collapse-icon,
.ProseMirror h6 .heading-collapse-icon {
cursor: pointer;
content: "";
background-size: 18px;
width: 18px;
height: 18px;
margin-inline-start: 8px;
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;
user-select: none;
}
.ProseMirror h1[dir="rtl"]::before,
.ProseMirror h2[dir="rtl"]::before,
.ProseMirror h3[dir="rtl"]::before,
.ProseMirror h4[dir="rtl"]::before,
.ProseMirror h5[dir="rtl"]::before,
.ProseMirror h6[dir="rtl"]::before {
display: none;
}
.ProseMirror h1[dir="rtl"]::after,
.ProseMirror h2[dir="rtl"]::after,
.ProseMirror h3[dir="rtl"]::after,
.ProseMirror h4[dir="rtl"]::after,
.ProseMirror h5[dir="rtl"]::after,
.ProseMirror h6[dir="rtl"]::after {
position: absolute;
cursor: pointer;
content: "";
background-size: 18px;
.ProseMirror h1 .heading-collapse-icon {
margin-top: 3.5px;
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(0deg);
transition: transform 250ms ease, opacity 200ms ease;
right: -22px;
opacity: 0;
}
.ProseMirror h1::before,
.ProseMirror h1::after {
top: 8px;
.ProseMirror h2 .heading-collapse-icon {
margin-top: 3px;
width: 16px;
height: 16px;
}
.ProseMirror h2::before,
.ProseMirror h2::after
{
top: 3px;
.ProseMirror h3 .heading-collapse-icon {
margin-top: 2.3px;
width: 15px;
height: 15px;
}
.ProseMirror h3::before,
.ProseMirror h3::after {
top: 0px;
.ProseMirror h4 .heading-collapse-icon {
margin-top: 1.8px;
width: 14px;
height: 14px;
}
.ProseMirror h4::before,
.ProseMirror h4::after {
top: -1px;
.ProseMirror h5 .heading-collapse-icon {
margin-top: 1.3px;
width: 13px;
height: 13px;
}
.ProseMirror h5::before,
.ProseMirror h5::after {
top: -2px;
.ProseMirror h6 .heading-collapse-icon {
margin-top: 0.3px;
width: 12px;
height: 12px;
}
.ProseMirror h6::before,
.ProseMirror h6::after {
top: -4px;
}
.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 {
.ProseMirror h1[data-collapsed="true"] .heading-collapse-icon,
.ProseMirror h2[data-collapsed="true"] .heading-collapse-icon,
.ProseMirror h3[data-collapsed="true"] .heading-collapse-icon,
.ProseMirror h4[data-collapsed="true"] .heading-collapse-icon,
.ProseMirror h5[data-collapsed="true"] .heading-collapse-icon,
.ProseMirror h6[data-collapsed="true"] .heading-collapse-icon {
transform: rotate(-90deg);
opacity: 1;
}
.ProseMirror h1[data-collapsed="true"]::after,
.ProseMirror h2[data-collapsed="true"]::after,
.ProseMirror h3[data-collapsed="true"]::after,
.ProseMirror h4[data-collapsed="true"]::after,
.ProseMirror h5[data-collapsed="true"]::after,
.ProseMirror h6[data-collapsed="true"]::after {
.ProseMirror h1[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
.ProseMirror h2[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
.ProseMirror h3[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
.ProseMirror h4[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
.ProseMirror h5[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
.ProseMirror h6[data-collapsed="true"][dir="rtl"] .heading-collapse-icon {
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,
.ProseMirror h1:hover::after,
.ProseMirror h2:hover::after,
.ProseMirror h3:hover::after,
.ProseMirror h4:hover::after,
.ProseMirror h5:hover::after,
.ProseMirror h6:hover::after {
.ProseMirror h1:hover .heading-collapse-icon,
.ProseMirror h2:hover .heading-collapse-icon,
.ProseMirror h3:hover .heading-collapse-icon,
.ProseMirror h4:hover .heading-collapse-icon,
.ProseMirror h5:hover .heading-collapse-icon,
.ProseMirror h6:hover .heading-collapse-icon {
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 {
.ProseMirror div.callout h1 .heading-collapse-icon,
.ProseMirror div.callout h2 .heading-collapse-icon,
.ProseMirror div.callout h3 .heading-collapse-icon,
.ProseMirror div.callout h4 .heading-collapse-icon,
.ProseMirror div.callout h5 .heading-collapse-icon,
.ProseMirror div.callout h6 .heading-collapse-icon {
display: none;
}
/* hide collapse icon when heading is empty (only contains trailing break) */
.ProseMirror h1:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
.ProseMirror h2:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
.ProseMirror h3:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
.ProseMirror h4:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
.ProseMirror h5:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
.ProseMirror h6:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon {
display: none !important;
}
@media screen and (max-width: 768px) {
.ProseMirror h1 .heading-collapse-icon,
.ProseMirror h2 .heading-collapse-icon,
.ProseMirror h3 .heading-collapse-icon,
.ProseMirror h4 .heading-collapse-icon,
.ProseMirror h5 .heading-collapse-icon,
.ProseMirror h6 .heading-collapse-icon {
opacity: 1 !important;
}
}
[data-hidden="true"] {
display: none !important;
}
@@ -1028,3 +1040,4 @@ del.diffdel {
pre[class*="language-"] {
overflow: initial !important;
}