@byline/richtext-lexical 4.16.0 → 4.17.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.
@@ -2,7 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useMemo, useState } from "react";
4
4
  import { RelationPicker } from "@byline/admin/react";
5
- import { getCollectionDefinition } from "@byline/core";
5
+ import { getCollectionDefinition, isSingleton } from "@byline/core";
6
6
  import { Button, Checkbox, CloseIcon, ErrorText, IconButton, Input, Label, Modal, RadioGroup, RadioGroupItem } from "@byline/ui/react";
7
7
  import { useModalFormState } from "../../shared/useModalFormState.js";
8
8
  import { isAltTextValid, positionOptions } from "./fields.js";
@@ -49,7 +49,8 @@ const InlineImageModal = ({ isOpen, collection, data: dataFromProps, onSubmit, o
49
49
  setAltError(null);
50
50
  setImageError(null);
51
51
  });
52
- const targetDef = getCollectionDefinition(collection);
52
+ const targetDefinition = getCollectionDefinition(collection);
53
+ const targetDef = null == targetDefinition || isSingleton(targetDefinition) ? null : targetDefinition;
53
54
  const pickedImage = useMemo(()=>{
54
55
  const img = state.documentRelation?.document?.image;
55
56
  return img ?? null;
@@ -2,7 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useMemo, useState } from "react";
4
4
  import { RelationPicker } from "@byline/admin/react";
5
- import { getAdminConfig, getCollectionDefinition } from "@byline/core";
5
+ import { getAdminConfig, getCollectionDefinition, isSingleton } from "@byline/core";
6
6
  import { Button, Checkbox, CloseIcon, IconButton, Input, Label, Modal, RadioGroup, RadioGroupItem, Select } from "@byline/ui/react";
7
7
  import { useModalFormState } from "../../shared/useModalFormState.js";
8
8
  import { validateUrl } from "../../utils/url.js";
@@ -41,11 +41,12 @@ function fromLinkData(data, linkable) {
41
41
  };
42
42
  }
43
43
  const LinkModal = ({ isOpen = false, onSubmit, onClose, data: dataFromProps })=>{
44
- const linkable = useMemo(()=>getAdminConfig().collections.filter((c)=>true === c.linksInEditor), []);
44
+ const linkable = useMemo(()=>getAdminConfig().collections.filter((collection)=>!isSingleton(collection) && true === collection.linksInEditor), []);
45
45
  const [pickerOpen, setPickerOpen] = useState(false);
46
46
  const [urlError, setUrlError] = useState(null);
47
47
  const [state, setState] = useModalFormState(isOpen, ()=>fromLinkData(dataFromProps, linkable), ()=>setUrlError(null));
48
- const targetDef = state.targetCollection ? getCollectionDefinition(state.targetCollection) : null;
48
+ const targetDefinition = state.targetCollection ? getCollectionDefinition(state.targetCollection) : null;
49
+ const targetDef = null == targetDefinition || isSingleton(targetDefinition) ? null : targetDefinition;
49
50
  const collectionItems = useMemo(()=>linkable.map((c)=>({
50
51
  label: c.labels.singular,
51
52
  value: c.path
@@ -56,7 +57,8 @@ const LinkModal = ({ isOpen = false, onSubmit, onClose, data: dataFromProps })=>
56
57
  if ('internal' !== state.linkType || !state.picked) return null;
57
58
  const title = state.picked.document?.title;
58
59
  if ('string' == typeof title && title.length > 0) return title;
59
- const pickedDef = getCollectionDefinition(state.picked.targetCollectionPath);
60
+ const pickedDefinition = getCollectionDefinition(state.picked.targetCollectionPath);
61
+ const pickedDef = null == pickedDefinition || isSingleton(pickedDefinition) ? null : pickedDefinition;
60
62
  const short = state.picked.targetDocumentId.slice(0, 8);
61
63
  return `${pickedDef?.labels.singular ?? state.picked.targetCollectionPath} · ${short}…`;
62
64
  }, [
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type * as React from 'react';
9
9
  import { DecoratorBlockNode, type SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
10
- import type { DOMConversionMap, DOMExportOutput, EditorConfig, ElementFormatType, LexicalEditor, LexicalNode, NodeKey, Spread } from 'lexical';
10
+ import type { DOMConversionMap, DOMExportOutput, EditorConfig, ElementFormatType, LexicalEditor, NodeKey, Spread } from 'lexical';
11
11
  export type SerializedVimeoNode = Spread<{
12
12
  videoID: string;
13
13
  }, SerializedDecoratorBlockNode>;
@@ -26,4 +26,3 @@ export declare class VimeoNode extends DecoratorBlockNode {
26
26
  decorate(_editor: LexicalEditor, config: EditorConfig): React.JSX.Element;
27
27
  }
28
28
  export declare function $createVimeoNode(videoID: string): VimeoNode;
29
- export declare function $isVimeoNode(node: VimeoNode | LexicalNode | null | undefined): node is VimeoNode;
@@ -103,7 +103,4 @@ class VimeoNode extends DecoratorBlockNode {
103
103
  function $createVimeoNode(videoID) {
104
104
  return new VimeoNode(videoID);
105
105
  }
106
- function $isVimeoNode(node) {
107
- return node instanceof VimeoNode;
108
- }
109
- export { $createVimeoNode, $isVimeoNode, VimeoNode };
106
+ export { $createVimeoNode, VimeoNode };
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type * as React from 'react';
9
9
  import { DecoratorBlockNode, type SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
10
- import type { DOMConversionMap, DOMExportOutput, EditorConfig, ElementFormatType, LexicalEditor, LexicalNode, NodeKey, Spread } from 'lexical';
10
+ import type { DOMConversionMap, DOMExportOutput, EditorConfig, ElementFormatType, LexicalEditor, NodeKey, Spread } from 'lexical';
11
11
  export type SerializedYouTubeNode = Spread<{
12
12
  videoID: string;
13
13
  }, SerializedDecoratorBlockNode>;
@@ -26,4 +26,3 @@ export declare class YouTubeNode extends DecoratorBlockNode {
26
26
  decorate(_editor: LexicalEditor, config: EditorConfig): React.JSX.Element;
27
27
  }
28
28
  export declare function $createYouTubeNode(videoID: string): YouTubeNode;
29
- export declare function $isYouTubeNode(node: YouTubeNode | LexicalNode | null | undefined): node is YouTubeNode;
@@ -104,7 +104,4 @@ class YouTubeNode extends DecoratorBlockNode {
104
104
  function $createYouTubeNode(videoID) {
105
105
  return new YouTubeNode(videoID);
106
106
  }
107
- function $isYouTubeNode(node) {
108
- return node instanceof YouTubeNode;
109
- }
110
- export { $createYouTubeNode, $isYouTubeNode, YouTubeNode };
107
+ export { $createYouTubeNode, YouTubeNode };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "4.16.0",
6
+ "version": "4.17.0",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -72,28 +72,26 @@
72
72
  "clsx": "^2.1.1",
73
73
  "lexical": "0.49.0",
74
74
  "react-error-boundary": "^6.1.3",
75
- "@byline/admin": "4.16.0",
76
- "@byline/client": "4.16.0",
77
- "@byline/core": "4.16.0",
78
- "@byline/ui": "4.16.0"
75
+ "@byline/admin": "4.17.0",
76
+ "@byline/ui": "4.17.0",
77
+ "@byline/client": "4.17.0",
78
+ "@byline/core": "4.17.0"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "react": "^19.0.0",
82
82
  "react-dom": "^19.0.0"
83
83
  },
84
84
  "devDependencies": {
85
- "@biomejs/biome": "2.5.9",
85
+ "@biomejs/biome": "2.5.10",
86
86
  "@rsbuild/plugin-react": "^2.1.0",
87
87
  "@rslib/core": "^0.23.2",
88
- "@types/node": "^26.2.0",
88
+ "@types/node": "^26.4.0",
89
89
  "@types/react": "19.2.18",
90
- "@types/react-dom": "19.2.4",
90
+ "@types/react-dom": "19.2.5",
91
91
  "@vitejs/plugin-react": "^6.1.0",
92
- "lightningcss": "^1.33.0",
93
92
  "npm-run-all": "^4.1.5",
94
93
  "react": "^19.2.8",
95
94
  "react-dom": "^19.2.8",
96
- "rimraf": "^6.1.3",
97
95
  "typescript": "^7.0.2",
98
96
  "typescript-plugin-css-modules": "^5.2.0",
99
97
  "vite": "^8.2.2",
@@ -12,8 +12,8 @@ import type * as React from 'react'
12
12
  import { useMemo, useState } from 'react'
13
13
 
14
14
  import { RelationPicker } from '@byline/admin/react'
15
- import type { CollectionDefinition, StoredFileValue } from '@byline/core'
16
- import { getCollectionDefinition } from '@byline/core'
15
+ import type { MultiCollectionDefinition, StoredFileValue } from '@byline/core'
16
+ import { getCollectionDefinition, isSingleton } from '@byline/core'
17
17
  import {
18
18
  Button,
19
19
  Checkbox,
@@ -106,7 +106,9 @@ export const InlineImageModal: React.FC<InlineImageModalProps> = ({
106
106
  }
107
107
  )
108
108
 
109
- const targetDef: CollectionDefinition | null = getCollectionDefinition(collection)
109
+ const targetDefinition = getCollectionDefinition(collection)
110
+ const targetDef: MultiCollectionDefinition | null =
111
+ targetDefinition != null && !isSingleton(targetDefinition) ? targetDefinition : null
110
112
 
111
113
  // The image StoredFileValue lives inside the picked media doc's denormalised
112
114
  // `documentRelation.document.image` field — see `handlePickerSelect` below.
@@ -12,8 +12,8 @@ import type * as React from 'react'
12
12
  import { useMemo, useState } from 'react'
13
13
 
14
14
  import { RelationPicker } from '@byline/admin/react'
15
- import type { CollectionDefinition } from '@byline/core'
16
- import { getAdminConfig, getCollectionDefinition } from '@byline/core'
15
+ import type { MultiCollectionDefinition } from '@byline/core'
16
+ import { getAdminConfig, getCollectionDefinition, isSingleton } from '@byline/core'
17
17
  import {
18
18
  Button,
19
19
  Checkbox,
@@ -48,7 +48,7 @@ interface FormState {
48
48
  picked: DocumentRelation | null
49
49
  }
50
50
 
51
- function emptyState(linkable: CollectionDefinition[]): FormState {
51
+ function emptyState(linkable: MultiCollectionDefinition[]): FormState {
52
52
  return {
53
53
  text: '',
54
54
  linkType: linkable.length > 0 ? 'internal' : 'custom',
@@ -59,7 +59,10 @@ function emptyState(linkable: CollectionDefinition[]): FormState {
59
59
  }
60
60
  }
61
61
 
62
- function fromLinkData(data: LinkData | undefined, linkable: CollectionDefinition[]): FormState {
62
+ function fromLinkData(
63
+ data: LinkData | undefined,
64
+ linkable: MultiCollectionDefinition[]
65
+ ): FormState {
63
66
  const base = emptyState(linkable)
64
67
  if (!data) return base
65
68
  const fields = data.fields
@@ -101,8 +104,12 @@ export const LinkModal: React.FC<LinkModalProps> = ({
101
104
  onClose,
102
105
  data: dataFromProps,
103
106
  }) => {
104
- const linkable = useMemo<CollectionDefinition[]>(
105
- () => getAdminConfig().collections.filter((c) => c.linksInEditor === true),
107
+ const linkable = useMemo<MultiCollectionDefinition[]>(
108
+ () =>
109
+ getAdminConfig().collections.filter(
110
+ (collection): collection is MultiCollectionDefinition =>
111
+ !isSingleton(collection) && collection.linksInEditor === true
112
+ ),
106
113
  []
107
114
  )
108
115
 
@@ -115,9 +122,11 @@ export const LinkModal: React.FC<LinkModalProps> = ({
115
122
  () => setUrlError(null)
116
123
  )
117
124
 
118
- const targetDef: CollectionDefinition | null = state.targetCollection
125
+ const targetDefinition = state.targetCollection
119
126
  ? getCollectionDefinition(state.targetCollection)
120
127
  : null
128
+ const targetDef: MultiCollectionDefinition | null =
129
+ targetDefinition != null && !isSingleton(targetDefinition) ? targetDefinition : null
121
130
 
122
131
  const collectionItems: SelectValue<string>[] = useMemo(
123
132
  () => linkable.map((c) => ({ label: c.labels.singular, value: c.path })),
@@ -129,7 +138,9 @@ export const LinkModal: React.FC<LinkModalProps> = ({
129
138
  const title = state.picked.document?.title
130
139
  if (typeof title === 'string' && title.length > 0) return title
131
140
  // No title cached — show a stable stub keyed off the collection.
132
- const pickedDef = getCollectionDefinition(state.picked.targetCollectionPath)
141
+ const pickedDefinition = getCollectionDefinition(state.picked.targetCollectionPath)
142
+ const pickedDef =
143
+ pickedDefinition != null && !isSingleton(pickedDefinition) ? pickedDefinition : null
133
144
  const short = state.picked.targetDocumentId.slice(0, 8)
134
145
  return `${pickedDef?.labels.singular ?? state.picked.targetCollectionPath} · ${short}…`
135
146
  }, [state.linkType, state.picked])
@@ -20,7 +20,6 @@ import type {
20
20
  EditorConfig,
21
21
  ElementFormatType,
22
22
  LexicalEditor,
23
- LexicalNode,
24
23
  NodeKey,
25
24
  Spread,
26
25
  } from 'lexical'
@@ -166,7 +165,3 @@ export class VimeoNode extends DecoratorBlockNode {
166
165
  export function $createVimeoNode(videoID: string): VimeoNode {
167
166
  return new VimeoNode(videoID)
168
167
  }
169
-
170
- export function $isVimeoNode(node: VimeoNode | LexicalNode | null | undefined): node is VimeoNode {
171
- return node instanceof VimeoNode
172
- }
@@ -20,7 +20,6 @@ import type {
20
20
  EditorConfig,
21
21
  ElementFormatType,
22
22
  LexicalEditor,
23
- LexicalNode,
24
23
  NodeKey,
25
24
  Spread,
26
25
  } from 'lexical'
@@ -170,9 +169,3 @@ export class YouTubeNode extends DecoratorBlockNode {
170
169
  export function $createYouTubeNode(videoID: string): YouTubeNode {
171
170
  return new YouTubeNode(videoID)
172
171
  }
173
-
174
- export function $isYouTubeNode(
175
- node: YouTubeNode | LexicalNode | null | undefined
176
- ): node is YouTubeNode {
177
- return node instanceof YouTubeNode
178
- }