@reldens/cms 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -34
- package/admin/reldens-admin-client.css +28 -23
- package/admin/reldens-admin-client.js +7 -0
- package/admin/templates/edit.html +3 -1
- package/admin/templates/fields/view/textarea.html +1 -0
- package/admin/templates/sections/editForm/cms-pages.html +15 -0
- package/admin/templates/sections/viewForm/cms-pages.html +15 -0
- package/admin/templates/view.html +1 -0
- package/bin/reldens-cms-generate-entities.js +47 -3
- package/bin/reldens-cms.js +26 -8
- package/install/js/installer.js +5 -0
- package/install/success.html +1 -1
- package/lib/admin-manager.js +80 -34
- package/lib/cms-pages-route-manager.js +105 -0
- package/lib/frontend.js +53 -50
- package/lib/installer.js +41 -20
- package/lib/manager.js +48 -9
- package/lib/template-engine.js +310 -42
- package/lib/templates-list.js +9 -0
- package/migrations/default-blocks.sql +1 -1
- package/migrations/default-entity-access.sql +2 -2
- package/migrations/default-homepage.sql +24 -4
- package/migrations/install.sql +31 -35
- package/package.json +3 -3
- package/templates/index.js.dist +3 -3
- package/templates/js/scripts.js +5 -0
- package/templates/layouts/default.html +4 -4
- package/templates/page.html +14 -10
- package/templates/partials/footer.html +2 -23
- package/templates/partials/header.html +2 -35
package/README.md
CHANGED
|
@@ -18,8 +18,9 @@ A powerful, flexible Content Management System built with Node.js, featuring an
|
|
|
18
18
|
- **Entity-based URLs** (e.g., `/articles/123`)
|
|
19
19
|
- **Template fallback system** (domain → default → base)
|
|
20
20
|
- **Layout system** with body content layouts and page wrappers
|
|
21
|
-
- **Reusable content blocks** with
|
|
22
|
-
- **Collection rendering** with filtering and loop support
|
|
21
|
+
- **Reusable content blocks** with `<entity>` template functions
|
|
22
|
+
- **Collection rendering** with filtering, sorting, pagination, and loop support
|
|
23
|
+
- **Custom partial tags** with HTML-style syntax for complex partials
|
|
23
24
|
- **Entity access control** for public/private content
|
|
24
25
|
- **Static asset serving** with Express integration as default
|
|
25
26
|
- **Template engine** with Mustache integration as default
|
|
@@ -41,7 +42,7 @@ A powerful, flexible Content Management System built with Node.js, featuring an
|
|
|
41
42
|
- **Custom entity configuration** with validation rules
|
|
42
43
|
- **Translation support** for entity labels and properties
|
|
43
44
|
- **Content blocks management** via cms_blocks table
|
|
44
|
-
- **Entity access control** via
|
|
45
|
+
- **Entity access control** via entities_access table
|
|
45
46
|
|
|
46
47
|
### - Configuration & Architecture
|
|
47
48
|
- **Environment-based configuration** (.env file)
|
|
@@ -66,7 +67,7 @@ const cms = new Manager({
|
|
|
66
67
|
projectRoot: process.cwd(),
|
|
67
68
|
entityAccess: {
|
|
68
69
|
cmsPages: { public: true, operations: ['read'] },
|
|
69
|
-
|
|
70
|
+
articles: { public: true, operations: ['read'] },
|
|
70
71
|
users: { public: false }
|
|
71
72
|
}
|
|
72
73
|
});
|
|
@@ -128,43 +129,157 @@ const cms = new Manager({
|
|
|
128
129
|
## Enhanced Templating System
|
|
129
130
|
|
|
130
131
|
### Template Functions
|
|
131
|
-
Templates support dynamic content blocks, entity rendering, and collections:
|
|
132
|
+
Templates support dynamic content blocks, entity rendering, and collections with advanced query options:
|
|
132
133
|
|
|
133
134
|
**Single Entity Rendering:**
|
|
134
135
|
```html
|
|
135
136
|
<!-- Render by any identifier field (like 'name' for content blocks) -->
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
<entity name="cmsBlocks" field="name" value="header-main"/>
|
|
138
|
+
<entity name="cmsBlocks" field="name" value="sidebar-left"/>
|
|
138
139
|
|
|
139
140
|
<!-- Render by ID (default identifier) -->
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
<entity name="articles" id="123"/>
|
|
142
|
+
<entity name="cmsPages" id="1"/>
|
|
142
143
|
```
|
|
143
144
|
|
|
144
145
|
**Single Field Collections:**
|
|
145
146
|
```html
|
|
146
147
|
<!-- Extract and concatenate a single field from multiple records -->
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
<collection name="cmsBlocks" filters="{status: 'active', category: 'navigation'}" field="content"/>
|
|
149
|
+
<collection name="articles" filters="{featured: true}" field="title"/>
|
|
150
|
+
|
|
151
|
+
<!-- With query options for sorting and limiting -->
|
|
152
|
+
<collection name="articles" filters="{featured: true}" field="title" data="{limit: 5, sortBy: 'created_at', sortDirection: 'desc'}"/>
|
|
153
|
+
<collection name="cmsBlocks" filters="{status: 'active'}" field="content" data="{limit: 3, offset: 10, sortBy: 'priority'}"/>
|
|
149
154
|
```
|
|
150
155
|
|
|
151
156
|
**Loop Collections:**
|
|
152
157
|
```html
|
|
153
158
|
<!-- Loop through records with full template rendering -->
|
|
154
|
-
|
|
159
|
+
<collection name="cmsBlocks" filters="{status: 'active'}">
|
|
155
160
|
<div class="block">
|
|
156
161
|
<h3>{{row.title}}</h3>
|
|
157
162
|
<div class="content">{{row.content}}</div>
|
|
158
163
|
</div>
|
|
159
|
-
|
|
164
|
+
</collection>
|
|
165
|
+
|
|
166
|
+
<collection name="articles" filters="{category: 'technology'}">
|
|
167
|
+
<div class="article">
|
|
168
|
+
<h4>{{row.title}}</h4>
|
|
169
|
+
<p>{{row.summary}}</p>
|
|
170
|
+
<img src="{{row.featured_image}}" alt="{{row.title}}">
|
|
171
|
+
</div>
|
|
172
|
+
</collection>
|
|
160
173
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
<
|
|
165
|
-
<
|
|
174
|
+
<!-- With pagination and sorting -->
|
|
175
|
+
<collection name="articles" filters="{featured: true}" data="{limit: 10, offset: 0, sortBy: 'created_at', sortDirection: 'asc'}">
|
|
176
|
+
<div class="article-card">
|
|
177
|
+
<h4>{{row.title}}</h4>
|
|
178
|
+
<p>{{row.summary}}</p>
|
|
166
179
|
</div>
|
|
167
|
-
|
|
180
|
+
</collection>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Custom Partials with Variables:**
|
|
184
|
+
|
|
185
|
+
*New HTML-style partial tags:*
|
|
186
|
+
```html
|
|
187
|
+
<!-- Clean HTML-style syntax for complex partials -->
|
|
188
|
+
<partial name="hero"
|
|
189
|
+
sectionStyle=" bg-black"
|
|
190
|
+
bigTextHtml="A free open-source platform to create multiplayer games!"
|
|
191
|
+
mediumTextHtml="Build with Node.js, MySQL, Colyseus, and Phaser 3"
|
|
192
|
+
htmlContentWrapper='<div class="d-lg-flex"><a href="/documentation" target="_blank" class="btn-get-started">Get Started!</a><a href="https://demo.reldens.com/" target="_blank" class="btn-watch-video"> Demo </a></div>'
|
|
193
|
+
imageUrl="/assets/web/reldens-check.png"
|
|
194
|
+
imageAlt="Reldens - MMORPG Platform" />
|
|
195
|
+
|
|
196
|
+
<!-- Self-closing and open/close syntax both supported -->
|
|
197
|
+
<partial name="productCard"
|
|
198
|
+
title="Premium Package"
|
|
199
|
+
price="$99"
|
|
200
|
+
highlighted="true">
|
|
201
|
+
</partial>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
*Traditional Mustache syntax is still supported:*
|
|
205
|
+
```html
|
|
206
|
+
<!-- Call a partial with an inline JSON object -->
|
|
207
|
+
{{>hero -{
|
|
208
|
+
bigTextHtml: "A free open-source platform to create multiplayer games!",
|
|
209
|
+
mediumTextHtml: "Build with Node.js, MySQL, Colyseus, and Phaser 3",
|
|
210
|
+
imageUrl: "https://example.com/hero.jpg",
|
|
211
|
+
ctaText: "Get Started",
|
|
212
|
+
ctaLink: "/documentation"
|
|
213
|
+
}-}}
|
|
214
|
+
|
|
215
|
+
<!-- Call a partial within collections using row data -->
|
|
216
|
+
<collection name="cmsPages" filters="{featured: true}" data="{limit: 3}">
|
|
217
|
+
{{>cardView -{row}-}}
|
|
218
|
+
</collection>
|
|
219
|
+
|
|
220
|
+
<!-- Call a partial with mixed data -->
|
|
221
|
+
{{>productCard -{
|
|
222
|
+
title: "Premium Package",
|
|
223
|
+
price: "$99",
|
|
224
|
+
features: ["Advanced Analytics", "Priority Support", "Custom Themes"],
|
|
225
|
+
highlighted: true
|
|
226
|
+
}-}}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Example Partial Templates:**
|
|
230
|
+
|
|
231
|
+
**partials/hero.mustache:**
|
|
232
|
+
```html
|
|
233
|
+
<section class="hero{{#sectionStyle}}{{sectionStyle}}{{/sectionStyle}}">
|
|
234
|
+
<div class="hero-content">
|
|
235
|
+
<h1>{{&bigTextHtml}}</h1>
|
|
236
|
+
<p>{{&mediumTextHtml}}</p>
|
|
237
|
+
{{#htmlContentWrapper}}
|
|
238
|
+
{{&htmlContentWrapper}}
|
|
239
|
+
{{/htmlContentWrapper}}
|
|
240
|
+
{{#imageUrl}}
|
|
241
|
+
<img src="{{imageUrl}}" alt="{{imageAlt}}" class="hero-image">
|
|
242
|
+
{{/imageUrl}}
|
|
243
|
+
</div>
|
|
244
|
+
</section>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**partials/cardView.mustache:**
|
|
248
|
+
```html
|
|
249
|
+
<div class="card">
|
|
250
|
+
<h3>{{title}}</h3>
|
|
251
|
+
<p>{{json_data.excerpt}}</p>
|
|
252
|
+
{{#json_data.featured_image}}
|
|
253
|
+
<img src="{{json_data.featured_image}}" alt="{{title}}">
|
|
254
|
+
{{/json_data.featured_image}}
|
|
255
|
+
{{#json_data.cta_text}}
|
|
256
|
+
<a href="{{json_data.cta_link}}" class="btn">{{json_data.cta_text}}</a>
|
|
257
|
+
{{/json_data.cta_text}}
|
|
258
|
+
</div>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Collection Query Options
|
|
262
|
+
Collections support advanced query parameters for pagination and sorting:
|
|
263
|
+
|
|
264
|
+
- **limit** - Maximum number of records to return
|
|
265
|
+
- **offset** - Number of records to skip (for pagination)
|
|
266
|
+
- **sortBy** - Field name to sort by
|
|
267
|
+
- **sortDirection** - Sort direction ('asc' or 'desc')
|
|
268
|
+
|
|
269
|
+
**Examples:**
|
|
270
|
+
```html
|
|
271
|
+
<!-- Get the first 5 articles sorted by title -->
|
|
272
|
+
<collection name="articles" filters="{}" field="title" data="{limit: 5, sortBy: 'title'}"/>
|
|
273
|
+
|
|
274
|
+
<!-- Paginated results: skip first 20, get next 10 -->
|
|
275
|
+
<collection name="articles" filters="{published: true}" data="{limit: 10, offset: 20, sortBy: 'created_at', sortDirection: 'desc'}">
|
|
276
|
+
<article>{{row.title}}</article>
|
|
277
|
+
</collection>
|
|
278
|
+
|
|
279
|
+
<!-- The latest 3 featured articles by creation date -->
|
|
280
|
+
<collection name="articles" filters="{featured: true}" data="{limit: 3, sortBy: 'created_at', sortDirection: 'desc'}">
|
|
281
|
+
<div class="featured-article">{{row.title}}</div>
|
|
282
|
+
</collection>
|
|
168
283
|
```
|
|
169
284
|
|
|
170
285
|
### Layout System
|
|
@@ -180,7 +295,7 @@ The CMS uses a two-tier layout system:
|
|
|
180
295
|
<link href="/css/styles.css" rel="stylesheet"/>
|
|
181
296
|
</head>
|
|
182
297
|
<body class="{{siteHandle}}">
|
|
183
|
-
{{
|
|
298
|
+
{{&content}}
|
|
184
299
|
<script src="/js/scripts.js"></script>
|
|
185
300
|
</body>
|
|
186
301
|
</html>
|
|
@@ -188,22 +303,22 @@ The CMS uses a two-tier layout system:
|
|
|
188
303
|
|
|
189
304
|
**layouts/default.html** - Body content only:
|
|
190
305
|
```html
|
|
191
|
-
|
|
306
|
+
<entity name="cmsBlocks" field="name" value="header-main"/>
|
|
192
307
|
|
|
193
308
|
<main id="main" class="main-container">
|
|
194
309
|
<div class="container">
|
|
195
310
|
<div class="row">
|
|
196
311
|
<div class="col-md-3">
|
|
197
|
-
|
|
312
|
+
<entity name="cmsBlocks" field="name" value="sidebar-left"/>
|
|
198
313
|
</div>
|
|
199
314
|
<div class="col-md-9">
|
|
200
|
-
{{
|
|
315
|
+
{{&content}}
|
|
201
316
|
</div>
|
|
202
317
|
</div>
|
|
203
318
|
</div>
|
|
204
319
|
</main>
|
|
205
320
|
|
|
206
|
-
|
|
321
|
+
<entity name="cmsBlocks" field="name" value="footer-main"/>
|
|
207
322
|
```
|
|
208
323
|
|
|
209
324
|
Pages can use different layouts by setting the `layout` field in `cms_pages`:
|
|
@@ -212,12 +327,12 @@ Pages can use different layouts by setting the `layout` field in `cms_pages`:
|
|
|
212
327
|
- `minimal` - Basic layout with minimal styling
|
|
213
328
|
|
|
214
329
|
### Content Blocks
|
|
215
|
-
Create reusable content blocks in the `cms_blocks` table via admin panel:
|
|
330
|
+
Create reusable content blocks in the `cms_blocks` table via the admin panel:
|
|
216
331
|
```sql
|
|
217
332
|
INSERT INTO cms_blocks (name, title, content) VALUES
|
|
218
333
|
('contact-info', 'Contact Information', '<p>Email: info@example.com</p>'),
|
|
219
|
-
('
|
|
220
|
-
'<div class="categories"><h3>Categories</h3><ul><li><a href="/
|
|
334
|
+
('article-sidebar', 'Article Categories',
|
|
335
|
+
'<div class="categories"><h3>Categories</h3><ul><li><a href="/articles/technology">Technology</a></li></ul></div>');
|
|
221
336
|
```
|
|
222
337
|
|
|
223
338
|
### Entity Access Control
|
|
@@ -225,7 +340,7 @@ Control which entities are publicly accessible:
|
|
|
225
340
|
```javascript
|
|
226
341
|
const cms = new Manager({
|
|
227
342
|
entityAccess: {
|
|
228
|
-
|
|
343
|
+
articles: { public: true, operations: ['read'] },
|
|
229
344
|
cmsPages: { public: true, operations: ['read'] },
|
|
230
345
|
users: { public: false }
|
|
231
346
|
}
|
|
@@ -310,7 +425,7 @@ cms.events.on('adminEntityExtraData', ({entitySerializedData, entity}) => {
|
|
|
310
425
|
- `routes` - URL routing and SEO metadata
|
|
311
426
|
- `cms_pages` - Page content with layout assignments
|
|
312
427
|
- `cms_blocks` - Reusable content blocks
|
|
313
|
-
- `
|
|
428
|
+
- `entities_access` - Entity access control rules
|
|
314
429
|
- `entities_meta` - Generic metadata storage
|
|
315
430
|
- `cms_pages_meta` - Page-specific metadata
|
|
316
431
|
|
|
@@ -331,18 +446,19 @@ The installer provides checkboxes for:
|
|
|
331
446
|
- `initializeServices()` - Initialize all services
|
|
332
447
|
|
|
333
448
|
### Frontend Class
|
|
334
|
-
- `initialize()` -
|
|
449
|
+
- `initialize()` - Set up frontend routes and templates
|
|
335
450
|
- `handleRequest(req, res)` - Main request handler
|
|
336
451
|
- `findRouteByPath(path)` - Database route lookup
|
|
337
452
|
- `findEntityByPath(path)` - Entity-based URL handling
|
|
338
453
|
|
|
339
454
|
### TemplateEngine Class
|
|
340
455
|
- `render(template, data, partials)` - Main template rendering with enhanced functions
|
|
341
|
-
- `processEntityFunctions(template)` - Process
|
|
342
|
-
- `processSingleFieldCollections(template)` - Process single field collections
|
|
343
|
-
- `processLoopCollections(template)` - Process loop collections
|
|
456
|
+
- `processEntityFunctions(template)` - Process `<entity>` functions
|
|
457
|
+
- `processSingleFieldCollections(template)` - Process single field collections with query options
|
|
458
|
+
- `processLoopCollections(template)` - Process loop collections with query options
|
|
459
|
+
- `processCustomPartials(template)` - Process `<partial>` tags with attribute parsing
|
|
344
460
|
- `fetchEntityForTemplate(tableName, identifier, identifierField)` - Load single entity
|
|
345
|
-
- `fetchCollectionForTemplate(tableName, filtersJson)` - Load entity collections
|
|
461
|
+
- `fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson)` - Load entity collections with pagination and sorting
|
|
346
462
|
|
|
347
463
|
### AdminManager Class
|
|
348
464
|
- `setupAdmin()` - Initialize admin panel
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Reldens - CMS
|
|
2
|
+
* Reldens - CMS - Administration Panel
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
:root {
|
|
7
6
|
--normalFont: Verdana, Geneva, sans-serif;
|
|
8
7
|
--reldensFont: "Play", sans-serif;
|
|
@@ -163,7 +162,7 @@
|
|
|
163
162
|
padding: 1rem;
|
|
164
163
|
overflow: auto;
|
|
165
164
|
cursor: pointer;
|
|
166
|
-
background-color:
|
|
165
|
+
background-color: var(--white);
|
|
167
166
|
border-radius: 8px;
|
|
168
167
|
|
|
169
168
|
& canvas {
|
|
@@ -241,7 +240,7 @@
|
|
|
241
240
|
text-decoration: none;
|
|
242
241
|
cursor: pointer;
|
|
243
242
|
font-size: 14px;
|
|
244
|
-
font-weight:
|
|
243
|
+
font-weight: var(--font-semi-bold);
|
|
245
244
|
border-bottom: 1px solid var(--darkBlue);
|
|
246
245
|
|
|
247
246
|
&:hover {
|
|
@@ -314,6 +313,9 @@
|
|
|
314
313
|
}
|
|
315
314
|
|
|
316
315
|
& textarea {
|
|
316
|
+
width: 98%;
|
|
317
|
+
margin: 0;
|
|
318
|
+
padding: 1%;
|
|
317
319
|
resize: vertical;
|
|
318
320
|
min-height: 300px;
|
|
319
321
|
}
|
|
@@ -401,30 +403,30 @@
|
|
|
401
403
|
padding: 0 1rem;
|
|
402
404
|
color: var(--black);
|
|
403
405
|
}
|
|
406
|
+
}
|
|
404
407
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
408
|
+
& .field-edit, & .field-delete {
|
|
409
|
+
& span {
|
|
410
|
+
display: block;
|
|
411
|
+
text-align: center;
|
|
412
|
+
cursor: pointer;
|
|
411
413
|
}
|
|
414
|
+
}
|
|
412
415
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
416
|
+
& .field-edit {
|
|
417
|
+
& span {
|
|
418
|
+
& svg, path {
|
|
419
|
+
width: 24px;
|
|
420
|
+
color: var(--lightBlue);
|
|
419
421
|
}
|
|
420
422
|
}
|
|
423
|
+
}
|
|
421
424
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
425
|
+
& .field-delete {
|
|
426
|
+
& span {
|
|
427
|
+
& svg, path {
|
|
428
|
+
width: 24px;
|
|
429
|
+
fill: var(--lightBlue);
|
|
428
430
|
}
|
|
429
431
|
}
|
|
430
432
|
}
|
|
@@ -438,6 +440,7 @@
|
|
|
438
440
|
display: flex;
|
|
439
441
|
flex-wrap: wrap;
|
|
440
442
|
justify-content: flex-start;
|
|
443
|
+
margin-top: 1rem;
|
|
441
444
|
|
|
442
445
|
&.hidden {
|
|
443
446
|
display: none;
|
|
@@ -532,6 +535,8 @@
|
|
|
532
535
|
color: var(--darkGrey);
|
|
533
536
|
|
|
534
537
|
&.filters-toggle {
|
|
538
|
+
margin-bottom: 0;
|
|
539
|
+
|
|
535
540
|
& img {
|
|
536
541
|
max-width: 30px;
|
|
537
542
|
margin-right: 1rem;
|
|
@@ -784,7 +789,7 @@
|
|
|
784
789
|
&:not([disabled]) {
|
|
785
790
|
margin: 0;
|
|
786
791
|
border: 1px solid #7f8c8d;
|
|
787
|
-
background:
|
|
792
|
+
background: var(--white);
|
|
788
793
|
|
|
789
794
|
&[type="checkbox"] {
|
|
790
795
|
max-width: max-content;
|
|
@@ -59,6 +59,12 @@ window.addEventListener('DOMContentLoaded', () => {
|
|
|
59
59
|
errorId: 'Missing entity ID on POST.'
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
// copyright year:
|
|
63
|
+
let copyRightYear = document.querySelector('.copyright-year');
|
|
64
|
+
if(copyRightYear){
|
|
65
|
+
copyRightYear.innerHTML = String((new Date()).getFullYear());
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
// activate expand/collapse elements
|
|
63
69
|
let expandCollapseButtons = document.querySelectorAll('[data-expand-collapse]');
|
|
64
70
|
if(expandCollapseButtons){
|
|
@@ -170,6 +176,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
|
|
170
176
|
let filtersToggleContent = document.querySelector('.filters-toggle-content');
|
|
171
177
|
if(filtersToggle && filtersToggleContent){
|
|
172
178
|
filtersToggle.addEventListener('click', () => {
|
|
179
|
+
filtersToggle.classList.toggle('active');
|
|
173
180
|
filtersToggleContent.classList.toggle('hidden');
|
|
174
181
|
});
|
|
175
182
|
let allFilters = document.querySelectorAll('.filters-toggle-content .filter input');
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
<span class="field-value">{{&value}}</span>
|
|
15
15
|
</div>
|
|
16
16
|
{{/editFields}}
|
|
17
|
+
{{&extraFormContent}}
|
|
17
18
|
<div class="actions">
|
|
18
|
-
<
|
|
19
|
+
<button class="button button-primary button-submit" type="submit" name="saveAction" value="save">Save</button>
|
|
20
|
+
<button class="button button-primary button-submit" type="submit" name="saveAction" value="saveAndContinue">Save and continue...</button>
|
|
19
21
|
<a class="button button-secondary button-back" href="{{&entityViewRoute}}">Cancel</a>
|
|
20
22
|
</div>
|
|
21
23
|
</form>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<textarea name="{{&fieldName}}" id="{{&fieldName}}" disabled="disabled">{{&fieldValue}}</textarea>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<fieldset class="cms-page-route">
|
|
2
|
+
<legend>Route data</legend>
|
|
3
|
+
<div class="edit-field">
|
|
4
|
+
<span class="field-name">Path</span>
|
|
5
|
+
<span class="field-value">
|
|
6
|
+
<input type="text" name="routePath" value="{{routePath}}"/>
|
|
7
|
+
</span>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="edit-field">
|
|
10
|
+
<span class="field-name">Domain</span>
|
|
11
|
+
<span class="field-value">
|
|
12
|
+
<input type="text" name="routeDomain" value="{{routeDomain}}"/>
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
</fieldset>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<fieldset class="cms-page-route">
|
|
2
|
+
<legend>Route data</legend>
|
|
3
|
+
<div class="edit-field">
|
|
4
|
+
<span class="field-name">Path</span>
|
|
5
|
+
<span class="field-value">
|
|
6
|
+
<input type="text" name="route-path" value="{{routePath}}" disabled="disabled"/>
|
|
7
|
+
</span>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="edit-field">
|
|
10
|
+
<span class="field-name">Domain</span>
|
|
11
|
+
<span class="field-value">
|
|
12
|
+
<input type="text" name="route-domain" value="{{routeDomain}}" disabled="disabled"/>
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
</fieldset>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<span class="field-value">{{&value}}</span>
|
|
7
7
|
</div>
|
|
8
8
|
{{/fields}}
|
|
9
|
+
{{&extraFormContent}}
|
|
9
10
|
<div class="actions">
|
|
10
11
|
<a class="button button-primary" href="{{&entityNewRoute}}">Create New</a>
|
|
11
12
|
<a class="button button-primary" href="{{&entityEditRoute}}">Edit</a>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const { Manager } = require('../index');
|
|
10
10
|
const { Logger } = require('@reldens/utils');
|
|
11
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
11
12
|
const readline = require('readline');
|
|
12
13
|
|
|
13
14
|
class CmsEntitiesGenerator
|
|
@@ -18,6 +19,17 @@ class CmsEntitiesGenerator
|
|
|
18
19
|
this.args = process.argv.slice(2);
|
|
19
20
|
this.projectRoot = process.cwd();
|
|
20
21
|
this.isOverride = this.args.includes('--override');
|
|
22
|
+
this.prismaClientPath = this.extractArgument('--prisma-client');
|
|
23
|
+
this.driver = this.extractArgument('--driver') || process.env.RELDENS_STORAGE_DRIVER || 'prisma';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
extractArgument(argumentName)
|
|
27
|
+
{
|
|
28
|
+
let argIndex = this.args.indexOf(argumentName);
|
|
29
|
+
if(-1 === argIndex || argIndex + 1 >= this.args.length){
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return this.args[argIndex + 1];
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
async run()
|
|
@@ -29,14 +41,21 @@ class CmsEntitiesGenerator
|
|
|
29
41
|
return false;
|
|
30
42
|
}
|
|
31
43
|
}
|
|
32
|
-
let
|
|
44
|
+
let managerConfig = {projectRoot: this.projectRoot};
|
|
45
|
+
if('prisma' === this.driver){
|
|
46
|
+
let prismaClient = await this.loadPrismaClient();
|
|
47
|
+
if(prismaClient){
|
|
48
|
+
managerConfig.prismaClient = prismaClient;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
let manager = new Manager(managerConfig);
|
|
33
52
|
if(!manager.isInstalled()){
|
|
34
53
|
Logger.error('CMS is not installed. Please run installation first.');
|
|
35
54
|
return false;
|
|
36
55
|
}
|
|
37
56
|
Logger.debug('Reldens CMS Manager instance created for entities generation.');
|
|
38
57
|
await manager.initializeDataServer();
|
|
39
|
-
let success = await manager.installer.generateEntities(manager.dataServer, this.isOverride);
|
|
58
|
+
let success = await manager.installer.generateEntities(manager.dataServer, this.isOverride, false);
|
|
40
59
|
if(!success){
|
|
41
60
|
Logger.error('Entities generation failed.');
|
|
42
61
|
return false;
|
|
@@ -45,6 +64,32 @@ class CmsEntitiesGenerator
|
|
|
45
64
|
return true;
|
|
46
65
|
}
|
|
47
66
|
|
|
67
|
+
async loadPrismaClient()
|
|
68
|
+
{
|
|
69
|
+
let clientPath = this.prismaClientPath;
|
|
70
|
+
if(!clientPath){
|
|
71
|
+
clientPath = FileHandler.joinPaths(process.cwd(), 'generated-entities', 'prisma');
|
|
72
|
+
}
|
|
73
|
+
if(!FileHandler.exists(clientPath)){
|
|
74
|
+
Logger.error('Prisma client not found at: '+clientPath);
|
|
75
|
+
Logger.error('Please ensure the client exists or specify a custom path with --prisma-client');
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
let PrismaClientModule = require(clientPath);
|
|
80
|
+
let PrismaClient = PrismaClientModule.PrismaClient || PrismaClientModule.default?.PrismaClient;
|
|
81
|
+
if(!PrismaClient){
|
|
82
|
+
Logger.error('PrismaClient not found in module: '+clientPath);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
Logger.debug('Prisma client loaded from: '+clientPath);
|
|
86
|
+
return new PrismaClient();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
Logger.error('Failed to load Prisma client from '+clientPath+': '+error.message);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
48
93
|
async confirmOverride()
|
|
49
94
|
{
|
|
50
95
|
let rl = readline.createInterface({
|
|
@@ -59,7 +104,6 @@ class CmsEntitiesGenerator
|
|
|
59
104
|
});
|
|
60
105
|
});
|
|
61
106
|
}
|
|
62
|
-
|
|
63
107
|
}
|
|
64
108
|
|
|
65
109
|
let generator = new CmsEntitiesGenerator();
|
package/bin/reldens-cms.js
CHANGED
|
@@ -8,14 +8,37 @@
|
|
|
8
8
|
|
|
9
9
|
const { Manager } = require('../index');
|
|
10
10
|
const { Logger } = require('@reldens/utils');
|
|
11
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
11
12
|
|
|
12
13
|
let args = process.argv.slice(2);
|
|
13
14
|
let projectRoot = args[0] || process.cwd();
|
|
15
|
+
let indexPath = FileHandler.joinPaths(projectRoot, 'index.js');
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
if(FileHandler.exists(indexPath)){
|
|
18
|
+
require(indexPath);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let managerConfig = {projectRoot};
|
|
23
|
+
let entitiesPath = FileHandler.joinPaths(
|
|
24
|
+
projectRoot,
|
|
25
|
+
'generated-entities',
|
|
26
|
+
'models',
|
|
27
|
+
'prisma',
|
|
28
|
+
'registered-models-prisma.js'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if(FileHandler.exists(entitiesPath)){
|
|
32
|
+
let entitiesModule = require(entitiesPath);
|
|
33
|
+
managerConfig.rawRegisteredEntities = entitiesModule.rawRegisteredEntities;
|
|
34
|
+
managerConfig.entitiesConfig = entitiesModule.entitiesConfig;
|
|
35
|
+
managerConfig.entitiesTranslations = entitiesModule.entitiesTranslations;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let manager = new Manager(managerConfig);
|
|
16
39
|
Logger.debug('Reldens CMS Manager instance created.', {configuration: manager.config});
|
|
17
40
|
|
|
18
|
-
|
|
41
|
+
manager.start().then((result) => {
|
|
19
42
|
if(!result){
|
|
20
43
|
Logger.info('Reldens CMS started by command failed.');
|
|
21
44
|
return false;
|
|
@@ -25,9 +48,4 @@ let started = manager.start().then((result) => {
|
|
|
25
48
|
}).catch((error) => {
|
|
26
49
|
Logger.critical('Failed to start CMS:', error);
|
|
27
50
|
process.exit();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if(!started){
|
|
31
|
-
Logger.error('Reldens CMS start process failed.');
|
|
32
|
-
process.exit();
|
|
33
|
-
}
|
|
51
|
+
});
|
package/install/js/installer.js
CHANGED
|
@@ -29,4 +29,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
29
29
|
installButton.disabled = true;
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
|
+
|
|
33
|
+
let copyRightYear = document.querySelector('.copyright-year');
|
|
34
|
+
if(copyRightYear){
|
|
35
|
+
copyRightYear.innerHTML = String((new Date()).getFullYear());
|
|
36
|
+
}
|
|
32
37
|
});
|