@lazlon-platform/html-editor 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/hooks/textMarks.ts
CHANGED
|
@@ -149,39 +149,8 @@ function useTextMarksState(
|
|
|
149
149
|
return null
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
// HACK:
|
|
153
|
-
// Clicking on the toolbar blurs the editor, but for editing actions we need to track it.
|
|
154
|
-
// We cannot do it purely in the `useFocusedTiptap` hook because the component using it
|
|
155
|
-
// might render *after* the blur, e.g the sidebar, but the `useTextMarks` hook operates
|
|
156
|
-
// on locally selected editor instances, e.g CalendarFields in the sidebar. We might be
|
|
157
|
-
// able to preserve the local state with an <Activity> but I feel like it makes more sense
|
|
158
|
-
// to handle it here, since there might only be one focused editor at a time either way.
|
|
159
|
-
let lastFocusedEditor: EditableNode | null = null
|
|
160
|
-
|
|
161
|
-
function getFocusedEditor(node: EditableNode[]) {
|
|
162
|
-
const focused = node.find((e) => e.tiptap.isFocused)
|
|
163
|
-
|
|
164
|
-
if (focused) {
|
|
165
|
-
return (lastFocusedEditor = focused)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (lastFocusedEditor && node.includes(lastFocusedEditor)) {
|
|
169
|
-
return lastFocusedEditor
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return null
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export function blurNode(node: EditableNode) {
|
|
176
|
-
if (lastFocusedEditor === node) {
|
|
177
|
-
node.tiptap.commands.blur()
|
|
178
|
-
node.tiptap.commands.selectAll()
|
|
179
|
-
lastFocusedEditor = null
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
152
|
function useFocusedTiptap(editables: EditableNode[]) {
|
|
184
|
-
const [focused, setFocused] = useState(
|
|
153
|
+
const [focused, setFocused] = useState(EditableNode.getFocused(editables))
|
|
185
154
|
|
|
186
155
|
useEffect(() => {
|
|
187
156
|
const dispose = editables.map((e) => {
|
|
@@ -13,6 +13,7 @@ export type EditableNodeProps = NodeProps &
|
|
|
13
13
|
export abstract class EditableNode extends Node {
|
|
14
14
|
@state accessor contentRef: HTMLElement | null = null
|
|
15
15
|
@state private accessor _isEmpty: boolean = true
|
|
16
|
+
@state private accessor _isFocused: boolean = false
|
|
16
17
|
@state private accessor _content: JSONContent = { type: "doc", content: [] }
|
|
17
18
|
|
|
18
19
|
@state accessor lineHeight: number
|
|
@@ -33,6 +34,10 @@ export abstract class EditableNode extends Node {
|
|
|
33
34
|
return this._isEmpty
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
@computed get isFocused() {
|
|
38
|
+
return this._isFocused
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
set locked(isLocked: boolean) {
|
|
37
42
|
super.locked = isLocked
|
|
38
43
|
// this is invoked from super() which has no tiptap, hence the ?
|
|
@@ -45,12 +50,25 @@ export abstract class EditableNode extends Node {
|
|
|
45
50
|
|
|
46
51
|
readonly tiptap = new Tiptap({
|
|
47
52
|
extensions: TiptapExtensions,
|
|
48
|
-
onUpdate: () => {
|
|
49
|
-
this._content =
|
|
50
|
-
this._isEmpty =
|
|
53
|
+
onUpdate: ({ editor }) => {
|
|
54
|
+
this._content = editor.getJSON()
|
|
55
|
+
this._isEmpty = editor.isEmpty
|
|
56
|
+
},
|
|
57
|
+
onFocus: () => {
|
|
58
|
+
/**
|
|
59
|
+
* The blur event does not work, because we hide the UI before.
|
|
60
|
+
* {@link EditableNode.blur} is invoked in `EditableContent`
|
|
61
|
+
*/
|
|
62
|
+
this._isFocused = true
|
|
51
63
|
},
|
|
52
64
|
})
|
|
53
65
|
|
|
66
|
+
blur() {
|
|
67
|
+
this.tiptap.commands.selectAll()
|
|
68
|
+
this._isFocused = false
|
|
69
|
+
EditableNode.lastFocused = null
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
constructor(
|
|
55
73
|
editor: Editor,
|
|
56
74
|
page: Page,
|
|
@@ -81,4 +99,25 @@ export abstract class EditableNode extends Node {
|
|
|
81
99
|
serialize(): SerializedNode<string, EditableNodeProps> {
|
|
82
100
|
return super.serialize()
|
|
83
101
|
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* {@link Tiptap.isFocused} uses the DOM element which gets blurred when
|
|
105
|
+
* an interactive element, such as a button is clicked on another panel.
|
|
106
|
+
* We need to track the last focused tiptap instance for editing purposes.
|
|
107
|
+
*/
|
|
108
|
+
private static lastFocused: EditableNode | null = null
|
|
109
|
+
|
|
110
|
+
static getFocused(node: EditableNode[]) {
|
|
111
|
+
const focused = node.find((e) => e.tiptap.isFocused)
|
|
112
|
+
|
|
113
|
+
if (focused) {
|
|
114
|
+
return (this.lastFocused = focused)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (this.lastFocused && node.includes(this.lastFocused)) {
|
|
118
|
+
return this.lastFocused
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
84
123
|
}
|
|
@@ -4,7 +4,6 @@ import { debounce, isEqual } from "es-toolkit"
|
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from "react"
|
|
5
5
|
import { useStore } from "react-bolt"
|
|
6
6
|
import { useEditor } from "../../hooks/editor"
|
|
7
|
-
import { blurNode } from "../../hooks/textMarks"
|
|
8
7
|
import type { EditableNode, EditableNodeProps } from "../../model/node/editable"
|
|
9
8
|
import { useDoubleClick } from "./useDoubleClick"
|
|
10
9
|
|
|
@@ -38,7 +37,7 @@ export function EditableContent(props: {
|
|
|
38
37
|
|
|
39
38
|
useEffect(() => {
|
|
40
39
|
if (!selection.has(node)) {
|
|
41
|
-
|
|
40
|
+
node.blur()
|
|
42
41
|
setClicked(false)
|
|
43
42
|
}
|
|
44
43
|
}, [selection, node])
|