@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.
@@ -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: 'https://example.com/image.jpg',
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 automatic changes and revert them if needed
143
- sector.on('change:visible', (model, open) => {
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
- const tags = [
25
- 'DIV',
26
- 'P',
27
- 'H1',
28
- 'H2',
29
- 'H3',
30
- 'H4',
31
- 'H5',
32
- 'H6',
33
- 'SPAN',
34
- 'MAIN',
35
- 'ASIDE',
36
- 'SECTION',
37
- 'ADDRESS',
38
- 'ARTICLE',
39
- 'BUTTON',
40
- 'NAV',
41
- 'HEADER',
42
- 'FOOTER',
43
- 'DETAILS',
44
- 'SUMMARY',
45
- 'PRE',
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
- // Add the new trait to all component types
52
- editor.DomComponents.getTypes().map(type => {
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 doRender(el: HTMLElement, tagName: string) {
72
- const tagsWithCurrent = tags.includes(tagName.toUpperCase()) ? tags : tags.concat(tagName.toUpperCase())
73
- render(html`
74
- <label for="semantic__select" class="gjs-one-bg silex-label">Type</label>
75
- <select id="semantic__select" @change=${event => doRender(el, event.target.value)}>
76
- ${map<string>(tagsWithCurrent, tag => html`
77
- <option value="${tag}" ?selected=${tagName.toUpperCase() === tag}>${tag}</option>
78
- `)}
79
- </select>
80
- `, el)
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
- doRender(el, editor.getSelected()?.get('tagName') || '')
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 = elInput.querySelector('#semantic__select').value
102
- if(component.get('tagName').toUpperCase() === value.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
- doRender(elInput, tagName)
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, opts) => {
21
- function getNext(prefix) {
22
+ export default (editor: Editor) => {
23
+ function getNext(prefix: string) {
22
24
  let idx = 1
23
- while(editor.Symbols.find(s => s.get('label') === prefix + idx)) {
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: (_, sender) => {
34
+ run: (_) => {
30
35
  const label = prompt('Label', getNext('Symbol '))
31
- const icon = 'fa-gem'
32
- editor.runCommand('symbols:add', { label, icon })
36
+ editor.runCommand('symbols:add', { label })
33
37
  }
34
38
  })
35
39
  }