@silexlabs/silex 3.0.1-0 → 3.0.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 (250) hide show
  1. package/.env.default +3 -2
  2. package/.silex.js +39 -1
  3. package/README.md +37 -35
  4. package/dist/client/client/api.d.ts +8 -1
  5. package/dist/client/client/assetUrl.d.ts +54 -0
  6. package/dist/client/client/config.d.ts +60 -1
  7. package/dist/client/client/events.d.ts +19 -10
  8. package/dist/client/client/grapesjs/PublicationManager.d.ts +6 -8
  9. package/dist/client/client/grapesjs/PublicationUi.d.ts +8 -10
  10. package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  11. package/dist/client/client/grapesjs/css-props.d.ts +6 -0
  12. package/dist/client/client/grapesjs/footer.d.ts +2 -0
  13. package/dist/client/client/grapesjs/img.d.ts +6 -0
  14. package/dist/client/client/grapesjs/index.d.ts +6 -2
  15. package/dist/client/client/grapesjs/internal-links.d.ts +1 -1
  16. package/dist/client/client/grapesjs/keymaps.d.ts +123 -0
  17. package/dist/client/client/grapesjs/new-page-dialog.d.ts +1 -1
  18. package/dist/client/client/grapesjs/page-panel.d.ts +7 -1
  19. package/dist/client/client/grapesjs/project-bar.d.ts +20 -0
  20. package/dist/client/client/grapesjs/resize-panel.d.ts +1 -0
  21. package/dist/client/client/grapesjs/rich-text.d.ts +4 -1
  22. package/dist/client/client/grapesjs/semantic.d.ts +1 -1
  23. package/dist/client/client/grapesjs/settings-sections.d.ts +16 -0
  24. package/dist/client/client/grapesjs/settings.d.ts +3 -1
  25. package/dist/client/client/grapesjs/storage.d.ts +1 -0
  26. package/dist/client/client/grapesjs/symbolDialogs.d.ts +1 -1
  27. package/dist/client/client/grapesjs/view-buttons.d.ts +1 -0
  28. package/dist/client/client/index.d.ts +5 -0
  29. package/dist/client/client/publication-transformers.d.ts +42 -0
  30. package/dist/client/client/publish-custom-code-block.d.ts +4 -0
  31. package/dist/client/client/publish-fonts.d.ts +4 -0
  32. package/dist/client/client/utils.d.ts +22 -1
  33. package/dist/client/constants.d.ts +3 -0
  34. package/dist/client/css/admin.css +1107 -411
  35. package/dist/client/css/admin.css.map +1 -0
  36. package/dist/client/grapesjs-filter-styles/dist/index.d.ts +4 -0
  37. package/dist/client/grapesjs-fonts/dist/index.d.ts +4 -0
  38. package/dist/client/grapesjs-fonts/src/commands.d.ts +3 -0
  39. package/dist/client/grapesjs-fonts/src/fonts.d.ts +4 -0
  40. package/dist/client/grapesjs-storage-rate-limit/dist/index.d.ts +4 -0
  41. package/dist/client/grapesjs-ui-suggest-classes/dist/index.d.ts +4 -0
  42. package/dist/client/index.html +3 -2
  43. package/dist/client/js/main.js +305 -170
  44. package/dist/client/js/main.js.LICENSE.txt +21 -1
  45. package/dist/client/page.d.ts +1 -1
  46. package/dist/client/silex-lib/src/ts/client/api.d.ts +90 -0
  47. package/dist/client/silex-lib/src/ts/client/assetUrl.d.ts +54 -0
  48. package/dist/client/silex-lib/src/ts/client/config.d.ts +102 -0
  49. package/dist/client/silex-lib/src/ts/client/events.d.ts +23 -0
  50. package/dist/client/silex-lib/src/ts/client/expose.d.ts +8 -0
  51. package/dist/client/silex-lib/src/ts/client/grapesjs/LoginDialog.d.ts +15 -0
  52. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationManager.d.ts +73 -0
  53. package/dist/client/silex-lib/src/ts/client/grapesjs/PublicationUi.d.ts +59 -0
  54. package/dist/client/silex-lib/src/ts/client/grapesjs/blocks.d.ts +1 -0
  55. package/dist/client/silex-lib/src/ts/client/grapesjs/breadcrumbs.d.ts +1 -0
  56. package/dist/client/silex-lib/src/ts/client/grapesjs/css-props.d.ts +6 -0
  57. package/dist/client/silex-lib/src/ts/client/grapesjs/footer.d.ts +2 -0
  58. package/dist/client/silex-lib/src/ts/client/grapesjs/img.d.ts +6 -0
  59. package/dist/client/silex-lib/src/ts/client/grapesjs/index.d.ts +9 -0
  60. package/dist/client/silex-lib/src/ts/client/grapesjs/internal-links.d.ts +1 -0
  61. package/dist/client/silex-lib/src/ts/client/grapesjs/keymaps.d.ts +123 -0
  62. package/dist/client/silex-lib/src/ts/client/grapesjs/new-page-dialog.d.ts +2 -0
  63. package/dist/client/silex-lib/src/ts/client/grapesjs/openImport.d.ts +9 -0
  64. package/dist/client/silex-lib/src/ts/client/grapesjs/page-panel.d.ts +9 -0
  65. package/dist/client/silex-lib/src/ts/client/grapesjs/project-bar.d.ts +21 -0
  66. package/dist/client/silex-lib/src/ts/client/grapesjs/resize-panel.d.ts +1 -0
  67. package/dist/client/silex-lib/src/ts/client/grapesjs/rich-text.d.ts +4 -0
  68. package/dist/client/silex-lib/src/ts/client/grapesjs/semantic.d.ts +1 -0
  69. package/dist/client/silex-lib/src/ts/client/grapesjs/settings-sections.d.ts +16 -0
  70. package/dist/client/silex-lib/src/ts/client/grapesjs/settings.d.ts +4 -0
  71. package/dist/client/silex-lib/src/ts/client/grapesjs/storage.d.ts +2 -0
  72. package/dist/client/silex-lib/src/ts/client/grapesjs/symbolDialogs.d.ts +3 -0
  73. package/dist/client/silex-lib/src/ts/client/grapesjs/view-buttons.d.ts +1 -0
  74. package/dist/client/silex-lib/src/ts/client/index.d.ts +10 -0
  75. package/dist/client/silex-lib/src/ts/client/publication-transformers.d.ts +42 -0
  76. package/dist/client/silex-lib/src/ts/client/publish-custom-code-block.d.ts +4 -0
  77. package/dist/client/silex-lib/src/ts/client/publish-fonts.d.ts +4 -0
  78. package/dist/client/silex-lib/src/ts/client/utils.d.ts +22 -0
  79. package/dist/client/silex-lib/src/ts/constants.d.ts +32 -0
  80. package/dist/client/silex-lib/src/ts/page.d.ts +2 -0
  81. package/dist/client/silex-lib/src/ts/types.d.ts +339 -0
  82. package/dist/client/types.d.ts +59 -35
  83. package/dist/plugins/client/client/events.d.ts +19 -10
  84. package/dist/plugins/client/client/events.js +11 -0
  85. package/dist/plugins/client/client/events.js.map +1 -1
  86. package/dist/plugins/client/plugins/client/template.js +119 -71
  87. package/dist/plugins/client/plugins/client/template.js.map +1 -1
  88. package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
  89. package/dist/plugins/client/plugins/client/website-info.js +72 -0
  90. package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
  91. package/dist/plugins/client/types.d.ts +59 -35
  92. package/dist/plugins/client/types.js +23 -0
  93. package/dist/plugins/client/types.js.map +1 -1
  94. package/dist/plugins/server/constants.d.ts +3 -0
  95. package/dist/plugins/server/constants.js +28 -1
  96. package/dist/plugins/server/constants.js.map +1 -1
  97. package/dist/plugins/server/plugins/server/Cache.js +9 -5
  98. package/dist/plugins/server/plugins/server/Cache.js.map +1 -1
  99. package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
  100. package/dist/plugins/server/plugins/server/DownloadConnector.js +127 -0
  101. package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
  102. package/dist/plugins/server/plugins/server/FtpConnector.d.ts +5 -2
  103. package/dist/plugins/server/plugins/server/FtpConnector.js +38 -23
  104. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  105. package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +63 -9
  106. package/dist/plugins/server/plugins/server/GitlabConnector.js +367 -91
  107. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  108. package/dist/plugins/server/plugins/server/GitlabHostingConnector.d.ts +16 -0
  109. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js +209 -0
  110. package/dist/plugins/server/plugins/server/GitlabHostingConnector.js.map +1 -0
  111. package/dist/plugins/server/plugins/server/SslPlugin.js +6 -6
  112. package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -1
  113. package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +8 -1
  114. package/dist/plugins/server/plugins/server/StaticPlugin.js +5 -31
  115. package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -1
  116. package/dist/plugins/server/server/config.js +8 -9
  117. package/dist/plugins/server/server/config.js.map +1 -1
  118. package/dist/plugins/server/server/connectors/FsHosting.d.ts +3 -2
  119. package/dist/plugins/server/server/connectors/FsHosting.js +18 -7
  120. package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -1
  121. package/dist/plugins/server/server/connectors/FsStorage.d.ts +3 -1
  122. package/dist/plugins/server/server/connectors/FsStorage.js +35 -28
  123. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  124. package/dist/plugins/server/server/connectors/connectors.d.ts +7 -7
  125. package/dist/plugins/server/server/connectors/connectors.js +6 -7
  126. package/dist/plugins/server/server/connectors/connectors.js.map +1 -1
  127. package/dist/plugins/server/server/events.js +16 -0
  128. package/dist/plugins/server/server/events.js.map +1 -1
  129. package/dist/plugins/server/server/jobs.d.ts +6 -0
  130. package/dist/plugins/server/server/jobs.js +12 -8
  131. package/dist/plugins/server/server/jobs.js.map +1 -1
  132. package/dist/plugins/server/server/utils/validation.js +1 -2
  133. package/dist/plugins/server/server/utils/validation.js.map +1 -1
  134. package/dist/plugins/server/types.d.ts +59 -35
  135. package/dist/plugins/server/types.js +24 -1
  136. package/dist/plugins/server/types.js.map +1 -1
  137. package/dist/server/constants.js +27 -1
  138. package/dist/server/plugins/server/Cache.js +9 -5
  139. package/dist/server/server/api/api.js +1 -1
  140. package/dist/server/server/api/connectorApi.js +154 -25
  141. package/dist/server/server/api/publicationApi.js +17 -25
  142. package/dist/server/server/api/websiteApi.js +30 -8
  143. package/dist/server/server/cli.js +9 -6
  144. package/dist/server/server/config.js +8 -9
  145. package/dist/server/server/connectors/FsHosting.js +17 -7
  146. package/dist/server/server/connectors/FsStorage.js +34 -28
  147. package/dist/server/server/connectors/connectors.js +6 -7
  148. package/dist/server/server/express.js +4 -5
  149. package/dist/server/server/index.js +5 -1
  150. package/dist/server/server/jobs.js +11 -8
  151. package/dist/server/server/utils/validation.js +1 -2
  152. package/dist/server/types.js +13 -1
  153. package/package.json +56 -42
  154. package/public/assets/download.png +0 -0
  155. package/public/assets/favicon.ico +0 -0
  156. package/public/assets/favicon.png +0 -0
  157. package/public/assets/ftp.png +0 -0
  158. package/public/assets/gitlab.png +0 -0
  159. package/public/assets/gitlab2.png +0 -0
  160. package/public/assets/laptop.png +0 -0
  161. package/public/assets/logo-silex-small.png +0 -0
  162. package/public/silex.js +5 -23
  163. package/src/css/_variables.scss +23 -20
  164. package/src/css/dialog.scss +27 -1
  165. package/src/css/fonts.scss +31 -0
  166. package/src/css/footer.scss +12 -0
  167. package/src/css/grapesjs-light.scss +180 -0
  168. package/src/css/grapesjs-plugins.scss +227 -0
  169. package/src/css/grapesjs.scss +84 -10
  170. package/src/css/index.scss +12 -1
  171. package/src/css/light.scss +7 -0
  172. package/src/css/project-bar.scss +32 -16
  173. package/src/css/silex.scss +79 -5
  174. package/src/css/website-info.scss +14 -0
  175. package/src/html/index.pug +3 -3
  176. package/src/ts/client/api.ts +38 -14
  177. package/src/ts/client/assetUrl.test.ts +164 -0
  178. package/src/ts/client/assetUrl.ts +254 -0
  179. package/src/ts/client/config.ts +133 -5
  180. package/src/ts/client/events.ts +21 -9
  181. package/src/ts/client/grapesjs/LoginDialog.ts +4 -2
  182. package/src/ts/client/grapesjs/PublicationManager.ts +180 -54
  183. package/src/ts/client/grapesjs/PublicationUi.ts +122 -86
  184. package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
  185. package/src/ts/client/grapesjs/css-props.ts +910 -0
  186. package/src/ts/client/grapesjs/eleventy.ts +11 -8
  187. package/{dist/plugins/client/page.js → src/ts/client/grapesjs/footer.ts} +24 -13
  188. package/src/ts/client/grapesjs/img.ts +147 -0
  189. package/src/ts/client/grapesjs/index.ts +234 -52
  190. package/src/ts/client/grapesjs/internal-links.test.ts +2 -1
  191. package/src/ts/client/grapesjs/internal-links.ts +14 -7
  192. package/src/ts/client/grapesjs/keymaps.ts +207 -0
  193. package/src/ts/client/grapesjs/new-page-dialog.ts +2 -3
  194. package/src/ts/client/grapesjs/page-panel.ts +92 -7
  195. package/src/ts/client/grapesjs/project-bar.ts +116 -72
  196. package/src/ts/client/grapesjs/resize-panel.ts +20 -0
  197. package/src/ts/client/grapesjs/rich-text.ts +21 -13
  198. package/src/ts/client/grapesjs/semantic.ts +3 -3
  199. package/src/ts/client/grapesjs/settings-sections.ts +117 -0
  200. package/src/ts/client/grapesjs/settings.ts +174 -96
  201. package/src/ts/client/grapesjs/storage.ts +98 -33
  202. package/src/ts/client/grapesjs/symbolDialogs.ts +3 -4
  203. package/src/ts/{plugins/server/AuthPlugin.ts → client/grapesjs/view-buttons.ts} +23 -19
  204. package/src/ts/client/index.ts +35 -16
  205. package/src/ts/client/publication-transformers.test.ts +207 -0
  206. package/src/ts/client/publication-transformers.ts +280 -0
  207. package/{dist/plugins/client/client/utils.js → src/ts/client/publish-custom-code-block.ts} +14 -8
  208. package/src/ts/client/publish-fonts.ts +36 -0
  209. package/src/ts/client/utils.ts +53 -1
  210. package/src/ts/constants.ts +31 -0
  211. package/src/ts/page.ts +2 -2
  212. package/src/ts/plugins/client/template.ts +125 -75
  213. package/src/ts/plugins/client/website-info.ts +84 -0
  214. package/src/ts/plugins/server/Cache.ts +6 -2
  215. package/src/ts/plugins/server/DownloadConnector.ts +146 -0
  216. package/src/ts/plugins/server/FtpConnector.ts +47 -21
  217. package/src/ts/plugins/server/GitlabConnector.ts +423 -108
  218. package/src/ts/plugins/server/GitlabHostingConnector.ts +215 -0
  219. package/src/ts/plugins/server/SslPlugin.ts +5 -5
  220. package/src/ts/plugins/server/StaticPlugin.ts +3 -30
  221. package/src/ts/server/api/connectorApi.ts +165 -28
  222. package/src/ts/server/api/publicationApi.ts +17 -24
  223. package/src/ts/server/api/websiteApi.ts +57 -14
  224. package/src/ts/server/cli.ts +7 -5
  225. package/src/ts/server/config.ts +9 -10
  226. package/src/ts/server/connectors/FsHosting.ts +14 -8
  227. package/src/ts/server/connectors/FsStorage.ts +40 -7
  228. package/src/ts/server/connectors/connectors.ts +8 -6
  229. package/src/ts/server/events.ts +17 -0
  230. package/src/ts/server/express.ts +2 -2
  231. package/src/ts/server/index.ts +6 -0
  232. package/src/ts/server/jobs.ts +14 -2
  233. package/src/ts/server/utils/BackwardCompat.ts +530 -529
  234. package/src/ts/types.ts +78 -34
  235. package/dist/plugins/client/client/utils.d.ts +0 -1
  236. package/dist/plugins/client/client/utils.js.map +0 -1
  237. package/dist/plugins/client/page.d.ts +0 -2
  238. package/dist/plugins/client/page.js.map +0 -1
  239. package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +0 -2
  240. package/dist/plugins/client/plugins/client/publicationRenderer.js +0 -128
  241. package/dist/plugins/client/plugins/client/publicationRenderer.js.map +0 -1
  242. package/dist/plugins/server/plugins/server/AuthPlugin.d.ts +0 -1
  243. package/dist/plugins/server/plugins/server/AuthPlugin.js +0 -38
  244. package/dist/plugins/server/plugins/server/AuthPlugin.js.map +0 -1
  245. package/dist/plugins/server/plugins/server/HooksPlugin.d.ts +0 -7
  246. package/dist/plugins/server/plugins/server/HooksPlugin.js +0 -74
  247. package/dist/plugins/server/plugins/server/HooksPlugin.js.map +0 -1
  248. package/src/example-client-config.js +0 -64
  249. package/src/ts/plugins/client/publicationRenderer.ts +0 -128
  250. package/src/ts/plugins/server/HooksPlugin.ts +0 -76
@@ -17,7 +17,6 @@
17
17
 
18
18
  import {html, render} from 'lit-html'
19
19
  import {map} from 'lit-html/directives/map.js'
20
- import grapesjs from 'grapesjs/dist/grapes.min.js'
21
20
 
22
21
  // constants
23
22
  const pluginName = 'semantic'
@@ -48,7 +47,7 @@ const tags = [
48
47
  ]
49
48
 
50
49
  // plugin code
51
- export const semanticPlugin = grapesjs.plugins.add(pluginName, (editor, opts) => {
50
+ export const semanticPlugin = (editor, opts) => {
52
51
  // Add the new trait to all component types
53
52
  editor.DomComponents.getTypes().map(type => {
54
53
  editor.DomComponents.addType(type.id, {
@@ -61,6 +60,7 @@ export const semanticPlugin = grapesjs.plugins.add(pluginName, (editor, opts) =>
61
60
  {
62
61
  label: 'Tag name',
63
62
  type: 'tag-name',
63
+ name: 'tag-name',
64
64
  },
65
65
  ]
66
66
  }
@@ -111,4 +111,4 @@ export const semanticPlugin = grapesjs.plugins.add(pluginName, (editor, opts) =>
111
111
  doRender(elInput, tagName)
112
112
  },
113
113
  })
114
- })
114
+ }
@@ -0,0 +1,117 @@
1
+ import {live} from 'lit-html/directives/live.js'
2
+ import {html, TemplateResult} from 'lit-html'
3
+ import { WebsiteSettings } from '../../types'
4
+
5
+ // ID of the code editor wrapper
6
+ export const idCodeWrapper = 'settings-head-wrapper'
7
+
8
+ // Is the model a site or a page?
9
+ export function isSite(model: any) { return !!model.getHtml }
10
+
11
+ export interface SettingsSection {
12
+ id: string
13
+ label: string
14
+ render: (settings: WebsiteSettings, model: Backbone.Model) => TemplateResult
15
+ }
16
+
17
+ /**
18
+ * This is the settings dialog default sections
19
+ * Each section has an entry in the sidebar
20
+ * Each section render as part of the settings form
21
+ * The form is submitted to save the settings with all FormData
22
+ */
23
+ export const defaultSections: SettingsSection[] = [{
24
+ id: 'general',
25
+ label: 'General',
26
+ render: (settings, model) => html`
27
+ <div id="settings-general" class="silex-hideable">
28
+ <div class="gjs-sm-sector-title">General</div>
29
+ <div class="silex-form__group col2">
30
+ <label class="silex-form__element">
31
+ <h3>${isSite(model) ? 'Site name' : 'Page name'}</h3>
32
+ <p class="silex-help">${isSite(model) ? 'The project name in the editor, for you to remember what it is about.' : 'Label of the page in the editor, and file name of the published HTML page.'}</p>
33
+ <input type="text" name="name" .value=${live(model.get('name') || '')}/>
34
+ </label>
35
+ <label class="silex-form__element">
36
+ <h3>Website language</h3>
37
+ <p class="silex-help">This is the default language code for this website. Example: en, fr, es...</p>
38
+ <input type="text" name="lang" .value=${live(settings.lang || '')}/>
39
+ </label>
40
+ </div>
41
+ </div>
42
+ `,
43
+ }, {
44
+ id: 'seo',
45
+ label: 'SEO',
46
+ render: (settings, model) => html`
47
+ <div id="settings-seo" class="silex-hideable silex-hidden">
48
+ <div class="gjs-sm-sector-title">SEO</div>
49
+ <div class="silex-form__group col2">
50
+ <label class="silex-form__element">
51
+ <h3>Title</h3>
52
+ <p class="silex-help">Title of the browser window, and title in the search engines results. It is used by search engines to find out what your site is talking about. The title should be a maximum of 70 characters long, including spaces.</p>
53
+ <input type="text" name="title" .value=${live(settings.title || '')}/>
54
+ </label>
55
+ <label class="silex-form__element">
56
+ <h3>Description</h3>
57
+ <p class="silex-help">Description displayed by the search engines in search results. It is used by search engines to find out what your site is talking about. It is best to keep meta descriptions between 150 and 160 characters.</p>
58
+ <input type="text" name="description" .value=${live(settings.description || '')}/>
59
+ </label>
60
+ <label class="silex-form__element">
61
+ <h3>Favicon</h3>
62
+ <p class="silex-help">Small image displayed in the browser's address bar and in tabs. The recommended size is 16×16 or 32×32 pixels. This can be a URL or a relative path.</p>
63
+ <input type="text" name="favicon" .value=${live(settings.favicon || '')}/>
64
+ </label>
65
+ </div>
66
+ </div>
67
+ `,
68
+ }, {
69
+ id: 'social',
70
+ label: 'Social',
71
+ render: (settings, model) => html`
72
+ <div id="settings-social" class="silex-hideable silex-hidden">
73
+ <div class="gjs-sm-sector-title">Social</div>
74
+ <div class="silex-help">
75
+ <p>Once your website is live, you can use these tools to test sharing:&nbsp;<a target="_blank" href="https://developers.facebook.com/tools/debug/">Facebook</a>,
76
+ <a target="_blank" href="https://cards-dev.twitter.com/validator">Twitter</a>,
77
+ <a target="_blank" href="https://www.linkedin.com/post-inspector/inspect/">Linkedin</a></p>
78
+ </div>
79
+ <div class="silex-form__group col2">
80
+ <label class="silex-form__element">
81
+ <h3>Title</h3>
82
+ <p class="silex-help">The title of your website displayed when a user shares your website on a social network.
83
+ Do not include any branding in this title, just eye-catching phrase, e.g. "Learn everything about fishing".
84
+ Title should be between 60 and 90 characters long.</p>
85
+ <input type="text" name="og:title" .value=${live(settings['og:title'] || '')}/>
86
+ </label>
87
+ <label class="silex-form__element">
88
+ <h3>Description</h3>
89
+ <p class="silex-help">Description displayed when a user shares your website on a social network. Make it catchy, and invite readers to visit your website too, e.g. "Sam's website about fishing, check it out!" Title should be between 60 and 90 characters long.</p>
90
+ <input type="text" name="og:description" .value=${live(settings['og:description'] || '')}/>
91
+ </label>
92
+ <label class="silex-form__element">
93
+ <h3>Image</h3>
94
+ <div class="silex-help">
95
+ <p>Thumbnail image which is displayed when your website is shared on a social network. The optimal size is 1200×627 pixels. At this size, your thumbnail will be big and stand out from the crowd. But do not exceed the 5MB size limit. If you use an image that is smaller than 400 pixels x 209 pixels, it will render as a much smaller thumbnail.</p>
96
+ <p>Please enter the full URL here, e.g. "http://mysite.com/path/to/image.jpg"</p>
97
+ </div>
98
+ <input type="text" name="og:image" .value=${live(settings['og:image'] || '')}/>
99
+ </label>
100
+ </div>
101
+ </div>
102
+ `,
103
+ }, {
104
+ id: 'code',
105
+ label: 'Code',
106
+ render: (settings, model) => html`
107
+ <div id="settings-code" class="silex-hideable silex-hidden">
108
+ <div class="gjs-sm-sector-title">Code</div>
109
+ <div class="silex-form__group">
110
+ <label class="silex-form__element" id="${idCodeWrapper}">
111
+ <h3>HTML head</h3>
112
+ <p class="silex-help">HTML code which will be inserted in the HEAD tag.</p>
113
+ </label>
114
+ </div>
115
+ </div>
116
+ `,
117
+ }]
@@ -15,30 +15,27 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import {html, render} from 'lit-html'
19
- import {live} from 'lit-html/directives/live.js'
20
- import * as grapesjs from 'grapesjs/dist/grapes.min.js'
18
+ import {html, render, TemplateResult} from 'lit-html'
19
+ import {defaultSections, idCodeWrapper, isSite, SettingsSection} from './settings-sections'
20
+
21
+ /**
22
+ * @fileoverview This file contains the settings dialog. The config API lets plugins add sections to the settings dialog.
23
+ */
21
24
 
22
25
  import { WebsiteSettings } from '../../types'
26
+ import { ClientEvent } from '../events'
27
+ import { SILEX_VERSION } from '../../constants'
28
+
29
+ const sectionsSite = [...defaultSections]
30
+ const sectionsPage = [...defaultSections]
23
31
 
24
- const pluginName = 'settings-dialog'
25
32
  const el = document.createElement('div')
26
33
  let modal
27
34
 
28
- // Get version from webpack (see plugin in webpack.config.js)
29
- declare const VERSION: string
30
-
31
35
  export const cmdOpenSettings = 'open-settings'
32
36
 
33
- let version = 'v3'
34
- export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) => {
35
- // Display Silex version from package.json
36
- try {
37
- version = VERSION
38
- } catch (error) {
39
- // Cannot get version from webpack (see plugin in webpack.config.js)
40
- }
41
- console.log('Silex version:', version)
37
+ let headEditor = null
38
+ export const settingsDialog = (editor, opts) => {
42
39
  editor.Commands.add(cmdOpenSettings, {
43
40
  run: (_, sender, {page}) => {
44
41
  modal = editor.Modal.open({
@@ -47,6 +44,7 @@ export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) =>
47
44
  attributes: { class: 'settings-dialog' },
48
45
  })
49
46
  .onceClose(() => {
47
+ sender?.set && sender.set('active', 0) // Deactivate the button to make it ready to be clicked again, only in case of a grapesjs button (not in the pages panel)
50
48
  editor.stopCommand(cmdOpenSettings) // apparently this is needed to be able to run the command several times
51
49
  })
52
50
  displaySettings(editor, opts, page)
@@ -54,14 +52,20 @@ export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) =>
54
52
  const form = el.querySelector('form')
55
53
  form.onsubmit = event => {
56
54
  event.preventDefault()
55
+ editor.trigger(ClientEvent.SETTINGS_SAVE_START, page)
57
56
  saveSettings(editor, opts, page)
57
+ editor.trigger(ClientEvent.SETTINGS_SAVE_END, page)
58
58
  editor.stopCommand(cmdOpenSettings)
59
59
  }
60
60
  form.querySelector('input')?.focus()
61
+ // Notify other plugins
62
+ editor.trigger(ClientEvent.SETTINGS_OPEN, page)
63
+ // Return the dialog
61
64
  return modal
62
65
  },
63
66
  stop: () => {
64
67
  modal.close()
68
+ editor.trigger(ClientEvent.SETTINGS_CLOSE)
65
69
  },
66
70
  })
67
71
  // add settings to the website
@@ -73,93 +77,156 @@ export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) =>
73
77
  const model = editor.getModel()
74
78
  model.set('settings', data.settings || {})
75
79
  model.set('name', data.name)
80
+ editor.once('canvas:frame:load', () => {
81
+ updateDom(editor)
82
+ })
83
+ })
84
+ // Normal way to apply the head content to the DOM
85
+ // fix #1559 Custom <head> code must be reapplied
86
+ editor.on('canvas:frame:load', () => {
76
87
  updateDom(editor)
77
88
  })
89
+ // When the page changes, update the dom
78
90
  editor.on('page', (e) => {
79
- editor.Canvas.getFrameEl().addEventListener('load', () => {
80
- updateDom(editor)
81
- })
91
+ updateDom(editor)
92
+ })
93
+ headEditor = editor.CodeManager.createViewer({
94
+ readOnly: false,
95
+ codeName: 'htmlmixed',
96
+ lineNumbers: true,
97
+ lineWrapping: true,
98
+ autoFormat: false,
82
99
  })
83
- })
100
+ }
101
+
102
+ function showSection(item: SettingsSection) {
103
+ // **
104
+ // Handle the side bar
105
+ const aside = el.querySelector('aside.silex-bar') as HTMLElement
106
+ const ul = aside.querySelector('ul.silex-list--menu') as HTMLUListElement
107
+ const li = ul.querySelector('li#settings-sidebar-' + item.id) as HTMLLIElement
108
+ currentSection = item
109
+ if(!li) {
110
+ console.warn('Cannot find section', item.id, 'in the side bar, fallback to the first section')
111
+ if(!defaultSections[0] || defaultSections[0].id === item.id) {
112
+ console.error('Cannot find the default section in the side bar')
113
+ return
114
+ }
115
+ showSection(defaultSections[0]) // Fallback to the first section
116
+ return
117
+ }
118
+ // Update active
119
+ Array.from(ul.querySelectorAll('.active')).forEach(el => el.classList.remove('active'))
120
+ li.classList.add('active')
121
+ // **
122
+ // Handle the main section
123
+ const main = el.querySelector('main#settings__main') as HTMLElement
124
+ const mainItem = main.querySelector(`#settings-${item.id}`)
125
+ if(!mainItem) {
126
+ console.warn('Cannot find section', item.id, 'in the settings dialog, fallback to the first section')
127
+ if(!defaultSections[0] || defaultSections[0].id === item.id) {
128
+ console.error('Cannot find the default section in the settings dialog')
129
+ return
130
+ }
131
+ showSection(defaultSections[0]) // Fallback to the first section
132
+ return
133
+ }
134
+ // Update hidden
135
+ Array.from(main.querySelectorAll('.silex-hideable')).forEach(el => el.classList.add('silex-hidden'))
136
+ mainItem.classList.remove('silex-hidden')
137
+ // This messes up with the save / cancel mechanism
138
+ // displaySettings(editor, config, model)
139
+ // Refresh the code editor just in case it went from hidden to visible
140
+ // This makes it ready to be used when the user clicks on the tab
141
+ headEditor.refresh()
142
+ }
143
+
144
+ export function addSection(section, siteOrPage: 'site' | 'page', position: 'first' | 'last' | number) {
145
+ const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
146
+ if (position === 'first') {
147
+ sections.unshift(section)
148
+ } else if (position === 'last') {
149
+ sections.push(section)
150
+ } else if (typeof position === 'number') {
151
+ sections.splice(position, 0, section)
152
+ } else {
153
+ throw new Error('Invalid position for settings section')
154
+ }
155
+ }
156
+
157
+ export function removeSection(id, siteOrPage: 'site' | 'page') {
158
+ const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
159
+ const index = sections.findIndex(section => section.id === id)
160
+ if(index === -1) throw new Error(`Cannot find section with id ${id}`)
161
+ sections.splice(index, 1)
162
+ }
84
163
 
85
- // Is the model a site or a page?
86
- function isSite(model) { return !!model.getHtml }
87
164
 
165
+ let currentSection
88
166
  function displaySettings(editor, config, model = editor.getModel()) {
167
+ // Update the model with the current settings
89
168
  const settings = model.get('settings') || {} as WebsiteSettings
90
169
  model.set('settings', settings)
170
+ // Get the current sections for page or site
171
+ const menuItemsCurrent = isSite(model) ? sectionsSite : sectionsPage
172
+ // Init the current section selection
173
+ currentSection = currentSection || menuItemsCurrent[0]
174
+ const sections: TemplateResult[] = menuItemsCurrent.map(item => {
175
+ try {
176
+ return item.render(settings, model)
177
+ } catch (e) {
178
+ console.error('Error rendering settings section', item.id, e)
179
+ return html`<div class="silex-error">Error rendering settings section ${item.id}</div>`
180
+ }
181
+ })
182
+ // Render the settings dialog
91
183
  render(html`
92
- <p>The page settings will override site settings, <a href="https://github.com/silexlabs/Silex/wiki/Settings" target="_blank">more info about settings here</a>.</p>
93
184
  <form class="silex-form">
94
- <h2>General settings</h2>
95
- <div class="silex-form__group col2">
96
- <label class="silex-form__element">
97
- <h3>${isSite(model) ? 'Site name' : 'Page name'}</h3>
98
- <p>${isSite(model) ? 'The project name in the editor, for you and your team.' : 'Label of the page in the editor, and file name of the HTML page.'}</p>
99
- <input type="text" name="name" .value=${live(model.get('name') || '')}/>
100
- </label>
101
- <label class="silex-form__element">
102
- <h3>Website language</h3>
103
- <p>This is the default language code for this website. Example: en, fr, es...</p>
104
- <input type="text" name="lang" .value=${live(settings.lang || '')}/>
105
- </label>
106
- <label class="silex-form__element">
107
- <h3>Title</h3>
108
- <p>Title of the browser window, and title in the search engines results. It is used by search engines to find out what your site is talking about. The title should be a maximum of 70 characters long, including spaces.</p>
109
- <input type="text" name="title" .value=${live(settings.title || '')}/>
110
- </label>
111
- <label class="silex-form__element">
112
- <h3>Description</h3>
113
- <p>Description displayed by the search engines in search results. It is used by search engines to find out what your site is talking about. It is best to keep meta descriptions between 150 and 160 characters.</p>
114
- <input type="text" name="description" .value=${live(settings.description || '')}/>
115
- </label>
116
- <label class="silex-form__element">
117
- <h3>Favicon</h3>
118
- <p>Small image displayed in the browser's address bar and in tabs. The recommended size is 16×16 or 32×32 pixels. This can be a URL or a relative path.</p>
119
- <input type="text" name="favicon" .value=${live(settings.favicon || '')}/>
120
- </label>
121
- </div>
122
- <h2>Social networks sharing</h2>
123
- <div>
124
- <p>Once your website is live, you can use these tools to test sharing:&nbsp;<a target="_blank" href="https://developers.facebook.com/tools/debug/">Facebook</a>,
125
- <a target="_blank" href="https://cards-dev.twitter.com/validator">Twitter</a>,
126
- <a target="_blank" href="https://www.linkedin.com/post-inspector/inspect/">Linkedin</a></p>
127
- </div>
128
- <div class="silex-form__group col2">
129
- <label class="silex-form__element">
130
- <h3>Title</h3>
131
- <p>The title of your website displayed when a user shares your website on a social network.
132
- Do not include any branding in this title, just eye-catching phrase, e.g. "Learn everything about fishing".
133
- Title should be between 60 and 90 characters long.</p>
134
- <input type="text" name="og:title" .value=${live(settings['og:title'] || '')}/>
135
- </label>
136
- <label class="silex-form__element">
137
- <h3>Description</h3>
138
- <p>Description displayed when a user shares your website on a social network. Make it catchy, and invite readers to visit your website too, e.g. "Sam's website about fishing, check it out!" Title should be between 60 and 90 characters long.</p>
139
- <input type="text" name="og:description" .value=${live(settings['og:description'] || '')}/>
140
- </label>
141
- <label class="silex-form__element">
142
- <h3>Image</h3>
143
- <p>Thumbnail image which is displayed when your website is shared on a social network. The optimal size is 1200×627 pixels. At this size, your thumbnail will be big and stand out from the crowd. But do not exceed the 5MB size limit. If you use an image that is smaller than 400 pixels x 209 pixels, it will render as a much smaller thumbnail.</p>
144
- <p>Please enter the full URL here, e.g. "http://mysite.com/path/to/image.jpg"</p>
145
- <input type="text" name="og:image" .value=${live(settings['og:image'] || '')}/>
146
- </label>
147
- </div>
148
- <h2>Code</h2>
149
- <div class="silex-form__group">
150
- <label class="silex-form__element">
151
- <h3>HTML head</h3>
152
- <p>HTML code which will be inserted in the HEAD tag.</p>
153
- <textarea name="head" .value=${live(settings.head || '')}></textarea>
154
- </label>
185
+ <div class="silex-help">
186
+ ${isSite(model) ? html`
187
+ <p>Here you can set the name of your website, SEO and social networks sharing data.</p>
188
+ <p>These settings are overriden by the page settings, <a href="https://github.com/silexlabs/Silex/wiki/Settings" target="_blank">more info about settings here</a>.</p>
189
+ ` : html`
190
+ <p>Here you can set the name of your page, SEO and social networks sharing data.</p>
191
+ <p>These settings override the site settings, <a href="https://github.com/silexlabs/Silex/wiki/Settings" target="_blank">more info about settings here</a>.</p>
192
+ `}
155
193
  </div>
194
+ <section class="silex-sidebar-dialog">
195
+ <aside class="silex-bar">
196
+ <ul class="silex-list silex-list--menu">
197
+ ${menuItemsCurrent.map(item => html`
198
+ <li
199
+ id="settings-sidebar-${item.id}"
200
+ class=${item.id === currentSection.id ? 'active' : ''}
201
+ @click=${e => {
202
+ e.preventDefault()
203
+ // Show the new section
204
+ showSection(item)
205
+ // Notify other plugins
206
+ editor.trigger(ClientEvent.SETTINGS_SECTION_CHANGE, item.id)
207
+ }}
208
+ >
209
+ ${item.label}
210
+ </li>
211
+ `)}
212
+ </ul>
213
+ </aside>
214
+ <main id="settings__main">
215
+ ${ sections }
216
+ </main>
217
+ </section>
156
218
  <footer>
157
- <p class="silex-version">Silex ${version}</p>
219
+ <p class="silex-version">Silex ${SILEX_VERSION}</p>
158
220
  <input class="silex-button" type="button" @click=${e => editor.stopCommand(cmdOpenSettings)} value="Cancel">
159
- <input class="silex-button" type="submit" value="Ok">
221
+ <input class="silex-button" type="submit" value="Apply">
160
222
  </footer>
161
223
  </form>
162
224
  `, el)
225
+ // Display the current section
226
+ showSection(currentSection)
227
+ // Init the code editor
228
+ el.querySelector(`#${idCodeWrapper}`)?.appendChild(headEditor.getElement())
229
+ headEditor.setContent(settings.head || '')
163
230
  }
164
231
 
165
232
  function saveSettings(editor, config, model = editor.getModel()) {
@@ -167,13 +234,19 @@ function saveSettings(editor, config, model = editor.getModel()) {
167
234
  const formData = new FormData(form)
168
235
  const data = Array.from(formData as any)
169
236
  .reduce((aggregate, [key, value]) => {
170
- aggregate[key] = value
237
+ // Keep only the values that are set
238
+ if(value !== '') {
239
+ aggregate[key] = value
240
+ }
171
241
  return aggregate
172
242
  }, {}) as {[key: string]: any}
173
- // take the name out to the main model (by design in grapesjs pages)
243
+ // take the name out to the main model (by design in grapesjs pages)
174
244
  const { name, ...settings } = data
175
245
  model.set({
176
- settings,
246
+ settings: {
247
+ ...data,
248
+ head: headEditor.getContent(),
249
+ },
177
250
  name,
178
251
  })
179
252
  // save if auto save is on
@@ -193,13 +266,18 @@ function getHeadContainer(doc, className) {
193
266
  }
194
267
  function updateDom(editor) {
195
268
  const doc = editor.Canvas.getDocument()
196
- if(doc) {
197
- // Site head
198
- getHeadContainer(doc, 'site-head')
199
- .innerHTML = editor.getModel().get('settings').head || ''
200
- // Pages head
201
- getHeadContainer(doc, 'page-head')
202
- .innerHTML = editor.Pages.getSelected().get('settings')?.head || ''
269
+ const settings = editor.getModel().get('settings')
270
+ const pageSettings = editor.Pages.getSelected().get('settings')
271
+ if(doc && settings) {
272
+ // Delay the update to let the DOM be ready
273
+ setTimeout(() => {
274
+ // Site head
275
+ getHeadContainer(doc, 'site-head')
276
+ .innerHTML = settings.head || ''
277
+ // Pages head
278
+ getHeadContainer(doc, 'page-head')
279
+ .innerHTML = pageSettings?.head || ''
280
+ })
203
281
  } else {
204
282
  // No document??
205
283
  }
@@ -18,57 +18,122 @@
18
18
  import { ConnectorId, WebsiteId, WebsiteData, ConnectorUser, ConnectorType, ApiError } from '../../types'
19
19
  import { websiteLoad, websiteSave } from '../api'
20
20
  import { cmdLogin, eventLoggedIn, eventLoggedOut, getCurrentUser, updateUser } from './LoginDialog'
21
+ import { addTempDataToPages, addTempDataToAssetUrl, addTempDataToStyles, removeTempDataFromAssetUrl, removeTempDataFromPages, removeTempDataFromStyles } from '../assetUrl'
22
+ import { PublicationStatus } from './PublicationManager'
23
+ import { ClientEvent } from '../events'
24
+
25
+ export const cmdPauseAutoSave = 'pause-auto-save'
26
+
27
+ // Mechanism to keep only the last save during publication
28
+ let lastPendingSaving: WebsiteData = null
29
+
30
+ // Mechanism to avoid concurrent saving
31
+ // Note: there is already such a mechanism in grapesjs but it fails when the save is delayed by the publication
32
+ let isSaving = false
21
33
 
22
- async function wait(ms: number) {
23
- return new Promise((resolve) => {
24
- setTimeout(resolve, ms)
25
- })
26
- }
27
34
  export const storagePlugin = (editor) => {
35
+ // Add useful commands
36
+ editor.Commands.add(cmdPauseAutoSave, {
37
+ run: () => {
38
+ if(!editor.StorageManager.isAutosave()) throw new Error('Autosave is not enabled')
39
+ editor.StorageManager.setAutosave(false)
40
+ },
41
+ stop: () => {
42
+ editor.editor.set('changesCount', 0)
43
+ editor.UndoManager.clear()
44
+ editor.StorageManager.setAutosave(true)
45
+ },
46
+ })
47
+ // Add the storage connector
28
48
  editor.Storage.add('connector', {
29
49
  async load(options: { id: WebsiteId, connectorId: ConnectorId }): Promise<WebsiteData> {
30
50
  try {
31
- console.log('storage get user')
32
51
  const user: ConnectorUser = await getCurrentUser(editor) ?? await updateUser(editor, ConnectorType.STORAGE, options.connectorId)
33
- if(user) {
34
- const data = await websiteLoad({websiteId: options.id, connectorId: user.storage.connectorId})
35
- return data
36
- } else {
37
- return new Promise((resolve, reject) => {
38
- editor.once(eventLoggedIn, async () => {
39
- try {
40
- editor.StorageManager.setAutosave(false)
41
- const data = await editor.Storage.load(options)
42
- editor.StorageManager.setAutosave(true)
43
- //editor.loadProjectData(data)
44
- resolve(data)
45
- } catch (err) {
46
- console.error('connectorPlugin load error', err)
47
- reject(err)
48
- }
49
- })
50
- editor.Commands.run(cmdLogin)
52
+ if (!user) throw new ApiError('Not logged in', 401)
53
+ const data = await websiteLoad({ websiteId: options.id, connectorId: user.storage.connectorId }) as WebsiteData
54
+ if (data.assets) data.assets = addTempDataToAssetUrl(data.assets, options.id, user.storage.connectorId)
55
+ if (data.pages) data.pages = addTempDataToPages(data.pages, options.id, user.storage.connectorId)
56
+ if (data.styles) data.styles = addTempDataToStyles(data.styles, options.id, user.storage.connectorId)
57
+ return data
58
+
59
+ } catch (err) {
60
+ editor.UndoManager.clear()
61
+ if (err.httpStatusCode === 401) {
62
+ editor.once(eventLoggedIn, async () => {
63
+ try {
64
+ editor.runCommand(cmdPauseAutoSave)
65
+ const data = await editor.Storage.load(options)
66
+ editor.once('canvas:frame:load', () => editor.stopCommand(cmdPauseAutoSave))
67
+ editor.loadProjectData(data)
68
+ } catch (err) {
69
+ console.error('connectorPlugin load error', err)
70
+ throw err
71
+ }
51
72
  })
73
+ editor.Commands.run(cmdLogin)
52
74
  }
53
- } catch (err) {
54
75
  console.error('connectorPlugin load error', err)
55
76
  throw err
56
77
  }
57
78
  },
58
79
 
59
80
  async store(data: WebsiteData, options: { id: WebsiteId, connectorId: ConnectorId }) {
81
+ // Be sure that it is immutable
82
+ const myData = { ...data }
83
+ return await this.addToQueue(myData, options)
84
+ },
85
+
86
+ async addToQueue(data: WebsiteData, options) {
87
+ // Handle concurrent saving
88
+ if (lastPendingSaving && lastPendingSaving !== data) {
89
+ // Cancel previous saving
90
+ }
91
+ lastPendingSaving = data
92
+ // Go ahaed and save
93
+ return this.doStore(lastPendingSaving, options)
94
+ },
95
+
96
+ async doStore(data, options) {
97
+ if (editor.PublicationManager?.status === PublicationStatus.STATUS_PENDING) {
98
+ // Publication is pending, save is delayed
99
+ return new Promise((resolve) => {
100
+ editor.once(ClientEvent.PUBLISH_END, () => {
101
+ resolve(this.doStore(data, options))
102
+ })
103
+ })
104
+ }
60
105
  try {
61
- if(await getCurrentUser(editor)) {
62
- const user = await getCurrentUser(editor)
63
- await websiteSave({websiteId: options.id, connectorId: user.storage.connectorId, data})
64
- } else {
65
- editor.once(eventLoggedIn, () => {
66
- return editor.Storage.save(options)
106
+ if (isSaving) {
107
+ // Concurrent saving, save is delayed
108
+ editor.once('storage:start:store', () => {
109
+ this.doStore(data, options)
67
110
  })
68
- editor.Commands.run(cmdLogin)
111
+ } else {
112
+ if (lastPendingSaving === data) {
113
+ lastPendingSaving = null
114
+ isSaving = true
115
+ const user = await getCurrentUser(editor)
116
+ if (user) {
117
+ data.assets = removeTempDataFromAssetUrl(data.assets)
118
+ data.pages = removeTempDataFromPages(data.pages)
119
+ data.styles = removeTempDataFromStyles(data.styles)
120
+ await websiteSave({ websiteId: options.id, connectorId: user.storage.connectorId, data })
121
+ isSaving = false
122
+ } else {
123
+ // Not logged in save is delayed
124
+ editor.once(eventLoggedIn, () => {
125
+ isSaving = false
126
+ return editor.Storage.store()
127
+ })
128
+ editor.Commands.run(cmdLogin)
129
+ }
130
+ } else {
131
+ // Canceled saving
132
+ }
69
133
  }
70
134
  } catch (err) {
71
135
  console.error('connectorPlugin store error', err)
136
+ isSaving = false
72
137
  throw err
73
138
  }
74
139
  }
@@ -81,7 +146,7 @@ export const storagePlugin = (editor) => {
81
146
  handleError(err)
82
147
  })
83
148
  editor.on('asset:upload:end', (err: Error) => {
84
- editor.save()
149
+ editor.store()
85
150
  })
86
151
  editor.on('asset:upload:error', (err: Error) => {
87
152
  handleError(err)