@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.
- package/.dockerignore +5 -0
- package/.editorconfig +14 -0
- package/.eslintrc.json +43 -0
- package/.github/FUNDING.yml +13 -0
- package/.github/workflows/docker-hub-publish.yml +36 -0
- package/.github/workflows/npm-publish.yml +19 -0
- package/.github/workflows/test.yml +26 -0
- package/.nvmrc +1 -0
- package/.silex.js +19 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +108 -0
- package/Dockerfile +15 -0
- package/LICENSE +203 -0
- package/app.json +106 -0
- package/directus.silex.js +7 -0
- package/dist/client/css/admin.css +4350 -0
- package/dist/client/index.html +9 -0
- package/dist/client/js/main.js +282 -0
- package/dist/client/js/main.js.LICENSE.txt +52 -0
- package/dist/server/config.js +50 -0
- package/dist/server/events.js +5 -0
- package/dist/server/index.js +29 -0
- package/dist/server/start.js +18 -0
- package/jest.config.js +10 -0
- package/logos/logo_by_ceubri/silex-icon-2018@200px.png +0 -0
- package/logos/logo_by_ceubri/silex-icon-2018@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white-transp.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.ai +2520 -6
- package/logos/logo_by_ceubri/silex-logo-2018.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.png +0 -0
- package/logos/silexlabs.png +0 -0
- package/package.json +170 -0
- package/public/assets/container.png +0 -0
- package/public/assets/favicon.png +0 -0
- package/public/assets/logo-silex-small.png +0 -0
- package/public/example-config.js +31 -0
- package/public/robots.txt +2 -0
- package/readme.md +51 -0
- package/src/css/_mixins.scss +8 -0
- package/src/css/_variables.scss +33 -0
- package/src/css/dialog.scss +34 -0
- package/src/css/grapesjs.scss +44 -0
- package/src/css/index.scss +22 -0
- package/src/css/loader.scss +10 -0
- package/src/css/page-panel.scss +65 -0
- package/src/css/project-bar.scss +102 -0
- package/src/css/silex.scss +183 -0
- package/src/html/debug.pug +8 -0
- package/src/html/index.pug +39 -0
- package/src/html/templates/includes.pug +2 -0
- package/src/html/templates/workspace.pug +2 -0
- package/src/plugins/AuthPlugin.js +21 -0
- package/src/plugins/Cache.js +11 -0
- package/src/plugins/DefaultBackend.js +113 -0
- package/src/plugins/ExpressPlugin.js +30 -0
- package/src/plugins/HooksPlugin.js +58 -0
- package/src/plugins/PublishPlugin.js +69 -0
- package/src/plugins/SslPlugin.js +66 -0
- package/src/plugins/StaticPlugin.js +48 -0
- package/src/plugins/WebsitePlugin.js +149 -0
- package/src/ts/client/config.ts +21 -0
- package/src/ts/client/expose.ts +4 -0
- package/src/ts/client/grapesjs/blocks.ts +88 -0
- package/src/ts/client/grapesjs/eleventy.ts +21 -0
- package/src/ts/client/grapesjs/index.ts +255 -0
- package/src/ts/client/grapesjs/internal-links.test.ts +87 -0
- package/src/ts/client/grapesjs/internal-links.ts +210 -0
- package/src/ts/client/grapesjs/new-page-dialog.ts +73 -0
- package/src/ts/client/grapesjs/openImport.ts +59 -0
- package/src/ts/client/grapesjs/page-panel.ts +101 -0
- package/src/ts/client/grapesjs/project-bar.ts +92 -0
- package/src/ts/client/grapesjs/publish.ts +386 -0
- package/src/ts/client/grapesjs/rich-text.ts +16 -0
- package/src/ts/client/grapesjs/semantic.ts +96 -0
- package/src/ts/client/grapesjs/settings.ts +190 -0
- package/src/ts/client/grapesjs/symbolDialogs.ts +21 -0
- package/src/ts/client/grapesjs/template.ts +166 -0
- package/src/ts/client/index.ts +38 -0
- package/src/ts/client/utils.ts +9 -0
- package/src/ts/externs.ts +1 -0
- package/src/ts/page.ts +14 -0
- package/src/ts/server/cli.ts +23 -0
- package/src/ts/server/config.ts +62 -0
- package/src/ts/server/events.ts +2 -0
- package/src/ts/server/index.ts +35 -0
- package/src/ts/server/start.ts +18 -0
- package/src/ts/server/utils/BackwardCompat.ts +534 -0
- package/src/ts/types.ts +138 -0
- package/tsconfig-server-debug.json +20 -0
- package/tsconfig-server-release.json +19 -0
- package/tsconfig.jest.json +9 -0
- package/tsconfig.json +27 -0
- package/webpack.config.js +29 -0
- 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
|
+
}
|