@silexlabs/silex 3.5.1-1 → 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/dist/client/client/grapesjs/PublicationManager.d.ts +43 -0
- package/dist/client/css/admin.css +29 -5
- package/dist/client/index.html +2 -2
- package/dist/client/js/main.js +157 -154
- package/package.json +4 -4
- package/src/css/grapesjs.scss +5 -0
- package/src/css/page-panel.scss +24 -5
- package/src/ts/client/grapesjs/PublicationManager.ts +159 -1
- package/src/ts/client/grapesjs/cms/buttons.ts +67 -41
- package/src/ts/client/grapesjs/cms/collection-pages.ts +22 -13
- package/src/ts/client/grapesjs/css-props.ts +15 -16
- package/src/ts/client/grapesjs/page-panel.ts +119 -5
- package/src/ts/client/grapesjs/semantic.ts +4 -0
- package/src/ts/client/grapesjs/storage.ts +6 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silexlabs/silex",
|
|
3
3
|
"description": "Free and easy website builder for everyone.",
|
|
4
|
-
"version": "3.5.1-
|
|
4
|
+
"version": "3.5.1-2",
|
|
5
5
|
"version:backwardcompat": "3.0.0",
|
|
6
6
|
"author": "Alex Hoyau (https://lexoyo.me/)",
|
|
7
7
|
"main": "dist/server/server/index.js",
|
|
@@ -99,13 +99,13 @@
|
|
|
99
99
|
"dependencies": {
|
|
100
100
|
"@fontsource/ubuntu": "^5.0.13",
|
|
101
101
|
"@fortawesome/fontawesome-free": "6.6.0",
|
|
102
|
-
"@silexlabs/grapesjs-advanced-selector": "1.3.0",
|
|
103
|
-
"@silexlabs/grapesjs-data-source": "0.1.1-
|
|
102
|
+
"@silexlabs/grapesjs-advanced-selector": "1.3.1-0",
|
|
103
|
+
"@silexlabs/grapesjs-data-source": "0.1.1-6",
|
|
104
104
|
"@silexlabs/grapesjs-filter-styles": "1.2.0",
|
|
105
105
|
"@silexlabs/grapesjs-fonts": "1.2.0",
|
|
106
106
|
"@silexlabs/grapesjs-keymaps-dialog": "1.2.0",
|
|
107
107
|
"@silexlabs/grapesjs-loading": "1.2.0",
|
|
108
|
-
"@silexlabs/grapesjs-notifications": "0.2.0",
|
|
108
|
+
"@silexlabs/grapesjs-notifications": "0.2.1-0",
|
|
109
109
|
"@silexlabs/grapesjs-storage-rate-limit": "1.2.0",
|
|
110
110
|
"@silexlabs/grapesjs-symbols": "1.3.0",
|
|
111
111
|
"@silexlabs/grapesjs-ui-suggest-classes": "1.2.0",
|
package/src/css/grapesjs.scss
CHANGED
|
@@ -233,6 +233,11 @@
|
|
|
233
233
|
.gjs-sm-sector .gjs-sm-sector-label {
|
|
234
234
|
margin-right: 0.3em
|
|
235
235
|
}
|
|
236
|
+
|
|
237
|
+
.fa-grip-vertical:before {
|
|
238
|
+
content: "\f58e";
|
|
239
|
+
font-weight: 900;
|
|
240
|
+
}
|
|
236
241
|
.gjs-sm-sector .gjs-sm-sector-title::before,
|
|
237
242
|
.gjs-sm-sector .gjs-sm-sector-title::after {
|
|
238
243
|
content: 'o';
|
package/src/css/page-panel.scss
CHANGED
|
@@ -19,13 +19,21 @@
|
|
|
19
19
|
align-items: center;
|
|
20
20
|
background-color: $darkerPrimaryColor;
|
|
21
21
|
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
|
|
22
|
-
cursor:
|
|
22
|
+
cursor: cursor;
|
|
23
|
+
transition: opacity 0.2s ease, background-color 0.2s ease;
|
|
23
24
|
&:hover {
|
|
24
25
|
background-color: $primaryColor;
|
|
25
26
|
.pages__page-name {
|
|
26
27
|
color: $quaternaryColor;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
&:active {
|
|
31
|
+
cursor: grabbing;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
.pages__page-dragging {
|
|
35
|
+
opacity: 0.4;
|
|
36
|
+
cursor: grabbing !important;
|
|
29
37
|
}
|
|
30
38
|
.pages__page-selected {
|
|
31
39
|
background-color: $lighterPrimaryColor;
|
|
@@ -41,20 +49,31 @@
|
|
|
41
49
|
white-space: nowrap;
|
|
42
50
|
overflow: hidden;
|
|
43
51
|
text-overflow: ellipsis;
|
|
44
|
-
|
|
45
|
-
font-size: initial;
|
|
52
|
+
font-size: 13px;
|
|
46
53
|
text-transform: capitalize;
|
|
47
|
-
padding:
|
|
54
|
+
padding: 6px;
|
|
48
55
|
flex: 1;
|
|
49
56
|
text-align: left;
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 4px;
|
|
50
60
|
}
|
|
51
61
|
.pages__icon {
|
|
52
|
-
padding:
|
|
62
|
+
padding: 6px;
|
|
53
63
|
cursor: pointer;
|
|
64
|
+
font-size: 12px;
|
|
54
65
|
}
|
|
55
66
|
.pages__icon:hover {
|
|
56
67
|
color: $quaternaryColor;
|
|
57
68
|
}
|
|
69
|
+
.pages__drag-handle {
|
|
70
|
+
cursor: grab;
|
|
71
|
+
opacity: 0.6;
|
|
72
|
+
flex-shrink: 0;
|
|
73
|
+
&:hover {
|
|
74
|
+
opacity: 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
58
77
|
}
|
|
59
78
|
.#{$pn-prefix}buttons {
|
|
60
79
|
flex-direction: column;
|
|
@@ -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 || ''}
|
|
@@ -58,76 +58,102 @@ export default function(editor: Editor/*, opts: EleventyPluginOptions*/): void {
|
|
|
58
58
|
panelButtons.add([{
|
|
59
59
|
id: 'refresh-data-sources',
|
|
60
60
|
className: `${REFRESH_BUTTON_BASE_CLASS} fa-refresh`,
|
|
61
|
-
command:
|
|
62
|
-
// Refresh all data sources
|
|
63
|
-
editor.runCommand(COMMAND_REFRESH)
|
|
64
|
-
},
|
|
61
|
+
command: COMMAND_REFRESH,
|
|
65
62
|
togglable: false,
|
|
66
|
-
attributes: { title: 'Refresh data sources (reload dynamic content) - Ctrl+Alt+R' }
|
|
63
|
+
attributes: { title: 'Refresh data sources (reload dynamic content) - Ctrl+Alt+R' }
|
|
67
64
|
}, {
|
|
68
65
|
id: 'toggle-data-source-preview',
|
|
69
|
-
className: 'fa fa-fw data-source-preview-toggle fa-table',
|
|
70
|
-
command:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
74
|
-
togglable: true,
|
|
75
|
-
active: true,
|
|
76
|
-
attributes: { title: 'Enable data source preview (Ctrl+Alt+V)' },
|
|
66
|
+
className: 'fa fa-fw data-source-preview-toggle fa-table gjs-pn-active gjs-four-color',
|
|
67
|
+
command: COMMAND_PREVIEW_TOGGLE,
|
|
68
|
+
togglable: false,
|
|
69
|
+
attributes: { title: 'Enable/Disable data source preview (Ctrl+Alt+V)' }
|
|
77
70
|
}, {
|
|
78
|
-
//
|
|
71
|
+
// Separator for visual grouping of data source buttons in the panel
|
|
79
72
|
id: 'data-source-separator',
|
|
80
73
|
className: 'data-source-separator',
|
|
81
|
-
command: () => {},
|
|
74
|
+
command: () => {},
|
|
82
75
|
togglable: false,
|
|
83
|
-
attributes: { title: '' }
|
|
76
|
+
attributes: { title: '' }
|
|
84
77
|
}], { at: 0 })
|
|
85
78
|
|
|
86
|
-
//
|
|
87
|
-
|
|
79
|
+
// Update the preview toggle button's tooltip and visual state based on activation
|
|
80
|
+
let isActive = true
|
|
81
|
+
function updatePreviewActiveButton(editor) {
|
|
88
82
|
const button = editor.Panels.getButton('options', 'toggle-data-source-preview')
|
|
89
83
|
if (button) {
|
|
90
|
-
button.set('
|
|
91
|
-
|
|
84
|
+
button.set('attributes', {
|
|
85
|
+
title: isActive
|
|
86
|
+
? 'Disable data source preview (Ctrl+Alt+V)'
|
|
87
|
+
: 'Enable data source preview (Ctrl+Alt+V)'
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
const el = getPanelButtonEl(editor, 'options', '.data-source-preview-toggle')
|
|
91
|
+
if (el) {
|
|
92
|
+
if (isActive) {
|
|
93
|
+
el.classList.add('gjs-pn-active', 'gjs-four-color')
|
|
94
|
+
} else {
|
|
95
|
+
el.classList.remove('gjs-pn-active', 'gjs-four-color')
|
|
96
|
+
}
|
|
92
97
|
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// FIXME: This should not be needed as the state is alreay set in the button definition
|
|
101
|
+
setTimeout(() => updatePreviewActiveButton(editor), 1000)
|
|
102
|
+
|
|
103
|
+
editor.on(PREVIEW_ACTIVATED, () => {
|
|
104
|
+
isActive = true
|
|
105
|
+
setTimeout(() => updatePreviewActiveButton(editor))
|
|
93
106
|
})
|
|
94
107
|
editor.on(PREVIEW_DEACTIVATED, () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
button.set('active', false)
|
|
98
|
-
button.set('attributes', { title: 'Enable data source preview (Ctrl+Alt+V)' })
|
|
99
|
-
}
|
|
108
|
+
isActive = false
|
|
109
|
+
updatePreviewActiveButton(editor)
|
|
100
110
|
})
|
|
101
111
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
// Returns the DOM element for a button in a panel using a selector
|
|
113
|
+
function getPanelButtonEl(editor, panelId, selector) {
|
|
114
|
+
const panel = editor.Panels.getPanel(panelId)
|
|
115
|
+
if (!panel) return null
|
|
116
|
+
console.log({panel, selector}, panel.view?.el, panel.view?.el?.querySelector(selector))
|
|
117
|
+
return panel.view
|
|
118
|
+
?.el
|
|
119
|
+
?.querySelector(selector)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Update the refresh button's visual state and tooltip based on data source loading status
|
|
123
|
+
function setRefreshButtonState(editor, status) {
|
|
105
124
|
const button = editor.Panels.getButton('options', 'refresh-data-sources')
|
|
106
|
-
if (button)
|
|
125
|
+
if (!button) return
|
|
126
|
+
switch (status) {
|
|
127
|
+
case 'loading':
|
|
107
128
|
button.set('className', `${REFRESH_BUTTON_BASE_CLASS} fa-spinner`)
|
|
108
129
|
button.set('title', 'Refreshing data sources (loading dynamic content)...')
|
|
130
|
+
break
|
|
131
|
+
case 'error':
|
|
132
|
+
button.set('className', `${REFRESH_BUTTON_BASE_CLASS} fa-exclamation-triangle`)
|
|
133
|
+
button.set('title', 'Error refreshing data sources, check the notifications')
|
|
134
|
+
break
|
|
135
|
+
default:
|
|
136
|
+
button.set('className', `${REFRESH_BUTTON_BASE_CLASS} fa-refresh`)
|
|
137
|
+
button.set('title', 'Refresh data sources (reload dynamic content) - Ctrl+Alt+R')
|
|
109
138
|
}
|
|
110
|
-
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Update refresh button state on data source events
|
|
111
142
|
editor.on(`
|
|
112
143
|
${ DATA_SOURCE_DATA_LOAD_END }
|
|
113
144
|
${ DATA_SOURCE_DATA_LOAD_CANCEL }
|
|
114
145
|
`, () => {
|
|
115
|
-
|
|
116
|
-
if (button) {
|
|
117
|
-
button.set('className', `${REFRESH_BUTTON_BASE_CLASS} fa-refresh`)
|
|
118
|
-
button.set('title', 'Refresh data sources (reload dynamic content) - Ctrl+Alt+R')
|
|
119
|
-
}
|
|
146
|
+
setRefreshButtonState(editor, 'ready')
|
|
120
147
|
})
|
|
121
148
|
editor.on(DATA_SOURCE_ERROR, (error: Error) => {
|
|
122
149
|
console.error('Data source error', error)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
150
|
+
setRefreshButtonState(editor, 'error')
|
|
151
|
+
})
|
|
152
|
+
editor.on(DATA_SOURCE_DATA_LOAD_START, () => {
|
|
153
|
+
setRefreshButtonState(editor, 'loading')
|
|
128
154
|
})
|
|
129
155
|
|
|
130
|
-
//
|
|
156
|
+
// Register keyboard shortcuts for data source actions
|
|
131
157
|
editor.Keymaps.add('data-source:refresh', 'ctrl+alt+r', COMMAND_REFRESH, {
|
|
132
158
|
prevent: true,
|
|
133
159
|
})
|
|
@@ -172,24 +172,33 @@ function getItemDisplayName(editor: Editor, index: number): string {
|
|
|
172
172
|
const settings = page?.get('settings') as Silex11tyPluginWebsiteSettings | undefined
|
|
173
173
|
|
|
174
174
|
const body = page?.getMainComponent()
|
|
175
|
-
if (!body) return
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
175
|
+
if (!body) return `Page ${index + 1}`
|
|
176
|
+
|
|
177
|
+
// Try SEO title first
|
|
178
|
+
if (settings?.eleventySeoTitle) {
|
|
179
|
+
const titleExpression = toExpression(settings.eleventySeoTitle)
|
|
180
|
+
if (Array.isArray(titleExpression) && titleExpression.length > 0) {
|
|
181
|
+
const title = getValue(titleExpression, body, false)
|
|
182
|
+
if (typeof title === 'string' && title.trim()) return title
|
|
183
|
+
}
|
|
184
|
+
}
|
|
180
185
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
// Try permalink second
|
|
187
|
+
if (settings?.eleventyPermalink) {
|
|
188
|
+
const permalinkExpression = toExpression(settings.eleventyPermalink)
|
|
189
|
+
if (Array.isArray(permalinkExpression) && permalinkExpression.length > 0) {
|
|
190
|
+
const permalink = getValue(permalinkExpression, body, false)
|
|
191
|
+
if (typeof permalink === 'string' && permalink.trim()) return permalink
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
|
-
|
|
194
|
+
|
|
195
|
+
// Final fallback to index-based name (1-indexed)
|
|
196
|
+
return `Page ${index + 1}`
|
|
187
197
|
} catch (e) {
|
|
188
|
-
console.error('Error generating
|
|
198
|
+
console.error('Error generating display name for item:', e)
|
|
199
|
+
// Final fallback to index-based name (1-indexed)
|
|
200
|
+
return `Page ${index + 1}`
|
|
189
201
|
}
|
|
190
|
-
|
|
191
|
-
// Final fallback to index-based name
|
|
192
|
-
return `Page ${index + 1}`
|
|
193
202
|
}
|
|
194
203
|
|
|
195
204
|
function handleItemClick(editor: Editor, index: number) {
|
|
@@ -243,19 +243,6 @@ export default (editor: Editor, opts) => {
|
|
|
243
243
|
info: 'Controls content overflow in a block.',
|
|
244
244
|
}, { at: 6 })
|
|
245
245
|
|
|
246
|
-
editor.StyleManager.addProperty('dimension', {
|
|
247
|
-
name: 'Scroll Behavior',
|
|
248
|
-
property: 'scroll-behavior',
|
|
249
|
-
type: 'select',
|
|
250
|
-
defaults: '',
|
|
251
|
-
options: [
|
|
252
|
-
{ id: '', value: '', name: '' },
|
|
253
|
-
{ id: 'auto', value: 'auto', name: 'auto' },
|
|
254
|
-
{ id: 'smooth', value: 'smooth', name: 'smooth' }
|
|
255
|
-
],
|
|
256
|
-
info: 'Sets smooth or auto scroll behavior.',
|
|
257
|
-
}, { at: 7 })
|
|
258
|
-
|
|
259
246
|
editor.StyleManager.removeProperty('dimension', 'margin')
|
|
260
247
|
editor.StyleManager.addProperty('dimension', {
|
|
261
248
|
name: 'Margin',
|
|
@@ -794,6 +781,18 @@ export default (editor: Editor, opts) => {
|
|
|
794
781
|
],
|
|
795
782
|
info: 'The column-span CSS property makes it possible for an element to span across all columns when its value is set to all.',
|
|
796
783
|
}, { at: 9 })
|
|
784
|
+
editor.StyleManager.addProperty('extra', {
|
|
785
|
+
name: 'Scroll Behavior',
|
|
786
|
+
property: 'scroll-behavior',
|
|
787
|
+
type: 'select',
|
|
788
|
+
defaults: '',
|
|
789
|
+
options: [
|
|
790
|
+
{ id: '', value: '', name: '' },
|
|
791
|
+
{ id: 'auto', value: 'auto', name: 'auto' },
|
|
792
|
+
{ id: 'smooth', value: 'smooth', name: 'smooth' }
|
|
793
|
+
],
|
|
794
|
+
info: 'Sets smooth or auto scroll behavior.',
|
|
795
|
+
}, { at: 10 })
|
|
797
796
|
editor.StyleManager.addProperty('extra', {
|
|
798
797
|
name: 'Scroll Snap Type',
|
|
799
798
|
property: 'scroll-snap-type',
|
|
@@ -824,7 +823,7 @@ export default (editor: Editor, opts) => {
|
|
|
824
823
|
],
|
|
825
824
|
}],
|
|
826
825
|
info: 'The scroll-snap-type CSS property sets the direction and mode of enforced snap points on the scroll container.',
|
|
827
|
-
}, { at:
|
|
826
|
+
}, { at: 11 })
|
|
828
827
|
editor.StyleManager.addProperty('extra', {
|
|
829
828
|
name: 'Scroll Padding',
|
|
830
829
|
property: 'scroll-padding',
|
|
@@ -832,7 +831,7 @@ export default (editor: Editor, opts) => {
|
|
|
832
831
|
defaults: '',
|
|
833
832
|
units: ['px', 'em', 'rem', '%'],
|
|
834
833
|
info: 'Defines offsets for scroll snapping.',
|
|
835
|
-
}, { at:
|
|
834
|
+
}, { at: 12 })
|
|
836
835
|
editor.StyleManager.addProperty('extra', {
|
|
837
836
|
name: 'Scroll Snap Align',
|
|
838
837
|
property: 'scroll-snap-align',
|
|
@@ -846,7 +845,7 @@ export default (editor: Editor, opts) => {
|
|
|
846
845
|
{ id: 'center', value: 'center', name: 'center' },
|
|
847
846
|
],
|
|
848
847
|
info: 'Aligns elements to scroll snaps.',
|
|
849
|
-
}, { at:
|
|
848
|
+
}, { at: 13 })
|
|
850
849
|
|
|
851
850
|
editor.SelectorManager.states.add({name: 'before', label: 'Before'})
|
|
852
851
|
editor.SelectorManager.states.add({name: 'after', label: 'After'})
|