@byline/richtext-lexical 3.14.0 → 3.15.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/lexical-to-text.d.ts +12 -0
- package/dist/field/lexical-to-text.js +82 -0
- package/dist/server.d.ts +10 -1
- package/dist/server.js +5 -1
- package/package.json +5 -5
- package/src/field/lexical-to-text.test.node.ts +75 -0
- package/src/field/lexical-to-text.ts +145 -0
- package/src/server.ts +14 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extract indexable plain text from a (possibly null / stringified)
|
|
10
|
+
* serialized Lexical editor state. Returns `''` when there is no content.
|
|
11
|
+
*/
|
|
12
|
+
export declare function lexicalToText(value: unknown): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const INLINE_CONTAINER_TYPES = new Set([
|
|
2
|
+
'link',
|
|
3
|
+
'autolink'
|
|
4
|
+
]);
|
|
5
|
+
const SKIP_TYPES = new Set([
|
|
6
|
+
'youtube',
|
|
7
|
+
'vimeo',
|
|
8
|
+
'horizontalrule',
|
|
9
|
+
'tab'
|
|
10
|
+
]);
|
|
11
|
+
class LexicalTextConverter {
|
|
12
|
+
addText(text) {
|
|
13
|
+
if (null != text) this.current += text;
|
|
14
|
+
}
|
|
15
|
+
endParagraph() {
|
|
16
|
+
const p = this.current.trim();
|
|
17
|
+
if (p.length > 0) this.paragraphs.push(p);
|
|
18
|
+
this.current = '';
|
|
19
|
+
}
|
|
20
|
+
getText() {
|
|
21
|
+
this.endParagraph();
|
|
22
|
+
return this.paragraphs.join('\n');
|
|
23
|
+
}
|
|
24
|
+
addBlockNodes(nodes) {
|
|
25
|
+
this.endParagraph();
|
|
26
|
+
for (const node of nodes ?? [])this.addNode(node);
|
|
27
|
+
this.endParagraph();
|
|
28
|
+
}
|
|
29
|
+
addInlineNodes(nodes) {
|
|
30
|
+
for (const node of nodes ?? [])this.addNode(node);
|
|
31
|
+
}
|
|
32
|
+
addNode(node) {
|
|
33
|
+
const type = node.type;
|
|
34
|
+
if ('text' === type || 'code-highlight' === type) return void this.addText(node.text);
|
|
35
|
+
if ('linebreak' === type) return void this.endParagraph();
|
|
36
|
+
if ('admonition' === type) {
|
|
37
|
+
this.endParagraph();
|
|
38
|
+
this.addText(node.title);
|
|
39
|
+
this.endParagraph();
|
|
40
|
+
this.addText(lexicalToText(node.content?.editorState).trim());
|
|
41
|
+
this.endParagraph();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if ('inline-image' === type) {
|
|
45
|
+
this.endParagraph();
|
|
46
|
+
this.addText(node.altText);
|
|
47
|
+
if (node.showCaption) {
|
|
48
|
+
this.endParagraph();
|
|
49
|
+
this.addText(lexicalToText(node.caption?.editorState).trim());
|
|
50
|
+
}
|
|
51
|
+
this.endParagraph();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (null != type && SKIP_TYPES.has(type)) return;
|
|
55
|
+
if (null != type && INLINE_CONTAINER_TYPES.has(type)) return void this.addInlineNodes(node.children);
|
|
56
|
+
if (Array.isArray(node.children)) this.addBlockNodes(node.children);
|
|
57
|
+
}
|
|
58
|
+
constructor(){
|
|
59
|
+
this.paragraphs = [];
|
|
60
|
+
this.current = '';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function resolveRoot(value) {
|
|
64
|
+
let v = value;
|
|
65
|
+
if ('string' == typeof v) try {
|
|
66
|
+
v = JSON.parse(v);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if ('object' != typeof v || null === v) return null;
|
|
71
|
+
const obj = v;
|
|
72
|
+
const root = obj.root ?? obj;
|
|
73
|
+
return 'root' === root.type ? root : null;
|
|
74
|
+
}
|
|
75
|
+
function lexicalToText(value) {
|
|
76
|
+
const root = resolveRoot(value);
|
|
77
|
+
if (null == root) return '';
|
|
78
|
+
const converter = new LexicalTextConverter();
|
|
79
|
+
converter.addBlockNodes(root.children);
|
|
80
|
+
return converter.getText();
|
|
81
|
+
}
|
|
82
|
+
export { lexicalToText };
|
package/dist/server.d.ts
CHANGED
|
@@ -36,9 +36,10 @@
|
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
38
|
import type { BylineClient } from '@byline/client';
|
|
39
|
-
import type { RichTextEmbedFn, RichTextPopulateFn, RichTextToMarkdownFn } from '@byline/core';
|
|
39
|
+
import type { RichTextEmbedFn, RichTextPopulateFn, RichTextToMarkdownFn, RichTextToTextFn } from '@byline/core';
|
|
40
40
|
import { type LexicalNodeVisitor } from './field/lexical-populate-shared';
|
|
41
41
|
export { defaultEditorConfig } from './field/config/default';
|
|
42
|
+
export { lexicalToText } from './field/lexical-to-text';
|
|
42
43
|
export { type LexicalToMarkdownOptions, type LexicalToMarkdownResult, type LexicalToMarkdownWarning, lexicalToMarkdown, } from './field/markdown/lexical-to-markdown';
|
|
43
44
|
import { type LexicalToMarkdownOptions } from './field/markdown/lexical-to-markdown';
|
|
44
45
|
export { inlineImageVisitor } from './field/extensions/inline-image/populate';
|
|
@@ -97,4 +98,12 @@ export declare function lexicalEditorPopulateServer(options: LexicalServerOption
|
|
|
97
98
|
* for the dialect contract (GFM alerts, lossy-OK).
|
|
98
99
|
*/
|
|
99
100
|
export declare function lexicalEditorToMarkdownServer(options?: LexicalToMarkdownOptions): RichTextToMarkdownFn;
|
|
101
|
+
/**
|
|
102
|
+
* Plain-text extractor for search indexing, shaped for
|
|
103
|
+
* `ServerConfig.fields.richText.toText`. Pure and synchronous — flattens
|
|
104
|
+
* the stored editor JSON to indexable plain text via `lexicalToText` (a
|
|
105
|
+
* recursive text-node accumulator, no markdown). Consumed by core's
|
|
106
|
+
* `buildSearchDocument` to feed a collection's searchable `body`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function lexicalEditorToTextServer(): RichTextToTextFn;
|
|
100
109
|
export declare function lexicalEditorEmbedServer(options: LexicalServerOptions): RichTextEmbedFn;
|
package/dist/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { inlineImageVisitor } from "./field/extensions/inline-image/populate.js"
|
|
|
2
2
|
import { linkVisitor } from "./field/extensions/link/populate.js";
|
|
3
3
|
import { runLexicalPopulate } from "./field/lexical-populate-shared.js";
|
|
4
4
|
import { lexicalToMarkdown } from "./field/markdown/lexical-to-markdown.js";
|
|
5
|
+
import { lexicalToText } from "./field/lexical-to-text.js";
|
|
5
6
|
function lexicalEditorPopulateServer(options) {
|
|
6
7
|
const visitors = options.visitors ?? [
|
|
7
8
|
inlineImageVisitor,
|
|
@@ -21,6 +22,9 @@ function lexicalEditorPopulateServer(options) {
|
|
|
21
22
|
function lexicalEditorToMarkdownServer(options = {}) {
|
|
22
23
|
return (ctx)=>lexicalToMarkdown(ctx.value, options).markdown;
|
|
23
24
|
}
|
|
25
|
+
function lexicalEditorToTextServer() {
|
|
26
|
+
return (ctx)=>lexicalToText(ctx.value);
|
|
27
|
+
}
|
|
24
28
|
function lexicalEditorEmbedServer(options) {
|
|
25
29
|
const visitors = options.visitors ?? [
|
|
26
30
|
inlineImageVisitor,
|
|
@@ -38,4 +42,4 @@ function lexicalEditorEmbedServer(options) {
|
|
|
38
42
|
};
|
|
39
43
|
}
|
|
40
44
|
export { defaultEditorConfig } from "./field/config/default.js";
|
|
41
|
-
export { inlineImageVisitor, lexicalEditorEmbedServer, lexicalEditorPopulateServer, lexicalEditorToMarkdownServer, lexicalToMarkdown, linkVisitor };
|
|
45
|
+
export { inlineImageVisitor, lexicalEditorEmbedServer, lexicalEditorPopulateServer, lexicalEditorToMarkdownServer, lexicalEditorToTextServer, lexicalToMarkdown, lexicalToText, linkVisitor };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.15.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -77,10 +77,10 @@
|
|
|
77
77
|
"npm-run-all": "^4.1.5",
|
|
78
78
|
"prism-react-renderer": "^2.4.1",
|
|
79
79
|
"react-error-boundary": "^6.1.1",
|
|
80
|
-
"@byline/core": "3.
|
|
81
|
-
"@byline/client": "3.
|
|
82
|
-
"@byline/
|
|
83
|
-
"@byline/
|
|
80
|
+
"@byline/core": "3.15.0",
|
|
81
|
+
"@byline/client": "3.15.0",
|
|
82
|
+
"@byline/ui": "3.15.0",
|
|
83
|
+
"@byline/admin": "3.15.0"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
86
|
"react": "^19.0.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from 'vitest'
|
|
10
|
+
|
|
11
|
+
import { lexicalToText } from './lexical-to-text'
|
|
12
|
+
|
|
13
|
+
const state = (children: unknown[]) => ({ root: { type: 'root', children } })
|
|
14
|
+
|
|
15
|
+
const para = (text: string) => ({
|
|
16
|
+
type: 'paragraph',
|
|
17
|
+
children: [{ type: 'text', text }],
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('lexicalToText', () => {
|
|
21
|
+
it('returns empty string for null / undefined / non-editor values', () => {
|
|
22
|
+
expect(lexicalToText(null)).toBe('')
|
|
23
|
+
expect(lexicalToText(undefined)).toBe('')
|
|
24
|
+
expect(lexicalToText(42)).toBe('')
|
|
25
|
+
expect(lexicalToText({ not: 'an editor state' })).toBe('')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('parses a stringified editor state', () => {
|
|
29
|
+
expect(lexicalToText(JSON.stringify(state([para('Hello world')])))).toBe('Hello world')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('joins block-level nodes with newlines', () => {
|
|
33
|
+
expect(lexicalToText(state([para('First'), para('Second')]))).toBe('First\nSecond')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('concatenates inline text and follows link children', () => {
|
|
37
|
+
const node = {
|
|
38
|
+
type: 'paragraph',
|
|
39
|
+
children: [
|
|
40
|
+
{ type: 'text', text: 'See ' },
|
|
41
|
+
{ type: 'link', children: [{ type: 'text', text: 'the docs' }] },
|
|
42
|
+
{ type: 'text', text: ' now.' },
|
|
43
|
+
],
|
|
44
|
+
}
|
|
45
|
+
expect(lexicalToText(state([node]))).toBe('See the docs now.')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('recurses into headings, lists, and quotes', () => {
|
|
49
|
+
const heading = { type: 'heading', tag: 'h1', children: [{ type: 'text', text: 'Title' }] }
|
|
50
|
+
const list = {
|
|
51
|
+
type: 'list',
|
|
52
|
+
children: [
|
|
53
|
+
{ type: 'listitem', children: [{ type: 'text', text: 'one' }] },
|
|
54
|
+
{ type: 'listitem', children: [{ type: 'text', text: 'two' }] },
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
expect(lexicalToText(state([heading, list]))).toBe('Title\none\ntwo')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('skips embed-only nodes (youtube/vimeo)', () => {
|
|
61
|
+
expect(
|
|
62
|
+
lexicalToText(state([para('before'), { type: 'youtube', videoID: 'x' }, para('after')]))
|
|
63
|
+
).toBe('before\nafter')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('extracts nested editor states from inline-image captions', () => {
|
|
67
|
+
const node = {
|
|
68
|
+
type: 'inline-image',
|
|
69
|
+
altText: 'A diagram',
|
|
70
|
+
showCaption: true,
|
|
71
|
+
caption: { editorState: state([para('Figure 1')]) },
|
|
72
|
+
}
|
|
73
|
+
expect(lexicalToText(state([node]))).toBe('A diagram\nFigure 1')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One-way Lexical → plain-text extractor for search indexing — flattens a
|
|
11
|
+
* stored `SerializedEditorState` to indexable plain text via a recursive
|
|
12
|
+
* text-node accumulator. No markdown syntax, no link URLs, no images: just
|
|
13
|
+
* the words, with paragraph breaks between block-level nodes.
|
|
14
|
+
*
|
|
15
|
+
* Registered through `ServerConfig.fields.richText.toText` (see
|
|
16
|
+
* `lexicalEditorToTextServer` in `../server`) and consumed by core's
|
|
17
|
+
* `buildSearchDocument` to feed a collection's searchable `body`. Walks the
|
|
18
|
+
* stored JSON directly — no editor instantiation, no DOM, no DB reads —
|
|
19
|
+
* exactly like the markdown serializer. Lossy by contract.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type AnyNode = {
|
|
23
|
+
type?: string
|
|
24
|
+
text?: string
|
|
25
|
+
children?: AnyNode[]
|
|
26
|
+
// Nested editor states (admonition body, inline-image caption).
|
|
27
|
+
content?: { editorState?: unknown }
|
|
28
|
+
caption?: { editorState?: unknown }
|
|
29
|
+
title?: string
|
|
30
|
+
altText?: string
|
|
31
|
+
showCaption?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Inline node types whose children continue the current paragraph. */
|
|
35
|
+
const INLINE_CONTAINER_TYPES = new Set(['link', 'autolink'])
|
|
36
|
+
|
|
37
|
+
/** Leaf / embed node types that contribute no indexable text. */
|
|
38
|
+
const SKIP_TYPES = new Set(['youtube', 'vimeo', 'horizontalrule', 'tab'])
|
|
39
|
+
|
|
40
|
+
class LexicalTextConverter {
|
|
41
|
+
private paragraphs: string[] = []
|
|
42
|
+
private current = ''
|
|
43
|
+
|
|
44
|
+
private addText(text: string | null | undefined): void {
|
|
45
|
+
if (text != null) this.current += text
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private endParagraph(): void {
|
|
49
|
+
const p = this.current.trim()
|
|
50
|
+
if (p.length > 0) this.paragraphs.push(p)
|
|
51
|
+
this.current = ''
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getText(): string {
|
|
55
|
+
this.endParagraph()
|
|
56
|
+
return this.paragraphs.join('\n')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Block-level children: flush before and after so blocks separate. */
|
|
60
|
+
addBlockNodes(nodes: AnyNode[] | null | undefined): void {
|
|
61
|
+
this.endParagraph()
|
|
62
|
+
for (const node of nodes ?? []) this.addNode(node)
|
|
63
|
+
this.endParagraph()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Inline children: continue the current paragraph. */
|
|
67
|
+
private addInlineNodes(nodes: AnyNode[] | null | undefined): void {
|
|
68
|
+
for (const node of nodes ?? []) this.addNode(node)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private addNode(node: AnyNode): void {
|
|
72
|
+
const type = node.type
|
|
73
|
+
|
|
74
|
+
if (type === 'text' || type === 'code-highlight') {
|
|
75
|
+
this.addText(node.text)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (type === 'linebreak') {
|
|
79
|
+
this.endParagraph()
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
if (type === 'admonition') {
|
|
83
|
+
this.endParagraph()
|
|
84
|
+
this.addText(node.title)
|
|
85
|
+
this.endParagraph()
|
|
86
|
+
this.addText(lexicalToText(node.content?.editorState).trim())
|
|
87
|
+
this.endParagraph()
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
if (type === 'inline-image') {
|
|
91
|
+
this.endParagraph()
|
|
92
|
+
this.addText(node.altText)
|
|
93
|
+
if (node.showCaption) {
|
|
94
|
+
this.endParagraph()
|
|
95
|
+
this.addText(lexicalToText(node.caption?.editorState).trim())
|
|
96
|
+
}
|
|
97
|
+
this.endParagraph()
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
if (type != null && SKIP_TYPES.has(type)) return
|
|
101
|
+
|
|
102
|
+
if (type != null && INLINE_CONTAINER_TYPES.has(type)) {
|
|
103
|
+
this.addInlineNodes(node.children)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
// Default: any node with children is treated as block-level (paragraph,
|
|
107
|
+
// heading, quote, code, list, listitem, table*, layout*, …). Unknown
|
|
108
|
+
// childless nodes contribute nothing.
|
|
109
|
+
if (Array.isArray(node.children)) {
|
|
110
|
+
this.addBlockNodes(node.children)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a stored value (a `SerializedEditorState`, its `root`, or a
|
|
117
|
+
* stringified version of either) to the root node, mirroring the markdown
|
|
118
|
+
* serializer's `resolveRoot`.
|
|
119
|
+
*/
|
|
120
|
+
function resolveRoot(value: unknown): AnyNode | null {
|
|
121
|
+
let v = value
|
|
122
|
+
if (typeof v === 'string') {
|
|
123
|
+
try {
|
|
124
|
+
v = JSON.parse(v)
|
|
125
|
+
} catch {
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (typeof v !== 'object' || v === null) return null
|
|
130
|
+
const obj = v as { root?: AnyNode } & AnyNode
|
|
131
|
+
const root = (obj.root ?? obj) as AnyNode
|
|
132
|
+
return root.type === 'root' ? root : null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Extract indexable plain text from a (possibly null / stringified)
|
|
137
|
+
* serialized Lexical editor state. Returns `''` when there is no content.
|
|
138
|
+
*/
|
|
139
|
+
export function lexicalToText(value: unknown): string {
|
|
140
|
+
const root = resolveRoot(value)
|
|
141
|
+
if (root == null) return ''
|
|
142
|
+
const converter = new LexicalTextConverter()
|
|
143
|
+
converter.addBlockNodes(root.children)
|
|
144
|
+
return converter.getText()
|
|
145
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type {
|
|
|
44
44
|
RichTextPopulateContext,
|
|
45
45
|
RichTextPopulateFn,
|
|
46
46
|
RichTextToMarkdownFn,
|
|
47
|
+
RichTextToTextFn,
|
|
47
48
|
} from '@byline/core'
|
|
48
49
|
|
|
49
50
|
import { inlineImageVisitor } from './field/extensions/inline-image/populate'
|
|
@@ -57,6 +58,7 @@ import { type LexicalNodeVisitor, runLexicalPopulate } from './field/lexical-pop
|
|
|
57
58
|
// their CSS imports; the `/server` subpath stays React-free.
|
|
58
59
|
// ---------------------------------------------------------------------------
|
|
59
60
|
export { defaultEditorConfig } from './field/config/default'
|
|
61
|
+
export { lexicalToText } from './field/lexical-to-text'
|
|
60
62
|
export {
|
|
61
63
|
type LexicalToMarkdownOptions,
|
|
62
64
|
type LexicalToMarkdownResult,
|
|
@@ -64,6 +66,7 @@ export {
|
|
|
64
66
|
lexicalToMarkdown,
|
|
65
67
|
} from './field/markdown/lexical-to-markdown'
|
|
66
68
|
|
|
69
|
+
import { lexicalToText } from './field/lexical-to-text'
|
|
67
70
|
import {
|
|
68
71
|
type LexicalToMarkdownOptions,
|
|
69
72
|
lexicalToMarkdown,
|
|
@@ -147,6 +150,17 @@ export function lexicalEditorToMarkdownServer(
|
|
|
147
150
|
return (ctx) => lexicalToMarkdown(ctx.value, options).markdown
|
|
148
151
|
}
|
|
149
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Plain-text extractor for search indexing, shaped for
|
|
155
|
+
* `ServerConfig.fields.richText.toText`. Pure and synchronous — flattens
|
|
156
|
+
* the stored editor JSON to indexable plain text via `lexicalToText` (a
|
|
157
|
+
* recursive text-node accumulator, no markdown). Consumed by core's
|
|
158
|
+
* `buildSearchDocument` to feed a collection's searchable `body`.
|
|
159
|
+
*/
|
|
160
|
+
export function lexicalEditorToTextServer(): RichTextToTextFn {
|
|
161
|
+
return (ctx) => lexicalToText(ctx.value)
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
export function lexicalEditorEmbedServer(options: LexicalServerOptions): RichTextEmbedFn {
|
|
151
165
|
const visitors = options.visitors ?? [inlineImageVisitor, linkVisitor]
|
|
152
166
|
return async (ctx: RichTextEmbedContext): Promise<void> => {
|