@silexlabs/silex-dashboard 1.5.0-canary.0 → 1.5.0-canary.2

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 (70) hide show
  1. package/DASHBOARD-ARCHITECTURE.md +1410 -0
  2. package/_site/css/connectors-e4ae24975f4df2e2bea5121337282d8f2201bb86dc8c041c9ef43c0c2d7a32a7.css +1 -0
  3. package/_site/css/fork-506e7c805e636be6225c35a11f5804b72cf41d7a6355aa64a5d7ddb7d66ea314.css +1 -0
  4. package/_site/css/fork-923d814802e46c2d0db0f71c5c488c616ca0016f340d8dae66b263ba7f1143bc.css +1 -0
  5. package/_site/css/index-38d10542f641eb789c11b589b95d0e6363e71978479fa5cddbec3da65774560c.css +1 -0
  6. package/_site/en/connectors/index.html +51 -26
  7. package/_site/en/fork/index.html +371 -0
  8. package/_site/en/index.html +30 -28
  9. package/_site/fr/connectors/index.html +52 -27
  10. package/_site/fr/fork/index.html +372 -0
  11. package/_site/fr/index.html +31 -29
  12. package/collections/fork/en.md +17 -0
  13. package/collections/fork/fr.md +17 -0
  14. package/package.json +2 -2
  15. package/silex/websites/default/meta.json +4 -0
  16. package/silex/websites/default/website.json +6 -0
  17. package/templates/connectors-en.html +66 -41
  18. package/templates/connectors-fr.html +66 -41
  19. package/templates/css/connectors-e4ae24975f4df2e2bea5121337282d8f2201bb86dc8c041c9ef43c0c2d7a32a7.css +1 -0
  20. package/templates/css/fork-506e7c805e636be6225c35a11f5804b72cf41d7a6355aa64a5d7ddb7d66ea314.css +1 -0
  21. package/templates/css/fork-923d814802e46c2d0db0f71c5c488c616ca0016f340d8dae66b263ba7f1143bc.css +1 -0
  22. package/templates/css/index-38d10542f641eb789c11b589b95d0e6363e71978479fa5cddbec3da65774560c.css +1 -0
  23. package/templates/fork-en.11tydata.mjs +117 -0
  24. package/templates/fork-en.html +412 -0
  25. package/templates/fork-fr.11tydata.mjs +117 -0
  26. package/templates/fork-fr.html +412 -0
  27. package/templates/{websites-en.11tydata.mjs → index-en.11tydata.mjs} +32 -32
  28. package/templates/{websites-en.html → index-en.html} +91 -89
  29. package/templates/{websites-fr.11tydata.mjs → index-fr.11tydata.mjs} +32 -32
  30. package/templates/{websites-fr.html → index-fr.html} +91 -89
  31. package/tina/config.ts +76 -0
  32. package/tina/tina-lock.json +1 -1
  33. package/_site/connectors/index.html +0 -278
  34. package/_site/css/connectors-1350f25629f7addac6970f5297f8aef050ba1c207b28c8a0e4a7b113246d69dc.css +0 -1
  35. package/_site/css/connectors-152b01dfacf0586ce2f3fb0ddbbf5a5bbcd181a48b4b20b275f837786b2889cd.css +0 -1
  36. package/_site/css/connectors-306649a254eff69e10db2fa5368e173e88e24449db25d65b7117ebfd5dbbcd31.css +0 -1
  37. package/_site/css/connectors-66fe82f10a7a257f59212e18bb384b019ffb12192fa3f748951da7d4428733b4.css +0 -1
  38. package/_site/css/connectors-748797d88cc32c0787052966e54e4f794629b8460d24f2aadac83c13899574fb.css +0 -1
  39. package/_site/css/connectors-87cf0f4b873d104a845dcafd5d80d4f8d482f8cea15f2412cd9655071dcb9ea8.css +0 -1
  40. package/_site/css/connectors-885e03b1611113e2363f7a86fba9aee867130a14836b3cc9c4714265708c1704.css +0 -1
  41. package/_site/css/connectors-9d0c9569a2ce8afaa4a30a0534b3d0597c761c7e0e5280dcbc966dc2b709e98e.css +0 -1
  42. package/_site/css/connectors-a8ae93c40b7c738a4514e47046a029afdcf74bb28a7724287e1350afac4fa60f.css +0 -1
  43. package/_site/css/connectors-c1d5f4c520a9a86d99b7201d30a8fe25a5779b7b2373b73262868f446fe3f855.css +0 -1
  44. package/_site/css/connectors-febcafaf261c0f23ca11fc13809f259c74dbdd50b6a47506ba295fdb99d1b4a4.css +0 -1
  45. package/_site/css/websites-19b822a8683767833f7656ff2df318ba13bed49f33d9bfc946ff619cf986b449.css +0 -1
  46. package/_site/css/websites-4b9b2b0a0a907b33eca109d50c2a3eee97f1955c742cef487110d21a0bbf8f50.css +0 -1
  47. package/_site/css/websites-7843a323acc9c4921d373250c5f647854910ea0913530659823fbe29844ee6b5.css +0 -1
  48. package/_site/css/websites-931c1ae713658c5aca51e25f5c43a79611c9a762dc38b989cc99ad4dbd4eda8e.css +0 -1
  49. package/_site/css/websites-c678e6ed7f508aa4b0e9218070d7235ed56f0eaea49350b30b849f4ef176cfb0.css +0 -1
  50. package/_site/css/websites-e6d63d0512032a2909a7f648cecfad311842ecba7bd03f94afc8639998e606f0.css +0 -1
  51. package/_site/websites/index.html +0 -360
  52. package/templates/connectors.html +0 -278
  53. package/templates/css/connectors-1350f25629f7addac6970f5297f8aef050ba1c207b28c8a0e4a7b113246d69dc.css +0 -1
  54. package/templates/css/connectors-152b01dfacf0586ce2f3fb0ddbbf5a5bbcd181a48b4b20b275f837786b2889cd.css +0 -1
  55. package/templates/css/connectors-306649a254eff69e10db2fa5368e173e88e24449db25d65b7117ebfd5dbbcd31.css +0 -1
  56. package/templates/css/connectors-66fe82f10a7a257f59212e18bb384b019ffb12192fa3f748951da7d4428733b4.css +0 -1
  57. package/templates/css/connectors-748797d88cc32c0787052966e54e4f794629b8460d24f2aadac83c13899574fb.css +0 -1
  58. package/templates/css/connectors-87cf0f4b873d104a845dcafd5d80d4f8d482f8cea15f2412cd9655071dcb9ea8.css +0 -1
  59. package/templates/css/connectors-885e03b1611113e2363f7a86fba9aee867130a14836b3cc9c4714265708c1704.css +0 -1
  60. package/templates/css/connectors-9d0c9569a2ce8afaa4a30a0534b3d0597c761c7e0e5280dcbc966dc2b709e98e.css +0 -1
  61. package/templates/css/connectors-a8ae93c40b7c738a4514e47046a029afdcf74bb28a7724287e1350afac4fa60f.css +0 -1
  62. package/templates/css/connectors-c1d5f4c520a9a86d99b7201d30a8fe25a5779b7b2373b73262868f446fe3f855.css +0 -1
  63. package/templates/css/connectors-febcafaf261c0f23ca11fc13809f259c74dbdd50b6a47506ba295fdb99d1b4a4.css +0 -1
  64. package/templates/css/websites-19b822a8683767833f7656ff2df318ba13bed49f33d9bfc946ff619cf986b449.css +0 -1
  65. package/templates/css/websites-4b9b2b0a0a907b33eca109d50c2a3eee97f1955c742cef487110d21a0bbf8f50.css +0 -1
  66. package/templates/css/websites-7843a323acc9c4921d373250c5f647854910ea0913530659823fbe29844ee6b5.css +0 -1
  67. package/templates/css/websites-931c1ae713658c5aca51e25f5c43a79611c9a762dc38b989cc99ad4dbd4eda8e.css +0 -1
  68. package/templates/css/websites-c678e6ed7f508aa4b0e9218070d7235ed56f0eaea49350b30b849f4ef176cfb0.css +0 -1
  69. package/templates/css/websites-e6d63d0512032a2909a7f648cecfad311842ecba7bd03f94afc8639998e606f0.css +0 -1
  70. package/templates/websites.html +0 -360
@@ -0,0 +1,1410 @@
1
+ # Silex Dashboard Architecture - Complete Analysis
2
+
3
+ > **Last Updated**: December 27, 2025
4
+ > **Silex Version**: 3.6.0-canary.1
5
+ > **Purpose**: Reference documentation for understanding and extending the dashboard
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ - [Quick Start](#quick-start)
12
+ - [Running Services](#running-services)
13
+ - [Project Structure](#project-structure)
14
+ - [Complete Data Flow](#complete-data-flow)
15
+ - [Key Integration Points](#key-integration-points)
16
+ - [Template System](#template-system)
17
+ - [Special Techniques](#special-techniques)
18
+ - [Implementation Guide](#implementation-guide)
19
+ - [Template/Fork Feature Design](#templatefork-feature-design)
20
+
21
+ ---
22
+
23
+ ## Quick Start
24
+
25
+ ### Starting the Dashboard
26
+
27
+ ```bash
28
+ cd /home/lexoyo/_/Silex/packages/silex-dashboard
29
+
30
+ # Start all services (TinaCMS, Silex, 11ty)
31
+ npm start
32
+
33
+ # OR start individually:
34
+ npm run tina:dev # TinaCMS on :4001
35
+ npm run silex:dev # Silex on :6805
36
+ npm run 11ty:dev # 11ty on :8080
37
+ ```
38
+
39
+ ### Accessing Services
40
+
41
+ - **Silex Editor**: http://localhost:6805/?id=dashboard
42
+ - **TinaCMS Admin**: http://localhost:4001/admin
43
+ - **11ty Preview**: http://localhost:8080/en/
44
+ - **GraphQL Playground**: http://localhost:4001/graphql
45
+
46
+ ---
47
+
48
+ ## Running Services
49
+
50
+ ### Service Architecture
51
+
52
+ ```
53
+ ┌─────────────────┐
54
+ │ TinaCMS │ Port 4001
55
+ │ GraphQL API │ (Content Management)
56
+ └────────┬────────┘
57
+
58
+ ├─────────────────────────────────┐
59
+ │ │
60
+ ▼ ▼
61
+ ┌─────────────────┐ ┌─────────────────┐
62
+ │ 11ty │ Port 8080 │ Silex │ Port 6805
63
+ │ Static Gen │ (Preview) │ Editor │ (Design)
64
+ └─────────────────┘ └─────────────────┘
65
+ │ │
66
+ │ │
67
+ ▼ ▼
68
+ ┌─────────────────────────────────────────────────┐
69
+ │ _site/ (Static Output) │
70
+ │ - /en/index.html - /fr/index.html │
71
+ │ - /en/connectors/ - /fr/connectors/ │
72
+ └─────────────────────────────────────────────────┘
73
+ ```
74
+
75
+ ### Service Responsibilities
76
+
77
+ | Service | Port | Purpose | Technologies |
78
+ |---------|------|---------|-------------|
79
+ | **TinaCMS** | 4001 | Content management, GraphQL API | TinaCMS, GraphQL |
80
+ | **Silex** | 6805 | Visual editor, design, page settings | GrapesJS, Express |
81
+ | **11ty** | 8080 | Static site generation, templating | Eleventy, Liquid |
82
+
83
+ ---
84
+
85
+ ## Project Structure
86
+
87
+ ### File Organization
88
+
89
+ ```
90
+ silex-dashboard/
91
+ ├── silex/ # Silex editor data
92
+ │ ├── websites/
93
+ │ │ └── dashboard/
94
+ │ │ ├── website.json # ★ Pages, components, styles (2 pages, 133 styles)
95
+ │ │ └── meta.json # Site metadata {"name":"Dashboard"}
96
+ │ ├── server-config.js # Express middleware (locale, routing)
97
+ │ └── client-config.js # CMS datasource config
98
+
99
+ ├── templates/ # 11ty input (Silex output)
100
+ │ ├── websites.html # Base template (Silex-generated)
101
+ │ ├── websites-en.html # ★ 11ty template with frontmatter (EN)
102
+ │ ├── websites-fr.html # ★ 11ty template with frontmatter (FR)
103
+ │ ├── connectors.html # Base template
104
+ │ ├── connectors-en.html # ★ 11ty template (EN)
105
+ │ ├── connectors-fr.html # ★ 11ty template (FR)
106
+ │ ├── css/ # Silex-generated CSS
107
+ │ └── assets/ # Images, fonts, favicon
108
+
109
+ ├── 11ty/ # 11ty configuration
110
+ │ ├── eleventy.config.mjs # Main config (i18n, passthroughs)
111
+ │ ├── _data/
112
+ │ │ ├── api-translations.json # Error messages, UI strings
113
+ │ │ └── site.js # Global site data
114
+ │ └── _includes/
115
+ │ └── alternate.liquid # Hreflang tags for SEO
116
+
117
+ ├── collections/ # ★ TinaCMS content
118
+ │ ├── home/
119
+ │ │ ├── en.md # English UI labels (title, buttons, etc.)
120
+ │ │ └── fr.md # French UI labels
121
+ │ ├── connectors/
122
+ │ │ ├── en.md # Connectors page labels
123
+ │ │ └── fr.md
124
+ │ ├── languages/
125
+ │ │ ├── en.json # Language config {"code":"en","label":"English"}
126
+ │ │ └── fr.json
127
+ │ └── settings/
128
+ │ ├── en.json # Nav, footer links
129
+ │ └── fr.json
130
+
131
+ ├── tina/ # TinaCMS config
132
+ │ ├── config.ts # ★ Schema definition (4 collections)
133
+ │ └── __generated__/ # Auto-generated GraphQL schema
134
+
135
+ └── _site/ # 11ty output (gitignored)
136
+ ├── en/
137
+ │ ├── index.html # Final websites page (EN)
138
+ │ └── connectors/
139
+ │ └── index.html # Final connectors page (EN)
140
+ ├── fr/
141
+ │ ├── index.html # Final websites page (FR)
142
+ │ └── connectors/
143
+ │ └── index.html # Final connectors page (FR)
144
+ ├── css/ # Copied CSS
145
+ ├── js/ # Vue.js runtime
146
+ └── assets/ # Copied assets
147
+ ```
148
+
149
+ ### Page Structure
150
+
151
+ **2 Pages in Silex** (defined in `website.json`):
152
+
153
+ | Page ID | Name | Purpose | Permalink |
154
+ |---------|------|---------|-----------|
155
+ | `mk3OKgfr4A9V7Dww` | Websites | Main dashboard (list user's websites) | `/{{lang}}/` |
156
+ | `BOCWuSXKn6FRo8x5L` | Connectors | Login/authentication page | `/{{lang}}/connectors/` |
157
+
158
+ **Page Multiplication for i18n**:
159
+ - Each Silex page → 2 language versions (EN, FR)
160
+ - Total: **4 final HTML files**
161
+
162
+ ---
163
+
164
+ ## Complete Data Flow
165
+
166
+ ### 1. Design Phase (Silex)
167
+
168
+ ```
169
+ Designer opens: http://localhost:6805/?id=dashboard
170
+
171
+ Designs pages visually:
172
+ - Drag & drop components
173
+ - Apply CSS styles
174
+ - Add Vue.js directives (v-if, v-for, v-on:click)
175
+
176
+ Configures page settings:
177
+ - General: Page name = "Websites"
178
+ - CMS: Permalink = /{{lang}}/
179
+ - CMS: Language list = "en,fr"
180
+ - Code: <head> content = Vue.js app
181
+ - SEO: Title expression (GraphQL query)
182
+
183
+ Clicks "Publish"
184
+
185
+ Silex generates templates/websites.html
186
+
187
+ Saves to silex/websites/dashboard/website.json
188
+ ```
189
+
190
+ **website.json structure**:
191
+ ```json
192
+ {
193
+ "pages": [
194
+ {
195
+ "id": "mk3OKgfr4A9V7Dww",
196
+ "name": "Websites",
197
+ "settings": {
198
+ "silexLanguagesList": "en,fr",
199
+ "eleventyPermalink": "[{...}]",
200
+ "head": "/* Vue.js code */"
201
+ }
202
+ }
203
+ ],
204
+ "styles": [ /* 133 CSS rules */ ],
205
+ "components": [],
206
+ "assets": [ /* images, fonts */ ]
207
+ }
208
+ ```
209
+
210
+ ### 2. Content Phase (TinaCMS)
211
+
212
+ ```
213
+ Content editor opens: http://localhost:4001/admin
214
+
215
+ Edits collections:
216
+ - home/en.md: title, subtitle, button labels
217
+ - home/fr.md: titre, sous-titre, étiquettes
218
+ - settings/en.json: navigation, footer links
219
+
220
+ Saves changes
221
+
222
+ TinaCMS writes to collections/ directory
223
+
224
+ TinaCMS GraphQL API auto-updates
225
+ ```
226
+
227
+ **TinaCMS Schema** (`tina/config.ts`):
228
+ ```typescript
229
+ {
230
+ collections: [
231
+ {
232
+ name: "home",
233
+ path: "collections/home",
234
+ fields: [
235
+ { label: 'Title', name: 'title', type: 'string' },
236
+ { label: 'Subtitle', name: 'subtitle', type: 'string' },
237
+ { label: 'Add Button', name: 'add_button', type: 'string' },
238
+ // ... 20+ fields
239
+ ]
240
+ },
241
+ // ... 3 more collections (connectors, languages, settings)
242
+ ]
243
+ }
244
+ ```
245
+
246
+ ### 3. Build Phase (11ty)
247
+
248
+ ```
249
+ 11ty watches templates/ directory
250
+
251
+ Detects websites.html changed
252
+
253
+ For each language (en, fr):
254
+
255
+ 1. Creates template file: websites-en.html
256
+ - Adds frontmatter (permalink, lang, collection)
257
+ - Keeps all HTML/CSS/JS from Silex
258
+
259
+ 2. Executes websites-en.11tydata.mjs:
260
+ - Fetches TinaCMS GraphQL (homeConnection, settingsConnection)
261
+ - Returns { tina: { ... } }
262
+
263
+ 3. Processes Liquid tags in HTML:
264
+ {% assign var = tina.homeConnection.edges
265
+ | where: "node.lang", "en"
266
+ | first %}
267
+ {{ var.node.title }}
268
+
269
+ 4. Outputs _site/en/index.html
270
+
271
+ Done! Static HTML ready
272
+ ```
273
+
274
+ **11tydata.mjs example**:
275
+ ```javascript
276
+ export default async function (configData) {
277
+ const response = await fetch('http://localhost:4001/graphql', {
278
+ method: 'POST',
279
+ body: JSON.stringify({
280
+ query: `{
281
+ homeConnection {
282
+ edges {
283
+ node {
284
+ title
285
+ subtitle
286
+ add_button
287
+ lang
288
+ }
289
+ }
290
+ }
291
+ settingsConnection {
292
+ edges {
293
+ node {
294
+ nav { label, url, target }
295
+ lang
296
+ }
297
+ }
298
+ }
299
+ }`
300
+ })
301
+ })
302
+
303
+ const json = await response.json()
304
+ return {
305
+ tina: json.data,
306
+ lang: 'en'
307
+ }
308
+ }
309
+ ```
310
+
311
+ ### 4. Runtime Phase (Vue.js)
312
+
313
+ ```
314
+ User visits: http://localhost:8080/en/
315
+
316
+ Browser loads HTML with embedded Vue.js in <head>
317
+
318
+ Vue app initializes on window.load:
319
+
320
+ 1. const { api } = silex
321
+ 2. const user = await api.getUser()
322
+ 3. if (user) {
323
+ this.websites = await api.websiteList()
324
+ } else {
325
+ redirect to /en/connectors/
326
+ }
327
+
328
+ Vue reactivity kicks in:
329
+ - v-if="!empty" shows/hides sections
330
+ - v-for="website in websites" renders list
331
+ - v-on:click="createWebsite" handles clicks
332
+
333
+ User sees fully interactive dashboard!
334
+ ```
335
+
336
+ **Vue.js app structure** (in page settings `head`):
337
+ ```javascript
338
+ const App = {
339
+ data() {
340
+ return {
341
+ websites: [], // From Silex API
342
+ user: null, // From Silex API
343
+ loading: true, // UI state
344
+ error: null, // Error messages
345
+ showCreationForm: false // UI state
346
+ }
347
+ },
348
+
349
+ async mounted() {
350
+ const user = await getUser({ type: ConnectorType.STORAGE })
351
+ if (user) {
352
+ this.user = user
353
+ this.websites = await websiteList({
354
+ connectorId: user.storage.connectorId
355
+ })
356
+ }
357
+ },
358
+
359
+ methods: {
360
+ async createWebsite() {
361
+ await websiteCreate({
362
+ websiteId: toSafeId(this.newWebsiteName),
363
+ data: { name: this.newWebsiteName }
364
+ })
365
+ this.websites = await websiteList()
366
+ },
367
+
368
+ async deleteWebsite(websiteId) {
369
+ if (confirm('Are you sure?')) {
370
+ await websiteDelete({ websiteId })
371
+ this.websites = await websiteList()
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ createApp(App).mount('.app')
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Key Integration Points
383
+
384
+ ### A. Silex Page Settings → 11ty Template
385
+
386
+ **How Language Multiplication Works**:
387
+
388
+ 1. **Silex Page Settings**:
389
+ ```javascript
390
+ {
391
+ name: 'Websites',
392
+ silexLanguagesList: 'en,fr', // ← Triggers multiplication
393
+ eleventyPermalink: '[{
394
+ "type":"property",
395
+ "fieldId":"fixed",
396
+ "options":{"value":"/{{lang}}/"}
397
+ }]'
398
+ }
399
+ ```
400
+
401
+ 2. **Silex Publishes** → `templates/websites.html` (base template, no frontmatter)
402
+
403
+ 3. **11ty Detects** `silexLanguagesList` and creates:
404
+ - `templates/websites-en.html`
405
+ - `templates/websites-fr.html`
406
+
407
+ 4. **Each Gets Frontmatter**:
408
+ ```yaml
409
+ ---
410
+ permalink: "/{{lang}}/" # From eleventyPermalink expression
411
+ lang: "en" # Added by language multiplication
412
+ collection: "Websites" # From page name
413
+ ---
414
+ ```
415
+
416
+ 5. **11ty Processes** each template independently with its own language context
417
+
418
+ ### B. TinaCMS → Liquid Templates
419
+
420
+ **Expression System**:
421
+
422
+ Silex uses a JSON-based expression builder that converts to Liquid syntax.
423
+
424
+ **Example Expression** (in page settings SEO Title):
425
+ ```json
426
+ [
427
+ {"type":"property","fieldId":"homeConnection"},
428
+ {"type":"property","fieldId":"edges"},
429
+ {"type":"filter","id":"where","options":{"key":"node.lang","value":"en"}},
430
+ {"type":"filter","id":"first"},
431
+ {"type":"property","fieldId":"node"},
432
+ {"type":"property","fieldId":"title"}
433
+ ]
434
+ ```
435
+
436
+ **Converts to Liquid**:
437
+ ```liquid
438
+ {% assign var_title = tina.homeConnection.edges
439
+ | where: "node.lang", page.lang
440
+ | first %}
441
+ {{ var_title.node.title }}
442
+ ```
443
+
444
+ **Used in HTML**:
445
+ ```html
446
+ <h1>
447
+ {% assign var = tina.homeConnection.edges
448
+ | where: "node.lang", page.lang
449
+ | first %}
450
+ {{ var.node.title }}
451
+ </h1>
452
+ <!-- Outputs: <h1>Silex Dashboard</h1> -->
453
+ ```
454
+
455
+ ### C. Vue.js Directives in Silex HTML
456
+
457
+ **How Vue.js is Embedded**:
458
+
459
+ 1. **In Page Settings** (Code tab):
460
+ ```html
461
+ <script src="/js/vue.global.prod.js"></script>
462
+ <script src="/js/main.js"></script>
463
+ <script type="module">
464
+ const App = { /* Vue app definition */ }
465
+ createApp(App).mount('.app')
466
+ </script>
467
+ ```
468
+
469
+ 2. **In Silex Visual Editor**, add Vue directives to HTML elements:
470
+ ```html
471
+ <div v-if="!empty" class="section">
472
+ <h1 v-text="title"></h1>
473
+ <button v-on:click="createWebsite">Create</button>
474
+ </div>
475
+
476
+ <div v-for="(website, index) in websites" :key="index">
477
+ <h3 v-text="website.name"></h3>
478
+ <p v-text="'Updated ' + new Date(website.updatedAt).toLocaleDateString()"></p>
479
+ </div>
480
+ ```
481
+
482
+ 3. **11ty Preserves** all Vue directives (they're just HTML attributes)
483
+
484
+ 4. **Browser Executes** Vue.js at runtime, making the page interactive
485
+
486
+ ### D. Dual Content Sources
487
+
488
+ **Static Content** (from TinaCMS via Liquid):
489
+ ```html
490
+ <button>
491
+ {% assign var = tina.homeConnection.edges | where: "node.lang", "en" | first %}
492
+ {{ var.node.add_button }}
493
+ </button>
494
+ <!-- Outputs: <button>Create website</button> -->
495
+ ```
496
+
497
+ **Dynamic Content** (from Vue.js):
498
+ ```html
499
+ <div v-for="website in websites">
500
+ <h3 v-text="website.name"></h3>
501
+ <!-- Outputs: <h3>My Project</h3> (from Silex API at runtime) -->
502
+ </div>
503
+ ```
504
+
505
+ **Why Both?**
506
+ - **TinaCMS**: UI labels, navigation, footer (changes rarely, SEO-friendly, static)
507
+ - **Vue.js**: User data, forms, API calls (changes frequently, requires authentication)
508
+
509
+ ---
510
+
511
+ ## Template System
512
+
513
+ ### Template Multiplication Process
514
+
515
+ ```
516
+ ┌─────────────────────────────────────────────────────────┐
517
+ │ Silex Editor (1 page) │
518
+ │ - name: "Websites" │
519
+ │ - silexLanguagesList: "en,fr" │
520
+ └────────────────────┬────────────────────────────────────┘
521
+ │ Publish
522
+
523
+ ┌─────────────────────────────────────────────────────────┐
524
+ │ templates/websites.html (base) │
525
+ │ - No frontmatter │
526
+ │ - All HTML/CSS/JS from Silex │
527
+ └────────────────────┬────────────────────────────────────┘
528
+ │ 11ty detects silexLanguagesList
529
+
530
+ ┌───────────┴───────────┐
531
+ ▼ ▼
532
+ ┌────────────────┐ ┌────────────────┐
533
+ │ websites- │ │ websites- │
534
+ │ en.html │ │ fr.html │
535
+ ├────────────────┤ ├────────────────┤
536
+ │ --- │ │ --- │
537
+ │ permalink: │ │ permalink: │
538
+ │ "/en/" │ │ "/fr/" │
539
+ │ lang: "en" │ │ lang: "fr" │
540
+ │ --- │ │ --- │
541
+ │ │ │ │
542
+ │ (same HTML) │ │ (same HTML) │
543
+ └────────┬───────┘ └────────┬───────┘
544
+ │ │
545
+ │ 11ty + Liquid │
546
+ ▼ ▼
547
+ ┌────────────────┐ ┌────────────────┐
548
+ │ _site/en/ │ │ _site/fr/ │
549
+ │ index.html │ │ index.html │
550
+ │ │ │ │
551
+ │ (English │ │ (French │
552
+ │ content) │ │ content) │
553
+ └────────────────┘ └────────────────┘
554
+ ```
555
+
556
+ ### Content Resolution Flow
557
+
558
+ ```
559
+ Template has Liquid tag:
560
+ {{ tina.homeConnection.edges | where: "node.lang", page.lang | first | node.title }}
561
+
562
+
563
+
564
+ For websites-en.html: For websites-fr.html:
565
+ page.lang = "en" page.lang = "fr"
566
+ │ │
567
+ ▼ ▼
568
+ Filters to en.md content Filters to fr.md content
569
+ │ │
570
+ ▼ ▼
571
+ Outputs: "Silex Dashboard" Outputs: "Tableau de bord Silex"
572
+ ```
573
+
574
+ ### File Relationships
575
+
576
+ ```
577
+ Silex Editor (Design)
578
+ ↓ publishes
579
+ templates/websites.html ────┐
580
+ │ copied + frontmatter added
581
+ ├──→ templates/websites-en.html
582
+ └──→ templates/websites-fr.html
583
+ ↓ 11ty + TinaCMS data
584
+
585
+ ┌──→ _site/en/index.html
586
+ └──→ _site/fr/index.html
587
+ ```
588
+
589
+ ---
590
+
591
+ ## Special Techniques
592
+
593
+ ### 1. Loading State Management
594
+
595
+ **Problem**: Prevent flash of unstyled content before Vue.js loads
596
+
597
+ **Solution**: CSS-based progressive enhancement
598
+
599
+ ```css
600
+ /* In templates (added by Silex) */
601
+ .before-js > * {
602
+ visibility: hidden;
603
+ opacity: 0;
604
+ transition: opacity .5s ease;
605
+ }
606
+
607
+ .after-js > * {
608
+ visibility: visible;
609
+ opacity: 1;
610
+ }
611
+
612
+ .before-js:before {
613
+ content: 'Loading';
614
+ position: absolute;
615
+ top: 49%;
616
+ left: 49%;
617
+ }
618
+ ```
619
+
620
+ ```html
621
+ <!-- Body has before-js class initially -->
622
+ <body class="app before-js">
623
+ <!-- Content hidden until Vue loads -->
624
+ </body>
625
+ ```
626
+
627
+ ```javascript
628
+ // After Vue mounts
629
+ setTimeout(() => {
630
+ document.querySelector('.before-js').classList.add('after-js')
631
+ }, 100)
632
+ ```
633
+
634
+ ### 2. Skeleton Loaders
635
+
636
+ **Problem**: Better UX while data is loading
637
+
638
+ **Solution**: Show placeholder skeletons with CSS animation
639
+
640
+ ```html
641
+ <!-- Loading state -->
642
+ <div v-if="loading" class="skeleton-anim">
643
+ <h3 class="skeleton-text skeleton">Loading...</h3>
644
+ <p class="skeleton-text skeleton">Loading...</p>
645
+ </div>
646
+
647
+ <!-- Loaded state -->
648
+ <div v-if="!loading" v-for="website in websites">
649
+ <h3 v-text="website.name"></h3>
650
+ <p v-text="website.updatedAt"></p>
651
+ </div>
652
+ ```
653
+
654
+ ```css
655
+ .skeleton-anim:after {
656
+ content: "";
657
+ position: absolute;
658
+ background: linear-gradient(
659
+ 0.25turn,
660
+ transparent,
661
+ rgba(255,255,255,.75),
662
+ transparent
663
+ );
664
+ animation: loading 1.5s infinite;
665
+ }
666
+
667
+ @keyframes loading {
668
+ to { background-position: 200% 0; }
669
+ }
670
+ ```
671
+
672
+ ### 3. Expression-Based Data Binding
673
+
674
+ **Visual Builder for GraphQL Queries**:
675
+
676
+ Instead of writing Liquid manually, Silex provides a dropdown UI:
677
+
678
+ ```
679
+ Field Selector:
680
+ ┌─────────────────────────────┐
681
+ │ homeConnection [▼] │ ← Dropdown shows all GraphQL fields
682
+ └─────────────────────────────┘
683
+
684
+ ┌─────────────────────────────┐
685
+ │ edges [▼] │
686
+ └─────────────────────────────┘
687
+
688
+ Filter:
689
+ ┌─────────────────────────────┐
690
+ │ where [▼] │
691
+ │ key: node.lang │
692
+ │ value: en │
693
+ └─────────────────────────────┘
694
+
695
+ ┌─────────────────────────────┐
696
+ │ first [▼] │
697
+ └─────────────────────────────┘
698
+
699
+ ┌─────────────────────────────┐
700
+ │ node.title [▼] │
701
+ └─────────────────────────────┘
702
+ ```
703
+
704
+ **Converts to**:
705
+ ```liquid
706
+ {{ tina.homeConnection.edges | where: "node.lang", "en" | first | node.title }}
707
+ ```
708
+
709
+ **Stored in website.json as**:
710
+ ```json
711
+ {
712
+ "eleventySeoTitle": "[
713
+ {\"type\":\"property\",\"fieldId\":\"homeConnection\"},
714
+ {\"type\":\"property\",\"fieldId\":\"edges\"},
715
+ {\"type\":\"filter\",\"id\":\"where\",\"options\":{\"key\":\"node.lang\",\"value\":\"en\"}},
716
+ {\"type\":\"filter\",\"id\":\"first\"},
717
+ {\"type\":\"property\",\"fieldId\":\"node\"},
718
+ {\"type\":\"property\",\"fieldId\":\"title\"}
719
+ ]"
720
+ }
721
+ ```
722
+
723
+ ### 4. API Translations Pattern
724
+
725
+ **Problem**: Translate API error messages
726
+
727
+ **Solution**: Centralized translation file + Liquid interpolation
728
+
729
+ **File**: `11ty/_data/api-translations.json`
730
+ ```json
731
+ {
732
+ "en": {
733
+ "Failed to start dashboard": "Failed to start dashboard",
734
+ "Website created successfully": "Website created successfully"
735
+ },
736
+ "fr": {
737
+ "Failed to start dashboard": "Erreur, impossible de démarrer",
738
+ "Website created successfully": "Le site a bien été créé"
739
+ }
740
+ }
741
+ ```
742
+
743
+ **Usage in Vue.js code**:
744
+ ```javascript
745
+ this.error = `{{ api-translations[lang]["Failed to start dashboard"] }} - ${error.message}`
746
+ this.message = '{{ api-translations[lang]["Website created successfully"] }}'
747
+ ```
748
+
749
+ **After 11ty processing**:
750
+ ```javascript
751
+ // In en/index.html
752
+ this.error = `Failed to start dashboard - ${error.message}`
753
+
754
+ // In fr/index.html
755
+ this.error = `Erreur, impossible de démarrer - ${error.message}`
756
+ ```
757
+
758
+ ### 5. Locale Detection & Routing
759
+
760
+ **Server-side routing** (`silex/server-config.js`):
761
+
762
+ ```javascript
763
+ // Detect language from browser
764
+ router.use(locale(languages.map(l => l.code), 'en'))
765
+
766
+ // Redirect to user's language
767
+ router.use('/', (req, res, next) => {
768
+ if (req.path === '/' && !req.query.id) {
769
+ res.redirect(`/${req.locale}/`) // → /en/ or /fr/
770
+ } else {
771
+ next()
772
+ }
773
+ })
774
+ ```
775
+
776
+ **Client-side routing** (in Vue.js):
777
+ ```javascript
778
+ openLogin() {
779
+ const path = `/{{lang}}/connectors/`
780
+ window.open(path, '_self')
781
+ }
782
+
783
+ openEditor(id) {
784
+ window.open(`/?id=${id}&lang={{lang}}&connectorId=${this.user.storage.connectorId}`, '_self')
785
+ }
786
+ ```
787
+
788
+ ### 6. Hover Effects with CSS
789
+
790
+ **Interactive buttons** without JavaScript:
791
+
792
+ ```css
793
+ .fx-scale-round {
794
+ position: relative;
795
+ overflow: hidden;
796
+ transition: transform 0.2s cubic-bezier(0, -0.530, 0.405, 2.8);
797
+ }
798
+
799
+ .fx-scale-round::after {
800
+ content: "";
801
+ background: #ffffff;
802
+ position: absolute;
803
+ border-radius: 50%;
804
+ left: -50%;
805
+ right: -50%;
806
+ top: -100%;
807
+ bottom: -100%;
808
+ transform: scale(0);
809
+ transition: all 0.3s ease-out;
810
+ }
811
+
812
+ .fx-scale-round:hover {
813
+ transform: scale(1.1);
814
+ }
815
+
816
+ .fx-scale-round:hover::after {
817
+ transform: scale(1);
818
+ }
819
+ ```
820
+
821
+ ---
822
+
823
+ ## Implementation Guide
824
+
825
+ ### Adding a New Page
826
+
827
+ 1. **In Silex Editor**:
828
+ ```
829
+ - Click "+" in Pages panel
830
+ - Name: "Settings"
831
+ - Design the page visually
832
+ - Configure page settings:
833
+ - General: name = "Settings"
834
+ - CMS: permalink = /{{lang}}/settings/
835
+ - Code: Add Vue.js if needed
836
+ - Click "Publish"
837
+ ```
838
+
839
+ 2. **In TinaCMS** (if page needs content):
840
+ ```typescript
841
+ // tina/config.ts
842
+ {
843
+ name: "settings_content",
844
+ path: "collections/settings_content",
845
+ fields: [
846
+ { name: 'title', type: 'string' },
847
+ // ... more fields
848
+ ]
849
+ }
850
+ ```
851
+
852
+ 3. **Create data file**:
853
+ ```javascript
854
+ // templates/settings-en.11tydata.mjs
855
+ export default async function() {
856
+ const response = await fetch('http://localhost:4001/graphql', {
857
+ body: JSON.stringify({
858
+ query: `{ settingsContentConnection { ... } }`
859
+ })
860
+ })
861
+ return { tina: response.data }
862
+ }
863
+ ```
864
+
865
+ 4. **11ty auto-generates**:
866
+ - `_site/en/settings/index.html`
867
+ - `_site/fr/settings/index.html`
868
+
869
+ ### Adding a New TinaCMS Field
870
+
871
+ 1. **Update schema** (`tina/config.ts`):
872
+ ```typescript
873
+ {
874
+ name: "home",
875
+ fields: [
876
+ // ... existing fields
877
+ {
878
+ label: 'Welcome Message',
879
+ name: 'welcome_message',
880
+ type: 'string'
881
+ }
882
+ ]
883
+ }
884
+ ```
885
+
886
+ 2. **Restart TinaCMS**:
887
+ ```bash
888
+ npm run tina:dev
889
+ ```
890
+
891
+ 3. **Edit content** in TinaCMS admin:
892
+ - Open http://localhost:4001/admin
893
+ - Edit `home/en.md`
894
+ - Add value for "Welcome Message"
895
+
896
+ 4. **Use in template** (via expression builder or Liquid):
897
+ ```liquid
898
+ {{ tina.homeConnection.edges | where: "node.lang", "en" | first | node.welcome_message }}
899
+ ```
900
+
901
+ ### Adding a New Vue.js Method
902
+
903
+ 1. **Open page settings** in Silex (Code tab)
904
+
905
+ 2. **Add method**:
906
+ ```javascript
907
+ methods: {
908
+ // ... existing methods
909
+
910
+ async archiveWebsite(websiteId) {
911
+ this.loading = true
912
+ try {
913
+ await websiteArchive({ websiteId, connectorId: this.user.storage.connectorId })
914
+ this.websites = await websiteList({ connectorId: this.user.storage.connectorId })
915
+ this.message = 'Website archived successfully'
916
+ } catch (error) {
917
+ this.error = `Failed to archive website - ${error.message}`
918
+ }
919
+ this.loading = false
920
+ }
921
+ }
922
+ ```
923
+
924
+ 3. **Add button** in Silex visual editor:
925
+ ```html
926
+ <button v-on:click="archiveWebsite(website.websiteId)">
927
+ Archive
928
+ </button>
929
+ ```
930
+
931
+ 4. **Publish** and test
932
+
933
+ ### Customizing Styles
934
+
935
+ **Option 1: Visual Editor**
936
+ - Select element in Silex
937
+ - Use Style Manager panel (right side)
938
+ - Apply styles visually
939
+
940
+ **Option 2: CSS Classes**
941
+ - Add class in Silex: `my-custom-button`
942
+ - Define in page settings (Code tab):
943
+ ```html
944
+ <style>
945
+ .my-custom-button {
946
+ background: linear-gradient(45deg, #8873fe, #ff6b9d);
947
+ border-radius: 8px;
948
+ padding: 12px 24px;
949
+ }
950
+ </style>
951
+ ```
952
+
953
+ **Option 3: Global Styles**
954
+ - Edit `templates/websites.html` `<style>` section
955
+ - Changes apply to all language versions
956
+
957
+ ---
958
+
959
+ ## Template/Fork Feature Design
960
+
961
+ ### Use Cases
962
+
963
+ 1. **Designer wants to share a template**:
964
+ - "Export my dashboard design as a reusable template"
965
+ - "Let others start from my design"
966
+
967
+ 2. **User wants to clone a site**:
968
+ - "Create a copy of this dashboard for another project"
969
+ - "Start from a template instead of blank"
970
+
971
+ 3. **Agency wants to offer templates**:
972
+ - "Pre-built dashboards for clients"
973
+ - "Customizable starting points"
974
+
975
+ ### What is a "Template"?
976
+
977
+ A **complete, self-contained** Silex website package:
978
+
979
+ ```
980
+ template-dashboard/
981
+ ├── metadata.json # Template info (name, author, preview)
982
+ ├── website.json # Silex pages, styles, components
983
+ ├── collections/ # TinaCMS content structure
984
+ ├── tina-schema.json # TinaCMS schema (exported)
985
+ ├── assets/ # Images, fonts
986
+ └── config/
987
+ ├── client-config.js # CMS datasource config
988
+ ├── server-config.js # Server middleware (optional)
989
+ └── eleventy.config.mjs # 11ty config (optional)
990
+ ```
991
+
992
+ ### Template Metadata Format
993
+
994
+ ```json
995
+ {
996
+ "id": "dashboard-v1",
997
+ "name": "Dashboard Template",
998
+ "description": "Multi-language dashboard with user management",
999
+ "author": "Silex Labs",
1000
+ "version": "1.0.0",
1001
+ "preview": "preview.png",
1002
+ "tags": ["dashboard", "admin", "i18n", "vue"],
1003
+ "technologies": ["TinaCMS", "Vue.js", "11ty"],
1004
+ "languages": ["en", "fr"],
1005
+ "pages": 2,
1006
+ "features": [
1007
+ "User authentication",
1008
+ "Website CRUD",
1009
+ "Multi-language support",
1010
+ "Responsive design"
1011
+ ],
1012
+ "customization": {
1013
+ "colors": ["primary", "secondary", "background"],
1014
+ "texts": ["site_name", "tagline"],
1015
+ "logo": true
1016
+ }
1017
+ }
1018
+ ```
1019
+
1020
+ ### Implementation Options
1021
+
1022
+ #### Option A: Export/Import System
1023
+
1024
+ **Export Flow**:
1025
+ ```
1026
+ 1. User clicks "Export as Template" in Silex
1027
+ 2. Silex packages:
1028
+ - website.json (from current site)
1029
+ - collections/ (from disk)
1030
+ - tina/config.ts → tina-schema.json
1031
+ - assets/ (images, fonts)
1032
+ - metadata.json (user fills form)
1033
+ 3. Downloads template-dashboard.zip
1034
+ ```
1035
+
1036
+ **Import Flow**:
1037
+ ```
1038
+ 1. User clicks "Import Template" in Silex
1039
+ 2. Uploads template-dashboard.zip
1040
+ 3. Silex extracts and creates:
1041
+ - New website ID
1042
+ - Copies website.json to silex/websites/{newId}/
1043
+ - Copies collections/ to project
1044
+ - Sets up TinaCMS schema
1045
+ - Imports assets
1046
+ 4. Opens new site in editor
1047
+ ```
1048
+
1049
+ **Implementation**:
1050
+ ```javascript
1051
+ // In Silex server
1052
+ router.post('/api/template/export', async (req, res) => {
1053
+ const { websiteId } = req.body
1054
+
1055
+ // Read website.json
1056
+ const website = await readWebsite(websiteId)
1057
+
1058
+ // Package files
1059
+ const zip = new JSZip()
1060
+ zip.file('website.json', JSON.stringify(website))
1061
+ zip.file('metadata.json', JSON.stringify(req.body.metadata))
1062
+
1063
+ // Add collections
1064
+ const collections = await fs.readdir('collections')
1065
+ for (const col of collections) {
1066
+ const files = await fs.readdir(`collections/${col}`)
1067
+ for (const file of files) {
1068
+ const content = await fs.readFile(`collections/${col}/${file}`)
1069
+ zip.file(`collections/${col}/${file}`, content)
1070
+ }
1071
+ }
1072
+
1073
+ // Add assets
1074
+ const assets = website.assets
1075
+ for (const asset of assets) {
1076
+ const file = await fs.readFile(`templates/${asset.src}`)
1077
+ zip.file(`assets/${asset.src}`, file)
1078
+ }
1079
+
1080
+ // Generate zip
1081
+ const buffer = await zip.generateAsync({ type: 'nodebuffer' })
1082
+ res.setHeader('Content-Disposition', `attachment; filename=${websiteId}.zip`)
1083
+ res.send(buffer)
1084
+ })
1085
+
1086
+ router.post('/api/template/import', upload.single('template'), async (req, res) => {
1087
+ const zip = await JSZip.loadAsync(req.file.buffer)
1088
+
1089
+ // Extract metadata
1090
+ const metadata = JSON.parse(await zip.file('metadata.json').async('string'))
1091
+
1092
+ // Generate new website ID
1093
+ const newId = generateId()
1094
+
1095
+ // Extract website.json
1096
+ const website = JSON.parse(await zip.file('website.json').async('string'))
1097
+ await writeWebsite(newId, website)
1098
+
1099
+ // Extract collections
1100
+ const collectionFiles = Object.keys(zip.files).filter(f => f.startsWith('collections/'))
1101
+ for (const file of collectionFiles) {
1102
+ const content = await zip.file(file).async('string')
1103
+ await fs.writeFile(file, content)
1104
+ }
1105
+
1106
+ // Extract assets
1107
+ const assetFiles = Object.keys(zip.files).filter(f => f.startsWith('assets/'))
1108
+ for (const file of assetFiles) {
1109
+ const content = await zip.file(file).async('nodebuffer')
1110
+ await fs.writeFile(`templates/${file}`, content)
1111
+ }
1112
+
1113
+ res.json({ websiteId: newId, name: metadata.name })
1114
+ })
1115
+ ```
1116
+
1117
+ #### Option B: Template API (In-App)
1118
+
1119
+ **Template Registry**:
1120
+ ```javascript
1121
+ // In Silex server
1122
+ const TEMPLATES = [
1123
+ {
1124
+ id: 'dashboard',
1125
+ name: 'Dashboard',
1126
+ path: './templates/dashboard-template'
1127
+ },
1128
+ {
1129
+ id: 'portfolio',
1130
+ name: 'Portfolio',
1131
+ path: './templates/portfolio-template'
1132
+ }
1133
+ ]
1134
+
1135
+ router.get('/api/templates', (req, res) => {
1136
+ res.json(TEMPLATES.map(t => ({
1137
+ id: t.id,
1138
+ ...require(`${t.path}/metadata.json`)
1139
+ })))
1140
+ })
1141
+
1142
+ router.post('/api/website/create-from-template', async (req, res) => {
1143
+ const { templateId, websiteId, customizations } = req.body
1144
+
1145
+ const template = TEMPLATES.find(t => t.id === templateId)
1146
+
1147
+ // Load template
1148
+ const website = require(`${template.path}/website.json`)
1149
+
1150
+ // Apply customizations
1151
+ if (customizations.colors) {
1152
+ applyColorCustomizations(website, customizations.colors)
1153
+ }
1154
+ if (customizations.texts) {
1155
+ applyTextCustomizations(website, customizations.texts)
1156
+ }
1157
+
1158
+ // Save new website
1159
+ await writeWebsite(websiteId, website)
1160
+
1161
+ // Copy collections
1162
+ await copyDir(`${template.path}/collections`, 'collections')
1163
+
1164
+ res.json({ websiteId })
1165
+ })
1166
+ ```
1167
+
1168
+ **UI Flow**:
1169
+ ```
1170
+ 1. User clicks "New Website from Template"
1171
+ 2. Shows template gallery:
1172
+ ┌─────────────┬─────────────┬─────────────┐
1173
+ │ Dashboard │ Portfolio │ Blog │
1174
+ │ [Preview] │ [Preview] │ [Preview] │
1175
+ │ [Use] │ [Use] │ [Use] │
1176
+ └─────────────┴─────────────┴─────────────┘
1177
+ 3. User clicks "Use Dashboard"
1178
+ 4. Customization dialog:
1179
+ - Site name: [My Dashboard____]
1180
+ - Primary color: [#8873fe]
1181
+ - Logo: [Upload___]
1182
+ 5. Creates website, opens in editor
1183
+ ```
1184
+
1185
+ #### Option C: Fork/Duplicate Feature
1186
+
1187
+ **Simple duplication** (already possible in dashboard):
1188
+
1189
+ ```javascript
1190
+ // In Vue.js app
1191
+ async duplicateWebsite(websiteId) {
1192
+ await websiteDuplicate({
1193
+ websiteId,
1194
+ connectorId: this.user.storage.connectorId
1195
+ })
1196
+ this.websites = await websiteList()
1197
+ }
1198
+ ```
1199
+
1200
+ **Enhanced with customization**:
1201
+
1202
+ ```javascript
1203
+ async forkWebsite(websiteId) {
1204
+ // Show customization dialog
1205
+ const customizations = await showForkDialog()
1206
+
1207
+ // Duplicate
1208
+ const newId = await websiteDuplicate({ websiteId })
1209
+
1210
+ // Apply customizations via API
1211
+ await websiteCustomize({
1212
+ websiteId: newId,
1213
+ name: customizations.name,
1214
+ colors: customizations.colors,
1215
+ logo: customizations.logo
1216
+ })
1217
+
1218
+ // Open in editor
1219
+ window.open(`/?id=${newId}`, '_self')
1220
+ }
1221
+ ```
1222
+
1223
+ ### Customization System
1224
+
1225
+ **Define customizable fields**:
1226
+
1227
+ ```json
1228
+ // In template metadata
1229
+ {
1230
+ "customization": {
1231
+ "schema": [
1232
+ {
1233
+ "id": "primary_color",
1234
+ "label": "Primary Color",
1235
+ "type": "color",
1236
+ "default": "#8873fe",
1237
+ "affects": [".button--primary", ".nav__item--active"]
1238
+ },
1239
+ {
1240
+ "id": "site_name",
1241
+ "label": "Site Name",
1242
+ "type": "text",
1243
+ "default": "My Dashboard",
1244
+ "affects": ["collections/home/*/title"]
1245
+ },
1246
+ {
1247
+ "id": "logo",
1248
+ "label": "Logo",
1249
+ "type": "image",
1250
+ "default": "/assets/logo.svg",
1251
+ "affects": [".nav__logo"]
1252
+ }
1253
+ ]
1254
+ }
1255
+ }
1256
+ ```
1257
+
1258
+ **Apply customizations**:
1259
+
1260
+ ```javascript
1261
+ function applyCustomizations(website, customizations) {
1262
+ // Replace colors in styles
1263
+ website.styles.forEach(style => {
1264
+ if (style.style['background-color'] === '#8873fe') {
1265
+ style.style['background-color'] = customizations.primary_color
1266
+ }
1267
+ })
1268
+
1269
+ // Replace text in collections
1270
+ collections.home.forEach(file => {
1271
+ file.title = file.title.replace('My Dashboard', customizations.site_name)
1272
+ })
1273
+
1274
+ // Replace logo
1275
+ website.assets.forEach(asset => {
1276
+ if (asset.src === '/assets/logo.svg') {
1277
+ asset.src = customizations.logo
1278
+ }
1279
+ })
1280
+ }
1281
+ ```
1282
+
1283
+ ### Recommended Approach
1284
+
1285
+ **Phase 1: Simple Export/Import**
1286
+ - Export button in Silex dashboard
1287
+ - Downloads .zip with website.json + collections
1288
+ - Import uploads .zip, creates new site
1289
+ - No customization UI yet
1290
+
1291
+ **Phase 2: Template Gallery**
1292
+ - Built-in templates in Silex
1293
+ - "New from Template" button
1294
+ - Gallery UI with previews
1295
+ - One-click creation
1296
+
1297
+ **Phase 3: Customization**
1298
+ - Template metadata defines customizable fields
1299
+ - UI for color picker, text inputs, image upload
1300
+ - Live preview of customizations
1301
+ - Apply before creating site
1302
+
1303
+ **Phase 4: Marketplace**
1304
+ - Community templates
1305
+ - Rating/reviews
1306
+ - Search/filter
1307
+ - Premium templates
1308
+
1309
+ ### Technical Considerations
1310
+
1311
+ **Storage**:
1312
+ - Templates in filesystem: `templates/library/`
1313
+ - Or in database with version control
1314
+ - Or remote registry (npm-style)
1315
+
1316
+ **Versioning**:
1317
+ - Template version in metadata
1318
+ - Migration scripts for breaking changes
1319
+ - Compatibility checks
1320
+
1321
+ **Dependencies**:
1322
+ - TinaCMS schema changes
1323
+ - Required plugins
1324
+ - Node version requirements
1325
+
1326
+ **Security**:
1327
+ - Validate uploaded templates
1328
+ - Sanitize user inputs
1329
+ - Prevent code injection in Vue.js snippets
1330
+
1331
+ ---
1332
+
1333
+ ## Summary
1334
+
1335
+ The Silex Dashboard demonstrates a **triple-layer architecture**:
1336
+
1337
+ 1. **Design Layer** (Silex + GrapesJS):
1338
+ - Visual editing
1339
+ - Component structure
1340
+ - CSS styling
1341
+ - Page settings
1342
+
1343
+ 2. **Content Layer** (TinaCMS + 11ty):
1344
+ - Static content management
1345
+ - Multi-language support
1346
+ - GraphQL API
1347
+ - Static site generation
1348
+
1349
+ 3. **Application Layer** (Vue.js):
1350
+ - Runtime interactivity
1351
+ - API integration
1352
+ - State management
1353
+ - User interactions
1354
+
1355
+ **Key Benefits**:
1356
+ - **Designers** work visually in Silex
1357
+ - **Content editors** use TinaCMS admin
1358
+ - **Developers** add features via Vue.js
1359
+ - **End result**: Fast, static, SEO-friendly, interactive site
1360
+
1361
+ **For Template/Fork Features**:
1362
+ - Package: website.json + collections + assets
1363
+ - Export: Zip download or API
1364
+ - Import: Upload or template gallery
1365
+ - Customize: Colors, text, images via UI
1366
+
1367
+ ---
1368
+
1369
+ ## Quick Reference
1370
+
1371
+ ### Common Tasks
1372
+
1373
+ | Task | Files to Edit | How |
1374
+ |------|---------------|-----|
1375
+ | **Change page design** | Use Silex Editor | Visual drag & drop |
1376
+ | **Update UI labels** | `collections/home/*.md` | Edit in TinaCMS or text editor |
1377
+ | **Add navigation link** | `collections/settings/*.json` | Edit `nav` array |
1378
+ | **Add new page** | Silex Editor | Pages panel → "+" button |
1379
+ | **Modify Vue.js logic** | Silex page settings (Code tab) | Edit `<script>` in HEAD |
1380
+ | **Add TinaCMS field** | `tina/config.ts` | Add to `fields` array |
1381
+ | **Change permalink** | Silex page settings (CMS tab) | Edit "Permalink" expression |
1382
+ | **Customize styles** | Silex Style Manager | Visual or CSS |
1383
+
1384
+ ### File Locations
1385
+
1386
+ | Content | Path |
1387
+ |---------|------|
1388
+ | **Page structure** | `silex/websites/dashboard/website.json` |
1389
+ | **UI labels** | `collections/home/*.md` |
1390
+ | **Navigation** | `collections/settings/*.json` |
1391
+ | **TinaCMS schema** | `tina/config.ts` |
1392
+ | **11ty config** | `11ty/eleventy.config.mjs` |
1393
+ | **Templates** | `templates/*.html` |
1394
+ | **Final output** | `_site/` |
1395
+
1396
+ ### API Endpoints
1397
+
1398
+ | Endpoint | Purpose |
1399
+ |----------|---------|
1400
+ | `http://localhost:6805/?id=dashboard` | Silex editor |
1401
+ | `http://localhost:4001/admin` | TinaCMS admin |
1402
+ | `http://localhost:4001/graphql` | GraphQL API |
1403
+ | `http://localhost:8080/en/` | Preview (English) |
1404
+ | `http://localhost:8080/fr/` | Preview (French) |
1405
+
1406
+ ---
1407
+
1408
+ **Last Updated**: December 27, 2025
1409
+ **Maintainer**: Silex Team
1410
+ **Questions?**: https://docs.silex.me or https://community.silex.me