Socket
Socket
Sign inDemoInstall

@ckeditor/ckeditor5-link

Package Overview
Dependencies
Maintainers
1
Versions
622
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-link - npm Package Compare versions

Comparing version 19.0.1 to 20.0.0

src/linkimage.js

1

lang/contexts.json

@@ -5,2 +5,3 @@ {

"Link URL": "Label for the URL input in the Link URL editing balloon.",
"Link image": "Label for the image link button.",
"Edit link": "Button opening the Link URL editing balloon.",

@@ -7,0 +8,0 @@ "Open link in new tab": "Button opening the link in new browser tab.",

32

package.json
{
"name": "@ckeditor/ckeditor5-link",
"version": "19.0.1",
"version": "20.0.0",
"description": "Link feature for CKEditor 5.",

@@ -13,20 +13,22 @@ "keywords": [

"dependencies": {
"@ckeditor/ckeditor5-core": "^19.0.1",
"@ckeditor/ckeditor5-engine": "^19.0.1",
"@ckeditor/ckeditor5-ui": "^19.0.1",
"lodash-es": "^4.17.10"
"@ckeditor/ckeditor5-core": "^20.0.0",
"@ckeditor/ckeditor5-engine": "^20.0.0",
"@ckeditor/ckeditor5-image": "^20.0.0",
"@ckeditor/ckeditor5-ui": "^20.0.0",
"@ckeditor/ckeditor5-utils": "^20.0.0",
"lodash-es": "^4.17.15"
},
"devDependencies": {
"@ckeditor/ckeditor5-block-quote": "^19.0.1",
"@ckeditor/ckeditor5-clipboard": "^19.0.1",
"@ckeditor/ckeditor5-editor-classic": "^19.0.1",
"@ckeditor/ckeditor5-enter": "^19.0.1",
"@ckeditor/ckeditor5-paragraph": "^19.1.0",
"@ckeditor/ckeditor5-theme-lark": "^19.1.0",
"@ckeditor/ckeditor5-typing": "^19.0.1",
"@ckeditor/ckeditor5-undo": "^19.0.1",
"@ckeditor/ckeditor5-utils": "^19.0.1"
"@ckeditor/ckeditor5-basic-styles": "^20.0.0",
"@ckeditor/ckeditor5-block-quote": "^20.0.0",
"@ckeditor/ckeditor5-clipboard": "^20.0.0",
"@ckeditor/ckeditor5-editor-classic": "^20.0.0",
"@ckeditor/ckeditor5-enter": "^20.0.0",
"@ckeditor/ckeditor5-paragraph": "^20.0.0",
"@ckeditor/ckeditor5-theme-lark": "^20.0.0",
"@ckeditor/ckeditor5-typing": "^20.0.0",
"@ckeditor/ckeditor5-undo": "^20.0.0"
},
"engines": {
"node": ">=8.0.0",
"node": ">=12.0.0",
"npm": ">=5.7.1"

@@ -33,0 +35,0 @@ },

@@ -61,2 +61,24 @@ /**

/**
* When set, the editor will add the given protocol to the link when the user creates a link without one.
* For example, when the user is creating a link and types `ckeditor.com` in the link form input — during link submission —
* the editor will automatically add the `http://` protocol, so the link will be as follows: `http://ckeditor.com`.
*
* The feature also comes with an email auto-detection. When you submit `hello@example.com`
* the plugin will automatically change it to `mailto:hello@example.com`.
*
* ClassicEditor
* .create( editorElement, {
* link: {
* defaultProtocol: 'http://'
* }
* } )
* .then( ... )
* .catch( ... );
*
* **NOTE:** In case no configuration is provided, the editor won't auto-fix the links.
*
* @member {String} module:link/link~LinkConfig#defaultProtocol
*/
/**
* When set to `true`, the `target="blank"` and `rel="noopener noreferrer"` attributes are automatically added to all external links

@@ -63,0 +85,0 @@ * in the editor. "External links" are all links in the editor content starting with `http`, `https`, or `//`.

@@ -14,2 +14,3 @@ /**

import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import first from '@ckeditor/ckeditor5-utils/src/first';

@@ -61,9 +62,17 @@ /**

this.value = doc.selection.getAttribute( 'linkHref' );
const selectedElement = first( doc.selection.getSelectedBlocks() );
// A check for the `LinkImage` plugin. If the selection contains an element, get values from the element.
// Currently the selection reads attributes from text nodes only. See #7429 and #7465.
if ( selectedElement && selectedElement.is( 'image' ) && model.schema.checkAttribute( 'image', 'linkHref' ) ) {
this.value = selectedElement.getAttribute( 'linkHref' );
this.isEnabled = model.schema.checkAttribute( selectedElement, 'linkHref' );
} else {
this.value = doc.selection.getAttribute( 'linkHref' );
this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'linkHref' );
}
for ( const manualDecorator of this.manualDecorators ) {
manualDecorator.value = this._getDecoratorStateFromModel( manualDecorator.id );
}
this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'linkHref' );
}

@@ -168,4 +177,4 @@

// Create new range wrapping changed link.
writer.setSelection( linkRange );
// Put the selection at the end of the updated link.
writer.setSelection( writer.createPositionAfter( linkRange.end.nodeBefore ) );
}

@@ -188,11 +197,37 @@ // If not then insert text node with `linkHref` attribute in place of caret.

// Create new range wrapping created node.
writer.setSelection( writer.createRangeOn( node ) );
// Put the selection at the end of the inserted link.
writer.setSelection( writer.createPositionAfter( node ) );
}
// Remove the `linkHref` attribute and all link decorators from the selection.
// It stops adding a new content into the link element.
[ 'linkHref', ...truthyManualDecorators, ...falsyManualDecorators ].forEach( item => {
writer.removeSelectionAttribute( item );
} );
} else {
// If selection has non-collapsed ranges, we change attribute on nodes inside those ranges
// omitting nodes where `linkHref` attribute is disallowed.
// omitting nodes where the `linkHref` attribute is disallowed.
const ranges = model.schema.getValidRanges( selection.getRanges(), 'linkHref' );
// But for the first, check whether the `linkHref` attribute is allowed on selected blocks (e.g. the "image" element).
const allowedRanges = [];
for ( const element of selection.getSelectedBlocks() ) {
if ( model.schema.checkAttribute( element, 'linkHref' ) ) {
allowedRanges.push( writer.createRangeOn( element ) );
}
}
// Ranges that accept the `linkHref` attribute. Since we will iterate over `allowedRanges`, let's clone it.
const rangesToUpdate = allowedRanges.slice();
// For all selection ranges we want to check whether given range is inside an element that accepts the `linkHref` attribute.
// If so, we don't want to propagate applying the attribute to its children.
for ( const range of ranges ) {
if ( this._isRangeToUpdate( range, allowedRanges ) ) {
rangesToUpdate.push( range );
}
}
for ( const range of rangesToUpdate ) {
writer.setAttribute( 'linkHref', href, range );

@@ -223,2 +258,21 @@

}
/**
* Checks whether specified `range` is inside an element that accepts the `linkHref` attribute.
*
* @private
* @param {module:engine/view/range~Range} range A range to check.
* @param {Array.<module:engine/view/range~Range>} allowedRanges An array of ranges created on elements where the attribute is accepted.
* @returns {Boolean}
*/
_isRangeToUpdate( range, allowedRanges ) {
for ( const allowedRange of allowedRanges ) {
// A range is inside an element that will have the `linkHref` attribute. Do not modify its nodes.
if ( allowedRange.containsRange( range ) ) {
return false;
}
}
return true;
}
}

@@ -11,9 +11,11 @@ /**

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
import bindTwoStepCaretToAttribute from '@ckeditor/ckeditor5-engine/src/utils/bindtwostepcarettoattribute';
import LinkCommand from './linkcommand';
import UnlinkCommand from './unlinkcommand';
import { createLinkElement, ensureSafeUrl, getLocalizedDecorators, normalizeDecorators } from './utils';
import AutomaticDecorators from './utils/automaticdecorators';
import ManualDecorator from './utils/manualdecorator';
import bindTwoStepCaretToAttribute from '@ckeditor/ckeditor5-engine/src/utils/bindtwostepcarettoattribute';
import findLinkRange from './findlinkrange';
import { createLinkElement, ensureSafeUrl, getLocalizedDecorators, normalizeDecorators } from './utils';
import '../theme/link.css';

@@ -108,2 +110,5 @@

this._enableInsertContentSelectionAttributesFixer();
// Handle a click at the beginning/end of a link element.
this._enableClickingAfterLink();
}

@@ -352,2 +357,63 @@

}
/**
* Starts listening to {@link module:engine/view/document~Document#event:mousedown} and
* {@link module:engine/view/document~Document#event:selectionChange} and puts the selection before/after a link node
* if clicked at the beginning/ending of the link.
*
* The purpose of this action is to allow typing around the link node directly after a click.
*
* See https://github.com/ckeditor/ckeditor5/issues/1016.
*
* @private
*/
_enableClickingAfterLink() {
const editor = this.editor;
editor.editing.view.addObserver( MouseObserver );
let clicked = false;
// Detect the click.
this.listenTo( editor.editing.view.document, 'mousedown', () => {
clicked = true;
} );
// When the selection has changed...
this.listenTo( editor.editing.view.document, 'selectionChange', () => {
if ( !clicked ) {
return;
}
// ...and it was caused by the click...
clicked = false;
const selection = editor.model.document.selection;
// ...and no text is selected...
if ( !selection.isCollapsed ) {
return;
}
// ...and clicked text is the link...
if ( !selection.hasAttribute( 'linkHref' ) ) {
return;
}
const position = selection.getFirstPosition();
const linkRange = findLinkRange( position, selection.getAttribute( 'linkHref' ), editor.model );
// ...check whether clicked start/end boundary of the link.
// If so, remove the `linkHref` attribute.
if ( position.isTouching( linkRange.start ) || position.isTouching( linkRange.end ) ) {
editor.model.change( writer => {
writer.removeSelectionAttribute( 'linkHref' );
for ( const manualDecorator of editor.commands.get( 'link' ).manualDecorators ) {
writer.removeSelectionAttribute( manualDecorator.id );
}
} );
}
} );
}
}

@@ -12,3 +12,4 @@ /**

import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
import { isLinkElement } from './utils';
import { isLinkElement, LINK_KEYSTROKE } from './utils';
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';

@@ -24,3 +25,5 @@

const linkKeystroke = 'Ctrl+K';
const protocolRegExp = /^((\w+:(\/{2,})?)|(\W))/i;
const emailRegExp = /[\w-]+@[\w-]+\.+[\w-]+/i;
const VISUAL_SELECTION_MARKER_NAME = 'link-ui';

@@ -85,2 +88,19 @@ /**

this._enableUserBalloonInteractions();
// Renders a fake visual selection marker on an expanded selection.
editor.conversion.for( 'downcast' ).markerToHighlight( {
model: VISUAL_SELECTION_MARKER_NAME,
view: {
classes: [ 'ck-fake-link-selection' ]
}
} );
// Renders a fake visual selection marker on a collapsed selection.
editor.conversion.for( 'downcast' ).markerToElement( {
model: VISUAL_SELECTION_MARKER_NAME,
view: {
name: 'span',
classes: [ 'ck-fake-link-selection', 'ck-fake-link-selection_collapsed' ]
}
} );
}

@@ -132,3 +152,3 @@

// Open the form view on Ctrl+K when the **actions have focus**..
actionsView.keystrokes.set( linkKeystroke, ( data, cancel ) => {
actionsView.keystrokes.set( LINK_KEYSTROKE, ( data, cancel ) => {
this._addFormView();

@@ -150,4 +170,5 @@ cancel();

const linkCommand = editor.commands.get( 'link' );
const defaultProtocol = editor.config.get( 'link.defaultProtocol' );
const formView = new LinkFormView( editor.locale, linkCommand );
const formView = new LinkFormView( editor.locale, linkCommand, defaultProtocol );

@@ -162,3 +183,13 @@ formView.urlInputView.fieldView.bind( 'value' ).to( linkCommand, 'value' );

this.listenTo( formView, 'submit', () => {
editor.execute( 'link', formView.urlInputView.fieldView.element.value, formView.getDecoratorSwitchesState() );
const { value } = formView.urlInputView.fieldView.element;
// The regex checks for the protocol syntax ('xxxx://' or 'xxxx:')
// or non-word characters at the beginning of the link ('/', '#' etc.).
const isProtocolNeeded = !!defaultProtocol && !protocolRegExp.test( value );
const isEmail = emailRegExp.test( value );
const protocol = isEmail ? 'mailto:' : defaultProtocol;
const parsedValue = value && isProtocolNeeded ? protocol + value : value;
editor.execute( 'link', parsedValue, formView.getDecoratorSwitchesState() );
this._closeFormView();

@@ -193,3 +224,3 @@ } );

// Handle the `Ctrl+K` keystroke and show the panel.
editor.keystrokes.set( linkKeystroke, ( keyEvtData, cancel ) => {
editor.keystrokes.set( LINK_KEYSTROKE, ( keyEvtData, cancel ) => {
// Prevent focusing the search bar in FF, Chrome and Edge. See https://github.com/ckeditor/ckeditor5/issues/4811.

@@ -207,3 +238,3 @@ cancel();

button.icon = linkIcon;
button.keystroke = linkKeystroke;
button.keystroke = LINK_KEYSTROKE;
button.tooltip = true;

@@ -360,2 +391,4 @@ button.isToggleable = true;

this.editor.editing.view.focus();
this._hideFakeVisualSelection();
}

@@ -381,2 +414,5 @@ }

this._addFormView();
// Show visual selection on a text without a link when the contextual balloon is displayed.
// See https://github.com/ckeditor/ckeditor5/issues/4721.
this._showFakeVisualSelection();
}

@@ -430,2 +466,4 @@ // If there's a link under the selection...

this._balloon.remove( this.actionsView );
this._hideFakeVisualSelection();
}

@@ -610,2 +648,42 @@

}
/**
* Displays a fake visual selection when the contextual balloon is displayed.
*
* This adds a 'link-ui' marker into the document that is rendered as a highlight on selected text fragment.
*
* @private
*/
_showFakeVisualSelection() {
const model = this.editor.model;
model.change( writer => {
if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
writer.updateMarker( VISUAL_SELECTION_MARKER_NAME, {
range: model.document.selection.getFirstRange()
} );
} else {
writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
usingOperation: false,
affectsData: false,
range: model.document.selection.getFirstRange()
} );
}
} );
}
/**
* Hides the fake visual selection created in {@link #_showFakeVisualSelection}.
*
* @private
*/
_hideFakeVisualSelection() {
const model = this.editor.model;
if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
model.change( writer => {
writer.removeMarker( VISUAL_SELECTION_MARKER_NAME );
} );
}
}
}

@@ -612,0 +690,0 @@

@@ -43,4 +43,5 @@ /**

* @param {module:link/linkcommand~LinkCommand} linkCommand Reference to {@link module:link/linkcommand~LinkCommand}.
* @param {String} [protocol] A value of a protocol to be displayed in the input's placeholder.
*/
constructor( locale, linkCommand ) {
constructor( locale, linkCommand, protocol ) {
super( locale );

@@ -71,3 +72,3 @@

*/
this.urlInputView = this._createUrlInput();
this.urlInputView = this._createUrlInput( protocol );

@@ -212,11 +213,11 @@ /**

* @private
* @param {String} [protocol=http://] A value of a protocol to be displayed in the input's placeholder.
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} Labeled field view instance.
*/
_createUrlInput() {
_createUrlInput( protocol = 'https://' ) {
const t = this.locale.t;
const labeledInput = new LabeledFieldView( this.locale, createLabeledInputText );
labeledInput.label = t( 'Link URL' );
labeledInput.fieldView.placeholder = 'https://example.com';
labeledInput.fieldView.placeholder = protocol + 'example.com';

@@ -223,0 +224,0 @@ return labeledInput;

@@ -12,2 +12,3 @@ /**

import findLinkRange from './findlinkrange';
import first from '@ckeditor/ckeditor5-utils/src/first';

@@ -24,3 +25,14 @@ /**

refresh() {
this.isEnabled = this.editor.model.document.selection.hasAttribute( 'linkHref' );
const model = this.editor.model;
const doc = model.document;
const selectedElement = first( doc.selection.getSelectedBlocks() );
// A check for the `LinkImage` plugin. If the selection contains an image element, get values from the element.
// Currently the selection reads attributes from text nodes only. See #7429 and #7465.
if ( selectedElement && selectedElement.is( 'image' ) && model.schema.checkAttribute( 'image', 'linkHref' ) ) {
this.isEnabled = model.schema.checkAttribute( selectedElement, 'linkHref' );
} else {
this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'linkHref' );
}
}

@@ -27,0 +39,0 @@

@@ -16,2 +16,7 @@ /**

/**
* A keystroke used by the {@link module:link/linkui~LinkUI link UI feature}.
*/
export const LINK_KEYSTROKE = 'Ctrl+K';
/**
* Returns `true` if a given view node is the link element.

@@ -18,0 +23,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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc