@beechcms/core 0.4.0-preview.3 → 0.4.0-preview.5

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.
@@ -1,87 +0,0 @@
1
- import { generateHTML } from '@tiptap/html'
2
- import type { JSONContent } from '@tiptap/core'
3
- import Highlight from '@tiptap/extension-highlight'
4
- import Image from '@tiptap/extension-image'
5
- import Link from '@tiptap/extension-link'
6
- import Subscript from '@tiptap/extension-subscript'
7
- import Superscript from '@tiptap/extension-superscript'
8
- import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table'
9
- import TextAlign from '@tiptap/extension-text-align'
10
- import { Mathematics } from '@tiptap/extension-mathematics'
11
- import StarterKit from '@tiptap/starter-kit'
12
-
13
- import { isRichtextEnvelopeV1 } from './richtext.js'
14
-
15
- /**
16
- * Allinea l'output HTML allo schema TipTap usato dall'editor dashboard.
17
- * Mantieni sincronizzato con `apps/dashboard/src/features/richtext-editor/extensions/build-editor-extensions.ts`.
18
- */
19
- function createRichTextHtmlExtensions() {
20
- return [
21
- StarterKit.configure({
22
- link: false,
23
- codeBlock: {
24
- HTMLAttributes: {
25
- class: 'richtext-code-block',
26
- },
27
- },
28
- }),
29
- Link.configure({
30
- openOnClick: false,
31
- autolink: true,
32
- defaultProtocol: 'https',
33
- }),
34
- Mathematics.configure({
35
- katexOptions: {
36
- throwOnError: false,
37
- },
38
- }),
39
- Highlight,
40
- Superscript,
41
- Subscript,
42
- Image.configure({
43
- allowBase64: false,
44
- }),
45
- TextAlign.configure({
46
- types: ['heading', 'paragraph'],
47
- }),
48
- Table.configure({
49
- resizable: false,
50
- }),
51
- TableRow,
52
- TableHeader,
53
- TableCell,
54
- ]
55
- }
56
-
57
- /**
58
- * Accetta JSON TipTap (`{ type: 'doc', ... }`), envelope v1, o stringa HTML legacy.
59
- */
60
- export function normalizeRichtextForRender(value: unknown): JSONContent | string | null {
61
- if (value == null || value === '') return null
62
- if (typeof value === 'string') return value
63
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
64
- const o = value as Record<string, unknown>
65
- if (isRichtextEnvelopeV1(value)) {
66
- return o.doc as JSONContent
67
- }
68
- if (o.type === 'doc') {
69
- return value as JSONContent
70
- }
71
- }
72
- return null
73
- }
74
-
75
- /**
76
- * Render deterministico JSON → HTML (per display, anteprime, API pubblica).
77
- * Per stringhe HTML legacy restituisce la stringa sanificata come pass-through (nessun parse TipTap).
78
- */
79
- export function renderRichText(value: unknown): string {
80
- const normalized = normalizeRichtextForRender(value)
81
- if (normalized == null) return ''
82
- if (typeof normalized === 'string') {
83
- return normalized
84
- }
85
- const extensions = createRichTextHtmlExtensions()
86
- return generateHTML(normalized, extensions)
87
- }
package/src/richtext.ts DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Convenzione storage richtext TipTap nel Content Engine.
3
- * @see docs/Sprints/tiptap-elevation.md
4
- */
5
- export const RICHTEXT_SCHEMA_VERSION = 1 as const
6
-
7
- export type RichtextEnvelopeV1 = {
8
- schemaVersion: typeof RICHTEXT_SCHEMA_VERSION
9
- doc: Record<string, unknown>
10
- }
11
-
12
- export function isRichtextEnvelopeV1(value: unknown): value is RichtextEnvelopeV1 {
13
- if (typeof value !== 'object' || value === null || Array.isArray(value)) return false
14
- const o = value as Record<string, unknown>
15
- return o.schemaVersion === RICHTEXT_SCHEMA_VERSION && typeof o.doc === 'object' && o.doc !== null
16
- }
package/src/seeds.ts DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * Seed Registry: configurazione degli schemi di contenuto.
3
- * In v0.4.0 ogni Seed genera una tabella `content_{slug}` con colonne reali.
4
- * `branch.alias` è il nome della colonna SQL.
5
- */
6
- import type { Seed } from './types.js'
7
-
8
- /**
9
- * Registro globale dei Seed.
10
- * In produzione, questo viene popolato dalle definizioni dell'utente.
11
- */
12
- export const SEED_REGISTRY: Record<string, Seed> = {}
13
-
14
- /**
15
- * Registra un set di Seed nel registro globale.
16
- */
17
- export function registerSeeds(seeds: Seed[]) {
18
- seeds.forEach(seed => {
19
- SEED_REGISTRY[seed.slug] = seed
20
- })
21
- }
22
-
23
- /**
24
- * Ritorna il Seed per lo slug dato, o null se non esiste.
25
- */
26
- export function getSeed(slug: string): Seed | null {
27
- return SEED_REGISTRY[slug] ?? null
28
- }
package/src/slug-utils.ts DELETED
@@ -1,33 +0,0 @@
1
- /**
2
- * Utility per la gestione degli slug.
3
- * Condivisa tra Dashboard e API pubbliche.
4
- */
5
-
6
- /**
7
- * Converte una stringa in uno slug URL-safe.
8
- * Limita la lunghezza a 15 caratteri come richiesto.
9
- */
10
- export function slugify(value: string): string {
11
- return value
12
- .normalize('NFKD') // Normalizza caratteri accentati
13
- .replace(/[^\w\s-]/g, '') // Rimuove tutto ciò che non è alfanumerico (accetta underscore temporaneamente)
14
- .trim()
15
- .toLowerCase()
16
- .replace(/[\s_-]+/g, '-') // Sostituisce spazi, underscore e trattini multipli con singolo trattino
17
- .replace(/^-+|-+$/g, '') // Rimuove trattini all'inizio o alla fine
18
- .slice(0, 15) // Limita a 15 caratteri
19
- }
20
-
21
- /**
22
- * Genera uno slug da un input (es. titolo o nome) o un fallback UUID.
23
- */
24
- export function generateEntrySlug(input: { slug?: string; title?: unknown; name?: unknown }): string {
25
- const candidate =
26
- (typeof input.slug === 'string' && input.slug) ||
27
- (typeof input.title === 'string' && input.title) ||
28
- (typeof input.name === 'string' && input.name) ||
29
- Math.random().toString(36).slice(2, 10) // Fallback compatto se crypto non disponibile (o slice 8)
30
-
31
- const normalized = slugify(candidate)
32
- return normalized || Math.random().toString(36).slice(2, 10)
33
- }
package/src/types.ts DELETED
@@ -1,144 +0,0 @@
1
- export type BranchType = 'text' | 'number' | 'boolean' | 'json' | 'date' | 'richtext' | 'file'
2
-
3
- /** Branch: definizione di una proprietà. alias = nome colonna SQL. */
4
- export interface Branch {
5
- /** Alias human-readable, usato nel payload API e come nome colonna SQL nella tabella dedicata */
6
- alias: string
7
- /** Etichetta per la UI */
8
- label: string
9
- /** Tipo del valore */
10
- type: BranchType
11
- /**
12
- * Variante semantica opzionale del campo per UI/validazione.
13
- * `asset-list` su `file` multiplo abilita la gestione galleria.
14
- */
15
- format?: 'plain' | 'markdown' | 'html' | 'date' | 'datetime' | 'asset-list'
16
- /**
17
- * Cardinalità opzionale per campi media:
18
- * - false/undefined: singolo asset (string URL)
19
- * - true: lista asset (string[] URL)
20
- */
21
- multiple?: boolean
22
- /**
23
- * Vocabolario predefinito per campi tag/select/multiselect.
24
- * Lista statica definita nel Seed (non salvata nel DB).
25
- */
26
- options?: string[]
27
- /** Campo obbligatorio in creazione — genera NOT NULL in generateCreateTable */
28
- requiredOnCreate?: boolean
29
- /** Campo obbligatorio in update */
30
- requiredOnUpdate?: boolean
31
- /**
32
- * Policy di accesso e trattamento del campo.
33
- * Tutti i valori sono opzionali — `resolvePolicies(branch)` fornisce i default.
34
- */
35
- policies?: {
36
- /** Come il valore viene memorizzato. Default: 'plain' */
37
- privacy?: 'plain' | 'hash' | 'encrypt'
38
- /** Come il valore viene restituito nelle risposte API. Default: 'full' */
39
- visibility?: 'full' | 'masked' | 'hidden'
40
- /** Il campo è incluso nelle query di ricerca full-text. Default: true */
41
- search?: boolean
42
- /** Il campo è disponibile come colonna di filtro nella dashboard. Default: true */
43
- filter?: boolean
44
- /** Il campo è disponibile come colonna di ordinamento nella dashboard. Default: true */
45
- sort?: boolean
46
- /** Il campo è incluso nelle risposte della Public API. Default: true */
47
- public?: boolean
48
- }
49
- }
50
-
51
- /** Dashboard-specific config embedded in a Seed. All fields optional — defaults applied by the dashboard. */
52
- export interface DashboardSeedConfig {
53
- /** Lucide icon name (string, resolved to component client-side). Default: 'Folder' */
54
- icon?: string
55
- /** Sidebar group label. Ungrouped seeds share a single 'Contents' section. */
56
- group?: string
57
- /** Sort order within the group. Lower = higher. Default: 99 */
58
- order?: number
59
- /** Hide from sidebar navigation. Default: false */
60
- hidden?: boolean
61
- /** Tooltip description shown in the sidebar. */
62
- description?: string
63
- /** UI feature toggles. All default to true unless specified. */
64
- features?: {
65
- search?: boolean
66
- filter?: boolean
67
- export?: boolean
68
- bulkDelete?: boolean
69
- }
70
- }
71
-
72
- /** Seed: definizione dello schema di un tipo di contenuto */
73
- export interface Seed {
74
- /** Slug identificativo — anche nome tabella: `content_{slug}` */
75
- slug: string
76
- /** Etichetta singolare per la UI */
77
- label: string
78
- /** Etichetta plurale per la UI. Se assente si usa `label`. */
79
- labelPlural?: string
80
- /** Abilita lettura dalla Public API (`GET /api/v1/public/:seed`). Default: false */
81
- allowPublicRead?: boolean
82
- /** Abilita creazione dalla Public API (`POST /api/v1/public/:seed/add`). Default: false */
83
- allowPublicPost?: boolean
84
- /** Abilita modifica dalla Public API (`PUT /api/v1/public/:seed/edit/:id`). Default: false */
85
- allowPublicEdit?: boolean
86
- /**
87
- * Abilita la feature "bozza in attesa" per questo seed.
88
- * Quando true, genera tabella `content_{slug}_drafts` e abilita gli endpoint `/draft`.
89
- * Default: false
90
- */
91
- allowDrafts?: boolean
92
- /**
93
- * Alias del branch usato come nome leggibile dell'entry (es. "title", "name", "author").
94
- * Obbligatorio — le UI lo usano per display senza euristica.
95
- */
96
- displayNameAlias: string
97
- /** Lista dei campi (Branch) */
98
- branches: Branch[]
99
- /** Optional dashboard-specific UI config. Ignored by the Botanical Engine. */
100
- dashboard?: DashboardSeedConfig
101
- }
102
-
103
- // ---- Query types (usati da buildSelectQuery nel Botanical Engine) ----
104
-
105
- export type FilterOperator =
106
- | 'eq'
107
- | 'gt'
108
- | 'gte'
109
- | 'lt'
110
- | 'lte'
111
- | 'contains'
112
- | 'is_empty'
113
- | 'is_not_empty'
114
-
115
- export type FilterType = 'text' | 'number' | 'date' | 'boolean' | 'tags' | 'select' | 'system'
116
-
117
- export interface FilterCondition {
118
- op: FilterOperator
119
- value: string | number | boolean | null
120
- }
121
-
122
- export interface FilterGroup {
123
- /** Nome colonna: system column (id/slug/status/created_at/updated_at) o branch alias */
124
- column: string
125
- type: FilterType
126
- conditions: FilterCondition[]
127
- }
128
-
129
- export interface SelectOptions {
130
- filters?: FilterGroup[]
131
- orderBy?: { column: string; dir: 'ASC' | 'DESC' }
132
- pagination?: { limit: number; offset: number }
133
- /** Filtra per status. null = nessun filtro status. */
134
- status?: string | null
135
- /** Full-text search — usa FTS5 se il seed ha branch richtext indicizzabili */
136
- search?: string
137
- /** Proiezione colonne. Vuoto = SELECT * */
138
- fields?: string[]
139
- }
140
-
141
- export interface ParameterizedQuery {
142
- sql: string
143
- bindings: (string | number | boolean | null)[]
144
- }