@silexlabs/silex 3.3.0 → 3.3.1-1
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/dist/client/client/grapesjs/flex.d.ts +7 -0
- package/dist/client/client/grapesjs/li.d.ts +7 -0
- package/dist/client/client/grapesjs/sectors.d.ts +11 -0
- package/dist/client/client/grapesjs/symbolDialogs.d.ts +2 -1
- package/dist/client/css/admin.css +3 -3
- package/dist/client/css/admin.css.map +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/js/main.js +777 -159
- package/dist/client/silex-lib/src/ts/client/grapesjs/flex.d.ts +7 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/li.d.ts +7 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/scroll.d.ts +7 -0
- package/dist/client/silex-lib/src/ts/client/grapesjs/sectors.d.ts +11 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.js +3 -67
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -1
- package/package.json +3 -2
- package/src/ts/client/grapesjs/LoginDialog.ts +1 -0
- package/src/ts/client/grapesjs/css-props.ts +230 -303
- package/src/ts/client/grapesjs/flex.ts +83 -0
- package/src/ts/client/grapesjs/img.ts +45 -6
- package/src/ts/client/grapesjs/index.ts +12 -9
- package/src/ts/client/grapesjs/li.ts +77 -0
- package/src/ts/client/grapesjs/sectors.ts +59 -0
- package/src/ts/client/grapesjs/semantic.ts +132 -58
- package/src/ts/client/grapesjs/symbolDialogs.ts +10 -6
- package/src/ts/client/index.ts +0 -38
- package/src/ts/plugins/server/GitlabConnector.ts +2 -67
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
import { Editor } from 'grapesjs'
|
|
18
|
+
import { SectorConfig, registerSector } from './sectors'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @fileoverview This plugin adds CSS properties for the UL, OL and LI elements
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export default (editor: Editor) => {
|
|
26
|
+
const sectorConfigs: SectorConfig[] = [{
|
|
27
|
+
name: 'Flex Item',
|
|
28
|
+
props: [
|
|
29
|
+
{
|
|
30
|
+
id: 'order',
|
|
31
|
+
name: 'Order',
|
|
32
|
+
property: 'order',
|
|
33
|
+
type: 'integer',
|
|
34
|
+
defaults: 0,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'flex-grow',
|
|
38
|
+
name: 'Flex Grow',
|
|
39
|
+
property: 'flex-grow',
|
|
40
|
+
type: 'integer',
|
|
41
|
+
defaults: 0,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'flex-shrink',
|
|
45
|
+
name: 'Flex Shrink',
|
|
46
|
+
property: 'flex-shrink',
|
|
47
|
+
type: 'integer',
|
|
48
|
+
defaults: 1,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'flex-basis',
|
|
52
|
+
name: 'Flex Basis',
|
|
53
|
+
property: 'flex-basis',
|
|
54
|
+
type: 'text',
|
|
55
|
+
defaults: 'auto',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'align-self',
|
|
59
|
+
name: 'Align Self',
|
|
60
|
+
property: 'align-self',
|
|
61
|
+
type: 'select',
|
|
62
|
+
options: [
|
|
63
|
+
{ id: '', label: '' },
|
|
64
|
+
{ id: 'auto', label: 'auto' },
|
|
65
|
+
{ id: 'flex-start', label: 'flex-start' },
|
|
66
|
+
{ id: 'flex-end', label: 'flex-end' },
|
|
67
|
+
{ id: 'center', label: 'center' },
|
|
68
|
+
{ id: 'baseline', label: 'baseline' },
|
|
69
|
+
{ id: 'stretch', label: 'stretch' },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
shouldShow: comp => {
|
|
74
|
+
const parent = comp.parent()
|
|
75
|
+
const display = parent?.getStyle()['display'] as string || ''
|
|
76
|
+
return display.includes('flex')
|
|
77
|
+
}
|
|
78
|
+
}]
|
|
79
|
+
|
|
80
|
+
editor.on('load', () => {
|
|
81
|
+
sectorConfigs.forEach(sectorConfig => registerSector(editor, sectorConfig, 2))
|
|
82
|
+
})
|
|
83
|
+
}
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { Editor } from 'grapesjs'
|
|
18
|
+
import { Component, ComponentView, Editor } from 'grapesjs'
|
|
19
|
+
import { displayedToStored, storedToDisplayed } from '../assetUrl'
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @fileoverview This plugin adds CSS properties object-fit and object-position to the image component.
|
|
@@ -25,13 +26,17 @@ export default (editor: Editor, opts) => {
|
|
|
25
26
|
opts = {
|
|
26
27
|
// Default options
|
|
27
28
|
labelSrc: 'Image URL',
|
|
28
|
-
placeholderSrc: '
|
|
29
|
+
placeholderSrc: '/assets/placeholder.png',
|
|
29
30
|
// These default values will never be used,
|
|
30
31
|
// as there is default values in config
|
|
31
32
|
replacedElements: ['img'],
|
|
32
33
|
// Override with provided config
|
|
33
34
|
...opts,
|
|
34
35
|
}
|
|
36
|
+
|
|
37
|
+
const domc = editor.DomComponents
|
|
38
|
+
const imgType = domc.getType('image')
|
|
39
|
+
|
|
35
40
|
// Add src trait in the properties
|
|
36
41
|
editor.TraitManager.addType('src', {
|
|
37
42
|
events: {
|
|
@@ -131,17 +136,51 @@ export default (editor: Editor, opts) => {
|
|
|
131
136
|
// It shows up when the selection is an image
|
|
132
137
|
editor.on('component:selected', (model) => {
|
|
133
138
|
if (model && opts.replacedElements.includes(model.get('tagName')?.toLowerCase())) {
|
|
134
|
-
// Not necessary, grapesjs does it automatically
|
|
135
|
-
// sector.set('visible', true);
|
|
136
139
|
showSector = true
|
|
140
|
+
sector.set('visible', true)
|
|
137
141
|
} else {
|
|
138
142
|
showSector = false
|
|
139
143
|
sector.set('visible', false)
|
|
140
144
|
}
|
|
141
145
|
})
|
|
142
|
-
// Watch
|
|
143
|
-
sector.on('change:visible', (
|
|
146
|
+
// Watch changes made by grapesjs and revert them if needed
|
|
147
|
+
sector.on('change:visible', () => {
|
|
144
148
|
if(sector.get('visible') !== showSector) sector.set('visible', showSector)
|
|
145
149
|
})
|
|
150
|
+
// Add a new image type that handles the storage URL vs displayed URL
|
|
151
|
+
domc.addType('image', {
|
|
152
|
+
...imgType,
|
|
153
|
+
view: {
|
|
154
|
+
...imgType.view,
|
|
155
|
+
onRender() {
|
|
156
|
+
const view = this as ComponentView
|
|
157
|
+
imgType.view.onRender && imgType.view.onRender.call(this) // call original
|
|
158
|
+
const el = view.el
|
|
159
|
+
const elImg = view.el as HTMLImageElement
|
|
160
|
+
const src = this.model.getAttributes().src
|
|
161
|
+
if (src && !src.startsWith('http')) {
|
|
162
|
+
elImg.src = storedToDisplayed(src, opts.websiteId, opts.storageId)
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
model: {
|
|
167
|
+
...imgType.model,
|
|
168
|
+
init() {
|
|
169
|
+
const model = this as Component
|
|
170
|
+
imgType.model.init && imgType.model.init.call(this) // call original
|
|
171
|
+
// Handle changes by the AssetManager
|
|
172
|
+
model.on('change', () => {
|
|
173
|
+
if (model.get('src')) {
|
|
174
|
+
const newSrc = displayedToStored(model.get('src'))
|
|
175
|
+
if (newSrc !== model.get('src')) {
|
|
176
|
+
model.set('src', newSrc)
|
|
177
|
+
model.view.render()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
|
|
146
185
|
})
|
|
147
186
|
}
|
|
@@ -32,16 +32,18 @@ import blocksBasicPlugin from 'grapesjs-blocks-basic'
|
|
|
32
32
|
import styleFilterPlugin from 'grapesjs-style-filter'
|
|
33
33
|
import formPlugin from 'grapesjs-plugin-forms'
|
|
34
34
|
import codePlugin from 'grapesjs-custom-code'
|
|
35
|
-
import uiSuggestClasses from '@silexlabs/grapesjs-ui-suggest-classes'
|
|
36
35
|
import filterStyles from '@silexlabs/grapesjs-filter-styles'
|
|
37
36
|
import symbolsPlugin from '@silexlabs/grapesjs-symbols'
|
|
38
37
|
import loadingPlugin from '@silexlabs/grapesjs-loading'
|
|
39
38
|
import fontsDialogPlugin from '@silexlabs/grapesjs-fonts'
|
|
39
|
+
import selectorPlugin from '@silexlabs/grapesjs-advanced-selector'
|
|
40
40
|
import symbolDialogsPlugin, { cmdPromptAddSymbol } from './symbolDialogs'
|
|
41
41
|
import loginDialogPlugin, { LoginDialogOptions, cmdLogout } from './LoginDialog'
|
|
42
42
|
import footerPlugin from './footer'
|
|
43
43
|
import breadcrumbsPlugin from './breadcrumbs'
|
|
44
44
|
import imgPlugin from './img'
|
|
45
|
+
import liPlugin from './li'
|
|
46
|
+
import flexPlugin from './flex'
|
|
45
47
|
import cssPropsPlugin from './css-props'
|
|
46
48
|
import rateLimitPlugin from '@silexlabs/grapesjs-storage-rate-limit'
|
|
47
49
|
import borderPugin from 'grapesjs-style-border'
|
|
@@ -72,6 +74,7 @@ const plugins = [
|
|
|
72
74
|
{name: 'grapesjs-style-bg', value: backgroundPlugin},
|
|
73
75
|
{name: './settings', value: settingsDialog},
|
|
74
76
|
{name: '@silexlabs/grapesjs-fonts', value: fontsDialogPlugin},
|
|
77
|
+
{name: '@silexlabs/grapesjs-advanced-selector', value: selectorPlugin},
|
|
75
78
|
{name: './new-page-dialog', value: newPageDialog},
|
|
76
79
|
{name: './page-panel', value: pagePanelPlugin},
|
|
77
80
|
{name: 'grapesjs-blocks-basic', value: blocksBasicPlugin},
|
|
@@ -84,7 +87,6 @@ const plugins = [
|
|
|
84
87
|
{name: 'grapesjs-custom-code', value: codePlugin},
|
|
85
88
|
{name: './internal-links', value: internalLinksPlugin},
|
|
86
89
|
{name: './keymaps', value: keymapsPlugin},
|
|
87
|
-
{name: '@silexlabs/grapesjs-ui-suggest-classes', value: uiSuggestClasses},
|
|
88
90
|
{name: '@silexlabs/grapesjs-filter-styles', value: filterStyles},
|
|
89
91
|
{name: './symbolDialogs', value: symbolDialogsPlugin},
|
|
90
92
|
{name: '@silexlabs/grapesjs-symbols', value: symbolsPlugin},
|
|
@@ -94,6 +96,8 @@ const plugins = [
|
|
|
94
96
|
{name: '@silexlabs/grapesjs-loading', value: loadingPlugin},
|
|
95
97
|
{name: './breadcrumbs', value: breadcrumbsPlugin},
|
|
96
98
|
{name: './img', value: imgPlugin},
|
|
99
|
+
{name: './li', value: liPlugin},
|
|
100
|
+
{name: './flex', value: flexPlugin},
|
|
97
101
|
{name: './css-props', value: cssPropsPlugin},
|
|
98
102
|
{name: './footer', value: footerPlugin},
|
|
99
103
|
{name: '@silexlabs/grapesjs-storage-rate-limit', value: rateLimitPlugin},
|
|
@@ -170,6 +174,10 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
|
|
|
170
174
|
actions: ['bold', 'italic', 'underline', 'strikethrough', 'link', 'wrap', orderedList, unorderedList],
|
|
171
175
|
},
|
|
172
176
|
|
|
177
|
+
selectorManager: {
|
|
178
|
+
custom: true, // This should not be needed, check index.js
|
|
179
|
+
escapeName: (name) => `${name}`,
|
|
180
|
+
},
|
|
173
181
|
|
|
174
182
|
plugins: plugins.map(p => p.value),
|
|
175
183
|
|
|
@@ -291,10 +299,6 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
|
|
|
291
299
|
cmdOpenSettings,
|
|
292
300
|
appendTo: '.page-panel-container',
|
|
293
301
|
},
|
|
294
|
-
[uiSuggestClasses.toString()]: {
|
|
295
|
-
enableCount: false,
|
|
296
|
-
enablePerformance: false,
|
|
297
|
-
},
|
|
298
302
|
[filterStyles.toString()]: {
|
|
299
303
|
appendBefore: '.gjs-sm-sectors',
|
|
300
304
|
},
|
|
@@ -334,6 +338,8 @@ export function getEditorConfig(config: ClientConfig): EditorConfig {
|
|
|
334
338
|
},
|
|
335
339
|
[imgPlugin.toString()]: {
|
|
336
340
|
replacedElements: config.replacedElements,
|
|
341
|
+
websiteId,
|
|
342
|
+
storageId,
|
|
337
343
|
},
|
|
338
344
|
[notificationsPlugin.toString()]: {
|
|
339
345
|
container: notificationContainer,
|
|
@@ -418,9 +424,6 @@ export async function initEditor(config: EditorConfig) {
|
|
|
418
424
|
// Use the style filter plugin
|
|
419
425
|
editor.StyleManager.addProperty('extra', { extend: 'filter' })
|
|
420
426
|
|
|
421
|
-
// Add a class to the Style Manager's sticky top section
|
|
422
|
-
editor.SelectorManager.selectorTags.el.parentElement.classList.add('top-style-section')
|
|
423
|
-
|
|
424
427
|
// Add the notifications container
|
|
425
428
|
document.body.querySelector('.notifications-container')?.appendChild(notificationContainer)
|
|
426
429
|
// Mark the button as dirty when there are notifications
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
import { Editor } from 'grapesjs'
|
|
18
|
+
import { SectorConfig, registerSector } from './sectors'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @fileoverview This plugin adds CSS properties for the UL, OL and LI elements
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export default (editor: Editor) => {
|
|
26
|
+
const sectorConfigs: SectorConfig[] = [
|
|
27
|
+
{
|
|
28
|
+
name: 'List',
|
|
29
|
+
props: [
|
|
30
|
+
{
|
|
31
|
+
id: 'list-style-type',
|
|
32
|
+
name: 'List Style Type',
|
|
33
|
+
property: 'list-style-type',
|
|
34
|
+
type: 'select',
|
|
35
|
+
options: [
|
|
36
|
+
{ id: '', label: '' },
|
|
37
|
+
{ id: 'disc', label: 'disc' },
|
|
38
|
+
{ id: 'circle', label: 'circle' },
|
|
39
|
+
{ id: 'square', label: 'square' },
|
|
40
|
+
{ id: 'decimal', label: 'decimal' },
|
|
41
|
+
{ id: 'decimal-leading-zero', label: 'decimal-leading-zero' },
|
|
42
|
+
{ id: 'lower-roman', label: 'lower-roman' },
|
|
43
|
+
{ id: 'upper-roman', label: 'upper-roman' },
|
|
44
|
+
{ id: 'lower-alpha', label: 'lower-alpha' },
|
|
45
|
+
{ id: 'upper-alpha', label: 'upper-alpha' },
|
|
46
|
+
{ id: 'none', label: 'none' },
|
|
47
|
+
],
|
|
48
|
+
defaults: '',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'list-style-position',
|
|
52
|
+
name: 'List Style Position',
|
|
53
|
+
property: 'list-style-position',
|
|
54
|
+
type: 'select',
|
|
55
|
+
options: [
|
|
56
|
+
{ id: '', label: '' },
|
|
57
|
+
{ id: 'outside', label: 'outside' },
|
|
58
|
+
{ id: 'inside', label: 'inside' },
|
|
59
|
+
],
|
|
60
|
+
defaults: '',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'list-style-image',
|
|
64
|
+
name: 'List Style Image',
|
|
65
|
+
property: 'list-style-image',
|
|
66
|
+
type: 'text',
|
|
67
|
+
defaults: '',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
shouldShow: comp => ['ul', 'ol', 'li'].includes(comp.get('tagName')?.toLowerCase() || ''),
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
editor.on('load', () => {
|
|
75
|
+
sectorConfigs.forEach(sectorConfig => registerSector(editor, sectorConfig))
|
|
76
|
+
})
|
|
77
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
import { Component, Editor } from 'grapesjs'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @fileoverview
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export type SectorConfig = {
|
|
25
|
+
name: string
|
|
26
|
+
props: any[]
|
|
27
|
+
shouldShow: (comp: Component) => boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function registerSector(editor: Editor, config: SectorConfig, at?: number) {
|
|
31
|
+
const sm = editor.StyleManager
|
|
32
|
+
let sector = sm.getSector(config.name)
|
|
33
|
+
if (!sector) {
|
|
34
|
+
sector = sm.addSector(config.name, { name: config.name, open: false, visible: false }, { at })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Add props if not already present
|
|
38
|
+
const existingProps = sector.get('properties')//.map(p => p.get('property'))
|
|
39
|
+
config.props.forEach(p => {
|
|
40
|
+
if (!existingProps.includes(p.property)) {
|
|
41
|
+
sector.addProperty(p, {})
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Keep sector in sync with internal visibility state
|
|
46
|
+
sector.on('change:visible', () => {
|
|
47
|
+
const shouldBeVisible = sector.get('visible')
|
|
48
|
+
if (shouldBeVisible !== config.shouldShow(editor.getSelected() as Component)) {
|
|
49
|
+
sector.set('visible', !shouldBeVisible)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
editor.on('component:selected', comp => {
|
|
54
|
+
const shouldShow = config.shouldShow(comp)
|
|
55
|
+
if (sector.get('visible') !== shouldShow) {
|
|
56
|
+
sector.set('visible', shouldShow)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
@@ -21,94 +21,168 @@ import {map} from 'lit-html/directives/map.js'
|
|
|
21
21
|
// constants
|
|
22
22
|
const pluginName = 'semantic'
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'BLOCKQUOTE',
|
|
24
|
+
// Group tags by category
|
|
25
|
+
const tagCategories = [
|
|
26
|
+
{
|
|
27
|
+
label: 'Containers',
|
|
28
|
+
tags: ['DIV', 'SPAN', 'P'],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: 'Headings',
|
|
32
|
+
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
label: 'Main document structure',
|
|
36
|
+
tags: ['MAIN', 'SECTION', 'ARTICLE', 'NAV', 'ASIDE', 'HEADER', 'FOOTER'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: 'Content grouping',
|
|
40
|
+
tags: ['ADDRESS', 'BLOCKQUOTE', 'PRE'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: 'Interactive/form',
|
|
44
|
+
tags: ['BUTTON', 'LABEL', 'DETAILS', 'SUMMARY'],
|
|
45
|
+
},
|
|
47
46
|
]
|
|
48
47
|
|
|
48
|
+
// Flatten all tags for lookup
|
|
49
|
+
const tags = tagCategories.flatMap(cat => cat.tags)
|
|
50
|
+
|
|
49
51
|
// plugin code
|
|
50
52
|
export const semanticPlugin = (editor, opts) => {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
+
// Helper to merge extra traits into a type's default traits
|
|
54
|
+
function withExtraTraits(type, extraTraits) {
|
|
55
|
+
const origTraits = editor.DomComponents.getType(type.id).model.prototype.defaults.traits
|
|
56
|
+
return [
|
|
57
|
+
...origTraits,
|
|
58
|
+
...extraTraits,
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Traits to add to all components
|
|
63
|
+
const extraTraits = [
|
|
64
|
+
{
|
|
65
|
+
label: 'Tag name',
|
|
66
|
+
type: 'tag-name',
|
|
67
|
+
name: 'tag-name',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
label: 'For',
|
|
71
|
+
name: 'for',
|
|
72
|
+
type: 'for-trait',
|
|
73
|
+
placeholder: 'ID of input',
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
// Add the extra traits to all component types
|
|
78
|
+
editor.DomComponents.getTypes().forEach(type => {
|
|
53
79
|
editor.DomComponents.addType(type.id, {
|
|
54
80
|
model: {
|
|
55
81
|
defaults: {
|
|
56
|
-
traits:
|
|
57
|
-
// Keep the type original traits
|
|
58
|
-
...editor.DomComponents.getType(type.id).model.prototype.defaults.traits,
|
|
59
|
-
// Add the new trait
|
|
60
|
-
{
|
|
61
|
-
label: 'Tag name',
|
|
62
|
-
type: 'tag-name',
|
|
63
|
-
name: 'tag-name',
|
|
64
|
-
},
|
|
65
|
-
]
|
|
82
|
+
traits: withExtraTraits(type, extraTraits),
|
|
66
83
|
}
|
|
67
84
|
}
|
|
68
85
|
})
|
|
69
86
|
})
|
|
70
87
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
// Shared render function for select and for-trait
|
|
89
|
+
function renderTrait(el: HTMLElement, opts: {
|
|
90
|
+
tagName?: string,
|
|
91
|
+
forAttr?: string,
|
|
92
|
+
type: 'tag' | 'for'
|
|
93
|
+
}) {
|
|
94
|
+
const tagName = (opts.tagName || '').toUpperCase()
|
|
95
|
+
if (opts.type === 'tag') {
|
|
96
|
+
// Render tag select
|
|
97
|
+
let categoriesWithCurrent = tagCategories.map(cat => ({
|
|
98
|
+
...cat,
|
|
99
|
+
tags: cat.tags.slice(),
|
|
100
|
+
}))
|
|
101
|
+
if (tagName && !tags.includes(tagName)) {
|
|
102
|
+
categoriesWithCurrent = [
|
|
103
|
+
...categoriesWithCurrent,
|
|
104
|
+
{ label: 'Other', tags: [tagName] }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
render(html`
|
|
108
|
+
<select @change=${event => renderTrait(el, { tagName: event.target.value, type: 'tag' })}>
|
|
109
|
+
${map(categoriesWithCurrent, cat => html`
|
|
110
|
+
<optgroup label="${cat.label}">
|
|
111
|
+
${map(cat.tags, tag => html`
|
|
112
|
+
<option value="${tag}" ?selected=${tagName === tag}>${tag}</option>
|
|
113
|
+
`)}
|
|
114
|
+
</optgroup>
|
|
115
|
+
`)}
|
|
116
|
+
</select>
|
|
117
|
+
`, el)
|
|
118
|
+
} else if (opts.type === 'for') {
|
|
119
|
+
// Render "for" input, hide if not LABEL
|
|
120
|
+
const wrapper = el.closest('.gjs-trt-trait__wrp-for') as HTMLElement
|
|
121
|
+
if (tagName !== 'LABEL') {
|
|
122
|
+
if(wrapper) wrapper.style.display = 'none'
|
|
123
|
+
render(html``, el)
|
|
124
|
+
} else {
|
|
125
|
+
if(wrapper) wrapper.style.display = 'initial'
|
|
126
|
+
render(html`
|
|
127
|
+
<input type="text" placeholder="ID of input" value="${opts.forAttr || ''}"
|
|
128
|
+
@input=${event => renderTrait(el, { forAttr: (event.target as HTMLInputElement).value, tagName, type: 'for' })}
|
|
129
|
+
>
|
|
130
|
+
`, el)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
81
133
|
}
|
|
134
|
+
|
|
82
135
|
function doRenderCurrent(el: HTMLElement) {
|
|
83
|
-
|
|
136
|
+
renderTrait(el, { tagName: editor.getSelected()?.get('tagName') || '', type: 'tag' })
|
|
137
|
+
}
|
|
138
|
+
function doRenderCurrentFor(el: HTMLElement) {
|
|
139
|
+
const selected = editor.getSelected()
|
|
140
|
+
renderTrait(el, {
|
|
141
|
+
tagName: selected?.get('tagName') || '',
|
|
142
|
+
forAttr: selected?.getAttributes().for || '',
|
|
143
|
+
type: 'for'
|
|
144
|
+
})
|
|
84
145
|
}
|
|
85
146
|
|
|
86
147
|
// Add semantic traits
|
|
87
148
|
// inspired by https://github.com/olivmonnier/grapesjs-plugin-header/blob/master/src/components.js
|
|
88
149
|
editor.TraitManager.addType('tag-name', {
|
|
89
|
-
createInput({ trait }) {
|
|
90
|
-
// Create a new element container and add some content
|
|
150
|
+
createInput({ trait, component }) {
|
|
91
151
|
const el = document.createElement('div')
|
|
92
|
-
// update the UI when a page is added/renamed/removed
|
|
93
152
|
editor.on('page', () => doRenderCurrent(el))
|
|
94
153
|
doRenderCurrent(el)
|
|
95
|
-
// this will be the element passed to onEvent and onUpdate
|
|
96
154
|
return el
|
|
97
155
|
},
|
|
98
|
-
// Update the component based on UI changes
|
|
99
|
-
// `elInput` is the result HTMLElement you get from `createInput`
|
|
100
156
|
onEvent({ elInput, component, event }) {
|
|
101
|
-
const value =
|
|
102
|
-
if(component.get('tagName').toUpperCase()
|
|
103
|
-
// Already done
|
|
104
|
-
} else {
|
|
157
|
+
const value = (event.target as HTMLSelectElement).value
|
|
158
|
+
if(component.get('tagName').toUpperCase() !== value.toUpperCase()){
|
|
105
159
|
component.set('tagName', value)
|
|
106
160
|
}
|
|
107
161
|
},
|
|
108
|
-
// Update UI on the component change
|
|
109
162
|
onUpdate({ elInput, component }) {
|
|
110
163
|
const tagName = component.get('tagName')
|
|
111
|
-
|
|
164
|
+
renderTrait(elInput, { tagName, type: 'tag' })
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
editor.TraitManager.addType('for-trait', {
|
|
169
|
+
createInput({ trait, component }) {
|
|
170
|
+
const el = document.createElement('div')
|
|
171
|
+
editor.on('page', () => doRenderCurrentFor(el))
|
|
172
|
+
editor.on('component:update', () => doRenderCurrentFor(el))
|
|
173
|
+
doRenderCurrentFor(el)
|
|
174
|
+
return el
|
|
175
|
+
},
|
|
176
|
+
onEvent({ elInput, component, event }) {
|
|
177
|
+
const value = (event.target as HTMLInputElement).value
|
|
178
|
+
if(component.getAttributes().for !== value){
|
|
179
|
+
component.setAttributes({ for: value })
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
onUpdate({ elInput, component }) {
|
|
183
|
+
const forAttr = component.getAttributes().for || ''
|
|
184
|
+
const tagName = component.get('tagName') || ''
|
|
185
|
+
renderTrait(elInput, { forAttr, tagName, type: 'for' })
|
|
112
186
|
},
|
|
113
187
|
})
|
|
114
188
|
}
|
|
@@ -15,21 +15,25 @@
|
|
|
15
15
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { Component, Editor } from 'grapesjs'
|
|
19
|
+
|
|
18
20
|
export const cmdPromptAddSymbol = 'symbol-prompt-add'
|
|
19
21
|
|
|
20
|
-
export default (editor
|
|
21
|
-
function getNext(prefix) {
|
|
22
|
+
export default (editor: Editor) => {
|
|
23
|
+
function getNext(prefix: string) {
|
|
22
24
|
let idx = 1
|
|
23
|
-
while(
|
|
25
|
+
while(
|
|
26
|
+
editor.Components.getSymbols()
|
|
27
|
+
.find((symbol: Component) => symbol.get('custom-name') === prefix + idx)
|
|
28
|
+
) {
|
|
24
29
|
idx++
|
|
25
30
|
}
|
|
26
31
|
return prefix + idx
|
|
27
32
|
}
|
|
28
33
|
editor.Commands.add(cmdPromptAddSymbol, {
|
|
29
|
-
run: (_
|
|
34
|
+
run: (_) => {
|
|
30
35
|
const label = prompt('Label', getNext('Symbol '))
|
|
31
|
-
|
|
32
|
-
editor.runCommand('symbols:add', { label, icon })
|
|
36
|
+
editor.runCommand('symbols:add', { label })
|
|
33
37
|
}
|
|
34
38
|
})
|
|
35
39
|
}
|