@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.
- package/DASHBOARD-ARCHITECTURE.md +1410 -0
- package/_site/css/connectors-e4ae24975f4df2e2bea5121337282d8f2201bb86dc8c041c9ef43c0c2d7a32a7.css +1 -0
- package/_site/css/fork-506e7c805e636be6225c35a11f5804b72cf41d7a6355aa64a5d7ddb7d66ea314.css +1 -0
- package/_site/css/fork-923d814802e46c2d0db0f71c5c488c616ca0016f340d8dae66b263ba7f1143bc.css +1 -0
- package/_site/css/index-38d10542f641eb789c11b589b95d0e6363e71978479fa5cddbec3da65774560c.css +1 -0
- package/_site/en/connectors/index.html +51 -26
- package/_site/en/fork/index.html +371 -0
- package/_site/en/index.html +30 -28
- package/_site/fr/connectors/index.html +52 -27
- package/_site/fr/fork/index.html +372 -0
- package/_site/fr/index.html +31 -29
- package/collections/fork/en.md +17 -0
- package/collections/fork/fr.md +17 -0
- package/package.json +2 -2
- package/silex/websites/default/meta.json +4 -0
- package/silex/websites/default/website.json +6 -0
- package/templates/connectors-en.html +66 -41
- package/templates/connectors-fr.html +66 -41
- package/templates/css/connectors-e4ae24975f4df2e2bea5121337282d8f2201bb86dc8c041c9ef43c0c2d7a32a7.css +1 -0
- package/templates/css/fork-506e7c805e636be6225c35a11f5804b72cf41d7a6355aa64a5d7ddb7d66ea314.css +1 -0
- package/templates/css/fork-923d814802e46c2d0db0f71c5c488c616ca0016f340d8dae66b263ba7f1143bc.css +1 -0
- package/templates/css/index-38d10542f641eb789c11b589b95d0e6363e71978479fa5cddbec3da65774560c.css +1 -0
- package/templates/fork-en.11tydata.mjs +117 -0
- package/templates/fork-en.html +412 -0
- package/templates/fork-fr.11tydata.mjs +117 -0
- package/templates/fork-fr.html +412 -0
- package/templates/{websites-en.11tydata.mjs → index-en.11tydata.mjs} +32 -32
- package/templates/{websites-en.html → index-en.html} +91 -89
- package/templates/{websites-fr.11tydata.mjs → index-fr.11tydata.mjs} +32 -32
- package/templates/{websites-fr.html → index-fr.html} +91 -89
- package/tina/config.ts +76 -0
- package/tina/tina-lock.json +1 -1
- package/_site/connectors/index.html +0 -278
- package/_site/css/connectors-1350f25629f7addac6970f5297f8aef050ba1c207b28c8a0e4a7b113246d69dc.css +0 -1
- package/_site/css/connectors-152b01dfacf0586ce2f3fb0ddbbf5a5bbcd181a48b4b20b275f837786b2889cd.css +0 -1
- package/_site/css/connectors-306649a254eff69e10db2fa5368e173e88e24449db25d65b7117ebfd5dbbcd31.css +0 -1
- package/_site/css/connectors-66fe82f10a7a257f59212e18bb384b019ffb12192fa3f748951da7d4428733b4.css +0 -1
- package/_site/css/connectors-748797d88cc32c0787052966e54e4f794629b8460d24f2aadac83c13899574fb.css +0 -1
- package/_site/css/connectors-87cf0f4b873d104a845dcafd5d80d4f8d482f8cea15f2412cd9655071dcb9ea8.css +0 -1
- package/_site/css/connectors-885e03b1611113e2363f7a86fba9aee867130a14836b3cc9c4714265708c1704.css +0 -1
- package/_site/css/connectors-9d0c9569a2ce8afaa4a30a0534b3d0597c761c7e0e5280dcbc966dc2b709e98e.css +0 -1
- package/_site/css/connectors-a8ae93c40b7c738a4514e47046a029afdcf74bb28a7724287e1350afac4fa60f.css +0 -1
- package/_site/css/connectors-c1d5f4c520a9a86d99b7201d30a8fe25a5779b7b2373b73262868f446fe3f855.css +0 -1
- package/_site/css/connectors-febcafaf261c0f23ca11fc13809f259c74dbdd50b6a47506ba295fdb99d1b4a4.css +0 -1
- package/_site/css/websites-19b822a8683767833f7656ff2df318ba13bed49f33d9bfc946ff619cf986b449.css +0 -1
- package/_site/css/websites-4b9b2b0a0a907b33eca109d50c2a3eee97f1955c742cef487110d21a0bbf8f50.css +0 -1
- package/_site/css/websites-7843a323acc9c4921d373250c5f647854910ea0913530659823fbe29844ee6b5.css +0 -1
- package/_site/css/websites-931c1ae713658c5aca51e25f5c43a79611c9a762dc38b989cc99ad4dbd4eda8e.css +0 -1
- package/_site/css/websites-c678e6ed7f508aa4b0e9218070d7235ed56f0eaea49350b30b849f4ef176cfb0.css +0 -1
- package/_site/css/websites-e6d63d0512032a2909a7f648cecfad311842ecba7bd03f94afc8639998e606f0.css +0 -1
- package/_site/websites/index.html +0 -360
- package/templates/connectors.html +0 -278
- package/templates/css/connectors-1350f25629f7addac6970f5297f8aef050ba1c207b28c8a0e4a7b113246d69dc.css +0 -1
- package/templates/css/connectors-152b01dfacf0586ce2f3fb0ddbbf5a5bbcd181a48b4b20b275f837786b2889cd.css +0 -1
- package/templates/css/connectors-306649a254eff69e10db2fa5368e173e88e24449db25d65b7117ebfd5dbbcd31.css +0 -1
- package/templates/css/connectors-66fe82f10a7a257f59212e18bb384b019ffb12192fa3f748951da7d4428733b4.css +0 -1
- package/templates/css/connectors-748797d88cc32c0787052966e54e4f794629b8460d24f2aadac83c13899574fb.css +0 -1
- package/templates/css/connectors-87cf0f4b873d104a845dcafd5d80d4f8d482f8cea15f2412cd9655071dcb9ea8.css +0 -1
- package/templates/css/connectors-885e03b1611113e2363f7a86fba9aee867130a14836b3cc9c4714265708c1704.css +0 -1
- package/templates/css/connectors-9d0c9569a2ce8afaa4a30a0534b3d0597c761c7e0e5280dcbc966dc2b709e98e.css +0 -1
- package/templates/css/connectors-a8ae93c40b7c738a4514e47046a029afdcf74bb28a7724287e1350afac4fa60f.css +0 -1
- package/templates/css/connectors-c1d5f4c520a9a86d99b7201d30a8fe25a5779b7b2373b73262868f446fe3f855.css +0 -1
- package/templates/css/connectors-febcafaf261c0f23ca11fc13809f259c74dbdd50b6a47506ba295fdb99d1b4a4.css +0 -1
- package/templates/css/websites-19b822a8683767833f7656ff2df318ba13bed49f33d9bfc946ff619cf986b449.css +0 -1
- package/templates/css/websites-4b9b2b0a0a907b33eca109d50c2a3eee97f1955c742cef487110d21a0bbf8f50.css +0 -1
- package/templates/css/websites-7843a323acc9c4921d373250c5f647854910ea0913530659823fbe29844ee6b5.css +0 -1
- package/templates/css/websites-931c1ae713658c5aca51e25f5c43a79611c9a762dc38b989cc99ad4dbd4eda8e.css +0 -1
- package/templates/css/websites-c678e6ed7f508aa4b0e9218070d7235ed56f0eaea49350b30b849f4ef176cfb0.css +0 -1
- package/templates/css/websites-e6d63d0512032a2909a7f648cecfad311842ecba7bd03f94afc8639998e606f0.css +0 -1
- 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
|