@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.
Files changed (95) hide show
  1. package/.silex.js +0 -3
  2. package/dist/client/client/config.d.ts +8 -0
  3. package/dist/client/client/grapesjs/PublicationManager.d.ts +43 -0
  4. package/dist/client/client/grapesjs/cms/DataSource.d.ts +43 -0
  5. package/dist/client/client/grapesjs/cms/blocks.d.ts +2 -0
  6. package/dist/client/client/grapesjs/cms/buttons.d.ts +2 -0
  7. package/dist/client/client/grapesjs/cms/collection-pages.d.ts +4 -0
  8. package/dist/client/client/grapesjs/cms/config.d.ts +9 -0
  9. package/dist/client/client/grapesjs/cms/filters.d.ts +3 -0
  10. package/dist/client/client/grapesjs/cms/index.d.ts +44 -0
  11. package/dist/client/client/grapesjs/cms/liquid.d.ts +59 -0
  12. package/dist/client/client/grapesjs/cms/page-settings.d.ts +6 -0
  13. package/dist/client/client/grapesjs/cms/publication.d.ts +60 -0
  14. package/dist/client/client/grapesjs/cms/site-settings.d.ts +3 -0
  15. package/dist/client/client/grapesjs/cms/states.d.ts +8 -0
  16. package/dist/client/client/grapesjs/cms/traits.d.ts +8 -0
  17. package/dist/client/client/grapesjs/settings.d.ts +12 -3
  18. package/dist/client/client/grapesjs/view-buttons.d.ts +2 -1
  19. package/dist/client/client/utils.d.ts +1 -0
  20. package/dist/client/constants.d.ts +2 -1
  21. package/dist/client/css/admin.css +117 -19
  22. package/dist/client/index.html +2 -2
  23. package/dist/client/js/main.js +1974 -267
  24. package/dist/client/types.d.ts +3 -1
  25. package/dist/plugins/client/plugins/client/template.js +1 -1
  26. package/dist/plugins/client/plugins/client/template.js.map +1 -1
  27. package/dist/plugins/client/types.d.ts +3 -1
  28. package/dist/plugins/client/types.js +11 -8
  29. package/dist/plugins/client/types.js.map +1 -1
  30. package/dist/plugins/server/constants.d.ts +2 -1
  31. package/dist/plugins/server/constants.js +3 -2
  32. package/dist/plugins/server/constants.js.map +1 -1
  33. package/dist/plugins/server/plugins/server/FtpConnector.js +15 -20
  34. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  35. package/dist/plugins/server/plugins/server/GitlabConnector.js +34 -7
  36. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  37. package/dist/plugins/server/server/connectors/FsStorage.js +16 -20
  38. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  39. package/dist/plugins/server/server/utils/websiteDataSerialization.d.ts +4 -0
  40. package/dist/plugins/server/server/utils/websiteDataSerialization.js +21 -12
  41. package/dist/plugins/server/server/utils/websiteDataSerialization.js.map +1 -1
  42. package/dist/plugins/server/types.d.ts +3 -1
  43. package/dist/plugins/server/types.js +12 -9
  44. package/dist/plugins/server/types.js.map +1 -1
  45. package/dist/server/constants.js +3 -2
  46. package/dist/server/server/connectors/FsStorage.js +14 -18
  47. package/dist/server/server/utils/websiteDataSerialization.js +16 -11
  48. package/dist/server/types.js +6 -2
  49. package/package.json +16 -11
  50. package/src/css/_variables.scss +2 -1
  51. package/src/css/grapesjs-plugins.scss +31 -0
  52. package/src/css/grapesjs.scss +20 -0
  53. package/src/css/page-panel.scss +25 -6
  54. package/src/css/silex.scss +64 -5
  55. package/src/ts/client/config.ts +23 -0
  56. package/src/ts/client/grapesjs/PublicationManager.ts +159 -1
  57. package/src/ts/client/grapesjs/breadcrumbs.ts +3 -2
  58. package/src/ts/client/grapesjs/cms/DataSource.ts +333 -0
  59. package/src/ts/client/grapesjs/cms/blocks.ts +124 -0
  60. package/src/ts/client/grapesjs/cms/buttons.ts +163 -0
  61. package/src/ts/client/grapesjs/cms/collection-pages.ts +214 -0
  62. package/src/ts/client/grapesjs/cms/config.ts +47 -0
  63. package/src/ts/client/grapesjs/cms/filters.ts +53 -0
  64. package/src/ts/client/grapesjs/cms/index.ts +130 -0
  65. package/src/ts/client/grapesjs/cms/liquid.mock.ts +181 -0
  66. package/src/ts/client/grapesjs/cms/liquid.test.ts +239 -0
  67. package/src/ts/client/grapesjs/cms/liquid.ts +325 -0
  68. package/src/ts/client/grapesjs/cms/page-settings.ts +285 -0
  69. package/src/ts/client/grapesjs/cms/publication.test.ts +300 -0
  70. package/src/ts/client/grapesjs/cms/publication.ts +708 -0
  71. package/src/ts/client/grapesjs/cms/site-settings.ts +79 -0
  72. package/src/ts/client/grapesjs/cms/states.ts +125 -0
  73. package/src/ts/client/grapesjs/cms/traits.ts +95 -0
  74. package/src/ts/client/grapesjs/css-props.ts +15 -16
  75. package/src/ts/client/grapesjs/flex.ts +59 -39
  76. package/src/ts/client/grapesjs/img.ts +1 -1
  77. package/src/ts/client/grapesjs/index.ts +77 -4
  78. package/src/ts/client/grapesjs/internal-links.ts +4 -2
  79. package/src/ts/client/grapesjs/page-panel.ts +126 -11
  80. package/src/ts/client/grapesjs/sectors.ts +18 -10
  81. package/src/ts/client/grapesjs/semantic.ts +4 -0
  82. package/src/ts/client/grapesjs/settings.ts +252 -101
  83. package/src/ts/client/grapesjs/storage.ts +12 -3
  84. package/src/ts/client/grapesjs/view-buttons.ts +39 -3
  85. package/src/ts/client/utils.ts +15 -2
  86. package/src/ts/constants.ts +2 -1
  87. package/src/ts/plugins/client/template.ts +1 -1
  88. package/src/ts/plugins/server/FtpConnector.test.ts +1 -0
  89. package/src/ts/plugins/server/FtpConnector.ts +20 -23
  90. package/src/ts/plugins/server/GitlabConnector.ts +40 -10
  91. package/src/ts/server/connectors/FsStorage.test.ts +3 -2
  92. package/src/ts/server/connectors/FsStorage.ts +21 -23
  93. package/src/ts/server/utils/websiteDataSerialization.test.ts +45 -13
  94. package/src/ts/server/utils/websiteDataSerialization.ts +23 -14
  95. 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.&nbsp;
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
+ }]