@silexlabs/silex 3.5.0 → 3.5.1-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/.silex.js +0 -3
- package/dist/client/client/config.d.ts +8 -0
- package/dist/client/client/grapesjs/PublicationManager.d.ts +43 -0
- package/dist/client/client/grapesjs/cms/DataSource.d.ts +43 -0
- package/dist/client/client/grapesjs/cms/blocks.d.ts +2 -0
- package/dist/client/client/grapesjs/cms/buttons.d.ts +2 -0
- package/dist/client/client/grapesjs/cms/collection-pages.d.ts +4 -0
- package/dist/client/client/grapesjs/cms/config.d.ts +9 -0
- package/dist/client/client/grapesjs/cms/filters.d.ts +3 -0
- package/dist/client/client/grapesjs/cms/index.d.ts +44 -0
- package/dist/client/client/grapesjs/cms/liquid.d.ts +59 -0
- package/dist/client/client/grapesjs/cms/page-settings.d.ts +6 -0
- package/dist/client/client/grapesjs/cms/publication.d.ts +60 -0
- package/dist/client/client/grapesjs/cms/site-settings.d.ts +3 -0
- package/dist/client/client/grapesjs/cms/states.d.ts +8 -0
- package/dist/client/client/grapesjs/cms/traits.d.ts +8 -0
- package/dist/client/client/grapesjs/settings.d.ts +12 -3
- package/dist/client/client/grapesjs/view-buttons.d.ts +2 -1
- package/dist/client/client/utils.d.ts +1 -0
- package/dist/client/constants.d.ts +2 -1
- package/dist/client/css/admin.css +117 -19
- package/dist/client/index.html +2 -2
- package/dist/client/js/main.js +1974 -267
- package/dist/client/types.d.ts +3 -1
- package/dist/plugins/client/plugins/client/template.js +1 -1
- package/dist/plugins/client/plugins/client/template.js.map +1 -1
- package/dist/plugins/client/types.d.ts +3 -1
- package/dist/plugins/client/types.js +11 -8
- package/dist/plugins/client/types.js.map +1 -1
- package/dist/plugins/server/constants.d.ts +2 -1
- package/dist/plugins/server/constants.js +3 -2
- package/dist/plugins/server/constants.js.map +1 -1
- package/dist/plugins/server/plugins/server/FtpConnector.js +15 -20
- package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
- package/dist/plugins/server/plugins/server/GitlabConnector.js +34 -7
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
- package/dist/plugins/server/server/connectors/FsStorage.js +16 -20
- package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
- package/dist/plugins/server/server/utils/websiteDataSerialization.d.ts +4 -0
- package/dist/plugins/server/server/utils/websiteDataSerialization.js +21 -12
- package/dist/plugins/server/server/utils/websiteDataSerialization.js.map +1 -1
- package/dist/plugins/server/types.d.ts +3 -1
- package/dist/plugins/server/types.js +12 -9
- package/dist/plugins/server/types.js.map +1 -1
- package/dist/server/constants.js +3 -2
- package/dist/server/server/connectors/FsStorage.js +14 -18
- package/dist/server/server/utils/websiteDataSerialization.js +16 -11
- package/dist/server/types.js +6 -2
- package/package.json +16 -11
- package/src/css/_variables.scss +2 -1
- package/src/css/grapesjs-plugins.scss +31 -0
- package/src/css/grapesjs.scss +20 -0
- package/src/css/page-panel.scss +25 -6
- package/src/css/silex.scss +64 -5
- package/src/ts/client/config.ts +23 -0
- package/src/ts/client/grapesjs/PublicationManager.ts +159 -1
- package/src/ts/client/grapesjs/breadcrumbs.ts +3 -2
- package/src/ts/client/grapesjs/cms/DataSource.ts +333 -0
- package/src/ts/client/grapesjs/cms/blocks.ts +124 -0
- package/src/ts/client/grapesjs/cms/buttons.ts +163 -0
- package/src/ts/client/grapesjs/cms/collection-pages.ts +214 -0
- package/src/ts/client/grapesjs/cms/config.ts +47 -0
- package/src/ts/client/grapesjs/cms/filters.ts +53 -0
- package/src/ts/client/grapesjs/cms/index.ts +130 -0
- package/src/ts/client/grapesjs/cms/liquid.mock.ts +181 -0
- package/src/ts/client/grapesjs/cms/liquid.test.ts +239 -0
- package/src/ts/client/grapesjs/cms/liquid.ts +325 -0
- package/src/ts/client/grapesjs/cms/page-settings.ts +285 -0
- package/src/ts/client/grapesjs/cms/publication.test.ts +300 -0
- package/src/ts/client/grapesjs/cms/publication.ts +708 -0
- package/src/ts/client/grapesjs/cms/site-settings.ts +79 -0
- package/src/ts/client/grapesjs/cms/states.ts +125 -0
- package/src/ts/client/grapesjs/cms/traits.ts +95 -0
- package/src/ts/client/grapesjs/css-props.ts +15 -16
- package/src/ts/client/grapesjs/flex.ts +59 -39
- package/src/ts/client/grapesjs/img.ts +1 -1
- package/src/ts/client/grapesjs/index.ts +77 -4
- package/src/ts/client/grapesjs/internal-links.ts +4 -2
- package/src/ts/client/grapesjs/page-panel.ts +126 -11
- package/src/ts/client/grapesjs/sectors.ts +18 -10
- package/src/ts/client/grapesjs/semantic.ts +4 -0
- package/src/ts/client/grapesjs/settings.ts +252 -101
- package/src/ts/client/grapesjs/storage.ts +12 -3
- package/src/ts/client/grapesjs/view-buttons.ts +39 -3
- package/src/ts/client/utils.ts +15 -2
- package/src/ts/constants.ts +2 -1
- package/src/ts/plugins/client/template.ts +1 -1
- package/src/ts/plugins/server/FtpConnector.test.ts +1 -0
- package/src/ts/plugins/server/FtpConnector.ts +20 -23
- package/src/ts/plugins/server/GitlabConnector.ts +40 -10
- package/src/ts/server/connectors/FsStorage.test.ts +3 -2
- package/src/ts/server/connectors/FsStorage.ts +21 -23
- package/src/ts/server/utils/websiteDataSerialization.test.ts +45 -13
- package/src/ts/server/utils/websiteDataSerialization.ts +23 -14
- package/src/ts/types.ts +6 -2
package/src/css/silex.scss
CHANGED
|
@@ -60,11 +60,70 @@
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// full screen settings
|
|
63
|
-
.settings-dialog
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
.settings-dialog {
|
|
64
|
+
position: fixed;
|
|
65
|
+
top: 0;
|
|
66
|
+
left: 0;
|
|
67
|
+
width: 100vw;
|
|
66
68
|
height: 100vh;
|
|
69
|
+
z-index: $z-dialog-inline;
|
|
70
|
+
pointer-events: auto;
|
|
67
71
|
overflow: auto;
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
|
|
75
|
+
.settings-content {
|
|
76
|
+
flex: 1;
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
background-color: var(--primaryColor);
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
}
|
|
82
|
+
.settings-header {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: space-between;
|
|
86
|
+
padding: 16px 20px;
|
|
87
|
+
border-bottom: 1px solid var(--gjs-border-color, #ddd);
|
|
88
|
+
}
|
|
89
|
+
.settings-header h3 {
|
|
90
|
+
margin: 0;
|
|
91
|
+
font-size: 18px;
|
|
92
|
+
}
|
|
93
|
+
.settings-close {
|
|
94
|
+
color: inherit;
|
|
95
|
+
background: none;
|
|
96
|
+
border: none;
|
|
97
|
+
font-size: 24px;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
padding: 0;
|
|
100
|
+
width: 30px;
|
|
101
|
+
height: 30px;
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
}
|
|
106
|
+
.settings-body {
|
|
107
|
+
flex: 1;
|
|
108
|
+
overflow: auto;
|
|
109
|
+
padding: 0;
|
|
110
|
+
}
|
|
111
|
+
.settings-body .silex-form {
|
|
112
|
+
height: 100%;
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
}
|
|
116
|
+
.settings-body .silex-sidebar-dialog {
|
|
117
|
+
flex: 1;
|
|
118
|
+
}
|
|
119
|
+
footer {
|
|
120
|
+
position: fixed;
|
|
121
|
+
z-index: 10;
|
|
122
|
+
bottom: 0;
|
|
123
|
+
left: 0;
|
|
124
|
+
width: 100%;
|
|
125
|
+
padding: 20px 0;
|
|
126
|
+
}
|
|
68
127
|
form.silex-form {
|
|
69
128
|
padding-bottom: 100px; // leave space for fixed footer
|
|
70
129
|
textarea {
|
|
@@ -91,7 +150,7 @@
|
|
|
91
150
|
.silex-version {
|
|
92
151
|
float: left;
|
|
93
152
|
padding: 0 15px;
|
|
94
|
-
font-size:
|
|
153
|
+
font-size: small;
|
|
95
154
|
}
|
|
96
155
|
}
|
|
97
156
|
.CodeMirror {
|
|
@@ -164,7 +223,7 @@ input.silex-button[type="submit"] {
|
|
|
164
223
|
margin: 5px;
|
|
165
224
|
}
|
|
166
225
|
main {
|
|
167
|
-
width:
|
|
226
|
+
width: 93%;
|
|
168
227
|
}
|
|
169
228
|
}
|
|
170
229
|
|
package/src/ts/client/config.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { addSection, removeSection } from './grapesjs/settings'
|
|
|
29
29
|
// Plugins
|
|
30
30
|
import publishCustomCodeBlock from './publish-custom-code-block'
|
|
31
31
|
import publishFonts from './publish-fonts'
|
|
32
|
+
import { EleventyPluginOptions } from './grapesjs/cms'
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* @fileoverview Silex client side config
|
|
@@ -118,6 +119,27 @@ export class ClientConfig extends Config {
|
|
|
118
119
|
fontsServerUrl = 'https://fonts.googleapis.com'
|
|
119
120
|
fontsApiUrl = 'https://www.googleapis.com'
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* CMS configuration options
|
|
124
|
+
* @default Default CMS configuration with enabled: true
|
|
125
|
+
*/
|
|
126
|
+
cmsConfig: Partial<EleventyPluginOptions> & { enabled?: boolean } = {
|
|
127
|
+
enabled: true,
|
|
128
|
+
enable11ty: true,
|
|
129
|
+
cacheBuster: false,
|
|
130
|
+
dataSources: [],
|
|
131
|
+
dir: {
|
|
132
|
+
input: '',
|
|
133
|
+
html: '',
|
|
134
|
+
assets: 'assets',
|
|
135
|
+
css: 'css',
|
|
136
|
+
},
|
|
137
|
+
urls: {
|
|
138
|
+
css: '/css',
|
|
139
|
+
assets: '/assets',
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
121
143
|
/**
|
|
122
144
|
* Init GrapesJS config which depend on the config file properties
|
|
123
145
|
*/
|
|
@@ -179,6 +201,7 @@ export class ClientConfig extends Config {
|
|
|
179
201
|
* Add a section to the settings dialog
|
|
180
202
|
*/
|
|
181
203
|
addSettings(section: SettingsSection, siteOrPage: 'site' | 'page', position: 'first' | 'last' | number = 'last') {
|
|
204
|
+
console.warn('addSettings API is deprecated, use the command instead:')
|
|
182
205
|
addSection(section, siteOrPage, position)
|
|
183
206
|
}
|
|
184
207
|
|
|
@@ -20,7 +20,7 @@ import { ApiConnectorLoggedInPostMessage, ApiConnectorLoginQuery, ApiPublication
|
|
|
20
20
|
import { Editor } from 'grapesjs'
|
|
21
21
|
import { PublicationUi } from './PublicationUi'
|
|
22
22
|
import { getUser, logout, publicationStatus, publish } from '../api'
|
|
23
|
-
import { API_CONNECTOR_LOGIN, API_CONNECTOR_PATH, API_PATH } from '../../constants'
|
|
23
|
+
import { API_CONNECTOR_LOGIN, API_CONNECTOR_PATH, API_PATH, SILEX_VERSION } from '../../constants'
|
|
24
24
|
import { ClientEvent } from '../events'
|
|
25
25
|
import { resetRenderComponents, resetRenderCssRules, transformPermalink, transformFiles, transformPath, renderComponents, renderCssRules } from '../publication-transformers'
|
|
26
26
|
import { hashString } from '../utils'
|
|
@@ -288,6 +288,8 @@ export class PublicationManager {
|
|
|
288
288
|
// Get the data to publish, clone the objects because plugins can change it
|
|
289
289
|
const projectData = { ...this.editor.getProjectData() as WebsiteData }
|
|
290
290
|
const siteSettings = { ...this.editor.getModel().get('settings') as WebsiteSettings }
|
|
291
|
+
// Check for missing SEO tags and warn user
|
|
292
|
+
this.checkSeoTags(siteSettings)
|
|
291
293
|
let preventDefaultStart = false
|
|
292
294
|
this.editor.trigger(ClientEvent.PUBLISH_START, {projectData, siteSettings, preventDefault: () => preventDefaultStart = true, publicationManager: this })
|
|
293
295
|
if(preventDefaultStart) {
|
|
@@ -354,6 +356,158 @@ export class PublicationManager {
|
|
|
354
356
|
return files
|
|
355
357
|
}
|
|
356
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Helper function to check if a viewport meta tag exists in the head content
|
|
361
|
+
* @param headContent - HTML content from head settings
|
|
362
|
+
* @returns true if viewport meta tag is found
|
|
363
|
+
*/
|
|
364
|
+
private hasViewportMeta(headContent: string): boolean {
|
|
365
|
+
if (!headContent) return false
|
|
366
|
+
// Check for viewport meta tag with various formats
|
|
367
|
+
const viewportPattern = /<meta[^>]*name\s*=\s*["']viewport["'][^>]*>/i
|
|
368
|
+
return viewportPattern.test(headContent)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Get the default viewport meta tag if no viewport is present
|
|
373
|
+
* @param siteHead - Head content from site settings
|
|
374
|
+
* @param pageHead - Head content from page settings
|
|
375
|
+
* @returns viewport meta tag or empty string if already present
|
|
376
|
+
*/
|
|
377
|
+
private ensureViewportMeta(siteHead: string, pageHead: string): string {
|
|
378
|
+
if (this.hasViewportMeta(siteHead) || this.hasViewportMeta(pageHead)) {
|
|
379
|
+
return ''
|
|
380
|
+
}
|
|
381
|
+
return '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Helper function to check if a generator meta tag exists in the head content
|
|
386
|
+
* @param headContent - HTML content from head settings
|
|
387
|
+
* @returns true if generator meta tag is found
|
|
388
|
+
*/
|
|
389
|
+
private hasGeneratorMeta(headContent: string): boolean {
|
|
390
|
+
if (!headContent) return false
|
|
391
|
+
// Check for generator meta tag with various formats
|
|
392
|
+
const generatorPattern = /<meta[^>]*name\s*=\s*["']generator["'][^>]*>/i
|
|
393
|
+
return generatorPattern.test(headContent)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get the default generator meta tag if no generator is present
|
|
398
|
+
* @param siteHead - Head content from site settings
|
|
399
|
+
* @param pageHead - Head content from page settings
|
|
400
|
+
* @returns generator meta tag or empty string if already present
|
|
401
|
+
*/
|
|
402
|
+
private ensureGeneratorMeta(siteHead: string, pageHead: string): string {
|
|
403
|
+
if (this.hasGeneratorMeta(siteHead) || this.hasGeneratorMeta(pageHead)) {
|
|
404
|
+
return ''
|
|
405
|
+
}
|
|
406
|
+
return `<meta name="generator" content="Silex v${ SILEX_VERSION }">`
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Helper function to check if a canonical link exists in the head content
|
|
411
|
+
* @param headContent - HTML content from head settings
|
|
412
|
+
* @returns true if canonical link is found
|
|
413
|
+
*/
|
|
414
|
+
private hasCanonicalLink(headContent: string): boolean {
|
|
415
|
+
if (!headContent) return false
|
|
416
|
+
const canonicalPattern = /<link[^>]*rel\s*=\s*["']canonical["'][^>]*>/i
|
|
417
|
+
return canonicalPattern.test(headContent)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Helper function to check if alternate links exist in the head content
|
|
422
|
+
* @param headContent - HTML content from head settings
|
|
423
|
+
* @returns true if alternate links are found
|
|
424
|
+
*/
|
|
425
|
+
private hasAlternateLinks(headContent: string): boolean {
|
|
426
|
+
if (!headContent) return false
|
|
427
|
+
const alternatePattern = /<link[^>]*rel\s*=\s*["']alternate["'][^>]*>/i
|
|
428
|
+
return alternatePattern.test(headContent)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Check for missing SEO tags and emit warnings
|
|
433
|
+
* @param siteSettings - Site settings
|
|
434
|
+
*/
|
|
435
|
+
private checkSeoTags(siteSettings: WebsiteSettings): void {
|
|
436
|
+
const pages = this.editor.Pages.getAll()
|
|
437
|
+
|
|
438
|
+
// Utility function to get a setting from the page or the site settings
|
|
439
|
+
const getSetting = (pageSettings: WebsiteSettings, name: string) =>
|
|
440
|
+
(pageSettings || {})[name] || (siteSettings || {})[name] || ''
|
|
441
|
+
|
|
442
|
+
for (const page of pages) {
|
|
443
|
+
const pageSettings = page.get('settings') as WebsiteSettings || {}
|
|
444
|
+
const siteHead = siteSettings?.head || ''
|
|
445
|
+
const pageHead = pageSettings?.head || ''
|
|
446
|
+
const pageName = page.get('name') || 'Unnamed page'
|
|
447
|
+
const bodyComponent = page.getMainComponent()
|
|
448
|
+
const componentId = bodyComponent?.getId()
|
|
449
|
+
const pageId = page.getId()
|
|
450
|
+
|
|
451
|
+
// Check for lang attribute
|
|
452
|
+
const lang = getSetting(pageSettings, 'lang')
|
|
453
|
+
if (!lang || lang.trim() === '') {
|
|
454
|
+
this.editor.runCommand('notifications:add', {
|
|
455
|
+
id: `seo-validation-lang-${pageId}`,
|
|
456
|
+
type: 'warning',
|
|
457
|
+
message: `Page "${pageName}": Missing lang attribute on <html> tag. This is important for accessibility and SEO.`,
|
|
458
|
+
componentId,
|
|
459
|
+
group: 'seo-validation'
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Check for title
|
|
464
|
+
const title = getSetting(pageSettings, 'title')
|
|
465
|
+
if (!title || title.trim() === '') {
|
|
466
|
+
this.editor.runCommand('notifications:add', {
|
|
467
|
+
id: `seo-validation-title-${pageId}`,
|
|
468
|
+
type: 'warning',
|
|
469
|
+
message: `Page "${pageName}": Missing <title> tag. This is critical for SEO.`,
|
|
470
|
+
componentId,
|
|
471
|
+
group: 'seo-validation'
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Check for description
|
|
476
|
+
const description = getSetting(pageSettings, 'description')
|
|
477
|
+
if (!description || description.trim() === '') {
|
|
478
|
+
this.editor.runCommand('notifications:add', {
|
|
479
|
+
id: `seo-validation-description-${pageId}`,
|
|
480
|
+
type: 'warning',
|
|
481
|
+
message: `Page "${pageName}": Missing meta description. This is important for SEO and search results display.`,
|
|
482
|
+
componentId,
|
|
483
|
+
group: 'seo-validation'
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Check for canonical link
|
|
488
|
+
if (!this.hasCanonicalLink(siteHead) && !this.hasCanonicalLink(pageHead)) {
|
|
489
|
+
this.editor.runCommand('notifications:add', {
|
|
490
|
+
id: `seo-validation-canonical-${pageId}`,
|
|
491
|
+
type: 'warning',
|
|
492
|
+
message: `Page "${pageName}": Missing canonical link. This helps prevent duplicate content issues.`,
|
|
493
|
+
componentId,
|
|
494
|
+
group: 'seo-validation'
|
|
495
|
+
})
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Check for alternate links (hreflang for multilingual sites)
|
|
499
|
+
if (!this.hasAlternateLinks(siteHead) && !this.hasAlternateLinks(pageHead)) {
|
|
500
|
+
this.editor.runCommand('notifications:add', {
|
|
501
|
+
id: `seo-validation-alternate-${pageId}`,
|
|
502
|
+
type: 'warning',
|
|
503
|
+
message: `Page "${pageName}": Missing alternate links (hreflang). Consider adding these for multilingual sites.`,
|
|
504
|
+
componentId,
|
|
505
|
+
group: 'seo-validation'
|
|
506
|
+
})
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
357
511
|
async *getHtmlFilesYield(siteSettings: WebsiteSettings, preventDefault): AsyncGenerator<WebsiteFile | undefined> {
|
|
358
512
|
for (const page of this.editor.Pages.getAll()) {
|
|
359
513
|
// Clone the settings because plugins can change them
|
|
@@ -395,6 +549,8 @@ export class PublicationManager {
|
|
|
395
549
|
// Useful data for HTML result
|
|
396
550
|
const title = getSetting('title')
|
|
397
551
|
const favicon = getSetting('favicon')
|
|
552
|
+
const viewportMeta = this.ensureViewportMeta(clonedSiteSettings?.head || '', pageSettings?.head || '')
|
|
553
|
+
const generatorMeta = this.ensureGeneratorMeta(clonedSiteSettings?.head || '', pageSettings?.head || '')
|
|
398
554
|
|
|
399
555
|
// Return the HTML file
|
|
400
556
|
yield {
|
|
@@ -402,6 +558,8 @@ export class PublicationManager {
|
|
|
402
558
|
<html lang="${getSetting('lang')}">
|
|
403
559
|
<head>
|
|
404
560
|
<meta charset="UTF-8">
|
|
561
|
+
${viewportMeta}
|
|
562
|
+
${generatorMeta}
|
|
405
563
|
<link rel="stylesheet" href="${cssPermalink}" />
|
|
406
564
|
${clonedSiteSettings?.head || ''}
|
|
407
565
|
${pageSettings?.head || ''}
|
|
@@ -47,9 +47,10 @@ export default function (editor, options) {
|
|
|
47
47
|
|
|
48
48
|
editor.on('component:selected style:target component:drag:end', () => renderBreadcrumbs())
|
|
49
49
|
function renderBreadcrumbs() {
|
|
50
|
-
|
|
50
|
+
const selectedPage = editor.Pages?.getSelected()
|
|
51
|
+
let component = editor.getSelected() ?? selectedPage?.getMainComponent()
|
|
51
52
|
|
|
52
|
-
if(!breadcrumbsContainer) return
|
|
53
|
+
if(!breadcrumbsContainer || !component) return
|
|
53
54
|
|
|
54
55
|
// Clear the breadcrumbs container
|
|
55
56
|
breadcrumbsContainer.innerHTML = ''
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { DataSourceType, Field, IDataSource, Type, addDataSource, addFilters, removeFilters } from '@silexlabs/grapesjs-data-source'
|
|
2
|
+
import { EleventyPluginOptions, Silex11tyPluginWebsiteSettings } from './index'
|
|
3
|
+
import { Editor } from 'grapesjs'
|
|
4
|
+
import { i18nFilters } from './filters'
|
|
5
|
+
import { ClientEvent } from '../../events'
|
|
6
|
+
|
|
7
|
+
//import { cmdPauseAutoSave } from '@silexlabs/silex/src/ts/client/grapesjs/storage'
|
|
8
|
+
const cmdPauseAutoSave = 'pause-auto-save'
|
|
9
|
+
|
|
10
|
+
export default function(editor: Editor, opts: EleventyPluginOptions): void {
|
|
11
|
+
editor.on(`
|
|
12
|
+
${ ClientEvent.SETTINGS_SAVE_END }
|
|
13
|
+
storage:after:load
|
|
14
|
+
`, () => {
|
|
15
|
+
updateFilters(editor, opts)
|
|
16
|
+
})
|
|
17
|
+
updateFilters(editor, opts)
|
|
18
|
+
if(opts.enable11ty) {
|
|
19
|
+
// Add the 11ty data source using the new functional API
|
|
20
|
+
const ds = new EleventyDataSource()
|
|
21
|
+
editor.runCommand(cmdPauseAutoSave)
|
|
22
|
+
|
|
23
|
+
// Add the data source
|
|
24
|
+
addDataSource(ds)
|
|
25
|
+
|
|
26
|
+
// Wait for the next tick to avoid triggering a save
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
editor.stopCommand(cmdPauseAutoSave)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function updateFilters(editor: Editor, opts: EleventyPluginOptions) {
|
|
34
|
+
const settings = (editor.getModel().get('settings') || {}) as Silex11tyPluginWebsiteSettings
|
|
35
|
+
if(settings.eleventyI18n) {
|
|
36
|
+
addFilters(i18nFilters)
|
|
37
|
+
} else if (!opts.i18nPlugin) {
|
|
38
|
+
removeFilters(i18nFilters)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const EleventyDataSourceId = 'eleventy'
|
|
43
|
+
|
|
44
|
+
export class EleventyDataSource implements IDataSource {
|
|
45
|
+
/**
|
|
46
|
+
* Unique identifier of the data source
|
|
47
|
+
* This is used to retrieve the data source from the editor
|
|
48
|
+
*/
|
|
49
|
+
public id = EleventyDataSourceId
|
|
50
|
+
public label = 'Eleventy'
|
|
51
|
+
public url = ''
|
|
52
|
+
public type = 'graphql' as DataSourceType
|
|
53
|
+
public method = 'POST'
|
|
54
|
+
public headers = {}
|
|
55
|
+
public hidden = true
|
|
56
|
+
public readonly = true
|
|
57
|
+
|
|
58
|
+
private eventListeners: Record<string, ((...args: unknown[]) => void)[]> = {}
|
|
59
|
+
|
|
60
|
+
// Simple event handling
|
|
61
|
+
on(event: string, callback: (...args: unknown[]) => void): void {
|
|
62
|
+
if (!this.eventListeners[event]) {
|
|
63
|
+
this.eventListeners[event] = []
|
|
64
|
+
}
|
|
65
|
+
this.eventListeners[event].push(callback)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
off(event: string, callback?: (...args: unknown[]) => void): void {
|
|
69
|
+
if (!this.eventListeners[event]) return
|
|
70
|
+
if (callback) {
|
|
71
|
+
this.eventListeners[event] = this.eventListeners[event].filter(cb => cb !== callback)
|
|
72
|
+
} else {
|
|
73
|
+
this.eventListeners[event] = []
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
trigger(event: string, ...args: unknown[]): void {
|
|
78
|
+
if (!this.eventListeners[event]) return
|
|
79
|
+
this.eventListeners[event].forEach(callback => callback(...args))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Implement IDatasource
|
|
84
|
+
*/
|
|
85
|
+
async connect(): Promise<void> {}
|
|
86
|
+
isConnected(): boolean { return true }
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Implement IDatasource
|
|
90
|
+
*/
|
|
91
|
+
getQuery(/*expressions: Expression[]*/): string { return '' }
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Implement IDatasource
|
|
95
|
+
*/
|
|
96
|
+
getTypes(): Type[] {
|
|
97
|
+
return [{
|
|
98
|
+
id: 'string',
|
|
99
|
+
label: 'String',
|
|
100
|
+
dataSourceId: 'eleventy',
|
|
101
|
+
fields: [],
|
|
102
|
+
}, {
|
|
103
|
+
id: 'number',
|
|
104
|
+
label: 'Number',
|
|
105
|
+
dataSourceId: 'eleventy',
|
|
106
|
+
fields: [],
|
|
107
|
+
}, {
|
|
108
|
+
id: 'date',
|
|
109
|
+
label: 'Date',
|
|
110
|
+
dataSourceId: 'eleventy',
|
|
111
|
+
fields: [],
|
|
112
|
+
}, {
|
|
113
|
+
id: 'page',
|
|
114
|
+
label: 'page',
|
|
115
|
+
dataSourceId: 'eleventy',
|
|
116
|
+
fields: [{
|
|
117
|
+
id: 'url',
|
|
118
|
+
label: 'url',
|
|
119
|
+
typeIds: ['string'],
|
|
120
|
+
kind: 'scalar',
|
|
121
|
+
dataSourceId: 'eleventy',
|
|
122
|
+
}, {
|
|
123
|
+
id: 'fileSlug',
|
|
124
|
+
label: 'fileSlug',
|
|
125
|
+
typeIds: ['string'],
|
|
126
|
+
kind: 'scalar',
|
|
127
|
+
dataSourceId: 'eleventy',
|
|
128
|
+
}, {
|
|
129
|
+
id: 'filePathStem',
|
|
130
|
+
label: 'filePathStem',
|
|
131
|
+
typeIds: ['string'],
|
|
132
|
+
kind: 'scalar',
|
|
133
|
+
dataSourceId: 'eleventy',
|
|
134
|
+
}, {
|
|
135
|
+
id: 'date',
|
|
136
|
+
label: 'date',
|
|
137
|
+
typeIds: ['date'],
|
|
138
|
+
kind: 'scalar',
|
|
139
|
+
dataSourceId: 'eleventy',
|
|
140
|
+
}, {
|
|
141
|
+
id: 'inputPath',
|
|
142
|
+
label: 'inputPath',
|
|
143
|
+
typeIds: ['string'],
|
|
144
|
+
kind: 'scalar',
|
|
145
|
+
dataSourceId: 'eleventy',
|
|
146
|
+
}, {
|
|
147
|
+
id: 'outputPath',
|
|
148
|
+
label: 'outputPath',
|
|
149
|
+
typeIds: ['string'],
|
|
150
|
+
kind: 'scalar',
|
|
151
|
+
dataSourceId: 'eleventy',
|
|
152
|
+
}, {
|
|
153
|
+
id: 'outputFileExtension',
|
|
154
|
+
label: 'outputFileExtension',
|
|
155
|
+
typeIds: ['string'],
|
|
156
|
+
kind: 'scalar',
|
|
157
|
+
dataSourceId: 'eleventy',
|
|
158
|
+
}, {
|
|
159
|
+
id: 'lang',
|
|
160
|
+
label: 'lang',
|
|
161
|
+
typeIds: ['string'],
|
|
162
|
+
kind: 'scalar',
|
|
163
|
+
dataSourceId: 'eleventy',
|
|
164
|
+
}],
|
|
165
|
+
}, {
|
|
166
|
+
id: 'pagination',
|
|
167
|
+
label: 'pagination',
|
|
168
|
+
dataSourceId: 'eleventy',
|
|
169
|
+
fields: [{
|
|
170
|
+
id: 'hrefs',
|
|
171
|
+
label: 'hrefs',
|
|
172
|
+
typeIds: ['string'],
|
|
173
|
+
kind: 'list',
|
|
174
|
+
dataSourceId: 'eleventy',
|
|
175
|
+
}, {
|
|
176
|
+
id: 'href',
|
|
177
|
+
label: 'href',
|
|
178
|
+
typeIds: ['paginationHref'],
|
|
179
|
+
kind: 'object',
|
|
180
|
+
dataSourceId: 'eleventy',
|
|
181
|
+
}, {
|
|
182
|
+
id: 'pageNumber',
|
|
183
|
+
label: 'pageNumber',
|
|
184
|
+
typeIds: ['number'],
|
|
185
|
+
kind: 'scalar',
|
|
186
|
+
dataSourceId: 'eleventy',
|
|
187
|
+
}, {
|
|
188
|
+
id: 'pages',
|
|
189
|
+
label: 'pages',
|
|
190
|
+
typeIds: ['page'],
|
|
191
|
+
kind: 'list',
|
|
192
|
+
dataSourceId: 'eleventy',
|
|
193
|
+
}],
|
|
194
|
+
}, {
|
|
195
|
+
id: 'paginationHref',
|
|
196
|
+
label: 'paginationHref',
|
|
197
|
+
dataSourceId: 'eleventy',
|
|
198
|
+
fields: [{
|
|
199
|
+
id: 'next',
|
|
200
|
+
label: 'next',
|
|
201
|
+
typeIds: ['string'],
|
|
202
|
+
kind: 'scalar',
|
|
203
|
+
dataSourceId: 'eleventy',
|
|
204
|
+
}, {
|
|
205
|
+
id: 'previous',
|
|
206
|
+
label: 'previous',
|
|
207
|
+
typeIds: ['string'],
|
|
208
|
+
kind: 'scalar',
|
|
209
|
+
dataSourceId: 'eleventy',
|
|
210
|
+
}, {
|
|
211
|
+
id: 'first',
|
|
212
|
+
label: 'first',
|
|
213
|
+
typeIds: ['string'],
|
|
214
|
+
kind: 'scalar',
|
|
215
|
+
dataSourceId: 'eleventy',
|
|
216
|
+
}, {
|
|
217
|
+
id: 'last',
|
|
218
|
+
label: 'last',
|
|
219
|
+
typeIds: ['string'],
|
|
220
|
+
kind: 'scalar',
|
|
221
|
+
dataSourceId: 'eleventy',
|
|
222
|
+
}],
|
|
223
|
+
}, {
|
|
224
|
+
id: 'eleventy',
|
|
225
|
+
label: 'eleventy',
|
|
226
|
+
dataSourceId: 'eleventy',
|
|
227
|
+
fields: [{
|
|
228
|
+
id: 'version',
|
|
229
|
+
label: 'version',
|
|
230
|
+
typeIds: ['string'],
|
|
231
|
+
kind: 'scalar',
|
|
232
|
+
dataSourceId: 'eleventy',
|
|
233
|
+
}, {
|
|
234
|
+
id: 'generator',
|
|
235
|
+
label: 'generator',
|
|
236
|
+
typeIds: ['string'],
|
|
237
|
+
kind: 'scalar',
|
|
238
|
+
dataSourceId: 'eleventy',
|
|
239
|
+
}, {
|
|
240
|
+
id: 'env',
|
|
241
|
+
label: 'env',
|
|
242
|
+
typeIds: ['env'],
|
|
243
|
+
kind: 'object',
|
|
244
|
+
dataSourceId: 'eleventy',
|
|
245
|
+
}]
|
|
246
|
+
}, {
|
|
247
|
+
id: 'env',
|
|
248
|
+
label: 'env',
|
|
249
|
+
dataSourceId: 'eleventy',
|
|
250
|
+
fields: [{
|
|
251
|
+
id: 'root',
|
|
252
|
+
label: 'root',
|
|
253
|
+
typeIds: ['string'],
|
|
254
|
+
kind: 'scalar',
|
|
255
|
+
dataSourceId: 'eleventy',
|
|
256
|
+
}, {
|
|
257
|
+
id: 'config',
|
|
258
|
+
label: 'config',
|
|
259
|
+
typeIds: ['string'],
|
|
260
|
+
kind: 'scalar',
|
|
261
|
+
dataSourceId: 'eleventy',
|
|
262
|
+
}, {
|
|
263
|
+
id: 'source',
|
|
264
|
+
label: 'source',
|
|
265
|
+
typeIds: ['string'],
|
|
266
|
+
kind: 'scalar',
|
|
267
|
+
dataSourceId: 'eleventy',
|
|
268
|
+
}, {
|
|
269
|
+
id: 'runMode',
|
|
270
|
+
label: 'runMode',
|
|
271
|
+
typeIds: ['string'],
|
|
272
|
+
kind: 'scalar',
|
|
273
|
+
dataSourceId: 'eleventy',
|
|
274
|
+
}],
|
|
275
|
+
}, {
|
|
276
|
+
id: 'locale_link',
|
|
277
|
+
label: 'locale_link',
|
|
278
|
+
dataSourceId: 'eleventy',
|
|
279
|
+
fields: [{
|
|
280
|
+
id: 'url',
|
|
281
|
+
label: 'url',
|
|
282
|
+
typeIds: ['string'],
|
|
283
|
+
kind: 'scalar',
|
|
284
|
+
dataSourceId: 'eleventy',
|
|
285
|
+
}, {
|
|
286
|
+
id: 'lang',
|
|
287
|
+
label: 'lang',
|
|
288
|
+
typeIds: ['string'],
|
|
289
|
+
kind: 'scalar',
|
|
290
|
+
dataSourceId: 'eleventy',
|
|
291
|
+
}, {
|
|
292
|
+
id: 'label',
|
|
293
|
+
label: 'label',
|
|
294
|
+
typeIds: ['string'],
|
|
295
|
+
kind: 'scalar',
|
|
296
|
+
dataSourceId: 'eleventy',
|
|
297
|
+
}],
|
|
298
|
+
}]
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Implement IDatasource
|
|
303
|
+
*/
|
|
304
|
+
getQueryables(): Field[] {
|
|
305
|
+
return [{
|
|
306
|
+
id: 'page',
|
|
307
|
+
label: 'page',
|
|
308
|
+
typeIds: ['page'],
|
|
309
|
+
kind: 'object',
|
|
310
|
+
dataSourceId: 'eleventy',
|
|
311
|
+
//}, {
|
|
312
|
+
// id: 'eleventy',
|
|
313
|
+
// label: 'eleventy',
|
|
314
|
+
// typeIds: ['eleventy'],
|
|
315
|
+
// kind: 'object',
|
|
316
|
+
// dataSourceId: 'eleventy',
|
|
317
|
+
//}, {
|
|
318
|
+
// id: 'env',
|
|
319
|
+
// label: 'env',
|
|
320
|
+
// typeIds: ['env'],
|
|
321
|
+
// kind: 'object',
|
|
322
|
+
// dataSourceId: 'eleventy',
|
|
323
|
+
}]
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fetchValues(query: string): Promise<unknown> {
|
|
327
|
+
console.log('fetch', {query})
|
|
328
|
+
throw new Error('not implemented yet')
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export class EleventyDataSourceTest extends EleventyDataSource {
|
|
333
|
+
}
|