'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var core = require('@tiptap/core'); var linkifyjs = require('linkifyjs'); var prosemirrorState = require('prosemirror-state'); function autolink(options) { return new prosemirrorState.Plugin({ key: new prosemirrorState.PluginKey('autolink'), appendTransaction: (transactions, oldState, newState) => { const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc); const preventAutolink = transactions.some(transaction => transaction.getMeta('preventAutolink')); if (!docChanges || preventAutolink) { return; } const { tr } = newState; const transform = core.combineTransactionSteps(oldState.doc, [...transactions]); const { mapping } = transform; const changes = core.getChangedRanges(transform); changes.forEach(({ oldRange, newRange }) => { // at first we check if we have to remove links core.getMarksBetween(oldRange.from, oldRange.to, oldState.doc) .filter(item => item.mark.type === options.type) .forEach(oldMark => { const newFrom = mapping.map(oldMark.from); const newTo = mapping.map(oldMark.to); const newMarks = core.getMarksBetween(newFrom, newTo, newState.doc) .filter(item => item.mark.type === options.type); if (!newMarks.length) { return; } const newMark = newMarks[0]; const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to, undefined, ' '); const newLinkText = newState.doc.textBetween(newMark.from, newMark.to, undefined, ' '); const wasLink = linkifyjs.test(oldLinkText); const isLink = linkifyjs.test(newLinkText); // remove only the link, if it was a link before too // because we don’t want to remove links that were set manually if (wasLink && !isLink) { tr.removeMark(newMark.from, newMark.to, options.type); } }); // now let’s see if we can add new links const nodesInChangedRanges = core.findChildrenInRange(newState.doc, newRange, node => node.isTextblock); let textBlock; let textBeforeWhitespace; if (nodesInChangedRanges.length > 1) { // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter) textBlock = nodesInChangedRanges[0]; textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, textBlock.pos + textBlock.node.nodeSize, undefined, ' '); } else if (nodesInChangedRanges.length // We want to make sure to include the block seperator argument to treat hard breaks like spaces && newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ')) { textBlock = nodesInChangedRanges[0]; textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, undefined, ' '); } if (textBlock && textBeforeWhitespace) { const wordsBeforeWhitespace = textBeforeWhitespace.split(' ').filter(s => s !== ''); if (wordsBeforeWhitespace.length <= 0) { return false; } const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1]; const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace); if (!lastWordBeforeSpace) { return false; } linkifyjs.find(lastWordBeforeSpace) .filter(link => link.isLink) .filter(link => { if (options.validate) { return options.validate(link.value); } return true; }) // calculate link position .map(link => ({ ...link, from: lastWordAndBlockOffset + link.start + 1, to: lastWordAndBlockOffset + link.end + 1, })) // add link mark .forEach(link => { tr.addMark(link.from, link.to, options.type.create({ href: link.href, })); }); } }); if (!tr.steps.length) { return; } return tr; }, }); } function clickHandler(options) { return new prosemirrorState.Plugin({ key: new prosemirrorState.PluginKey('handleClickLink'), props: { handleClick: (view, pos, event) => { var _a; const attrs = core.getAttributes(view.state, options.type.name); const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a'); if (link && attrs.href) { window.open(attrs.href, attrs.target); return true; } return false; }, }, }); } function pasteHandler(options) { return new prosemirrorState.Plugin({ key: new prosemirrorState.PluginKey('handlePasteLink'), props: { handlePaste: (view, event, slice) => { const { state } = view; const { selection } = state; const { empty } = selection; if (empty) { return false; } let textContent = ''; slice.content.forEach(node => { textContent += node.textContent; }); const link = linkifyjs.find(textContent).find(item => item.isLink && item.value === textContent); if (!textContent || !link) { return false; } options.editor.commands.setMark(options.type, { href: link.href, }); return true; }, }, }); } const Link = core.Mark.create({ name: 'link', priority: 1000, keepOnSplit: false, onCreate() { this.options.protocols.forEach(linkifyjs.registerCustomProtocol); }, onDestroy() { linkifyjs.reset(); }, inclusive() { return this.options.autolink; }, addOptions() { return { openOnClick: true, linkOnPaste: true, autolink: true, protocols: [], HTMLAttributes: { target: '_blank', rel: 'noopener noreferrer nofollow', class: null, }, validate: undefined, }; }, addAttributes() { return { href: { default: null, }, target: { default: this.options.HTMLAttributes.target, }, class: { default: this.options.HTMLAttributes.class, }, }; }, parseHTML() { return [ { tag: 'a[href]:not([href *= "javascript:" i])' }, ]; }, renderHTML({ HTMLAttributes }) { return [ 'a', core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0, ]; }, addCommands() { return { 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 [ core.markPasteRule({ find: text => linkifyjs.find(text) .filter(link => { if (this.options.validate) { return this.options.validate(link.value); } return true; }) .filter(link => link.isLink) .map(link => ({ text: link.value, index: link.start, data: link, })), type: this.type, getAttributes: match => { var _a; return ({ href: (_a = match.data) === null || _a === void 0 ? void 0 : _a.href, }); }, }), ]; }, addProseMirrorPlugins() { const plugins = []; if (this.options.autolink) { plugins.push(autolink({ type: this.type, validate: this.options.validate, })); } 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; }, }); exports.Link = Link; exports["default"] = Link; //# sourceMappingURL=tiptap-extension-link.cjs.map