@silexlabs/silex 3.4.0 → 3.5.1-1

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 (91) hide show
  1. package/.silex.js +0 -3
  2. package/dist/client/client/config.d.ts +8 -0
  3. package/dist/client/client/grapesjs/cms/DataSource.d.ts +43 -0
  4. package/dist/client/client/grapesjs/cms/blocks.d.ts +2 -0
  5. package/dist/client/client/grapesjs/cms/buttons.d.ts +2 -0
  6. package/dist/client/client/grapesjs/cms/collection-pages.d.ts +4 -0
  7. package/dist/client/client/grapesjs/cms/config.d.ts +9 -0
  8. package/dist/client/client/grapesjs/cms/filters.d.ts +3 -0
  9. package/dist/client/client/grapesjs/cms/index.d.ts +44 -0
  10. package/dist/client/client/grapesjs/cms/liquid.d.ts +59 -0
  11. package/dist/client/client/grapesjs/cms/page-settings.d.ts +6 -0
  12. package/dist/client/client/grapesjs/cms/publication.d.ts +60 -0
  13. package/dist/client/client/grapesjs/cms/site-settings.d.ts +3 -0
  14. package/dist/client/client/grapesjs/cms/states.d.ts +8 -0
  15. package/dist/client/client/grapesjs/cms/traits.d.ts +8 -0
  16. package/dist/client/client/grapesjs/settings.d.ts +12 -3
  17. package/dist/client/client/grapesjs/view-buttons.d.ts +2 -1
  18. package/dist/client/client/utils.d.ts +1 -0
  19. package/dist/client/constants.d.ts +2 -1
  20. package/dist/client/css/admin.css +88 -14
  21. package/dist/client/index.html +2 -2
  22. package/dist/client/js/main.js +1968 -264
  23. package/dist/client/types.d.ts +3 -1
  24. package/dist/plugins/client/plugins/client/template.js +1 -1
  25. package/dist/plugins/client/plugins/client/template.js.map +1 -1
  26. package/dist/plugins/client/types.d.ts +3 -1
  27. package/dist/plugins/client/types.js +11 -8
  28. package/dist/plugins/client/types.js.map +1 -1
  29. package/dist/plugins/server/constants.d.ts +2 -1
  30. package/dist/plugins/server/constants.js +3 -2
  31. package/dist/plugins/server/constants.js.map +1 -1
  32. package/dist/plugins/server/plugins/server/FtpConnector.js +15 -20
  33. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  34. package/dist/plugins/server/plugins/server/GitlabConnector.js +34 -7
  35. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  36. package/dist/plugins/server/server/connectors/FsStorage.js +16 -20
  37. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  38. package/dist/plugins/server/server/utils/websiteDataSerialization.d.ts +4 -0
  39. package/dist/plugins/server/server/utils/websiteDataSerialization.js +21 -12
  40. package/dist/plugins/server/server/utils/websiteDataSerialization.js.map +1 -1
  41. package/dist/plugins/server/types.d.ts +3 -1
  42. package/dist/plugins/server/types.js +12 -9
  43. package/dist/plugins/server/types.js.map +1 -1
  44. package/dist/server/constants.js +3 -2
  45. package/dist/server/server/connectors/FsStorage.js +14 -18
  46. package/dist/server/server/utils/websiteDataSerialization.js +16 -11
  47. package/dist/server/types.js +6 -2
  48. package/package.json +15 -10
  49. package/src/css/_variables.scss +2 -1
  50. package/src/css/grapesjs-plugins.scss +31 -0
  51. package/src/css/grapesjs.scss +15 -0
  52. package/src/css/page-panel.scss +1 -1
  53. package/src/css/silex.scss +64 -5
  54. package/src/ts/client/config.ts +23 -0
  55. package/src/ts/client/grapesjs/breadcrumbs.ts +3 -2
  56. package/src/ts/client/grapesjs/cms/DataSource.ts +333 -0
  57. package/src/ts/client/grapesjs/cms/blocks.ts +124 -0
  58. package/src/ts/client/grapesjs/cms/buttons.ts +137 -0
  59. package/src/ts/client/grapesjs/cms/collection-pages.ts +205 -0
  60. package/src/ts/client/grapesjs/cms/config.ts +47 -0
  61. package/src/ts/client/grapesjs/cms/filters.ts +53 -0
  62. package/src/ts/client/grapesjs/cms/index.ts +130 -0
  63. package/src/ts/client/grapesjs/cms/liquid.mock.ts +181 -0
  64. package/src/ts/client/grapesjs/cms/liquid.test.ts +239 -0
  65. package/src/ts/client/grapesjs/cms/liquid.ts +325 -0
  66. package/src/ts/client/grapesjs/cms/page-settings.ts +285 -0
  67. package/src/ts/client/grapesjs/cms/publication.test.ts +300 -0
  68. package/src/ts/client/grapesjs/cms/publication.ts +708 -0
  69. package/src/ts/client/grapesjs/cms/site-settings.ts +79 -0
  70. package/src/ts/client/grapesjs/cms/states.ts +125 -0
  71. package/src/ts/client/grapesjs/cms/traits.ts +95 -0
  72. package/src/ts/client/grapesjs/flex.ts +59 -39
  73. package/src/ts/client/grapesjs/img.ts +1 -1
  74. package/src/ts/client/grapesjs/index.ts +77 -4
  75. package/src/ts/client/grapesjs/internal-links.ts +4 -2
  76. package/src/ts/client/grapesjs/page-panel.ts +8 -7
  77. package/src/ts/client/grapesjs/sectors.ts +18 -10
  78. package/src/ts/client/grapesjs/settings.ts +252 -101
  79. package/src/ts/client/grapesjs/storage.ts +6 -1
  80. package/src/ts/client/grapesjs/view-buttons.ts +39 -3
  81. package/src/ts/client/utils.ts +15 -2
  82. package/src/ts/constants.ts +2 -1
  83. package/src/ts/plugins/client/template.ts +1 -1
  84. package/src/ts/plugins/server/FtpConnector.test.ts +1 -0
  85. package/src/ts/plugins/server/FtpConnector.ts +20 -23
  86. package/src/ts/plugins/server/GitlabConnector.ts +40 -10
  87. package/src/ts/server/connectors/FsStorage.test.ts +3 -2
  88. package/src/ts/server/connectors/FsStorage.ts +21 -23
  89. package/src/ts/server/utils/websiteDataSerialization.test.ts +45 -13
  90. package/src/ts/server/utils/websiteDataSerialization.ts +23 -14
  91. package/src/ts/types.ts +6 -2
@@ -0,0 +1,79 @@
1
+ import { ClientConfig } from '../../config'
2
+ import { CMS_SETTINGS_SECTION_ID, EleventyPluginOptions, Silex11tyPluginWebsiteSettings } from './index'
3
+ import { html } from 'lit-html'
4
+ import { addDataSource, createDataSource } from '@silexlabs/grapesjs-data-source'
5
+ import { Editor } from 'grapesjs'
6
+ import { cmdAddSection } from '../settings'
7
+
8
+ export default function(editor: Editor, opts: EleventyPluginOptions): void {
9
+ editor.runCommand(cmdAddSection, {
10
+ section: {
11
+ id: CMS_SETTINGS_SECTION_ID,
12
+ label: 'CMS',
13
+ render: () => {
14
+ const settings = (editor.getModel().get('settings') || {}) as Silex11tyPluginWebsiteSettings
15
+ return html`
16
+ <style>
17
+ #settings-${CMS_SETTINGS_SECTION_ID} label {
18
+ display: block;
19
+ margin-bottom: 10px;
20
+ }
21
+ .add-ds-btn {
22
+ width: 30px;
23
+ height: 30px;
24
+ margin-left: auto;
25
+ font-size: 24px;
26
+ background-color: var(--gjs-main-light-color);
27
+ }
28
+ </style>
29
+ <div id="settings-${CMS_SETTINGS_SECTION_ID}" class="silex-hideable silex-hidden">
30
+ <div class="gjs-sm-sector-title">Silex CMS</div>
31
+ <div class="silex-help">
32
+ <p>The <a target="_blank" href="https://github.com/silexlabs/silex-cms">Silex CMS feature</a> integrates with your favorite headless CMS, API or database.</p>
33
+ <p>By adding data sources to your website you activate <a target="_blank" href="https://www.11ty.dev/docs/">11ty static site generator</a> integration. When you wil publish your website, the generated files assume you build the site with 11ty and possibly with Gitlab pages.</p>
34
+ </div>
35
+ <div class="gjs-sm-sector-title">
36
+ Data Sources
37
+ <button
38
+ class="silex-button add-ds-btn"
39
+ title="Add a new data source"
40
+ @click=${async () => {
41
+ const newDataSource = createDataSource()
42
+ addDataSource(newDataSource)
43
+ try {
44
+ await newDataSource.connect()
45
+ } catch (error) {
46
+ console.error('🟡 [site-settings] Data source connection failed:', error)
47
+ }
48
+ }}>
49
+ +
50
+ </button>
51
+ </div>
52
+ ${opts.view?.settingsEl ? (opts.view.settingsEl as () => HTMLElement)() : ''}
53
+ <div class="gjs-sm-sector-title">11ty Config</div>
54
+ <div class="silex-help">
55
+ <p>These settings are used to configure the <a target="_blank" href="https://www.11ty.dev/docs/">11ty static site generator</a> integration.</p>
56
+ <p>Depending on your 11ty configuration, you may need to adjust these settings, it will enable or disable features in Silex.</p>
57
+ </div>
58
+ <div class="silex-help">
59
+ <p>⚠️ You need to reload Silex for these settings to take effect.</p>
60
+ </div>
61
+ <label>
62
+ I18N Plugin
63
+ <input id="i18n-checkbox" type="checkbox" name="eleventyI18n" ?checked=${settings.eleventyI18n || opts.i18nPlugin} ?disabled=${typeof opts.i18nPlugin !== 'undefined'}>
64
+ </label>
65
+ <label>
66
+ Fetch Plugin
67
+ <input type="checkbox" name="eleventyFetch" ?checked=${settings.eleventyFetch || !!opts.fetchPluginSettings} ?disabled=${typeof opts.fetchPluginSettings !== 'undefined'}>
68
+ </label>
69
+ <div class="silex-form__group col2">
70
+ </div>
71
+ </div>
72
+ </div>
73
+ `
74
+ }
75
+ },
76
+ siteOrPage: 'site',
77
+ position: 'last'
78
+ })
79
+ }
@@ -0,0 +1,125 @@
1
+ import { removeState, setState, COMPONENT_NAME_PREFIX, Property, toExpression, StoredFilter, State, StoredState, StoredToken, Expression } from '@silexlabs/grapesjs-data-source'
2
+ import { Silex11tyPluginWebsiteSettings } from './index'
3
+ import { Component, Editor, Page } from 'grapesjs'
4
+
5
+ export default function(editor: Editor/*, opts: EleventyPluginOptions */): void {
6
+ editor.on('page:select page:update', () => updatePaginationStates(editor))
7
+ editor.on('settings:save:start', () => updatePaginationStates(editor))
8
+ }
9
+
10
+ /**
11
+ * Update pagination states for a page with the ability to control which page index to show
12
+ * @param editor - The GrapesJS editor instance
13
+ * @param pageIndex - The current page index (0-based) for pagination preview. Use 0 for publication.
14
+ */
15
+ export function updatePaginationStates(editor: Editor, pageIndex = 0, preventTrigger = false) {
16
+ const page = editor.Pages.getSelected()
17
+ const body: Component = page?.getMainComponent() as Component
18
+ if (!body) return // This happens when the current page is deleted
19
+
20
+ // Do not show "Body's " prefix for states on the body
21
+ body.attributes.COMPONENT_NAME_PREFIX = ''
22
+
23
+ // Store pagination data in the body component
24
+ // This is for the GraphQL query to include it
25
+ const settings = page?.get('settings') as Silex11tyPluginWebsiteSettings | undefined
26
+ const pageData = toExpression(settings?.eleventyPageData) as (Property[] | null)
27
+
28
+ if (pageData && pageData.length > 0) {
29
+ try {
30
+ // Taken from the pagination object https://www.11ty.dev/docs/pagination/
31
+ // Apply pagination size limit using slice filter
32
+ const pageSize = parseInt(settings?.eleventyPageSize || '1')
33
+ const startIndex = pageIndex * pageSize
34
+ const endIndex = startIndex + pageSize
35
+
36
+ const slice = {
37
+ type: 'filter',
38
+ id: 'slice',
39
+ label: 'slice',
40
+ options: {
41
+ start: startIndex,
42
+ end: endIndex,
43
+ },
44
+ } as StoredFilter
45
+
46
+ const itemsExpression = [
47
+ ...pageData,
48
+ slice,
49
+ ]
50
+
51
+ if (preventTrigger) {
52
+ const itemsState: StoredState = body.attributes.publicStates.find((state: StoredFilter) => state.id === 'items')
53
+ if (!itemsState) {
54
+ if (!body.attributes.publicStates) body.attributes.publicStates = []
55
+ body.attributes.publicStates.push({
56
+ hidden: true,
57
+ label: 'pagination.items',
58
+ expression: itemsExpression,
59
+ })
60
+ } else {
61
+ itemsState.expression = itemsExpression
62
+ }
63
+
64
+ // Add or update pagination state in publicStates
65
+ const paginationState: StoredState = body.attributes.publicStates.find((state: StoredFilter) => state.id === 'pagination')
66
+ const paginationExpression = [{
67
+ label: 'Unused pagination label',
68
+ type: 'property',
69
+ propType: 'field',
70
+ fieldId: 'pagination',
71
+ dataSourceId: 'eleventy',
72
+ typeIds: ['pagination'],
73
+ kind: 'object',
74
+ }] as Expression
75
+ if (!paginationState) {
76
+ body.attributes.publicStates.push({
77
+ hidden: true,
78
+ label: 'pagination',
79
+ expression: paginationExpression,
80
+ })
81
+ } else {
82
+ paginationState.expression = paginationExpression
83
+ }
84
+ } else {
85
+ // FIXME: should we let UndoManager save?
86
+ editor.UndoManager.skip(() => {
87
+ setState(body, 'items', {
88
+ hidden: true,
89
+ label: 'pagination.items',
90
+ expression: itemsExpression,
91
+ }, true, 1)
92
+
93
+ // Update body states with the new settings
94
+ setState(body, 'pagination', {
95
+ hidden: true,
96
+ label: 'pagination',
97
+ expression: [{
98
+ label: 'Unused pagination label',
99
+ type: 'property' as const,
100
+ propType: 'field' as const,
101
+ fieldId: 'pagination',
102
+ dataSourceId: 'eleventy',
103
+ typeIds: ['pagination'],
104
+ kind: 'object' as const,
105
+ }]
106
+ }, true, 0)
107
+ })
108
+ }
109
+ } catch (e) {
110
+ console.error('Invalid JSON for eleventyPageData', e)
111
+ removeState(body, 'pagination', true)
112
+ removeState(body, 'items', true)
113
+ editor.runCommand('notifications:add', {
114
+ type: 'error',
115
+ message: 'Invalid JSON for eleventyPageData',
116
+ group: 'Errors in your settings',
117
+ componentId: body.id,
118
+ })
119
+ return
120
+ }
121
+ } else {
122
+ removeState(body, 'pagination', true)
123
+ removeState(body, 'items', true)
124
+ }
125
+ }
@@ -0,0 +1,95 @@
1
+ import { Editor } from 'grapesjs'
2
+ import { ClientConfig } from '../../config'
3
+ import { html, render } from 'lit-html'
4
+
5
+ /**
6
+ * @fileoverview
7
+ * This adds these traits to all components:
8
+ * - unwrap the component
9
+ */
10
+
11
+ export const UNWRAP_ID = 'plugin-unwrap'
12
+ const LABEL = 'Unwrap content'
13
+ const LABEL_DETAILS = 'Remove the component and keep its content'
14
+
15
+ export default function(editor: Editor/*, opts: EleventyPluginOptions */): void {
16
+ // Add the new trait to all component types
17
+ editor.DomComponents.getTypes().map(type => {
18
+ editor.DomComponents.addType(type.id, {
19
+ model: {
20
+ defaults: {
21
+ traits: [
22
+ // Keep the type original traits
23
+ ...(editor.DomComponents.getType(type.id)?.model.prototype.defaults.traits || []),
24
+ // Add the new trait
25
+ {
26
+ label: LABEL,
27
+ type: UNWRAP_ID,
28
+ name: UNWRAP_ID,
29
+ //type: 'checkbox',
30
+ },
31
+ ]
32
+ }
33
+ }
34
+ })
35
+ })
36
+
37
+ function doRender(el: HTMLElement, remove: boolean) {
38
+ render(html`
39
+ <label
40
+ for=${UNWRAP_ID}
41
+ class="gjs-field gjs-field-checkbox silex-label"
42
+ title=${LABEL_DETAILS}
43
+ style="width: 100%; background: var(--primaryColor);"
44
+ >
45
+ <input
46
+ type="checkbox"
47
+ id=${UNWRAP_ID}
48
+ @change=${(event: Event) => doRender(el, (event.target as HTMLInputElement)?.checked)}
49
+ ?checked=${remove}
50
+ style="
51
+ display: initial;
52
+ appearance: none;
53
+ width: 20px;
54
+ height: 20px;
55
+ position: absolute;
56
+ "
57
+ >
58
+ <i
59
+ class="gjs-chk-icon"
60
+ style="
61
+ position: absolute;
62
+ z-index: 1;
63
+ "
64
+ ></i>
65
+ </label>
66
+ `, el)
67
+ }
68
+ function doRenderCurrent(el: HTMLElement) {
69
+ doRender(el, editor.getSelected()?.get(UNWRAP_ID))
70
+ }
71
+
72
+ // inspired by https://github.com/olivmonnier/grapesjs-plugin-header/blob/master/src/components.js
73
+ editor.TraitManager.addType(UNWRAP_ID, {
74
+ createInput() {
75
+ // Create a new element container and add some content
76
+ const el = document.createElement('div')
77
+ // update the UI when a page is added/renamed/removed
78
+ editor.on('page', () => doRenderCurrent(el))
79
+ doRenderCurrent(el)
80
+ // this will be the element passed to onEvent and onUpdate
81
+ return el
82
+ },
83
+ // Update the component based on UI changes
84
+ // `elInput` is the result HTMLElement you get from `createInput`
85
+ onEvent({ elInput, component }) {
86
+ const value = (elInput.querySelector(`#${UNWRAP_ID}`) as HTMLInputElement)?.checked
87
+ component.set(UNWRAP_ID, value)
88
+ },
89
+ // Update UI on the component change
90
+ onUpdate({ elInput, component }) {
91
+ const value = component.get(UNWRAP_ID)
92
+ doRender(elInput, value)
93
+ },
94
+ })
95
+ }
@@ -42,47 +42,67 @@ const FLEX_ITEM_PROPS = [
42
42
  export default (editor: Editor) => {
43
43
  editor.on('load', () => {
44
44
  const sector = editor.StyleManager.getSector('flex')
45
- // Add missing sectors
46
- // FIXME: this makes grapesjs bug, all other props disappear
47
- // FLEX_PROPS
48
- // .concat(FLEX_ITEM_PROPS)
49
- // .forEach(id => {
50
- // if (!sector.getProperty(id)) {
51
- // editor.StyleManager.addProperty('flex', {
52
- // id,
53
- // })
54
- // }
55
- // })
45
+
46
+ let showSector = false
47
+ let visibleProps: string[] = []
48
+
49
+ // Update visibility depending on selected component
50
+ function updateVisibility(comp: Component) {
51
+ const computedDisplay = comp?.view?.el && getComputedStyle(comp.view.el)['display'] as string || ''
52
+ const isFlex = computedDisplay === 'flex' || computedDisplay === 'inline-flex'
53
+ const parent = comp.parent()
54
+ const parentComputedDisplay = parent?.view?.el && getComputedStyle(parent.view.el)['display'] as string || ''
55
+ const parentIsFlex = parentComputedDisplay === 'flex' || parentComputedDisplay === 'inline-flex'
56
+
57
+ // Determine which props should be visible
58
+ const newVisibleProps: string[] = []
59
+ if (isFlex) newVisibleProps.push(...FLEX_PROPS)
60
+ if (parentIsFlex) newVisibleProps.push(...FLEX_ITEM_PROPS)
61
+
62
+ // Set sector visibility
63
+ showSector = parentIsFlex || isFlex
64
+ sector.set('visible', showSector)
65
+
66
+ // Set property visibility
67
+ visibleProps = newVisibleProps
68
+ sector.getProperties().forEach(p => {
69
+ p.set('visible', visibleProps.includes(p.getId()))
70
+ })
71
+ }
72
+
56
73
  // Listen to the events that should trigger a visibility check
57
- editor.on('component:selected', comp => applyVisibility(comp))
58
- // `change:visible` is a workaround grapesjs behavior: https://github.com/GrapesJS/grapesjs/issues/3123
59
- editor.on('change:visible', applyVisibility)
74
+ editor.on('component:selected', (comp: Component) => {
75
+ if (!comp) return
76
+ updateVisibility(comp)
77
+ })
78
+
60
79
  // `style:property:update` is useful for instantly display the flex sector when selecting display: inline-flex;
61
- editor.on('style:property:update', () => applyVisibility())
62
- function applyVisibility(comp: Component = editor.getSelected()) {
63
- editor.off('change:visible', applyVisibility) // Avoid infinite loop
64
- requestAnimationFrame(() => {
65
- if(!comp) return // No selection yet
66
- const computedDisplay = comp?.view?.el && getComputedStyle(comp.view.el)['display'] as string || ''
67
- const isFlex = computedDisplay === 'flex' || computedDisplay === 'inline-flex'
68
- const parent = comp.parent()
69
- const parentComputedDisplay = parent?.view?.el && getComputedStyle(parent.view.el)['display'] as string || ''
70
- const parentIsFlex = parentComputedDisplay === 'flex' || parentComputedDisplay === 'inline-flex'
71
- if (parentIsFlex || isFlex) {
72
- sector.set('visible', true)
73
- }
74
- if (parentIsFlex && isFlex) {
75
- sector.getProperties()
76
- .forEach(p => p.set('visible', true))
77
- } else if (isFlex) {
78
- sector.getProperties()
79
- .forEach(p => p.set('visible', FLEX_PROPS.includes(p.getId())))
80
- } else if (parentIsFlex) {
81
- sector.getProperties()
82
- .forEach(p => p.set('visible', FLEX_ITEM_PROPS.includes(p.getId())))
83
- }
84
- editor.on('change:visible', applyVisibility)
85
- })
80
+ editor.on('style:property:update', () => {
81
+ const comp = editor.getSelected()
82
+ if (comp) updateVisibility(comp)
83
+ })
84
+
85
+ // Watch changes made by grapesjs and revert them if needed
86
+ function doubleCheckSector() {
87
+ if (sector.get('visible') !== showSector) {
88
+ sector.off('change:visible', doubleCheckSector)
89
+ sector.set('visible', showSector)
90
+ sector.on('change:visible', doubleCheckSector)
91
+ }
86
92
  }
93
+ sector.on('change:visible', doubleCheckSector)
94
+
95
+ // Watch property visibility changes
96
+ sector.getProperties().forEach(p => {
97
+ function doubleCheckProp() {
98
+ const shouldBeVisible = visibleProps.includes(p.getId())
99
+ if (p.get('visible') !== shouldBeVisible) {
100
+ p.off('change:visible', doubleCheckProp)
101
+ p.set('visible', shouldBeVisible)
102
+ p.on('change:visible', doubleCheckProp)
103
+ }
104
+ }
105
+ p.on('change:visible', doubleCheckProp)
106
+ })
87
107
  })
88
108
  }
@@ -29,7 +29,7 @@ export default (editor: Editor, opts) => {
29
29
  placeholderSrc: '/assets/placeholder.png',
30
30
  // These default values will never be used,
31
31
  // as there is default values in config
32
- replacedElements: ['img'],
32
+ replacedElements: ['img', 'li', 'video'],
33
33
  // Override with provided config
34
34
  ...opts,
35
35
  }
@@ -31,6 +31,7 @@ const notificationContainer = document.createElement('div')
31
31
  import blocksBasicPlugin from 'grapesjs-blocks-basic'
32
32
  import styleFilterPlugin from 'grapesjs-style-filter'
33
33
  import formPlugin from 'grapesjs-plugin-forms'
34
+ // import tailwindPlugin from 'grapesjs-tailwind'
34
35
  import codePlugin from 'grapesjs-custom-code'
35
36
  import filterStyles from '@silexlabs/grapesjs-filter-styles'
36
37
  import symbolsPlugin from '@silexlabs/grapesjs-symbols'
@@ -51,6 +52,7 @@ import backgroundPlugin from 'grapesjs-style-bg'
51
52
  import resizePanelPlugin from './resize-panel'
52
53
  import notificationsPlugin, { NOTIFICATION_CHANGED } from '@silexlabs/grapesjs-notifications'
53
54
  import keymapsDialogPlugin, { cmdKeymapsDialog } from '@silexlabs/grapesjs-keymaps-dialog'
55
+ import parserPostCSS from 'grapesjs-parser-postcss'
54
56
 
55
57
  import { pagePanelPlugin, cmdTogglePages, cmdAddPage } from './page-panel'
56
58
  import { newPageDialog, cmdOpenNewPageDialog } from './new-page-dialog'
@@ -68,6 +70,9 @@ import { API_PATH, API_WEBSITE_ASSETS_WRITE, API_WEBSITE_PATH, SILEX_VERSION } f
68
70
  import { ClientConfig } from '../config'
69
71
  import { titleCase } from '../utils'
70
72
  import uploadProgress from './upload-progress'
73
+ import cmsPlugin from './cms'
74
+ import { ClientEvent } from '../events'
75
+ import { ClientSideFileWithContent, PublicationData } from '../../types'
71
76
 
72
77
  const plugins = [
73
78
  {name: './project-bar', value: projectBarPlugin}, // has to be before panels and dialogs
@@ -84,6 +89,7 @@ const plugins = [
84
89
  {name: './rich-text', value: richTextPlugin},
85
90
  {name: 'grapesjs-style-filter', value: styleFilterPlugin},
86
91
  {name: 'grapesjs-plugin-forms', value: formPlugin},
92
+ // {name: 'grapesjs-tailwind', value: tailwindPlugin},
87
93
  {name: 'grapesjs-custom-code', value: codePlugin},
88
94
  {name: './internal-links', value: internalLinksPlugin},
89
95
  {name: './keymaps', value: keymapsPlugin},
@@ -105,6 +111,7 @@ const plugins = [
105
111
  {name: './resize-panel', value: resizePanelPlugin},
106
112
  {name: '@silexlabs/grapesjs-notifications', value: notificationsPlugin},
107
113
  {name: '@silexlabs/grapesjs-keymaps-dialog', value: keymapsDialogPlugin},
114
+ {name: 'grapesjs-parser-postcss', value: parserPostCSS},
108
115
  {name: './upload-progress', value: uploadProgress},
109
116
  ]
110
117
  // Check that all plugins are loaded correctly
@@ -136,6 +143,17 @@ const catComponents = 'Components'
136
143
 
137
144
  export function getEditorConfig(config: ClientConfig): EditorConfig {
138
145
  const { websiteId, storageId, rootUrl } = config
146
+
147
+ // Create dynamic plugins array with conditional CMS plugin
148
+ const dynamicPlugins = [...plugins]
149
+ const dynamicPluginsOpts: any = {}
150
+
151
+ // Add CMS plugin if enabled
152
+ if (config.cmsConfig?.enabled !== false) {
153
+ dynamicPlugins.push({name: './cms', value: cmsPlugin})
154
+ dynamicPluginsOpts[cmsPlugin.toString()] = config.cmsConfig
155
+ }
156
+
139
157
  return {
140
158
  container: '#gjs',
141
159
  height: '100%',
@@ -180,9 +198,10 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
180
198
  escapeName: (name) => `${name}`,
181
199
  },
182
200
 
183
- plugins: plugins.map(p => p.value),
201
+ plugins: dynamicPlugins.map(p => p.value),
184
202
 
185
203
  pluginsOpts: {
204
+ ...dynamicPluginsOpts,
186
205
  [blocksBasicPlugin.toString()]: {
187
206
  blocks: ['text', 'image', 'video', 'map'],
188
207
  category: catBasic,
@@ -248,6 +267,11 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
248
267
  name: 'Settings',
249
268
  attributes: { title: `Settings (${titleCase(defaultKms.kmOpenSettings.keys, '+')})` },
250
269
  command: cmdOpenSettings,
270
+ //}, {
271
+ // id: 'tailwind-theme',
272
+ // className: 'fa-solid fa-brush',
273
+ // attributes: { title: 'Tailwind color theme' },
274
+ // command: 'open-update-theme'
251
275
  }, {
252
276
  id: 'spacer',
253
277
  attributes: {},
@@ -310,6 +334,9 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
310
334
  [keymapsPlugin.toString()]: {
311
335
  disableKeymaps: false,
312
336
  },
337
+ // [tailwindPlugin.toString()]: {
338
+ // tailwindPlayCdn: `${config.rootUrl}/tailwind-3.4.17.js`,
339
+ // },
313
340
  [codePlugin.toString()]: {
314
341
  blockLabel: 'HTML',
315
342
  blockCustomCode: {
@@ -381,11 +408,18 @@ export async function initEditor(config: EditorConfig) {
381
408
 
382
409
  editor.Commands.add('gjs-open-import-webpage', openImport(editor, {
383
410
  modalImportLabel: '',
384
- modalImportContent: 'Paste a web page HTML code here.',
411
+ modalImportContent: 'Paste a web page HTML code here. This is an experiment, it might destroy the existing website.',
385
412
  modalImportButton: 'Import',
386
413
  modalImportTitle: 'Import from website',
387
414
  }))
388
415
 
416
+ // // Add tailwind css to the published site
417
+ // editor.on(ClientEvent.PUBLISH_DATA, ({ data }: { data: PublicationData }) => {
418
+ // editor.runCommand('get-tailwindCss', {
419
+ // callback: (css: string) => (data.files.find(f => f.type === 'css') as ClientSideFileWithContent).content += css,
420
+ // })
421
+ // })
422
+
389
423
  // Detect loading errors
390
424
  // Display a useful notification
391
425
  const typeConfig = {
@@ -403,8 +437,35 @@ export async function initEditor(config: EditorConfig) {
403
437
  },
404
438
  },
405
439
  }
406
- editor.DomComponents.addType('image', typeConfig)
407
- editor.DomComponents.addType('iframe', typeConfig)
440
+ const dc = editor.DomComponents
441
+ dc.addType('image', typeConfig)
442
+ dc.addType('iframe', typeConfig)
443
+
444
+ dc.getTypes().map(type => {
445
+ const dcmp = dc.getType(type.id)?.model.prototype
446
+ dc.addType(type.id, {
447
+ model: {
448
+ defaults: {
449
+ traits: [
450
+ {
451
+ type: 'text',
452
+ label: 'ID',
453
+ name: 'id',
454
+ },
455
+ ...(dcmp.defaults.traits || []),
456
+ ]
457
+ },
458
+ init(...args) {
459
+ (dcmp.init.apply(this, args))
460
+ if (!this.getAttributes().id) {
461
+ this.addAttributes({ id: this.getId() })
462
+ }
463
+ }
464
+ },
465
+ })
466
+ })
467
+
468
+
408
469
 
409
470
  // Adjustments to do when the editor is ready
410
471
  editor.on('load', () => {
@@ -415,6 +476,18 @@ export async function initEditor(config: EditorConfig) {
415
476
  views.buttons.remove(cmdToggleBlocks)
416
477
  views.buttons.remove(cmdToggleLayers)
417
478
 
479
+ // Select body when changing pages
480
+ // This includes initial loading, as the 1st page is selected in storage
481
+ editor.on('page:select', () => {
482
+ // Only select wrapper if nothing else is selected
483
+ if (!editor.getSelected()) {
484
+ const wrapper = editor.getWrapper()
485
+ if (wrapper) {
486
+ editor.select(wrapper)
487
+ }
488
+ }
489
+ })
490
+
418
491
  // Remove useless buttons
419
492
  editor.Panels.getPanel('options').buttons.remove('export-template')
420
493
  editor.Panels.getPanel('options').buttons.remove('fullscreen')
@@ -35,8 +35,10 @@ export const internalLinksPlugin = (editor, opts) => {
35
35
  function onAll(cbk) {
36
36
  editor.Pages.getAll()
37
37
  .forEach(page => {
38
- page.getMainComponent()
39
- .onAll(cbk)
38
+ const mainComponent = page.getMainComponent()
39
+ if (mainComponent) {
40
+ mainComponent.onAll(cbk)
41
+ }
40
42
  })
41
43
  }
42
44
  editor.on('page', ({ event, page }) => {
@@ -15,7 +15,7 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { Editor } from 'grapesjs'
18
+ import { Component, Editor, Page } from 'grapesjs'
19
19
  import {html, render} from 'lit-html'
20
20
  import {ref} from 'lit-html/directives/ref.js'
21
21
 
@@ -30,10 +30,10 @@ export const cmdSelectNextPage = 'pages:select-next'
30
30
  export const cmdSelectPrevPage = 'pages:select-prev'
31
31
  export const cmdSelectFirstPage = 'pages:select-first'
32
32
 
33
- function selectPage(editor, page) {
33
+ function selectPage(editor: Editor, page: Page) {
34
34
  editor.Pages.select(page)
35
35
  }
36
- function addPage(editor, config) {
36
+ function addPage(editor: Editor, config: { newPageName: string, cmdOpenNewPageDialog: string }) {
37
37
  const pages = editor.Pages.getAll()
38
38
  // Get a name
39
39
  let idx = 1
@@ -50,7 +50,7 @@ function addPage(editor, config) {
50
50
  editor.runCommand(config.cmdOpenNewPageDialog, {page})
51
51
  }
52
52
 
53
- function clonePage(editor, page) {
53
+ function clonePage(editor: Editor, page: Page) {
54
54
  const pages = editor.Pages.getAll()
55
55
  // Get a name
56
56
  let idx = 1
@@ -64,7 +64,7 @@ function clonePage(editor, page) {
64
64
  // Add page
65
65
  const newPage = editor.Pages.add({
66
66
  name: pageName,
67
- component: body,
67
+ component: body.toJSON(),
68
68
  })
69
69
  // Select the new page
70
70
  editor.Pages.select(newPage)
@@ -145,8 +145,9 @@ function renderPages(editor, config) {
145
145
  return html`<section class="pages">
146
146
  <main class="pages__main ${pages.length === 1 ? 'pages__single-page' : ''}">
147
147
  <div class="pages__list">
148
- ${ pages.map(page => {
149
- const name = page.getName() || page.attributes.type
148
+ ${ pages.map((page: Page, index: number) => {
149
+ const type = page.get('type') || index === 0 ? 'main' : 'Untitled Page'
150
+ const name = page.getName() || type
150
151
  // keep the same structure as the layers panel
151
152
  return html`
152
153
  <div class="pages__page ${selected === page ? 'pages__page-selected' : ''}" data-page-id=${page.id} @click=${e => selectPage(editor, getPageFromEvent(e))}>