@silexlabs/silex 3.3.1-3 → 3.3.1-5

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 (103) hide show
  1. package/README.md +27 -2
  2. package/dist/client/css/admin.css +26 -9
  3. package/dist/client/index.html +3 -3
  4. package/dist/client/js/main.js +285 -217
  5. package/dist/client/js/main.js.LICENSE.txt +2 -13
  6. package/dist/client/page.d.ts +1 -1
  7. package/dist/client/types.d.ts +1 -17
  8. package/dist/plugins/client/types.d.ts +1 -17
  9. package/dist/plugins/client/types.js.map +1 -1
  10. package/dist/plugins/server/page.d.ts +1 -1
  11. package/dist/plugins/server/page.js.map +1 -1
  12. package/dist/plugins/server/plugins/server/FtpConnector.js +53 -8
  13. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  14. package/dist/plugins/server/plugins/server/GitlabConnector.js +106 -85
  15. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  16. package/dist/plugins/server/server/connectors/FsStorage.js +50 -4
  17. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  18. package/dist/plugins/server/server/utils/websiteDataSerialization.d.ts +20 -0
  19. package/dist/plugins/server/server/utils/websiteDataSerialization.js +127 -0
  20. package/dist/plugins/server/server/utils/websiteDataSerialization.js.map +1 -0
  21. package/dist/plugins/server/types.d.ts +1 -17
  22. package/dist/plugins/server/types.js.map +1 -1
  23. package/dist/server/page.js +14 -0
  24. package/dist/server/server/cli.js +0 -0
  25. package/dist/server/server/connectors/FsStorage.js +40 -4
  26. package/dist/server/server/utils/websiteDataSerialization.js +85 -0
  27. package/package.json +13 -13
  28. package/src/css/_variables.scss +4 -0
  29. package/src/css/grapesjs-plugins.scss +5 -0
  30. package/src/css/grapesjs.scss +8 -2
  31. package/src/css/loader.scss +9 -6
  32. package/src/css/page-panel.scss +3 -2
  33. package/src/css/project-bar.scss +0 -1
  34. package/src/html/index.pug +1 -1
  35. package/src/ts/client/assetUrl.ts +1 -1
  36. package/src/ts/client/grapesjs/PublicationUi.ts +1 -1
  37. package/src/ts/client/grapesjs/css-props.ts +4 -3
  38. package/src/ts/client/grapesjs/flex.ts +8 -4
  39. package/src/ts/client/grapesjs/img.ts +9 -3
  40. package/src/ts/client/grapesjs/index.ts +9 -11
  41. package/src/ts/client/grapesjs/li.ts +3 -1
  42. package/src/ts/client/grapesjs/sectors.ts +3 -1
  43. package/src/ts/client/grapesjs/storage.ts +25 -30
  44. package/src/ts/client/index.ts +2 -3
  45. package/src/ts/page.ts +1 -1
  46. package/src/ts/plugins/server/FtpConnector.test.ts +2 -1
  47. package/src/ts/plugins/server/FtpConnector.ts +61 -9
  48. package/src/ts/plugins/server/GitlabConnector.ts +112 -102
  49. package/src/ts/server/connectors/FsStorage.test.ts +6 -2
  50. package/src/ts/server/connectors/FsStorage.ts +60 -5
  51. package/src/ts/server/utils/websiteDataSerialization.test.ts +59 -0
  52. package/src/ts/server/utils/websiteDataSerialization.ts +143 -0
  53. package/src/ts/types.ts +2 -20
  54. package/dist/client/css/admin.css.map +0 -1
  55. package/dist/client/grapesjs-filter-styles/dist/index.d.ts +0 -4
  56. package/dist/client/grapesjs-fonts/dist/index.d.ts +0 -4
  57. package/dist/client/grapesjs-fonts/src/commands.d.ts +0 -3
  58. package/dist/client/grapesjs-fonts/src/fonts.d.ts +0 -4
  59. package/dist/client/grapesjs-storage-rate-limit/dist/index.d.ts +0 -4
  60. package/dist/client/grapesjs-ui-suggest-classes/dist/index.d.ts +0 -4
  61. package/dist/client/silex-lib/src/ts/client/api.d.ts +0 -90
  62. package/dist/client/silex-lib/src/ts/client/assetUrl.d.ts +0 -46
  63. package/dist/client/silex-lib/src/ts/client/config.d.ts +0 -102
  64. package/dist/client/silex-lib/src/ts/client/events.d.ts +0 -23
  65. package/dist/client/silex-lib/src/ts/client/expose.d.ts +0 -8
  66. package/dist/client/silex-lib/src/ts/client/grapesjs/LoginDialog.d.ts +0 -15
  67. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationManager.d.ts +0 -74
  68. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationUi.d.ts +0 -59
  69. package/dist/client/silex-lib/src/ts/client/grapesjs/blocks.d.ts +0 -1
  70. package/dist/client/silex-lib/src/ts/client/grapesjs/breadcrumbs.d.ts +0 -1
  71. package/dist/client/silex-lib/src/ts/client/grapesjs/css-props.d.ts +0 -6
  72. package/dist/client/silex-lib/src/ts/client/grapesjs/flex.d.ts +0 -7
  73. package/dist/client/silex-lib/src/ts/client/grapesjs/footer.d.ts +0 -2
  74. package/dist/client/silex-lib/src/ts/client/grapesjs/img.d.ts +0 -6
  75. package/dist/client/silex-lib/src/ts/client/grapesjs/index.d.ts +0 -9
  76. package/dist/client/silex-lib/src/ts/client/grapesjs/internal-links.d.ts +0 -1
  77. package/dist/client/silex-lib/src/ts/client/grapesjs/keymaps.d.ts +0 -123
  78. package/dist/client/silex-lib/src/ts/client/grapesjs/li.d.ts +0 -7
  79. package/dist/client/silex-lib/src/ts/client/grapesjs/new-page-dialog.d.ts +0 -2
  80. package/dist/client/silex-lib/src/ts/client/grapesjs/openImport.d.ts +0 -9
  81. package/dist/client/silex-lib/src/ts/client/grapesjs/page-panel.d.ts +0 -9
  82. package/dist/client/silex-lib/src/ts/client/grapesjs/project-bar.d.ts +0 -21
  83. package/dist/client/silex-lib/src/ts/client/grapesjs/resize-panel.d.ts +0 -1
  84. package/dist/client/silex-lib/src/ts/client/grapesjs/rich-text.d.ts +0 -4
  85. package/dist/client/silex-lib/src/ts/client/grapesjs/scroll.d.ts +0 -7
  86. package/dist/client/silex-lib/src/ts/client/grapesjs/sectors.d.ts +0 -11
  87. package/dist/client/silex-lib/src/ts/client/grapesjs/semantic.d.ts +0 -1
  88. package/dist/client/silex-lib/src/ts/client/grapesjs/settings-sections.d.ts +0 -16
  89. package/dist/client/silex-lib/src/ts/client/grapesjs/settings.d.ts +0 -4
  90. package/dist/client/silex-lib/src/ts/client/grapesjs/storage.d.ts +0 -3
  91. package/dist/client/silex-lib/src/ts/client/grapesjs/symbolDialogs.d.ts +0 -3
  92. package/dist/client/silex-lib/src/ts/client/grapesjs/upload-progress.d.ts +0 -2
  93. package/dist/client/silex-lib/src/ts/client/grapesjs/view-buttons.d.ts +0 -1
  94. package/dist/client/silex-lib/src/ts/client/index.d.ts +0 -10
  95. package/dist/client/silex-lib/src/ts/client/publication-transformers.d.ts +0 -42
  96. package/dist/client/silex-lib/src/ts/client/publish-custom-code-block.d.ts +0 -4
  97. package/dist/client/silex-lib/src/ts/client/publish-fonts.d.ts +0 -4
  98. package/dist/client/silex-lib/src/ts/client/utils.d.ts +0 -22
  99. package/dist/client/silex-lib/src/ts/constants.d.ts +0 -33
  100. package/dist/client/silex-lib/src/ts/page.d.ts +0 -2
  101. package/dist/client/silex-lib/src/ts/types.d.ts +0 -341
  102. package/src/ts/client/grapesjs/dynamic-plugins.ts +0 -63
  103. package/src/ts/client/grapesjs/plugin-options.ts +0 -243
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.3.1-3",
4
+ "version": "3.3.1-5",
5
5
  "version:backwardcompat": "3.0.0",
6
6
  "author": {
7
7
  "name": "Alex Hoyau",
@@ -79,7 +79,7 @@
79
79
  "dev": "run-p watch start:debug",
80
80
  "watch": "npm-watch",
81
81
  "start": "node dist/server/server/",
82
- "start:debug": "cross-env SILEX_FS_ROOT=`pwd`/silex SILEX_DEBUG=true nodemon --watch dist/server dist/server/server/",
82
+ "start:debug": "cross-env SILEX_DEBUG=true nodemon --watch dist/server dist/server/server/",
83
83
  "start:preview": "http-server silex/hosting -o",
84
84
  "prepublishOnly": "$npm_execpath run lint && $npm_execpath test && $npm_execpath run build",
85
85
  "preversion": "$npm_execpath run prepublishOnly",
@@ -100,21 +100,21 @@
100
100
  "lint:fix": "$npm_execpath run lint -- --fix",
101
101
  "test": "node --experimental-vm-modules `node_modules jest`/jest/bin/jest.js",
102
102
  "test:watch": "$npm_execpath test -- --watch",
103
- "prepare": "husky install && npm run build"
103
+ "prepare": "husky && $npm_execpath run build"
104
104
  },
105
105
  "dependencies": {
106
106
  "@fontsource/ubuntu": "^5.0.13",
107
107
  "@fortawesome/fontawesome-free": "6.6.0",
108
- "@silexlabs/grapesjs-advanced-selector": "1.2.1-2",
109
- "@silexlabs/grapesjs-filter-styles": "1.1.0",
110
- "@silexlabs/grapesjs-fonts": "1.1.0",
111
- "@silexlabs/grapesjs-keymaps-dialog": "1.1.0",
112
- "@silexlabs/grapesjs-loading": "1.1.0",
113
- "@silexlabs/grapesjs-notifications": "0.1.0",
114
- "@silexlabs/grapesjs-storage-rate-limit": "1.1.0",
115
- "@silexlabs/grapesjs-symbols": "1.2.1-0",
116
- "@silexlabs/grapesjs-ui-suggest-classes": "1.1.0",
117
- "@silexlabs/silex-plugins": "1.1.0",
108
+ "@silexlabs/grapesjs-advanced-selector": "1.2.1-5",
109
+ "@silexlabs/grapesjs-filter-styles": "1.1.1-0",
110
+ "@silexlabs/grapesjs-fonts": "1.1.1-1",
111
+ "@silexlabs/grapesjs-keymaps-dialog": "1.1.1-0",
112
+ "@silexlabs/grapesjs-loading": "1.1.1-0",
113
+ "@silexlabs/grapesjs-notifications": "0.1.1-0",
114
+ "@silexlabs/grapesjs-storage-rate-limit": "1.1.1-0",
115
+ "@silexlabs/grapesjs-symbols": "1.2.1-1",
116
+ "@silexlabs/grapesjs-ui-suggest-classes": "1.1.1-0",
117
+ "@silexlabs/silex-plugins": "1.1.1-0",
118
118
  "@types/archiver": "^6.0.2",
119
119
  "adm-zip": "0.5.14",
120
120
  "archiver": "^7.0.1",
@@ -27,9 +27,13 @@ $fontFamily: 'Ubuntu', sans-serif;
27
27
 
28
28
  :root {
29
29
  --primaryColor: #{$primaryColor};
30
+ --gjs-primary-color: #{$primaryColor} !important;
30
31
  --secondaryColor: #{$secondaryColor};
32
+ --gjs-secondary-color: #{$secondaryColor} !important;
31
33
  --tertiaryColor: #{$tertiaryColor};
34
+ --gjs-tertiary-color: #{$tertiaryColor} !important;
32
35
  --quaternaryColor: #{$quaternaryColor};
36
+ --gjs-quaternary-color: #{$quaternaryColor} !important;
33
37
  --darkerPrimaryColor: #{$darkerPrimaryColor};
34
38
  --lighterPrimaryColor: #{$lighterPrimaryColor};
35
39
  --viewsPanelWidth: #{$viewsPanelWidth};
@@ -33,6 +33,11 @@
33
33
  border-right-color: $secondaryColor;
34
34
  }
35
35
 
36
+ // Hide tooltip info buttons for border properties that interfere with the visual squares
37
+ .gjs-radio-item-label:after {
38
+ display: none !important;
39
+ }
40
+
36
41
  // grapesjs-notifications
37
42
  .gjs-editor .gjs-notification {
38
43
  max-height: initial;
@@ -130,6 +130,12 @@
130
130
  .gjs-sm-icon[title]:not([title=""])::after {
131
131
  content: "?";
132
132
  margin-left: 0.5em;
133
+ padding: 0.1em 0.3em;
134
+ transition: background 0.5s;
135
+ font-size: x-small;
136
+ }
137
+ .gjs-sm-icon[title]:not([title=""]):hover::after {
138
+ background: var(--gjs-main-dark-color);
133
139
  }
134
140
 
135
141
  /*
@@ -214,9 +220,9 @@
214
220
  }
215
221
  .gjs-sm-sector .gjs-sm-sector-title::before,
216
222
  .gjs-sm-sector .gjs-sm-sector-title::after {
217
- content: '•';
223
+ content: 'o';
218
224
  display: none;
219
- margin-left: 0.1em;
225
+ margin-left: 0.4em;
220
226
  order: 5;
221
227
  }
222
228
  .gjs-sm-sector:has(.gjs-sm-label.gjs-four-color) .gjs-sm-sector-title::before {
@@ -22,20 +22,23 @@
22
22
  height: 0;
23
23
  position: absolute;
24
24
  background-color: variables.$primaryColor;
25
+ outline: 1px solid grey;
26
+ border: none;
25
27
  border-radius: 0.125rem;
26
28
  transform: translateY(-5px);
27
29
  transition: all 0.3s 0.3s ease;
30
+ /* CHROME */
28
31
  &::-webkit-progress-bar {
29
- background-color: variables.$secondaryColor;
30
- border-radius: 0.125rem;
32
+ background-color: variables.$primaryColor; // dark background (track)
31
33
  }
34
+
32
35
  &::-webkit-progress-value {
33
- background-color: variables.$primaryColor;
34
- border-radius: 0.125rem;
36
+ background-color: variables.$secondaryColor; // light foreground (filled)
35
37
  }
38
+
39
+ /* FIREFOX */
36
40
  &::-moz-progress-bar {
37
- background-color: variables.$secondaryColor;
38
- border-radius: 0.125rem;
41
+ background-color: variables.$secondaryColor; // light foreground (filled)
39
42
  }
40
43
  }
41
44
  }
@@ -2,16 +2,17 @@
2
2
 
3
3
  .page-panel-container {
4
4
  .pages__wrapper {
5
- height: inherit;
5
+ display: contents !important;
6
+ height: fit-content;
6
7
  }
7
8
  .pages {
8
- height: 100%;
9
9
  display: flex;
10
10
  flex-direction: column;
11
11
  .pages__main {
12
12
  flex: 1; // take the available space in height
13
13
  border: 1px solid $darkerPrimaryColor;
14
14
  background-color: $primaryColor;
15
+ min-height: 20vh;
15
16
  }
16
17
  .pages__page {
17
18
  display: flex;
@@ -115,4 +115,3 @@
115
115
  }
116
116
  }
117
117
  }
118
-
@@ -23,7 +23,7 @@ html(lang="en", translate="no")
23
23
  block head-end
24
24
 
25
25
  body.silex-loading
26
- .silex-loader.silex-dialog.
26
+ .silex-loader.silex-dialog(role="status", aria-live="polite", aria-busy="true").
27
27
  Loading
28
28
 
29
29
  block includes
@@ -1,5 +1,5 @@
1
1
  import { API_PATH, API_WEBSITE_ASSET_READ, API_WEBSITE_PATH } from '../constants'
2
- import { Asset, ClientSideFileType, Component, ConnectorId, Page, Style, WebsiteId } from '../types'
2
+ import { Asset, ClientSideFileType, ConnectorId, Style, WebsiteId } from '../types'
3
3
 
4
4
  /**
5
5
  * @fileoverview Helpers to manage assets URL
@@ -129,7 +129,7 @@ export class PublicationUi {
129
129
  // Create the dialog element
130
130
  const el = document.createElement('div')
131
131
  el.id = 'publish-dialog'
132
- el.className = 'silex-dialog-inline silex-dialog gjs-two-color'
132
+ el.className = 'silex-dialog-inline silex-dialog gjs-two-color silex-dialog-hide'
133
133
  document.body.append(el)
134
134
  // Let the other buttons be added, we want to be last
135
135
  setTimeout(() => {
@@ -128,19 +128,20 @@ export default (editor: Editor, opts) => {
128
128
  name: 'Content',
129
129
  property: 'content',
130
130
  type: 'text',
131
- defaults: '',
131
+ defaults: '"Content in double quotes"',
132
132
  info: 'Generates content for an element.',
133
133
  full: true,
134
134
  visible: false,
135
135
  }],
136
136
  shouldShow: async () => {
137
137
  return new Promise<boolean>((resolve, reject) => {
138
+
138
139
  requestAnimationFrame(() => {
139
140
  try {
140
141
  const state = editor.StyleManager
141
142
  .getSelected()
142
- .get('state')
143
- if (['before', 'after'].includes(state)) {
143
+ ?.get('state')
144
+ if (state && ['before', 'after'].includes(state)) {
144
145
  return resolve(true)
145
146
  }
146
147
  return resolve(false)
@@ -15,7 +15,6 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
  import { Component, Editor } from 'grapesjs'
18
- import { SectorConfig, registerSector } from './sectors'
19
18
 
20
19
  /**
21
20
  * @fileoverview This plugin adds CSS properties for the UL, OL and LI elements
@@ -54,12 +53,16 @@ export default (editor: Editor) => {
54
53
  // })
55
54
  // }
56
55
  // })
57
- // Update the visibility
58
- // This is a workaround grapesjs behavior: https://github.com/GrapesJS/grapesjs/issues/3123
59
- sector.on('change:visible', () => applyVisibility())
56
+ // Listen to the events that should trigger a visibility check
60
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)
60
+ // `style:property:update` is useful for instantly display the flex sector when selecting display: inline-flex;
61
+ editor.on('style:property:update', () => applyVisibility())
61
62
  function applyVisibility(comp: Component = editor.getSelected()) {
63
+ editor.off('change:visible', applyVisibility) // Avoid infinite loop
62
64
  requestAnimationFrame(() => {
65
+ if(!comp) return // No selection yet
63
66
  const computedDisplay = comp?.view?.el && getComputedStyle(comp.view.el)['display'] as string || ''
64
67
  const isFlex = computedDisplay === 'flex' || computedDisplay === 'inline-flex'
65
68
  const parent = comp.parent()
@@ -78,6 +81,7 @@ export default (editor: Editor) => {
78
81
  sector.getProperties()
79
82
  .forEach(p => p.set('visible', FLEX_ITEM_PROPS.includes(p.getId())))
80
83
  }
84
+ editor.on('change:visible', applyVisibility)
81
85
  })
82
86
  }
83
87
  })
@@ -144,9 +144,15 @@ export default (editor: Editor, opts) => {
144
144
  }
145
145
  })
146
146
  // Watch changes made by grapesjs and revert them if needed
147
- sector.on('change:visible', () => {
148
- if(sector.get('visible') !== showSector) sector.set('visible', showSector)
149
- })
147
+ function doubleCheck() {
148
+ if(sector.get('visible') !== showSector){
149
+ sector.off('change:visible', doubleCheck)
150
+ sector.set('visible', showSector)
151
+ sector.on('change:visible', doubleCheck)
152
+ }
153
+ }
154
+ sector.on('change:visible', doubleCheck)
155
+
150
156
  // Add a new image type that handles the storage URL vs displayed URL
151
157
  domc.addType('image', {
152
158
  ...imgType,
@@ -49,7 +49,7 @@ import rateLimitPlugin from '@silexlabs/grapesjs-storage-rate-limit'
49
49
  import borderPugin from 'grapesjs-style-border'
50
50
  import backgroundPlugin from 'grapesjs-style-bg'
51
51
  import resizePanelPlugin from './resize-panel'
52
- import notificationsPlugin, { NotificationEditor } from '@silexlabs/grapesjs-notifications'
52
+ import notificationsPlugin, { NOTIFICATION_CHANGED } from '@silexlabs/grapesjs-notifications'
53
53
  import keymapsDialogPlugin, { cmdKeymapsDialog } from '@silexlabs/grapesjs-keymaps-dialog'
54
54
 
55
55
  import { pagePanelPlugin, cmdTogglePages, cmdAddPage } from './page-panel'
@@ -428,16 +428,14 @@ export async function initEditor(config: EditorConfig) {
428
428
  // Add the notifications container
429
429
  document.body.querySelector('.notifications-container')?.appendChild(notificationContainer)
430
430
  // Mark the button as dirty when there are notifications
431
- // TODO: move this in the notifications plugin options
432
- editor.on(
433
- 'notifications:changed',
434
- () => {
435
- const notificationButton = editor.Panels.getPanel(PROJECT_BAR_PANEL_ID).view?.el.querySelector('.notifications-btn')
436
- ;(editor as unknown as NotificationEditor)
437
- ? notificationButton?.classList.add('project-bar__dirty')
438
- : notificationButton?.classList.remove('project-bar__dirty')
439
- }
440
- )
431
+ let notificationCount = 0
432
+ editor.on(NOTIFICATION_CHANGED, (notifications) => {
433
+ const notificationButton = editor.Panels.getPanel(PROJECT_BAR_PANEL_ID).view?.el.querySelector('.notifications-btn')
434
+ notificationCount = Array.isArray(notifications) ? notifications.length : 0
435
+ notificationCount > 0
436
+ ? notificationButton?.classList.add('project-bar__dirty')
437
+ : notificationButton?.classList.remove('project-bar__dirty')
438
+ })
441
439
 
442
440
  // GrapesJs editor is ready
443
441
  resolve(editor)
@@ -68,7 +68,9 @@ export default (editor: Editor) => {
68
68
  defaults: '',
69
69
  },
70
70
  ],
71
- shouldShow: comp => ['ul', 'ol', 'li'].includes(comp.get('tagName')?.toLowerCase() || ''),
71
+ shouldShow: comp => {
72
+ return comp && ['ul', 'ol', 'li'].includes(comp.get('tagName')?.toLowerCase() || '')
73
+ },
72
74
  },
73
75
  ]
74
76
 
@@ -46,12 +46,14 @@ export function registerSector(editor: Editor, config: SectorConfig, at?: number
46
46
  async function applyVisibility(comp: Component = editor.getSelected()) {
47
47
  const shouldShow = await config.shouldShow(comp)
48
48
  if (sector.get('visible') !== shouldShow) {
49
+ sector.off('change:visible', applyVisibility)
49
50
  sector.set('visible', shouldShow)
51
+ sector.on('change:visible', applyVisibility)
50
52
  // sector.getProperties()
51
53
  // .forEach(p => p.set('visible', shouldShow))
52
54
  }
53
55
  }
54
56
 
55
- sector.on('change:visible', () => applyVisibility())
57
+ sector.on('change:visible', applyVisibility)
56
58
  editor.on('component:selected', comp => applyVisibility(comp))
57
59
  }
@@ -21,7 +21,7 @@ import { cmdLogin, eventLoggedIn, eventLoggedOut, getCurrentUser, updateUser } f
21
21
  import { addTempDataToAssetUrl, addTempDataToStyles, removeTempDataFromAssetUrl, removeTempDataFromStyles } from '../assetUrl'
22
22
  import { PublicationStatus, PublishableEditor } from './PublicationManager'
23
23
  import { ClientEvent } from '../events'
24
- import { ProjectData } from 'grapesjs'
24
+ import { Page, PageProperties, ProjectData } from 'grapesjs'
25
25
  import { html, render, TemplateResult } from 'lit-html'
26
26
  import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'
27
27
 
@@ -62,10 +62,11 @@ export const storagePlugin = (editor: PublishableEditor) => {
62
62
  // Add useful commands
63
63
  editor.Commands.add(cmdPauseAutoSave, {
64
64
  run: () => {
65
- if(!editor.StorageManager.isAutosave()) throw new Error('Autosave is not enabled')
65
+ if(!editor.StorageManager.isAutosave()) console.warn('Autosave is not enabled')
66
66
  editor.StorageManager.setAutosave(false)
67
67
  },
68
68
  stop: () => {
69
+ if(editor.StorageManager.isAutosave()) console.warn('Autosave is already enabled')
69
70
  editor.editor.set('changesCount', 0)
70
71
  editor.UndoManager.clear()
71
72
  editor.StorageManager.setAutosave(true)
@@ -80,23 +81,34 @@ export const storagePlugin = (editor: PublishableEditor) => {
80
81
  const data = await websiteLoad({ websiteId: options.id, connectorId: user.storage.connectorId }) as WebsiteData
81
82
  if (data.assets) data.assets = addTempDataToAssetUrl(data.assets, options.id, user.storage.connectorId)
82
83
  if (data.styles) data.styles = addTempDataToStyles(data.styles, options.id, user.storage.connectorId)
83
- //setTimeout(() => progressiveLoadPages(editor, data))
84
84
  if (!data.pages) {
85
85
  // This happens when the website was just created
86
86
  // Let grapesjs create the pages in the frontend
87
87
  return data
88
88
  } else {
89
- await progressiveLoadPages(editor, data)
90
- return {}
89
+ editor.runCommand(cmdPauseAutoSave)
90
+ const { pages, ...rest } = data
91
+ // Load any additional project data, e.g. symbols, but not the ones we progressive load
92
+ loader && render(getLoaderHtml('Loading styles, assets and symbols', 0, pages.length + 1), loader)
93
+ // Add to the project, everything but pages
94
+ editor.loadProjectData(rest)
95
+ // Add the pages to the project
96
+ await progressiveLoadPages(editor, pages)
97
+ await nextFrame()
98
+ // Select the first page
99
+ const firstPage = editor.Pages.getAll()[0]
100
+ if (firstPage) editor.Pages.select(firstPage)
101
+ loader && render(getLoaderHtml('Starting', 1, 1), loader)
102
+ await nextFrame()
103
+ editor.once('canvas:frame:load', () => editor.stopCommand(cmdPauseAutoSave))
104
+ return data
91
105
  }
92
106
  } catch (err) {
93
107
  editor.UndoManager.clear()
94
108
  if (err.httpStatusCode === 401) {
95
109
  editor.once(eventLoggedIn, async () => {
96
110
  try {
97
- editor.runCommand(cmdPauseAutoSave)
98
111
  await editor.Storage.load(options)
99
- editor.once('canvas:frame:load', () => editor.stopCommand(cmdPauseAutoSave))
100
112
  } catch (err) {
101
113
  console.error('connectorPlugin load error', err)
102
114
  throw err
@@ -115,7 +127,7 @@ export const storagePlugin = (editor: PublishableEditor) => {
115
127
  return await this.addToQueue(myData, options)
116
128
  },
117
129
 
118
- async addToQueue(data: WebsiteData, options) {
130
+ async addToQueue(data: WebsiteData, options: { id: WebsiteId, connectorId: ConnectorId }) {
119
131
  // Handle concurrent saving
120
132
  if (lastPendingSaving && lastPendingSaving !== data) {
121
133
  // Cancel previous saving
@@ -125,7 +137,7 @@ export const storagePlugin = (editor: PublishableEditor) => {
125
137
  return this.doStore(lastPendingSaving, options)
126
138
  },
127
139
 
128
- async doStore(data, options) {
140
+ async doStore(data: WebsiteData, options: { id: WebsiteId, connectorId: ConnectorId }) {
129
141
  if (editor.PublicationManager.status === PublicationStatus.STATUS_PENDING) {
130
142
  // Publication is pending, save is delayed
131
143
  return new Promise((resolve) => {
@@ -217,32 +229,15 @@ export const storagePlugin = (editor: PublishableEditor) => {
217
229
  }
218
230
  }
219
231
 
220
- async function progressiveLoadPages(editor: PublishableEditor, data: ProjectData) {
232
+ async function progressiveLoadPages(editor: PublishableEditor, pages: Page[]) {
221
233
  editor.Pages.getAll().forEach(page => editor.Pages.remove(page))
222
- const max = data.pages.length + 1
223
234
  let i = 0
224
- for (const page of data.pages) {
225
- loader && render(getLoaderHtml(`Loading page <strong>${++i}</strong> / ${data.pages.length}`, i, max), loader)
235
+ for (const page of pages) {
236
+ loader && render(getLoaderHtml(`Loading page <strong>${++i}</strong> / ${pages.length + 1}`, i, pages.length + 1), loader)
226
237
  //await new Promise(resolve => setTimeout(resolve, 1000 * 60 * 1000))
227
238
  await nextFrame()
228
239
  const newPage = editor.Pages.add({
229
240
  ...page,
230
- })
241
+ } as PageProperties)
231
242
  }
232
-
233
- // Charger les styles, assets, etc. après les pages
234
- loader && render(getLoaderHtml('Loading styles and assets', data.pages.length + 1, max), loader)
235
- if (data.styles) editor.setStyle(data.styles)
236
- if (data.assets) editor.AssetManager.add(data.assets)
237
-
238
- await nextFrame()
239
- setTimeout(() => {
240
- // FIXME: Why is this setTimeout needed?
241
- editor.trigger('storage:end:load', data)
242
- editor.trigger('canvas:frame:load', editor)
243
- })
244
-
245
- // Sélectionner la première page
246
- const firstPage = editor.Pages.getAll()[0]
247
- if (firstPage) editor.Pages.select(firstPage)
248
243
  }
@@ -21,6 +21,7 @@
21
21
  *
22
22
  */
23
23
 
24
+ import { Editor } from 'grapesjs'
24
25
  import { DEV_MESSAGE } from '../constants'
25
26
  import { ClientConfig } from './config'
26
27
  import { ClientEvent } from './events'
@@ -85,9 +86,7 @@ export async function start(options = {}): Promise<void> {
85
86
 
86
87
  // Load the site
87
88
  try {
88
- editor.runCommand(cmdPauseAutoSave)
89
89
  await editor.load(null)
90
- editor.once('canvas:frame:load', () => editor.stopCommand(cmdPauseAutoSave))
91
90
  } catch(e) {
92
91
  if(e.httpStatusCode === 401) {
93
92
  // Unauthorized, will try to login
@@ -103,7 +102,7 @@ export async function start(options = {}): Promise<void> {
103
102
  }
104
103
  }
105
104
 
106
- function loaded(editor) {
105
+ function loaded(editor: Editor) {
107
106
  document.querySelector('.silex-loader').classList.add('silex-dialog-hide')
108
107
  document.querySelector('#gjs').classList.remove('silex-dialog-hide')
109
108
  config.emit(ClientEvent.STARTUP_END, { editor, config })
package/src/ts/page.ts CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  // Page related functions
19
19
  // This is used on the client and the server
20
- export function getPageSlug(pageName) {
20
+ export function getPageSlug(pageName: string | undefined) {
21
21
  return (pageName || 'index')
22
22
  .toLowerCase()
23
23
  .replace(/[^a-z0-9 -]/g, '')
@@ -2,6 +2,7 @@ import { ServerConfig } from '../../server/config'
2
2
  import { expect, jest, beforeEach, afterEach, it, describe } from '@jest/globals'
3
3
  import { ConnectorType, WebsiteData, WebsiteId } from '../../types'
4
4
  import { Readable } from 'stream'
5
+ import { WEBSITE_DATA_FILE } from '../../constants'
5
6
 
6
7
  const mocks = {
7
8
  access: jest.fn(),
@@ -64,7 +65,7 @@ describe('FtpConnector website', () => {
64
65
  await connector.updateWebsite(dummySession, dummyId, dummyWebsite)
65
66
  expect(mocks.uploadFrom).toHaveBeenCalledTimes(1)
66
67
  expect(mocks.uploadFrom.mock.calls[0][0]).toBeInstanceOf(Readable)
67
- expect(mocks.uploadFrom.mock.calls[0][1]).toBe(`${storageRootPath}/${dummyId}/website.json`)
68
+ expect(mocks.uploadFrom.mock.calls[0][1]).toBe(`${storageRootPath}/${dummyId}/${WEBSITE_DATA_FILE}`)
68
69
  })
69
70
  })
70
71
 
@@ -19,7 +19,7 @@ import { Client } from 'basic-ftp'
19
19
  import { Readable } from 'stream'
20
20
  import { ConnectorFile, ConnectorFileContent, HostingConnector, StatusCallback, StorageConnector, contentToReadable, contentToString, toConnectorData, toConnectorEnum} from '../../server/connectors/connectors'
21
21
  import { requiredParam } from '../../server/utils/validation'
22
- import { WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE } from '../../constants'
22
+ import { WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE, WEBSITE_PAGES_FOLDER } from '../../constants'
23
23
  import { ConnectorType, ConnectorUser, WebsiteMeta, FileMeta, JobData, JobStatus, WebsiteId, PublicationJobData, WebsiteMetaFileContent, defaultWebsiteData, WebsiteData, ConnectorOptions } from '../../types'
24
24
  import { ServerConfig } from '../../server/config'
25
25
  import { join } from 'path'
@@ -28,6 +28,7 @@ import { v4 as uuid } from 'uuid'
28
28
  import { JobManager } from '../../server/jobs'
29
29
  import { mkdtemp, rm, rmdir } from 'fs/promises'
30
30
  import { createReadStream } from 'fs'
31
+ import { stringify, split, merge } from '../../server/utils/websiteDataSerialization'
31
32
 
32
33
  /**
33
34
  * @fileoverview FTP connector for Silex
@@ -399,7 +400,7 @@ export default class FtpConnector implements StorageConnector<FtpSession> {
399
400
  const websiteId = requiredParam<WebsiteId>(id, 'website id')
400
401
  const path = join(this.rootPath(session), websiteId, WEBSITE_META_DATA_FILE)
401
402
  const ftp = await this.getClient(this.sessionData(session))
402
- await this.write(ftp, path, JSON.stringify(data))
403
+ await this.write(ftp, path, stringify(data))
403
404
  this.closeClient(ftp)
404
405
  }
405
406
 
@@ -442,10 +443,10 @@ export default class FtpConnector implements StorageConnector<FtpSession> {
442
443
  await this.mkdir(ftp, assetsPath)
443
444
  // create website data file
444
445
  const websiteDataPath = join(this.rootPath(session), id, WEBSITE_DATA_FILE)
445
- await this.write(ftp, websiteDataPath, JSON.stringify(defaultWebsiteData))
446
+ await this.write(ftp, websiteDataPath, stringify(defaultWebsiteData))
446
447
  // create website meta data file
447
448
  const websiteMetaDataPath = join(this.rootPath(session), id, WEBSITE_META_DATA_FILE)
448
- await this.write(ftp, websiteMetaDataPath, JSON.stringify({}))
449
+ await this.write(ftp, websiteMetaDataPath, stringify({}))
449
450
  // Clean up
450
451
  this.closeClient(ftp)
451
452
  // All good
@@ -469,16 +470,67 @@ export default class FtpConnector implements StorageConnector<FtpSession> {
469
470
  const storageRootPath = this.rootPath(session)
470
471
  const ftp = await this.getClient(this.sessionData(session))
471
472
  const websiteDataPath = join(storageRootPath, websiteId, WEBSITE_DATA_FILE)
472
- const data = await this.read(ftp, websiteDataPath)
473
- this.closeClient(ftp)
474
- return data
473
+
474
+ try {
475
+ const websiteDataStream = await this.read(ftp, websiteDataPath)
476
+ const websiteDataContent = await contentToString(websiteDataStream)
477
+
478
+ // Check if this is the new split format by trying to parse
479
+ const parsedData = JSON.parse(websiteDataContent)
480
+ if (parsedData.fileFormatVersion === '1.0.0') {
481
+ // Use the merge function to reconstruct website data
482
+ const pageLoader = async (pagePath: string): Promise<string> => {
483
+ const fullPath = join(storageRootPath, websiteId, pagePath)
484
+ const pageStream = await this.read(ftp, fullPath)
485
+ return await contentToString(pageStream)
486
+ }
487
+
488
+ const result = await merge(websiteDataContent, pageLoader)
489
+ this.closeClient(ftp)
490
+ return result
491
+ } else {
492
+ // Legacy format, return as is
493
+ this.closeClient(ftp)
494
+ return parsedData
495
+ }
496
+ } catch (error) {
497
+ this.closeClient(ftp)
498
+ throw error
499
+ }
475
500
  }
476
501
 
477
502
  async updateWebsite(session: FtpSession, websiteId: string, data: WebsiteData): Promise<void> {
478
503
  const storageRootPath = this.rootPath(session)
479
504
  const ftp = await this.getClient(this.sessionData(session))
480
- const websiteDataPath = join(storageRootPath, websiteId, WEBSITE_DATA_FILE)
481
- await this.write(ftp, websiteDataPath, JSON.stringify(data))
505
+
506
+ // Use the split function to create separate files for pages
507
+ const filesToWrite = split(data)
508
+
509
+ // **
510
+ // Delete pages that are not in the new website data
511
+ const pagesPath = join(storageRootPath, websiteId, WEBSITE_PAGES_FOLDER)
512
+ try {
513
+ const existingPageFiles = await this.readdir(ftp, pagesPath)
514
+ const newPageFiles = new Set(
515
+ filesToWrite
516
+ .filter(f => f.path.startsWith(WEBSITE_PAGES_FOLDER))
517
+ .map(f => f.path.replace(`${WEBSITE_PAGES_FOLDER}/`, ''))
518
+ )
519
+
520
+ for (const existingFile of existingPageFiles) {
521
+ if (existingFile.name.endsWith('.json') && !newPageFiles.has(existingFile.name)) {
522
+ await this.unlink(ftp, join(pagesPath, existingFile.name))
523
+ }
524
+ }
525
+ } catch (error) {
526
+ // Ignore error if pages directory doesn't exist yet
527
+ }
528
+
529
+ for (const file of filesToWrite) {
530
+ const filePath = join(storageRootPath, websiteId, file.path)
531
+ await this.write(ftp, filePath, file.content)
532
+ }
533
+
482
534
  this.closeClient(ftp)
483
535
  }
484
536