@byline/richtext-lexical 2.5.2 → 2.6.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/inline-image/inline-image-modal.js +2 -1
- package/dist/field/extensions/link/link-modal.js +2 -1
- package/dist/richtext-field.js +2 -1
- package/package.json +7 -5
- package/src/field/extensions/inline-image/inline-image-modal.tsx +1 -1
- package/src/field/extensions/link/link-modal.tsx +1 -1
- package/src/field/extensions/link/populate.test.node.ts +351 -0
- package/src/richtext-field.tsx +2 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
|
+
import { RelationPicker } from "@byline/admin/react";
|
|
4
5
|
import { getCollectionDefinition } from "@byline/core";
|
|
5
|
-
import { Button, Checkbox, CloseIcon, ErrorText, IconButton, Input, Label, Modal, RadioGroup, RadioGroupItem
|
|
6
|
+
import { Button, Checkbox, CloseIcon, ErrorText, IconButton, Input, Label, Modal, RadioGroup, RadioGroupItem } from "@byline/ui/react";
|
|
6
7
|
import { useModalFormState } from "../../shared/useModalFormState.js";
|
|
7
8
|
import { isAltTextValid, positionOptions } from "./fields.js";
|
|
8
9
|
import { deriveImageSizes, getPreferredSize } from "./utils.js";
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
|
+
import { RelationPicker } from "@byline/admin/react";
|
|
4
5
|
import { getClientConfig, getCollectionDefinition } from "@byline/core";
|
|
5
|
-
import { Button, Checkbox, CloseIcon, IconButton, Input, Label, Modal, RadioGroup, RadioGroupItem,
|
|
6
|
+
import { Button, Checkbox, CloseIcon, IconButton, Input, Label, Modal, RadioGroup, RadioGroupItem, Select } from "@byline/ui/react";
|
|
6
7
|
import { useModalFormState } from "../../shared/useModalFormState.js";
|
|
7
8
|
import { validateUrl } from "../../utils/url.js";
|
|
8
9
|
function emptyState(linkable) {
|
package/dist/richtext-field.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { LocaleBadge, useFieldError, useFieldValue } from "@byline/admin/react";
|
|
3
|
+
import { ErrorText, Label } from "@byline/ui/react";
|
|
3
4
|
import classnames from "classnames";
|
|
4
5
|
import { defaultEditorConfig } from "./field/config/default.js";
|
|
5
6
|
import { defaultExtensionsList } from "./field/config/default-extensions.js";
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.6.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -72,9 +72,10 @@
|
|
|
72
72
|
"npm-run-all": "^4.1.5",
|
|
73
73
|
"prism-react-renderer": "^2.4.1",
|
|
74
74
|
"react-error-boundary": "^6.1.1",
|
|
75
|
-
"@byline/
|
|
76
|
-
"@byline/
|
|
77
|
-
"@byline/
|
|
75
|
+
"@byline/admin": "2.6.0",
|
|
76
|
+
"@byline/client": "2.6.0",
|
|
77
|
+
"@byline/core": "2.6.0",
|
|
78
|
+
"@byline/ui": "2.6.0"
|
|
78
79
|
},
|
|
79
80
|
"peerDependencies": {
|
|
80
81
|
"react": "^19.0.0",
|
|
@@ -115,6 +116,7 @@
|
|
|
115
116
|
"typecheck": "tsc --noEmit",
|
|
116
117
|
"clean": "node scripts/clean.js node_modules dist build types .turbo",
|
|
117
118
|
"lint": "biome check --write --unsafe --diagnostic-level=error",
|
|
118
|
-
"
|
|
119
|
+
"test": "vitest run --mode=node",
|
|
120
|
+
"test:watch": "vitest --mode=node"
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type * as React from 'react'
|
|
12
12
|
import { useMemo, useState } from 'react'
|
|
13
13
|
|
|
14
|
+
import { RelationPicker } from '@byline/admin/react'
|
|
14
15
|
import type { CollectionDefinition, StoredFileValue } from '@byline/core'
|
|
15
16
|
import { getCollectionDefinition } from '@byline/core'
|
|
16
17
|
import {
|
|
@@ -24,7 +25,6 @@ import {
|
|
|
24
25
|
Modal,
|
|
25
26
|
RadioGroup,
|
|
26
27
|
RadioGroupItem,
|
|
27
|
-
RelationPicker,
|
|
28
28
|
} from '@byline/ui/react'
|
|
29
29
|
|
|
30
30
|
import { useModalFormState } from '../../shared/useModalFormState'
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type * as React from 'react'
|
|
12
12
|
import { useMemo, useState } from 'react'
|
|
13
13
|
|
|
14
|
+
import { RelationPicker } from '@byline/admin/react'
|
|
14
15
|
import type { CollectionDefinition } from '@byline/core'
|
|
15
16
|
import { getClientConfig, getCollectionDefinition } from '@byline/core'
|
|
16
17
|
import {
|
|
@@ -23,7 +24,6 @@ import {
|
|
|
23
24
|
Modal,
|
|
24
25
|
RadioGroup,
|
|
25
26
|
RadioGroupItem,
|
|
26
|
-
RelationPicker,
|
|
27
27
|
Select,
|
|
28
28
|
type SelectValue,
|
|
29
29
|
} from '@byline/ui/react'
|
|
@@ -0,0 +1,351 @@
|
|
|
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
|
+
* Fixture-driven tests for the link visitor's three resolution branches:
|
|
11
|
+
*
|
|
12
|
+
* - Found — target resolved, `buildDocumentPath` returned a string
|
|
13
|
+
* - Found (fallback) — target resolved, generic `/${collectionPath}/${path}` compose
|
|
14
|
+
* - Branch A (threw) — `buildDocumentPath` raised
|
|
15
|
+
* - Branch B (missing) — target deleted between picker and walker
|
|
16
|
+
*
|
|
17
|
+
* The visitor itself is framework-agnostic and exercised directly: build
|
|
18
|
+
* a `LexicalNodeLike`, call `linkVisitor.match(node)`, then invoke the
|
|
19
|
+
* returned `apply` / `applyMissing` and assert on the mutated `node`.
|
|
20
|
+
*
|
|
21
|
+
* Branch C (hard transport failures) is owned by the framework caller
|
|
22
|
+
* (`embedRichTextFields`) and tested in `packages/core` against that
|
|
23
|
+
* service — see `richtext-embed.test.node.ts`.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { type BylineLogger, type CollectionDefinition, defineServerConfig } from '@byline/core'
|
|
27
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
28
|
+
|
|
29
|
+
import { linkVisitor } from './populate'
|
|
30
|
+
import type { LexicalNodeLike } from '../../lexical-populate-shared'
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Test harness — logger + config registration
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
function makeLogger(): BylineLogger {
|
|
37
|
+
return {
|
|
38
|
+
log: vi.fn(),
|
|
39
|
+
fatal: vi.fn(),
|
|
40
|
+
error: vi.fn(),
|
|
41
|
+
warn: vi.fn(),
|
|
42
|
+
info: vi.fn(),
|
|
43
|
+
debug: vi.fn(),
|
|
44
|
+
trace: vi.fn(),
|
|
45
|
+
silent: vi.fn(),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function registerCollection(definition: Partial<CollectionDefinition> & { path: string }): void {
|
|
50
|
+
defineServerConfig({
|
|
51
|
+
collections: [
|
|
52
|
+
{
|
|
53
|
+
labels: { singular: 'Page', plural: 'Pages' },
|
|
54
|
+
fields: [{ name: 'title', type: 'text', label: 'Title' }],
|
|
55
|
+
useAsTitle: 'title',
|
|
56
|
+
...definition,
|
|
57
|
+
} as CollectionDefinition,
|
|
58
|
+
],
|
|
59
|
+
} as Parameters<typeof defineServerConfig>[0])
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function clearConfig(): void {
|
|
63
|
+
// Mirror the config module's globalThis-symbol storage so tests can
|
|
64
|
+
// register a fresh collection set per case without bleed-over.
|
|
65
|
+
;(globalThis as any)[Symbol.for('__byline_server_config__')] = null
|
|
66
|
+
;(globalThis as any)[Symbol.for('__byline_client_config__')] = null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function installLogger(logger: BylineLogger): void {
|
|
70
|
+
// `defineLogger` is not re-exported from `@byline/core`'s root barrel —
|
|
71
|
+
// setting the symbol directly mirrors what `initBylineCore()` does at
|
|
72
|
+
// boot and avoids growing the public surface for test wiring.
|
|
73
|
+
;(globalThis as any)[Symbol.for('__byline_logger__')] = logger
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Node fixtures
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
function makeInternalLinkNode(opts: {
|
|
81
|
+
documentId?: string
|
|
82
|
+
collectionPath?: string
|
|
83
|
+
document?: Record<string, any>
|
|
84
|
+
}): LexicalNodeLike {
|
|
85
|
+
return {
|
|
86
|
+
type: 'link',
|
|
87
|
+
attributes: {
|
|
88
|
+
linkType: 'internal',
|
|
89
|
+
targetDocumentId: opts.documentId ?? 'doc-1',
|
|
90
|
+
targetCollectionPath: opts.collectionPath ?? 'pages',
|
|
91
|
+
document: opts.document,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const targetFixture = {
|
|
97
|
+
id: 'doc-1',
|
|
98
|
+
path: 'about',
|
|
99
|
+
status: 'published',
|
|
100
|
+
fields: { title: 'About Us', area: 'root' },
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Suite
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
describe('linkVisitor', () => {
|
|
108
|
+
let logger: BylineLogger
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
logger = makeLogger()
|
|
112
|
+
installLogger(logger)
|
|
113
|
+
clearConfig()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
clearConfig()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// -------------------------------------------------------------------------
|
|
121
|
+
// match() — skip-conditions
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
describe('match', () => {
|
|
125
|
+
it('returns null for non-link nodes', () => {
|
|
126
|
+
const node: LexicalNodeLike = { type: 'paragraph' }
|
|
127
|
+
expect(linkVisitor.match(node)).toBeNull()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('returns null for custom-URL links', () => {
|
|
131
|
+
const node: LexicalNodeLike = {
|
|
132
|
+
type: 'link',
|
|
133
|
+
attributes: { linkType: 'custom', url: 'https://example.com' },
|
|
134
|
+
}
|
|
135
|
+
expect(linkVisitor.match(node)).toBeNull()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('returns null when targetDocumentId is missing', () => {
|
|
139
|
+
const node: LexicalNodeLike = {
|
|
140
|
+
type: 'link',
|
|
141
|
+
attributes: { linkType: 'internal', targetCollectionPath: 'pages' },
|
|
142
|
+
}
|
|
143
|
+
expect(linkVisitor.match(node)).toBeNull()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('returns null when targetCollectionPath is missing', () => {
|
|
147
|
+
const node: LexicalNodeLike = {
|
|
148
|
+
type: 'link',
|
|
149
|
+
attributes: { linkType: 'internal', targetDocumentId: 'doc-1' },
|
|
150
|
+
}
|
|
151
|
+
expect(linkVisitor.match(node)).toBeNull()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('returns a PendingHydration for a well-formed internal link', () => {
|
|
155
|
+
registerCollection({ path: 'pages' })
|
|
156
|
+
const node = makeInternalLinkNode({})
|
|
157
|
+
const pending = linkVisitor.match(node)
|
|
158
|
+
expect(pending).not.toBeNull()
|
|
159
|
+
expect(pending?.collectionPath).toBe('pages')
|
|
160
|
+
expect(pending?.documentId).toBe('doc-1')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// -------------------------------------------------------------------------
|
|
165
|
+
// apply() — Found branches
|
|
166
|
+
// -------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
describe('apply (found)', () => {
|
|
169
|
+
it('uses buildDocumentPath when defined and returning a string', () => {
|
|
170
|
+
registerCollection({
|
|
171
|
+
path: 'pages',
|
|
172
|
+
useAsTitle: 'title',
|
|
173
|
+
buildDocumentPath: (doc) => `/custom/${doc.path}`,
|
|
174
|
+
})
|
|
175
|
+
const node = makeInternalLinkNode({})
|
|
176
|
+
const pending = linkVisitor.match(node)
|
|
177
|
+
pending?.apply(targetFixture)
|
|
178
|
+
|
|
179
|
+
expect(node.attributes?.document).toEqual({
|
|
180
|
+
title: 'About Us',
|
|
181
|
+
path: '/custom/about',
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('falls back to generic compose when buildDocumentPath returns null', () => {
|
|
186
|
+
registerCollection({
|
|
187
|
+
path: 'pages',
|
|
188
|
+
useAsTitle: 'title',
|
|
189
|
+
buildDocumentPath: () => null,
|
|
190
|
+
})
|
|
191
|
+
const node = makeInternalLinkNode({})
|
|
192
|
+
const pending = linkVisitor.match(node)
|
|
193
|
+
pending?.apply(targetFixture)
|
|
194
|
+
|
|
195
|
+
expect(node.attributes?.document?.path).toBe('/pages/about')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('falls back to generic compose when buildDocumentPath is not defined', () => {
|
|
199
|
+
registerCollection({ path: 'pages', useAsTitle: 'title' })
|
|
200
|
+
const node = makeInternalLinkNode({})
|
|
201
|
+
const pending = linkVisitor.match(node)
|
|
202
|
+
pending?.apply(targetFixture)
|
|
203
|
+
|
|
204
|
+
expect(node.attributes?.document?.path).toBe('/pages/about')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('refreshes title from useAsTitle (defaults to "title" when not set)', () => {
|
|
208
|
+
registerCollection({ path: 'pages' })
|
|
209
|
+
const node = makeInternalLinkNode({
|
|
210
|
+
document: { title: 'Stale Title', path: '/stale' },
|
|
211
|
+
})
|
|
212
|
+
const pending = linkVisitor.match(node)
|
|
213
|
+
pending?.apply(targetFixture)
|
|
214
|
+
|
|
215
|
+
expect(node.attributes?.document?.title).toBe('About Us')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('honours a custom useAsTitle field', () => {
|
|
219
|
+
registerCollection({
|
|
220
|
+
path: 'pages',
|
|
221
|
+
useAsTitle: 'headline',
|
|
222
|
+
fields: [
|
|
223
|
+
{ name: 'title', type: 'text', label: 'Title' },
|
|
224
|
+
{ name: 'headline', type: 'text', label: 'Headline' },
|
|
225
|
+
],
|
|
226
|
+
})
|
|
227
|
+
const node = makeInternalLinkNode({})
|
|
228
|
+
const pending = linkVisitor.match(node)
|
|
229
|
+
pending?.apply({
|
|
230
|
+
...targetFixture,
|
|
231
|
+
fields: { title: 'fallback', headline: 'Real Headline' },
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
expect(node.attributes?.document?.title).toBe('Real Headline')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('clears a stale _resolved: false flag when the target is now found', () => {
|
|
238
|
+
registerCollection({ path: 'pages' })
|
|
239
|
+
const node = makeInternalLinkNode({
|
|
240
|
+
document: { _resolved: false },
|
|
241
|
+
})
|
|
242
|
+
const pending = linkVisitor.match(node)
|
|
243
|
+
pending?.apply(targetFixture)
|
|
244
|
+
|
|
245
|
+
expect(node.attributes?.document?._resolved).toBeUndefined()
|
|
246
|
+
expect(node.attributes?.document?.path).toBe('/pages/about')
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('leaves path untouched when target.path is empty and no hook is defined', () => {
|
|
250
|
+
// Generic fallback bails when target.path is empty rather than
|
|
251
|
+
// emitting `/pages/undefined`.
|
|
252
|
+
registerCollection({ path: 'pages' })
|
|
253
|
+
const node = makeInternalLinkNode({
|
|
254
|
+
document: { title: 'Old', path: '/previous' },
|
|
255
|
+
})
|
|
256
|
+
const pending = linkVisitor.match(node)
|
|
257
|
+
pending?.apply({ ...targetFixture, path: '' })
|
|
258
|
+
|
|
259
|
+
expect(node.attributes?.document?.path).toBe('/previous')
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// -------------------------------------------------------------------------
|
|
264
|
+
// apply() — Branch A: buildDocumentPath threw
|
|
265
|
+
// -------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
describe('apply (branch A — hook threw)', () => {
|
|
268
|
+
it('logs at info and leaves document.path untouched', () => {
|
|
269
|
+
registerCollection({
|
|
270
|
+
path: 'pages',
|
|
271
|
+
useAsTitle: 'title',
|
|
272
|
+
buildDocumentPath: () => {
|
|
273
|
+
throw new Error('boom')
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
const node = makeInternalLinkNode({
|
|
277
|
+
document: { title: 'Previous', path: '/previous' },
|
|
278
|
+
})
|
|
279
|
+
const pending = linkVisitor.match(node)
|
|
280
|
+
pending?.apply(targetFixture)
|
|
281
|
+
|
|
282
|
+
// Path preserved (branch A's whole point).
|
|
283
|
+
expect(node.attributes?.document?.path).toBe('/previous')
|
|
284
|
+
// Title still refreshes — only the path resolution failed.
|
|
285
|
+
expect(node.attributes?.document?.title).toBe('About Us')
|
|
286
|
+
expect(logger.info).toHaveBeenCalledWith(
|
|
287
|
+
expect.objectContaining({
|
|
288
|
+
collectionPath: 'pages',
|
|
289
|
+
documentId: 'doc-1',
|
|
290
|
+
err: expect.any(Error),
|
|
291
|
+
}),
|
|
292
|
+
'buildDocumentPath threw'
|
|
293
|
+
)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('does not fall back to generic compose when the hook threw', () => {
|
|
297
|
+
registerCollection({
|
|
298
|
+
path: 'pages',
|
|
299
|
+
buildDocumentPath: () => {
|
|
300
|
+
throw new Error('boom')
|
|
301
|
+
},
|
|
302
|
+
})
|
|
303
|
+
const node = makeInternalLinkNode({ document: {} })
|
|
304
|
+
const pending = linkVisitor.match(node)
|
|
305
|
+
pending?.apply(targetFixture)
|
|
306
|
+
|
|
307
|
+
// No path written — neither hook output nor generic fallback fired.
|
|
308
|
+
expect(node.attributes?.document?.path).toBeUndefined()
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
// -------------------------------------------------------------------------
|
|
313
|
+
// applyMissing() — Branch B: target deleted
|
|
314
|
+
// -------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
describe('applyMissing (branch B — target deleted)', () => {
|
|
317
|
+
it('deletes title and path and sets _resolved: false', () => {
|
|
318
|
+
registerCollection({ path: 'pages' })
|
|
319
|
+
const node = makeInternalLinkNode({
|
|
320
|
+
document: { title: 'Stale', path: '/stale-path' },
|
|
321
|
+
})
|
|
322
|
+
const pending = linkVisitor.match(node)
|
|
323
|
+
pending?.applyMissing?.()
|
|
324
|
+
|
|
325
|
+
expect(node.attributes?.document?.title).toBeUndefined()
|
|
326
|
+
expect(node.attributes?.document?.path).toBeUndefined()
|
|
327
|
+
expect(node.attributes?.document?._resolved).toBe(false)
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('logs at warn level', () => {
|
|
331
|
+
registerCollection({ path: 'pages' })
|
|
332
|
+
const node = makeInternalLinkNode({ document: { title: 'Stale' } })
|
|
333
|
+
const pending = linkVisitor.match(node)
|
|
334
|
+
pending?.applyMissing?.()
|
|
335
|
+
|
|
336
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
337
|
+
{ collectionPath: 'pages', documentId: 'doc-1' },
|
|
338
|
+
'internal link target not found'
|
|
339
|
+
)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('still produces a usable envelope when no prior document was present', () => {
|
|
343
|
+
registerCollection({ path: 'pages' })
|
|
344
|
+
const node = makeInternalLinkNode({})
|
|
345
|
+
const pending = linkVisitor.match(node)
|
|
346
|
+
pending?.applyMissing?.()
|
|
347
|
+
|
|
348
|
+
expect(node.attributes?.document).toEqual({ _resolved: false })
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
})
|
package/src/richtext-field.tsx
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import type React from 'react'
|
|
10
10
|
|
|
11
|
+
import { LocaleBadge, useFieldError, useFieldValue } from '@byline/admin/react'
|
|
11
12
|
import type { RichTextField as FieldType } from '@byline/core'
|
|
12
|
-
import { ErrorText, Label
|
|
13
|
+
import { ErrorText, Label } from '@byline/ui/react'
|
|
13
14
|
import cx from 'classnames'
|
|
14
15
|
|
|
15
16
|
import { defaultEditorConfig } from './field/config/default'
|