@silexlabs/silex 3.5.0 → 3.5.1-2
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/.silex.js +0 -3
- package/dist/client/client/config.d.ts +8 -0
- package/dist/client/client/grapesjs/PublicationManager.d.ts +43 -0
- package/dist/client/client/grapesjs/cms/DataSource.d.ts +43 -0
- package/dist/client/client/grapesjs/cms/blocks.d.ts +2 -0
- package/dist/client/client/grapesjs/cms/buttons.d.ts +2 -0
- package/dist/client/client/grapesjs/cms/collection-pages.d.ts +4 -0
- package/dist/client/client/grapesjs/cms/config.d.ts +9 -0
- package/dist/client/client/grapesjs/cms/filters.d.ts +3 -0
- package/dist/client/client/grapesjs/cms/index.d.ts +44 -0
- package/dist/client/client/grapesjs/cms/liquid.d.ts +59 -0
- package/dist/client/client/grapesjs/cms/page-settings.d.ts +6 -0
- package/dist/client/client/grapesjs/cms/publication.d.ts +60 -0
- package/dist/client/client/grapesjs/cms/site-settings.d.ts +3 -0
- package/dist/client/client/grapesjs/cms/states.d.ts +8 -0
- package/dist/client/client/grapesjs/cms/traits.d.ts +8 -0
- package/dist/client/client/grapesjs/settings.d.ts +12 -3
- package/dist/client/client/grapesjs/view-buttons.d.ts +2 -1
- package/dist/client/client/utils.d.ts +1 -0
- package/dist/client/constants.d.ts +2 -1
- package/dist/client/css/admin.css +117 -19
- package/dist/client/index.html +2 -2
- package/dist/client/js/main.js +1974 -267
- package/dist/client/types.d.ts +3 -1
- package/dist/plugins/client/plugins/client/template.js +1 -1
- package/dist/plugins/client/plugins/client/template.js.map +1 -1
- package/dist/plugins/client/types.d.ts +3 -1
- package/dist/plugins/client/types.js +11 -8
- package/dist/plugins/client/types.js.map +1 -1
- package/dist/plugins/server/constants.d.ts +2 -1
- package/dist/plugins/server/constants.js +3 -2
- package/dist/plugins/server/constants.js.map +1 -1
- package/dist/plugins/server/plugins/server/FtpConnector.js +15 -20
- package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -1
- package/dist/plugins/server/plugins/server/GitlabConnector.js +34 -7
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
- package/dist/plugins/server/server/connectors/FsStorage.js +16 -20
- package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -1
- package/dist/plugins/server/server/utils/websiteDataSerialization.d.ts +4 -0
- package/dist/plugins/server/server/utils/websiteDataSerialization.js +21 -12
- package/dist/plugins/server/server/utils/websiteDataSerialization.js.map +1 -1
- package/dist/plugins/server/types.d.ts +3 -1
- package/dist/plugins/server/types.js +12 -9
- package/dist/plugins/server/types.js.map +1 -1
- package/dist/server/constants.js +3 -2
- package/dist/server/server/connectors/FsStorage.js +14 -18
- package/dist/server/server/utils/websiteDataSerialization.js +16 -11
- package/dist/server/types.js +6 -2
- package/package.json +16 -11
- package/src/css/_variables.scss +2 -1
- package/src/css/grapesjs-plugins.scss +31 -0
- package/src/css/grapesjs.scss +20 -0
- package/src/css/page-panel.scss +25 -6
- package/src/css/silex.scss +64 -5
- package/src/ts/client/config.ts +23 -0
- package/src/ts/client/grapesjs/PublicationManager.ts +159 -1
- package/src/ts/client/grapesjs/breadcrumbs.ts +3 -2
- package/src/ts/client/grapesjs/cms/DataSource.ts +333 -0
- package/src/ts/client/grapesjs/cms/blocks.ts +124 -0
- package/src/ts/client/grapesjs/cms/buttons.ts +163 -0
- package/src/ts/client/grapesjs/cms/collection-pages.ts +214 -0
- package/src/ts/client/grapesjs/cms/config.ts +47 -0
- package/src/ts/client/grapesjs/cms/filters.ts +53 -0
- package/src/ts/client/grapesjs/cms/index.ts +130 -0
- package/src/ts/client/grapesjs/cms/liquid.mock.ts +181 -0
- package/src/ts/client/grapesjs/cms/liquid.test.ts +239 -0
- package/src/ts/client/grapesjs/cms/liquid.ts +325 -0
- package/src/ts/client/grapesjs/cms/page-settings.ts +285 -0
- package/src/ts/client/grapesjs/cms/publication.test.ts +300 -0
- package/src/ts/client/grapesjs/cms/publication.ts +708 -0
- package/src/ts/client/grapesjs/cms/site-settings.ts +79 -0
- package/src/ts/client/grapesjs/cms/states.ts +125 -0
- package/src/ts/client/grapesjs/cms/traits.ts +95 -0
- package/src/ts/client/grapesjs/css-props.ts +15 -16
- package/src/ts/client/grapesjs/flex.ts +59 -39
- package/src/ts/client/grapesjs/img.ts +1 -1
- package/src/ts/client/grapesjs/index.ts +77 -4
- package/src/ts/client/grapesjs/internal-links.ts +4 -2
- package/src/ts/client/grapesjs/page-panel.ts +126 -11
- package/src/ts/client/grapesjs/sectors.ts +18 -10
- package/src/ts/client/grapesjs/semantic.ts +4 -0
- package/src/ts/client/grapesjs/settings.ts +252 -101
- package/src/ts/client/grapesjs/storage.ts +12 -3
- package/src/ts/client/grapesjs/view-buttons.ts +39 -3
- package/src/ts/client/utils.ts +15 -2
- package/src/ts/constants.ts +2 -1
- package/src/ts/plugins/client/template.ts +1 -1
- package/src/ts/plugins/server/FtpConnector.test.ts +1 -0
- package/src/ts/plugins/server/FtpConnector.ts +20 -23
- package/src/ts/plugins/server/GitlabConnector.ts +40 -10
- package/src/ts/server/connectors/FsStorage.test.ts +3 -2
- package/src/ts/server/connectors/FsStorage.ts +21 -23
- package/src/ts/server/utils/websiteDataSerialization.test.ts +45 -13
- package/src/ts/server/utils/websiteDataSerialization.ts +23 -14
- package/src/ts/types.ts +6 -2
|
@@ -22,69 +22,221 @@ import {defaultSections, idCodeWrapper, isSite, SettingsSection} from './setting
|
|
|
22
22
|
* @fileoverview This file contains the settings dialog. The config API lets plugins add sections to the settings dialog.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { WebsiteSettings } from '../../types'
|
|
25
|
+
import { WebsiteData, WebsiteSettings } from '../../types'
|
|
26
26
|
import { ClientEvent } from '../events'
|
|
27
27
|
import { SILEX_VERSION } from '../../constants'
|
|
28
|
+
import { Button, Editor } from 'grapesjs'
|
|
28
29
|
|
|
29
|
-
const sectionsSite = [...defaultSections]
|
|
30
|
-
const sectionsPage = [...defaultSections]
|
|
30
|
+
const sectionsSite: SettingsSection[] = [...defaultSections]
|
|
31
|
+
const sectionsPage: SettingsSection[] = [...defaultSections]
|
|
31
32
|
|
|
32
|
-
const el = document.createElement('div')
|
|
33
|
-
let modal
|
|
33
|
+
const el: HTMLDivElement = document.createElement('div')
|
|
34
|
+
let modal: any | undefined // the modal var should be of type ModalModule, not Modal, but it is not exported from grapesjs
|
|
35
|
+
let settingsState: {
|
|
36
|
+
isOpen: boolean
|
|
37
|
+
title: string
|
|
38
|
+
page: unknown
|
|
39
|
+
sectionId?: string
|
|
40
|
+
sender?: Button
|
|
41
|
+
} | null = null
|
|
42
|
+
let customSettingsDialog: HTMLDivElement | null = null
|
|
34
43
|
|
|
35
44
|
export const cmdOpenSettings = 'open-settings'
|
|
45
|
+
export const cmdAddSection = 'settings:add-section'
|
|
46
|
+
export const cmdRemoveSection = 'settings:remove-section'
|
|
47
|
+
|
|
48
|
+
export interface AddSectionOption {
|
|
49
|
+
section: SettingsSection,
|
|
50
|
+
siteOrPage: 'site' | 'page',
|
|
51
|
+
position: 'first' | 'last' | number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let headEditor: ReturnType<Editor['CodeManager']['createViewer']> | null = null
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
function createCustomSettingsDialog(editor: Editor, opts: Record<string, unknown>, page: unknown, sectionId?: string): HTMLDivElement {
|
|
58
|
+
const dialog = document.createElement('div')
|
|
59
|
+
dialog.className = 'settings-dialog gjs-two-color'
|
|
60
|
+
dialog.innerHTML = `
|
|
61
|
+
<div class="settings-content" role="dialog" aria-modal="true" aria-labelledby="settings-title">
|
|
62
|
+
<div class="settings-header">
|
|
63
|
+
<h3 id="settings-title">${page ? 'Page settings' : 'Site Settings'}</h3>
|
|
64
|
+
<button type="button" class="settings-close" title="Close" aria-label="Close settings">×</button>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="settings-body"></div>
|
|
67
|
+
</div>
|
|
68
|
+
`
|
|
69
|
+
|
|
70
|
+
// Handle close button
|
|
71
|
+
const closeButton = dialog.querySelector('.settings-close') as HTMLButtonElement
|
|
72
|
+
closeButton.addEventListener('click', () => {
|
|
73
|
+
closeCustomSettingsDialog(editor)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Handle keyboard shortcuts
|
|
77
|
+
dialog.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
78
|
+
// Close on Escape
|
|
79
|
+
if (e.key === 'Escape') {
|
|
80
|
+
e.preventDefault()
|
|
81
|
+
closeCustomSettingsDialog(editor)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Save on Ctrl/Cmd + Enter
|
|
86
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
87
|
+
e.preventDefault()
|
|
88
|
+
const form = dialog.querySelector('form') as HTMLFormElement
|
|
89
|
+
if (form) {
|
|
90
|
+
form.requestSubmit()
|
|
91
|
+
}
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle Alt+C (Cancel) and Alt+A (Apply)
|
|
96
|
+
if (e.altKey) {
|
|
97
|
+
if (e.key.toLowerCase() === 'c') {
|
|
98
|
+
e.preventDefault()
|
|
99
|
+
closeCustomSettingsDialog(editor)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if (e.key.toLowerCase() === 'a') {
|
|
103
|
+
e.preventDefault()
|
|
104
|
+
const form = dialog.querySelector('form') as HTMLFormElement
|
|
105
|
+
if (form) {
|
|
106
|
+
form.requestSubmit()
|
|
107
|
+
}
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Add content
|
|
114
|
+
displaySettings(editor, opts, page, sectionId)
|
|
115
|
+
const body = dialog.querySelector('.settings-body') as HTMLDivElement
|
|
116
|
+
body.appendChild(el)
|
|
117
|
+
|
|
118
|
+
// Initialize focus when dialog opens
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
const firstTab = dialog.querySelector('.silex-list--menu li[role="button"]') as HTMLElement
|
|
121
|
+
if (firstTab) {
|
|
122
|
+
firstTab.focus()
|
|
123
|
+
}
|
|
124
|
+
}, 100)
|
|
125
|
+
|
|
126
|
+
// Handle form submission
|
|
127
|
+
const form = el.querySelector('form') as HTMLFormElement
|
|
128
|
+
form.onsubmit = (event: Event) => {
|
|
129
|
+
event.preventDefault()
|
|
130
|
+
editor.trigger(ClientEvent.SETTINGS_SAVE_START, page)
|
|
131
|
+
saveSettings(editor, opts, page)
|
|
132
|
+
editor.trigger(ClientEvent.SETTINGS_SAVE_END, page)
|
|
133
|
+
closeCustomSettingsDialog(editor, false)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return dialog
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function closeCustomSettingsDialog(editor: Editor, fromCommand = false) {
|
|
140
|
+
if (customSettingsDialog) {
|
|
141
|
+
customSettingsDialog.remove()
|
|
142
|
+
customSettingsDialog = null
|
|
143
|
+
}
|
|
144
|
+
settingsState?.sender?.set && settingsState.sender.set('active', 0)
|
|
145
|
+
|
|
146
|
+
// Only call stopCommand if not already being called from the command's stop method
|
|
147
|
+
if (!fromCommand) {
|
|
148
|
+
editor.stopCommand(cmdOpenSettings)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
settingsState = null
|
|
152
|
+
editor.trigger(ClientEvent.SETTINGS_CLOSE)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function reopenSettingsModal(editor: Editor, opts: Record<string, unknown>) {
|
|
156
|
+
if (!settingsState) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
modal = editor.Modal.open({
|
|
161
|
+
title: settingsState.title,
|
|
162
|
+
content: '',
|
|
163
|
+
attributes: { class: 'settings-dialog' },
|
|
164
|
+
})
|
|
165
|
+
.onceClose(() => {
|
|
166
|
+
settingsState?.sender?.set && settingsState.sender.set('active', 0)
|
|
167
|
+
editor.stopCommand(cmdOpenSettings)
|
|
168
|
+
settingsState = null
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
displaySettings(editor, opts, settingsState.page, settingsState.sectionId)
|
|
172
|
+
modal.setContent(el)
|
|
173
|
+
|
|
174
|
+
const form = el.querySelector('form') as HTMLFormElement
|
|
175
|
+
form.onsubmit = (event: Event) => {
|
|
176
|
+
event.preventDefault()
|
|
177
|
+
editor.trigger(ClientEvent.SETTINGS_SAVE_START, settingsState?.page)
|
|
178
|
+
saveSettings(editor, opts, settingsState?.page)
|
|
179
|
+
editor.trigger(ClientEvent.SETTINGS_SAVE_END, settingsState?.page)
|
|
180
|
+
editor.stopCommand(cmdOpenSettings)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const settingsDialog = (
|
|
185
|
+
editor: Editor,
|
|
186
|
+
opts: Record<string, unknown>
|
|
187
|
+
): void => {
|
|
188
|
+
// No need to override Modal system - we use custom dialog
|
|
36
189
|
|
|
37
|
-
let headEditor = null
|
|
38
|
-
export const settingsDialog = (editor, opts) => {
|
|
39
190
|
editor.Commands.add(cmdOpenSettings, {
|
|
40
|
-
run: (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
event.preventDefault()
|
|
55
|
-
editor.trigger(ClientEvent.SETTINGS_SAVE_START, page)
|
|
56
|
-
saveSettings(editor, opts, page)
|
|
57
|
-
editor.trigger(ClientEvent.SETTINGS_SAVE_END, page)
|
|
58
|
-
editor.stopCommand(cmdOpenSettings)
|
|
191
|
+
run: (
|
|
192
|
+
_: Editor,
|
|
193
|
+
sender: Button,
|
|
194
|
+
{page, sectionId}: {page?: unknown, sectionId?: string}
|
|
195
|
+
) => {
|
|
196
|
+
const title = page ? 'Page settings' : 'Site Settings'
|
|
197
|
+
|
|
198
|
+
// Store settings state
|
|
199
|
+
settingsState = {
|
|
200
|
+
isOpen: true,
|
|
201
|
+
title,
|
|
202
|
+
page,
|
|
203
|
+
sectionId,
|
|
204
|
+
sender
|
|
59
205
|
}
|
|
60
|
-
|
|
61
|
-
//
|
|
206
|
+
|
|
207
|
+
// Create and show custom dialog instead of using GrapesJS modal
|
|
208
|
+
customSettingsDialog = createCustomSettingsDialog(editor, opts, page, sectionId)
|
|
209
|
+
document.body.appendChild(customSettingsDialog)
|
|
210
|
+
|
|
211
|
+
// Let the first focusable element get focus naturally
|
|
212
|
+
|
|
62
213
|
editor.trigger(ClientEvent.SETTINGS_OPEN, page)
|
|
63
|
-
|
|
64
|
-
return modal
|
|
214
|
+
return customSettingsDialog
|
|
65
215
|
},
|
|
66
|
-
stop: () => {
|
|
67
|
-
|
|
68
|
-
editor.trigger(ClientEvent.SETTINGS_CLOSE)
|
|
216
|
+
stop: (): void => {
|
|
217
|
+
closeCustomSettingsDialog(editor, true)
|
|
69
218
|
},
|
|
70
219
|
})
|
|
71
|
-
|
|
72
|
-
|
|
220
|
+
editor.Commands.add(cmdAddSection, (_editor: Editor, _sender: Button, options: AddSectionOption): void => {
|
|
221
|
+
addSection(options.section, options.siteOrPage, options.position)
|
|
222
|
+
})
|
|
223
|
+
editor.Commands.add(cmdRemoveSection, (_editor: Editor, _sender: Button, options: AddSectionOption): void => {
|
|
224
|
+
removeSection(options.section.id, options.siteOrPage)
|
|
225
|
+
})
|
|
226
|
+
editor.on('storage:start:store', (data: WebsiteData): void => {
|
|
73
227
|
data.settings = editor.getModel().get('settings')
|
|
228
|
+
/* @ts-ignore FIXME: this should not be there? or is it used on the server side in the sotrage providers? */
|
|
74
229
|
data.name = editor.getModel().get('name')
|
|
75
230
|
})
|
|
76
|
-
editor.on('storage:end:load', (data) => {
|
|
231
|
+
editor.on('storage:end:load', (data: WebsiteData): void => {
|
|
77
232
|
const model = editor.getModel()
|
|
78
233
|
model.set('settings', data.settings || {})
|
|
79
|
-
model.set('name',
|
|
234
|
+
model.set('name', editor.getModel().get('name'))
|
|
80
235
|
})
|
|
81
|
-
|
|
82
|
-
// fix #1559 Custom <head> code must be reapplied
|
|
83
|
-
editor.on('canvas:frame:load', () => {
|
|
236
|
+
editor.on('canvas:frame:load', (): void => {
|
|
84
237
|
updateDom(editor)
|
|
85
238
|
})
|
|
86
|
-
|
|
87
|
-
editor.on('page', (e) => {
|
|
239
|
+
editor.on('page', (_e: unknown): void => {
|
|
88
240
|
updateDom(editor)
|
|
89
241
|
})
|
|
90
242
|
headEditor = editor.CodeManager.createViewer({
|
|
@@ -96,9 +248,7 @@ export const settingsDialog = (editor, opts) => {
|
|
|
96
248
|
})
|
|
97
249
|
}
|
|
98
250
|
|
|
99
|
-
function showSection(item: SettingsSection) {
|
|
100
|
-
// **
|
|
101
|
-
// Handle the side bar
|
|
251
|
+
function showSection(item: SettingsSection): void {
|
|
102
252
|
const aside = el.querySelector('aside.silex-bar') as HTMLElement
|
|
103
253
|
const ul = aside.querySelector('ul.silex-list--menu') as HTMLUListElement
|
|
104
254
|
const li = ul.querySelector('li#settings-sidebar-' + item.id) as HTMLLIElement
|
|
@@ -109,14 +259,12 @@ function showSection(item: SettingsSection) {
|
|
|
109
259
|
console.error('Cannot find the default section in the side bar')
|
|
110
260
|
return
|
|
111
261
|
}
|
|
112
|
-
showSection(defaultSections[0])
|
|
262
|
+
showSection(defaultSections[0])
|
|
113
263
|
return
|
|
114
264
|
}
|
|
115
|
-
// Update active
|
|
116
265
|
Array.from(ul.querySelectorAll('.active')).forEach(el => el.classList.remove('active'))
|
|
117
266
|
li.classList.add('active')
|
|
118
|
-
|
|
119
|
-
// Handle the main section
|
|
267
|
+
|
|
120
268
|
const main = el.querySelector('main#settings__main') as HTMLElement
|
|
121
269
|
const mainItem = main.querySelector(`#settings-${item.id}`)
|
|
122
270
|
if(!mainItem) {
|
|
@@ -125,20 +273,19 @@ function showSection(item: SettingsSection) {
|
|
|
125
273
|
console.error('Cannot find the default section in the settings dialog')
|
|
126
274
|
return
|
|
127
275
|
}
|
|
128
|
-
showSection(defaultSections[0])
|
|
276
|
+
showSection(defaultSections[0])
|
|
129
277
|
return
|
|
130
278
|
}
|
|
131
|
-
// Update hidden
|
|
132
279
|
Array.from(main.querySelectorAll('.silex-hideable')).forEach(el => el.classList.add('silex-hidden'))
|
|
133
280
|
mainItem.classList.remove('silex-hidden')
|
|
134
|
-
|
|
135
|
-
// displaySettings(editor, config, model)
|
|
136
|
-
// Refresh the code editor just in case it went from hidden to visible
|
|
137
|
-
// This makes it ready to be used when the user clicks on the tab
|
|
138
|
-
headEditor.refresh()
|
|
281
|
+
headEditor?.refresh()
|
|
139
282
|
}
|
|
140
283
|
|
|
141
|
-
export function addSection(
|
|
284
|
+
export function addSection(
|
|
285
|
+
section: SettingsSection,
|
|
286
|
+
siteOrPage: 'site' | 'page',
|
|
287
|
+
position: 'first' | 'last' | number
|
|
288
|
+
): void {
|
|
142
289
|
const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
|
|
143
290
|
if (position === 'first') {
|
|
144
291
|
sections.unshift(section)
|
|
@@ -151,7 +298,10 @@ export function addSection(section, siteOrPage: 'site' | 'page', position: 'firs
|
|
|
151
298
|
}
|
|
152
299
|
}
|
|
153
300
|
|
|
154
|
-
export function removeSection(
|
|
301
|
+
export function removeSection(
|
|
302
|
+
id: string,
|
|
303
|
+
siteOrPage: 'site' | 'page'
|
|
304
|
+
): void {
|
|
155
305
|
const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
|
|
156
306
|
const index = sections.findIndex(section => section.id === id)
|
|
157
307
|
if(index === -1) throw new Error(`Cannot find section with id ${id}`)
|
|
@@ -159,15 +309,18 @@ export function removeSection(id, siteOrPage: 'site' | 'page') {
|
|
|
159
309
|
}
|
|
160
310
|
|
|
161
311
|
|
|
162
|
-
let currentSection
|
|
163
|
-
function displaySettings(
|
|
164
|
-
|
|
165
|
-
|
|
312
|
+
let currentSection: SettingsSection | undefined
|
|
313
|
+
function displaySettings(
|
|
314
|
+
editor: Editor,
|
|
315
|
+
config: Record<string, unknown>,
|
|
316
|
+
model: any = editor.getModel(),
|
|
317
|
+
sectionId?: string
|
|
318
|
+
): void {
|
|
319
|
+
const settings: WebsiteSettings = model.get('settings') || {} as WebsiteSettings
|
|
166
320
|
model.set('settings', settings)
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
currentSection = currentSection || menuItemsCurrent[0]
|
|
321
|
+
const menuItemsCurrent: SettingsSection[] = isSite(model) ? sectionsSite : sectionsPage
|
|
322
|
+
const targetSection = !!sectionId && menuItemsCurrent.find(section => section.id === sectionId)
|
|
323
|
+
currentSection = targetSection || currentSection || menuItemsCurrent[0]
|
|
171
324
|
const sections: TemplateResult[] = menuItemsCurrent.map(item => {
|
|
172
325
|
try {
|
|
173
326
|
return item.render(settings, model)
|
|
@@ -176,7 +329,6 @@ function displaySettings(editor, config, model = editor.getModel()) {
|
|
|
176
329
|
return html`<div class="silex-error">Error rendering settings section ${item.id}</div>`
|
|
177
330
|
}
|
|
178
331
|
})
|
|
179
|
-
// Render the settings dialog
|
|
180
332
|
render(html`
|
|
181
333
|
<form class="silex-form">
|
|
182
334
|
<div class="silex-help">
|
|
@@ -190,17 +342,24 @@ function displaySettings(editor, config, model = editor.getModel()) {
|
|
|
190
342
|
</div>
|
|
191
343
|
<section class="silex-sidebar-dialog">
|
|
192
344
|
<aside class="silex-bar">
|
|
193
|
-
<ul class="silex-list silex-list--menu">
|
|
194
|
-
${menuItemsCurrent.map(item => html`
|
|
345
|
+
<ul class="silex-list silex-list--menu" aria-label="Settings sections">
|
|
346
|
+
${menuItemsCurrent.map((item, index) => html`
|
|
195
347
|
<li
|
|
196
348
|
id="settings-sidebar-${item.id}"
|
|
197
|
-
class=${item.id === currentSection
|
|
198
|
-
|
|
349
|
+
class=${item.id === currentSection!.id ? 'active' : ''}
|
|
350
|
+
role="button"
|
|
351
|
+
tabindex="0"
|
|
352
|
+
@click=${(e: Event) => {
|
|
199
353
|
e.preventDefault()
|
|
200
|
-
// Show the new section
|
|
201
354
|
showSection(item)
|
|
202
|
-
// Notify other plugins
|
|
203
355
|
editor.trigger(ClientEvent.SETTINGS_SECTION_CHANGE, item.id)
|
|
356
|
+
}}
|
|
357
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
358
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
359
|
+
e.preventDefault()
|
|
360
|
+
showSection(item)
|
|
361
|
+
editor.trigger(ClientEvent.SETTINGS_SECTION_CHANGE, item.id)
|
|
362
|
+
}
|
|
204
363
|
}}
|
|
205
364
|
>
|
|
206
365
|
${item.label}
|
|
@@ -212,47 +371,45 @@ function displaySettings(editor, config, model = editor.getModel()) {
|
|
|
212
371
|
${ sections }
|
|
213
372
|
</main>
|
|
214
373
|
</section>
|
|
215
|
-
<footer>
|
|
374
|
+
<footer role="contentinfo">
|
|
216
375
|
<p class="silex-version">Silex ${SILEX_VERSION}</p>
|
|
217
|
-
<input class="silex-button" type="button" @click=${
|
|
218
|
-
<input class="silex-button" type="submit" value="Apply">
|
|
376
|
+
<input class="silex-button" type="button" value="Cancel" @click=${() => editor.stopCommand(cmdOpenSettings)} accesskey="c">
|
|
377
|
+
<input class="silex-button" type="submit" value="Apply" accesskey="a">
|
|
219
378
|
</footer>
|
|
220
379
|
</form>
|
|
221
380
|
`, el)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
el.querySelector(`#${idCodeWrapper}`)?.appendChild(headEditor.getElement())
|
|
226
|
-
headEditor.setContent(settings.head || '')
|
|
381
|
+
showSection(currentSection!)
|
|
382
|
+
el.querySelector(`#${idCodeWrapper}`)?.appendChild(headEditor!.getElement())
|
|
383
|
+
headEditor!.setContent(settings.head || '')
|
|
227
384
|
}
|
|
228
385
|
|
|
229
|
-
function saveSettings(
|
|
230
|
-
|
|
386
|
+
function saveSettings(
|
|
387
|
+
editor: Editor,
|
|
388
|
+
config: Record<string, unknown>,
|
|
389
|
+
model: any = editor.getModel()
|
|
390
|
+
): void {
|
|
391
|
+
const form = el.querySelector('form') as HTMLFormElement
|
|
231
392
|
const formData = new FormData(form)
|
|
232
|
-
const data = Array.from(formData
|
|
233
|
-
.reduce((aggregate, [key, value]) => {
|
|
234
|
-
// Keep only the values that are set
|
|
393
|
+
const data = Array.from(formData)
|
|
394
|
+
.reduce((aggregate: {[key: string]: any}, [key, value]) => {
|
|
235
395
|
if(value !== '') {
|
|
236
396
|
aggregate[key] = value
|
|
237
397
|
}
|
|
238
398
|
return aggregate
|
|
239
|
-
}, {})
|
|
240
|
-
// take the name out to the main model (by design in grapesjs pages)
|
|
399
|
+
}, {})
|
|
241
400
|
const { name, ...settings } = data
|
|
242
401
|
model.set({
|
|
243
402
|
settings: {
|
|
244
403
|
...data,
|
|
245
|
-
head: headEditor
|
|
404
|
+
head: headEditor!.getContent(),
|
|
246
405
|
},
|
|
247
406
|
name,
|
|
248
407
|
})
|
|
249
|
-
// save if auto save is on
|
|
250
408
|
editor.getModel().set('changesCount', editor.getDirtyCount() + 1)
|
|
251
|
-
// update the DOM
|
|
252
409
|
updateDom(editor)
|
|
253
410
|
}
|
|
254
|
-
function getHeadContainer(doc, className) {
|
|
255
|
-
const container = doc.head.querySelector(`.${className}`)
|
|
411
|
+
function getHeadContainer(doc: Document, className: string): HTMLElement {
|
|
412
|
+
const container = doc.head.querySelector(`.${className}`) as HTMLElement | null
|
|
256
413
|
if(container) {
|
|
257
414
|
return container
|
|
258
415
|
}
|
|
@@ -261,22 +418,16 @@ function getHeadContainer(doc, className) {
|
|
|
261
418
|
doc.head.appendChild(newContainer)
|
|
262
419
|
return newContainer
|
|
263
420
|
}
|
|
264
|
-
function updateDom(editor) {
|
|
421
|
+
function updateDom(editor: Editor): void {
|
|
265
422
|
const doc = editor.Canvas.getDocument()
|
|
266
423
|
const settings = editor.getModel().get('settings')
|
|
267
|
-
const pageSettings = editor.Pages.getSelected().get('settings')
|
|
424
|
+
const pageSettings = editor.Pages.getSelected().get('settings') as WebsiteSettings
|
|
268
425
|
if(doc && settings) {
|
|
269
|
-
// Delay the update to let the DOM be ready
|
|
270
426
|
setTimeout(() => {
|
|
271
|
-
// Site head
|
|
272
427
|
getHeadContainer(doc, 'site-head')
|
|
273
428
|
.innerHTML = settings.head || ''
|
|
274
|
-
// Pages head
|
|
275
429
|
getHeadContainer(doc, 'page-head')
|
|
276
430
|
.innerHTML = pageSettings?.head || ''
|
|
277
431
|
})
|
|
278
|
-
} else {
|
|
279
|
-
// No document??
|
|
280
432
|
}
|
|
281
433
|
}
|
|
282
|
-
|
|
@@ -87,20 +87,28 @@ export const storagePlugin = (editor: PublishableEditor) => {
|
|
|
87
87
|
return data
|
|
88
88
|
} else {
|
|
89
89
|
editor.runCommand(cmdPauseAutoSave)
|
|
90
|
-
const { pages, ...rest } = data
|
|
90
|
+
const { pages, pagesFolder, ...rest } = data
|
|
91
91
|
// Load any additional project data, e.g. symbols, but not the ones we progressive load
|
|
92
92
|
loader && render(getLoaderHtml('Loading styles, assets and symbols', 0, pages.length + 1), loader)
|
|
93
93
|
// Add to the project, everything but pages
|
|
94
94
|
editor.loadProjectData(rest)
|
|
95
|
+
editor.getModel().set('pagesFolder', pagesFolder)
|
|
95
96
|
// Add the pages to the project
|
|
96
97
|
await progressiveLoadPages(editor, pages)
|
|
97
98
|
await nextFrame()
|
|
99
|
+
// Trigger symbol update to recount instances now that pages are loaded
|
|
100
|
+
editor.trigger('symbol')
|
|
101
|
+
await nextFrame()
|
|
98
102
|
// Select the first page
|
|
99
103
|
const firstPage = editor.Pages.getAll()[0]
|
|
100
104
|
if (firstPage) editor.Pages.select(firstPage)
|
|
101
105
|
loader && render(getLoaderHtml('Starting', 1, 1), loader)
|
|
102
106
|
await nextFrame()
|
|
103
|
-
editor.once('canvas:frame:load', () =>
|
|
107
|
+
editor.once('canvas:frame:load', () => {
|
|
108
|
+
setTimeout(() => { // This is needed in chrome, otherwise a save is triggered
|
|
109
|
+
editor.stopCommand(cmdPauseAutoSave)
|
|
110
|
+
}, 500)
|
|
111
|
+
})
|
|
104
112
|
return data
|
|
105
113
|
}
|
|
106
114
|
} catch (err) {
|
|
@@ -138,7 +146,7 @@ export const storagePlugin = (editor: PublishableEditor) => {
|
|
|
138
146
|
},
|
|
139
147
|
|
|
140
148
|
async doStore(data: WebsiteData, options: { id: WebsiteId, connectorId: ConnectorId }) {
|
|
141
|
-
if (editor.PublicationManager
|
|
149
|
+
if (editor.PublicationManager?.status === PublicationStatus.STATUS_PENDING) {
|
|
142
150
|
// Publication is pending, save is delayed
|
|
143
151
|
return new Promise((resolve) => {
|
|
144
152
|
editor.once(ClientEvent.PUBLISH_END, () => {
|
|
@@ -160,6 +168,7 @@ export const storagePlugin = (editor: PublishableEditor) => {
|
|
|
160
168
|
if (user) {
|
|
161
169
|
data.assets = removeTempDataFromAssetUrl(data.assets)
|
|
162
170
|
data.styles = removeTempDataFromStyles(data.styles)
|
|
171
|
+
data.pagesFolder = editor.getModel().get('pagesFolder')
|
|
163
172
|
await websiteSave({ websiteId: options.id, connectorId: user.storage.connectorId, data })
|
|
164
173
|
isSaving = false
|
|
165
174
|
} else {
|
|
@@ -16,12 +16,27 @@
|
|
|
16
16
|
*
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { Editor } from 'grapesjs'
|
|
20
|
+
|
|
19
21
|
const pasteBtnClass = 'fa fa-fw fa-paste silex-button'
|
|
22
|
+
const undoBtnClass = 'fa fa-fw fa-rotate-left silex-button'
|
|
23
|
+
const redoBtnClass = 'fa fa-fw fa-rotate-right silex-button'
|
|
20
24
|
const disabledClass = ' disabled'
|
|
21
25
|
|
|
22
|
-
export default function(editor) {
|
|
23
|
-
// Add copy/paste buttons in the top bar
|
|
24
|
-
|
|
26
|
+
export default function(editor: Editor) {
|
|
27
|
+
// Add undo/redo and copy/paste buttons in the top bar
|
|
28
|
+
// Add undo/redo and copy/paste buttons in the top bar
|
|
29
|
+
editor.Panels.addButton('options', [{
|
|
30
|
+
id: 'undo',
|
|
31
|
+
className: undoBtnClass + disabledClass,
|
|
32
|
+
command: () => editor.UndoManager.undo(),
|
|
33
|
+
attributes: { title: 'Undo (Ctrl+Z)' },
|
|
34
|
+
}, {
|
|
35
|
+
id: 'redo',
|
|
36
|
+
className: redoBtnClass + disabledClass,
|
|
37
|
+
command: () => editor.UndoManager.redo(),
|
|
38
|
+
attributes: { title: 'Redo (Ctrl+Shift+Z)' },
|
|
39
|
+
}, {
|
|
25
40
|
id: 'copy',
|
|
26
41
|
className: 'fa fa-fw fa-copy',
|
|
27
42
|
command: 'core:copy',
|
|
@@ -34,6 +49,27 @@ export default function(editor) {
|
|
|
34
49
|
active: false,
|
|
35
50
|
}])
|
|
36
51
|
|
|
52
|
+
// Get buttons after creation
|
|
53
|
+
const undoBtn = editor.Panels.getButton('options', 'undo')
|
|
54
|
+
const redoBtn = editor.Panels.getButton('options', 'redo')
|
|
55
|
+
const pasteBtn = editor.Panels.getButton('options', 'paste')
|
|
56
|
+
|
|
57
|
+
// Update undo/redo button states based on UndoManager
|
|
58
|
+
const updateUndoRedoButtons = () => {
|
|
59
|
+
const hasUndo = editor.UndoManager.hasUndo()
|
|
60
|
+
const hasRedo = editor.UndoManager.hasRedo()
|
|
61
|
+
undoBtn.set('className', undoBtnClass + (hasUndo ? '' : disabledClass))
|
|
62
|
+
redoBtn.set('className', redoBtnClass + (hasRedo ? '' : disabledClass))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Listen for changes in the undo stack
|
|
66
|
+
editor.on('change:changesCount', updateUndoRedoButtons)
|
|
67
|
+
editor.on('undo', updateUndoRedoButtons)
|
|
68
|
+
editor.on('redo', updateUndoRedoButtons)
|
|
69
|
+
|
|
70
|
+
// Initial state
|
|
71
|
+
updateUndoRedoButtons()
|
|
72
|
+
|
|
37
73
|
// Disable paste button if clipboard is empty
|
|
38
74
|
editor.EditorModel.on('change:clipboard', (model, value) => {
|
|
39
75
|
const hasClipboard = value && value.length > 0
|
package/src/ts/client/utils.ts
CHANGED
|
@@ -21,8 +21,10 @@ import { Component, Editor } from 'grapesjs'
|
|
|
21
21
|
export function onAll(editor: Editor, cbk: (c: Component) => void) {
|
|
22
22
|
editor.Pages.getAll()
|
|
23
23
|
.forEach(page => {
|
|
24
|
-
page.getMainComponent()
|
|
25
|
-
|
|
24
|
+
const mainComponent = page.getMainComponent()
|
|
25
|
+
if (mainComponent) {
|
|
26
|
+
mainComponent.onAll(c => cbk(c))
|
|
27
|
+
}
|
|
26
28
|
})
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -76,3 +78,14 @@ export function titleCase(str: string, sep: string = ' '): string {
|
|
|
76
78
|
const split = str.split(sep)
|
|
77
79
|
return split.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(sep)
|
|
78
80
|
}
|
|
81
|
+
|
|
82
|
+
export function debounce<T extends (...args: any[]) => any>(func: T, wait = 100): (...args: Parameters<T>) => void {
|
|
83
|
+
let timeout: ReturnType<typeof setTimeout> | null = null
|
|
84
|
+
|
|
85
|
+
return function(this: any, ...args: Parameters<T>) {
|
|
86
|
+
if (timeout) clearTimeout(timeout)
|
|
87
|
+
timeout = setTimeout(() => {
|
|
88
|
+
func.apply(this, args)
|
|
89
|
+
}, wait)
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/ts/constants.ts
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
export const WEBSITE_DATA_FILE = 'website.json'
|
|
23
23
|
export const WEBSITE_META_DATA_FILE = 'meta.json'
|
|
24
|
-
export const
|
|
24
|
+
export const LEGACY_WEBSITE_PAGES_FOLDER = 'src' // This is used in legacy websites, check the value in EMPTY_WEBSITE
|
|
25
|
+
export const WEBSITE_PAGES_FOLDER = 'pages' // This is the default now, if not specified in the website data
|
|
25
26
|
|
|
26
27
|
export const DEFAULT_WEBSITE_ID = 'default'
|
|
27
28
|
export const DEFAULT_LANGUAGE = 'en'
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
throw new Error('This file is obsolete, templates have been replaced by Silex CMS
|
|
18
|
+
throw new Error('This file is obsolete, templates have been replaced by the built-in Silex CMS feature')
|
|
19
19
|
|
|
20
20
|
// Silex serves /clients
|
|
21
21
|
// @ts-ignore
|