You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

@tiptap/extension-link

Package Overview
Dependencies
Maintainers
2
Versions
255
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tiptap/extension-link - npm Package Compare versions

Comparing version

to
2.0.0-beta.31

dist/packages/extension-link/src/helpers/autolink.d.ts

4

dist/packages/extension-link/src/link.d.ts
import { Mark } from '@tiptap/core';
export interface LinkOptions {
/**
* If enabled, it adds links as you type.
*/
autolink: boolean;
/**
* If enabled, links will be opened on click.

@@ -5,0 +9,0 @@ */

178

dist/tiptap-extension-link.cjs.js

@@ -6,9 +6,127 @@ 'use strict';

var core = require('@tiptap/core');
var linkifyjs = require('linkifyjs');
var prosemirrorState = require('prosemirror-state');
var linkifyjs = require('linkifyjs');
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);
if (!docChanges) {
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);
const newLinkText = newState.doc.textBetween(newMark.from, newMark.to);
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
core.findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
.forEach(textBlock => {
linkifyjs.find(textBlock.node.textContent)
.filter(link => link.isLink)
// calculate link position
.map(link => ({
...link,
from: textBlock.pos + link.start + 1,
to: textBlock.pos + link.end + 1,
}))
// check if link is within the changed range
.filter(link => {
const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to;
const toIsInRange = newRange.to >= link.from && newRange.to <= link.to;
return fromIsInRange || toIsInRange;
})
// 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,
inclusive: false,
keepOnSplit: false,
inclusive() {
return this.options.autolink;
},
addOptions() {

@@ -18,2 +136,3 @@ return {

linkOnPaste: true,
autolink: true,
HTMLAttributes: {

@@ -41,3 +160,7 @@ target: '_blank',

renderHTML({ HTMLAttributes }) {
return ['a', core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
return [
'a',
core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
];
},

@@ -79,45 +202,16 @@ addCommands() {

const plugins = [];
if (this.options.autolink) {
plugins.push(autolink({
type: this.type,
}));
}
if (this.options.openOnClick) {
plugins.push(new prosemirrorState.Plugin({
key: new prosemirrorState.PluginKey('handleClickLink'),
props: {
handleClick: (view, pos, event) => {
var _a;
const attrs = this.editor.getAttributes(this.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;
},
},
plugins.push(clickHandler({
type: this.type,
}));
}
if (this.options.linkOnPaste) {
plugins.push(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;
}
this.editor.commands.setMark(this.type, {
href: link.href,
});
return true;
},
},
plugins.push(pasteHandler({
editor: this.editor,
type: this.type,
}));

@@ -124,0 +218,0 @@ }

@@ -1,9 +0,127 @@

import { Mark, mergeAttributes, markPasteRule } from '@tiptap/core';
import { combineTransactionSteps, getChangedRanges, getMarksBetween, findChildrenInRange, getAttributes, Mark, mergeAttributes, markPasteRule } from '@tiptap/core';
import { test, find } from 'linkifyjs';
import { Plugin, PluginKey } from 'prosemirror-state';
import { find } from 'linkifyjs';
function autolink(options) {
return new Plugin({
key: new PluginKey('autolink'),
appendTransaction: (transactions, oldState, newState) => {
const docChanges = transactions.some(transaction => transaction.docChanged)
&& !oldState.doc.eq(newState.doc);
if (!docChanges) {
return;
}
const { tr } = newState;
const transform = combineTransactionSteps(oldState.doc, transactions);
const { mapping } = transform;
const changes = getChangedRanges(transform);
changes.forEach(({ oldRange, newRange }) => {
// at first we check if we have to remove links
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 = 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);
const newLinkText = newState.doc.textBetween(newMark.from, newMark.to);
const wasLink = test(oldLinkText);
const isLink = 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
findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
.forEach(textBlock => {
find(textBlock.node.textContent)
.filter(link => link.isLink)
// calculate link position
.map(link => ({
...link,
from: textBlock.pos + link.start + 1,
to: textBlock.pos + link.end + 1,
}))
// check if link is within the changed range
.filter(link => {
const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to;
const toIsInRange = newRange.to >= link.from && newRange.to <= link.to;
return fromIsInRange || toIsInRange;
})
// 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 Plugin({
key: new PluginKey('handleClickLink'),
props: {
handleClick: (view, pos, event) => {
var _a;
const attrs = 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 Plugin({
key: new 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 = 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 = Mark.create({
name: 'link',
priority: 1000,
inclusive: false,
keepOnSplit: false,
inclusive() {
return this.options.autolink;
},
addOptions() {

@@ -13,2 +131,3 @@ return {

linkOnPaste: true,
autolink: true,
HTMLAttributes: {

@@ -36,3 +155,7 @@ target: '_blank',

renderHTML({ HTMLAttributes }) {
return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
return [
'a',
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
];
},

@@ -74,45 +197,16 @@ addCommands() {

const plugins = [];
if (this.options.autolink) {
plugins.push(autolink({
type: this.type,
}));
}
if (this.options.openOnClick) {
plugins.push(new Plugin({
key: new PluginKey('handleClickLink'),
props: {
handleClick: (view, pos, event) => {
var _a;
const attrs = this.editor.getAttributes(this.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;
},
},
plugins.push(clickHandler({
type: this.type,
}));
}
if (this.options.linkOnPaste) {
plugins.push(new Plugin({
key: new 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 = find(textContent)
.find(item => item.isLink && item.value === textContent);
if (!textContent || !link) {
return false;
}
this.editor.commands.setMark(this.type, {
href: link.href,
});
return true;
},
},
plugins.push(pasteHandler({
editor: this.editor,
type: this.type,
}));

@@ -119,0 +213,0 @@ }

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('prosemirror-state'), require('linkifyjs')) :
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', 'prosemirror-state', 'linkifyjs'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-link"] = {}, global.core, global.prosemirrorState, global.linkifyjs));
})(this, (function (exports, core, prosemirrorState, linkifyjs) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('linkifyjs'), require('prosemirror-state')) :
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', 'linkifyjs', 'prosemirror-state'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-link"] = {}, global.core, global.linkifyjs, global.prosemirrorState));
})(this, (function (exports, core, linkifyjs, prosemirrorState) { 'use strict';
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);
if (!docChanges) {
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);
const newLinkText = newState.doc.textBetween(newMark.from, newMark.to);
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
core.findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
.forEach(textBlock => {
linkifyjs.find(textBlock.node.textContent)
.filter(link => link.isLink)
// calculate link position
.map(link => ({
...link,
from: textBlock.pos + link.start + 1,
to: textBlock.pos + link.end + 1,
}))
// check if link is within the changed range
.filter(link => {
const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to;
const toIsInRange = newRange.to >= link.from && newRange.to <= link.to;
return fromIsInRange || toIsInRange;
})
// 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,
inclusive: false,
keepOnSplit: false,
inclusive() {
return this.options.autolink;
},
addOptions() {

@@ -15,2 +133,3 @@ return {

linkOnPaste: true,
autolink: true,
HTMLAttributes: {

@@ -38,3 +157,7 @@ target: '_blank',

renderHTML({ HTMLAttributes }) {
return ['a', core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
return [
'a',
core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
];
},

@@ -76,45 +199,16 @@ addCommands() {

const plugins = [];
if (this.options.autolink) {
plugins.push(autolink({
type: this.type,
}));
}
if (this.options.openOnClick) {
plugins.push(new prosemirrorState.Plugin({
key: new prosemirrorState.PluginKey('handleClickLink'),
props: {
handleClick: (view, pos, event) => {
var _a;
const attrs = this.editor.getAttributes(this.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;
},
},
plugins.push(clickHandler({
type: this.type,
}));
}
if (this.options.linkOnPaste) {
plugins.push(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;
}
this.editor.commands.setMark(this.type, {
href: link.href,
});
return true;
},
},
plugins.push(pasteHandler({
editor: this.editor,
type: this.type,
}));

@@ -121,0 +215,0 @@ }

{
"name": "@tiptap/extension-link",
"description": "link extension for tiptap",
"version": "2.0.0-beta.30",
"version": "2.0.0-beta.31",
"homepage": "https://tiptap.dev",

@@ -28,2 +28,3 @@ "keywords": [

"linkifyjs": "^3.0.4",
"prosemirror-model": "^1.15.0",
"prosemirror-state": "^1.3.4"

@@ -36,3 +37,3 @@ },

},
"gitHead": "6360278660d9c1e256975699d911a55dfa943d5d"
"gitHead": "4e1a50250bc5d3ef916326e5bed30c93448284b3"
}

@@ -1,11 +0,13 @@

import {
Mark,
markPasteRule,
mergeAttributes,
} from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
import { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'
import { find } from 'linkifyjs'
import autolink from './helpers/autolink'
import clickHandler from './helpers/clickHandler'
import pasteHandler from './helpers/pasteHandler'
export interface LinkOptions {
/**
* If enabled, it adds links as you type.
*/
autolink: boolean,
/**
* If enabled, links will be opened on click.

@@ -48,4 +50,8 @@ */

inclusive: false,
keepOnSplit: false,
inclusive() {
return this.options.autolink
},
addOptions() {

@@ -55,2 +61,3 @@ return {

linkOnPaste: true,
autolink: true,
HTMLAttributes: {

@@ -81,3 +88,7 @@ target: '_blank',

renderHTML({ HTMLAttributes }) {
return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
return [
'a',
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
]
},

@@ -90,5 +101,7 @@

},
toggleLink: attributes => ({ commands }) => {
return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
},
unsetLink: () => ({ commands }) => {

@@ -121,60 +134,19 @@ return commands.unsetMark(this.name, { extendEmptyMarkRange: true })

if (this.options.autolink) {
plugins.push(autolink({
type: this.type,
}))
}
if (this.options.openOnClick) {
plugins.push(
new Plugin({
key: new PluginKey('handleClickLink'),
props: {
handleClick: (view, pos, event) => {
const attrs = this.editor.getAttributes(this.name)
const link = (event.target as HTMLElement)?.closest('a')
if (link && attrs.href) {
window.open(attrs.href, attrs.target)
return true
}
return false
},
},
}),
)
plugins.push(clickHandler({
type: this.type,
}))
}
if (this.options.linkOnPaste) {
plugins.push(
new Plugin({
key: new 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 = find(textContent)
.find(item => item.isLink && item.value === textContent)
if (!textContent || !link) {
return false
}
this.editor.commands.setMark(this.type, {
href: link.href,
})
return true
},
},
}),
)
plugins.push(pasteHandler({
editor: this.editor,
type: this.type,
}))
}

@@ -181,0 +153,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet