@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.
Files changed (34) hide show
  1. package/dist/manifest/deps.d.ts +5 -7
  2. package/dist/manifest/deps.d.ts.map +1 -1
  3. package/dist/manifest/deps.js +46 -8
  4. package/dist/manifest/deps.js.map +1 -1
  5. package/dist/templates/byline/i18n.ts +1 -1
  6. package/dist/templates/byline-examples/admin.config.ts +12 -0
  7. package/dist/templates/byline-examples/collections/media/components/media-list-view.module.css +1 -1
  8. package/dist/templates/byline-examples/collections/news/admin.tsx +7 -2
  9. package/dist/templates/byline-examples/collections/pages/admin.tsx +2 -1
  10. package/dist/templates/byline-examples/fields/ai-text.ts +47 -0
  11. package/dist/templates/byline-examples/fields/ai-textarea.ts +50 -0
  12. package/dist/templates/byline-examples/fields/ai-widgets/ai-field-label.tsx +48 -0
  13. package/dist/templates/byline-examples/fields/ai-widgets/ai-field-panel.tsx +42 -0
  14. package/dist/templates/byline-examples/fields/ai-widgets/ai-panel-store.ts +52 -0
  15. package/dist/templates/byline-examples/fields/lexical-richtext-ai.tsx +82 -0
  16. package/dist/templates/byline-examples/fields/lexical-richtext-compact.ts +8 -4
  17. package/dist/templates/byline-examples/i18n.ts +1 -1
  18. package/dist/templates/byline-examples/plugins/ai/ai-plugin-text.tsx +58 -0
  19. package/dist/templates/byline-examples/scripts/import-docs.ts +272 -0
  20. package/dist/templates/byline-examples/scripts/lib/frontmatter.ts +136 -0
  21. package/dist/templates/byline-examples/scripts/lib/mdast-to-lexical.ts +497 -0
  22. package/dist/templates/byline-examples/scripts/lib/strip-leading-h1.ts +31 -0
  23. package/dist/templates/host/vite.config.ts +33 -25
  24. package/dist/templates/migrations/{0000_slimy_lilandra.sql → 0000_cold_red_wolf.sql} +40 -34
  25. package/dist/templates/migrations/meta/0000_snapshot.json +100 -68
  26. package/dist/templates/migrations/meta/_journal.json +2 -2
  27. package/package.json +1 -1
  28. package/dist/templates/byline-examples/collections/docs/components/feature-formatter.tsx +0 -10
  29. package/dist/templates/byline-examples/collections/docs-categories/admin.tsx +0 -78
  30. package/dist/templates/byline-examples/collections/docs-categories/components/.gitkeep +0 -0
  31. package/dist/templates/byline-examples/collections/docs-categories/hooks/.gitkeep +0 -0
  32. package/dist/templates/byline-examples/collections/docs-categories/index.ts +0 -10
  33. package/dist/templates/byline-examples/collections/docs-categories/schema.ts +0 -33
  34. package/dist/templates/byline-examples/seeds/doc-categories.ts +0 -71
@@ -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 in lockstep, so
8
- * they share `BYLINE_VERSION`. `@byline/host-tanstack-start` rides its
9
- * own `1.x` line because its API surface (route stubs, integrations) is
10
- * stable across Byline minor bumps and consumers pin against it
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 = "^1.0.0";
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;;;;;;;;;;;;;;;;GAgBG;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;AACtC,eAAO,MAAM,qBAAqB,WAAW,CAAA;AAE7C,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EA4G9B,CAAA"}
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"}
@@ -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 in lockstep, so
8
- * they share `BYLINE_VERSION`. `@byline/host-tanstack-start` rides its
9
- * own `1.x` line because its API surface (route stubs, integrations) is
10
- * stable across Byline minor bumps and consumers pin against it
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 = '^1.0.0';
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: HOST_TANSTACK_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;;;;;;;;;;;;;;;;GAgBG;AAYH,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA;AACtC,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAA;AAE7C,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,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,qBAAqB;QAC9B,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: 'fr', label: 'Français' },
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,7 +11,7 @@
11
11
  }
12
12
 
13
13
  .heading {
14
- margin: 0 !important;
14
+ margin: 0;
15
15
  padding-bottom: 0.125rem;
16
16
  }
17
17
 
@@ -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
- summary: {
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 = doc.fields?.area && doc.fields.area !== 'root' ? `${doc.fields.area}/${doc.path}` : doc.path
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 to bake into schemas, and floating
56
- * UIs are now extension-presence controlled rather than settings-controlled.
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: 'fr', label: 'Français' },
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
+ }