@silexlabs/silex 3.0.0-alpha.97 → 3.0.0

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 (206) hide show
  1. package/.env.default +2 -1
  2. package/README.md +31 -9
  3. package/dist/client/client/api.d.ts +5 -0
  4. package/dist/client/client/assetUrl.d.ts +3 -0
  5. package/dist/client/client/config.d.ts +31 -0
  6. package/dist/client/client/events.d.ts +7 -1
  7. package/dist/client/client/grapesjs/PublicationManager.d.ts +2 -1
  8. package/dist/client/client/grapesjs/css-props.d.ts +6 -0
  9. package/dist/client/client/grapesjs/img.d.ts +6 -0
  10. package/dist/client/client/grapesjs/index.d.ts +2 -2
  11. package/dist/client/client/grapesjs/new-page-dialog.d.ts +1 -1
  12. package/dist/client/client/grapesjs/page-panel.d.ts +1 -1
  13. package/dist/client/client/grapesjs/project-bar.d.ts +16 -0
  14. package/dist/client/client/grapesjs/resize-panel.d.ts +1 -0
  15. package/dist/client/client/grapesjs/rich-text.d.ts +4 -1
  16. package/dist/client/client/grapesjs/semantic.d.ts +1 -1
  17. package/dist/client/client/grapesjs/settings-sections.d.ts +15 -0
  18. package/dist/client/client/grapesjs/settings.d.ts +3 -1
  19. package/dist/client/client/grapesjs/symbolDialogs.d.ts +1 -1
  20. package/dist/client/client/grapesjs/view-buttons.d.ts +1 -0
  21. package/dist/client/client/index.d.ts +10 -0
  22. package/dist/client/client/publication-transformers.d.ts +4 -3
  23. package/dist/client/client/publish-custom-code-block.d.ts +4 -0
  24. package/dist/client/client/publish-fonts.d.ts +4 -0
  25. package/dist/client/client/utils.d.ts +4 -0
  26. package/dist/client/constants.d.ts +1 -0
  27. package/dist/client/css/admin.css +273 -32
  28. package/dist/client/index.html +2 -1
  29. package/dist/client/js/main.js +164 -123
  30. package/dist/client/js/main.js.LICENSE.txt +15 -1
  31. package/dist/client/types.d.ts +12 -7
  32. package/dist/plugins/client/client/events.d.ts +7 -1
  33. package/dist/plugins/client/client/events.js +7 -1
  34. package/dist/plugins/client/client/events.js.map +1 -1
  35. package/dist/plugins/client/plugins/client/template.js +108 -58
  36. package/dist/plugins/client/plugins/client/template.js.map +1 -1
  37. package/dist/plugins/client/types.d.ts +12 -7
  38. package/dist/plugins/client/types.js +6 -0
  39. package/dist/plugins/client/types.js.map +1 -1
  40. package/dist/plugins/server/constants.d.ts +1 -0
  41. package/dist/plugins/server/constants.js +2 -1
  42. package/dist/plugins/server/constants.js.map +1 -1
  43. package/dist/plugins/server/plugins/server/FtpConnector.d.ts +1 -0
  44. package/dist/plugins/server/plugins/server/FtpConnector.js +12 -1
  45. package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
  46. package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +3 -0
  47. package/dist/plugins/server/plugins/server/GitlabConnector.js +72 -35
  48. package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
  49. package/dist/plugins/server/server/config.js +3 -3
  50. package/dist/plugins/server/server/config.js.map +1 -1
  51. package/dist/plugins/server/server/connectors/FsHosting.d.ts +1 -0
  52. package/dist/plugins/server/server/connectors/FsHosting.js +13 -1
  53. package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -1
  54. package/dist/plugins/server/server/connectors/FsStorage.d.ts +2 -1
  55. package/dist/plugins/server/server/connectors/FsStorage.js +29 -25
  56. package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
  57. package/dist/plugins/server/server/connectors/connectors.d.ts +1 -0
  58. package/dist/plugins/server/server/jobs.js +4 -2
  59. package/dist/plugins/server/server/jobs.js.map +1 -1
  60. package/dist/plugins/server/types.d.ts +12 -7
  61. package/dist/plugins/server/types.js +7 -1
  62. package/dist/plugins/server/types.js.map +1 -1
  63. package/dist/server/constants.js +2 -1
  64. package/dist/server/server/api/publicationApi.js +3 -5
  65. package/dist/server/server/api/websiteApi.js +22 -3
  66. package/dist/server/server/cli.js +9 -6
  67. package/dist/server/server/config.js +3 -3
  68. package/dist/server/server/connectors/FsHosting.js +13 -1
  69. package/dist/server/server/connectors/FsStorage.js +28 -25
  70. package/dist/server/server/jobs.js +4 -2
  71. package/dist/server/types.js +7 -1
  72. package/package.json +34 -30
  73. package/public/assets/favicon.ico +0 -0
  74. package/public/assets/favicon.png +0 -0
  75. package/public/assets/logo-silex-small.png +0 -0
  76. package/src/css/_variables.scss +5 -3
  77. package/src/css/dialog.scss +22 -1
  78. package/src/css/fonts.scss +31 -0
  79. package/src/css/footer.scss +1 -1
  80. package/src/css/grapesjs-plugins.scss +34 -0
  81. package/src/css/grapesjs.scss +26 -11
  82. package/src/css/index.scss +1 -0
  83. package/src/css/project-bar.scss +11 -7
  84. package/src/css/silex.scss +58 -5
  85. package/src/html/index.pug +2 -2
  86. package/src/ts/client/api.ts +14 -9
  87. package/src/ts/client/assetUrl.test.ts +39 -0
  88. package/src/ts/client/assetUrl.ts +24 -5
  89. package/src/ts/client/config.ts +85 -4
  90. package/src/ts/client/events.ts +15 -9
  91. package/src/ts/client/grapesjs/PublicationManager.ts +71 -38
  92. package/src/ts/client/grapesjs/PublicationUi.ts +13 -11
  93. package/src/ts/client/grapesjs/css-props.ts +544 -0
  94. package/src/ts/client/grapesjs/eleventy.ts +2 -5
  95. package/src/ts/client/grapesjs/footer.ts +24 -9
  96. package/src/ts/client/grapesjs/img.ts +147 -0
  97. package/src/ts/client/grapesjs/index.ts +63 -25
  98. package/src/ts/client/grapesjs/internal-links.test.ts +2 -1
  99. package/src/ts/client/grapesjs/internal-links.ts +0 -1
  100. package/src/ts/client/grapesjs/new-page-dialog.ts +2 -3
  101. package/src/ts/client/grapesjs/page-panel.ts +28 -3
  102. package/src/ts/client/grapesjs/project-bar.ts +83 -64
  103. package/src/ts/client/grapesjs/resize-panel.ts +20 -0
  104. package/src/ts/client/grapesjs/rich-text.ts +21 -13
  105. package/src/ts/client/grapesjs/semantic.ts +3 -3
  106. package/src/ts/client/grapesjs/settings-sections.ts +117 -0
  107. package/src/ts/client/grapesjs/settings.ts +120 -77
  108. package/src/ts/client/grapesjs/symbolDialogs.ts +2 -3
  109. package/src/ts/client/grapesjs/view-buttons.ts +42 -0
  110. package/src/ts/client/index.ts +13 -6
  111. package/src/ts/client/publication-transformers.test.ts +13 -5
  112. package/src/ts/client/publication-transformers.ts +60 -47
  113. package/{dist/plugins/client/client/utils.js → src/ts/client/publish-custom-code-block.ts} +14 -8
  114. package/{dist/plugins/client/page.js → src/ts/client/publish-fonts.ts} +19 -14
  115. package/src/ts/client/utils.ts +18 -0
  116. package/src/ts/constants.ts +1 -0
  117. package/src/ts/plugins/client/template.ts +112 -60
  118. package/src/ts/plugins/server/FtpConnector.ts +14 -2
  119. package/src/ts/plugins/server/GitlabConnector.ts +71 -32
  120. package/src/ts/server/api/publicationApi.ts +5 -5
  121. package/src/ts/server/api/websiteApi.ts +39 -9
  122. package/src/ts/server/cli.ts +7 -5
  123. package/src/ts/server/config.ts +4 -4
  124. package/src/ts/server/connectors/FsHosting.ts +11 -5
  125. package/src/ts/server/connectors/FsStorage.ts +33 -4
  126. package/src/ts/server/connectors/connectors.ts +1 -1
  127. package/src/ts/server/jobs.ts +4 -2
  128. package/src/ts/types.ts +10 -7
  129. package/dist/plugins/client/client/api.d.ts +0 -85
  130. package/dist/plugins/client/client/api.js +0 -114
  131. package/dist/plugins/client/client/api.js.map +0 -1
  132. package/dist/plugins/client/client/assetUrl.d.ts +0 -51
  133. package/dist/plugins/client/client/assetUrl.js +0 -224
  134. package/dist/plugins/client/client/assetUrl.js.map +0 -1
  135. package/dist/plugins/client/client/config.d.ts +0 -58
  136. package/dist/plugins/client/client/config.js +0 -90
  137. package/dist/plugins/client/client/config.js.map +0 -1
  138. package/dist/plugins/client/client/grapesjs/LoginDialog.d.ts +0 -15
  139. package/dist/plugins/client/client/grapesjs/LoginDialog.js +0 -195
  140. package/dist/plugins/client/client/grapesjs/LoginDialog.js.map +0 -1
  141. package/dist/plugins/client/client/grapesjs/PublicationManager.d.ts +0 -72
  142. package/dist/plugins/client/client/grapesjs/PublicationManager.js +0 -356
  143. package/dist/plugins/client/client/grapesjs/PublicationManager.js.map +0 -1
  144. package/dist/plugins/client/client/grapesjs/PublicationUi.d.ts +0 -62
  145. package/dist/plugins/client/client/grapesjs/PublicationUi.js +0 -336
  146. package/dist/plugins/client/client/grapesjs/PublicationUi.js.map +0 -1
  147. package/dist/plugins/client/client/grapesjs/blocks.d.ts +0 -1
  148. package/dist/plugins/client/client/grapesjs/blocks.js +0 -37
  149. package/dist/plugins/client/client/grapesjs/blocks.js.map +0 -1
  150. package/dist/plugins/client/client/grapesjs/breadcrumbs.d.ts +0 -1
  151. package/dist/plugins/client/client/grapesjs/breadcrumbs.js +0 -75
  152. package/dist/plugins/client/client/grapesjs/breadcrumbs.js.map +0 -1
  153. package/dist/plugins/client/client/grapesjs/footer.d.ts +0 -2
  154. package/dist/plugins/client/client/grapesjs/footer.js +0 -28
  155. package/dist/plugins/client/client/grapesjs/footer.js.map +0 -1
  156. package/dist/plugins/client/client/grapesjs/index.d.ts +0 -5
  157. package/dist/plugins/client/client/grapesjs/index.js +0 -313
  158. package/dist/plugins/client/client/grapesjs/index.js.map +0 -1
  159. package/dist/plugins/client/client/grapesjs/internal-links.d.ts +0 -1
  160. package/dist/plugins/client/client/grapesjs/internal-links.js +0 -219
  161. package/dist/plugins/client/client/grapesjs/internal-links.js.map +0 -1
  162. package/dist/plugins/client/client/grapesjs/new-page-dialog.d.ts +0 -2
  163. package/dist/plugins/client/client/grapesjs/new-page-dialog.js +0 -83
  164. package/dist/plugins/client/client/grapesjs/new-page-dialog.js.map +0 -1
  165. package/dist/plugins/client/client/grapesjs/openImport.d.ts +0 -9
  166. package/dist/plugins/client/client/grapesjs/openImport.js +0 -74
  167. package/dist/plugins/client/client/grapesjs/openImport.js.map +0 -1
  168. package/dist/plugins/client/client/grapesjs/page-panel.d.ts +0 -3
  169. package/dist/plugins/client/client/grapesjs/page-panel.js +0 -113
  170. package/dist/plugins/client/client/grapesjs/page-panel.js.map +0 -1
  171. package/dist/plugins/client/client/grapesjs/project-bar.d.ts +0 -1
  172. package/dist/plugins/client/client/grapesjs/project-bar.js +0 -104
  173. package/dist/plugins/client/client/grapesjs/project-bar.js.map +0 -1
  174. package/dist/plugins/client/client/grapesjs/rich-text.d.ts +0 -1
  175. package/dist/plugins/client/client/grapesjs/rich-text.js +0 -31
  176. package/dist/plugins/client/client/grapesjs/rich-text.js.map +0 -1
  177. package/dist/plugins/client/client/grapesjs/semantic.d.ts +0 -1
  178. package/dist/plugins/client/client/grapesjs/semantic.js +0 -110
  179. package/dist/plugins/client/client/grapesjs/semantic.js.map +0 -1
  180. package/dist/plugins/client/client/grapesjs/settings.d.ts +0 -2
  181. package/dist/plugins/client/client/grapesjs/settings.js +0 -198
  182. package/dist/plugins/client/client/grapesjs/settings.js.map +0 -1
  183. package/dist/plugins/client/client/grapesjs/storage.d.ts +0 -1
  184. package/dist/plugins/client/client/grapesjs/storage.js +0 -126
  185. package/dist/plugins/client/client/grapesjs/storage.js.map +0 -1
  186. package/dist/plugins/client/client/grapesjs/symbolDialogs.d.ts +0 -3
  187. package/dist/plugins/client/client/grapesjs/symbolDialogs.js +0 -36
  188. package/dist/plugins/client/client/grapesjs/symbolDialogs.js.map +0 -1
  189. package/dist/plugins/client/client/publication-transformers.d.ts +0 -41
  190. package/dist/plugins/client/client/publication-transformers.js +0 -238
  191. package/dist/plugins/client/client/publication-transformers.js.map +0 -1
  192. package/dist/plugins/client/client/utils.d.ts +0 -2
  193. package/dist/plugins/client/client/utils.js.map +0 -1
  194. package/dist/plugins/client/constants.d.ts +0 -29
  195. package/dist/plugins/client/constants.js +0 -46
  196. package/dist/plugins/client/constants.js.map +0 -1
  197. package/dist/plugins/client/page.d.ts +0 -2
  198. package/dist/plugins/client/page.js.map +0 -1
  199. package/dist/plugins/client/plugins/client/11ty.d.ts +0 -17
  200. package/dist/plugins/client/plugins/client/11ty.js +0 -86
  201. package/dist/plugins/client/plugins/client/11ty.js.map +0 -1
  202. package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +0 -2
  203. package/dist/plugins/client/plugins/client/publicationRenderer.js +0 -170
  204. package/dist/plugins/client/plugins/client/publicationRenderer.js.map +0 -1
  205. package/src/ts/plugins/client/11ty.ts +0 -104
  206. package/src/ts/plugins/client/publicationRenderer.ts +0 -174
@@ -0,0 +1,20 @@
1
+ export default function(editor) {
2
+ editor.Panels.addPanel({
3
+ id: 'viewsOptions',
4
+ buttons: [{
5
+ id: 'resize',
6
+ // icon for arrow left
7
+ className: 'viewsOptions__size-btn',
8
+ command: 'resize-panel',
9
+ attributes: { title: 'Resize panel' },
10
+ }],
11
+ })
12
+ editor.Commands.add('resize-panel', {
13
+ run: (editor, sender) => {
14
+ document.documentElement.style.setProperty('--viewsPanelWidth', '40%')
15
+ },
16
+ stop: (editor, sender) => {
17
+ document.documentElement.style.setProperty('--viewsPanelWidth', '20%')
18
+ },
19
+ })
20
+ }
@@ -15,19 +15,27 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import * as grapesjs from 'grapesjs/dist/grapes.min.js'
18
+ import { RichTextEditorAction } from 'grapesjs'
19
19
 
20
20
  const pluginName = 'richText'
21
21
 
22
- export const richTextPlugin = grapesjs.plugins.add(pluginName, (editor, opts) => {
23
- editor.RichTextEditor.add('orderedList', {
24
- icon: '1.',
25
- attributes: {title: 'Ordered List'},
26
- result: rte => rte.exec('insertOrderedList'),
27
- })
28
- editor.RichTextEditor.add('unorderedList', {
29
- icon: '<i class="fa fa-list-ul"></i>',
30
- attributes: {title: 'Unordered List'},
31
- result: rte => rte.exec('insertUnorderedList'),
32
- })
33
- })
22
+ export const orderedList: RichTextEditorAction = {
23
+ name: 'orderedList',
24
+ icon: '1.',
25
+ attributes: { title: 'Ordered List' },
26
+ result: rte => rte.exec('insertOrderedList'),
27
+ }
28
+
29
+ export const unorderedList: RichTextEditorAction = {
30
+ name: 'unorderedList',
31
+ icon: '•',
32
+ //icon: '<i class="fa fa-list-ul"></i>',
33
+ attributes: { title: 'Unordered List' },
34
+ result: rte => rte.exec('insertUnorderedList'),
35
+ }
36
+
37
+ export const richTextPlugin = (editor, opts) => {
38
+ // This doesn't work, it is now added in the editor config
39
+ //editor.RichTextEditor.add('orderedList', orderedList)
40
+ //editor.RichTextEditor.add('unorderedList', unorderedList)
41
+ }
@@ -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} 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) { return !!model.getHtml }
10
+
11
+ export interface SettingsSection {
12
+ id: string
13
+ label: string
14
+ render: (settings: WebsiteSettings, model) => any
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
+ }]
@@ -16,12 +16,18 @@
16
16
  */
17
17
 
18
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'
19
+ import {defaultSections, idCodeWrapper, isSite} 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
+
28
+ const sectionsSite = [...defaultSections]
29
+ const sectionsPage = [...defaultSections]
23
30
 
24
- const pluginName = 'settings-dialog'
25
31
  const el = document.createElement('div')
26
32
  let modal
27
33
 
@@ -31,7 +37,8 @@ declare const VERSION: string
31
37
  export const cmdOpenSettings = 'open-settings'
32
38
 
33
39
  let version = 'v3'
34
- export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) => {
40
+ let headEditor = null
41
+ export const settingsDialog = (editor, opts) => {
35
42
  // Display Silex version from package.json
36
43
  try {
37
44
  version = VERSION
@@ -47,7 +54,7 @@ export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) =>
47
54
  attributes: { class: 'settings-dialog' },
48
55
  })
49
56
  .onceClose(() => {
50
- sender.set('active', 0) // Deactivate the button to make it ready to be clicked again
57
+ 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)
51
58
  editor.stopCommand(cmdOpenSettings) // apparently this is needed to be able to run the command several times
52
59
  })
53
60
  displaySettings(editor, opts, page)
@@ -55,14 +62,20 @@ export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) =>
55
62
  const form = el.querySelector('form')
56
63
  form.onsubmit = event => {
57
64
  event.preventDefault()
65
+ editor.trigger(ClientEvent.SETTINGS_SAVE_START, page)
58
66
  saveSettings(editor, opts, page)
67
+ editor.trigger(ClientEvent.SETTINGS_SAVE_END, page)
59
68
  editor.stopCommand(cmdOpenSettings)
60
69
  }
61
70
  form.querySelector('input')?.focus()
71
+ // Notify other plugins
72
+ editor.trigger(ClientEvent.SETTINGS_OPEN, page)
73
+ // Return the dialog
62
74
  return modal
63
75
  },
64
76
  stop: () => {
65
77
  modal.close()
78
+ editor.trigger(ClientEvent.SETTINGS_CLOSE)
66
79
  },
67
80
  })
68
81
  // add settings to the website
@@ -74,93 +87,117 @@ export const settingsDialog = grapesjs.plugins.add(pluginName, (editor, opts) =>
74
87
  const model = editor.getModel()
75
88
  model.set('settings', data.settings || {})
76
89
  model.set('name', data.name)
77
- updateDom(editor)
78
90
  })
79
- editor.on('page', (e) => {
80
- editor.Canvas.getFrameEl().addEventListener('load', () => {
91
+ // On chrome this is needed to apply the head content to the DOM
92
+ editor.on('storage:end:load', () => {
93
+ setTimeout(() => {
81
94
  updateDom(editor)
82
95
  })
83
96
  })
84
- })
97
+ // Normal way to apply the head content to the DOM
98
+ // fix #1559 Custom <head> code must be reapplied
99
+ editor.on('canvas:frame:load', () => {
100
+ updateDom(editor)
101
+ })
102
+ // When the page changes, update the dom
103
+ editor.on('page', (e) => {
104
+ updateDom(editor)
105
+ })
106
+ headEditor = editor.CodeManager.createViewer({
107
+ readOnly: false,
108
+ codeName: 'htmlmixed',
109
+ lineNumbers: true,
110
+ lineWrapping: true,
111
+ autoFormat: false,
112
+ })
113
+ }
114
+
115
+ export function addSection(section, siteOrPage: 'site' | 'page', position: 'first' | 'last' | number) {
116
+ const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
117
+ if (position === 'first') {
118
+ sections.unshift(section)
119
+ } else if (position === 'last') {
120
+ sections.push(section)
121
+ } else if (typeof position === 'number') {
122
+ sections.splice(position, 0, section)
123
+ } else {
124
+ throw new Error('Invalid position for settings section')
125
+ }
126
+ }
127
+
128
+ export function removeSection(id, siteOrPage: 'site' | 'page') {
129
+ const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
130
+ const index = sections.findIndex(section => section.id === id)
131
+ if(index === -1) throw new Error(`Cannot find section with id ${id}`)
132
+ sections.splice(index, 1)
133
+ }
85
134
 
86
- // Is the model a site or a page?
87
- function isSite(model) { return !!model.getHtml }
88
135
 
89
136
  function displaySettings(editor, config, model = editor.getModel()) {
137
+ // Update the model with the current settings
90
138
  const settings = model.get('settings') || {} as WebsiteSettings
91
139
  model.set('settings', settings)
140
+ // Get the current sections for page or site
141
+ const menuItemsCurrent = isSite(model) ? sectionsSite : sectionsPage
142
+ // Init the current section selection
143
+ let sections = menuItemsCurrent[0]
92
144
  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
145
  <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>
146
+ <div class="silex-help">
147
+ ${isSite(model) ? html`
148
+ <p>Here you can set the name of your website, SEO and social networks sharing data.</p>
149
+ <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>
150
+ ` : html`
151
+ <p>Here you can set the name of your page, SEO and social networks sharing data.</p>
152
+ <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>
153
+ `}
156
154
  </div>
155
+ <section class="silex-sidebar-dialog">
156
+ <aside class="silex-bar">
157
+ <ul class="silex-list silex-list--menu">
158
+ ${menuItemsCurrent.map(item => html`
159
+ <li
160
+ class=${item.id === sections.id ? 'active' : ''}
161
+ @click=${e => {
162
+ e.preventDefault()
163
+ sections = item
164
+ const li = e.target as HTMLElement
165
+ const ul = li.closest('ul')
166
+ const section = li.closest('section')
167
+ const mainItem = section.querySelector(`#settings-${item.id}`)
168
+ // Update active
169
+ Array.from(ul.querySelectorAll('.active')).forEach(el => el.classList.remove('active'))
170
+ li.classList.add('active')
171
+ // Update hidden
172
+ Array.from(section.querySelectorAll('.silex-hideable')).forEach(el => el.classList.add('silex-hidden'))
173
+ mainItem.classList.remove('silex-hidden')
174
+ // This messes up with the save / cancel mechanism
175
+ // displaySettings(editor, config, model)
176
+ // Refresh the code editor just in case it went from hidden to visible
177
+ // This makes it ready to be used when the user clicks on the tab
178
+ headEditor.refresh()
179
+ // Notify other plugins
180
+ editor.trigger(ClientEvent.SETTINGS_SECTION_CHANGE, item.id)
181
+ }}
182
+ >
183
+ ${item.label}
184
+ </li>
185
+ `)}
186
+ </ul>
187
+ </aside>
188
+ <main>
189
+ ${menuItemsCurrent.map(item => item.render(settings, model))}
190
+ </main>
191
+ </section>
157
192
  <footer>
158
193
  <p class="silex-version">Silex ${version}</p>
159
194
  <input class="silex-button" type="button" @click=${e => editor.stopCommand(cmdOpenSettings)} value="Cancel">
160
- <input class="silex-button" type="submit" value="Ok">
195
+ <input class="silex-button" type="submit" value="Apply">
161
196
  </footer>
162
197
  </form>
163
198
  `, el)
199
+ el.querySelector(`#${idCodeWrapper}`)?.appendChild(headEditor.getElement())
200
+ headEditor.setContent(settings.head || '')
164
201
  }
165
202
 
166
203
  function saveSettings(editor, config, model = editor.getModel()) {
@@ -168,13 +205,19 @@ function saveSettings(editor, config, model = editor.getModel()) {
168
205
  const formData = new FormData(form)
169
206
  const data = Array.from(formData as any)
170
207
  .reduce((aggregate, [key, value]) => {
171
- aggregate[key] = value
208
+ // Keep only the values that are set
209
+ if(value !== '') {
210
+ aggregate[key] = value
211
+ }
172
212
  return aggregate
173
213
  }, {}) as {[key: string]: any}
174
- // take the name out to the main model (by design in grapesjs pages)
214
+ // take the name out to the main model (by design in grapesjs pages)
175
215
  const { name, ...settings } = data
176
216
  model.set({
177
- settings,
217
+ settings: {
218
+ ...data,
219
+ head: headEditor.getContent(),
220
+ },
178
221
  name,
179
222
  })
180
223
  // save if auto save is on
@@ -15,12 +15,11 @@
15
15
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import * as grapesjs from 'grapesjs/dist/grapes.min.js'
19
18
  import { cmdAddSymbol } from '@silexlabs/grapesjs-symbols'
20
19
 
21
20
  export const cmdPromptAddSymbol = 'symbol-prompt-add'
22
21
 
23
- export default grapesjs.plugins.add(name, (editor, opts) => {
22
+ export default (editor, opts) => {
24
23
  function getNext(prefix) {
25
24
  let idx = 1
26
25
  while(editor.Symbols.find(s => s.get('label') === prefix + idx)) {
@@ -35,4 +34,4 @@ export default grapesjs.plugins.add(name, (editor, opts) => {
35
34
  editor.runCommand(cmdAddSymbol, { label, icon })
36
35
  }
37
36
  })
38
- })
37
+ }
@@ -0,0 +1,42 @@
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
+ const pasteBtnClass = 'fa fa-fw fa-paste silex-button'
20
+ const disabledClass = ' disabled'
21
+
22
+ export default function(editor) {
23
+ // Add copy/paste buttons in the top bar
24
+ const [copyBtn, pasteBtn] = editor.Panels.addButton('options', [{
25
+ id: 'copy',
26
+ className: 'fa fa-fw fa-copy',
27
+ command: 'core:copy',
28
+ attributes: { title: 'Copy (Ctrl+C)' },
29
+ }, {
30
+ id: 'paste',
31
+ className: pasteBtnClass + disabledClass,
32
+ command: 'core:paste',
33
+ attributes: { title: 'Paste (Ctrl+V)' },
34
+ active: false,
35
+ }])
36
+
37
+ // Disable paste button if clipboard is empty
38
+ editor.EditorModel.on('change:clipboard', (model, value) => {
39
+ const hasClipboard = value && value.length > 0
40
+ pasteBtn.set('className', pasteBtnClass + (hasClipboard ? '' : disabledClass))
41
+ })
42
+ }
@@ -21,7 +21,6 @@
21
21
  *
22
22
  */
23
23
 
24
- import { assetsPublicationTransformer } from './assetUrl'
25
24
  import { ClientConfig } from './config'
26
25
  import { ClientEvent } from './events'
27
26
  import { initEditor, getEditor } from './grapesjs/index'
@@ -29,16 +28,18 @@ import { initEditor, getEditor } from './grapesjs/index'
29
28
  // Expose API to calling app as window.silex
30
29
  export * from './expose'
31
30
 
31
+ /**
32
+ * Expose the config object
33
+ */
34
+ export let config: ClientConfig
35
+
32
36
  /**
33
37
  * Start Silex, called from host HTML page with window.silex.start()
34
38
  */
35
- export async function start(options = {}) {
36
- const config = new ClientConfig()
39
+ export async function start(options = {}): Promise<void> {
40
+ config = new ClientConfig()
37
41
  Object.assign(config, options)
38
42
 
39
- // Add the publication transformers which are used to cleanup assets URLs
40
- config.addPublicationTransformers(assetsPublicationTransformer)
41
-
42
43
  // Config file in root folder which may be generated by the server
43
44
  await config.addPlugin(config.clientConfigUrl, {})
44
45
 
@@ -54,6 +55,7 @@ export async function start(options = {}) {
54
55
  config.initGrapesConfig()
55
56
 
56
57
  // Start grapesjs
58
+ // Here you can change the config before passing it to grapesjs
57
59
  config.emit(ClientEvent.GRAPESJS_START)
58
60
  try{
59
61
  await initEditor(config.grapesJsConfig)
@@ -73,6 +75,9 @@ export async function start(options = {}) {
73
75
  // Init internationalization module
74
76
  editor.I18n.setLocale(config.lang)
75
77
 
78
+ // Add default plugins
79
+ await config.addDefaultPlugins()
80
+
76
81
  // Load the site
77
82
  editor.StorageManager.setAutosave(false)
78
83
  try {
@@ -94,5 +99,7 @@ export async function start(options = {}) {
94
99
  document.querySelector('#gjs').classList.remove('silex-dialog-hide')
95
100
  config.emit(ClientEvent.STARTUP_END, { editor, config })
96
101
  }, 100)
102
+ const um = editor.UndoManager
103
+ um.clear()
97
104
  }
98
105
  }
@@ -10,7 +10,7 @@ import {
10
10
  transformBgImage
11
11
  } from './publication-transformers'
12
12
  import { ClientConfig } from './config'
13
- import GrapesJS, { Component, Editor, ObjectStrings, Page } from 'grapesjs'
13
+ import grapesjs, { Component, Editor, ObjectStrings, Page } from 'grapesjs'
14
14
  import { ClientSideFile, ClientSideFileType, PublicationData } from '../types'
15
15
 
16
16
  describe('publication-transformers', () => {
@@ -23,7 +23,8 @@ describe('publication-transformers', () => {
23
23
 
24
24
  beforeEach(() => {
25
25
  // Initialize your mocks here
26
- editor = GrapesJS.init({
26
+ /* @ts-ignore */
27
+ editor = grapesjs.init({
27
28
  headless: true,
28
29
  storageManager: { autoload: false },
29
30
  })
@@ -136,13 +137,20 @@ describe('publication-transformers', () => {
136
137
  expect(transformPermalink.mock.calls[0][0]).toBe('test.png')
137
138
  expect(transformPermalink.mock.calls[0][1]).toBe('asset')
138
139
  })
140
+
139
141
  it('should transform a style rule with background image', () => {
140
142
  expect(transformBgImage(editor, { 'background-image': 'url(test.png)' })).toEqual({ 'background-image': 'url(test.png)' })
141
143
  const transformPermalink = transformer.transformPermalink as Mock
142
144
  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)' })
145
+ const transformedSrc2 = 'transformed2.png'
146
+ transformPermalink.mockImplementation((url, type) => (url as string).replace('test.png', transformedSrc).replace('test2.png', transformedSrc2))
147
+ expect(transformBgImage(editor, { 'background-image': 'url(test.png)' })).toEqual({ 'background-image': `url(${transformedSrc})` })
148
+ expect(transformBgImage(editor, { 'background-image': 'url("test.png")' })).toEqual({ 'background-image': `url(${transformedSrc})` })
149
+ expect(transformBgImage(editor, {
150
+ 'background-image': 'linear-gradient(to right, rgb(30 75 115 / 100%), url(test.png), linear-gradient(to left, rgb(30 75 115 / 10%), url(test2.png), rgb(255 255 255 / 0%));',
151
+ })).toEqual({
152
+ 'background-image': `linear-gradient(to right, rgb(30 75 115 / 100%), url(${transformedSrc}), linear-gradient(to left, rgb(30 75 115 / 10%), url(${transformedSrc2}), rgb(255 255 255 / 0%));`,
153
+ })
146
154
  })
147
155
 
148
156
  it('should transform permalinks of background images in inline css', () => {