UNPKG

@silexlabs/silex

Version:

Free and easy website builder for everyone.

235 lines (226 loc) 8.2 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' import grapesjs from 'grapesjs/dist/grapes.min.js' import { getPageLink } from '../../page' // constants const pluginName = 'internal-links' const options: grapesjs.SelectOption = [ { id: '', name: '-' }, { id: 'url', name: 'URL' }, { id: 'email', name: 'Email' }, { id: 'page', name: 'Page' }, ] // plugin code export const internalLinksPlugin = (editor, opts) => { // update links when a page link changes function onAll(cbk) { editor.Pages.getAll() .forEach(page => { page.getMainComponent() .onAll(cbk) }) } editor.on('page', ({ event, page }) => { if(!page) return // fixes UT switch (event) { case 'change:name': // update all links to this page onAll(component => { if(component.getAttributes().href === getPageLink(page.previous('name'))) { component.setAttributes({ href: getPageLink(page.get('name')), }) } }) break case 'remove': // mark all links as issues const issues = [] onAll(component => { if(component.getAttributes().href === getPageLink(page.previous('name'))) { issues.push(component) } }) // Add a notification for the issues if(issues.length) { issues.forEach(component => { editor.runCommand('notifications:add', { type: 'error', message: `Page ${page.previous('name')} was removed, please fix the following links:`, componentId: component.getId(), group: 'Broken internal links', }) }) } break } }) // utility functions to render the UI function getUi(type, href) { switch (type) { case 'email': const mailTo = href.replace('mailto:', '').split('?') const email = mailTo[0] const params = (mailTo[1] || '').split('&').reduce((acc, item) => { const items = item.split('=') acc[items[0]] = items[1] return acc }, {}) return html`<div class="href-next__email-inputs"> <label for="href-next__email" class="gjs-one-bg silex-label">Email address</label> <input class="href-next__email" placeholder="Insert email" type="email" value=${email || ''}/> <label for="href-next__email-subject" class="gjs-one-bg silex-label">Subject</label> <input class="href-next__email-subject" placeholder="Insert subject" value=${ params.subject || '' }/> </div>` case 'page': return html`<div class="href-next__page-inputs"> <label for="href-next__page" class="gjs-one-bg silex-label">Page name</label> <select id="href-next__page" class="href-next__page"> ${ map<grapesjs.Page>(editor.Pages.getAll(), page => html`<option ?selected=${getPageLink(page.getName()) === href} value=${getPageLink(page.getName())}> ${page.getName() || 'Main'} </option>`) } </select> </div>` case 'url': return html`<div class="href-next__url-inputs"> <label for="href-next__url" class="gjs-one-bg silex-label">URL</label> <input class="href-next__url" placeholder="Insert URL" type="url" value=${ href }/> </div>` default: return '' } } function getDefaultHref(type) { switch(type) { case 'email': return 'mailto:' case 'page': return getPageLink(null) case 'url': return 'https://' } } function doRender(el: HTMLElement, href = '') { const type = getType(href) const ui = getUi(type, href) render(html` <label for="href-next__type" class="gjs-one-bg silex-label">Type</label> <select id="href-next__type" class="href-next__type" @change=${event => doRender(el, getDefaultHref(event.target.value))}> ${map<grapesjs.SelectOption>(options, opt => html` <option value="${opt.id}" ?selected=${type === opt.id}>${opt.name}</option> `)} </select> ${ ui } `, el) } function getType(href: string) { if (href.indexOf('mailto:') === 0) { return 'email' } else if(href.indexOf('./') === 0){ return 'page' } else if(href){ return 'url' } return '' } function doRenderCurrent(el) { doRender(el, editor.getSelected()?.getAttributes().href || '') } // Add the new trait to all component types editor.DomComponents.getTypes().map(type => { editor.DomComponents.addType(type.id, { model: { defaults: { traits: [ // Keep the type original traits ...editor.DomComponents.getType(type.id).model.prototype.defaults.traits // Filter original href trait .filter(trait => trait.name ? trait.name !== 'href' : trait !== 'href'), // Add the new trait { label: 'Link', type: 'href-next', name: 'href', }, ] } } }) }) editor.TraitManager.addType('href-next', { createInput({ trait }) { // Create a new element container and add some content const el = document.createElement('div') // update the UI when a page is added/renamed/removed editor.on('page', () => doRenderCurrent(el)) doRenderCurrent(el) // this will be the element passed to onEvent and onUpdate return el }, // Update the component based on UI changes // `elInput` is the result HTMLElement you get from `createInput` onEvent({ elInput, component, event }) { const inputType = elInput.querySelector('.href-next__type') let href = '' // Compute the new HREF value switch (inputType.value) { case 'page': const valPage = elInput.querySelector('.href-next__page')?.value href = valPage || getDefaultHref('page') break case 'url': const valUrl = elInput.querySelector('.href-next__url')?.value href = valUrl || getDefaultHref('url') break case 'email': const valEmail = elInput.querySelector('.href-next__email')?.value const valSubj = elInput.querySelector('.href-next__email-subject')?.value href = valEmail ? `mailto:${valEmail}${valSubj ? `?subject=${valSubj}` : ''}` : getDefaultHref('email') break default: href = '' } // Store the new HREF value component.addAttributes({ href }) // Handle the tag name if(href === '') { // Not a link if(component.get('tagName').toUpperCase() === 'A'){ // Retrieve the original stored value const original = component.get('originalTagName') ?? 'DIV' // Case of the inline A tags inserted by the text bar const value = original.toUpperCase() === 'A' ? 'SPAN' : original component.set('tagName', value) } } else { // Link if(component.get('tagName').toUpperCase() !== 'A') { component.set('originalTagName', component.get('tagName')) component.set('tagName', 'A') } } }, // Update UI on the component change onUpdate({ elInput, component }) { const href = component.getAttributes().href || '' doRender(elInput, href) }, }) }