@byline/cli 2.1.2 → 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 +5 -7
- package/dist/manifest/deps.d.ts.map +1 -1
- package/dist/manifest/deps.js +46 -8
- 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/host/vite.config.ts +33 -25
- 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
package/dist/manifest/deps.d.ts
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
* `deps` phase (to install missing entries) and by `doctor` (to report
|
|
5
5
|
* what's missing without re-running the install).
|
|
6
6
|
*
|
|
7
|
-
* Versioning policy: `@byline/*` packages are released
|
|
8
|
-
* they share `BYLINE_VERSION
|
|
9
|
-
* own `1.x`
|
|
10
|
-
*
|
|
11
|
-
* separately.
|
|
7
|
+
* Versioning policy: all publishable `@byline/*` packages are released
|
|
8
|
+
* in lockstep, so they share `BYLINE_VERSION` — including
|
|
9
|
+
* `@byline/host-tanstack-start`, which previously rode its own `1.x`
|
|
10
|
+
* line but is now part of the lockstep set from 2.x onwards.
|
|
12
11
|
*
|
|
13
12
|
* Scope: this list is intentionally minimal — only packages that are
|
|
14
13
|
* directly imported by files we drop into the user's tree (`byline/`,
|
|
@@ -23,7 +22,6 @@ export interface DepSpec {
|
|
|
23
22
|
/** Short human-readable reason this is on the list. */
|
|
24
23
|
note: string;
|
|
25
24
|
}
|
|
26
|
-
export declare const BYLINE_VERSION = "^
|
|
27
|
-
export declare const HOST_TANSTACK_VERSION = "^1.0.0";
|
|
25
|
+
export declare const BYLINE_VERSION = "^2.0.0";
|
|
28
26
|
export declare const DEP_SPECS: readonly DepSpec[];
|
|
29
27
|
//# sourceMappingURL=deps.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"AAAA
|
|
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
|
@@ -4,19 +4,17 @@
|
|
|
4
4
|
* `deps` phase (to install missing entries) and by `doctor` (to report
|
|
5
5
|
* what's missing without re-running the install).
|
|
6
6
|
*
|
|
7
|
-
* Versioning policy: `@byline/*` packages are released
|
|
8
|
-
* they share `BYLINE_VERSION
|
|
9
|
-
* own `1.x`
|
|
10
|
-
*
|
|
11
|
-
* separately.
|
|
7
|
+
* Versioning policy: all publishable `@byline/*` packages are released
|
|
8
|
+
* in lockstep, so they share `BYLINE_VERSION` — including
|
|
9
|
+
* `@byline/host-tanstack-start`, which previously rode its own `1.x`
|
|
10
|
+
* line but is now part of the lockstep set from 2.x onwards.
|
|
12
11
|
*
|
|
13
12
|
* Scope: this list is intentionally minimal — only packages that are
|
|
14
13
|
* directly imported by files we drop into the user's tree (`byline/`,
|
|
15
14
|
* `src/routes/_byline/`, `src/ui/byline/`). Transitive deps reach the
|
|
16
15
|
* user via the `@byline/*` package boundary and don't need declaring.
|
|
17
16
|
*/
|
|
18
|
-
export const BYLINE_VERSION = '^
|
|
19
|
-
export const HOST_TANSTACK_VERSION = '^1.0.0';
|
|
17
|
+
export const BYLINE_VERSION = '^2.0.0';
|
|
20
18
|
export const DEP_SPECS = [
|
|
21
19
|
// ---- @byline/* — released in lockstep at BYLINE_VERSION -----------------
|
|
22
20
|
{
|
|
@@ -25,6 +23,12 @@ export const DEP_SPECS = [
|
|
|
25
23
|
group: 'byline',
|
|
26
24
|
note: 'admin user / role / permission modules + JwtSessionProvider',
|
|
27
25
|
},
|
|
26
|
+
{
|
|
27
|
+
name: '@byline/ai',
|
|
28
|
+
version: BYLINE_VERSION,
|
|
29
|
+
group: 'byline',
|
|
30
|
+
note: 'AI subsystem; pre-bundled by the host vite.config.ts via optimizeDeps.include',
|
|
31
|
+
},
|
|
28
32
|
{
|
|
29
33
|
name: '@byline/auth',
|
|
30
34
|
version: BYLINE_VERSION,
|
|
@@ -51,7 +55,7 @@ export const DEP_SPECS = [
|
|
|
51
55
|
},
|
|
52
56
|
{
|
|
53
57
|
name: '@byline/host-tanstack-start',
|
|
54
|
-
version:
|
|
58
|
+
version: BYLINE_VERSION,
|
|
55
59
|
group: 'byline',
|
|
56
60
|
note: 'TanStack Start integrations + route stubs',
|
|
57
61
|
},
|
|
@@ -123,5 +127,39 @@ export const DEP_SPECS = [
|
|
|
123
127
|
group: 'dev',
|
|
124
128
|
note: 'runs byline/seed.ts and byline/scripts/* without a build step',
|
|
125
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
|
+
},
|
|
126
164
|
];
|
|
127
165
|
//# sourceMappingURL=deps.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"AAAA
|
|
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
|
+
}
|