Skip to content

Commit bdd78df

Browse files
01zulfithecodrr
andauthored
editor: change heading collapse icon pos (#8953)
* editor: enable heading in table, change collapse icon pos, && disable empty heading collapse Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * editor: migrate empty collapsed headings in parseHTML instead of plugin Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * editor: remove migration for empty collapsed headings Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * editor: fix heading collapse on mobile Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * editor: use ontouchend instead of ontouchstart --------- Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
1 parent cec05b6 commit bdd78df

File tree

2 files changed

+110
-142
lines changed

2 files changed

+110
-142
lines changed

packages/editor/src/extensions/heading/heading.ts

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ import {
2323
textblockTypeInputRule
2424
} from "@tiptap/core";
2525
import { Heading as TiptapHeading } from "@tiptap/extension-heading";
26-
import { isClickWithinBounds } from "../../utils/prosemirror.js";
2726
import { Plugin, PluginKey, Selection, Transaction } from "@tiptap/pm/state";
2827
import { Node } from "@tiptap/pm/model";
29-
import { useToolbarStore } from "../../toolbar/stores/toolbar-store.js";
3028

3129
const COLLAPSIBLE_BLOCK_TYPES = [
3230
"paragraph",
@@ -168,6 +166,14 @@ export const Heading = TiptapHeading.extend({
168166
addNodeView() {
169167
return ({ node, getPos, editor, HTMLAttributes }) => {
170168
const heading = document.createElement(`h${node.attrs.level}`);
169+
const contentWrapper = document.createElement("div");
170+
const icon = document.createElement("span");
171+
172+
// providing a minWidth so that empty headings show the blinking cursor
173+
contentWrapper.style.minWidth = "1px";
174+
175+
icon.className = "heading-collapse-icon";
176+
icon.contentEditable = "false";
171177

172178
for (const attr in HTMLAttributes) {
173179
heading.setAttribute(attr, HTMLAttributes[attr]);
@@ -176,15 +182,16 @@ export const Heading = TiptapHeading.extend({
176182
if (node.attrs.collapsed) heading.dataset.collapsed = "true";
177183
else delete heading.dataset.collapsed;
178184

179-
function onClick(e: MouseEvent | TouchEvent) {
180-
if (e instanceof MouseEvent && e.button !== 0) return;
181-
if (!(e.target instanceof HTMLHeadingElement)) return;
185+
function onIconClick(e: MouseEvent | TouchEvent) {
186+
e.preventDefault();
187+
e.stopPropagation();
188+
e.stopImmediatePropagation();
182189

183190
const pos = typeof getPos === "function" ? getPos() : 0;
184191
if (typeof pos !== "number") return;
185192

186193
const resolvedPos = editor.state.doc.resolve(pos);
187-
const forbiddenParents = ["callout", "table"];
194+
const forbiddenParents = ["callout"];
188195
if (
189196
findParentNodeClosestToPos(resolvedPos, (node) =>
190197
forbiddenParents.includes(node.type.name)
@@ -193,36 +200,28 @@ export const Heading = TiptapHeading.extend({
193200
return;
194201
}
195202

196-
if (
197-
isClickWithinBounds(
198-
e,
199-
resolvedPos,
200-
useToolbarStore.getState().isMobile ? "right" : "left"
201-
)
202-
) {
203-
e.preventDefault();
204-
e.stopImmediatePropagation();
205-
206-
editor.commands.command(({ tr }) => {
207-
const currentNode = tr.doc.nodeAt(pos);
208-
if (currentNode && currentNode.type.name === "heading") {
209-
const shouldCollapse = !currentNode.attrs.collapsed;
210-
const headingLevel = currentNode.attrs.level;
203+
editor.commands.command(({ tr }) => {
204+
const currentNode = tr.doc.nodeAt(pos);
205+
if (currentNode && currentNode.type.name === "heading") {
206+
const shouldCollapse = !currentNode.attrs.collapsed;
207+
const headingLevel = currentNode.attrs.level;
211208

212-
tr.setNodeAttribute(pos, "collapsed", shouldCollapse);
213-
toggleNodesUnderHeading(tr, pos, headingLevel, shouldCollapse);
214-
}
215-
return true;
216-
});
217-
}
209+
tr.setNodeAttribute(pos, "collapsed", shouldCollapse);
210+
toggleNodesUnderHeading(tr, pos, headingLevel, shouldCollapse);
211+
}
212+
return true;
213+
});
218214
}
219215

220-
heading.onmousedown = onClick;
221-
heading.ontouchstart = onClick;
216+
icon.onmousedown = onIconClick;
217+
icon.ontouchend = onIconClick;
218+
219+
heading.appendChild(contentWrapper);
220+
heading.appendChild(icon);
222221

223222
return {
224223
dom: heading,
225-
contentDOM: heading,
224+
contentDOM: contentWrapper,
226225
update: (updatedNode) => {
227226
if (updatedNode.type !== this.type) {
228227
return false;

packages/editor/styles/styles.css

Lines changed: 81 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -870,163 +870,131 @@ del.diffdel {
870870
text-decoration: none;
871871
}
872872

873-
.ProseMirror h1,
874-
.ProseMirror h2,
875-
.ProseMirror h3,
876-
.ProseMirror h4,
877-
.ProseMirror h5,
873+
.ProseMirror h1 ,
874+
.ProseMirror h2 ,
875+
.ProseMirror h3 ,
876+
.ProseMirror h4 ,
877+
.ProseMirror h5 ,
878878
.ProseMirror h6 {
879-
position: relative;
879+
display: flex;
880+
align-items: center;
880881
}
881882

882-
.ProseMirror h1::before,
883-
.ProseMirror h2::before,
884-
.ProseMirror h3::before,
885-
.ProseMirror h4::before,
886-
.ProseMirror h5::before,
887-
.ProseMirror h6::before {
888-
position: absolute;
883+
.ProseMirror h1 .heading-collapse-icon,
884+
.ProseMirror h2 .heading-collapse-icon,
885+
.ProseMirror h3 .heading-collapse-icon,
886+
.ProseMirror h4 .heading-collapse-icon,
887+
.ProseMirror h5 .heading-collapse-icon,
888+
.ProseMirror h6 .heading-collapse-icon {
889889
cursor: pointer;
890-
content: "";
891890
background-size: 18px;
892-
width: 18px;
893-
height: 18px;
891+
margin-inline-start: 8px;
894892

895893
background-color: var(--icon);
896894
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9IiM4ODg4ODgiIGQ9Ik03LjQxIDguNThMMTIgMTMuMTdsNC41OS00LjU5TDE4IDEwbC02IDZsLTYtNmwxLjQxLTEuNDJaIi8+PC9zdmc+)
897895
no-repeat 50% 50%;
898896
mask-size: cover;
899-
border: 1px solid var(--background);
900897

901898
transform: rotate(0);
902899
transition: transform 250ms ease, opacity 200ms ease;
903-
left: -22px;
904900
opacity: 0;
901+
user-select: none;
905902
}
906903

907-
.ProseMirror h1[dir="rtl"]::before,
908-
.ProseMirror h2[dir="rtl"]::before,
909-
.ProseMirror h3[dir="rtl"]::before,
910-
.ProseMirror h4[dir="rtl"]::before,
911-
.ProseMirror h5[dir="rtl"]::before,
912-
.ProseMirror h6[dir="rtl"]::before {
913-
display: none;
914-
}
915-
916-
.ProseMirror h1[dir="rtl"]::after,
917-
.ProseMirror h2[dir="rtl"]::after,
918-
.ProseMirror h3[dir="rtl"]::after,
919-
.ProseMirror h4[dir="rtl"]::after,
920-
.ProseMirror h5[dir="rtl"]::after,
921-
.ProseMirror h6[dir="rtl"]::after {
922-
position: absolute;
923-
cursor: pointer;
924-
content: "";
925-
background-size: 18px;
904+
.ProseMirror h1 .heading-collapse-icon {
905+
margin-top: 3.5px;
926906
width: 18px;
927907
height: 18px;
928-
929-
background-color: var(--icon);
930-
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9IiM4ODg4ODgiIGQ9Ik03LjQxIDguNThMMTIgMTMuMTdsNC41OS00LjU5TDE4IDEwbC02IDZsLTYtNmwxLjQxLTEuNDJaIi8+PC9zdmc+)
931-
no-repeat 50% 50%;
932-
mask-size: cover;
933-
border: 1px solid var(--background);
934-
935-
transform: rotate(0deg);
936-
transition: transform 250ms ease, opacity 200ms ease;
937-
right: -22px;
938-
opacity: 0;
939908
}
940909

941-
.ProseMirror h1::before,
942-
.ProseMirror h1::after {
943-
top: 8px;
910+
.ProseMirror h2 .heading-collapse-icon {
911+
margin-top: 3px;
912+
width: 16px;
913+
height: 16px;
944914
}
945915

946-
.ProseMirror h2::before,
947-
.ProseMirror h2::after
948-
{
949-
top: 3px;
916+
.ProseMirror h3 .heading-collapse-icon {
917+
margin-top: 2.3px;
918+
width: 15px;
919+
height: 15px;
950920
}
951921

952-
.ProseMirror h3::before,
953-
.ProseMirror h3::after {
954-
top: 0px;
922+
.ProseMirror h4 .heading-collapse-icon {
923+
margin-top: 1.8px;
924+
width: 14px;
925+
height: 14px;
955926
}
956927

957-
.ProseMirror h4::before,
958-
.ProseMirror h4::after {
959-
top: -1px;
928+
.ProseMirror h5 .heading-collapse-icon {
929+
margin-top: 1.3px;
930+
width: 13px;
931+
height: 13px;
960932
}
961933

962-
.ProseMirror h5::before,
963-
.ProseMirror h5::after {
964-
top: -2px;
934+
.ProseMirror h6 .heading-collapse-icon {
935+
margin-top: 0.3px;
936+
width: 12px;
937+
height: 12px;
965938
}
966939

967-
.ProseMirror h6::before,
968-
.ProseMirror h6::after {
969-
top: -4px;
970-
}
971940

972-
.ProseMirror h1[data-collapsed="true"]::before,
973-
.ProseMirror h2[data-collapsed="true"]::before,
974-
.ProseMirror h3[data-collapsed="true"]::before,
975-
.ProseMirror h4[data-collapsed="true"]::before,
976-
.ProseMirror h5[data-collapsed="true"]::before,
977-
.ProseMirror h6[data-collapsed="true"]::before {
941+
.ProseMirror h1[data-collapsed="true"] .heading-collapse-icon,
942+
.ProseMirror h2[data-collapsed="true"] .heading-collapse-icon,
943+
.ProseMirror h3[data-collapsed="true"] .heading-collapse-icon,
944+
.ProseMirror h4[data-collapsed="true"] .heading-collapse-icon,
945+
.ProseMirror h5[data-collapsed="true"] .heading-collapse-icon,
946+
.ProseMirror h6[data-collapsed="true"] .heading-collapse-icon {
978947
transform: rotate(-90deg);
979948
opacity: 1;
980949
}
981950

982-
.ProseMirror h1[data-collapsed="true"]::after,
983-
.ProseMirror h2[data-collapsed="true"]::after,
984-
.ProseMirror h3[data-collapsed="true"]::after,
985-
.ProseMirror h4[data-collapsed="true"]::after,
986-
.ProseMirror h5[data-collapsed="true"]::after,
987-
.ProseMirror h6[data-collapsed="true"]::after {
951+
.ProseMirror h1[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
952+
.ProseMirror h2[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
953+
.ProseMirror h3[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
954+
.ProseMirror h4[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
955+
.ProseMirror h5[data-collapsed="true"][dir="rtl"] .heading-collapse-icon,
956+
.ProseMirror h6[data-collapsed="true"][dir="rtl"] .heading-collapse-icon {
988957
transform: rotate(90deg);
989958
opacity: 1;
990959
}
991960

992-
993-
.ProseMirror h1:hover::before,
994-
.ProseMirror h2:hover::before,
995-
.ProseMirror h3:hover::before,
996-
.ProseMirror h4:hover::before,
997-
.ProseMirror h5:hover::before,
998-
.ProseMirror h6:hover::before,
999-
.ProseMirror h1:hover::after,
1000-
.ProseMirror h2:hover::after,
1001-
.ProseMirror h3:hover::after,
1002-
.ProseMirror h4:hover::after,
1003-
.ProseMirror h5:hover::after,
1004-
.ProseMirror h6:hover::after {
961+
.ProseMirror h1:hover .heading-collapse-icon,
962+
.ProseMirror h2:hover .heading-collapse-icon,
963+
.ProseMirror h3:hover .heading-collapse-icon,
964+
.ProseMirror h4:hover .heading-collapse-icon,
965+
.ProseMirror h5:hover .heading-collapse-icon,
966+
.ProseMirror h6:hover .heading-collapse-icon {
1005967
opacity: 1;
1006968
}
1007969

1008-
.ProseMirror div.callout h1::before,
1009-
.ProseMirror div.callout h2::before,
1010-
.ProseMirror div.callout h3::before,
1011-
.ProseMirror div.callout h4::before,
1012-
.ProseMirror div.callout h5::before,
1013-
.ProseMirror div.callout h6::before {
970+
.ProseMirror div.callout h1 .heading-collapse-icon,
971+
.ProseMirror div.callout h2 .heading-collapse-icon,
972+
.ProseMirror div.callout h3 .heading-collapse-icon,
973+
.ProseMirror div.callout h4 .heading-collapse-icon,
974+
.ProseMirror div.callout h5 .heading-collapse-icon,
975+
.ProseMirror div.callout h6 .heading-collapse-icon {
1014976
display: none;
1015977
}
1016978

1017-
.ProseMirror table h1::before,
1018-
.ProseMirror table h2::before,
1019-
.ProseMirror table h3::before,
1020-
.ProseMirror table h4::before,
1021-
.ProseMirror table h5::before,
1022-
.ProseMirror table h6::before,
1023-
.ProseMirror table h1::after,
1024-
.ProseMirror table h2::after,
1025-
.ProseMirror table h3::after,
1026-
.ProseMirror table h4::after,
1027-
.ProseMirror table h5::after,
1028-
.ProseMirror table h6::after {
1029-
display: none;
979+
/* hide collapse icon when heading is empty (only contains trailing break) */
980+
.ProseMirror h1:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
981+
.ProseMirror h2:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
982+
.ProseMirror h3:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
983+
.ProseMirror h4:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
984+
.ProseMirror h5:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon,
985+
.ProseMirror h6:has(> div > br.ProseMirror-trailingBreak:only-child) .heading-collapse-icon {
986+
display: none !important;
987+
}
988+
989+
@media screen and (max-width: 768px) {
990+
.ProseMirror h1 .heading-collapse-icon,
991+
.ProseMirror h2 .heading-collapse-icon,
992+
.ProseMirror h3 .heading-collapse-icon,
993+
.ProseMirror h4 .heading-collapse-icon,
994+
.ProseMirror h5 .heading-collapse-icon,
995+
.ProseMirror h6 .heading-collapse-icon {
996+
opacity: 1 !important;
997+
}
1030998
}
1031999

10321000
[data-hidden="true"] {
@@ -1047,3 +1015,4 @@ del.diffdel {
10471015
pre[class*="language-"] {
10481016
overflow: initial !important;
10491017
}
1018+

0 commit comments

Comments
 (0)