@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
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Component, Editor } from 'grapesjs'
|
|
2
|
+
import { ClientConfig } from '../../config'
|
|
3
|
+
|
|
4
|
+
export default function(editor: Editor/*, opts: EleventyPluginOptions*/): void {
|
|
5
|
+
// **
|
|
6
|
+
// Shortcode
|
|
7
|
+
editor.BlockManager.add('eleventy-shortcode', {
|
|
8
|
+
label: 'Shortcode',
|
|
9
|
+
category: 'Eleventy',
|
|
10
|
+
content: { type: 'eleventy-shortcode' },
|
|
11
|
+
media: '<span style="font-size: 50px;">{%</span>',
|
|
12
|
+
})
|
|
13
|
+
editor.DomComponents.addType('eleventy-shortcode', {
|
|
14
|
+
model: {
|
|
15
|
+
defaults: {
|
|
16
|
+
traits: [{
|
|
17
|
+
type: 'shortcode-name',
|
|
18
|
+
label: 'Shortcode name',
|
|
19
|
+
name: 'shortcode_name',
|
|
20
|
+
placeholder: 'shortcode_name',
|
|
21
|
+
}, {
|
|
22
|
+
type: 'shortcode-attributes',
|
|
23
|
+
label: 'Shortcode attributes',
|
|
24
|
+
name: 'shortcode_attributes',
|
|
25
|
+
placeholder: 'param1, "example param 2"',
|
|
26
|
+
}],
|
|
27
|
+
},
|
|
28
|
+
init() {
|
|
29
|
+
let pending = false
|
|
30
|
+
editor.on('component:add component:remove component:mount', model => {
|
|
31
|
+
if (pending) return
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
// FIXME: why do we need a timeout?
|
|
34
|
+
if (model.parent() === this || model === this) {
|
|
35
|
+
pending = true
|
|
36
|
+
updateShortcode(this)
|
|
37
|
+
pending = false
|
|
38
|
+
}
|
|
39
|
+
}, 500)
|
|
40
|
+
})
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
function updateShortcode(component: Component) {
|
|
45
|
+
const { shortcode_attributes, shortcode_name } = component.get('attributes')
|
|
46
|
+
const componentsToKeep = component.components()
|
|
47
|
+
.filter(c => c.view?.el?.nodeType === 1)
|
|
48
|
+
if (componentsToKeep.length) {
|
|
49
|
+
// Paired shortcode
|
|
50
|
+
component.components(`
|
|
51
|
+
{% ${shortcode_name} ${shortcode_attributes ?? ''} %}
|
|
52
|
+
<div class="replace_content"></div>
|
|
53
|
+
{% end${shortcode_name} %}
|
|
54
|
+
`)
|
|
55
|
+
const replaceContentDiv = component.components().find(c => c.getClasses().includes('replace_content'))
|
|
56
|
+
if (!replaceContentDiv) {
|
|
57
|
+
console.error('No replace_content found', { component, componentsToKeep })
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
replaceContentDiv.replaceWith(componentsToKeep)
|
|
61
|
+
} else {
|
|
62
|
+
component.components(`
|
|
63
|
+
{% ${shortcode_name} ${shortcode_attributes ?? ''} %}
|
|
64
|
+
`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
editor.TraitManager.addType('shortcode-name', {
|
|
68
|
+
onEvent({ component }) {
|
|
69
|
+
updateShortcode(component)
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
editor.TraitManager.addType('shortcode-attributes', {
|
|
73
|
+
onEvent({ component }) {
|
|
74
|
+
updateShortcode(component)
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
// **
|
|
78
|
+
// Select element
|
|
79
|
+
editor.BlockManager.add('eleventy-select', {
|
|
80
|
+
label: 'select',
|
|
81
|
+
category: 'Eleventy',
|
|
82
|
+
media: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M22 9c0-.6-.5-1-1.3-1H3.4C2.5 8 2 8.4 2 9v6c0 .6.5 1 1.3 1h17.4c.8 0 1.3-.4 1.3-1V9zm-1 6H3V9h18v6z"></path><path d="M18.5 13l1.5-2h-3zM4 11.5h11v1H4z"></path></svg>',
|
|
83
|
+
content: { type: 'eleventy-select' },
|
|
84
|
+
})
|
|
85
|
+
editor.DomComponents.addType('eleventy-select', {
|
|
86
|
+
model: {
|
|
87
|
+
defaults: {
|
|
88
|
+
tagName: 'select',
|
|
89
|
+
droppable: true,
|
|
90
|
+
// // Prevent the drop down from opening on click
|
|
91
|
+
// script: function() {
|
|
92
|
+
// this.addEventListener('mousedown', event => {
|
|
93
|
+
// event.preventDefault()
|
|
94
|
+
// })
|
|
95
|
+
// },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
// **
|
|
100
|
+
// Option element
|
|
101
|
+
editor.BlockManager.add('eleventy-option', {
|
|
102
|
+
label: 'option',
|
|
103
|
+
category: 'Eleventy',
|
|
104
|
+
media: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
|
|
105
|
+
content: { type: 'eleventy-option' },
|
|
106
|
+
})
|
|
107
|
+
editor.DomComponents.addType('eleventy-option', {
|
|
108
|
+
model: {
|
|
109
|
+
defaults: {
|
|
110
|
+
tagName: 'option',
|
|
111
|
+
droppable: false,
|
|
112
|
+
//draggable: 'select, optgroup', // Sometimes we need a container between the select and the option to have states inside the loop
|
|
113
|
+
traits: [
|
|
114
|
+
{
|
|
115
|
+
type: 'text',
|
|
116
|
+
label: 'Value',
|
|
117
|
+
name: 'value',
|
|
118
|
+
placeholder: 'Value',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Editor } from 'grapesjs'
|
|
2
|
+
import { ClientConfig } from '../../config'
|
|
3
|
+
import { COMMAND_REFRESH, COMMAND_PREVIEW_ACTIVATE, COMMAND_PREVIEW_DEACTIVATE, DATA_SOURCE_CHANGED, DATA_SOURCE_DATA_LOAD_CANCEL, DATA_SOURCE_DATA_LOAD_END, DATA_SOURCE_DATA_LOAD_START, DATA_SOURCE_ERROR, COMMAND_PREVIEW_TOGGLE, PREVIEW_DEACTIVATED, PREVIEW_ACTIVATED } from '@silexlabs/grapesjs-data-source'
|
|
4
|
+
|
|
5
|
+
document.querySelector('head')?.insertAdjacentHTML('beforeend', `
|
|
6
|
+
<style>
|
|
7
|
+
.data-source-refresh {
|
|
8
|
+
&.fa-refresh:before {
|
|
9
|
+
content: "\\f021";
|
|
10
|
+
font-weight: 900;
|
|
11
|
+
}
|
|
12
|
+
&.fa-refresh:hover {
|
|
13
|
+
transform: rotate(180deg);
|
|
14
|
+
transition: transform 0.5s;
|
|
15
|
+
}
|
|
16
|
+
&.fa-spinner:before {
|
|
17
|
+
content: "\\f110";
|
|
18
|
+
font-weight: 900;
|
|
19
|
+
}
|
|
20
|
+
&.fa-spinner {
|
|
21
|
+
animation: fa-spin 2s infinite linear;
|
|
22
|
+
}
|
|
23
|
+
&.fa-exclamation-triangle:before {
|
|
24
|
+
content: "\\f071";
|
|
25
|
+
font-weight: 900;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
.data-source-preview-toggle {
|
|
29
|
+
&.fa-table:before {
|
|
30
|
+
content: "\\f0ce";
|
|
31
|
+
font-weight: 900;
|
|
32
|
+
}
|
|
33
|
+
&.gjs-pn-active {
|
|
34
|
+
background-color: rgba(0, 0, 0, 0.15);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
.gjs-pn-btn.data-source-separator {
|
|
38
|
+
min-width: 1px;
|
|
39
|
+
height: 24px;
|
|
40
|
+
min-height: auto;
|
|
41
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
42
|
+
margin: 0 15px 0 10px;
|
|
43
|
+
padding: 0;
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
cursor: default;
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
48
|
+
`)
|
|
49
|
+
|
|
50
|
+
const REFRESH_BUTTON_BASE_CLASS = 'fa fa-fw data-source-refresh'
|
|
51
|
+
|
|
52
|
+
export default function(editor: Editor/*, opts: EleventyPluginOptions*/): void {
|
|
53
|
+
// Get the panel to add buttons at specific positions
|
|
54
|
+
const panel = editor.Panels.getPanel('options')
|
|
55
|
+
const panelButtons = panel.get('buttons')
|
|
56
|
+
|
|
57
|
+
// Add data source buttons at the beginning of the options panel
|
|
58
|
+
panelButtons.add([{
|
|
59
|
+
id: 'refresh-data-sources',
|
|
60
|
+
className: `${REFRESH_BUTTON_BASE_CLASS} fa-refresh`,
|
|
61
|
+
command: COMMAND_REFRESH,
|
|
62
|
+
togglable: false,
|
|
63
|
+
attributes: { title: 'Refresh data sources (reload dynamic content) - Ctrl+Alt+R' }
|
|
64
|
+
}, {
|
|
65
|
+
id: 'toggle-data-source-preview',
|
|
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)' }
|
|
70
|
+
}, {
|
|
71
|
+
// Separator for visual grouping of data source buttons in the panel
|
|
72
|
+
id: 'data-source-separator',
|
|
73
|
+
className: 'data-source-separator',
|
|
74
|
+
command: () => {},
|
|
75
|
+
togglable: false,
|
|
76
|
+
attributes: { title: '' }
|
|
77
|
+
}], { at: 0 })
|
|
78
|
+
|
|
79
|
+
// Update the preview toggle button's tooltip and visual state based on activation
|
|
80
|
+
let isActive = true
|
|
81
|
+
function updatePreviewActiveButton(editor) {
|
|
82
|
+
const button = editor.Panels.getButton('options', 'toggle-data-source-preview')
|
|
83
|
+
if (button) {
|
|
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
|
+
}
|
|
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))
|
|
106
|
+
})
|
|
107
|
+
editor.on(PREVIEW_DEACTIVATED, () => {
|
|
108
|
+
isActive = false
|
|
109
|
+
updatePreviewActiveButton(editor)
|
|
110
|
+
})
|
|
111
|
+
|
|
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) {
|
|
124
|
+
const button = editor.Panels.getButton('options', 'refresh-data-sources')
|
|
125
|
+
if (!button) return
|
|
126
|
+
switch (status) {
|
|
127
|
+
case 'loading':
|
|
128
|
+
button.set('className', `${REFRESH_BUTTON_BASE_CLASS} fa-spinner`)
|
|
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')
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Update refresh button state on data source events
|
|
142
|
+
editor.on(`
|
|
143
|
+
${ DATA_SOURCE_DATA_LOAD_END }
|
|
144
|
+
${ DATA_SOURCE_DATA_LOAD_CANCEL }
|
|
145
|
+
`, () => {
|
|
146
|
+
setRefreshButtonState(editor, 'ready')
|
|
147
|
+
})
|
|
148
|
+
editor.on(DATA_SOURCE_ERROR, (error: Error) => {
|
|
149
|
+
console.error('Data source error', error)
|
|
150
|
+
setRefreshButtonState(editor, 'error')
|
|
151
|
+
})
|
|
152
|
+
editor.on(DATA_SOURCE_DATA_LOAD_START, () => {
|
|
153
|
+
setRefreshButtonState(editor, 'loading')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Register keyboard shortcuts for data source actions
|
|
157
|
+
editor.Keymaps.add('data-source:refresh', 'ctrl+alt+r', COMMAND_REFRESH, {
|
|
158
|
+
prevent: true,
|
|
159
|
+
})
|
|
160
|
+
editor.Keymaps.add('data-source:preview-toggle', 'ctrl+alt+v', COMMAND_PREVIEW_TOGGLE, {
|
|
161
|
+
prevent: true,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { ClientConfig } from '../../config'
|
|
2
|
+
import { CMS_SETTINGS_SECTION_ID, EleventyPluginOptions, Silex11tyPluginWebsiteSettings } from './index'
|
|
3
|
+
import { Editor } from 'grapesjs'
|
|
4
|
+
import { html, render } from 'lit-html'
|
|
5
|
+
import { COMMAND_PREVIEW_REFRESH, COMMAND_REFRESH, DATA_SOURCE_DATA_LOAD_END, getState, getValue, toExpression, setPreviewIndex, Property } from '@silexlabs/grapesjs-data-source'
|
|
6
|
+
import { TemplateResult } from 'lit-html'
|
|
7
|
+
import { ClientEvent } from '../../events'
|
|
8
|
+
import { cmdOpenSettings } from '../settings'
|
|
9
|
+
import { updatePaginationStates } from './states'
|
|
10
|
+
import { debounce } from '../../utils'
|
|
11
|
+
|
|
12
|
+
// Add CSS for collection pages
|
|
13
|
+
document.querySelector('head')?.insertAdjacentHTML('beforeend', `
|
|
14
|
+
<style>
|
|
15
|
+
.pages__empty {
|
|
16
|
+
padding: 16px;
|
|
17
|
+
text-align: center;
|
|
18
|
+
color: #888;
|
|
19
|
+
font-size: 12px;
|
|
20
|
+
font-style: italic;
|
|
21
|
+
}
|
|
22
|
+
.pages__page {
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
}
|
|
25
|
+
.pages__page:hover {
|
|
26
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
27
|
+
}
|
|
28
|
+
</style>
|
|
29
|
+
`)
|
|
30
|
+
|
|
31
|
+
let currentPage = 0
|
|
32
|
+
export const EVENT_UPDATE_PAGE_LIST = 'update:collection:pages:list'
|
|
33
|
+
|
|
34
|
+
export default function (editor: Editor, opts: EleventyPluginOptions): void {
|
|
35
|
+
let done = false
|
|
36
|
+
editor.once(`
|
|
37
|
+
${ ClientEvent.STARTUP_END }
|
|
38
|
+
storage:end:load
|
|
39
|
+
`, (...args) => {
|
|
40
|
+
if (done) throw new Error('FIXME: this should never happen')
|
|
41
|
+
done = true
|
|
42
|
+
|
|
43
|
+
if (!opts.enable11ty) return // Do not add the settings if 11ty is disabled
|
|
44
|
+
|
|
45
|
+
const pagesContainer = editor.Panels.getPanel('project-bar-container')
|
|
46
|
+
?.view
|
|
47
|
+
?.el
|
|
48
|
+
?.querySelector('.page-panel-container')
|
|
49
|
+
|
|
50
|
+
const container = document.createElement('div')
|
|
51
|
+
pagesContainer?.appendChild(container)
|
|
52
|
+
|
|
53
|
+
// Listen for data changes and page selection
|
|
54
|
+
editor.on(`
|
|
55
|
+
${ DATA_SOURCE_DATA_LOAD_END }
|
|
56
|
+
storage:load:end
|
|
57
|
+
page
|
|
58
|
+
`, debounce<() => void>(() => {
|
|
59
|
+
currentPage = 0
|
|
60
|
+
renderCollectionPageList(editor, container)
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
editor.on(`
|
|
64
|
+
${ EVENT_UPDATE_PAGE_LIST }
|
|
65
|
+
`, debounce<() => void>(() => {
|
|
66
|
+
renderCollectionPageList(editor, container)
|
|
67
|
+
editor.runCommand(COMMAND_PREVIEW_REFRESH)
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
// Initial render
|
|
71
|
+
renderCollectionPageList(editor, container)
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderCollectionPageList(editor: Editor, container: HTMLElement) {
|
|
76
|
+
// Guard: Ensure Pages exist, there are pages, and a selected page has a main component
|
|
77
|
+
const pages = editor.Pages
|
|
78
|
+
if (
|
|
79
|
+
!pages ||
|
|
80
|
+
pages.getAll().length === 0 ||
|
|
81
|
+
!pages.getSelected()?.getMainComponent()
|
|
82
|
+
) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
render(getHtml(editor), container)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getHtml(editor: Editor): TemplateResult {
|
|
89
|
+
const groups = getCollectionData(editor)
|
|
90
|
+
const result = html`
|
|
91
|
+
<header class="project-bar__panel-header">
|
|
92
|
+
<h3 class="project-bar__panel-header-title">Collection Pages</h3>
|
|
93
|
+
</header>
|
|
94
|
+
<div class="pages__wrapper">
|
|
95
|
+
<section class="pages">
|
|
96
|
+
<main class="pages__main">
|
|
97
|
+
<div class="pages__list">
|
|
98
|
+
${groups.length === 0
|
|
99
|
+
? html`<div class="pages__empty">
|
|
100
|
+
No collection items found.
|
|
101
|
+
<a
|
|
102
|
+
href="#"
|
|
103
|
+
@click=${(event: MouseEvent) => {
|
|
104
|
+
event.preventDefault()
|
|
105
|
+
editor.runCommand(cmdOpenSettings, {
|
|
106
|
+
page: editor.Pages.getSelected(),
|
|
107
|
+
sectionId: CMS_SETTINGS_SECTION_ID,
|
|
108
|
+
})
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Add pagination data in the page settings
|
|
112
|
+
</a>
|
|
113
|
+
</div>`
|
|
114
|
+
: groups.map((items, index) => {
|
|
115
|
+
// Temporary pagination to this particular page
|
|
116
|
+
updatePaginationStates(editor, index, true)
|
|
117
|
+
return html`
|
|
118
|
+
<div
|
|
119
|
+
class="pages__page ${index === currentPage ? 'pages__page-selected' : ''}"
|
|
120
|
+
data-item-index="${index}"
|
|
121
|
+
@click=${() => handleItemClick(editor, index)}
|
|
122
|
+
>
|
|
123
|
+
<div class="pages__page-name">
|
|
124
|
+
${getItemDisplayName(editor, index)}
|
|
125
|
+
</div>
|
|
126
|
+
${index === currentPage ? html`<i class="pages__icon pages__remove-btn"></i>` : ''}
|
|
127
|
+
</div>
|
|
128
|
+
`
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
</div>
|
|
132
|
+
</main>
|
|
133
|
+
</section>
|
|
134
|
+
</div>
|
|
135
|
+
`
|
|
136
|
+
if (groups.length > 0) {
|
|
137
|
+
// Reset to current page
|
|
138
|
+
updatePaginationStates(editor, currentPage, true)
|
|
139
|
+
}
|
|
140
|
+
return result
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getCollectionData(editor: Editor): unknown[][] {
|
|
144
|
+
const page = editor.Pages.getSelected()
|
|
145
|
+
const body = page?.getMainComponent()
|
|
146
|
+
|
|
147
|
+
if (!body) return
|
|
148
|
+
|
|
149
|
+
const settings = page?.get('settings') as Silex11tyPluginWebsiteSettings | undefined
|
|
150
|
+
const pageData = toExpression(settings?.eleventyPageData) as (Property[] | null)
|
|
151
|
+
const pageSize = parseInt(settings?.eleventyPageSize || '1')
|
|
152
|
+
|
|
153
|
+
if (pageData && pageData.length > 0) {
|
|
154
|
+
const rawData = getValue(pageData, body as never, false)
|
|
155
|
+
|
|
156
|
+
if (Array.isArray(rawData) && rawData.length > 0) {
|
|
157
|
+
// Group rawData into chunks of size groupBy
|
|
158
|
+
const grouped: unknown[][] = []
|
|
159
|
+
for (let i = 0; i < rawData.length; i += pageSize) {
|
|
160
|
+
grouped.push(rawData.slice(i, i + pageSize))
|
|
161
|
+
}
|
|
162
|
+
return grouped
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return []
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getItemDisplayName(editor: Editor, index: number): string {
|
|
170
|
+
try {
|
|
171
|
+
const page = editor.Pages.getSelected()
|
|
172
|
+
const settings = page?.get('settings') as Silex11tyPluginWebsiteSettings | undefined
|
|
173
|
+
|
|
174
|
+
const body = page?.getMainComponent()
|
|
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
|
+
}
|
|
185
|
+
|
|
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
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Final fallback to index-based name (1-indexed)
|
|
196
|
+
return `Page ${index + 1}`
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error('Error generating display name for item:', e)
|
|
199
|
+
// Final fallback to index-based name (1-indexed)
|
|
200
|
+
return `Page ${index + 1}`
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function handleItemClick(editor: Editor, index: number) {
|
|
205
|
+
const page = editor.Pages.getSelected()
|
|
206
|
+
const body = page?.getMainComponent()
|
|
207
|
+
currentPage = index
|
|
208
|
+
|
|
209
|
+
if (!body) return
|
|
210
|
+
|
|
211
|
+
// Trigger the canvas refresh
|
|
212
|
+
editor.trigger(EVENT_UPDATE_PAGE_LIST)
|
|
213
|
+
// editor.Canvas.refresh()
|
|
214
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview DataSource configuration with defaults
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import DataSourcePlugin from '@silexlabs/grapesjs-data-source'
|
|
6
|
+
import { EleventyPluginOptions } from './index'
|
|
7
|
+
import { Editor, EditorConfig } from 'grapesjs'
|
|
8
|
+
|
|
9
|
+
const settingsEl = document.createElement('div')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default for the data source plugin to work without config
|
|
13
|
+
*/
|
|
14
|
+
export function getZeroConfig(editor: Editor): EleventyPluginOptions {
|
|
15
|
+
return {
|
|
16
|
+
view: {
|
|
17
|
+
el: () => editor.Panels.getPanel('views-container')?.view.el,
|
|
18
|
+
button: () => editor.Panels.getPanel('views')!.get('buttons')!.get('open-tm'),
|
|
19
|
+
settingsEl: () => settingsEl,
|
|
20
|
+
defaultFixed: true,
|
|
21
|
+
// Show all editors by default
|
|
22
|
+
disableStates: false,
|
|
23
|
+
disableAttributes: false,
|
|
24
|
+
disableProperties: false,
|
|
25
|
+
},
|
|
26
|
+
previewActive: true,
|
|
27
|
+
// Liquid filters - will be set by optionsToGrapesJsConfig
|
|
28
|
+
filters: [],
|
|
29
|
+
// Default data source
|
|
30
|
+
dataSources: [],
|
|
31
|
+
// Enable 11ty publication and filters
|
|
32
|
+
enable11ty: true,
|
|
33
|
+
// 11ty plugins
|
|
34
|
+
// fetchPlugin: { cache: 'no-cache' },
|
|
35
|
+
// i18nPlugin: true,
|
|
36
|
+
// Default publication paths
|
|
37
|
+
dir: {
|
|
38
|
+
input: '',
|
|
39
|
+
assets: 'assets',
|
|
40
|
+
css: 'css',
|
|
41
|
+
},
|
|
42
|
+
urls: {
|
|
43
|
+
assets: '/assets',
|
|
44
|
+
css: '/css',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Field, Filter, Options } from '@silexlabs/grapesjs-data-source'
|
|
2
|
+
import { html } from 'lit-html'
|
|
3
|
+
import { Component } from 'grapesjs'
|
|
4
|
+
|
|
5
|
+
// Export filters for use in client.ts
|
|
6
|
+
export const eleventyFilters: Filter[] = [{
|
|
7
|
+
type: 'filter',
|
|
8
|
+
id: 'log',
|
|
9
|
+
label: 'log (11ty)',
|
|
10
|
+
validate: (field: Field | null) => !!field,
|
|
11
|
+
output: type => type,
|
|
12
|
+
apply: (str) => {
|
|
13
|
+
console.info(str)
|
|
14
|
+
return str
|
|
15
|
+
},
|
|
16
|
+
options: {},
|
|
17
|
+
}, {
|
|
18
|
+
type: 'filter',
|
|
19
|
+
id: 'slugify',
|
|
20
|
+
label: 'slugify (11ty)',
|
|
21
|
+
validate: (field: Field | null) => !!field && field.typeIds.map(t => t.toLowerCase()).includes('string') && field.kind === 'scalar',
|
|
22
|
+
output: type => type,
|
|
23
|
+
apply: (str) => {
|
|
24
|
+
return str?.toString().toLowerCase().replace(/\s+/g, '-')
|
|
25
|
+
},
|
|
26
|
+
options: {},
|
|
27
|
+
}]
|
|
28
|
+
|
|
29
|
+
export const i18nFilters: Filter[] = [{
|
|
30
|
+
type: 'filter',
|
|
31
|
+
id: 'locale_url',
|
|
32
|
+
label: 'locale_url (11ty)',
|
|
33
|
+
validate: (input: Field | null) => !!input?.typeIds.map(t => t.toLowerCase()).includes('string') && input?.kind === 'scalar',
|
|
34
|
+
apply: (input: unknown/*, options: Options*/) => input,
|
|
35
|
+
output: (input: Field | null/*, options: Options*/) => ({ ...(input || {} as Field), typeIds: ['String'] }),
|
|
36
|
+
options: {
|
|
37
|
+
locale: '',
|
|
38
|
+
},
|
|
39
|
+
quotedOptions: ['locale'],
|
|
40
|
+
optionsForm: (selected: Component, input: Field | null, options: Options) => html`
|
|
41
|
+
<label>Locale
|
|
42
|
+
<input type="text" name="locale" value="${options.locale}" />
|
|
43
|
+
</label>
|
|
44
|
+
`,
|
|
45
|
+
}, {
|
|
46
|
+
type: 'filter',
|
|
47
|
+
id: 'locale_links',
|
|
48
|
+
label: 'locale_links (11ty)',
|
|
49
|
+
validate: (input: Field | null) => !!input?.typeIds.map(t => t.toLowerCase()).includes('string') && input?.kind === 'scalar',
|
|
50
|
+
apply: (input: unknown/*, options: Options*/) => ([{ url: input, lang: 'en', label: 'English' }, { url: input, lang: 'fr', label: 'Français' }]),
|
|
51
|
+
output: (input: Field | null/*, options: Options*/) => ({ ...(input || {} as Field), typeIds: ['locale_link'], kind: 'list' }),
|
|
52
|
+
options: {},
|
|
53
|
+
}]
|