@silexlabs/silex 3.0.0-alpha.39 → 3.0.0-alpha.41

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 (110) hide show
  1. package/.silex.js +38 -8
  2. package/dist/client/client/config.d.ts +11 -41
  3. package/dist/client/client/grapesjs/LoginDialog.d.ts +13 -0
  4. package/dist/client/client/grapesjs/PublicationManager.d.ts +69 -0
  5. package/dist/client/client/grapesjs/PublicationUi.d.ts +58 -0
  6. package/dist/client/client/grapesjs/index.d.ts +18 -44
  7. package/dist/client/client/grapesjs/storage.d.ts +1 -0
  8. package/dist/client/client/services.d.ts +35 -0
  9. package/dist/client/constants.d.ts +17 -0
  10. package/dist/client/css/admin.css +86 -3
  11. package/dist/client/js/main.js +201 -90
  12. package/dist/client/js/main.js.LICENSE.txt +6 -0
  13. package/dist/client/types.d.ts +200 -29
  14. package/dist/constants.d.ts +21 -0
  15. package/dist/constants.js +38 -1
  16. package/dist/constants.js.map +1 -0
  17. package/dist/events.d.ts +2 -0
  18. package/dist/events.js +17 -0
  19. package/dist/events.js.map +1 -0
  20. package/dist/plugins/AuthPlugin.d.ts +1 -0
  21. package/dist/plugins/AuthPlugin.js +38 -0
  22. package/dist/plugins/AuthPlugin.js.map +1 -0
  23. package/dist/plugins/Cache.d.ts +2 -0
  24. package/dist/plugins/Cache.js +32 -0
  25. package/dist/plugins/Cache.js.map +1 -0
  26. package/dist/plugins/FtpBackend.d.ts +92 -0
  27. package/dist/plugins/FtpBackend.js +604 -0
  28. package/dist/plugins/FtpBackend.js.map +1 -0
  29. package/dist/plugins/HooksPlugin.d.ts +7 -0
  30. package/dist/plugins/HooksPlugin.js +76 -0
  31. package/dist/plugins/HooksPlugin.js.map +1 -0
  32. package/dist/plugins/PublicationPlugin.d.ts +17 -0
  33. package/dist/plugins/PublicationPlugin.js +170 -0
  34. package/dist/plugins/PublicationPlugin.js.map +1 -0
  35. package/dist/plugins/SslPlugin.d.ts +1 -0
  36. package/dist/plugins/SslPlugin.js +86 -0
  37. package/dist/plugins/SslPlugin.js.map +1 -0
  38. package/dist/plugins/StaticPlugin.d.ts +1 -0
  39. package/dist/plugins/StaticPlugin.js +66 -0
  40. package/dist/plugins/StaticPlugin.js.map +1 -0
  41. package/dist/plugins/WebsitePlugin.d.ts +14 -0
  42. package/dist/plugins/WebsitePlugin.js +302 -0
  43. package/dist/plugins/WebsitePlugin.js.map +1 -0
  44. package/dist/server/backends/FsBackend.d.ts +42 -0
  45. package/dist/server/backends/FsBackend.js +269 -0
  46. package/dist/server/backends/FsBackend.js.map +1 -0
  47. package/dist/server/backends/index.d.ts +95 -0
  48. package/dist/server/backends/index.js +255 -0
  49. package/dist/server/backends/index.js.map +1 -0
  50. package/dist/server/cli.js +0 -1
  51. package/dist/server/config.d.ts +58 -0
  52. package/dist/server/config.js +128 -24
  53. package/dist/server/config.js.map +1 -0
  54. package/dist/server/express.js +31 -3
  55. package/dist/server/index.js +9 -20
  56. package/dist/server/jobs.d.ts +6 -0
  57. package/dist/server/jobs.js +75 -0
  58. package/dist/server/jobs.js.map +1 -0
  59. package/dist/server/utils/validation.d.ts +7 -0
  60. package/dist/server/utils/validation.js +37 -0
  61. package/dist/server/utils/validation.js.map +1 -0
  62. package/dist/types.d.ts +270 -0
  63. package/dist/types.js +35 -28
  64. package/dist/types.js.map +1 -0
  65. package/package.json +19 -8
  66. package/public/silex.js +17 -0
  67. package/src/css/dialog.scss +9 -2
  68. package/src/css/grapesjs.scss +79 -0
  69. package/src/css/project-bar.scss +2 -2
  70. package/src/example-client-config.js +17 -0
  71. package/src/html/templates/includes.pug +0 -1
  72. package/src/ts/client/config.ts +7 -6
  73. package/src/ts/client/grapesjs/LoginDialog.ts +190 -0
  74. package/src/ts/client/grapesjs/PublicationManager.ts +333 -0
  75. package/src/ts/client/grapesjs/PublicationUi.ts +320 -0
  76. package/src/ts/client/grapesjs/index.ts +88 -85
  77. package/src/ts/client/grapesjs/storage.ts +110 -0
  78. package/src/ts/client/index.ts +5 -7
  79. package/src/ts/client/services.ts +129 -0
  80. package/src/ts/constants.ts +21 -0
  81. package/src/ts/plugins/AuthPlugin.ts +38 -0
  82. package/src/ts/plugins/Cache.ts +28 -0
  83. package/src/ts/plugins/FtpBackend.ts +689 -0
  84. package/src/ts/plugins/HooksPlugin.ts +79 -0
  85. package/src/ts/plugins/PublicationPlugin.ts +170 -0
  86. package/src/{plugins/SslPlugin.js → ts/plugins/SslPlugin.ts} +24 -6
  87. package/src/ts/plugins/StaticPlugin.ts +68 -0
  88. package/src/ts/plugins/WebsitePlugin.ts +318 -0
  89. package/src/ts/server/backends/FsBackend.ts +266 -0
  90. package/src/ts/server/backends/index.ts +309 -0
  91. package/src/ts/server/cli.ts +0 -2
  92. package/src/ts/server/config.ts +103 -33
  93. package/src/ts/server/express.ts +37 -3
  94. package/src/ts/server/index.ts +24 -14
  95. package/src/ts/server/jobs.test.ts +74 -0
  96. package/src/ts/server/jobs.ts +77 -0
  97. package/src/ts/server/utils/validation.ts +33 -0
  98. package/src/ts/types.ts +167 -59
  99. package/dist/client/client/grapesjs/publish.d.ts +0 -18
  100. package/dist/page.js +0 -15
  101. package/src/plugins/AuthPlugin.js +0 -21
  102. package/src/plugins/Cache.js +0 -11
  103. package/src/plugins/DefaultBackend.js +0 -149
  104. package/src/plugins/ExpressPlugin.js +0 -39
  105. package/src/plugins/HooksPlugin.js +0 -58
  106. package/src/plugins/PublishPlugin.js +0 -69
  107. package/src/plugins/StaticPlugin.js +0 -48
  108. package/src/plugins/WebsitePlugin.js +0 -207
  109. package/src/ts/client/grapesjs/publish.ts +0 -403
  110. /package/.silex/default/{.silex.data.json → website.json} +0 -0
@@ -15,7 +15,7 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import grapesjs from 'grapesjs'
18
+ import grapesjs, { Editor } from 'grapesjs'
19
19
  import openImport from './openImport'
20
20
 
21
21
  /**
@@ -35,6 +35,7 @@ import symbolsPlugin from '@silexlabs/grapesjs-symbols'
35
35
  import loadingPlugin from '@silexlabs/grapesjs-loading'
36
36
  import fontsDialogPlugin, { cmdOpenFonts } from '@silexlabs/grapesjs-fonts'
37
37
  import symbolDialogsPlugin, { cmdPromptAddSymbol } from './symbolDialogs'
38
+ import loginDialogPlugin, { LoginDialogOptions } from './LoginDialog'
38
39
 
39
40
  import { pagePanelPlugin, cmdTogglePages, cmdAddPage } from './page-panel'
40
41
  import { newPageDialog, cmdOpenNewPageDialog } from './new-page-dialog'
@@ -44,30 +45,36 @@ import { blocksPlugin } from './blocks'
44
45
  import { semanticPlugin } from './semantic'
45
46
  import { richTextPlugin } from './rich-text'
46
47
  import { internalLinksPlugin } from './internal-links'
47
- import { publishPlugin } from './publish'
48
+ import publicationManagerPlugin, { PublicationManagerOptions } from './PublicationManager'
48
49
  import { templatePlugin } from './template'
49
50
  import { eleventyPlugin } from './eleventy'
51
+ import { storagePlugin } from './storage'
52
+ import { API_WEBSITE_READ, API_WEBSITE_WRITE } from '../../constants'
53
+ import { BackendId, WebsiteId } from '../../types'
54
+ import { ApiError } from '../services'
50
55
 
51
56
  const plugins = [
52
- {name: './grapesjs/project-bar', value: projectBarPlugin}, // has to be before panels and dialogs
53
- {name: './grapesjs/settings', value: settingsDialog},
57
+ {name: './project-bar', value: projectBarPlugin}, // has to be before panels and dialogs
58
+ {name: './settings', value: settingsDialog},
54
59
  {name: '@silexlabs/grapesjs-fonts', value: fontsDialogPlugin},
55
- {name: './grapesjs/new-page-dialog', value: newPageDialog},
56
- {name: './grapesjs/page-panel', value: pagePanelPlugin},
60
+ {name: './new-page-dialog', value: newPageDialog},
61
+ {name: './page-panel', value: pagePanelPlugin},
57
62
  {name: 'grapesjs-blocks-basic', value: blocksBasicPlugin},
58
- {name: './grapesjs/blocks', value: blocksPlugin},
59
- {name: './grapesjs/semantic', value: semanticPlugin},
60
- {name: './grapesjs/rich-text', value: richTextPlugin},
63
+ {name: './blocks', value: blocksPlugin},
64
+ {name: './semantic', value: semanticPlugin},
65
+ {name: './rich-text', value: richTextPlugin},
61
66
  {name: 'grapesjs-style-filter', value: styleFilterPlugin},
62
67
  {name: 'grapesjs-plugin-forms', value: formPlugin},
63
68
  {name: 'grapesjs-custom-code', value: codePlugin},
64
- {name: './grapesjs/internal-links', value: internalLinksPlugin},
69
+ {name: './internal-links', value: internalLinksPlugin},
65
70
  {name: '@silexlabs/grapesjs-ui-suggest-classes', value: uiSuggestClasses},
66
- {name: './grapesjs/symbolDialogs', value: symbolDialogsPlugin},
71
+ {name: './symbolDialogs', value: symbolDialogsPlugin},
67
72
  {name: '@silexlabs/grapesjs-symbols', value: symbolsPlugin},
68
- {name: './grapesjs/publish', value: publishPlugin},
69
- {name: './grapesjs/template', value: templatePlugin},
70
- {name: './grapesjs/eleventy', value: eleventyPlugin},
73
+ {name: './PublicationManager', value: publicationManagerPlugin},
74
+ {name: './template', value: templatePlugin},
75
+ {name: './eleventy', value: eleventyPlugin},
76
+ {name: './storage', value: storagePlugin},
77
+ {name: './LoginDialog', value: loginDialogPlugin},
71
78
  {name: '@silexlabs/grapesjs-loading', value: loadingPlugin},
72
79
  ]
73
80
  // Check that all plugins are loaded correctly
@@ -83,7 +90,7 @@ plugins
83
90
  const catBasic = 'Containers'
84
91
  const catComponents = 'Components'
85
92
 
86
- export function getEditorConfig(id: string, rootUrl: string) {
93
+ export function getEditorConfig(id: WebsiteId, backendId: BackendId, rootUrl: string) {
87
94
  return {
88
95
  container: '#gjs',
89
96
  height: '100%',
@@ -101,20 +108,27 @@ export function getEditorConfig(id: string, rootUrl: string) {
101
108
  },
102
109
 
103
110
  assetManager: {
104
- upload: `${rootUrl}assets/`,
111
+ upload: `${rootUrl}/assets/?id=${id}`,
105
112
  },
106
113
 
107
114
  storageManager: {
108
115
  autoload: true,
109
- type: 'remote',
116
+ //type: 'remote',
117
+ type: 'backend',
110
118
  options: {
111
- remote: {
112
- urlStore: `${rootUrl}website/?id=${id}`,
113
- urlLoad: `${rootUrl}website/?id=${id}`,
119
+ backend: {
120
+ id,
121
+ backendId,
114
122
  },
123
+ //remote: {
124
+ // urlStore: `${rootUrl}/${API_WEBSITE_WRITE}?id=${id}`,
125
+ // urlLoad: `${rootUrl}/${API_WEBSITE_READ}?id=${id}`,
126
+ //},
115
127
  },
116
128
  },
117
129
 
130
+ cssIcons: '/css/all.min.css',
131
+
118
132
  plugins: plugins.map(p => p.value),
119
133
 
120
134
  pluginsOpts: {
@@ -138,13 +152,13 @@ export function getEditorConfig(id: string, rootUrl: string) {
138
152
  command: 'open-blocks',
139
153
  }, {
140
154
  id: 'symbols-btn',
141
- className: 'symbols-btn fa fa-fw fa-diamond',
155
+ className: 'symbols-btn fa-regular fa-gem',
142
156
  attributes: { title: 'Symbols', containerClassName: 'symbols-list-container', },
143
157
  command: 'open-symbols',
144
158
  buttons: [
145
159
  {
146
160
  id: 'symbol-create-button',
147
- className: 'pages__add-page fa fa-plus',
161
+ className: 'pages__add-page fa-solid fa-plus',
148
162
  label: 'Create symbol from selection',
149
163
  command: cmdPromptAddSymbol,
150
164
  },
@@ -161,17 +175,17 @@ export function getEditorConfig(id: string, rootUrl: string) {
161
175
  }],
162
176
  }, {
163
177
  id: 'layer-manager-btn',
164
- className: 'layer-manager-btn fa fa-fw fa-list',
178
+ className: 'layer-manager-btn fa-solid fa-layer-group',
165
179
  attributes: { title: 'Layers', containerClassName: 'layer-manager-container', },
166
180
  command: 'open-layers',
167
181
  }, {
168
182
  id: 'font-dialog-btn',
169
- className: 'font-manager-btn fa fa-fw fa-font',
183
+ className: 'font-manager-btn fa-solid fa-font',
170
184
  attributes: { title: 'Fonts' },
171
185
  command: cmdOpenFonts,
172
186
  }, {
173
187
  id: 'settings-dialog-btn',
174
- className: 'page-panel-btn fa fa-fw fa-cog',
188
+ className: 'page-panel-btn fa-solid fa-gears',
175
189
  attributes: { title: 'Settings' },
176
190
  command: cmdOpenSettings,
177
191
  }, {
@@ -186,11 +200,10 @@ export function getEditorConfig(id: string, rootUrl: string) {
186
200
  },
187
201
  ],
188
202
  },
189
- [publishPlugin as any]: {
203
+ [publicationManagerPlugin as any]: {
190
204
  appendTo: 'options',
191
- rootUrl,
192
- id,
193
- },
205
+ websiteId: id,
206
+ } as PublicationManagerOptions,
194
207
  [pagePanelPlugin as any]: {
195
208
  cmdOpenNewPageDialog,
196
209
  cmdOpenSettings,
@@ -216,6 +229,9 @@ export function getEditorConfig(id: string, rootUrl: string) {
216
229
  [fontsDialogPlugin as any]: {
217
230
  api_key: 'AIzaSyAdJTYSLPlKz4w5Iqyy-JAF2o8uQKd1FKc',
218
231
  },
232
+ [loginDialogPlugin as any]: {
233
+ id,
234
+ } as LoginDialogOptions,
219
235
  },
220
236
  }
221
237
  }
@@ -223,74 +239,61 @@ export function getEditorConfig(id: string, rootUrl: string) {
223
239
  // ////////////////////
224
240
  // Initialize editor
225
241
  // ////////////////////
226
- // Expose grapes for plugins
227
- // window['grapesjs'] = grapesjs
228
-
229
242
  // Keep a ref to the editor singleton
230
- let editor
231
-
232
- export function initEditor(config, grapesJsPlugins) {
243
+ let editor: Editor
244
+ export async function initEditor(config, grapesJsPlugins) {
233
245
  if(editor) throw new Error('Grapesjs editor already created')
246
+ return new Promise<Editor>((resolve, reject) => {
247
+ try {
248
+ editor = grapesjs.init({
249
+ plugins: [
250
+ ...grapesJsPlugins,
251
+ ...config.plugins,
252
+ ],
253
+ ...config,
254
+ })
255
+ } catch(e) {
256
+ console.error('Error initializing GrapesJs with plugins:', plugins, e)
257
+ reject(e)
258
+ }
234
259
 
235
- try {
236
- editor = grapesjs.init({
237
- plugins: [
238
- ...grapesJsPlugins,
239
- ...config.plugins,
240
- ],
241
- ...config,
242
- })
243
- } catch(e) {
244
- console.error('Error initializing GrapesJs with plugins:', plugins, e)
245
- throw e
246
- }
260
+ // customize the editor
261
+ ['text']
262
+ .forEach(id => editor.Blocks.get(id)?.set('category', 'Basics'))
263
+ ;['image', 'video']
264
+ .forEach(id => editor.Blocks.get(id)?.set('category', 'Media'))
265
+ ;['map']
266
+ .forEach(id => editor.Blocks.get(id)?.set('category', 'Components'))
267
+ editor.Blocks.render([])
247
268
 
248
- // customize the editor
249
- ['text']
250
- .forEach(id => editor.Blocks.get(id)?.set('category', 'Basics'))
251
- ;['image', 'video']
252
- .forEach(id => editor.Blocks.get(id)?.set('category', 'Media'))
253
- ;['map']
254
- .forEach(id => editor.Blocks.get(id)?.set('category', 'Components'))
255
- editor.Blocks.render()
269
+ editor.Commands.add('gjs-open-import-webpage', openImport(editor, {
270
+ modalImportLabel: '',
271
+ modalImportContent: 'Paste a web page HTML code here.',
272
+ modalImportButton: 'Import',
273
+ modalImportTitle: 'Import from website',
274
+ }))
256
275
 
257
- editor.Commands.add('gjs-open-import-webpage', openImport(editor, {
258
- modalImportLabel: '',
259
- modalImportContent: 'Paste a web page HTML code here.',
260
- modalImportButton: 'Import',
261
- modalImportTitle: 'Import from website',
262
- }))
276
+ editor.on('load', () => {
277
+ // // move the options panel to the sidebar
278
+ // const optionsEl = editor.Panels.getPanel('options').view.el
279
+ // editor.Panels.getPanel('project-bar-panel').view.el
280
+ // .appendChild(options)
281
+ // options.style.width = 0
282
+ // options.style.position = 'static'
263
283
 
264
- editor.on('load', () => {
265
- // // move the options panel to the sidebar
266
- // const optionsEl = editor.Panels.getPanel('options').view.el
267
- // editor.Panels.getPanel('project-bar-panel').view.el
268
- // .appendChild(options)
269
- // options.style.width = 0
270
- // options.style.position = 'static'
271
- // remove blocks and layers buttons from the properties
272
- setTimeout(() => {
284
+ // remove blocks and layers buttons from the properties
273
285
  editor.Panels.getPanel('views').buttons.remove('open-blocks')
274
286
  editor.Panels.getPanel('views').buttons.remove('open-layers')
275
287
  editor.Panels.getPanel('views').view.el.firstChild.style.justifyContent = 'initial' // align left
276
288
  editor.Panels.getPanel('options').buttons.remove('export-template')
277
- })
278
289
 
279
- // use the style filter plugin
280
- editor.StyleManager.addProperty('extra',{ extend: 'filter' })
281
- })
290
+ editor.BlockManager.render(null)
282
291
 
283
- // Handle errors
284
- editor.on('storage:error:load', (err) => {
285
- editor.Modal.open({
286
- title: 'Error loading website',
287
- content: `This website could not be loaded.<br><hr>${err}`,
288
- })
289
- })
290
- editor.on('storage:error:store', (err) => {
291
- editor.Modal.open({
292
- title: 'Error saving website',
293
- content: `This website could not be saved.<br><hr>${err}`,
292
+ // use the style filter plugin
293
+ editor.StyleManager.addProperty('extra',{ extend: 'filter' })
294
+
295
+ // GrapesJs editor is ready
296
+ resolve(editor)
294
297
  })
295
298
  })
296
299
  }
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import { BackendId, WebsiteId, WebsiteData } from '../../types'
19
+ import { ApiError, LoginStatus, websiteLoad, websiteSave } from '../services'
20
+ import { cmdLogin, eventLoggedIn, eventLoggedOut, getCurrentLoginStatus } from './LoginDialog'
21
+
22
+ async function wait(ms: number) {
23
+ return new Promise((resolve) => {
24
+ setTimeout(resolve, ms)
25
+ })
26
+ }
27
+ export const storagePlugin = (editor) => {
28
+ editor.Storage.add('backend', {
29
+ async load(options: { id: WebsiteId, backendId: BackendId }): Promise<WebsiteData> {
30
+ try {
31
+ const status = await getCurrentLoginStatus(editor)
32
+ if(status?.backend?.isLoggedIn) {
33
+ const data = await websiteLoad(options.id, options.backendId)
34
+ return data
35
+ } else {
36
+ return new Promise((resolve, reject) => {
37
+ editor.once(eventLoggedIn, async () => {
38
+ try {
39
+ const data = await editor.Storage.load(options)
40
+ //editor.loadProjectData(data)
41
+ resolve(data)
42
+ } catch (err) {
43
+ console.error('backendPlugin load error', err)
44
+ reject(err)
45
+ }
46
+ })
47
+ editor.Commands.run(cmdLogin)
48
+ })
49
+ }
50
+ } catch (err) {
51
+ console.error('backendPlugin load error', err)
52
+ throw err
53
+ }
54
+ },
55
+
56
+ async store(data: WebsiteData, options: { id: WebsiteId, backendId: BackendId }) {
57
+ try {
58
+ if(await getCurrentLoginStatus(editor)) {
59
+ await websiteSave(options.id, options.backendId, data)
60
+ } else {
61
+ editor.once(eventLoggedIn, () => {
62
+ return editor.Storage.save(options)
63
+ })
64
+ editor.Commands.run(cmdLogin)
65
+ }
66
+ } catch (err) {
67
+ console.error('backendPlugin store error', err)
68
+ throw err
69
+ }
70
+ }
71
+ })
72
+ // Handle errors
73
+ editor.on('storage:error:load', (err) => {
74
+ handleError(err)
75
+ })
76
+ editor.on('storage:error:store', (err) => {
77
+ handleError(err)
78
+ })
79
+ function handleError(err: Error) {
80
+ console.error('Error with loading / saving', err, err instanceof ApiError)
81
+ if (err instanceof ApiError) {
82
+ console.error('ApiError', err.code, err.message)
83
+ switch (err.code) {
84
+ case 404:
85
+ return editor.Modal.open({
86
+ title: 'Website not found',
87
+ content: `This website could not be found.<br><hr>${err.message}`,
88
+ })
89
+ case 403:
90
+ return editor.Modal.open({
91
+ title: 'Access denied',
92
+ content: `You are not allowed to access this website.<br><hr>${err.message}`,
93
+ })
94
+ case 401:
95
+ editor.trigger(eventLoggedOut)
96
+ return
97
+ default:
98
+ return editor.Modal.open({
99
+ title: 'Error',
100
+ content: `An error occured. ${err.message}`,
101
+ })
102
+ }
103
+ } else {
104
+ return editor.Modal.open({
105
+ title: 'Error',
106
+ content: `An error occured. ${err.message}`,
107
+ })
108
+ }
109
+ }
110
+ }
@@ -50,7 +50,7 @@ export async function start(options = {}) {
50
50
  // Start grapesjs
51
51
  console.log('start', {config})
52
52
  try{
53
- initEditor(config.editor, config.grapesJsPlugins)
53
+ await initEditor(config.editor, config.grapesJsPlugins)
54
54
  } catch(e) {
55
55
  console.error('Error starting Silex', e)
56
56
  throw e
@@ -60,10 +60,8 @@ export async function start(options = {}) {
60
60
  getEditor().I18n.setLocale(config.lang)
61
61
 
62
62
  // End of loading
63
- getEditor().on('load', () => {
64
- console.log('Silex started')
65
- document.querySelector('.silex-loader').classList.add('silex-dialog-hide')
66
- document.querySelector('#gjs').classList.remove('silex-dialog-hide')
67
- config.emit(EVENT_STARTUP_END)
68
- })
63
+ console.log('Silex started')
64
+ document.querySelector('.silex-loader').classList.add('silex-dialog-hide')
65
+ document.querySelector('#gjs').classList.remove('silex-dialog-hide')
66
+ config.emit(EVENT_STARTUP_END)
69
67
  }
@@ -0,0 +1,129 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import { API_BACKEND_LIST, API_BACKEND_LOGIN_STATUS, API_BACKEND_LOGOUT, API_PUBLICATION_PUBLISH, API_PUBLICATION_STATUS, API_WEBSITE_ASSETS_READ, API_WEBSITE_ASSETS_WRITE, API_WEBSITE_DELETE, API_WEBSITE_LIST, API_WEBSITE_READ, API_WEBSITE_WRITE } from '../constants'
19
+ import { ApiPublicationPublishRequestBody, ApiPublicationPublishRequestQuery, ApiPublicationPublishResponse, ApiPublicationStatusRequestQuery, ApiPublicationStatusResponse, BackendId, JobData, JobId, PublicationJobData, WebsiteId, ApiBackendListResponse, ApiBackendListRequestQuery, BackendData, BackendType, ApiWebsiteReadRequestQuery, ApiWebsiteReadResponse, WebsiteData, ApiWebsiteWriteRequestQuery, ApiWebsiteWriteRequestBody, ApiWebsiteWriteResponse, ApiWebsiteDeleteRequestQuery, ApiWebsiteAssetsReadRequestQuery, ApiWebsiteAssetsReadResponse, ApiWebsiteAssetsWriteRequestQuery, ApiWebsiteAssetsWriteRequestBody, ApiWebsiteAssetsWriteResponse, ClientSideFile, PublicationData, ApiBackendLoginStatusRequestQuery, ApiBackendLoginStatusResponse, BackendUser, WebsiteMeta, ApiBackendLogoutRequestQuery } from '../types'
20
+
21
+ export enum ApiRoute {
22
+ PUBLICATION_PUBLISH = API_PUBLICATION_PUBLISH,
23
+ PUBLICATION_STATUS = API_PUBLICATION_STATUS,
24
+ BACKEND_LOGOUT = API_BACKEND_LOGOUT,
25
+ BACKEND_LIST = API_BACKEND_LIST,
26
+ BACKEND_LOGIN_STATUS = API_BACKEND_LOGIN_STATUS,
27
+ WEBSITE_READ = API_WEBSITE_READ,
28
+ WEBSITE_WRITE = API_WEBSITE_WRITE,
29
+ WEBSITE_DELETE = API_WEBSITE_DELETE,
30
+ WEBSITE_LIST = API_WEBSITE_LIST,
31
+ WEBSITE_ASSETS_READ = API_WEBSITE_ASSETS_READ,
32
+ WEBSITE_ASSETS_WRITE = API_WEBSITE_ASSETS_WRITE,
33
+ }
34
+
35
+ export interface LoginStatus {
36
+ backend: BackendData,
37
+ user: BackendUser,
38
+ websiteMeta?: WebsiteMeta,
39
+ }
40
+
41
+ export class ApiError extends Error {
42
+ constructor(message: string, public readonly code: number) {
43
+ super(message)
44
+ console.error({ message, code })
45
+ }
46
+ }
47
+
48
+ const ROOT_URL = window.location.origin
49
+
50
+ export async function loginStatus(backendId: BackendId, type: BackendType, id?: WebsiteId): Promise<LoginStatus> {
51
+ return api<ApiBackendLoginStatusRequestQuery, null, ApiBackendLoginStatusResponse>(ApiRoute.BACKEND_LOGIN_STATUS, 'GET', {
52
+ backendId,
53
+ type,
54
+ id,
55
+ })
56
+ }
57
+
58
+ export async function logout(backendId: BackendId, type: BackendType): Promise<void> {
59
+ return api<ApiBackendLogoutRequestQuery, null, null>(ApiRoute.BACKEND_LOGOUT, 'POST', { backendId, type })
60
+ }
61
+
62
+ export async function publish(websiteId: WebsiteId, data: PublicationData): Promise<[url: string, job: PublicationJobData]> {
63
+ const { url, job } = await api<ApiPublicationPublishRequestQuery, ApiPublicationPublishRequestBody, ApiPublicationPublishResponse>(
64
+ ApiRoute.PUBLICATION_PUBLISH,
65
+ 'POST',
66
+ { id: websiteId },
67
+ data
68
+ )
69
+ return [url, job]
70
+ }
71
+
72
+ export async function publicationStatus(jobId: JobId): Promise<PublicationJobData> {
73
+ return api<ApiPublicationStatusRequestQuery, null, ApiPublicationStatusResponse>(
74
+ ApiRoute.PUBLICATION_STATUS,
75
+ 'GET',
76
+ { jobId },
77
+ )
78
+ }
79
+
80
+ export async function backendList(type: BackendType): Promise<BackendData[]> {
81
+ const list = await api<ApiBackendListRequestQuery, null, ApiBackendListResponse>(ApiRoute.BACKEND_LIST, 'GET', { type })
82
+ return list as BackendData[]
83
+ }
84
+
85
+
86
+ export async function websiteList(backendId: BackendId): Promise<WebsiteMeta[]> {
87
+ return api<ApiWebsiteReadRequestQuery, null, ApiWebsiteReadResponse>(ApiRoute.WEBSITE_LIST, 'GET', { backendId }) as Promise<WebsiteMeta[]>
88
+ }
89
+
90
+ export async function websiteLoad(id: WebsiteId, backendId: BackendId): Promise<WebsiteData> {
91
+ return api<ApiWebsiteReadRequestQuery, null, ApiWebsiteReadResponse>(ApiRoute.WEBSITE_READ, 'GET', { id, backendId }) as Promise<WebsiteData>
92
+ }
93
+
94
+ export async function websiteSave(id: WebsiteId, backendId: BackendId, data: WebsiteData): Promise<void> {
95
+ const { message } = await api<ApiWebsiteWriteRequestQuery, ApiWebsiteWriteRequestBody, ApiWebsiteWriteResponse>(ApiRoute.WEBSITE_WRITE, 'POST', { id, backendId }, data)
96
+ }
97
+
98
+ export async function websiteDelete(id: WebsiteId, backendId: BackendId): Promise<void> {
99
+ await api<ApiWebsiteDeleteRequestQuery, null, null>(ApiRoute.WEBSITE_DELETE, 'DELETE', { id, backendId })
100
+ }
101
+
102
+ export async function websiteAssetsLoad(path: string, id: WebsiteId, backendId: BackendId): Promise<string> {
103
+ return api<ApiWebsiteAssetsReadRequestQuery, null, ApiWebsiteAssetsReadResponse>(`${ApiRoute.WEBSITE_ASSETS_READ}/${path}`, 'GET', { id, backendId })
104
+ }
105
+
106
+ export async function websiteAssetsSave(id: WebsiteId, backendId: BackendId, files: ClientSideFile[]): Promise<string[]> {
107
+ const { data } = await api<ApiWebsiteAssetsWriteRequestQuery, ApiWebsiteAssetsWriteRequestBody, ApiWebsiteAssetsWriteResponse>(ApiRoute.WEBSITE_ASSETS_WRITE, 'POST', { id, backendId }, files)
108
+ return data
109
+ }
110
+
111
+ export async function api<ReqQuery, ReqBody, ResBody>(route: ApiRoute | string, method: string, query?: ReqQuery, payload?: ReqBody): Promise<ResBody> {
112
+ const url = `${ROOT_URL}/${route.toString()}?${new URLSearchParams(Object.entries(query)).toString()}`
113
+ const response = await fetch(url, {
114
+ method,
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ },
118
+ body: payload && JSON.stringify(payload), // assuming that the payload needs to be stringified
119
+ credentials: 'include', // sends the cookies with the request
120
+ })
121
+
122
+ if (!response.ok) {
123
+ const json = await response.json()
124
+ console.error('There was a problem calling the API', response, json)
125
+ throw new ApiError(json.message, response.status)
126
+ }
127
+
128
+ return response.json()
129
+ }
@@ -19,4 +19,25 @@
19
19
  * @fileoverview define constants for Silex client and server
20
20
  */
21
21
 
22
+ export const WEBSITE_DATA_FILE_NAME = 'website.json'
23
+
24
+ export const DEFAULT_WEBSITE_ID = 'default'
25
+ export const DEFAULT_BACKEND_ID = ''
26
+ export const DEFAULT_LANGUAGE = 'en'
22
27
  export const CLIENT_CONFIG_FILE_NAME = 'silex.js'
28
+
29
+ export const API_BACKEND_LOGIN_STATUS = 'api/backend/login/status/'
30
+ export const API_BACKEND_LIST = 'api/backend/'
31
+ export const API_BACKEND_LOGOUT = 'api/backend/logout/'
32
+ export const API_BACKEND_LOGIN = 'api/backend/login/'
33
+ export const API_BACKEND_LOGIN_CALLBACK = 'api/backend/login/'
34
+
35
+ export const API_PUBLICATION_PUBLISH = 'api/publication/'
36
+ export const API_PUBLICATION_STATUS = '/api/publication/status/'
37
+
38
+ export const API_WEBSITE_READ = 'api/website/'
39
+ export const API_WEBSITE_WRITE = 'api/website/'
40
+ export const API_WEBSITE_DELETE = 'api/website/'
41
+ export const API_WEBSITE_LIST = 'api/website/'
42
+ export const API_WEBSITE_ASSETS_READ = 'assets/:path'
43
+ export const API_WEBSITE_ASSETS_WRITE = 'assets/'
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import express from 'express'
19
+ import { Directus } from '@directus/sdk'
20
+
21
+ export default async function(config, opts = {}) {
22
+ // config.on('silex:startup:start', ({app}) => {
23
+ // const router = express.Router()
24
+ // router.post(['/publish', '/website', '/assets'], async function(req, res, next) {
25
+ // try {
26
+ // const token = req.body.token || opts.directusToken
27
+ // const directus = new Directus(opts.directusUrl)
28
+ // directus.storage.auth_token = token
29
+ // const me = await directus.users.me.read()
30
+ // next()
31
+ // } catch(err) {
32
+ // console.error('Publish failed in auth module. Did you provide a directus token?', err.message)
33
+ // res.status(403).json({ message: err.message })
34
+ // }
35
+ // })
36
+ // app.use(router)
37
+ // })
38
+ }
@@ -0,0 +1,28 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ export function noCache (req, res, next) {
19
+ res.header('Cache-Control', 'private,no-cache,no-store,must-revalidate,proxy-revalidate')
20
+ res.header('Expires', '-1')
21
+ res.header('Pragma', 'no-cache')
22
+ next()
23
+ }
24
+
25
+ export function withCache(req, res, next) {
26
+ res.header('Cache-Control', 'public,max-age=86400,immutable') // 24h
27
+ next()
28
+ }