@brightsideoy/kama 0.1.0-alpha.39 → 0.1.0-alpha.40

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 (39) hide show
  1. package/defaults/definitions/block-layouts/three-column.yaml +20 -0
  2. package/defaults/definitions/block-layouts/two-column.yaml +29 -0
  3. package/defaults/definitions/blocks/header.yaml +15 -0
  4. package/defaults/definitions/blocks/header_image.yaml +22 -0
  5. package/defaults/definitions/blocks/hero.yaml +27 -0
  6. package/defaults/definitions/blocks/image_card.yaml +53 -0
  7. package/defaults/definitions/blocks/text.yaml +16 -0
  8. package/defaults/definitions/channels/ecommerce.yaml +24 -0
  9. package/defaults/definitions/channels/website.yaml +32 -0
  10. package/defaults/definitions/field-sets/access.yaml +29 -0
  11. package/defaults/definitions/field-sets/channel-base.yaml +31 -0
  12. package/defaults/definitions/field-sets/comments.yaml +11 -0
  13. package/defaults/definitions/field-sets/content-block.yaml +46 -0
  14. package/defaults/definitions/field-sets/page-base.yaml +49 -0
  15. package/defaults/definitions/field-sets/page-meta.yaml +15 -0
  16. package/defaults/definitions/field-sets/page-social.yaml +80 -0
  17. package/defaults/definitions/field-sets/tree-base.yaml +31 -0
  18. package/defaults/definitions/field-sets/visibility.yaml +8 -0
  19. package/defaults/definitions/node-layouts/landing-page.yml +22 -0
  20. package/defaults/definitions/node-layouts/single-column.yml +38 -0
  21. package/defaults/definitions/node-layouts/three-column.yml +41 -0
  22. package/defaults/definitions/node-layouts/two-column.yml +40 -0
  23. package/defaults/definitions/nodes/address.yaml +32 -0
  24. package/defaults/definitions/nodes/article.yaml +41 -0
  25. package/defaults/definitions/nodes/event.yaml +40 -0
  26. package/defaults/definitions/nodes/external-link.yaml +26 -0
  27. package/defaults/definitions/nodes/page.yaml +63 -0
  28. package/defaults/definitions/nodes/person.yaml +35 -0
  29. package/defaults/definitions/nodes/product.yaml +79 -0
  30. package/defaults/definitions/trees/navigation.yaml +29 -0
  31. package/defaults/definitions/trees/social-links.yaml +9 -0
  32. package/definitions/_page.yaml +52 -0
  33. package/definitions/editor/default.ts +17 -0
  34. package/definitions/editor/header.ts +33 -0
  35. package/definitions/editor/minimal.ts +13 -0
  36. package/definitions/validators/iban.ts +12 -0
  37. package/definitions/validators/magic-word.ts +7 -0
  38. package/package.json +3 -1
  39. package/scripts/bundle-definitions.js +40 -16
@@ -0,0 +1,20 @@
1
+ id: three-column
2
+ label: Three Columns (33/33/33)
3
+ showLabel: false
4
+ group: Sub-Layouts
5
+
6
+ layout:
7
+ columns: "1fr 1fr 1fr"
8
+ areas:
9
+ - ["left", "middle", "right"]
10
+
11
+ regions:
12
+ left:
13
+ label: Left Column
14
+ showLabel: false # 👈 Hides "LEFT COLUMN" header on this region cell
15
+ middle:
16
+ label: Middle Column
17
+ showLabel: false # 👈 Hides "LEFT COLUMN" header on this region cell
18
+ right:
19
+ label: Right Column
20
+ showLabel: false # 👈 Keeps "RIGHT COLUMN" visible
@@ -0,0 +1,29 @@
1
+ id: two-column
2
+ label: Two Columns (50/50)
3
+ showLabel: false # 👈 Hides the main "Two Columns" badge on the canvas
4
+ group: Sub-Layouts
5
+
6
+ layout:
7
+ columns: "1fr 1fr"
8
+ areas:
9
+ - ["left", "right"]
10
+
11
+ regions:
12
+ left:
13
+ label: Left Column
14
+ showLabel: false # 👈 Hides "LEFT COLUMN" header on this region cell
15
+ right:
16
+ label: Right Column
17
+ showLabel: false # 👈 Keeps "RIGHT COLUMN" visible
18
+
19
+ # Optional: We can give the layout block its own settings!
20
+ fields:
21
+ gap:
22
+ label: Column Gap
23
+ type: select
24
+ default: "2rem"
25
+ options:
26
+ - { label: "None", value: "0" }
27
+ - { label: "Small", value: "1rem" }
28
+ - { label: "Medium", value: "2rem" }
29
+ - { label: "Large", value: "4rem" }
@@ -0,0 +1,15 @@
1
+ id: header
2
+ label: Header
3
+ group: Content
4
+
5
+ fields:
6
+ - headline:
7
+ type: richtext
8
+ editor: header
9
+ label: Headline
10
+ public: true
11
+ default: "Header here..."
12
+
13
+ - fieldSet: access
14
+ - hidden:
15
+ default: false
@@ -0,0 +1,22 @@
1
+ id: header_image
2
+ label: Header Image
3
+ group: Content
4
+ description: Image for page header.
5
+
6
+ fields:
7
+ - image:
8
+ label: Image
9
+ type: asset
10
+ required: true
11
+ multiple: false
12
+ maxSizeMB: 15
13
+ allowedTypes: ["image/*"]
14
+ crops:
15
+ standard:
16
+ label: "Page header (21:9)"
17
+ aspectRatio: 2.333
18
+ public: true
19
+
20
+ - fieldSet: access
21
+ - hidden:
22
+ default: false
@@ -0,0 +1,27 @@
1
+ id: hero
2
+ label: Hero
3
+ group: Content
4
+
5
+ fields:
6
+ - heroBadge:
7
+ type: text
8
+ label: Badge
9
+ public: true
10
+ - heroTitle:
11
+ type: text
12
+ label: Title
13
+ public: true
14
+ - heroSubTitle:
15
+ type: text
16
+ label: Sub-Title
17
+ public: true
18
+ - headline:
19
+ type: richtext
20
+ editor: header
21
+ label: Headline
22
+ public: true
23
+ default: "Header here..."
24
+
25
+ - fieldSet: access
26
+ - hidden:
27
+ default: false
@@ -0,0 +1,53 @@
1
+ id: image_card
2
+ label: Image Card
3
+ Group: Content
4
+ description: A versatile card with an image, text, and a CTA button.
5
+
6
+ fields:
7
+ - image:
8
+ label: Card Image
9
+ showLabel: false
10
+ type: asset
11
+ required: true
12
+ multiple: false
13
+ maxSizeMB: 15
14
+ maxPreviewWidth: 300px
15
+ allowedTypes: ["image/*"]
16
+ crops:
17
+ standard:
18
+ label: "Card Header (16:9)"
19
+ aspectRatio: 3.0
20
+ public: true
21
+
22
+ - headline:
23
+ label: Headline
24
+ type: text
25
+ required: true
26
+ public: true
27
+ default: "Headline..."
28
+
29
+ - summary:
30
+ label: Text
31
+ description: Keep it brief, 2-3 sentences max.
32
+ type: richtext
33
+ editor: minimal
34
+ public: true
35
+ default: "Card text content..."
36
+
37
+ - linkUrl:
38
+ label: Link URL
39
+ type: text
40
+ description: Where should this card link to? (e.g., /about or https://...)
41
+ public: true
42
+ default: "https://"
43
+
44
+ - linkText:
45
+ label: Button Text
46
+ type: text
47
+ description: "e.g., 'Read More' or 'Buy Now'"
48
+ default: "Read More"
49
+ public: true
50
+
51
+ - fieldSet: access
52
+ - hidden:
53
+ default: false
@@ -0,0 +1,16 @@
1
+ id: text
2
+ label: Text
3
+ group: Content
4
+ description: Paragraph body
5
+
6
+ fields:
7
+ - body:
8
+ label: Text
9
+ type: richtext
10
+ editor: default
11
+ default: "Write content here..."
12
+ public: true
13
+
14
+ - fieldSet: access
15
+ - hidden:
16
+ default: false
@@ -0,0 +1,24 @@
1
+ id: ecommerce
2
+ label: "E-Commerce"
3
+
4
+ tabs:
5
+ settings:
6
+ label: "Channel"
7
+ fields:
8
+ - fieldSet: channel-base
9
+
10
+ - currency:
11
+ label: Default Currency
12
+ type: select
13
+ default: "EUR"
14
+ options:
15
+ - "EUR"
16
+ - "USD"
17
+ - "GBP"
18
+
19
+ - checkoutUrl:
20
+ label: Checkout Endpoint URL
21
+ type: text
22
+ public: true
23
+
24
+ - fieldSet: access
@@ -0,0 +1,32 @@
1
+ id: website
2
+ label: "Website"
3
+
4
+ tabs:
5
+ settings:
6
+ label: "Channel"
7
+ fields:
8
+ - fieldSet: channel-base
9
+
10
+ - domain:
11
+ label: Hostname
12
+ type: text
13
+ dbType: text
14
+ index: true
15
+ public: true
16
+
17
+ - baseUrl:
18
+ label: Base URL
19
+ type: text
20
+ dbType: text
21
+ public: true
22
+
23
+ - defaultLanguage:
24
+ label: Channel Language / Locale
25
+ type: language-select
26
+ dbType: text
27
+ public: true
28
+
29
+ access:
30
+ label: "Access"
31
+ fields:
32
+ - fieldSet: access
@@ -0,0 +1,29 @@
1
+ tab:
2
+ id: access
3
+ label: "Access"
4
+
5
+ fields:
6
+ isHidden: # 🎯 Renamed from 'hidden' to match the DB column
7
+ type: boolean
8
+ label: Hidden
9
+ default: false
10
+ public: false
11
+ variant: danger
12
+ dbType: integer
13
+ index: true
14
+ position: sidebar
15
+ liveSync: true
16
+
17
+ startShowingAt:
18
+ type: datetime
19
+ label: Start Showing At
20
+ position: sidebar
21
+ public: false
22
+ index: true
23
+
24
+ stopShowingAt:
25
+ type: datetime
26
+ label: Stop Showing At
27
+ position: sidebar
28
+ public: false
29
+ index: true
@@ -0,0 +1,31 @@
1
+ id: channel-base
2
+ label: Channel Base Properties
3
+
4
+ fields:
5
+ - id:
6
+ label: Internal ID
7
+ type: hidden
8
+ dbType: integer
9
+ primaryKey: true
10
+
11
+ - label:
12
+ label: Channel Name
13
+ type: text
14
+ dbType: text
15
+ public: true
16
+ rules:
17
+ required: true
18
+
19
+ - key:
20
+ label: "API Identifier"
21
+ type: slug
22
+ dbType: text
23
+ public: true
24
+ index: true
25
+ linkedTo: label
26
+ placeholder: "e.g. main-site, store-eu"
27
+ rules:
28
+ required: true
29
+ unique: true
30
+ pattern: "^[a-z0-9-_]+$"
31
+ customMessage: "API key must be unique and contain only lowercase letters, numbers, hyphens, or underscores."
@@ -0,0 +1,11 @@
1
+ tab:
2
+ id: comments
3
+ label: "Comments"
4
+
5
+ fields:
6
+ isHidden:
7
+ type: textarea
8
+ label: Comments
9
+ public: false
10
+ index: true
11
+ position: sidebar
@@ -0,0 +1,46 @@
1
+ # definitions/field-sets/content-block.yaml
2
+ id: content-block
3
+ label: Content Block Base Properties
4
+
5
+ fields:
6
+ - id:
7
+ label: ID
8
+ type: hidden
9
+ dbType: text
10
+ primaryKey: true
11
+
12
+ - contentId:
13
+ label: Node ID
14
+ type: text
15
+ dbType: text
16
+ rules:
17
+ required: true
18
+
19
+ - sortOrder:
20
+ label: Sort Order
21
+ type: number
22
+ dbType: integer
23
+ rules:
24
+ required: true
25
+
26
+ - elementType:
27
+ label: Element Component Type
28
+ type: text
29
+ dbType: text
30
+ rules:
31
+ required: true
32
+
33
+ - dataJson:
34
+ label: Content Field Data
35
+ type: json
36
+ dbType: json
37
+ rules:
38
+ required: true
39
+
40
+ - region:
41
+ label: Grid Region
42
+ type: text
43
+ dbType: text
44
+ default: "main"
45
+ rules:
46
+ required: true
@@ -0,0 +1,49 @@
1
+ # definitions/field-sets/page-base.yaml
2
+ id: page-base
3
+ label: Base Page Properties
4
+
5
+ fields:
6
+ - id:
7
+ label: ID
8
+ type: hidden
9
+ dbType: text
10
+ primaryKey: true
11
+ public: true
12
+
13
+ - channelId:
14
+ label: Channel ID
15
+ type: hidden
16
+ dbType: text
17
+ public: true
18
+ index: true
19
+
20
+ - nodeType:
21
+ label: Type
22
+ type: select
23
+ dbType: text
24
+ default: "page"
25
+ public: true
26
+ position: sidebar
27
+ rules:
28
+ required: true
29
+
30
+ - title:
31
+ label: Page Title
32
+ type: text
33
+ dbType: text
34
+ public: true
35
+ rules:
36
+ required: true
37
+
38
+ - slug:
39
+ label: URL Slug
40
+ type: slug
41
+ linkedTo: title
42
+ dbType: text
43
+ public: true
44
+ index: true
45
+ rules:
46
+ required: true
47
+ uniqueSibling: true
48
+ pattern: "^[a-z0-9_-]+$"
49
+ customMessage: "Slug can only contain lowercase letters, numbers, hyphens, and underscores."
@@ -0,0 +1,15 @@
1
+ tab:
2
+ id: page-meta
3
+ label: "Meta"
4
+
5
+ fields:
6
+ metaDescription:
7
+ label: Meta Description
8
+ type: textarea
9
+ public: true
10
+
11
+ metaKeywords:
12
+ label: Meta Keywords
13
+ type: textarea
14
+ required: false
15
+ public: true
@@ -0,0 +1,80 @@
1
+ tab:
2
+ id: page-social
3
+ label: "Social"
4
+
5
+ fields:
6
+ # --- Facebook / Open Graph Texts ---
7
+ fbTitle:
8
+ label: "Facebook Title (og:title)"
9
+ type: text
10
+ inherits: title # 🔗 Syncs from SEO Title automatically
11
+ public: true
12
+ description: "The title of your article as it should appear on Facebook."
13
+
14
+ fbDescription:
15
+ label: "Facebook Description (og:description)"
16
+ type: textarea
17
+ inherits: metaDescription # 🔗 Or whatever your main SEO description field is named
18
+ public: true
19
+ description: "A 2-4 sentence summary of the content."
20
+
21
+ # --- X (Twitter) Texts ---
22
+ xTitle:
23
+ label: "X Title (twitter:title)"
24
+ type: text
25
+ inherits: fbTitle # 🔗 Cascades from Facebook Title (which cascades from SEO)
26
+ public: true
27
+ description: "Keep it concise (under 70 characters is ideal for X)."
28
+
29
+ xDescription:
30
+ label: "X Description (twitter:description)"
31
+ type: textarea
32
+ inherits: fbDescription
33
+ public: true
34
+
35
+ # ==========================================
36
+ # SOCIAL IMAGES (The Waterfall)
37
+ # ==========================================
38
+
39
+ # 1. The Source Image
40
+ socialImage:
41
+ label: "Master Social Image"
42
+ type: image
43
+ maxPreviewWidth: 620px
44
+ multiple: false
45
+ maxSizeMB: 10
46
+ allowedTypes: ["image/jpeg", "image/png", "image/webp"]
47
+ public: true
48
+
49
+ # 2. The Auto-Synced Crops
50
+ fbImage:
51
+ label: "Facebook / LinkedIn Image"
52
+ type: image
53
+ maxPreviewWidth: 620px
54
+ inherits: socialImage # 🔗 Syncs from socialImage
55
+ public: true
56
+ description: "Auto-cropped to 1.91:1. Drop a new image here to override."
57
+ crops:
58
+ aspectRatio: 1.91
59
+
60
+ xImage:
61
+ label: "X (Twitter) Image"
62
+ type: image
63
+ maxPreviewWidth: 620px
64
+ inherits: socialImage # 🔗 Syncs from socialImage
65
+ public: true
66
+ description: "Auto-cropped to 2:1. Drop a new image here to override."
67
+ crops:
68
+ aspectRatio: 2.0
69
+
70
+ whatsappImage:
71
+ label: "Messaging / Square Image"
72
+ type: image
73
+ maxPreviewWidth: 620px
74
+ inherits: socialImage # 🔗 Syncs from socialImage
75
+ public: true
76
+ description: "Auto-cropped to 1:1. Drop a new image here to override."
77
+ crops:
78
+ whatsappImage:
79
+ label: "WhatsApp Image (1:1)"
80
+ aspectRatio: 1.0
@@ -0,0 +1,31 @@
1
+ id: tree-base
2
+ label: Tree Base Properties
3
+
4
+ fields:
5
+ - id:
6
+ label: Internal ID
7
+ type: hidden
8
+ dbType: integer
9
+ primaryKey: true
10
+
11
+ - label:
12
+ label: "Label"
13
+ type: text
14
+ dbType: text
15
+ public: true
16
+ rules:
17
+ required: true
18
+
19
+ - key:
20
+ label: "API Identifier"
21
+ type: slug
22
+ dbType: text
23
+ index: true
24
+ public: true
25
+ linkedTo: label
26
+ placeholder: "e.g. main-site, store-eu"
27
+ rules:
28
+ required: true
29
+ unique: true
30
+ pattern: "^[a-z0-9-_]+$"
31
+ customMessage: "API key must be unique and contain only lowercase letters, numbers, hyphens, or underscores."
@@ -0,0 +1,8 @@
1
+ fields:
2
+ showInMenus:
3
+ type: boolean
4
+ label: Show in Menus
5
+ default: true
6
+ position: sidebar
7
+ public: true
8
+ variant: alert
@@ -0,0 +1,22 @@
1
+ id: landing-page
2
+ label: Landing Page
3
+
4
+ layout:
5
+ columns: "1fr"
6
+ areas:
7
+ - ["header"]
8
+ - ["hero"]
9
+ - ["content"]
10
+ - ["footer"]
11
+
12
+ regions:
13
+ header:
14
+ label: Header
15
+ inherit: parent
16
+ hero:
17
+ label: Hero / Top Banner
18
+ content:
19
+ label: Content
20
+ footer:
21
+ label: Footer
22
+ inherit: parent
@@ -0,0 +1,38 @@
1
+ id: single-column
2
+ label: Single Column
3
+
4
+ layout:
5
+ columns: "1fr"
6
+ areas:
7
+ - ["header"]
8
+ - ["content"]
9
+ - ["footer"]
10
+
11
+ regions:
12
+ header:
13
+ label: Header Image
14
+ showLabel: true
15
+ inherit: parent
16
+ allowOverride: true
17
+ dropType: image
18
+ maxPreviewWidth: 800px
19
+ multiple: false
20
+ maxSizeMB: 20
21
+ allowedTypes: ["image/*"]
22
+ crops:
23
+ standard:
24
+ label: "Desktop Header (4:1)"
25
+ aspectRatio: 4.0
26
+ tablet:
27
+ label: "Tablet Header (2:1)"
28
+ aspectRatio: 2.0
29
+ mobile:
30
+ label: "Mobile Header (4:3)"
31
+ aspectRatio: 1.33
32
+ public: true
33
+
34
+ content:
35
+ label: Content
36
+ footer:
37
+ label: Footer
38
+ inherit: parent
@@ -0,0 +1,41 @@
1
+ id: three-column
2
+ label: Three Column
3
+
4
+ layout:
5
+ columns: "2fr 7fr 3fr"
6
+ areas:
7
+ - ["header", "header", "header"]
8
+ - ["left_nav", "content", "right_sidebar"]
9
+ - ["footer", "footer", "footer"]
10
+
11
+ regions:
12
+ header:
13
+ label: Header Image
14
+ showLabel: true
15
+ inherit: parent
16
+ allowOverride: true
17
+ dropType: image
18
+ maxPreviewWidth: 800px
19
+ multiple: false
20
+ maxSizeMB: 20
21
+ allowedTypes: ["image/*"]
22
+ crops:
23
+ standard:
24
+ label: "Desktop Header (4:1)"
25
+ aspectRatio: 4.0
26
+ tablet:
27
+ label: "Tablet Header (2:1)"
28
+ aspectRatio: 2.0
29
+ mobile:
30
+ label: "Mobile Header (4:3)"
31
+ aspectRatio: 1.33
32
+ public: true
33
+ left_nav:
34
+ label: Left Navigation
35
+ content:
36
+ label: Content
37
+ right_sidebar:
38
+ label: Right Sidebar
39
+ footer:
40
+ label: Footer
41
+ inherit: parent
@@ -0,0 +1,40 @@
1
+ # defaults/definitions/layouts/two-column-split.yml
2
+ id: two-column
3
+ label: Two Column
4
+
5
+ layout:
6
+ columns: "10fr 4fr"
7
+ areas:
8
+ - ["header", "header"]
9
+ - ["content", "sidebar"] # 👈 Changed 'main' to 'content' to match old data!
10
+ - ["footer", "footer"]
11
+
12
+ regions:
13
+ header:
14
+ label: Header Image
15
+ showLabel: true
16
+ inherit: parent
17
+ allowOverride: true
18
+ dropType: image
19
+ maxPreviewWidth: 800px
20
+ multiple: false
21
+ maxSizeMB: 20
22
+ allowedTypes: ["image/*"]
23
+ crops:
24
+ standard:
25
+ label: "Desktop Header (4:1)"
26
+ aspectRatio: 4.0
27
+ tablet:
28
+ label: "Tablet Header (2:1)"
29
+ aspectRatio: 2.0
30
+ mobile:
31
+ label: "Mobile Header (4:3)"
32
+ aspectRatio: 1.33
33
+ public: true
34
+ content: # 👈 Changed 'main' to 'content'
35
+ label: Content
36
+ sidebar:
37
+ label: Sidebar
38
+ footer:
39
+ label: Footer
40
+ inherit: parent
@@ -0,0 +1,32 @@
1
+ id: address
2
+ label: Address
3
+ inTree: false
4
+
5
+ # 👉 TABS DEFINITION
6
+ tabs:
7
+ settings:
8
+ label: "Address"
9
+ fields:
10
+ - title:
11
+ label: Address Title
12
+ required: true
13
+ - street:
14
+ label: Street
15
+ - city:
16
+ label: City
17
+ - area:
18
+ label: County / Area
19
+ - zip:
20
+ label: ZIP
21
+ - country:
22
+ label: Country
23
+ geolocation:
24
+ label: "Geolocation"
25
+ fields:
26
+ - lat:
27
+ label: Latitude
28
+ - lng:
29
+ label: Longitude
30
+ - timezone:
31
+ label: Timezone
32
+ - fieldSet: access
@@ -0,0 +1,41 @@
1
+ id: article
2
+ label: Article
3
+ inTree: false
4
+ hasCanvas: true
5
+
6
+ regions:
7
+ content:
8
+ label: Content
9
+
10
+ allowedElements:
11
+ - header
12
+ - hero
13
+ - text
14
+ - image_card
15
+ - header_image
16
+ - two-column
17
+ - three-column
18
+
19
+ tabs:
20
+ settings:
21
+ label: "Article"
22
+ fields:
23
+ - fieldSet: page-base
24
+ - title:
25
+ label: Article title
26
+ required: true
27
+ - slug:
28
+ linkedTo: "title"
29
+ - articleDate:
30
+ type: date
31
+ label: Article Date
32
+ public: true
33
+ default: now
34
+ index: true
35
+ - abstract:
36
+ label: Abstract
37
+ type: textarea
38
+ public: true
39
+ - fieldSet: page-meta
40
+ - fieldSet: page-social
41
+ - fieldSet: access
@@ -0,0 +1,40 @@
1
+ id: event
2
+ label: Event
3
+ inTree: false
4
+ hasCanvas: true
5
+
6
+ regions:
7
+ main:
8
+ label: Content
9
+
10
+ allowedElements:
11
+ - title-set
12
+ - text-block
13
+
14
+ tabs:
15
+ settings:
16
+ label: Event
17
+ fields:
18
+ - fieldSet: page-base
19
+ - title:
20
+ label: Event Title
21
+ required: true
22
+ - slug:
23
+ linkedTo: "title"
24
+ - startTime:
25
+ type: datetime
26
+ label: Starts
27
+ public: true
28
+ index: true
29
+ - endTime:
30
+ type: datetime
31
+ label: Finishes
32
+ public: true
33
+ index: true
34
+ - abstract:
35
+ label: Abstract
36
+ type: textarea
37
+ public: true
38
+ - fieldSet: page-meta
39
+ - fieldSet: page-social
40
+ - fieldSet: access
@@ -0,0 +1,26 @@
1
+ id: external-link
2
+ label: External Link
3
+ inTree: true
4
+
5
+ tabs:
6
+ settings:
7
+ label: "Link"
8
+ fields:
9
+ - fieldSet: page-base
10
+ - nodeType
11
+ - title:
12
+ label: Link Text
13
+ - iconName:
14
+ label: Icon
15
+ type: icon
16
+ dbType: text
17
+ packages: ["heroicons", "lucide", "mdi"]
18
+ - slug:
19
+ label: Slug
20
+ linkedTo: "menuTitle, title"
21
+ - url:
22
+ label: URL
23
+ type: text
24
+ public: true
25
+
26
+ - fieldSet: access
@@ -0,0 +1,63 @@
1
+ id: page
2
+ label: Page
3
+ inTree: true
4
+ inVault: true
5
+ hasCanvas: true
6
+
7
+ # 👈 Keep this as the absolute last-resort fallback for the Public API
8
+ defaultLayout: "single-column"
9
+
10
+ # 👈 Cleaned up naming!
11
+ allowedBlocks:
12
+ - header
13
+ - hero
14
+ - text
15
+ - image_card
16
+ - header_image
17
+ - two-column
18
+ - three-column
19
+
20
+ tabs:
21
+ settings:
22
+ label: "Page"
23
+ fields:
24
+ - fieldSet: page-base
25
+
26
+ # 👉 THE CURRENT PAGE'S LAYOUT
27
+ - layoutId:
28
+ label: Page Layout
29
+ type: layout-select
30
+ dbType: text
31
+ default: "inherit"
32
+ inheritsFromParent: true # 👈 Tells the UI to bind to the parent!
33
+ allowedLayouts:
34
+ - two-column
35
+ - single-column
36
+ - three-column
37
+ - landing-page
38
+ options:
39
+ - { label: "Inherit Layout", value: "inherit" }
40
+
41
+ # 👉 THE BEHAVIOR FOR ALL CHILDREN
42
+ - subpageLayoutId:
43
+ label: Subpages Layout Rule
44
+ type: layout-select
45
+ dbType: text
46
+ default: "inherit"
47
+ inheritsFromParent: true # 👈 A child's subpage rules can ALSO inherit!
48
+ allowedLayouts:
49
+ - two-column
50
+ - three-column
51
+ - single-column
52
+ - landing-page
53
+ options:
54
+ - { label: "Inherit Layout", value: "inherit" }
55
+
56
+ - menuTitle:
57
+ label: Menu Title
58
+ type: text
59
+ dbType: text
60
+ public: true
61
+ - fieldSet: page-meta
62
+ - fieldSet: page-social
63
+ - fieldSet: access
@@ -0,0 +1,35 @@
1
+ id: person
2
+ label: Person
3
+ inTree: false
4
+ inVault: true
5
+ hasCanvas: false
6
+
7
+ # 👉 TABS DEFINITION
8
+ tabs:
9
+ settings:
10
+ label: "Person"
11
+ fields:
12
+ - image:
13
+ label: Image
14
+ type: asset
15
+ multiple: false
16
+ maxSizeMB: 15
17
+ allowedTypes: ["image/*"]
18
+ maxPreviewWidth: 300
19
+ crops:
20
+ list_thumb:
21
+ label: "List View"
22
+ aspectRatio: 1
23
+ shape: "circle"
24
+ public: true
25
+
26
+ - title:
27
+ label: Title
28
+ required: false
29
+ - firstName:
30
+ label: First Name
31
+ - lastName:
32
+ label: Last Name
33
+ - position:
34
+
35
+ - fieldSet: access
@@ -0,0 +1,79 @@
1
+ id: product
2
+ label: Product
3
+ inTree: false
4
+ inVault: true
5
+ hasCanvas: false
6
+
7
+ layout:
8
+ columns: "3fr 1fr"
9
+ areas:
10
+ - ["main", "sidebar"]
11
+
12
+ regions:
13
+ main:
14
+ label: Product Content
15
+ sidebar:
16
+ label: Sidebar
17
+
18
+ allowedElements:
19
+ - text-block
20
+ - image_card
21
+
22
+ tabs:
23
+ settings:
24
+ label: "Product"
25
+ fields:
26
+ - fieldSet: page-base
27
+ - title:
28
+ label: Product Name
29
+ required: true
30
+ public: true
31
+ - slug:
32
+ linkedTo: "title"
33
+ - price:
34
+ label: Price (€)
35
+ type: number
36
+ public: true
37
+ - description:
38
+ label: Short Abstract
39
+ type: textarea
40
+ public: true
41
+ - fieldSet: meta
42
+ - fieldSet: access
43
+
44
+ media:
45
+ label: "Media"
46
+ fields:
47
+ # 🎯 ZONE 1: The Main Image (Strict crops for lists/catalogs)
48
+ - featuredImage:
49
+ label: Main Product Image
50
+ description: "This is the primary image used in product grids, search results, and catalogs."
51
+ type: asset
52
+ multiple: false
53
+ maxSizeMB: 15
54
+ allowedTypes: ["image/jpeg", "image/png", "image/webp"]
55
+ crops:
56
+ list_thumb:
57
+ label: "List View (4:3)"
58
+ aspectRatio: 1.333
59
+ catalog_card:
60
+ label: "Portrait Card (3:4)"
61
+ aspectRatio: 0.75
62
+ public: true
63
+
64
+ # 🎯 ZONE 2: The Gallery (Different crops for the carousel)
65
+ - gallery:
66
+ label: Product Gallery
67
+ description: "Drag up to 10 images here for the product detail carousel."
68
+ type: asset
69
+ multiple: true
70
+ maxItems: 10
71
+ allowedTypes: ["image/jpeg", "image/png", "image/webp"]
72
+ crops:
73
+ carousel:
74
+ label: "Carousel Hero (16:9)"
75
+ aspectRatio: 1.777
76
+ zoom:
77
+ label: "Detail Zoom (Square)"
78
+ aspectRatio: 1.0
79
+ public: true
@@ -0,0 +1,29 @@
1
+ id: navigation
2
+ label: "Site Navigation"
3
+
4
+ tabs:
5
+ settings:
6
+ label: "Tree"
7
+ fields:
8
+ - fieldSet: tree-base
9
+ - baseUrl:
10
+ label: Base URL
11
+ type: text
12
+ dbType: text
13
+ public: true
14
+ - defaultLanguage:
15
+ label: Channel Language / Locale
16
+ type: language-select
17
+ dbType: text
18
+ public: true
19
+ - maxDepth:
20
+ type: select
21
+ label: "Maximum Menu Depth"
22
+ default: "3"
23
+ options:
24
+ - "1"
25
+ - "2"
26
+ - "3"
27
+ - "4"
28
+
29
+ - fieldSet: access
@@ -0,0 +1,9 @@
1
+ id: social-links
2
+ label: "Social links"
3
+
4
+ tabs:
5
+ settings:
6
+ label: "Tree"
7
+ fields:
8
+ - fieldSet: tree-base
9
+ - fieldSet: access
@@ -0,0 +1,52 @@
1
+ id: _page
2
+ label: Page Old
3
+ inTree: true
4
+ inVault: true
5
+ hasCanvas: true
6
+
7
+ layout:
8
+ columns: "10fr 4fr"
9
+ areas:
10
+ - ["header", "header"]
11
+ - ["main", "sidebar"]
12
+ - ["footer", "footer"]
13
+
14
+ regions:
15
+ header:
16
+ label: Header
17
+ inherit: parent
18
+ main:
19
+ label: Content
20
+ sidebar:
21
+ label: Sidebar
22
+ footer:
23
+ label: Footer
24
+ inherit: parent
25
+
26
+ allowedElements:
27
+ - header
28
+ - hero
29
+ - text
30
+ - image_card
31
+
32
+ # 👉 TABS DEFINITION
33
+ tabs:
34
+ settings:
35
+ label: "Page"
36
+ fields:
37
+ # Imports core fields: title, slug, isHidden
38
+ - fieldSet: page-base
39
+
40
+ # Overrides & custom properties
41
+ - menuTitle:
42
+ label: Menu Title
43
+ type: text
44
+ dbType: text
45
+ public: true
46
+ placeholder: "Short title for navigation menus"
47
+
48
+ # SEO / OpenGraph metadata
49
+ - fieldSet: meta
50
+
51
+ # Role permissions & access control
52
+ - fieldSet: access
@@ -0,0 +1,17 @@
1
+ import StarterKit from "@tiptap/starter-kit";
2
+ import Link from "@tiptap/extension-link";
3
+ import type { RteProfile } from "../../utils/rteProfiles";
4
+
5
+ export const profile: RteProfile = {
6
+ extensions: [StarterKit, Link.configure({ openOnClick: false })],
7
+ toolbar: [
8
+ "heading-1",
9
+ "heading-2",
10
+ "bold",
11
+ "italic",
12
+ "bullet-list",
13
+ "ordered-list",
14
+ "blockquote",
15
+ "link",
16
+ ],
17
+ };
@@ -0,0 +1,33 @@
1
+ // definitions/editor/header.ts
2
+ import StarterKit from "@tiptap/starter-kit";
3
+ import Document from "@tiptap/extension-document";
4
+ import Subscript from "@tiptap/extension-subscript";
5
+ import Superscript from "@tiptap/extension-superscript";
6
+ import type { RteProfile } from "../../src/utils/rteProfiles";
7
+
8
+ // Custom document node forcing default top-level block to be a heading (H2)
9
+ const CustomDocument = Document.extend({
10
+ content: "heading block*",
11
+ });
12
+
13
+ export const profile: RteProfile = {
14
+ extensions: [
15
+ CustomDocument,
16
+ StarterKit.configure({
17
+ document: false, // Replaces TipTap's default Document with CustomDocument
18
+ heading: { levels: [1, 2, 3, 4] },
19
+ }),
20
+ Subscript.configure(),
21
+ Superscript.configure(),
22
+ ],
23
+ toolbar: [
24
+ "heading-1",
25
+ "heading-2",
26
+ "heading-3",
27
+ "heading-4",
28
+ "bold",
29
+ "italic",
30
+ "superscript",
31
+ "subscript",
32
+ ],
33
+ };
@@ -0,0 +1,13 @@
1
+ import StarterKit from "@tiptap/starter-kit";
2
+ import Link from "@tiptap/extension-link";
3
+ import type { RteProfile } from "../../utils/rteProfiles";
4
+
5
+ export const profile: RteProfile = {
6
+ extensions: [StarterKit, Link.configure({ openOnClick: false })],
7
+ toolbar: [
8
+ "bold",
9
+ "italic",
10
+ "bullet-list",
11
+ "ordered-list",
12
+ ],
13
+ };
@@ -0,0 +1,12 @@
1
+ // definitions/validators/iban.ts
2
+
3
+ export const iban = (value: any, ruleValue: any, field: any) => {
4
+ if (!value || !ruleValue) return "";
5
+
6
+ const isValid = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/.test(String(value).toUpperCase());
7
+ if (!isValid) {
8
+ return field.rules?.customMessage || "Invalid IBAN format.";
9
+ }
10
+
11
+ return "";
12
+ };
@@ -0,0 +1,7 @@
1
+ // definitions/validators/test.ts
2
+ export const isMagicWord = (value: any, ruleValue: any, field: any) => {
3
+ if (value && value.toLowerCase() !== "magic") {
4
+ return field.rules?.customMessage || "You didn't say the magic word!";
5
+ }
6
+ return "";
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightsideoy/kama",
3
- "version": "0.1.0-alpha.39",
3
+ "version": "0.1.0-alpha.40",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -16,6 +16,8 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
+ "defaults",
20
+ "definitions",
19
21
  "bin",
20
22
  "scripts",
21
23
  "schema.sql"
@@ -1,12 +1,29 @@
1
1
  // scripts/bundle-definitions.js
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
  import YAML from 'yaml';
5
6
 
6
- // Look for definitions across package core folders
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ // 🎯 Package root is one directory up from scripts/
11
+ const packageRootDir = path.resolve(__dirname, '..');
12
+
13
+ // Look for definitions across package core AND consumer project overrides
7
14
  function getDefinitionFiles(subDir) {
8
15
  const fileMap = new Map();
16
+
9
17
  const possibleDirs = [
18
+ // 1. Core package defaults (from inside node_modules or package source)
19
+ path.resolve(packageRootDir, "defaults/definitions", subDir),
20
+ path.resolve(packageRootDir, "definitions", subDir),
21
+
22
+ // 2. Fallbacks relative to node_modules if running from workspace root
23
+ path.resolve(process.cwd(), "node_modules/@brightsideoy/kama/defaults/definitions", subDir),
24
+ path.resolve(process.cwd(), "node_modules/@brightsideoy/kama/definitions", subDir),
25
+
26
+ // 3. Consumer project local overrides (highest priority — overwrites defaults)
10
27
  path.resolve(process.cwd(), "defaults/definitions", subDir),
11
28
  path.resolve(process.cwd(), "definitions", subDir),
12
29
  path.resolve(process.cwd(), "schemas", subDir),
@@ -29,6 +46,7 @@ function getDefinitionFiles(subDir) {
29
46
  entry.name.endsWith(".yml") ||
30
47
  entry.name.endsWith(".json")
31
48
  ) {
49
+ // Overwrite earlier entries so local consumer files override package defaults
32
50
  fileMap.set(entryRelPath, fullPath);
33
51
  }
34
52
  }
@@ -103,23 +121,31 @@ const outputContent = `// Auto-generated pre-bundled definitions for Kama Core E
103
121
  export const BUNDLED_DEFINITIONS = ${JSON.stringify(bundled, null, 2)};
104
122
  `;
105
123
 
106
- const outputDir = path.resolve(process.cwd(), 'server/db');
107
- if (!fs.existsSync(outputDir)) {
108
- fs.mkdirSync(outputDir, { recursive: true });
124
+ // 🎯 Write to BOTH package dist/server locations so imports resolve cleanly
125
+ const targetDirs = [
126
+ path.resolve(packageRootDir, 'server/db'),
127
+ path.resolve(process.cwd(), 'node_modules/@brightsideoy/kama/server/db'),
128
+ path.resolve(process.cwd(), 'node_modules/@brightsideoy/kama/dist'),
129
+ path.resolve(process.cwd(), 'server/db'),
130
+ ];
131
+
132
+ for (const dir of targetDirs) {
133
+ if (fs.existsSync(dir)) {
134
+ const outputPath = path.resolve(dir, 'bundled-definitions.ts');
135
+ fs.writeFileSync(outputPath, outputContent, 'utf-8');
136
+ // Also write .js version for built packages
137
+ const jsOutputPath = path.resolve(dir, 'bundled-definitions.js');
138
+ fs.writeFileSync(jsOutputPath, `export const BUNDLED_DEFINITIONS = ${JSON.stringify(bundled, null, 2)};\n`, 'utf-8');
139
+ }
109
140
  }
110
141
 
111
- const outputPath = path.resolve(outputDir, 'bundled-definitions.ts');
112
- fs.writeFileSync(outputPath, outputContent, 'utf-8');
113
-
114
- console.log(`✅ Definitions bundled successfully into Kama Core:
142
+ console.log(`✅ Definitions bundled successfully:
115
143
  • Nodes: ${Object.keys(bundled.rawNodes).length} (${Object.keys(bundled.rawNodes).join(', ')})
116
- • Blocks: ${Object.keys(bundled.rawBlocks).length}
117
- • Channels: ${Object.keys(bundled.rawChannels).length}
118
- Saved into ${outputPath}`);
144
+ • Blocks: ${Object.keys(bundled.rawBlocks).length} (${Object.keys(bundled.rawBlocks).join(', ')})
145
+ • Channels: ${Object.keys(bundled.rawChannels).length} (${Object.keys(bundled.rawChannels).join(', ')})
146
+ Trees: ${Object.keys(bundled.rawTrees).length} (${Object.keys(bundled.rawTrees).join(', ')})`);
119
147
 
120
- // ==========================================
121
- // AUTO-GENERATE SQL INDEXES FROM SCHEMA
122
- // ==========================================
148
+ // Auto-generate SQL Indexes
123
149
  const indexQueries = new Set([
124
150
  "-- Native Columns",
125
151
  "CREATE INDEX IF NOT EXISTS idx_content_nodes_slug ON content_nodes (slug);",
@@ -130,7 +156,6 @@ const indexQueries = new Set([
130
156
 
131
157
  function extractIndexes(obj) {
132
158
  if (typeof obj !== 'object' || obj === null) return;
133
-
134
159
  for (const [key, value] of Object.entries(obj)) {
135
160
  if (typeof value === 'object' && value !== null) {
136
161
  if (value.index === true && key !== "id" && key !== "slug" && key !== "contentType") {
@@ -148,5 +173,4 @@ const schemaSqlPath = path.resolve(process.cwd(), 'schema.sql');
148
173
 
149
174
  if (fs.existsSync(schemaSqlPath)) {
150
175
  fs.appendFileSync(schemaSqlPath, '\n\n' + sqlContent + '\n', 'utf-8');
151
- console.log(`✅ Appended JSON auto-indexes directly to schema.sql`);
152
176
  }