import {html, render} from 'lit-html'
import {map} from 'lit-html/directives/map.js'
import grapesjs from 'grapesjs/dist/grapes.min.js'
import { Page } from '../../types'
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 = grapesjs.plugins.add(pluginName, (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 => {
console.log(component.getAttributes().href, getPageLink(page.previous('name')), page.previous('name'))
if(component.getAttributes().href === getPageLink(page.previous('name'))) {
issues.push(component)
}
})
opts?.onError(issues)
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`
`
case 'page':
return html`
`
case 'url':
return html`
`
default:
return ''
}
}
function getDefaultHref(type) {
switch(type) {
case 'email': return 'mailto:'
case 'page': return getPageLink()
case 'url': return 'https://'
}
}
function doRender(el: HTMLElement, href = '') {
const type = getType(href)
const ui = getUi(type, href)
render(html`
${ 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)
},
})
})