@silexlabs/silex 3.0.0-alpha.100

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 (282) hide show
  1. package/.env.default +19 -0
  2. package/.silex.js +37 -0
  3. package/LICENSE +661 -0
  4. package/README.md +96 -0
  5. package/dist/client/client/api.d.ts +85 -0
  6. package/dist/client/client/assetUrl.d.ts +51 -0
  7. package/dist/client/client/config.d.ts +58 -0
  8. package/dist/client/client/events.d.ts +14 -0
  9. package/dist/client/client/expose.d.ts +8 -0
  10. package/dist/client/client/grapesjs/LoginDialog.d.ts +15 -0
  11. package/dist/client/client/grapesjs/PublicationManager.d.ts +72 -0
  12. package/dist/client/client/grapesjs/PublicationUi.d.ts +62 -0
  13. package/dist/client/client/grapesjs/blocks.d.ts +1 -0
  14. package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  15. package/dist/client/client/grapesjs/footer.d.ts +2 -0
  16. package/dist/client/client/grapesjs/index.d.ts +5 -0
  17. package/dist/client/client/grapesjs/internal-links.d.ts +1 -0
  18. package/dist/client/client/grapesjs/new-page-dialog.d.ts +2 -0
  19. package/dist/client/client/grapesjs/openImport.d.ts +9 -0
  20. package/dist/client/client/grapesjs/page-panel.d.ts +3 -0
  21. package/dist/client/client/grapesjs/project-bar.d.ts +1 -0
  22. package/dist/client/client/grapesjs/rich-text.d.ts +1 -0
  23. package/dist/client/client/grapesjs/semantic.d.ts +1 -0
  24. package/dist/client/client/grapesjs/settings.d.ts +2 -0
  25. package/dist/client/client/grapesjs/storage.d.ts +1 -0
  26. package/dist/client/client/grapesjs/symbolDialogs.d.ts +3 -0
  27. package/dist/client/client/index.d.ts +5 -0
  28. package/dist/client/client/publication-transformers.d.ts +41 -0
  29. package/dist/client/client/utils.d.ts +2 -0
  30. package/dist/client/constants.d.ts +29 -0
  31. package/dist/client/css/admin.css +4491 -0
  32. package/dist/client/index.html +9 -0
  33. package/dist/client/js/main.js +321 -0
  34. package/dist/client/js/main.js.LICENSE.txt +24 -0
  35. package/dist/client/page.d.ts +2 -0
  36. package/dist/client/types.d.ts +334 -0
  37. package/dist/plugins/client/client/api.d.ts +85 -0
  38. package/dist/plugins/client/client/api.js +114 -0
  39. package/dist/plugins/client/client/api.js.map +1 -0
  40. package/dist/plugins/client/client/assetUrl.d.ts +51 -0
  41. package/dist/plugins/client/client/assetUrl.js +224 -0
  42. package/dist/plugins/client/client/assetUrl.js.map +1 -0
  43. package/dist/plugins/client/client/config.d.ts +58 -0
  44. package/dist/plugins/client/client/config.js +90 -0
  45. package/dist/plugins/client/client/config.js.map +1 -0
  46. package/dist/plugins/client/client/events.d.ts +14 -0
  47. package/dist/plugins/client/client/events.js +34 -0
  48. package/dist/plugins/client/client/events.js.map +1 -0
  49. package/dist/plugins/client/client/grapesjs/LoginDialog.d.ts +15 -0
  50. package/dist/plugins/client/client/grapesjs/LoginDialog.js +195 -0
  51. package/dist/plugins/client/client/grapesjs/LoginDialog.js.map +1 -0
  52. package/dist/plugins/client/client/grapesjs/PublicationManager.d.ts +72 -0
  53. package/dist/plugins/client/client/grapesjs/PublicationManager.js +356 -0
  54. package/dist/plugins/client/client/grapesjs/PublicationManager.js.map +1 -0
  55. package/dist/plugins/client/client/grapesjs/PublicationUi.d.ts +62 -0
  56. package/dist/plugins/client/client/grapesjs/PublicationUi.js +336 -0
  57. package/dist/plugins/client/client/grapesjs/PublicationUi.js.map +1 -0
  58. package/dist/plugins/client/client/grapesjs/blocks.d.ts +1 -0
  59. package/dist/plugins/client/client/grapesjs/blocks.js +37 -0
  60. package/dist/plugins/client/client/grapesjs/blocks.js.map +1 -0
  61. package/dist/plugins/client/client/grapesjs/breadcrumbs.d.ts +1 -0
  62. package/dist/plugins/client/client/grapesjs/breadcrumbs.js +75 -0
  63. package/dist/plugins/client/client/grapesjs/breadcrumbs.js.map +1 -0
  64. package/dist/plugins/client/client/grapesjs/footer.d.ts +2 -0
  65. package/dist/plugins/client/client/grapesjs/footer.js +28 -0
  66. package/dist/plugins/client/client/grapesjs/footer.js.map +1 -0
  67. package/dist/plugins/client/client/grapesjs/index.d.ts +5 -0
  68. package/dist/plugins/client/client/grapesjs/index.js +313 -0
  69. package/dist/plugins/client/client/grapesjs/index.js.map +1 -0
  70. package/dist/plugins/client/client/grapesjs/internal-links.d.ts +1 -0
  71. package/dist/plugins/client/client/grapesjs/internal-links.js +219 -0
  72. package/dist/plugins/client/client/grapesjs/internal-links.js.map +1 -0
  73. package/dist/plugins/client/client/grapesjs/new-page-dialog.d.ts +2 -0
  74. package/dist/plugins/client/client/grapesjs/new-page-dialog.js +83 -0
  75. package/dist/plugins/client/client/grapesjs/new-page-dialog.js.map +1 -0
  76. package/dist/plugins/client/client/grapesjs/openImport.d.ts +9 -0
  77. package/dist/plugins/client/client/grapesjs/openImport.js +74 -0
  78. package/dist/plugins/client/client/grapesjs/openImport.js.map +1 -0
  79. package/dist/plugins/client/client/grapesjs/page-panel.d.ts +3 -0
  80. package/dist/plugins/client/client/grapesjs/page-panel.js +113 -0
  81. package/dist/plugins/client/client/grapesjs/page-panel.js.map +1 -0
  82. package/dist/plugins/client/client/grapesjs/project-bar.d.ts +1 -0
  83. package/dist/plugins/client/client/grapesjs/project-bar.js +104 -0
  84. package/dist/plugins/client/client/grapesjs/project-bar.js.map +1 -0
  85. package/dist/plugins/client/client/grapesjs/rich-text.d.ts +1 -0
  86. package/dist/plugins/client/client/grapesjs/rich-text.js +31 -0
  87. package/dist/plugins/client/client/grapesjs/rich-text.js.map +1 -0
  88. package/dist/plugins/client/client/grapesjs/semantic.d.ts +1 -0
  89. package/dist/plugins/client/client/grapesjs/semantic.js +110 -0
  90. package/dist/plugins/client/client/grapesjs/semantic.js.map +1 -0
  91. package/dist/plugins/client/client/grapesjs/settings.d.ts +2 -0
  92. package/dist/plugins/client/client/grapesjs/settings.js +198 -0
  93. package/dist/plugins/client/client/grapesjs/settings.js.map +1 -0
  94. package/dist/plugins/client/client/grapesjs/storage.d.ts +1 -0
  95. package/dist/plugins/client/client/grapesjs/storage.js +126 -0
  96. package/dist/plugins/client/client/grapesjs/storage.js.map +1 -0
  97. package/dist/plugins/client/client/grapesjs/symbolDialogs.d.ts +3 -0
  98. package/dist/plugins/client/client/grapesjs/symbolDialogs.js +36 -0
  99. package/dist/plugins/client/client/grapesjs/symbolDialogs.js.map +1 -0
  100. package/dist/plugins/client/client/publication-transformers.d.ts +41 -0
  101. package/dist/plugins/client/client/publication-transformers.js +238 -0
  102. package/dist/plugins/client/client/publication-transformers.js.map +1 -0
  103. package/dist/plugins/client/client/utils.d.ts +2 -0
  104. package/dist/plugins/client/client/utils.js +25 -0
  105. package/dist/plugins/client/client/utils.js.map +1 -0
  106. package/dist/plugins/client/constants.d.ts +29 -0
  107. package/dist/plugins/client/constants.js +46 -0
  108. package/dist/plugins/client/constants.js.map +1 -0
  109. package/dist/plugins/client/page.d.ts +2 -0
  110. package/dist/plugins/client/page.js +31 -0
  111. package/dist/plugins/client/page.js.map +1 -0
  112. package/dist/plugins/client/plugins/client/11ty.d.ts +17 -0
  113. package/dist/plugins/client/plugins/client/11ty.js +86 -0
  114. package/dist/plugins/client/plugins/client/11ty.js.map +1 -0
  115. package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +2 -0
  116. package/dist/plugins/client/plugins/client/publicationRenderer.js +170 -0
  117. package/dist/plugins/client/plugins/client/publicationRenderer.js.map +1 -0
  118. package/dist/plugins/client/plugins/client/template.d.ts +2 -0
  119. package/dist/plugins/client/plugins/client/template.js +183 -0
  120. package/dist/plugins/client/plugins/client/template.js.map +1 -0
  121. package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
  122. package/dist/plugins/client/plugins/client/website-info.js +72 -0
  123. package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
  124. package/dist/plugins/client/types.d.ts +334 -0
  125. package/dist/plugins/client/types.js +74 -0
  126. package/dist/plugins/client/types.js.map +1 -0
  127. package/dist/plugins/server/constants.d.ts +29 -0
  128. package/dist/plugins/server/constants.js +49 -0
  129. package/dist/plugins/server/constants.js.map +1 -0
  130. package/dist/plugins/server/plugins/server/AuthPlugin.d.ts +1 -0
  131. package/dist/plugins/server/plugins/server/AuthPlugin.js +40 -0
  132. package/dist/plugins/server/plugins/server/AuthPlugin.js.map +1 -0
  133. package/dist/plugins/server/plugins/server/Cache.d.ts +2 -0
  134. package/dist/plugins/server/plugins/server/Cache.js +32 -0
  135. package/dist/plugins/server/plugins/server/Cache.js.map +1 -0
  136. package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
  137. package/dist/plugins/server/plugins/server/DownloadConnector.js +129 -0
  138. package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
  139. package/dist/plugins/server/plugins/server/FtpConnector.d.ts +86 -0
  140. package/dist/plugins/server/plugins/server/FtpConnector.js +526 -0
  141. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -0
  142. package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +87 -0
  143. package/dist/plugins/server/plugins/server/GitlabConnector.js +564 -0
  144. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -0
  145. package/dist/plugins/server/plugins/server/PublicationOptimizer.d.ts +0 -0
  146. package/dist/plugins/server/plugins/server/PublicationOptimizer.js +14 -0
  147. package/dist/plugins/server/plugins/server/PublicationOptimizer.js.map +1 -0
  148. package/dist/plugins/server/plugins/server/SslPlugin.d.ts +1 -0
  149. package/dist/plugins/server/plugins/server/SslPlugin.js +86 -0
  150. package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -0
  151. package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +1 -0
  152. package/dist/plugins/server/plugins/server/StaticPlugin.js +78 -0
  153. package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -0
  154. package/dist/plugins/server/server/config.d.ts +59 -0
  155. package/dist/plugins/server/server/config.js +181 -0
  156. package/dist/plugins/server/server/config.js.map +1 -0
  157. package/dist/plugins/server/server/connectors/FsHosting.d.ts +13 -0
  158. package/dist/plugins/server/server/connectors/FsHosting.js +54 -0
  159. package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -0
  160. package/dist/plugins/server/server/connectors/FsStorage.d.ts +43 -0
  161. package/dist/plugins/server/server/connectors/FsStorage.js +257 -0
  162. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -0
  163. package/dist/plugins/server/server/connectors/connectors.d.ts +87 -0
  164. package/dist/plugins/server/server/connectors/connectors.js +127 -0
  165. package/dist/plugins/server/server/connectors/connectors.js.map +1 -0
  166. package/dist/plugins/server/server/events.d.ts +26 -0
  167. package/dist/plugins/server/server/events.js +31 -0
  168. package/dist/plugins/server/server/events.js.map +1 -0
  169. package/dist/plugins/server/server/jobs.d.ts +12 -0
  170. package/dist/plugins/server/server/jobs.js +77 -0
  171. package/dist/plugins/server/server/jobs.js.map +1 -0
  172. package/dist/plugins/server/server/utils/validation.d.ts +7 -0
  173. package/dist/plugins/server/server/utils/validation.js +38 -0
  174. package/dist/plugins/server/server/utils/validation.js.map +1 -0
  175. package/dist/plugins/server/types.d.ts +334 -0
  176. package/dist/plugins/server/types.js +78 -0
  177. package/dist/plugins/server/types.js.map +1 -0
  178. package/dist/server/constants.js +29 -0
  179. package/dist/server/plugins/server/Cache.js +15 -0
  180. package/dist/server/server/api/api.js +20 -0
  181. package/dist/server/server/api/connectorApi.js +216 -0
  182. package/dist/server/server/api/publicationApi.js +108 -0
  183. package/dist/server/server/api/websiteApi.js +296 -0
  184. package/dist/server/server/cli.js +68 -0
  185. package/dist/server/server/config.js +120 -0
  186. package/dist/server/server/connectors/FsHosting.js +35 -0
  187. package/dist/server/server/connectors/FsStorage.js +225 -0
  188. package/dist/server/server/connectors/connectors.js +89 -0
  189. package/dist/server/server/events.js +14 -0
  190. package/dist/server/server/expose.js +27 -0
  191. package/dist/server/server/express.js +47 -0
  192. package/dist/server/server/index.js +90 -0
  193. package/dist/server/server/jobs.js +48 -0
  194. package/dist/server/server/utils/validation.js +15 -0
  195. package/dist/server/types.js +34 -0
  196. package/package.json +209 -0
  197. package/public/assets/container.png +0 -0
  198. package/public/assets/favicon.png +0 -0
  199. package/public/assets/logo-silex-small.png +0 -0
  200. package/public/robots.txt +2 -0
  201. package/public/silex.js +24 -0
  202. package/src/css/_mixins.scss +8 -0
  203. package/src/css/_variables.scss +33 -0
  204. package/src/css/dialog.scss +41 -0
  205. package/src/css/footer.scss +12 -0
  206. package/src/css/grapesjs-light.scss +180 -0
  207. package/src/css/grapesjs.scss +152 -0
  208. package/src/css/index.scss +32 -0
  209. package/src/css/light.scss +7 -0
  210. package/src/css/loader.scss +12 -0
  211. package/src/css/page-panel.scss +65 -0
  212. package/src/css/project-bar.scss +102 -0
  213. package/src/css/silex.scss +189 -0
  214. package/src/css/website-info.scss +14 -0
  215. package/src/html/debug.pug +8 -0
  216. package/src/html/index.pug +39 -0
  217. package/src/html/templates/includes.pug +1 -0
  218. package/src/html/templates/workspace.pug +2 -0
  219. package/src/ts/client/api.ts +144 -0
  220. package/src/ts/client/assetUrl.test.ts +125 -0
  221. package/src/ts/client/assetUrl.ts +235 -0
  222. package/src/ts/client/config.ts +105 -0
  223. package/src/ts/client/events.ts +34 -0
  224. package/src/ts/client/expose.ts +30 -0
  225. package/src/ts/client/grapesjs/LoginDialog.ts +204 -0
  226. package/src/ts/client/grapesjs/PublicationManager.ts +370 -0
  227. package/src/ts/client/grapesjs/PublicationUi.ts +342 -0
  228. package/src/ts/client/grapesjs/blocks.ts +37 -0
  229. package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
  230. package/src/ts/client/grapesjs/eleventy.ts +47 -0
  231. package/src/ts/client/grapesjs/footer.ts +27 -0
  232. package/src/ts/client/grapesjs/index.ts +335 -0
  233. package/src/ts/client/grapesjs/internal-links.test.ts +105 -0
  234. package/src/ts/client/grapesjs/internal-links.ts +225 -0
  235. package/src/ts/client/grapesjs/new-page-dialog.ts +90 -0
  236. package/src/ts/client/grapesjs/openImport.ts +81 -0
  237. package/src/ts/client/grapesjs/page-panel.ts +118 -0
  238. package/src/ts/client/grapesjs/project-bar.ts +107 -0
  239. package/src/ts/client/grapesjs/rich-text.ts +33 -0
  240. package/src/ts/client/grapesjs/semantic.ts +114 -0
  241. package/src/ts/client/grapesjs/settings.ts +208 -0
  242. package/src/ts/client/grapesjs/storage.ts +121 -0
  243. package/src/ts/client/grapesjs/symbolDialogs.ts +38 -0
  244. package/src/ts/client/index.ts +98 -0
  245. package/src/ts/client/publication-transformers.test.ts +199 -0
  246. package/src/ts/client/publication-transformers.ts +263 -0
  247. package/src/ts/client/utils.ts +28 -0
  248. package/src/ts/constants.ts +52 -0
  249. package/src/ts/externs.ts +18 -0
  250. package/src/ts/page.ts +31 -0
  251. package/src/ts/plugins/client/11ty.ts +104 -0
  252. package/src/ts/plugins/client/publicationRenderer.ts +174 -0
  253. package/src/ts/plugins/client/template.ts +192 -0
  254. package/src/ts/plugins/client/website-info.ts +84 -0
  255. package/src/ts/plugins/server/AuthPlugin.ts +38 -0
  256. package/src/ts/plugins/server/Cache.ts +28 -0
  257. package/src/ts/plugins/server/DownloadConnector.ts +148 -0
  258. package/src/ts/plugins/server/FtpConnector.test.ts +82 -0
  259. package/src/ts/plugins/server/FtpConnector.ts +609 -0
  260. package/src/ts/plugins/server/GitlabConnector.ts +644 -0
  261. package/src/ts/plugins/server/PublicationOptimizer.ts +14 -0
  262. package/src/ts/plugins/server/SslPlugin.ts +84 -0
  263. package/src/ts/plugins/server/StaticPlugin.ts +79 -0
  264. package/src/ts/server/api/api.ts +19 -0
  265. package/src/ts/server/api/connectorApi.ts +345 -0
  266. package/src/ts/server/api/publicationApi.ts +159 -0
  267. package/src/ts/server/api/websiteApi.ts +432 -0
  268. package/src/ts/server/cli.ts +65 -0
  269. package/src/ts/server/config.ts +189 -0
  270. package/src/ts/server/connectors/FsHosting.ts +60 -0
  271. package/src/ts/server/connectors/FsStorage.test.ts +51 -0
  272. package/src/ts/server/connectors/FsStorage.ts +261 -0
  273. package/src/ts/server/connectors/connectors.ts +203 -0
  274. package/src/ts/server/events.ts +37 -0
  275. package/src/ts/server/expose.ts +19 -0
  276. package/src/ts/server/express.ts +70 -0
  277. package/src/ts/server/index.ts +100 -0
  278. package/src/ts/server/jobs.test.ts +75 -0
  279. package/src/ts/server/jobs.ts +87 -0
  280. package/src/ts/server/utils/BackwardCompat.ts +552 -0
  281. package/src/ts/server/utils/validation.ts +34 -0
  282. package/src/ts/types.ts +339 -0
@@ -0,0 +1,208 @@
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 {html, render} from 'lit-html'
19
+ import {live} from 'lit-html/directives/live.js'
20
+ import * as grapesjs from 'grapesjs/dist/grapes.min.js'
21
+
22
+ import { WebsiteSettings } from '../../types'
23
+
24
+ const pluginName = 'settings-dialog'
25
+ const el = document.createElement('div')
26
+ let modal
27
+
28
+ // Get version from webpack (see plugin in webpack.config.js)
29
+ declare const VERSION: string
30
+
31
+ export const cmdOpenSettings = 'open-settings'
32
+
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.info('Silex version:', version)
42
+ editor.Commands.add(cmdOpenSettings, {
43
+ run: (_, sender, {page}) => {
44
+ modal = editor.Modal.open({
45
+ title: page ? 'Page settings' : 'Site Settings',
46
+ content: '',
47
+ attributes: { class: 'settings-dialog' },
48
+ })
49
+ .onceClose(() => {
50
+ sender.set('active', 0) // Deactivate the button to make it ready to be clicked again
51
+ editor.stopCommand(cmdOpenSettings) // apparently this is needed to be able to run the command several times
52
+ })
53
+ displaySettings(editor, opts, page)
54
+ modal.setContent(el)
55
+ const form = el.querySelector('form')
56
+ form.onsubmit = event => {
57
+ event.preventDefault()
58
+ saveSettings(editor, opts, page)
59
+ editor.stopCommand(cmdOpenSettings)
60
+ }
61
+ form.querySelector('input')?.focus()
62
+ return modal
63
+ },
64
+ stop: () => {
65
+ modal.close()
66
+ },
67
+ })
68
+ // add settings to the website
69
+ editor.on('storage:start:store', (data) => {
70
+ data.settings = editor.getModel().get('settings')
71
+ data.name = editor.getModel().get('name')
72
+ })
73
+ editor.on('storage:end:load', (data) => {
74
+ const model = editor.getModel()
75
+ model.set('settings', data.settings || {})
76
+ model.set('name', data.name)
77
+ updateDom(editor)
78
+ })
79
+ editor.on('page', (e) => {
80
+ editor.Canvas.getFrameEl().addEventListener('load', () => {
81
+ updateDom(editor)
82
+ })
83
+ })
84
+ })
85
+
86
+ // Is the model a site or a page?
87
+ function isSite(model) { return !!model.getHtml }
88
+
89
+ function displaySettings(editor, config, model = editor.getModel()) {
90
+ const settings = model.get('settings') || {} as WebsiteSettings
91
+ model.set('settings', settings)
92
+ render(html`
93
+ <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>
94
+ <form class="silex-form">
95
+ <h2>General settings</h2>
96
+ <div class="silex-form__group col2">
97
+ <label class="silex-form__element">
98
+ <h3>${isSite(model) ? 'Site name' : 'Page name'}</h3>
99
+ <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>
100
+ <input type="text" name="name" .value=${live(model.get('name') || '')}/>
101
+ </label>
102
+ <label class="silex-form__element">
103
+ <h3>Website language</h3>
104
+ <p>This is the default language code for this website. Example: en, fr, es...</p>
105
+ <input type="text" name="lang" .value=${live(settings.lang || '')}/>
106
+ </label>
107
+ <label class="silex-form__element">
108
+ <h3>Title</h3>
109
+ <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>
110
+ <input type="text" name="title" .value=${live(settings.title || '')}/>
111
+ </label>
112
+ <label class="silex-form__element">
113
+ <h3>Description</h3>
114
+ <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>
115
+ <input type="text" name="description" .value=${live(settings.description || '')}/>
116
+ </label>
117
+ <label class="silex-form__element">
118
+ <h3>Favicon</h3>
119
+ <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>
120
+ <input type="text" name="favicon" .value=${live(settings.favicon || '')}/>
121
+ </label>
122
+ </div>
123
+ <h2>Social networks sharing</h2>
124
+ <div>
125
+ <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>,
126
+ <a target="_blank" href="https://cards-dev.twitter.com/validator">Twitter</a>,
127
+ <a target="_blank" href="https://www.linkedin.com/post-inspector/inspect/">Linkedin</a></p>
128
+ </div>
129
+ <div class="silex-form__group col2">
130
+ <label class="silex-form__element">
131
+ <h3>Title</h3>
132
+ <p>The title of your website displayed when a user shares your website on a social network.
133
+ Do not include any branding in this title, just eye-catching phrase, e.g. "Learn everything about fishing".
134
+ Title should be between 60 and 90 characters long.</p>
135
+ <input type="text" name="og:title" .value=${live(settings['og:title'] || '')}/>
136
+ </label>
137
+ <label class="silex-form__element">
138
+ <h3>Description</h3>
139
+ <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>
140
+ <input type="text" name="og:description" .value=${live(settings['og:description'] || '')}/>
141
+ </label>
142
+ <label class="silex-form__element">
143
+ <h3>Image</h3>
144
+ <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>
145
+ <p>Please enter the full URL here, e.g. "http://mysite.com/path/to/image.jpg"</p>
146
+ <input type="text" name="og:image" .value=${live(settings['og:image'] || '')}/>
147
+ </label>
148
+ </div>
149
+ <h2>Code</h2>
150
+ <div class="silex-form__group">
151
+ <label class="silex-form__element">
152
+ <h3>HTML head</h3>
153
+ <p>HTML code which will be inserted in the HEAD tag.</p>
154
+ <textarea name="head" .value=${live(settings.head || '')}></textarea>
155
+ </label>
156
+ </div>
157
+ <footer>
158
+ <p class="silex-version">Silex ${version}</p>
159
+ <input class="silex-button" type="button" @click=${e => editor.stopCommand(cmdOpenSettings)} value="Cancel">
160
+ <input class="silex-button" type="submit" value="Ok">
161
+ </footer>
162
+ </form>
163
+ `, el)
164
+ }
165
+
166
+ function saveSettings(editor, config, model = editor.getModel()) {
167
+ const form = el.querySelector('form')
168
+ const formData = new FormData(form)
169
+ const data = Array.from(formData as any)
170
+ .reduce((aggregate, [key, value]) => {
171
+ aggregate[key] = value
172
+ return aggregate
173
+ }, {}) as {[key: string]: any}
174
+ // take the name out to the main model (by design in grapesjs pages)
175
+ const { name, ...settings } = data
176
+ model.set({
177
+ settings,
178
+ name,
179
+ })
180
+ // save if auto save is on
181
+ editor.getModel().set('changesCount', editor.getDirtyCount() + 1)
182
+ // update the DOM
183
+ updateDom(editor)
184
+ }
185
+ function getHeadContainer(doc, className) {
186
+ const container = doc.head.querySelector(`.${className}`)
187
+ if(container) {
188
+ return container
189
+ }
190
+ const newContainer = doc.createElement('div')
191
+ newContainer.classList.add(className)
192
+ doc.head.appendChild(newContainer)
193
+ return newContainer
194
+ }
195
+ function updateDom(editor) {
196
+ const doc = editor.Canvas.getDocument()
197
+ if(doc) {
198
+ // Site head
199
+ getHeadContainer(doc, 'site-head')
200
+ .innerHTML = editor.getModel().get('settings').head || ''
201
+ // Pages head
202
+ getHeadContainer(doc, 'page-head')
203
+ .innerHTML = editor.Pages.getSelected().get('settings')?.head || ''
204
+ } else {
205
+ // No document??
206
+ }
207
+ }
208
+
@@ -0,0 +1,121 @@
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 { ConnectorId, WebsiteId, WebsiteData, ConnectorUser, ConnectorType, ApiError, Asset, Page, Component, Style } from '../../types'
19
+ import { websiteLoad, websiteSave } from '../api'
20
+ import { cmdLogin, eventLoggedIn, eventLoggedOut, getCurrentUser, updateUser } from './LoginDialog'
21
+ import { addTempDataToPages, addTempDataToAssetUrl, addTempDataToStyles, removeTempDataFromAssetUrl, removeTempDataFromPages, removeTempDataFromStyles } from '../assetUrl'
22
+
23
+ export const storagePlugin = (editor) => {
24
+ editor.Storage.add('connector', {
25
+ async load(options: { id: WebsiteId, connectorId: ConnectorId }): Promise<WebsiteData> {
26
+ try {
27
+ const user: ConnectorUser = await getCurrentUser(editor) ?? await updateUser(editor, ConnectorType.STORAGE, options.connectorId)
28
+ if(!user) throw new ApiError('Not logged in', 401)
29
+ const data = await websiteLoad({websiteId: options.id, connectorId: user.storage.connectorId}) as WebsiteData
30
+ if(data.assets) data.assets = addTempDataToAssetUrl(data.assets, options.id, user.storage.connectorId)
31
+ if(data.pages) data.pages = addTempDataToPages(data.pages, options.id, user.storage.connectorId)
32
+ if(data.styles) data.styles = addTempDataToStyles(data.styles, options.id, user.storage.connectorId)
33
+ return data
34
+
35
+ } catch (err) {
36
+ if(err.httpStatusCode === 401) {
37
+ editor.once(eventLoggedIn, async () => {
38
+ try {
39
+ editor.StorageManager.setAutosave(false)
40
+ const data = await editor.Storage.load(options)
41
+ editor.StorageManager.setAutosave(true)
42
+ editor.loadProjectData(data)
43
+ } catch (err) {
44
+ console.error('connectorPlugin load error', err)
45
+ throw err
46
+ }
47
+ })
48
+ editor.Commands.run(cmdLogin)
49
+ }
50
+ console.error('connectorPlugin load error', err)
51
+ throw err
52
+ }
53
+ },
54
+
55
+ async store(data: WebsiteData, options: { id: WebsiteId, connectorId: ConnectorId }) {
56
+ try {
57
+ if(await getCurrentUser(editor)) {
58
+ const user = await getCurrentUser(editor)
59
+ data.assets = removeTempDataFromAssetUrl(data.assets)
60
+ data.pages = removeTempDataFromPages(data.pages)
61
+ data.styles = removeTempDataFromStyles(data.styles)
62
+ await websiteSave({websiteId: options.id, connectorId: user.storage.connectorId, data})
63
+ } else {
64
+ editor.once(eventLoggedIn, () => {
65
+ return editor.Storage.store()
66
+ })
67
+ editor.Commands.run(cmdLogin)
68
+ }
69
+ } catch (err) {
70
+ console.error('connectorPlugin store error', err)
71
+ throw err
72
+ }
73
+ }
74
+ })
75
+ // Handle errors
76
+ editor.on('storage:error:load', (err: Error) => {
77
+ handleError(err)
78
+ })
79
+ editor.on('storage:error:store', (err: Error) => {
80
+ handleError(err)
81
+ })
82
+ editor.on('asset:upload:end', (err: Error) => {
83
+ editor.store()
84
+ })
85
+ editor.on('asset:upload:error', (err: Error) => {
86
+ handleError(err)
87
+ })
88
+ function handleError(_err: Error | string) {
89
+ const err: Error = typeof _err === 'string' ? JSON.parse(_err) : _err
90
+ console.error('Error with loading or saving website or uploading asset', err, err instanceof ApiError)
91
+ if (err instanceof ApiError) {
92
+ console.error('ApiError', err.httpStatusCode, err.message)
93
+ switch (err.httpStatusCode) {
94
+ case 404:
95
+ return editor.Modal.open({
96
+ title: 'Website not found',
97
+
98
+ content: `This website could not be found.<br><hr>${err.message}`,
99
+ })
100
+ case 403:
101
+ return editor.Modal.open({
102
+ title: 'Access denied',
103
+ content: `You are not allowed to access this website.<br><hr>${err.message}`,
104
+ })
105
+ case 401:
106
+ editor.trigger(eventLoggedOut)
107
+ return
108
+ default:
109
+ return editor.Modal.open({
110
+ title: 'Error',
111
+ content: `An error occured.<br>${err.message}`,
112
+ })
113
+ }
114
+ } else {
115
+ return editor.Modal.open({
116
+ title: 'Error',
117
+ content: `An error occured.<br>${err.message}`,
118
+ })
119
+ }
120
+ }
121
+ }
@@ -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 * as grapesjs from 'grapesjs/dist/grapes.min.js'
19
+ import { cmdAddSymbol } from '@silexlabs/grapesjs-symbols'
20
+
21
+ export const cmdPromptAddSymbol = 'symbol-prompt-add'
22
+
23
+ export default grapesjs.plugins.add(name, (editor, opts) => {
24
+ function getNext(prefix) {
25
+ let idx = 1
26
+ while(editor.Symbols.find(s => s.get('label') === prefix + idx)) {
27
+ idx++
28
+ }
29
+ return prefix + idx
30
+ }
31
+ editor.Commands.add(cmdPromptAddSymbol, {
32
+ run: (_, sender) => {
33
+ const label = prompt('Label', getNext('Symbol '))
34
+ const icon = 'fa-gem'
35
+ editor.runCommand(cmdAddSymbol, { label, icon })
36
+ }
37
+ })
38
+ })
@@ -0,0 +1,98 @@
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
+ /**
19
+ * @fileoverview
20
+ * Defines the entry point of Silex client side application
21
+ *
22
+ */
23
+
24
+ import { assetsPublicationTransformer } from './assetUrl'
25
+ import { ClientConfig } from './config'
26
+ import { ClientEvent } from './events'
27
+ import { initEditor, getEditor } from './grapesjs/index'
28
+
29
+ // Expose API to calling app as window.silex
30
+ export * from './expose'
31
+
32
+ /**
33
+ * Start Silex, called from host HTML page with window.silex.start()
34
+ */
35
+ export async function start(options = {}) {
36
+ const config = new ClientConfig()
37
+ Object.assign(config, options)
38
+
39
+ // Add the publication transformers which are used to cleanup assets URLs
40
+ config.addPublicationTransformers(assetsPublicationTransformer)
41
+
42
+ // Config file in root folder which may be generated by the server
43
+ await config.addPlugin(config.clientConfigUrl, {})
44
+
45
+ // Debug mode
46
+ if (config.debug) {
47
+ console.warn('Silex starting in debug mode.', {config})
48
+ }
49
+
50
+ // Notify plugins that loading is over and Silex is starting
51
+ config.emit(ClientEvent.STARTUP_START)
52
+
53
+ // Init GrapesJS config which depend on the config file properties
54
+ config.initGrapesConfig()
55
+
56
+ // Start grapesjs
57
+ config.emit(ClientEvent.GRAPESJS_START)
58
+ try{
59
+ await initEditor(config.grapesJsConfig)
60
+ } catch(e) {
61
+ console.error('Error starting Silex', e)
62
+ throw e
63
+ }
64
+
65
+ const editor = getEditor()
66
+
67
+ // Store the config in the editor
68
+ editor.getModel().set('config', config)
69
+
70
+ // Notify plugins
71
+ config.emit(ClientEvent.GRAPESJS_END, { editor })
72
+
73
+ // Init internationalization module
74
+ editor.I18n.setLocale(config.lang)
75
+
76
+ // Load the site
77
+ editor.StorageManager.setAutosave(false)
78
+ try {
79
+ await editor.load(null)
80
+ } catch(e) {
81
+ if(e.httpStatusCode === 401) {
82
+ // Unauthorized, will try to login
83
+ } else {
84
+ // Will display an error message, see in storage.ts
85
+ }
86
+ } finally {
87
+ setTimeout(() => {
88
+ // This needs time for grapesjs
89
+ editor.editor.set('changesCount', 0)
90
+ editor.StorageManager.setAutosave(true)
91
+
92
+ // This needs time for the loader to be hidden
93
+ document.querySelector('.silex-loader').classList.add('silex-dialog-hide')
94
+ document.querySelector('#gjs').classList.remove('silex-dialog-hide')
95
+ config.emit(ClientEvent.STARTUP_END, { editor, config })
96
+ }, 100)
97
+ }
98
+ }
@@ -0,0 +1,199 @@
1
+ import { jest, expect, describe, it, beforeEach } from '@jest/globals'
2
+ import { Mock } from 'jest-mock'
3
+ import {
4
+ renderComponents,
5
+ renderCssRules,
6
+ resetRenderComponents,
7
+ resetRenderCssRules,
8
+ PublicationTransformer,
9
+ transformFiles,
10
+ transformBgImage
11
+ } from './publication-transformers'
12
+ import { ClientConfig } from './config'
13
+ import GrapesJS, { Component, Editor, ObjectStrings, Page } from 'grapesjs'
14
+ import { ClientSideFile, ClientSideFileType, PublicationData } from '../types'
15
+
16
+ describe('publication-transformers', () => {
17
+ let mockConfig: ClientConfig
18
+ let mockFile: ClientSideFile
19
+ let mockData: PublicationData
20
+ let transformer: PublicationTransformer
21
+
22
+ let editor: Editor
23
+
24
+ beforeEach(() => {
25
+ // Initialize your mocks here
26
+ editor = GrapesJS.init({
27
+ headless: true,
28
+ storageManager: { autoload: false },
29
+ })
30
+ expect(editor).not.toBeUndefined()
31
+ editor.addComponents(`
32
+ <nav class="parent">
33
+ <div class="child">Hello world</div>
34
+ <nav>
35
+ `)
36
+ editor.addStyle(`
37
+ .parent {
38
+ color: red;
39
+ }
40
+ .child {
41
+ color: blue;
42
+ }
43
+ `)
44
+ mockFile = {
45
+ path: 'test path',
46
+ content: 'test content',
47
+ } as ClientSideFile
48
+ mockData = {
49
+ files: [mockFile],
50
+ pages: [{} as Page],
51
+ } as any as PublicationData
52
+ transformer = {
53
+ renderComponent: jest.fn(),
54
+ renderCssRule: jest.fn(),
55
+ transformFile: jest.fn(),
56
+ transformPermalink: jest.fn(),
57
+ transformPath: jest.fn(),
58
+ } as PublicationTransformer
59
+ mockConfig = { getEditor: jest.fn(() => editor), publicationTransformers: [transformer] } as unknown as ClientConfig
60
+ editor.getModel().set('config', mockConfig)
61
+ })
62
+
63
+ it('should transform components', () => {
64
+ const renderComponent = transformer.renderComponent as Mock
65
+ renderComponent.mockReturnValue('mockHtml')
66
+ renderComponents(editor)
67
+ const html = editor.getHtml()
68
+ expect(renderComponent).toBeCalledTimes(6)
69
+ const results = renderComponent.mock.calls.map(call => call[0] as Component)
70
+ const parent = results.find(c => c.getClasses()[0] === 'parent')
71
+ expect(parent).not.toBeUndefined()
72
+ const child = results.find(c => c.getClasses()[0] === 'child')
73
+ expect(child).not.toBeUndefined()
74
+ })
75
+
76
+ it('should reset transformed pages', () => {
77
+ const renderComponent = transformer.renderComponent as Mock
78
+ renderComponent.mockReturnValue('mockHtml')
79
+ renderComponents(editor)
80
+ resetRenderComponents(editor)
81
+ const html = editor.getHtml()
82
+ expect(renderComponent).toBeCalledTimes(0)
83
+ expect(html).toContain('Hello world')
84
+ })
85
+
86
+ it('should transform styles', () => {
87
+ const transformCssRule = transformer.renderCssRule as Mock
88
+ transformCssRule.mockImplementation((rule: any) => {
89
+ return { color: 'test'+rule.color } as ObjectStrings
90
+ })
91
+ renderCssRules(editor)
92
+ const css = editor.getCss()
93
+ expect(transformCssRule).toBeCalledTimes(2)
94
+ const results = transformCssRule.mock.results.map(r => r.value as ObjectStrings)
95
+ const parent = results.find(c => c.color === 'testred')
96
+ expect(parent).not.toBeUndefined()
97
+ const child = results.find(c => c.color === 'testblue')
98
+ expect(child).not.toBeUndefined()
99
+ expect(css).toContain('color:test')
100
+ expect(css).not.toContain('color:blue')
101
+ })
102
+
103
+ it('should reset transformed styles', () => {
104
+ const renderCssRule = transformer.renderCssRule as Mock
105
+ const returned = {color: 'test'} as ObjectStrings
106
+ renderCssRule.mockReturnValue(returned)
107
+ renderCssRules(editor)
108
+ resetRenderCssRules(editor)
109
+ const css = editor.getCss()
110
+ expect(renderCssRule).toBeCalledTimes(0)
111
+ expect(css).toContain('color:blue')
112
+ })
113
+
114
+ it('should transform files', () => {
115
+ const transformFile = transformer.transformFile as Mock
116
+ const returned = {path: 'test path changed', content: 'test content changed'} as ClientSideFile
117
+ transformFile.mockReturnValue(returned)
118
+ expect(mockData.files?.length).toBe(1)
119
+ if (mockData.files?.length) { // To avoid ! operator on mockData.files and make the lint happy
120
+ expect(mockData.files[0]).toBe(mockFile)
121
+ transformFiles(editor, mockData)
122
+ expect(transformFile).toBeCalledTimes(1)
123
+ expect(mockData.files[0]).toBe(returned)
124
+ }
125
+ })
126
+ it('should transform permalinks of images src', () => {
127
+ editor.addComponents(`
128
+ <img src="test.png">
129
+ `)
130
+ const transformPermalink = transformer.transformPermalink as Mock
131
+ const transformedSrc = 'transformed.png'
132
+ transformPermalink.mockReturnValue(transformedSrc)
133
+ renderComponents(editor)
134
+ const html = editor.getHtml()
135
+ expect(transformPermalink).toBeCalledTimes(2) // 2x because of c.attributes.src and c.attributes.attributes.src
136
+ expect(transformPermalink.mock.calls[0][0]).toBe('test.png')
137
+ expect(transformPermalink.mock.calls[0][1]).toBe('asset')
138
+ })
139
+ it('should transform a style rule with background image', () => {
140
+ expect(transformBgImage(editor, { 'background-image': 'url(test.png)' })).toEqual({ 'background-image': 'url(test.png)' })
141
+ const transformPermalink = transformer.transformPermalink as Mock
142
+ const transformedSrc = 'transformed.png'
143
+ transformPermalink.mockImplementation((url, type) => (url as string).replace('test.png', transformedSrc))
144
+ expect(transformBgImage(editor, { 'background-image': 'url(test.png)' })).toEqual({ 'background-image': 'url(transformed.png)' })
145
+ expect(transformBgImage(editor, { 'background-image': 'url("test.png")' })).toEqual({ 'background-image': 'url(transformed.png)' })
146
+ })
147
+
148
+ it('should transform permalinks of background images in inline css', () => {
149
+ const el = document.createElement('div')
150
+ const [comp] = editor.addComponents(el)
151
+ comp.setStyle({ 'background-image': 'url(test.png)' })
152
+ //editor.addComponents(`
153
+ // <div style="color: black; background-image: url(test.png);"></div>
154
+ //`)
155
+ const transformPermalink = transformer.transformPermalink as Mock
156
+ const transformedSrc = 'transformed.png'
157
+ transformPermalink.mockReturnValue(transformedSrc)
158
+ renderComponents(editor)
159
+ const css = editor.getCss()
160
+ expect(transformPermalink).toBeCalledTimes(1)
161
+ expect(transformPermalink.mock.calls[0][0]).toBe('test.png')
162
+ expect(transformPermalink.mock.calls[0][1]).toBe('asset')
163
+ expect(css).toContain('url(test.png)')
164
+ })
165
+ it('should transform permalinks of background images in styles', () => {
166
+ editor.addComponents(`
167
+ <div class="test"></div>
168
+ `)
169
+ editor.addStyle(`
170
+ .test {
171
+ background-image: url(test.png);
172
+ }
173
+ `)
174
+ const transformPermalink = transformer.transformPermalink as Mock
175
+ const transformedSrc = 'transformed.png'
176
+ transformPermalink.mockReturnValue(transformedSrc)
177
+ renderCssRules(editor)
178
+ const css = editor.getCss()
179
+ expect(transformPermalink).toBeCalledTimes(1)
180
+ expect(transformPermalink.mock.calls[0][0]).toBe('test.png')
181
+ expect(transformPermalink.mock.calls[0][1]).toBe('asset')
182
+ expect(css).toContain(`url(${transformedSrc})`)
183
+ })
184
+ it('should transform links to pages to match permalinks ', () => {
185
+ editor.addComponents(`
186
+ <a href="./index.html">test</a>
187
+ `)
188
+ const transformPermalink = transformer.transformPermalink as Mock
189
+ const transformedPermalink = './transformed-page.html'
190
+ transformPermalink.mockReturnValue(transformedPermalink)
191
+ renderComponents(editor)
192
+ const html = editor.getHtml()
193
+ expect(transformPermalink).toBeCalledTimes(1)
194
+ expect((transformPermalink.mock.calls[0][0])).toBe('./index.html')
195
+ expect((transformPermalink.mock.calls[0][1])).toBe(ClientSideFileType.HTML)
196
+ expect(html).not.toContain('href="./index.html"')
197
+ expect(html).toContain(`href="${transformedPermalink}"`)
198
+ })
199
+ })