diff --git a/docs/src/demos/Extensions/Link/index.vue b/docs/src/demos/Extensions/Link/index.vue index 7b0e6a98..eec5c0d4 100644 --- a/docs/src/demos/Extensions/Link/index.vue +++ b/docs/src/demos/Extensions/Link/index.vue @@ -10,7 +10,7 @@ import { EditorContent } from '@tiptap/vue' import Document from '@tiptap/extension-document' import Paragraph from '@tiptap/extension-paragraph' import Text from '@tiptap/extension-text' -// import Link from '@tiptap/extension-link' +import Link from '@tiptap/extension-link' export default { components: { @@ -29,7 +29,7 @@ export default { Document(), Paragraph(), Text(), - // Link(), + Link(), ], content: `

diff --git a/packages/core/index.ts b/packages/core/index.ts index 13b1a00a..822ac111 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -18,3 +18,4 @@ export { default as getHtmlFromFragment } from './src/utils/getHtmlFromFragment' export { default as getTopNodeFromExtensions } from './src/utils/getTopNodeFromExtensions' export { default as getNodesFromExtensions } from './src/utils/getNodesFromExtensions' export { default as getMarksFromExtensions } from './src/utils/getMarksFromExtensions' +export { default as getMarkAttrs } from './src/utils/getMarkAttrs' diff --git a/packages/extension-link/index.ts b/packages/extension-link/index.ts new file mode 100644 index 00000000..72124083 --- /dev/null +++ b/packages/extension-link/index.ts @@ -0,0 +1,85 @@ +import { + Command, Mark, markPasteRule, getMarkAttrs, +} from '@tiptap/core' +import { Plugin, PluginKey } from 'prosemirror-state' + +export interface LinkOptions { + openOnClick: boolean, + target: string, +} + +export type LinkCommand = () => Command + +declare module '@tiptap/core/src/Editor' { + interface Commands { + link: LinkCommand, + } +} + +export const pasteRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/gi + +export default new Mark() + .name('link') + .defaults({ + openOnClick: true, + target: '', + }) + .schema(({ options }) => ({ + attrs: { + href: { + default: null, + }, + target: { + default: null, + }, + }, + inclusive: false, + parseDOM: [ + { + tag: 'a[href]', + getAttrs: node => ({ + href: (node as HTMLElement).getAttribute('href'), + target: (node as HTMLElement).getAttribute('target'), + }), + }, + ], + toDOM: node => ['a', { + ...node.attrs, + rel: 'noopener noreferrer nofollow', + target: options.target, + }, 0], + })) + .commands(({ name }) => ({ + link: () => ({ commands }) => { + return commands.toggleMark(name) + }, + })) + .pasteRules(({ type }) => [ + markPasteRule(pasteRegex, type, (url: string) => ({ href: url })), + ]) + .plugins(({ options }) => { + if (!options.openOnClick) { + return [] + } + + return [ + new Plugin({ + key: new PluginKey('handleClick'), + props: { + handleClick: (view, pos, event) => { + const { schema } = view.state + const attrs = getMarkAttrs(view.state, schema.marks.link) + + if (attrs.href && event.target instanceof HTMLAnchorElement) { + window.open(attrs.href, attrs.target) + + return false + } + + return true + }, + }, + }), + ] + }) + .create() diff --git a/packages/extension-link/package.json b/packages/extension-link/package.json new file mode 100644 index 00000000..05cfae73 --- /dev/null +++ b/packages/extension-link/package.json @@ -0,0 +1,17 @@ +{ + "name": "@tiptap/extension-link", + "version": "1.0.0", + "source": "index.ts", + "main": "dist/tiptap-extension-link.js", + "umd:main": "dist/tiptap-extension-link.umd.js", + "module": "dist/tiptap-extension-link.mjs", + "unpkg": "dist/tiptap-extension-link.js", + "jsdelivr": "dist/tiptap-extension-link.js", + "files": [ + "src", + "dist" + ], + "peerDependencies": { + "@tiptap/core": "2.x" + } +}