@silexlabs/silex 3.0.0-alpha.21

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 (106) hide show
  1. package/.dockerignore +5 -0
  2. package/.editorconfig +14 -0
  3. package/.eslintrc.json +43 -0
  4. package/.github/FUNDING.yml +13 -0
  5. package/.github/workflows/docker-hub-publish.yml +36 -0
  6. package/.github/workflows/npm-publish.yml +19 -0
  7. package/.github/workflows/test.yml +26 -0
  8. package/.nvmrc +1 -0
  9. package/.silex.js +19 -0
  10. package/CODE_OF_CONDUCT.md +128 -0
  11. package/CONTRIBUTING.md +108 -0
  12. package/Dockerfile +15 -0
  13. package/LICENSE +203 -0
  14. package/app.json +106 -0
  15. package/directus.silex.js +7 -0
  16. package/dist/client/css/admin.css +4350 -0
  17. package/dist/client/index.html +9 -0
  18. package/dist/client/js/main.js +282 -0
  19. package/dist/client/js/main.js.LICENSE.txt +52 -0
  20. package/dist/server/config.js +50 -0
  21. package/dist/server/events.js +5 -0
  22. package/dist/server/index.js +29 -0
  23. package/dist/server/start.js +18 -0
  24. package/jest.config.js +10 -0
  25. package/logos/logo_by_ceubri/silex-icon-2018@200px.png +0 -0
  26. package/logos/logo_by_ceubri/silex-icon-2018@4x.png +0 -0
  27. package/logos/logo_by_ceubri/silex-logo-2018-white-transp.pdf +0 -0
  28. package/logos/logo_by_ceubri/silex-logo-2018-white.jpg +0 -0
  29. package/logos/logo_by_ceubri/silex-logo-2018-white.pdf +0 -0
  30. package/logos/logo_by_ceubri/silex-logo-2018-white.png +0 -0
  31. package/logos/logo_by_ceubri/silex-logo-2018-white@0.5x.png +0 -0
  32. package/logos/logo_by_ceubri/silex-logo-2018-white@0.75x.png +0 -0
  33. package/logos/logo_by_ceubri/silex-logo-2018-white@4x.jpg +0 -0
  34. package/logos/logo_by_ceubri/silex-logo-2018-white@4x.png +0 -0
  35. package/logos/logo_by_ceubri/silex-logo-2018.ai +2520 -6
  36. package/logos/logo_by_ceubri/silex-logo-2018.jpg +0 -0
  37. package/logos/logo_by_ceubri/silex-logo-2018.pdf +0 -0
  38. package/logos/logo_by_ceubri/silex-logo-2018.png +0 -0
  39. package/logos/logo_by_ceubri/silex-logo-2018@0.5x.png +0 -0
  40. package/logos/logo_by_ceubri/silex-logo-2018@0.75x.png +0 -0
  41. package/logos/logo_by_ceubri/silex-logo-2018@4x.jpg +0 -0
  42. package/logos/logo_by_ceubri/silex-logo-2018@4x.png +0 -0
  43. package/logos/silexlabs.png +0 -0
  44. package/package.json +170 -0
  45. package/public/assets/container.png +0 -0
  46. package/public/assets/favicon.png +0 -0
  47. package/public/assets/logo-silex-small.png +0 -0
  48. package/public/example-config.js +31 -0
  49. package/public/robots.txt +2 -0
  50. package/readme.md +51 -0
  51. package/src/css/_mixins.scss +8 -0
  52. package/src/css/_variables.scss +33 -0
  53. package/src/css/dialog.scss +34 -0
  54. package/src/css/grapesjs.scss +44 -0
  55. package/src/css/index.scss +22 -0
  56. package/src/css/loader.scss +10 -0
  57. package/src/css/page-panel.scss +65 -0
  58. package/src/css/project-bar.scss +102 -0
  59. package/src/css/silex.scss +183 -0
  60. package/src/html/debug.pug +8 -0
  61. package/src/html/index.pug +39 -0
  62. package/src/html/templates/includes.pug +2 -0
  63. package/src/html/templates/workspace.pug +2 -0
  64. package/src/plugins/AuthPlugin.js +21 -0
  65. package/src/plugins/Cache.js +11 -0
  66. package/src/plugins/DefaultBackend.js +113 -0
  67. package/src/plugins/ExpressPlugin.js +30 -0
  68. package/src/plugins/HooksPlugin.js +58 -0
  69. package/src/plugins/PublishPlugin.js +69 -0
  70. package/src/plugins/SslPlugin.js +66 -0
  71. package/src/plugins/StaticPlugin.js +48 -0
  72. package/src/plugins/WebsitePlugin.js +149 -0
  73. package/src/ts/client/config.ts +21 -0
  74. package/src/ts/client/expose.ts +4 -0
  75. package/src/ts/client/grapesjs/blocks.ts +88 -0
  76. package/src/ts/client/grapesjs/eleventy.ts +21 -0
  77. package/src/ts/client/grapesjs/index.ts +255 -0
  78. package/src/ts/client/grapesjs/internal-links.test.ts +87 -0
  79. package/src/ts/client/grapesjs/internal-links.ts +210 -0
  80. package/src/ts/client/grapesjs/new-page-dialog.ts +73 -0
  81. package/src/ts/client/grapesjs/openImport.ts +59 -0
  82. package/src/ts/client/grapesjs/page-panel.ts +101 -0
  83. package/src/ts/client/grapesjs/project-bar.ts +92 -0
  84. package/src/ts/client/grapesjs/publish.ts +386 -0
  85. package/src/ts/client/grapesjs/rich-text.ts +16 -0
  86. package/src/ts/client/grapesjs/semantic.ts +96 -0
  87. package/src/ts/client/grapesjs/settings.ts +190 -0
  88. package/src/ts/client/grapesjs/symbolDialogs.ts +21 -0
  89. package/src/ts/client/grapesjs/template.ts +166 -0
  90. package/src/ts/client/index.ts +38 -0
  91. package/src/ts/client/utils.ts +9 -0
  92. package/src/ts/externs.ts +1 -0
  93. package/src/ts/page.ts +14 -0
  94. package/src/ts/server/cli.ts +23 -0
  95. package/src/ts/server/config.ts +62 -0
  96. package/src/ts/server/events.ts +2 -0
  97. package/src/ts/server/index.ts +35 -0
  98. package/src/ts/server/start.ts +18 -0
  99. package/src/ts/server/utils/BackwardCompat.ts +534 -0
  100. package/src/ts/types.ts +138 -0
  101. package/tsconfig-server-debug.json +20 -0
  102. package/tsconfig-server-release.json +19 -0
  103. package/tsconfig.jest.json +9 -0
  104. package/tsconfig.json +27 -0
  105. package/webpack.config.js +29 -0
  106. package/webpack.debug.config.js +8 -0
@@ -0,0 +1,210 @@
1
+ import {html, render} from 'lit-html'
2
+ import {map} from 'lit-html/directives/map.js'
3
+ import grapesjs from 'grapesjs/dist/grapes.min.js'
4
+
5
+ import { Page } from '../../types'
6
+ import { getPageLink } from '../../page'
7
+
8
+ // constants
9
+ const pluginName = 'internal-links'
10
+ const options: grapesjs.SelectOption = [
11
+ { id: '', name: '-' },
12
+ { id: 'url', name: 'URL' },
13
+ { id: 'email', name: 'Email' },
14
+ { id: 'page', name: 'Page' },
15
+ ]
16
+
17
+ // plugin code
18
+ export const internalLinksPlugin = grapesjs.plugins.add(pluginName, (editor, opts) => {
19
+ // update links when a page link changes
20
+ function onAll(cbk) {
21
+ editor.Pages.getAll()
22
+ .forEach(page => {
23
+ page.getMainComponent()
24
+ .onAll(cbk)
25
+ })
26
+ }
27
+ editor.on('page', ({ event, page }) => {
28
+ if(!page) return // fixes UT
29
+ switch (event) {
30
+ case 'change:name':
31
+ // update all links to this page
32
+ onAll(component => {
33
+ if(component.getAttributes().href === getPageLink(page.previous('name'))) {
34
+ component.setAttributes({
35
+ href: getPageLink(page.get('name')),
36
+ })
37
+ }
38
+ })
39
+ break
40
+ case 'remove':
41
+ // mark all links as issues
42
+ const issues = []
43
+ onAll(component => {
44
+ console.log(component.getAttributes().href, getPageLink(page.previous('name')), page.previous('name'))
45
+ if(component.getAttributes().href === getPageLink(page.previous('name'))) {
46
+ issues.push(component)
47
+ }
48
+ })
49
+ opts?.onError(issues)
50
+ break
51
+ }
52
+ })
53
+ // utility functions to render the UI
54
+ function getUi(type, href) {
55
+ switch (type) {
56
+ case 'email':
57
+ const mailTo = href.replace('mailto:', '').split('?')
58
+ const email = mailTo[0]
59
+ const params = (mailTo[1] || '').split('&').reduce((acc, item) => {
60
+ const items = item.split('=')
61
+ acc[items[0]] = items[1]
62
+ return acc
63
+ }, {})
64
+ return html`<div class="href-next__email-inputs">
65
+ <label for="href-next__email" class="gjs-one-bg silex-label">Email address</label>
66
+ <input class="href-next__email" placeholder="Insert email" type="email" value=${email || ''}/>
67
+ <label for="href-next__email-subject" class="gjs-one-bg silex-label">Subject</label>
68
+ <input class="href-next__email-subject" placeholder="Insert subject" value=${ params.subject || '' }/>
69
+ </div>`
70
+ case 'page':
71
+ return html`<div class="href-next__page-inputs">
72
+ <label for="href-next__page" class="gjs-one-bg silex-label">Page name</label>
73
+ <select id="href-next__page" class="href-next__page">
74
+ ${
75
+ map<grapesjs.Page>(editor.Pages.getAll(), page => html`<option
76
+ ?selected=${getPageLink(page.getName()) === href}
77
+ value=${getPageLink(page.getName())}>
78
+ ${page.getName() || 'Main'}
79
+ </option>`)
80
+ }
81
+ </select>
82
+ </div>`
83
+ case 'url':
84
+ return html`<div class="href-next__url-inputs">
85
+ <label for="href-next__url" class="gjs-one-bg silex-label">URL</label>
86
+ <input class="href-next__url" placeholder="Insert URL" type="url" value=${ href }/>
87
+ </div>`
88
+ default:
89
+ return ''
90
+ }
91
+ }
92
+ function getDefaultHref(type) {
93
+ switch(type) {
94
+ case 'email': return 'mailto:'
95
+ case 'page': return getPageLink()
96
+ case 'url': return 'https://'
97
+ }
98
+ }
99
+ function doRender(el: HTMLElement, href = '') {
100
+ const type = getType(href)
101
+ const ui = getUi(type, href)
102
+
103
+ render(html`
104
+ <label for="href-next__type" class="gjs-one-bg silex-label">Type</label>
105
+ <select id="href-next__type" class="href-next__type" @change=${event => doRender(el, getDefaultHref(event.target.value))}>
106
+ ${map<grapesjs.SelectOption>(options, opt => html`
107
+ <option value="${opt.id}" ?selected=${type === opt.id}>${opt.name}</option>
108
+ `)}
109
+ </select>
110
+ ${ ui }
111
+ `, el)
112
+ }
113
+ function getType(href: string) {
114
+ if (href.indexOf('mailto:') === 0) {
115
+ return 'email'
116
+ } else if(href.indexOf('./') === 0){
117
+ return 'page'
118
+ } else if(href){
119
+ return 'url'
120
+ }
121
+ return ''
122
+ }
123
+ function doRenderCurrent(el) {
124
+ doRender(el, editor.getSelected()?.getAttributes().href || '')
125
+ }
126
+
127
+ // Add the new trait to all component types
128
+ editor.DomComponents.getTypes().map(type => {
129
+ editor.DomComponents.addType(type.id, {
130
+ model: {
131
+ defaults: {
132
+ traits: [
133
+ // Keep the type original traits
134
+ ...editor.DomComponents.getType(type.id).model.prototype.defaults.traits
135
+ // Filter original href trait
136
+ .filter(trait => trait.name ? trait.name !== 'href' : trait !== 'href'),
137
+ // Add the new trait
138
+ {
139
+ label: 'Link',
140
+ type: 'href-next',
141
+ name: 'href',
142
+ },
143
+ ]
144
+ }
145
+ }
146
+ })
147
+ })
148
+
149
+ editor.TraitManager.addType('href-next', {
150
+ createInput({ trait }) {
151
+ // Create a new element container and add some content
152
+ const el = document.createElement('div')
153
+ // update the UI when a page is added/renamed/removed
154
+ editor.on('page', () => doRenderCurrent(el))
155
+ doRenderCurrent(el)
156
+ // this will be the element passed to onEvent and onUpdate
157
+ return el
158
+ },
159
+ // Update the component based on UI changes
160
+ // `elInput` is the result HTMLElement you get from `createInput`
161
+ onEvent({ elInput, component, event }) {
162
+ const inputType = elInput.querySelector('.href-next__type')
163
+ let href = ''
164
+ // Compute the new HREF value
165
+ switch (inputType.value) {
166
+ case 'page':
167
+ const valPage = elInput.querySelector('.href-next__page')?.value
168
+ href = valPage || getDefaultHref('page')
169
+ break
170
+ case 'url':
171
+ const valUrl = elInput.querySelector('.href-next__url')?.value
172
+ href = valUrl || getDefaultHref('url')
173
+ break
174
+ case 'email':
175
+ const valEmail = elInput.querySelector('.href-next__email')?.value
176
+ const valSubj = elInput.querySelector('.href-next__email-subject')?.value
177
+ href = valEmail ? `mailto:${valEmail}${valSubj ? `?subject=${valSubj}` : ''}` : getDefaultHref('email')
178
+ break
179
+ default:
180
+ href = ''
181
+ }
182
+
183
+ // Store the new HREF value
184
+ component.addAttributes({ href })
185
+
186
+ // Handle the tag name
187
+ if(href === '') {
188
+ // Not a link
189
+ if(component.get('tagName').toUpperCase() === 'A'){
190
+ // Retrieve the original stored value
191
+ const original = component.get('originalTagName') ?? 'DIV'
192
+ // Case of the inline A tags inserted by the text bar
193
+ const value = original.toUpperCase() === 'A' ? 'SPAN' : original
194
+ component.set('tagName', value)
195
+ }
196
+ } else {
197
+ // Link
198
+ if(component.get('tagName').toUpperCase() !== 'A') {
199
+ component.set('originalTagName', component.get('tagName'))
200
+ component.set('tagName', 'A')
201
+ }
202
+ }
203
+ },
204
+ // Update UI on the component change
205
+ onUpdate({ elInput, component }) {
206
+ const href = component.getAttributes().href || ''
207
+ doRender(elInput, href)
208
+ },
209
+ })
210
+ })
@@ -0,0 +1,73 @@
1
+ import * as grapesjs from 'grapesjs/dist/grapes.min.js'
2
+ import {html, render} from 'lit-html'
3
+ import {live} from 'lit-html/directives/live.js'
4
+
5
+
6
+ const name = 'new-page-dialog'
7
+ const el = document.createElement('div')
8
+ let modal
9
+
10
+ export const cmdOpenNewPageDialog = 'new-page-dialog'
11
+
12
+ export const newPageDialog = grapesjs.plugins.add(name, (editor, opts) => {
13
+ editor.Commands.add(cmdOpenNewPageDialog, {
14
+ run: (_, sender, {page}) => {
15
+ modal = editor.Modal.open({
16
+ title: 'New Page',
17
+ content: '',
18
+ attributes: { class: 'new-page-dialog' },
19
+ })
20
+ .onceClose(() => {
21
+ editor.stopCommand(cmdOpenNewPageDialog) // apparently this is needed to be able to run the command several times
22
+ })
23
+ displayDialog(editor, opts, page)
24
+ modal.setContent(el)
25
+ const form = el.querySelector('form')
26
+ form.onsubmit = event => {
27
+ event.preventDefault()
28
+ saveDialog(editor, opts, page)
29
+ editor.stopCommand(cmdOpenNewPageDialog)
30
+ }
31
+ form.querySelector('input')?.focus()
32
+ return modal
33
+ },
34
+ stop: () => {
35
+ modal.close()
36
+ // el.innerHTML = ''
37
+ },
38
+ })
39
+ })
40
+
41
+ function onImport(editor, page) {
42
+ editor.Pages.select(page)
43
+ editor.runCommand('gjs-open-import-webpage')
44
+ }
45
+
46
+ function displayDialog(editor, config, page) {
47
+ render(html`
48
+ <form class="silex-form">
49
+ <label class="silex-form__element">
50
+ <h3>Name</h3>
51
+ <input type="text" name="name" .value=${live(page.getName() || '')}/>
52
+ </label>
53
+ <footer>
54
+ <input class="silex-button" type="button" @click=${e => onImport(editor, page)} value="Import from website">
55
+ <input class="silex-button" type="button" @click=${e => editor.stopCommand(cmdOpenNewPageDialog)} value="Cancel">
56
+ <input class="silex-button" type="submit" value="Ok">
57
+ </footer>
58
+ </form>
59
+ `, el)
60
+ }
61
+
62
+ function saveDialog(editor, config, page) {
63
+ const form = el.querySelector('form')
64
+ const formData = new FormData(form)
65
+ const data = Array.from(formData)
66
+ .reduce((aggregate, [key, value]) => {
67
+ aggregate[key] = value
68
+ return aggregate
69
+ }, {})
70
+ page.set(data)
71
+ // save if auto save is on
72
+ editor.getModel().set('changesCount', editor.getDirtyCount() + 1)
73
+ }
@@ -0,0 +1,59 @@
1
+ // from https://github.com/artf/grapesjs-preset-webpage/blob/3e5a9e12998c9a32b6f1199953084163678e6c17/src/commands/openImport.js
2
+ export default function openImport(editor, config) {
3
+ const pfx = editor.getConfig('stylePrefix')
4
+ const modal = editor.Modal
5
+ const codeViewer = editor.CodeManager.getViewer('CodeMirror').clone()
6
+ const container = document.createElement('div')
7
+ const importLabel = config.modalImportLabel
8
+ const importCnt = config.modalImportContent
9
+ let viewerEditor = codeViewer.editor
10
+
11
+ // Init import button
12
+ const btnImp = document.createElement('button')
13
+ btnImp.type = 'button'
14
+ btnImp.innerHTML = config.modalImportButton
15
+ btnImp.className = `${pfx}btn-prim ${pfx}btn-import`
16
+ btnImp.onclick = e => {
17
+ editor.setComponents(viewerEditor.getValue().trim())
18
+ modal.close()
19
+ }
20
+
21
+ // Init code viewer
22
+ codeViewer.set({ ...{
23
+ codeName: 'htmlmixed',
24
+ theme: 'hopscotch',
25
+ readOnly: 0
26
+ }, ...config.importViewerOptions})
27
+
28
+ return {
29
+ run(_) {
30
+ if (!viewerEditor) {
31
+ const txtarea = document.createElement('textarea')
32
+
33
+ if (importLabel) {
34
+ const labelEl = document.createElement('div')
35
+ labelEl.className = `${pfx}import-label`
36
+ labelEl.innerHTML = importLabel
37
+ container.appendChild(labelEl)
38
+ }
39
+
40
+ container.appendChild(txtarea)
41
+ container.appendChild(btnImp)
42
+ codeViewer.init(txtarea)
43
+ viewerEditor = codeViewer.editor
44
+ }
45
+
46
+ modal.setTitle(config.modalImportTitle)
47
+ modal.setContent(container)
48
+ const cnt = typeof importCnt === 'function' ? importCnt(editor) : importCnt
49
+ codeViewer.setContent(cnt || '')
50
+ modal.open().getModel()
51
+ .once('change:open', () => editor.stopCommand(this.id))
52
+ viewerEditor.refresh()
53
+ },
54
+
55
+ stop() {
56
+ modal.close()
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,101 @@
1
+ import * as grapesjs from 'grapesjs/dist/grapes.min.js'
2
+ import {html, render} from 'lit-html'
3
+
4
+ const pluginName = 'page-panel'
5
+ let open
6
+
7
+ export const cmdTogglePages = 'pages:open-panel'
8
+ export const cmdAddPage = 'pages:add'
9
+
10
+ function selectPage(editor, page) {
11
+ editor.Pages.select(page)
12
+ }
13
+ function addPage(editor, config) {
14
+ const pages = editor.Pages.getAll()
15
+ let idx = 1
16
+ const newPageName = config.newPageName || 'New page'
17
+ let pageName = newPageName
18
+ while(pages.find(p => p.getName() === pageName)) {
19
+ pageName = `${newPageName} ${idx++}`
20
+ }
21
+ const page = editor.Pages.add({ name: pageName })
22
+ editor.runCommand(config.cmdOpenNewPageDialog, {page})
23
+ }
24
+
25
+ function removePage(editor, page) {
26
+ if(editor.Pages.getAll().length === 1) {
27
+ console.error('can not delete the only page')
28
+ } else {
29
+ const isMain = page.get('type') === 'main'
30
+ const isSelected = editor.Pages.getSelected() === page
31
+ editor.Pages.remove(page.id)
32
+ const firstPage = editor.Pages.getAll()[0]
33
+ if(isMain) firstPage.set('type', 'main')
34
+ if(isSelected) selectPage(editor, firstPage)
35
+ }
36
+ }
37
+
38
+ function settingsPage(editor, config, page) {
39
+ editor.runCommand(config.cmdOpenSettings, {page})
40
+ }
41
+
42
+ function renderPages(editor, config) {
43
+ const pages = editor.Pages.getAll()
44
+ const selected = editor.Pages.getSelected()
45
+ const getPageFromEvent = (e) => {
46
+ const el = e.target.hasAttribute('data-page-id') ? e.target : e.target.parentNode
47
+ e.stopPropagation()
48
+ return editor.Pages.get(el.getAttribute('data-page-id'))
49
+ }
50
+ return html`<section class="pages">
51
+ <main class="pages__main ${pages.length === 1 ? 'pages__single-page' : ''}">
52
+ <div class="pages__list">
53
+ ${ pages.map(page => {
54
+ const name = page.getName() || page.attributes.type
55
+ // keep the same structure as the layers panel
56
+ return html`
57
+ <div class="pages__page ${selected === page ? 'pages__page-selected' : ''}" data-page-id=${page.id} @click=${e => selectPage(editor, getPageFromEvent(e))}>
58
+ <div class="pages__page-name">
59
+ ${ name }
60
+ </div>
61
+ <i class="pages__icon pages__remove-btn fa fa-trash" @click=${e => removePage(editor, getPageFromEvent(e))}></i>
62
+ <i class="pages__icon fa fa-cog" @click=${e => settingsPage(editor, config, getPageFromEvent(e))}></i>
63
+ </div>
64
+ </div>
65
+ `
66
+ })}
67
+ </div>
68
+ ${pages.length ? '' : html`<div class="flex-row">
69
+ No page yet.
70
+ </div>
71
+ `}
72
+ </main></section>`
73
+ }
74
+
75
+ export const pagePanelPlugin = grapesjs.plugins.add(pluginName, (editor, opts) => {
76
+ // create wrapper
77
+ const el = document.createElement('div')
78
+ el.classList.add('pages__wrapper')
79
+ // update
80
+ const doRender = () => render(renderPages(editor, opts), el)
81
+ editor.on('page', () => {
82
+ doRender()
83
+ })
84
+ editor.on('load', () => {
85
+ open = false
86
+ document.querySelector(opts.appendTo)
87
+ .appendChild(el)
88
+ doRender()
89
+ // click anywhere close it
90
+ // const close = (event) => {
91
+ // if(open) {
92
+ // editor.stopCommand(cmdTogglePages)
93
+ // //event.preventDefault()
94
+ // }
95
+ // }
96
+ // document.addEventListener('mousedown', close)
97
+ // add command to add pages
98
+ editor.Commands.add(cmdAddPage, () => addPage(editor, opts))
99
+ })
100
+ })
101
+
@@ -0,0 +1,92 @@
1
+ import {html, render} from 'lit-html'
2
+
3
+ const panelId = 'project-bar-panel'
4
+ const containerPanelId = 'project-bar-container'
5
+
6
+ export const projectBarPlugin = (editor, opts) => {
7
+ // create the panels container for all panels in grapesjs
8
+ const containerPanel = editor.Panels.addPanel({
9
+ id: containerPanelId,
10
+ visible : false,
11
+ })
12
+ // create the project bar panel in grapesjs
13
+ const projectBarPanel = editor.Panels.addPanel({
14
+ id: panelId,
15
+ buttons: opts.panels,
16
+ visible : true,
17
+ })
18
+ const panelsEl = opts.panels.map(panel => {
19
+ const el = document.createElement('div')
20
+
21
+ // create container for panel
22
+ if(panel.attributes.containerClassName) {
23
+ el.classList.add('project-bar__panel', panel.attributes.containerClassName, 'gjs-hidden')
24
+
25
+ // add header
26
+ if(panel.attributes.title) {
27
+ render(html`
28
+ <header class="project-bar__panel-header">
29
+ <h3 class="project-bar__panel-header-title">${ panel.attributes.title }</h3>
30
+ ${ panel.buttons?.map(button => {
31
+ return html`
32
+ <div
33
+ class="project-bar__panel-header-button ${ button.className }"
34
+ @click=${e => editor.runCommand(button.command)}
35
+ ><span>${ button.text }</span></div>
36
+ `
37
+ }) }
38
+ </header>
39
+ `, el)
40
+ }
41
+
42
+ // temporarily attach it to the body
43
+ // this lets the block manager and other plugins attach to their container
44
+ document.body.appendChild(el)
45
+
46
+ // on load attach the panels to the main container
47
+ // this is when the main containerPanel has an element
48
+ editor.on('load', () => {
49
+ const containerPanelEl = containerPanel.view.el
50
+ containerPanelEl.appendChild(el)
51
+ })
52
+ }
53
+
54
+ // commands for show / hide panels
55
+ editor.Commands.add(panel.command, {
56
+ run() {
57
+ if(panel.link) {
58
+ window.open(panel.link)
59
+ }
60
+ if(panel.attributes.containerClassName) {
61
+ containerPanel.set('visible', true)
62
+ el.classList.remove('gjs-hidden')
63
+ editor.Canvas.getFrameEl().classList.add('silex-squeeze-left')
64
+ }
65
+ },
66
+ stop() {
67
+ if(panel.attributes.containerClassName) {
68
+ containerPanel.set('visible', false)
69
+ el.classList.add('gjs-hidden')
70
+ editor.Canvas.getFrameEl().classList.remove('silex-squeeze-left')
71
+ }
72
+ },
73
+ })
74
+ })
75
+ function updateSqueez() {
76
+ const containerPanelEl = containerPanel.view.el
77
+ const iframe = editor.Canvas.getFrameEl()
78
+ iframe.classList.remove('enable-squeeze')
79
+ setTimeout(() => {
80
+ const left = iframe.getClientRects()[0]?.left
81
+ const right = containerPanelEl.getClientRects()[0]?.right
82
+ if(left < right || !right) iframe.classList.add('enable-squeeze')
83
+ else iframe.classList.remove('enable-squeeze')
84
+ }, 400) // More than the transition duration
85
+ // make sure the squeez corresponds to the state (reset when change page)
86
+ if(containerPanel.get('visible')) iframe.classList.add('silex-squeeze-left')
87
+ else iframe.classList.remove('silex-squeeze-left')
88
+ }
89
+ editor.on('load device:select page', () => {
90
+ updateSqueez()
91
+ })
92
+ }