@jackuait/blok 0.23.3 → 0.23.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/README.md +3 -3
- package/dist/blok.cjs +1 -1
- package/dist/blok.iife.js +14 -14
- package/dist/blok.mjs +2 -2
- package/dist/blok.umd.js +6 -6
- package/dist/chunks/{blok-J3Lfa_bK.cjs → blok-DixLFA0K.cjs} +5 -5
- package/dist/chunks/{blok-1H9roBGf.mjs → blok-TvOrZOjF.mjs} +2195 -2170
- package/dist/chunks/{constants-BZUc1kAY.cjs → constants-Bze_TXgc.cjs} +1 -1
- package/dist/chunks/{constants-BJ79cqMa.mjs → constants-DpIX_GOn.mjs} +2 -1
- package/dist/chunks/{tools-OHDv2C7w.cjs → tools-CRAPWuhj.cjs} +1 -1
- package/dist/chunks/{tools-ByOjWADT.mjs → tools-DBb88o-I.mjs} +1 -1
- package/dist/full.cjs +1 -1
- package/dist/full.mjs +3 -3
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +42 -31
- package/dist/tools.cjs +1 -1
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/components/block/index.ts +3 -2
- package/src/components/block/tunes-manager.ts +7 -2
- package/src/components/constants/data-attributes.ts +4 -0
- package/src/components/modules/modificationsObserver.ts +40 -2
- package/src/components/modules/paste/handlers/html-handler.ts +14 -1
- package/src/components/modules/paste/index.ts +1 -1
- package/src/components/modules/renderer.ts +32 -4
- package/src/components/modules/toolbar/blockSettings.ts +15 -2
- package/src/components/utils/apply-link-config.ts +40 -0
- package/src/react/BlokEditor.tsx +10 -2
- package/src/react/config-keys.ts +3 -0
- package/src/react/types.ts +4 -0
- package/src/react/useBlok.ts +45 -6
- package/types/block-tunes/block-tune.d.ts +21 -1
- package/types/configs/blok-config.d.ts +62 -5
- package/types/index.d.ts +1 -1
package/src/react/useBlok.ts
CHANGED
|
@@ -43,6 +43,17 @@ export function useBlok(config: UseBlokConfig, deps?: DependencyList): Blok | nu
|
|
|
43
43
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
44
|
const depsToken = useMemo(() => ({}), deps ?? []);
|
|
45
45
|
|
|
46
|
+
// Tracks the data the editor currently reflects — set when content is seeded
|
|
47
|
+
// or rendered (the reactive `data` effect below) AND when the editor emits its
|
|
48
|
+
// own serialized content via `onSave`. The latter is what makes a controlled
|
|
49
|
+
// `onSave -> setData -> data` round-trip a no-op: the echoed payload deep-equals
|
|
50
|
+
// this baseline, so the data effect skips the redundant render() (which would
|
|
51
|
+
// otherwise reset the caret). Declared here so the `onSave` wrapper below can
|
|
52
|
+
// update it before the consumer's setState re-runs the data effect.
|
|
53
|
+
const lastRenderedDataRef = useRef(config.data);
|
|
54
|
+
const seededEditorRef = useRef<Blok | null>(null);
|
|
55
|
+
const renderChainRef = useRef<Promise<void>>(Promise.resolve());
|
|
56
|
+
|
|
46
57
|
// Main lifecycle effect
|
|
47
58
|
useEffect(() => {
|
|
48
59
|
if (typeof window === 'undefined') {
|
|
@@ -99,6 +110,35 @@ export function useBlok(config: UseBlokConfig, deps?: DependencyList): Blok | nu
|
|
|
99
110
|
},
|
|
100
111
|
};
|
|
101
112
|
|
|
113
|
+
// Only attach onSave when the consumer opted in: its mere presence makes the
|
|
114
|
+
// core serialize on every change batch, so an absent prop must stay absent.
|
|
115
|
+
// The wrapper reads through the ref so the latest callback is always used.
|
|
116
|
+
if (currentConfig.onSave) {
|
|
117
|
+
blokConfig.onSave = (...args: Parameters<NonNullable<UseBlokConfig['onSave']>>): void => {
|
|
118
|
+
// Record the editor's own serialized output as the rendered baseline so a
|
|
119
|
+
// controlled consumer echoing it straight back into `data` is a no-op —
|
|
120
|
+
// no redundant render(), no caret reset, no round-trip recursion.
|
|
121
|
+
lastRenderedDataRef.current = args[0];
|
|
122
|
+
configRef.current.onSave?.(...args);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// onBeforeRender / onAfterRender are opt-in (absent prop must stay absent so
|
|
127
|
+
// the core skips them). When present, route through the ref so the latest
|
|
128
|
+
// callback is always used without recreating the editor.
|
|
129
|
+
if (currentConfig.onBeforeRender) {
|
|
130
|
+
blokConfig.onBeforeRender = (
|
|
131
|
+
...args: Parameters<NonNullable<UseBlokConfig['onBeforeRender']>>
|
|
132
|
+
): ReturnType<NonNullable<UseBlokConfig['onBeforeRender']>> =>
|
|
133
|
+
configRef.current.onBeforeRender?.(...args) ?? args[0];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (currentConfig.onAfterRender) {
|
|
137
|
+
blokConfig.onAfterRender = (...args: Parameters<NonNullable<UseBlokConfig['onAfterRender']>>): void => {
|
|
138
|
+
configRef.current.onAfterRender?.(...args);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
102
142
|
const blok = new BlokRuntime(blokConfig) as unknown as Blok;
|
|
103
143
|
state.editor = blok;
|
|
104
144
|
setHolder(blok, holder);
|
|
@@ -178,13 +218,12 @@ export function useBlok(config: UseBlokConfig, deps?: DependencyList): Blok | nu
|
|
|
178
218
|
//
|
|
179
219
|
// `data` seeds the editor at construction. Afterwards, changing the prop to
|
|
180
220
|
// new *content* re-renders the editor via the public render() API — no
|
|
181
|
-
// recreation. Updates are deep-equal–deduped
|
|
182
|
-
//
|
|
183
|
-
//
|
|
221
|
+
// recreation. Updates are deep-equal–deduped against the editor's current
|
|
222
|
+
// content baseline (`lastRenderedDataRef`, declared above and also updated by
|
|
223
|
+
// the `onSave` wrapper) so a new reference with the same content — including
|
|
224
|
+
// the editor's own serialized output echoed back — is a no-op and won't
|
|
225
|
+
// clobber the caret. Renders are serialized so rapid changes can't overlap.
|
|
184
226
|
const { data } = config;
|
|
185
|
-
const lastRenderedDataRef = useRef(config.data);
|
|
186
|
-
const seededEditorRef = useRef<Blok | null>(null);
|
|
187
|
-
const renderChainRef = useRef<Promise<void>>(Promise.resolve());
|
|
188
227
|
useEffect(() => {
|
|
189
228
|
if (editor === null || data === undefined) {
|
|
190
229
|
return;
|
|
@@ -2,6 +2,24 @@ import {API, BlockAPI, ToolConfig} from '../index';
|
|
|
2
2
|
import { BlockTuneData } from './block-tune-data';
|
|
3
3
|
import { BaseToolConstructable, MenuConfig } from '../tools';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Context passed to a Block Tune's `render()` method, giving the tune a handle
|
|
7
|
+
* on the popover it is rendered into — so custom tunes can anchor sub-menus or
|
|
8
|
+
* portals inside Blok's tune popover instead of reaching into the DOM via
|
|
9
|
+
* `closest('[data-blok-popover]')`.
|
|
10
|
+
*/
|
|
11
|
+
export interface BlockTuneRenderContext {
|
|
12
|
+
/**
|
|
13
|
+
* Returns the popover element (`[data-blok-popover]`) the tune is rendered
|
|
14
|
+
* into, or `null` when accessed before the popover has mounted.
|
|
15
|
+
*
|
|
16
|
+
* `render()` itself runs *before* the popover exists, so calling this
|
|
17
|
+
* synchronously inside `render()` returns `null`. Capture the context and
|
|
18
|
+
* call this later (e.g. when opening a sub-menu) to get the live element.
|
|
19
|
+
*/
|
|
20
|
+
getPopoverElement(): HTMLElement | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
/**
|
|
6
24
|
* Describes BLockTune blueprint
|
|
7
25
|
*/
|
|
@@ -10,8 +28,10 @@ export interface BlockTune {
|
|
|
10
28
|
* Returns BlockTune's UI.
|
|
11
29
|
* Should return either MenuConfig (recommended)
|
|
12
30
|
* or an HTMLElement (UI consistency is not guaranteed)
|
|
31
|
+
*
|
|
32
|
+
* @param context - render context exposing the host tune popover element
|
|
13
33
|
*/
|
|
14
|
-
render(): HTMLElement | MenuConfig;
|
|
34
|
+
render(context?: BlockTuneRenderContext): HTMLElement | MenuConfig;
|
|
15
35
|
|
|
16
36
|
/**
|
|
17
37
|
* Method called on Tool render. Pass Tool content as an argument.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThemeMode, ResolvedTheme } from '../api/theme';
|
|
2
2
|
import {ToolConstructable, ToolSettings} from '../tools';
|
|
3
|
-
import {API, LogLevels, OutputData} from '../index';
|
|
3
|
+
import {API, LogLevels, OutputBlockData, OutputData} from '../index';
|
|
4
4
|
import {SanitizerConfig} from './sanitizer-config';
|
|
5
5
|
import {I18nConfig} from './i18n-config';
|
|
6
6
|
import { BlockMutationEvent } from '../events/block';
|
|
@@ -136,8 +136,17 @@ export interface BlokConfig {
|
|
|
136
136
|
i18n?: I18nConfig;
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
|
-
* Configures how
|
|
140
|
-
*
|
|
139
|
+
* Configures how Blok builds anchor (`<a>`) elements, so consumers can control
|
|
140
|
+
* the created links instead of post-processing the rendered DOM.
|
|
141
|
+
*
|
|
142
|
+
* Applies on every path that produces anchors:
|
|
143
|
+
* - the interactive **Link inline tool** (links the user creates by hand),
|
|
144
|
+
* - **render** — anchors coming from stored block HTML when `blocks.render()`
|
|
145
|
+
* runs (e.g. saved articles whose `<a>` never went through the inline tool),
|
|
146
|
+
* - **paste** — `<a>` arriving via the clipboard.
|
|
147
|
+
*
|
|
148
|
+
* On the render and paste paths `target`/`rel` are forced and `transformHref`
|
|
149
|
+
* rewrites the href, exactly as for hand-created links.
|
|
141
150
|
*/
|
|
142
151
|
link?: {
|
|
143
152
|
/**
|
|
@@ -154,8 +163,13 @@ export interface BlokConfig {
|
|
|
154
163
|
|
|
155
164
|
/**
|
|
156
165
|
* Transforms the href just before it is assigned to the anchor.
|
|
157
|
-
*
|
|
158
|
-
*
|
|
166
|
+
*
|
|
167
|
+
* On the inline-tool path it runs after URL validation/normalization, on the
|
|
168
|
+
* final value. On the render and paste paths it runs against each anchor's
|
|
169
|
+
* existing href, so it must be idempotent (re-rendering already-transformed
|
|
170
|
+
* content must not change the result again).
|
|
171
|
+
*
|
|
172
|
+
* @param href - the href to transform
|
|
159
173
|
* @returns the href to set on the anchor
|
|
160
174
|
*/
|
|
161
175
|
transformHref?: (href: string) => string;
|
|
@@ -187,6 +201,49 @@ export interface BlokConfig {
|
|
|
187
201
|
*/
|
|
188
202
|
onChange?(api: API, event: BlockMutationEvent | BlockMutationEvent[]): void;
|
|
189
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Fires with the full serialized {@link OutputData} whenever the content
|
|
206
|
+
* changes. This is the "output half" of a controlled editor: pair it with the
|
|
207
|
+
* `data` config (or the React `data` prop) to mirror the editor state into your
|
|
208
|
+
* own store with a single callback instead of calling `saver.save()` by hand.
|
|
209
|
+
*
|
|
210
|
+
* Serialization is debounced through the same change-batching window as
|
|
211
|
+
* `onChange`, so a burst of edits produces a single `onSave` call. Only
|
|
212
|
+
* user-driven content changes trigger it — programmatic `render()` does not
|
|
213
|
+
* (the change observer is disabled during render), so a controlled
|
|
214
|
+
* `data → render → onSave → setData` round-trip won't recurse.
|
|
215
|
+
*
|
|
216
|
+
* @param data - the full serialized output of the editor
|
|
217
|
+
* @param api - blok.js api
|
|
218
|
+
*/
|
|
219
|
+
onSave?(data: OutputData, api: API): void;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Transforms the blocks array just before it is rendered, on every render
|
|
223
|
+
* (the initial render and every `blocks.render()` call). Use it to run
|
|
224
|
+
* app-specific data migrations inside Blok instead of pre-processing the
|
|
225
|
+
* data before handing it to the editor.
|
|
226
|
+
*
|
|
227
|
+
* Receives the raw saved blocks (the exact array passed to render, before any
|
|
228
|
+
* format analysis or hierarchical expansion) and must return the blocks to
|
|
229
|
+
* render. Returning an empty array renders an empty document; returning blocks
|
|
230
|
+
* for an empty input injects them.
|
|
231
|
+
*
|
|
232
|
+
* @param blocks - the blocks about to be rendered
|
|
233
|
+
* @returns the blocks to actually render
|
|
234
|
+
*/
|
|
235
|
+
onBeforeRender?(blocks: OutputBlockData[]): OutputBlockData[];
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Fires after a render completes and the blocks are in the DOM — on the
|
|
239
|
+
* initial render and on every `blocks.render()` call. Use it for post-render
|
|
240
|
+
* side effects (scroll restoration, attaching observers, …). Distinct from
|
|
241
|
+
* `onReady`, which fires once when the editor first becomes ready.
|
|
242
|
+
*
|
|
243
|
+
* @param api - blok.js api
|
|
244
|
+
*/
|
|
245
|
+
onAfterRender?(api: API): void;
|
|
246
|
+
|
|
190
247
|
/**
|
|
191
248
|
* Defines default toolbar for all tools.
|
|
192
249
|
*/
|
package/types/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export {
|
|
|
72
72
|
FilePasteEvent,
|
|
73
73
|
FilePasteEventDetail,
|
|
74
74
|
} from './tools';
|
|
75
|
-
export {BlockTune, BlockTuneConstructable} from './block-tunes';
|
|
75
|
+
export {BlockTune, BlockTuneConstructable, BlockTuneRenderContext} from './block-tunes';
|
|
76
76
|
export {
|
|
77
77
|
BlokConfig,
|
|
78
78
|
SanitizerConfig,
|