Skip to content

Commit 675d7fe

Browse files
committed
refactor: heading action buttons
- Removed ChatCommentExtension and its associated logic from the TipTap editor. - Integrated HeadingActionsExtension to manage heading-related actions, including chat functionality. - Updated editor configuration to support new heading actions and improved mobile handling. - Cleaned up unused styles and plugins related to the removed ChatCommentExtension.
1 parent 3d8015f commit 675d7fe

19 files changed

Lines changed: 693 additions & 677 deletions

packages/webapp/src/components/TipTap/TipTap.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table
7070

7171
import { Indent } from '@docs.plus/extension-indent'
7272

73-
import ChatCommentExtension from './extentions/ChatCommentExtension'
73+
import { HeadingActionsExtension } from './extentions/HeadingActions'
7474
import { IOSCaretFix } from './plugins/iosCaretFixPlugin'
7575

7676
import { InlineCode } from '@docs.plus/extension-inline-code'
@@ -194,7 +194,11 @@ const Editor = ({
194194
Blockquote,
195195
TextAlign,
196196
Underline,
197-
...(isMobile ? [] : [ChatCommentExtension]),
197+
HeadingActionsExtension.configure({
198+
hoverChat: true,
199+
selectionChat: !isMobile,
200+
headingToggle: true
201+
}),
198202
Hyperlink.configure({
199203
protocols: ['ftp', 'mailto'],
200204
hyperlinkOnPaste: false,

packages/webapp/src/components/TipTap/extentions/ChatCommentExtension.ts

Lines changed: 0 additions & 167 deletions
This file was deleted.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Extension } from '@tiptap/core'
2+
import { Plugin } from '@tiptap/pm/state'
3+
import { createHoverChatPlugin } from './plugins/hoverChatPlugin'
4+
import { createSelectionChatPlugin } from './plugins/selectionChatPlugin'
5+
import { createHeadingTogglePlugin } from './plugins/headingTogglePlugin'
6+
import type { HeadingActionsOptions } from './types'
7+
8+
/**
9+
* HeadingActions Extension
10+
*
11+
* A unified extension that combines heading-related action features:
12+
* - Hover chat: Chat button on heading hover
13+
* - Selection chat: Comment button on text selection
14+
* - Heading toggle: Fold/unfold heading sections
15+
*
16+
* @example
17+
* ```typescript
18+
* HeadingActionsExtension.configure({
19+
* hoverChat: true,
20+
* selectionChat: !isMobile,
21+
* headingToggle: true
22+
* })
23+
* ```
24+
*/
25+
export const HeadingActionsExtension = Extension.create<HeadingActionsOptions>({
26+
name: 'headingActions',
27+
28+
addOptions() {
29+
return {
30+
hoverChat: true,
31+
selectionChat: true,
32+
headingToggle: true
33+
}
34+
},
35+
36+
addProseMirrorPlugins() {
37+
const plugins: Plugin[] = []
38+
const { editor } = this
39+
40+
if (this.options.hoverChat) {
41+
plugins.push(createHoverChatPlugin(editor))
42+
}
43+
44+
if (this.options.selectionChat) {
45+
plugins.push(createSelectionChatPlugin(editor))
46+
}
47+
48+
if (this.options.headingToggle) {
49+
plugins.push(createHeadingTogglePlugin(editor))
50+
}
51+
52+
return plugins
53+
}
54+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export {
2+
HeadingActionsExtension,
3+
HeadingActionsExtension as default
4+
} from './HeadingActionsExtension'
5+
export { HEADING_ACTIONS_CLASSES } from './types'
6+
export type { HeadingActionsOptions, HAClassName, HeadingNodeData } from './types'
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Plugin, PluginKey } from '@tiptap/pm/state'
2+
import { TipTapEditor, EditorEventData, TIPTAP_EVENTS, TRANSACTION_META } from '@types'
3+
import * as PubSub from 'pubsub-js'
4+
import { db } from '@db/headingCrinckleDB'
5+
import { useStore } from '@stores'
6+
7+
interface HeadingState {
8+
headingId: string
9+
crinkleOpen: boolean
10+
}
11+
12+
let isProcessing = false
13+
14+
const dispatchToggleHeadingSection = (el: Element): void => {
15+
const headingId = el.getAttribute('data-id')
16+
const detailsContent = el.querySelector('div.contentWrapper')
17+
18+
const event = new CustomEvent('toggleHeadingsContent', {
19+
detail: { headingId, el: detailsContent }
20+
})
21+
22+
detailsContent?.dispatchEvent(event)
23+
}
24+
25+
const handleHeadingToggle = ({ headingId }: EditorEventData): void => {
26+
if (isProcessing || !headingId) return
27+
isProcessing = true
28+
29+
const editor = useStore.getState().settings.editor.instance
30+
const headingNodeEl = editor?.view.dom.querySelector<HTMLElement>(
31+
`.heading[data-id="${headingId}"]`
32+
)
33+
34+
if (!editor?.view || !headingNodeEl) {
35+
isProcessing = false
36+
return
37+
}
38+
39+
const view = editor.view
40+
const tr = editor.state.tr
41+
const nodePos = view.state.doc.resolve(view.posAtDOM(headingNodeEl, 0))
42+
const currentNode = tr.doc.nodeAt(nodePos.pos)
43+
44+
if (!currentNode) {
45+
isProcessing = false
46+
return
47+
}
48+
49+
tr.setMeta(TRANSACTION_META.FOLD_AND_UNFOLD, true)
50+
51+
const documentId = localStorage.getItem('docId')
52+
const headingMapString = localStorage.getItem('headingMap')
53+
const headingMap: HeadingState[] = headingMapString ? JSON.parse(headingMapString) : []
54+
const nodeState = headingMap.find((h) => h.headingId === headingId) || { crinkleOpen: true }
55+
const filterMode = document.body.classList.contains('filter-mode')
56+
const database = filterMode ? db.docFilter : db.meta
57+
58+
const dispatch = () => {
59+
view.dispatch(tr)
60+
dispatchToggleHeadingSection(headingNodeEl)
61+
isProcessing = false
62+
}
63+
64+
if (filterMode) {
65+
dispatch()
66+
return
67+
}
68+
69+
if (!documentId) {
70+
isProcessing = false
71+
return
72+
}
73+
74+
database
75+
.put({
76+
docId: documentId,
77+
headingId,
78+
crinkleOpen: !nodeState.crinkleOpen,
79+
level: currentNode.attrs.level
80+
})
81+
.then(() => {
82+
database.toArray().then((data: any[]) => {
83+
localStorage.setItem('headingMap', JSON.stringify(data))
84+
})
85+
dispatch()
86+
})
87+
.catch((err: Error) => {
88+
console.error(err)
89+
isProcessing = false
90+
})
91+
}
92+
93+
/**
94+
* Creates the heading toggle plugin for fold/unfold functionality
95+
* @param editor - TipTap editor instance
96+
* @returns ProseMirror plugin
97+
*/
98+
export function createHeadingTogglePlugin(_editor: TipTapEditor): Plugin {
99+
return new Plugin({
100+
key: new PluginKey('headingToggle'),
101+
view() {
102+
PubSub.subscribe(TIPTAP_EVENTS.FOLD_AND_UNFOLD, (_msg: string, data: EditorEventData) => {
103+
handleHeadingToggle(data)
104+
})
105+
106+
return {
107+
destroy() {
108+
PubSub.unsubscribe(TIPTAP_EVENTS.FOLD_AND_UNFOLD)
109+
}
110+
}
111+
}
112+
})
113+
}

0 commit comments

Comments
 (0)