@byline/cli 2.1.3 → 2.2.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/manifest/deps.d.ts.map +1 -1
- package/dist/manifest/deps.js +34 -0
- package/dist/manifest/deps.js.map +1 -1
- package/dist/templates/byline/i18n.ts +1 -1
- package/dist/templates/byline-examples/admin.config.ts +12 -0
- package/dist/templates/byline-examples/collections/media/components/media-list-view.module.css +1 -1
- package/dist/templates/byline-examples/collections/news/admin.tsx +7 -2
- package/dist/templates/byline-examples/collections/pages/admin.tsx +2 -1
- package/dist/templates/byline-examples/fields/ai-text.ts +47 -0
- package/dist/templates/byline-examples/fields/ai-textarea.ts +50 -0
- package/dist/templates/byline-examples/fields/ai-widgets/ai-field-label.tsx +48 -0
- package/dist/templates/byline-examples/fields/ai-widgets/ai-field-panel.tsx +42 -0
- package/dist/templates/byline-examples/fields/ai-widgets/ai-panel-store.ts +52 -0
- package/dist/templates/byline-examples/fields/lexical-richtext-ai.tsx +82 -0
- package/dist/templates/byline-examples/fields/lexical-richtext-compact.ts +8 -4
- package/dist/templates/byline-examples/i18n.ts +1 -1
- package/dist/templates/byline-examples/plugins/ai/ai-plugin-text.tsx +58 -0
- package/dist/templates/byline-examples/scripts/import-docs.ts +272 -0
- package/dist/templates/byline-examples/scripts/lib/frontmatter.ts +136 -0
- package/dist/templates/byline-examples/scripts/lib/mdast-to-lexical.ts +497 -0
- package/dist/templates/byline-examples/scripts/lib/strip-leading-h1.ts +31 -0
- package/dist/templates/migrations/{0000_slimy_lilandra.sql → 0000_cold_red_wolf.sql} +40 -34
- package/dist/templates/migrations/meta/0000_snapshot.json +100 -68
- package/dist/templates/migrations/meta/_journal.json +2 -2
- package/package.json +1 -1
- package/dist/templates/byline-examples/collections/docs/components/feature-formatter.tsx +0 -10
- package/dist/templates/byline-examples/collections/docs-categories/admin.tsx +0 -78
- package/dist/templates/byline-examples/collections/docs-categories/components/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/docs-categories/hooks/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/docs-categories/index.ts +0 -10
- package/dist/templates/byline-examples/collections/docs-categories/schema.ts +0 -33
- package/dist/templates/byline-examples/seeds/doc-categories.ts +0 -71
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAA;AAEnD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,QAAQ,CAAA;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,eAAO,MAAM,cAAc,WAAW,CAAA;AAEtC,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAA;AAEnD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,QAAQ,CAAA;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,eAAO,MAAM,cAAc,WAAW,CAAA;AAEtC,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAqJ9B,CAAA"}
|
package/dist/manifest/deps.js
CHANGED
|
@@ -127,5 +127,39 @@ export const DEP_SPECS = [
|
|
|
127
127
|
group: 'dev',
|
|
128
128
|
note: 'runs byline/seed.ts and byline/scripts/* without a build step',
|
|
129
129
|
},
|
|
130
|
+
// ---- Dev: required by byline/scripts/import-docs.ts --------------------
|
|
131
|
+
// Markdown ingestion stack used only by the optional import-docs example
|
|
132
|
+
// script. Kept in `dev` because the production app never imports them —
|
|
133
|
+
// they only matter when the developer runs `tsx byline/scripts/import-docs.ts`.
|
|
134
|
+
{
|
|
135
|
+
name: 'gray-matter',
|
|
136
|
+
version: '^4.0.3',
|
|
137
|
+
group: 'dev',
|
|
138
|
+
note: 'frontmatter parser used by byline/scripts/import-docs.ts',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'unified',
|
|
142
|
+
version: '^11.0.5',
|
|
143
|
+
group: 'dev',
|
|
144
|
+
note: 'remark/mdast pipeline runner used by byline/scripts/import-docs.ts',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'remark-parse',
|
|
148
|
+
version: '^11.0.0',
|
|
149
|
+
group: 'dev',
|
|
150
|
+
note: 'markdown → mdast parser used by byline/scripts/import-docs.ts',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'remark-gfm',
|
|
154
|
+
version: '^4.0.1',
|
|
155
|
+
group: 'dev',
|
|
156
|
+
note: 'GitHub-Flavoured Markdown extensions for remark; used by byline/scripts/import-docs.ts',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: '@types/mdast',
|
|
160
|
+
version: '^4.0.4',
|
|
161
|
+
group: 'dev',
|
|
162
|
+
note: 'TypeScript types for mdast nodes; consumed as type-only by byline/scripts/lib/mdast-to-lexical.ts',
|
|
163
|
+
},
|
|
130
164
|
];
|
|
131
165
|
//# sourceMappingURL=deps.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA;AAEtC,MAAM,CAAC,MAAM,SAAS,GAAuB;IAC3C,4EAA4E;IAC5E;QACE,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,6DAA6D;KACpE;IACD;QACE,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,+EAA+E;KACtF;IACD;QACE,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,mDAAmD;KAC1D;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,6DAA6D;KACpE;IACD;QACE,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,sDAAsD;KAC7D;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,gCAAgC;KACvC;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,2CAA2C;KAClD;IACD;QACE,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,iDAAiD;KACxD;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,6CAA6C;KACpD;IACD;QACE,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,gDAAgD;KACvD;IAED,4EAA4E;IAC5E;QACE,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,QAAQ;QACjB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,+EAA+E;KACtF;IACD;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,qBAAqB;KAC5B;IACD;QACE,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,2FAA2F;KAClG;IACD;QACE,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,QAAQ;QACjB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,oFAAoF;KAC3F;IACD;QACE,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,mIAAmI;KAC1I;IACD;QACE,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,0BAA0B;QACnC,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,2LAA2L;KAClM;IAED,2EAA2E;IAC3E;QACE,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,QAAQ;QACjB,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,kEAAkE;KACzE;IACD;QACE,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,+DAA+D;KACtE;CACO,CAAA"}
|
|
1
|
+
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ export interface LocaleDefinition {
|
|
|
23
23
|
/** Locales available in the CMS admin interface. */
|
|
24
24
|
export const interfaceLocales: LocaleDefinition[] = [
|
|
25
25
|
{ code: 'en', label: 'English' },
|
|
26
|
-
{ code: '
|
|
26
|
+
{ code: 'es', label: 'Español' },
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
/** Locales a document can be published in. */
|
|
@@ -76,6 +76,18 @@ export const config: ClientConfig = {
|
|
|
76
76
|
// }),
|
|
77
77
|
// },
|
|
78
78
|
// ---------------------------------------------------------------------
|
|
79
|
+
//
|
|
80
|
+
// Or — enable the AI assistant on every richtext field globally by
|
|
81
|
+
// registering the `LexicalRichTextAi` editor. It is built with
|
|
82
|
+
// `lexicalEditor((c) => c.extensions.add(AiLexicalExtension))`, so
|
|
83
|
+
// the AI drawer mounts as a Lexical extension decorator and the
|
|
84
|
+
// toolbar button arrives via the BylineToolbarExtension peer
|
|
85
|
+
// contract. Server-side auth is provided by `executeAiInstruction`
|
|
86
|
+
// via `<BylineAiAdminProvider>` in the admin layout.
|
|
87
|
+
//
|
|
88
|
+
// import { LexicalRichTextAi } from './fields/lexical-richtext-ai.js'
|
|
89
|
+
// richText: { editor: LexicalRichTextAi },
|
|
90
|
+
// ---------------------------------------------------------------------
|
|
79
91
|
},
|
|
80
92
|
}
|
|
81
93
|
|
|
@@ -11,6 +11,9 @@ import { DateTimeFormatter } from '@byline/ui/react'
|
|
|
11
11
|
|
|
12
12
|
import { SummaryLength } from '~/components/summary-length.js'
|
|
13
13
|
|
|
14
|
+
import { aiTextFieldAdmin } from '../../fields/ai-text.js'
|
|
15
|
+
import { aiTextAreaFieldAdmin } from '../../fields/ai-textarea.js'
|
|
16
|
+
import { aiRichTextAdmin } from '../../fields/lexical-richtext-ai.js'
|
|
14
17
|
import { FeaturedFormatter } from './components/feature-formatter.js'
|
|
15
18
|
import { News } from './schema.js'
|
|
16
19
|
|
|
@@ -111,11 +114,13 @@ export const NewsAdmin: CollectionAdminConfig = defineAdmin(News, {
|
|
|
111
114
|
* Placement is controlled exclusively through the layout primitives below.
|
|
112
115
|
*/
|
|
113
116
|
fields: {
|
|
114
|
-
|
|
117
|
+
title: aiTextFieldAdmin(),
|
|
118
|
+
summary: aiTextAreaFieldAdmin({
|
|
115
119
|
components: {
|
|
116
120
|
HelpText: SummaryLength,
|
|
117
121
|
},
|
|
118
|
-
},
|
|
122
|
+
}),
|
|
123
|
+
content: aiRichTextAdmin(),
|
|
119
124
|
},
|
|
120
125
|
|
|
121
126
|
/**
|
|
@@ -139,7 +139,8 @@ export const PagesAdmin: CollectionAdminConfig = defineAdmin(Pages, {
|
|
|
139
139
|
url: (doc, { locale }) => {
|
|
140
140
|
if (!doc.path) return null
|
|
141
141
|
const prefix = locale && locale !== i18n.interface.defaultLocale ? `/${locale}` : ''
|
|
142
|
-
const pathWithArea =
|
|
142
|
+
const pathWithArea =
|
|
143
|
+
doc.fields?.area && doc.fields.area !== 'root' ? `${doc.fields.area}/${doc.path}` : doc.path
|
|
143
144
|
return `${prefix}/${pathWithArea}`
|
|
144
145
|
},
|
|
145
146
|
},
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
* **Admin-side helper.** Returns a `FieldAdminConfig` — drop into a
|
|
11
|
+
* collection's `fields` map in `<collection>/admin.tsx`, keyed by the
|
|
12
|
+
* schema field's name. Pairs with a plain `{ type: 'text' }` entry on
|
|
13
|
+
* the schema side; this file owns the React (label icon + AI panel).
|
|
14
|
+
*
|
|
15
|
+
* See `docs/FIELDS.md` for the schema-vs-admin model.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { FieldAdminConfig, FieldComponentSlots } from '@byline/core'
|
|
19
|
+
|
|
20
|
+
import { AiFieldLabel } from './ai-widgets/ai-field-label.js'
|
|
21
|
+
import { AiFieldPanel } from './ai-widgets/ai-field-panel.js'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns a `FieldAdminConfig` that adds an AI toggle button to the field
|
|
25
|
+
* label and an `<AiPluginText>` panel as an `afterField` adornment. Spread
|
|
26
|
+
* any extra `components` overrides via the optional argument.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* // apps/webapp/byline/collections/news/admin.tsx
|
|
31
|
+
* fields: {
|
|
32
|
+
* title: aiTextFieldAdmin(),
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function aiTextFieldAdmin(
|
|
37
|
+
options: { components?: FieldComponentSlots } = {}
|
|
38
|
+
): FieldAdminConfig {
|
|
39
|
+
const { components: extra } = options
|
|
40
|
+
return {
|
|
41
|
+
components: {
|
|
42
|
+
Label: AiFieldLabel,
|
|
43
|
+
afterField: AiFieldPanel,
|
|
44
|
+
...extra,
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
* **Admin-side helper.** Returns a `FieldAdminConfig` — drop into a
|
|
11
|
+
* collection's `fields` map in `<collection>/admin.tsx`, keyed by the
|
|
12
|
+
* schema field's name. Pairs with a plain `{ type: 'textArea' }` entry
|
|
13
|
+
* on the schema side; this file owns the React (label icon + AI panel).
|
|
14
|
+
*
|
|
15
|
+
* See `docs/FIELDS.md` for the schema-vs-admin model.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { FieldAdminConfig, FieldComponentSlots } from '@byline/core'
|
|
19
|
+
|
|
20
|
+
import { AiFieldLabel } from './ai-widgets/ai-field-label.js'
|
|
21
|
+
import { AiFieldPanel } from './ai-widgets/ai-field-panel.js'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns a `FieldAdminConfig` that adds an AI toggle button to the field
|
|
25
|
+
* label and an `<AiPluginText>` panel as an `afterField` adornment for a
|
|
26
|
+
* `textArea` schema field. Spread any extra `components` overrides via
|
|
27
|
+
* the optional argument.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* // apps/webapp/byline/collections/news/admin.tsx
|
|
32
|
+
* fields: {
|
|
33
|
+
* summary: aiTextAreaFieldAdmin({
|
|
34
|
+
* components: { HelpText: SummaryLength },
|
|
35
|
+
* }),
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function aiTextAreaFieldAdmin(
|
|
40
|
+
options: { components?: FieldComponentSlots } = {}
|
|
41
|
+
): FieldAdminConfig {
|
|
42
|
+
const { components: extra } = options
|
|
43
|
+
return {
|
|
44
|
+
components: {
|
|
45
|
+
Label: AiFieldLabel,
|
|
46
|
+
afterField: AiFieldPanel,
|
|
47
|
+
...extra,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 type { FieldLabelSlotProps } from '@byline/core'
|
|
10
|
+
import { AiIcon, IconButton, Label } from '@byline/ui/react'
|
|
11
|
+
|
|
12
|
+
import { useAiPanelOpen } from './ai-panel-store.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `Label` slot for AI-enabled text fields. Renders the standard label
|
|
16
|
+
* alongside a small icon button that toggles the AI panel rendered by
|
|
17
|
+
* the matching `afterField` slot.
|
|
18
|
+
*/
|
|
19
|
+
export function AiFieldLabel({
|
|
20
|
+
field,
|
|
21
|
+
path,
|
|
22
|
+
label,
|
|
23
|
+
required,
|
|
24
|
+
id,
|
|
25
|
+
}: FieldLabelSlotProps): React.JSX.Element {
|
|
26
|
+
const [open, setOpen] = useAiPanelOpen(path)
|
|
27
|
+
return (
|
|
28
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
29
|
+
<Label
|
|
30
|
+
id={`${id}-label`}
|
|
31
|
+
htmlFor={id}
|
|
32
|
+
label={label ?? field.label ?? field.name}
|
|
33
|
+
required={required}
|
|
34
|
+
/>
|
|
35
|
+
<IconButton
|
|
36
|
+
size="sm"
|
|
37
|
+
variant="text"
|
|
38
|
+
className="outline-none"
|
|
39
|
+
type="button"
|
|
40
|
+
aria-label={open ? 'Hide AI assistant' : 'Show AI assistant'}
|
|
41
|
+
aria-pressed={open}
|
|
42
|
+
onClick={() => setOpen(!open)}
|
|
43
|
+
>
|
|
44
|
+
<AiIcon />
|
|
45
|
+
</IconButton>
|
|
46
|
+
</span>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 { useCallback } from 'react'
|
|
10
|
+
|
|
11
|
+
import { AiPluginText } from '@byline/ai/plugins/text'
|
|
12
|
+
import type { FieldAdornmentSlotProps } from '@byline/core'
|
|
13
|
+
import { useFormContext } from '@byline/ui/react'
|
|
14
|
+
|
|
15
|
+
import { useAiPanelOpen } from './ai-panel-store.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `afterField` slot for AI-enabled text fields. Renders `<AiPluginText>`
|
|
19
|
+
* controlled by the per-path store, reads the current value from form
|
|
20
|
+
* context, and dispatches a value update via `setFieldValue` when the
|
|
21
|
+
* AI returns a result.
|
|
22
|
+
*/
|
|
23
|
+
export function AiFieldPanel({ path, value }: FieldAdornmentSlotProps): React.JSX.Element {
|
|
24
|
+
const { setFieldValue } = useFormContext()
|
|
25
|
+
const [open, setOpen] = useAiPanelOpen(path)
|
|
26
|
+
|
|
27
|
+
const handleApply = useCallback(
|
|
28
|
+
(nextText: string) => {
|
|
29
|
+
setFieldValue(path, nextText)
|
|
30
|
+
},
|
|
31
|
+
[path, setFieldValue]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<AiPluginText
|
|
36
|
+
inputText={typeof value === 'string' ? value : ''}
|
|
37
|
+
onApplyResult={handleApply}
|
|
38
|
+
open={open}
|
|
39
|
+
onOpenChange={setOpen}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
* Tiny per-field-path open/closed store for the AI panel. The `Label`
|
|
11
|
+
* slot (toggle button) and the `afterField` slot (panel) for the same
|
|
12
|
+
* field render as siblings in the form, so they coordinate through this
|
|
13
|
+
* module-level store rather than React context.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useSyncExternalStore } from 'react'
|
|
17
|
+
|
|
18
|
+
type Listener = () => void
|
|
19
|
+
|
|
20
|
+
const openByPath = new Map<string, boolean>()
|
|
21
|
+
const listenersByPath = new Map<string, Set<Listener>>()
|
|
22
|
+
|
|
23
|
+
function notify(path: string): void {
|
|
24
|
+
const set = listenersByPath.get(path)
|
|
25
|
+
if (set == null) return
|
|
26
|
+
for (const listener of set) listener()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function subscribe(path: string, listener: Listener): () => void {
|
|
30
|
+
let set = listenersByPath.get(path)
|
|
31
|
+
if (set == null) {
|
|
32
|
+
set = new Set()
|
|
33
|
+
listenersByPath.set(path, set)
|
|
34
|
+
}
|
|
35
|
+
set.add(listener)
|
|
36
|
+
return () => {
|
|
37
|
+
set?.delete(listener)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useAiPanelOpen(path: string): [boolean, (open: boolean) => void] {
|
|
42
|
+
const open = useSyncExternalStore(
|
|
43
|
+
(listener) => subscribe(path, listener),
|
|
44
|
+
() => openByPath.get(path) ?? false,
|
|
45
|
+
() => false
|
|
46
|
+
)
|
|
47
|
+
const setOpen = (next: boolean) => {
|
|
48
|
+
openByPath.set(path, next)
|
|
49
|
+
notify(path)
|
|
50
|
+
}
|
|
51
|
+
return [open, setOpen]
|
|
52
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
* **Admin-side module.** Exports two things — both consumed admin-side,
|
|
11
|
+
* never from a schema:
|
|
12
|
+
*
|
|
13
|
+
* - `LexicalRichTextAi` — a `RichTextEditorComponent` that registers
|
|
14
|
+
* the AI assistant by adding `AiLexicalExtension` to the editor's
|
|
15
|
+
* extension graph. Register globally in
|
|
16
|
+
* `apps/webapp/byline/admin.config.ts` under
|
|
17
|
+
* `fields.richText.editor` to AI-enable every richtext field.
|
|
18
|
+
* - `aiRichTextAdmin()` — a `FieldAdminConfig` factory. Drop into a
|
|
19
|
+
* collection's `fields` map in `<collection>/admin.tsx` to opt one
|
|
20
|
+
* field into the AI editor without changing the global registration.
|
|
21
|
+
*
|
|
22
|
+
* Pairs with a plain `{ type: 'richText' }` entry on the schema side —
|
|
23
|
+
* schema files must stay React-free and tsx-loadable.
|
|
24
|
+
*
|
|
25
|
+
* See `docs/FIELDS.md` for the schema-vs-admin model.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { AiLexicalExtension } from '@byline/ai/plugins/lexical'
|
|
29
|
+
import type { FieldAdminConfig, RichTextEditorProps } from '@byline/core'
|
|
30
|
+
import { lexicalEditor } from '@byline/richtext-lexical'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* AI-enabled wrapper around `@byline/richtext-lexical`'s editor.
|
|
34
|
+
*
|
|
35
|
+
* Composes a `lexicalEditor()` with `AiLexicalExtension` added to the
|
|
36
|
+
* extensions graph. The extension contributes a toolbar button via the
|
|
37
|
+
* `BylineToolbarExtension` peer-dependency contract and mounts the AI
|
|
38
|
+
* drawer via `ReactExtension.decorators` — no `featureAfterEditor`, no
|
|
39
|
+
* React-context registry hop.
|
|
40
|
+
*
|
|
41
|
+
* **Global** opt-in — register in `apps/webapp/byline/admin.config.ts`:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* fields: {
|
|
45
|
+
* richText: { editor: LexicalRichTextAi },
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* **Per-field** opt-in — see `aiRichTextAdmin()` below.
|
|
50
|
+
*/
|
|
51
|
+
export const LexicalRichTextAi = lexicalEditor((c) => {
|
|
52
|
+
c.extensions.add(AiLexicalExtension)
|
|
53
|
+
return c
|
|
54
|
+
}) satisfies (props: RichTextEditorProps) => React.JSX.Element
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns a `FieldAdminConfig` that opts a single richText field into
|
|
58
|
+
* the AI-enabled editor (`LexicalRichTextAi`) without changing the
|
|
59
|
+
* site-wide registration. Drop into a `CollectionAdminConfig.fields`
|
|
60
|
+
* map, keyed by the schema field's name.
|
|
61
|
+
*
|
|
62
|
+
* Lives on the admin side (alongside `aiTextFieldAdmin` /
|
|
63
|
+
* `aiTextAreaFieldAdmin`) so the schema graph stays React-free and
|
|
64
|
+
* tsx-loadable — the server bootstrap in `byline/server.config.ts`
|
|
65
|
+
* must be able to import collection schemas without pulling in the
|
|
66
|
+
* Lexical editor and the AI plugin.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* // apps/webapp/byline/collections/news/admin.tsx
|
|
71
|
+
* import { aiRichTextAdmin } from '../../fields/lexical-richtext-ai.js'
|
|
72
|
+
*
|
|
73
|
+
* fields: {
|
|
74
|
+
* content: aiRichTextAdmin(),
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function aiRichTextAdmin(): FieldAdminConfig {
|
|
79
|
+
return {
|
|
80
|
+
editor: LexicalRichTextAi,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
* Extension references (TableExtension, AdmonitionExtension, etc.) are
|
|
19
19
|
* not JSON-safe and would break tsx-loaded seeds; per-field extension
|
|
20
20
|
* removal goes through a client-side wrapper component registered via
|
|
21
|
-
* `FieldAdminConfig.editor
|
|
21
|
+
* `FieldAdminConfig.editor` — see `aiRichTextAdmin()` for the pattern.
|
|
22
|
+
*
|
|
23
|
+
* To AI-enable the resulting field, pair this with `aiRichTextAdmin()`
|
|
24
|
+
* on the admin side — see `lexical-richtext-ai.tsx`.
|
|
22
25
|
*
|
|
23
26
|
* See `docs/FIELDS.md` for the full schema-vs-admin model.
|
|
24
27
|
*/
|
|
@@ -51,9 +54,10 @@ type Options = Partial<Omit<RichTextField, 'type' | 'editorConfig'>> & {
|
|
|
51
54
|
*
|
|
52
55
|
* To narrow the *extension* set per-field — drop tables, lists, embeds,
|
|
53
56
|
* the floating format toolbar, the table action menu — register a
|
|
54
|
-
* `LexicalRichTextCompact` wrapper component via `FieldAdminConfig.editor
|
|
55
|
-
* Extension references aren't safe
|
|
56
|
-
* UIs are now extension-presence
|
|
57
|
+
* `LexicalRichTextCompact` wrapper component via `FieldAdminConfig.editor`
|
|
58
|
+
* (same pattern as `aiRichTextAdmin()`). Extension references aren't safe
|
|
59
|
+
* to bake into schemas, and floating UIs are now extension-presence
|
|
60
|
+
* controlled rather than settings-controlled.
|
|
57
61
|
*/
|
|
58
62
|
function applyCompactPreset(config: EditorConfig): EditorConfig {
|
|
59
63
|
const o = config.settings.options
|
|
@@ -23,7 +23,7 @@ export interface LocaleDefinition {
|
|
|
23
23
|
/** Locales available in the CMS admin interface. */
|
|
24
24
|
export const interfaceLocales: LocaleDefinition[] = [
|
|
25
25
|
{ code: 'en', label: 'English' },
|
|
26
|
-
{ code: '
|
|
26
|
+
{ code: 'es', label: 'Español' },
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
/** Locales a document can be published in. */
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type * as React from 'react'
|
|
4
|
+
import { useCallback, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
import { AiPluginText as AiPluginTextRoot } from '@byline/ai/plugins/text'
|
|
7
|
+
import { AiIcon, IconButton, Input } from '@byline/ui/react'
|
|
8
|
+
|
|
9
|
+
export function AiPluginText() {
|
|
10
|
+
const [inputText, setInputText] = useState('')
|
|
11
|
+
const [open, setOpen] = useState(false)
|
|
12
|
+
|
|
13
|
+
const handleToggleOpen = useCallback(() => {
|
|
14
|
+
setOpen((prevOpen) => !prevOpen)
|
|
15
|
+
}, [])
|
|
16
|
+
|
|
17
|
+
const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
18
|
+
setInputText(event.target.value)
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
const handleApplyResult = useCallback((nextText: string) => {
|
|
22
|
+
setInputText(nextText)
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
const handleClearInput = useCallback(() => {
|
|
26
|
+
setInputText('')
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="ai-plugin-text">
|
|
31
|
+
<IconButton
|
|
32
|
+
onClick={handleToggleOpen}
|
|
33
|
+
variant="text"
|
|
34
|
+
size="md"
|
|
35
|
+
className="w-7 h-7 max-w-7 max-h-7 min-w-7 min-h-7"
|
|
36
|
+
>
|
|
37
|
+
<AiIcon />
|
|
38
|
+
</IconButton>
|
|
39
|
+
<Input
|
|
40
|
+
id="foo"
|
|
41
|
+
name="foo"
|
|
42
|
+
label="Simple Text Input"
|
|
43
|
+
type="text"
|
|
44
|
+
onChange={handleInputChange}
|
|
45
|
+
value={inputText}
|
|
46
|
+
helpText="Enter some text, or enter a prompt below to generate text."
|
|
47
|
+
placeholder="Start writing your content here..."
|
|
48
|
+
/>
|
|
49
|
+
<AiPluginTextRoot
|
|
50
|
+
inputText={inputText}
|
|
51
|
+
onApplyResult={handleApplyResult}
|
|
52
|
+
onClearInput={handleClearInput}
|
|
53
|
+
open={open}
|
|
54
|
+
onOpenChange={setOpen}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|