@byline/richtext-lexical 6.4.0 → 6.5.0
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/dist/field/extensions/code-highlight/code-highlight-extension.js +2 -1
- package/dist/field/extensions/inline-image/inline-image-extension.js +4 -2
- package/dist/field/extensions/inline-image/inline-image-modal.css +67 -0
- package/dist/field/extensions/inline-image/inline-image-modal.js +173 -98
- package/dist/field/extensions/inline-image/inline-image-node-component.css +37 -0
- package/dist/field/extensions/inline-image/inline-image-node-component.d.ts +3 -1
- package/dist/field/extensions/inline-image/inline-image-node-component.js +39 -1
- package/dist/field/extensions/inline-image/inline-image-node.d.ts +13 -2
- package/dist/field/extensions/inline-image/inline-image-node.js +63 -13
- package/dist/field/extensions/inline-image/node-types.d.ts +19 -0
- package/dist/field/extensions/inline-image/populate.d.ts +22 -0
- package/dist/field/extensions/inline-image/populate.js +19 -1
- package/dist/field/extensions/inline-image/types.d.ts +7 -0
- package/dist/field/extensions/link/link-href.d.ts +30 -0
- package/dist/field/extensions/link/link-href.js +14 -0
- package/dist/field/extensions/link/link-modal.js +96 -288
- package/dist/field/extensions/link/link-target-fields.css +59 -0
- package/dist/field/extensions/link/link-target-fields.d.ts +38 -0
- package/dist/field/extensions/link/link-target-fields.js +227 -0
- package/dist/field/extensions/link/link-target-state.d.ts +54 -0
- package/dist/field/extensions/link/link-target-state.js +70 -0
- package/dist/field/extensions/link/populate.d.ts +15 -1
- package/dist/field/extensions/link/populate.js +55 -50
- package/dist/field/markdown/lexical-to-markdown.js +6 -1
- package/dist/field/plugins/toolbar-plugin/index.js +2 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +4 -2
- package/dist/validate/createEmptyEditorState.d.ts +2 -2
- package/dist/validate/hasText.d.ts +2 -2
- package/package.json +25 -24
- package/src/field/extensions/code-highlight/code-highlight-extension.ts +8 -1
- package/src/field/extensions/inline-image/inline-image-extension.tsx +5 -0
- package/src/field/extensions/inline-image/inline-image-modal.css +100 -0
- package/src/field/extensions/inline-image/inline-image-modal.tsx +178 -85
- package/src/field/extensions/inline-image/inline-image-node-component.css +44 -0
- package/src/field/extensions/inline-image/inline-image-node-component.tsx +52 -0
- package/src/field/extensions/inline-image/inline-image-node.test.tsx +331 -0
- package/src/field/extensions/inline-image/inline-image-node.tsx +108 -11
- package/src/field/extensions/inline-image/node-types.ts +19 -0
- package/src/field/extensions/inline-image/populate-link.test.node.ts +320 -0
- package/src/field/extensions/inline-image/populate.ts +44 -0
- package/src/field/extensions/inline-image/types.ts +7 -0
- package/src/field/extensions/link/link-href.test.node.ts +78 -0
- package/src/field/extensions/link/link-href.ts +52 -0
- package/src/field/extensions/link/link-modal.tsx +86 -325
- package/src/field/extensions/link/link-target-fields.css +81 -0
- package/src/field/extensions/link/link-target-fields.tsx +275 -0
- package/src/field/extensions/link/link-target-state.test.node.ts +197 -0
- package/src/field/extensions/link/link-target-state.ts +158 -0
- package/src/field/extensions/link/populate.ts +94 -67
- package/src/field/markdown/lexical-to-markdown.test.node.ts +68 -0
- package/src/field/markdown/lexical-to-markdown.ts +10 -1
- package/src/field/plugins/toolbar-plugin/index.tsx +7 -4
- package/src/server.ts +11 -5
- package/src/validate/createEmptyEditorState.ts +2 -7
- package/src/validate/hasText.test.ts +1 -2
- package/src/validate/hasText.ts +2 -9
|
@@ -45,7 +45,99 @@
|
|
|
45
45
|
|
|
46
46
|
import { getCollectionDefinition, getLogger } from '@byline/core'
|
|
47
47
|
|
|
48
|
-
import type {
|
|
48
|
+
import type {
|
|
49
|
+
LexicalNodeLike,
|
|
50
|
+
LexicalNodeVisitor,
|
|
51
|
+
PendingHydration,
|
|
52
|
+
} from '../../lexical-populate-shared'
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build the `apply` / `applyMissing` pair that refreshes an internal-link
|
|
56
|
+
* relation envelope in place.
|
|
57
|
+
*
|
|
58
|
+
* Shared by `linkVisitor` (where the envelope is `node.attributes`) and by
|
|
59
|
+
* the inline-image plugin's `inlineImageLinkVisitor` (where it is
|
|
60
|
+
* `node.link`). Both carry the same `DocumentRelation` shape and want the
|
|
61
|
+
* same three branches, so the resolution rules documented above live here
|
|
62
|
+
* once rather than being copied per node type.
|
|
63
|
+
*
|
|
64
|
+
* `envelope` is mutated in place — the caller owns where it hangs off the
|
|
65
|
+
* node.
|
|
66
|
+
*/
|
|
67
|
+
export function createInternalLinkHydration(
|
|
68
|
+
envelope: Record<string, any>,
|
|
69
|
+
collectionPath: string,
|
|
70
|
+
documentId: string
|
|
71
|
+
): Pick<PendingHydration, 'apply' | 'applyMissing'> {
|
|
72
|
+
return {
|
|
73
|
+
apply(target: Record<string, any>) {
|
|
74
|
+
const definition = getCollectionDefinition(collectionPath)
|
|
75
|
+
const useAsTitle = definition?.useAsTitle ?? 'title'
|
|
76
|
+
const targetFields = (target.fields ?? {}) as Record<string, any>
|
|
77
|
+
const next: Record<string, any> = { ...(envelope.document ?? {}) }
|
|
78
|
+
|
|
79
|
+
// Title — `useAsTitle` lookup with `title` fallback.
|
|
80
|
+
const title = targetFields[useAsTitle]
|
|
81
|
+
if (typeof title === 'string' && title.length > 0) {
|
|
82
|
+
next.title = title
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Path — buildDocumentPath, then generic compose fallback.
|
|
86
|
+
// Branch A: hook threw — leave any existing `document.path`
|
|
87
|
+
// untouched and surface a log line so operators can find the
|
|
88
|
+
// bug without it taking the save / read down with it.
|
|
89
|
+
let pathThrew = false
|
|
90
|
+
let built: string | null | undefined
|
|
91
|
+
if (definition?.buildDocumentPath != null) {
|
|
92
|
+
try {
|
|
93
|
+
built = definition.buildDocumentPath(
|
|
94
|
+
{
|
|
95
|
+
id: target.id as string,
|
|
96
|
+
path: target.path as string,
|
|
97
|
+
status: target.status as string,
|
|
98
|
+
fields: targetFields,
|
|
99
|
+
},
|
|
100
|
+
{ collectionPath }
|
|
101
|
+
)
|
|
102
|
+
} catch (err) {
|
|
103
|
+
pathThrew = true
|
|
104
|
+
getLogger().info({ collectionPath, documentId, err }, 'buildDocumentPath threw')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!pathThrew) {
|
|
109
|
+
if (typeof built === 'string') {
|
|
110
|
+
next.path = built
|
|
111
|
+
} else {
|
|
112
|
+
// Generic compose fallback. Only fires when the target has a
|
|
113
|
+
// non-empty `path` — otherwise we'd produce `/${collectionPath}/`
|
|
114
|
+
// or `/${collectionPath}/undefined`, both of which are worse
|
|
115
|
+
// than leaving the previous value alone.
|
|
116
|
+
const targetPath = target.path as string | undefined
|
|
117
|
+
if (typeof targetPath === 'string' && targetPath.length > 0) {
|
|
118
|
+
next.path = `/${collectionPath}/${targetPath}`
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Found-and-resolved: clear any stale miss flag from a prior pass.
|
|
124
|
+
if ('_resolved' in next) {
|
|
125
|
+
delete next._resolved
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
envelope.document = next
|
|
129
|
+
},
|
|
130
|
+
applyMissing() {
|
|
131
|
+
// Branch B — target deleted between picker and walker.
|
|
132
|
+
getLogger().warn({ collectionPath, documentId }, 'internal link target not found')
|
|
133
|
+
const next: Record<string, any> = { ...(envelope.document ?? {}) }
|
|
134
|
+
delete next.title
|
|
135
|
+
delete next.path
|
|
136
|
+
next._resolved = false
|
|
137
|
+
envelope.document = next
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|
|
49
141
|
|
|
50
142
|
export const linkVisitor: LexicalNodeVisitor = {
|
|
51
143
|
match(node: LexicalNodeLike) {
|
|
@@ -61,72 +153,7 @@ export const linkVisitor: LexicalNodeVisitor = {
|
|
|
61
153
|
node,
|
|
62
154
|
collectionPath,
|
|
63
155
|
documentId,
|
|
64
|
-
|
|
65
|
-
const definition = getCollectionDefinition(collectionPath)
|
|
66
|
-
const useAsTitle = definition?.useAsTitle ?? 'title'
|
|
67
|
-
const targetFields = (target.fields ?? {}) as Record<string, any>
|
|
68
|
-
const next: Record<string, any> = { ...(attributes.document ?? {}) }
|
|
69
|
-
|
|
70
|
-
// Title — `useAsTitle` lookup with `title` fallback.
|
|
71
|
-
const title = targetFields[useAsTitle]
|
|
72
|
-
if (typeof title === 'string' && title.length > 0) {
|
|
73
|
-
next.title = title
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Path — buildDocumentPath, then generic compose fallback.
|
|
77
|
-
// Branch A: hook threw — leave any existing `document.path`
|
|
78
|
-
// untouched and surface a log line so operators can find the
|
|
79
|
-
// bug without it taking the save / read down with it.
|
|
80
|
-
let pathThrew = false
|
|
81
|
-
let built: string | null | undefined
|
|
82
|
-
if (definition?.buildDocumentPath != null) {
|
|
83
|
-
try {
|
|
84
|
-
built = definition.buildDocumentPath(
|
|
85
|
-
{
|
|
86
|
-
id: target.id as string,
|
|
87
|
-
path: target.path as string,
|
|
88
|
-
status: target.status as string,
|
|
89
|
-
fields: targetFields,
|
|
90
|
-
},
|
|
91
|
-
{ collectionPath }
|
|
92
|
-
)
|
|
93
|
-
} catch (err) {
|
|
94
|
-
pathThrew = true
|
|
95
|
-
getLogger().info({ collectionPath, documentId, err }, 'buildDocumentPath threw')
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!pathThrew) {
|
|
100
|
-
if (typeof built === 'string') {
|
|
101
|
-
next.path = built
|
|
102
|
-
} else {
|
|
103
|
-
// Generic compose fallback. Only fires when the target has a
|
|
104
|
-
// non-empty `path` — otherwise we'd produce `/${collectionPath}/`
|
|
105
|
-
// or `/${collectionPath}/undefined`, both of which are worse
|
|
106
|
-
// than leaving the previous value alone.
|
|
107
|
-
const targetPath = target.path as string | undefined
|
|
108
|
-
if (typeof targetPath === 'string' && targetPath.length > 0) {
|
|
109
|
-
next.path = `/${collectionPath}/${targetPath}`
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Found-and-resolved: clear any stale miss flag from a prior pass.
|
|
115
|
-
if ('_resolved' in next) {
|
|
116
|
-
delete next._resolved
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
attributes.document = next
|
|
120
|
-
},
|
|
121
|
-
applyMissing() {
|
|
122
|
-
// Branch B — target deleted between picker and walker.
|
|
123
|
-
getLogger().warn({ collectionPath, documentId }, 'internal link target not found')
|
|
124
|
-
const next: Record<string, any> = { ...(attributes.document ?? {}) }
|
|
125
|
-
delete next.title
|
|
126
|
-
delete next.path
|
|
127
|
-
next._resolved = false
|
|
128
|
-
attributes.document = next
|
|
129
|
-
},
|
|
156
|
+
...createInternalLinkHydration(attributes, collectionPath, documentId),
|
|
130
157
|
}
|
|
131
158
|
},
|
|
132
159
|
}
|
|
@@ -309,6 +309,74 @@ describe('lexicalToMarkdown — inline', () => {
|
|
|
309
309
|
)
|
|
310
310
|
})
|
|
311
311
|
|
|
312
|
+
it('wraps a linked inline image in a markdown link', () => {
|
|
313
|
+
const image = {
|
|
314
|
+
type: 'inline-image',
|
|
315
|
+
src: '/uploads/media/photo.avif',
|
|
316
|
+
altText: 'A photo',
|
|
317
|
+
showCaption: false,
|
|
318
|
+
link: { linkType: 'custom', url: 'https://example.com/report', newTab: true },
|
|
319
|
+
version: 1,
|
|
320
|
+
}
|
|
321
|
+
expect(md(root(paragraph(image)))).toBe(
|
|
322
|
+
'[](https://example.com/report)'
|
|
323
|
+
)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('resolves an internal link target on a linked inline image', () => {
|
|
327
|
+
const image = {
|
|
328
|
+
type: 'inline-image',
|
|
329
|
+
src: '/uploads/media/photo.avif',
|
|
330
|
+
altText: 'A photo',
|
|
331
|
+
showCaption: false,
|
|
332
|
+
link: {
|
|
333
|
+
linkType: 'internal',
|
|
334
|
+
targetDocumentId: 'doc-1',
|
|
335
|
+
targetCollectionPath: 'pages',
|
|
336
|
+
document: { title: 'About Us', path: '/pages/about' },
|
|
337
|
+
},
|
|
338
|
+
version: 1,
|
|
339
|
+
}
|
|
340
|
+
expect(md(root(paragraph(image)))).toBe('[](/pages/about)')
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
// The link wraps the image only. The caption is separate content and
|
|
344
|
+
// may carry its own links, so nesting it inside the image's link would
|
|
345
|
+
// produce a nested-anchor shape in every downstream renderer.
|
|
346
|
+
it('links the image but not its caption', () => {
|
|
347
|
+
const image = {
|
|
348
|
+
type: 'inline-image',
|
|
349
|
+
src: '/uploads/media/photo.avif',
|
|
350
|
+
altText: 'A photo',
|
|
351
|
+
showCaption: true,
|
|
352
|
+
caption: { editorState: root(paragraph(text('Taken in 2026.'))) },
|
|
353
|
+
link: { linkType: 'custom', url: 'https://example.com/report' },
|
|
354
|
+
version: 1,
|
|
355
|
+
}
|
|
356
|
+
expect(md(root(paragraph(image)))).toBe(
|
|
357
|
+
'[](https://example.com/report)\n*Taken in 2026.*'
|
|
358
|
+
)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('drops the link but keeps the image when the target is unresolved', () => {
|
|
362
|
+
const image = {
|
|
363
|
+
type: 'inline-image',
|
|
364
|
+
src: '/uploads/media/photo.avif',
|
|
365
|
+
altText: 'A photo',
|
|
366
|
+
showCaption: false,
|
|
367
|
+
link: {
|
|
368
|
+
linkType: 'internal',
|
|
369
|
+
targetDocumentId: 'doc-1',
|
|
370
|
+
targetCollectionPath: 'pages',
|
|
371
|
+
document: { _resolved: false },
|
|
372
|
+
},
|
|
373
|
+
version: 1,
|
|
374
|
+
}
|
|
375
|
+
const result = lexicalToMarkdown(root(paragraph(image)))
|
|
376
|
+
expect(result.markdown).toBe('')
|
|
377
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ kind: 'unresolved-link' }))
|
|
378
|
+
})
|
|
379
|
+
|
|
312
380
|
it('serializes hard line breaks', () => {
|
|
313
381
|
expect(
|
|
314
382
|
md(root(paragraph(text('line one'), { type: 'linebreak', version: 1 }, text('line two'))))
|
|
@@ -424,7 +424,16 @@ function serializeImage(node: AnyNode, ctx: Ctx): string {
|
|
|
424
424
|
const src = typeof node.src === 'string' ? node.src : ''
|
|
425
425
|
if (src.length === 0) return ''
|
|
426
426
|
const alt = typeof node.altText === 'string' ? node.altText : ''
|
|
427
|
-
|
|
427
|
+
let image = `![${alt.replace(/[[\]]/g, '')}](${src})`
|
|
428
|
+
|
|
429
|
+
// Optional click-through target. Resolved through the same rules as a
|
|
430
|
+
// text link, so an unresolved internal target drops the link and keeps
|
|
431
|
+
// the image rather than emitting a broken href.
|
|
432
|
+
const link = node.link as Record<string, unknown> | undefined
|
|
433
|
+
if (link != null) {
|
|
434
|
+
const href = resolveLinkUrl(link, ctx)
|
|
435
|
+
if (href != null) image = `[${image}](${href})`
|
|
436
|
+
}
|
|
428
437
|
// The caption is a nested SerializedEditor — flatten to emphasized text.
|
|
429
438
|
const caption = node.caption as { editorState?: unknown } | undefined
|
|
430
439
|
if (node.showCaption === true && caption?.editorState != null) {
|
|
@@ -18,14 +18,17 @@
|
|
|
18
18
|
import type * as React from 'react'
|
|
19
19
|
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
|
|
20
20
|
|
|
21
|
+
import { $createCodeNode, $isCodeNode, CodeNode } from '@lexical/code'
|
|
22
|
+
// Lexical 0.51 moved the Prism language tables out of `@lexical/code` and
|
|
23
|
+
// into `@lexical/code-prism`, alongside the tokenizer that populates them.
|
|
24
|
+
// `@lexical/code-shiki` is the other highlighter and exposes
|
|
25
|
+
// `getCodeLanguageOptions()` instead of these maps, so a switch to it would
|
|
26
|
+
// change this dropdown's source of truth, not just its import path.
|
|
21
27
|
import {
|
|
22
|
-
$createCodeNode,
|
|
23
|
-
$isCodeNode,
|
|
24
28
|
CODE_LANGUAGE_FRIENDLY_NAME_MAP,
|
|
25
29
|
CODE_LANGUAGE_MAP,
|
|
26
|
-
CodeNode,
|
|
27
30
|
getLanguageFriendlyName,
|
|
28
|
-
} from '@lexical/code'
|
|
31
|
+
} from '@lexical/code-prism'
|
|
29
32
|
import {
|
|
30
33
|
$isListNode,
|
|
31
34
|
CheckListExtension,
|
package/src/server.ts
CHANGED
|
@@ -47,7 +47,10 @@ import type {
|
|
|
47
47
|
RichTextToTextFn,
|
|
48
48
|
} from '@byline/core'
|
|
49
49
|
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
inlineImageLinkVisitor,
|
|
52
|
+
inlineImageVisitor,
|
|
53
|
+
} from './field/extensions/inline-image/populate'
|
|
51
54
|
import { linkVisitor } from './field/extensions/link/populate'
|
|
52
55
|
import { type LexicalNodeVisitor, runLexicalPopulate } from './field/lexical-populate-shared'
|
|
53
56
|
|
|
@@ -72,7 +75,10 @@ import {
|
|
|
72
75
|
lexicalToMarkdown,
|
|
73
76
|
} from './field/markdown/lexical-to-markdown'
|
|
74
77
|
|
|
75
|
-
export {
|
|
78
|
+
export {
|
|
79
|
+
inlineImageLinkVisitor,
|
|
80
|
+
inlineImageVisitor,
|
|
81
|
+
} from './field/extensions/inline-image/populate'
|
|
76
82
|
export { linkVisitor } from './field/extensions/link/populate'
|
|
77
83
|
export type { EditorConfig, EditorSettings, EditorSettingsOverride } from './field/config/types'
|
|
78
84
|
export type {
|
|
@@ -91,7 +97,7 @@ export interface LexicalServerOptions {
|
|
|
91
97
|
getClient: () => BylineClient<any>
|
|
92
98
|
/**
|
|
93
99
|
* Override the visitor list. Defaults to every visitor the package
|
|
94
|
-
* ships — currently `[inlineImageVisitor, linkVisitor]`. Useful when a
|
|
100
|
+
* ships — currently `[inlineImageVisitor, inlineImageLinkVisitor, linkVisitor]`. Useful when a
|
|
95
101
|
* host wants to register additional custom visitors alongside the
|
|
96
102
|
* built-ins, or temporarily disable a built-in:
|
|
97
103
|
*
|
|
@@ -112,7 +118,7 @@ export interface LexicalServerOptions {
|
|
|
112
118
|
* document tree, gated by each leaf field's `populateRelationsOnRead`.
|
|
113
119
|
*/
|
|
114
120
|
export function lexicalEditorPopulateServer(options: LexicalServerOptions): RichTextPopulateFn {
|
|
115
|
-
const visitors = options.visitors ?? [inlineImageVisitor, linkVisitor]
|
|
121
|
+
const visitors = options.visitors ?? [inlineImageVisitor, inlineImageLinkVisitor, linkVisitor]
|
|
116
122
|
return async (ctx: RichTextPopulateContext): Promise<void> => {
|
|
117
123
|
await runLexicalPopulate({
|
|
118
124
|
readContext: ctx.readContext,
|
|
@@ -162,7 +168,7 @@ export function lexicalEditorToTextServer(): RichTextToTextFn {
|
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
export function lexicalEditorEmbedServer(options: LexicalServerOptions): RichTextEmbedFn {
|
|
165
|
-
const visitors = options.visitors ?? [inlineImageVisitor, linkVisitor]
|
|
171
|
+
const visitors = options.visitors ?? [inlineImageVisitor, inlineImageLinkVisitor, linkVisitor]
|
|
166
172
|
return async (ctx: RichTextEmbedContext): Promise<void> => {
|
|
167
173
|
await runLexicalPopulate({
|
|
168
174
|
readContext: ctx.readContext,
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SerializedEditorState,
|
|
3
|
-
SerializedLexicalNode,
|
|
4
|
-
SerializedParagraphNode,
|
|
5
|
-
SerializedRootNode,
|
|
6
|
-
} from 'lexical'
|
|
1
|
+
import type { SerializedEditorState, SerializedParagraphNode, SerializedRootNode } from 'lexical'
|
|
7
2
|
|
|
8
3
|
/**
|
|
9
4
|
* createEmptyEditorState
|
|
@@ -27,7 +22,7 @@ import type {
|
|
|
27
22
|
"version": 1
|
|
28
23
|
}
|
|
29
24
|
*/
|
|
30
|
-
export function createEmptyEditorState(): SerializedEditorState
|
|
25
|
+
export function createEmptyEditorState(): SerializedEditorState {
|
|
31
26
|
const emptyParagraphNode: SerializedParagraphNode = {
|
|
32
27
|
children: [],
|
|
33
28
|
direction: null,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
SerializedEditorState,
|
|
3
|
-
SerializedLexicalNode,
|
|
4
3
|
SerializedParagraphNode,
|
|
5
4
|
SerializedRootNode,
|
|
6
5
|
SerializedTextNode,
|
|
@@ -9,7 +8,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
9
8
|
|
|
10
9
|
import { hasText } from './hasText'
|
|
11
10
|
|
|
12
|
-
type State = SerializedEditorState
|
|
11
|
+
type State = SerializedEditorState
|
|
13
12
|
|
|
14
13
|
import { createEmptyEditorState } from './createEmptyEditorState'
|
|
15
14
|
|
package/src/validate/hasText.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SerializedEditorState,
|
|
3
|
-
SerializedLexicalNode,
|
|
4
|
-
SerializedParagraphNode,
|
|
5
|
-
SerializedTextNode,
|
|
6
|
-
} from 'lexical'
|
|
1
|
+
import type { SerializedEditorState, SerializedParagraphNode, SerializedTextNode } from 'lexical'
|
|
7
2
|
|
|
8
|
-
export function hasText(
|
|
9
|
-
value: SerializedEditorState<SerializedLexicalNode> | null | undefined
|
|
10
|
-
): boolean {
|
|
3
|
+
export function hasText(value: SerializedEditorState | null | undefined): boolean {
|
|
11
4
|
if (value === null || value === undefined) return false
|
|
12
5
|
|
|
13
6
|
const rootChildren = value.root?.children
|