Files
mixtape/zero.Backoffice.UI/app/components/ui-rte/extensions/link/link.ts
T
2022-01-13 23:07:34 +01:00

253 lines
4.9 KiB
TypeScript

import { Mark, markPasteRule, mergeAttributes } from '@tiptap/core';
import { find } from 'linkifyjs';
import { autolink } from '@tiptap/extension-link/src/helpers/autolink';
import { clickHandler } from '@tiptap/extension-link/src/helpers/clickHandler';
import { pasteHandler } from '@tiptap/extension-link/src/helpers/pasteHandler';
import * as overlays from '../../../../services/overlay';
export interface ZeroLinkOptions
{
/**
* If enabled, it adds links as you type.
*/
autolink: boolean,
/**
* If enabled, links will be opened on click.
*/
openOnClick: boolean,
/**
* Adds a link to the current selection if the pasted content only contains an url.
*/
linkOnPaste: boolean,
/**
* A list of HTML attributes to be rendered.
*/
HTMLAttributes: Record<string, any>,
}
declare module '@tiptap/core' {
interface Commands<ReturnType>
{
zerolink: {
/**
* Pick a link
*/
pickLink: () => ReturnType,
/**
* Set a link mark from data
*/
setLinkData: (data: object) => ReturnType,
/**
* Set a link mark
*/
setLink: (attributes: { href: string, target?: string }) => ReturnType,
/**
* Toggle a link mark
*/
toggleLink: (attributes: { href: string, target?: string }) => ReturnType,
/**
* Unset a link mark
*/
unsetLink: () => ReturnType,
}
}
}
export async function pickLink(editor)
{
const result = await overlays.open({
component: () => import('../../../../modules/links/ui-linkpicker-overlay.vue'),
display: 'editor',
model: {
value: null,
areas: [],
allowTitle: false,
allowTarget: true,
allowSuffix: true
}
});
if (result.eventType == 'confirm')
{
return editor.chain().focus().setLinkData(result.value).run();
}
}
export const ZeroLink = Mark.create<ZeroLinkOptions>({
name: 'zerolink',
priority: 1000,
keepOnSplit: false,
inclusive()
{
return this.options.autolink
},
addOptions()
{
return {
openOnClick: true,
linkOnPaste: true,
autolink: true,
HTMLAttributes: {
target: '_self',
rel: 'noopener noreferrer nofollow',
},
}
},
addAttributes()
{
return {
href: {
default: null,
},
'zero-link': {
default: null
},
target: {
default: this.options.HTMLAttributes.target,
},
title: {
default: null
}
}
},
parseHTML()
{
return [
{ tag: 'a[href]' },
{ tag: 'a[zero-link]' }
]
},
renderHTML({ HTMLAttributes })
{
return [
'a',
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
]
},
addCommands()
{
return {
setLinkData: data => ({ chain }) =>
{
let attributes = {};
if (data.target !== 'default')
{
attributes.target = data.target;
}
if (data.title)
{
attributes.title = data.title;
}
let link = {
area: data.area
};
if (data.values)
{
link.values = data.values;
}
if (data.urlSuffix)
{
link.suffix = data.urlSuffix;
}
attributes['zero-link'] = JSON.stringify(link);
return chain()
.setMark(this.name, attributes)
.setMeta('preventAutolink', true)
.run()
},
setLink: attributes => ({ chain }) =>
{
return chain()
.setMark(this.name, attributes)
.setMeta('preventAutolink', true)
.run()
},
toggleLink: attributes => ({ chain }) =>
{
return chain()
.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
.setMeta('preventAutolink', true)
.run()
},
unsetLink: () => ({ chain }) =>
{
return chain()
.unsetMark(this.name, { extendEmptyMarkRange: true })
.setMeta('preventAutolink', true)
.run()
},
}
},
addPasteRules()
{
return [
markPasteRule({
find: text => find(text)
.filter(link => link.isLink)
.map(link => ({
text: link.value,
index: link.start,
data: link,
})),
type: this.type,
getAttributes: match => ({
href: match.data?.href,
}),
}),
]
},
addProseMirrorPlugins()
{
const plugins = []
if (this.options.autolink)
{
plugins.push(autolink({
type: this.type,
}))
}
if (this.options.openOnClick)
{
plugins.push(clickHandler({
type: this.type,
}))
}
if (this.options.linkOnPaste)
{
plugins.push(pasteHandler({
editor: this.editor,
type: this.type,
}))
}
return plugins
},
})