UNPKG

@silexlabs/silex

Version:

Free and easy website builder for everyone.

193 lines (181 loc) 5.92 kB
/* * Silex website builder, free/libre no-code tool for makers. * Copyright (c) 2023 lexoyo and Silex Labs foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import {html, render} from 'lit-html' import {map} from 'lit-html/directives/map.js' // constants const pluginName = 'semantic' // Group tags by category const tagCategories = [ { label: 'Containers', tags: ['DIV', 'SPAN', 'P'], }, { label: 'Headings', tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'], }, { label: 'Main document structure', tags: ['MAIN', 'SECTION', 'ARTICLE', 'NAV', 'ASIDE', 'HEADER', 'FOOTER'], }, { label: 'Content grouping', tags: ['ADDRESS', 'BLOCKQUOTE', 'PRE'], }, { label: 'Lists', tags: ['UL', 'OL', 'LI'], }, { label: 'Interactive/form', tags: ['BUTTON', 'LABEL', 'DETAILS', 'SUMMARY'], }, ] // Flatten all tags for lookup const tags = tagCategories.flatMap(cat => cat.tags) // plugin code export const semanticPlugin = (editor, opts) => { // Helper to merge extra traits into a type's default traits function withExtraTraits(type, extraTraits) { const origTraits = editor.DomComponents.getType(type.id).model.prototype.defaults.traits return [ ...origTraits, ...extraTraits, ] } // Traits to add to all components const extraTraits = [ { label: 'Tag name', type: 'tag-name', name: 'tag-name', }, { label: 'For', name: 'for', type: 'for-trait', placeholder: 'ID of input', } ] // Add the extra traits to all component types editor.DomComponents.getTypes().forEach(type => { editor.DomComponents.addType(type.id, { model: { defaults: { traits: withExtraTraits(type, extraTraits), } } }) }) // Shared render function for select and for-trait function renderTrait(el: HTMLElement, opts: { tagName?: string, forAttr?: string, type: 'tag' | 'for' }) { const tagName = (opts.tagName || '').toUpperCase() if (opts.type === 'tag') { // Render tag select let categoriesWithCurrent = tagCategories.map(cat => ({ ...cat, tags: cat.tags.slice(), })) if (tagName && !tags.includes(tagName)) { categoriesWithCurrent = [ ...categoriesWithCurrent, { label: 'Other', tags: [tagName] } ] } render(html` <select @change=${event => renderTrait(el, { tagName: event.target.value, type: 'tag' })}> ${map(categoriesWithCurrent, cat => html` <optgroup label="${cat.label}"> ${map(cat.tags, tag => html` <option value="${tag}" ?selected=${tagName === tag}>${tag}</option> `)} </optgroup> `)} </select> `, el) } else if (opts.type === 'for') { // Render "for" input, hide if not LABEL const wrapper = el.closest('.gjs-trt-trait__wrp-for') as HTMLElement if (tagName !== 'LABEL') { if(wrapper) wrapper.style.display = 'none' render(html``, el) } else { if(wrapper) wrapper.style.display = 'initial' render(html` <input type="text" placeholder="ID of input" value="${opts.forAttr || ''}" @input=${event => renderTrait(el, { forAttr: (event.target as HTMLInputElement).value, tagName, type: 'for' })} > `, el) } } } function doRenderCurrent(el: HTMLElement) { renderTrait(el, { tagName: editor.getSelected()?.get('tagName') || '', type: 'tag' }) } function doRenderCurrentFor(el: HTMLElement) { const selected = editor.getSelected() renderTrait(el, { tagName: selected?.get('tagName') || '', forAttr: selected?.getAttributes().for || '', type: 'for' }) } // Add semantic traits // inspired by https://github.com/olivmonnier/grapesjs-plugin-header/blob/master/src/components.js editor.TraitManager.addType('tag-name', { createInput({ trait, component }) { const el = document.createElement('div') editor.on('page', () => doRenderCurrent(el)) doRenderCurrent(el) return el }, onEvent({ elInput, component, event }) { const value = (event.target as HTMLSelectElement).value if(component.get('tagName').toUpperCase() !== value.toUpperCase()){ component.set('tagName', value) } }, onUpdate({ elInput, component }) { const tagName = component.get('tagName') renderTrait(elInput, { tagName, type: 'tag' }) }, }) editor.TraitManager.addType('for-trait', { createInput({ trait, component }) { const el = document.createElement('div') editor.on('page', () => doRenderCurrentFor(el)) editor.on('component:update', () => doRenderCurrentFor(el)) doRenderCurrentFor(el) return el }, onEvent({ elInput, component, event }) { const value = (event.target as HTMLInputElement).value if(component.getAttributes().for !== value){ component.setAttributes({ for: value }) } }, onUpdate({ elInput, component }) { const forAttr = component.getAttributes().for || '' const tagName = component.get('tagName') || '' renderTrait(elInput, { forAttr, tagName, type: 'for' }) }, }) }