jodit
Version:
Jodit is awesome and usefully wysiwyg editor with filebrowser
323 lines (271 loc) • 7.67 kB
text/typescript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Released under MIT see LICENSE.txt in the project root for license information.
* Copyright (c) 2013-2020 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
*/
import { Config } from '../../config';
import { Dom } from '../../core/dom';
import {
attr,
convertMediaUrlToVideoEmbed,
isURL,
refs,
stripTags
} from '../../core/helpers';
import { Select } from '../../core/selection/select';
import { IDictionary, IJodit, IControlType, Nullable } from '../../types';
import { formTemplate } from './template';
/**
* @property {object} link `{@link link|link}` plugin's options
* @property {boolean} link.followOnDblClick=true Follow lnk address after dblclick
* @property {boolean} link.processVideoLink=true Replace inserted youtube/vimeo link toWYSIWYG `iframe`
* @property {boolean} link.processPastedLink=true Wrap inserted link in <a href="link">link</a>
* @property {boolean} link.removeLinkAfterFormat=true When the button is pressed toWYSIWYG clean format,
* if it was done on the link is removed like command `unlink`
*/
declare module '../../config' {
interface Config {
link: {
formTemplate: (editor: IJodit) => string;
formClassName?: string;
followOnDblClick: boolean;
processVideoLink: boolean;
processPastedLink: boolean;
removeLinkAfterFormat: boolean;
noFollowCheckbox: boolean;
openInNewTabCheckbox: boolean;
};
}
}
Config.prototype.link = {
formTemplate,
followOnDblClick: false,
processVideoLink: true,
processPastedLink: true,
removeLinkAfterFormat: true,
noFollowCheckbox: true,
openInNewTabCheckbox: true
};
Config.prototype.controls.unlink = {
exec: (editor: IJodit, current: Node) => {
const anchor: HTMLAnchorElement | false = Dom.closest(
current,
'a',
editor.editor
) as HTMLAnchorElement;
if (anchor) {
Dom.unwrap(anchor);
}
editor.setEditorValue();
editor.e.fire('hidePopup');
},
tooltip: 'Unlink'
} as IControlType;
Config.prototype.controls.link = {
isActive: (editor: IJodit): boolean => {
const current = editor.s.current();
return Boolean(current && Dom.closest(current, 'a', editor.editor));
},
popup: (editor: IJodit, current, self: IControlType, close: () => void) => {
const i18n = editor.i18n.bind(editor),
{
openInNewTabCheckbox,
noFollowCheckbox,
formTemplate,
formClassName
} = editor.o.link,
form = editor.c.fromHTML(formTemplate(editor), {
target_checkbox_box: openInNewTabCheckbox,
nofollow_checkbox_box: noFollowCheckbox
}) as HTMLFormElement;
const elements = refs(form),
{ insert, unlink, content_input_box } = elements,
{
target_checkbox,
nofollow_checkbox,
url_input
} = elements as IDictionary<HTMLInputElement>,
currentElement = current,
isImageContent = Dom.isImage(currentElement, editor.ew);
let { content_input } = elements as IDictionary<HTMLInputElement>;
if (!content_input) {
content_input = editor.c.element('input', {
type: 'hidden',
ref: 'content_input'
});
}
if (formClassName) {
form.classList.add(formClassName);
}
if (isImageContent) {
Dom.hide(content_input_box);
}
let link: false | HTMLAnchorElement;
const getSelectionText = () =>
link
? link.innerText
: stripTags(editor.s.range.cloneContents(), editor.ed);
if (current && Dom.closest(current, 'a', editor.editor)) {
link = Dom.closest(
current,
'a',
editor.editor
) as HTMLAnchorElement;
} else {
link = false;
}
if (!isImageContent && current) {
content_input.value = getSelectionText();
}
if (link) {
url_input.value = attr(link, 'href') || '';
if (openInNewTabCheckbox && target_checkbox) {
target_checkbox.checked = attr(link, 'target') === '_blank';
}
if (noFollowCheckbox && nofollow_checkbox) {
nofollow_checkbox.checked = attr(link, 'rel') === 'nofollow';
}
insert.textContent = i18n('Update');
} else {
Dom.hide(unlink);
}
const snapshot = editor.observer.snapshot.make();
if (unlink) {
editor.e.on(unlink, 'click', (e: MouseEvent) => {
editor.observer.snapshot.restore(snapshot);
if (link) {
Dom.unwrap(link);
}
editor.setEditorValue();
close();
e.preventDefault();
});
}
editor.e.on(form, 'submit', (event: Event) => {
event.preventDefault();
event.stopImmediatePropagation();
if (!url_input.value.trim().length) {
url_input.focus();
url_input.classList.add('jodit_error');
return false;
}
let links: HTMLAnchorElement[];
editor.observer.snapshot.restore(snapshot);
const textWasChanged =
getSelectionText() !== content_input.value.trim();
if (!link) {
if (!editor.s.isCollapsed()) {
links = editor.s.wrapInTag('a') as HTMLAnchorElement[];
} else {
const a = editor.createInside.element('a');
editor.s.insertNode(a);
links = [a];
}
} else {
links = [link];
}
links.forEach(a => {
a.setAttribute('href', url_input.value);
if (!isImageContent) {
if (content_input.value.trim().length) {
if (textWasChanged) {
a.textContent = content_input.value;
}
} else {
a.textContent = url_input.value;
}
}
if (openInNewTabCheckbox && target_checkbox) {
if (target_checkbox.checked) {
a.setAttribute('target', '_blank');
} else {
a.removeAttribute('target');
}
}
if (noFollowCheckbox && nofollow_checkbox) {
if (nofollow_checkbox.checked) {
a.setAttribute('rel', 'nofollow');
} else {
a.removeAttribute('rel');
}
}
});
editor.setEditorValue();
close();
return false;
});
return form;
},
tags: ['a'],
tooltip: 'Insert link'
} as IControlType;
/**
* Process link. Insert, dbclick or remove format
*
* @module plugins/link
*/
export function link(jodit: IJodit): void {
if (jodit.o.link.followOnDblClick) {
jodit.e.on('afterInit changePlace', () => {
jodit.e
.off('dblclick.link')
.on(jodit.editor, 'dblclick.link', (e: MouseEvent) => {
if (!Dom.isTag(e.target, 'a')) {
return;
}
const href = attr(e.target, 'href');
if (href) {
location.href = href;
e.preventDefault();
}
});
});
}
if (jodit.o.link.processPastedLink) {
jodit.e.on(
'processPaste.link',
(event: ClipboardEvent, html: string): HTMLAnchorElement | void => {
if (isURL(html)) {
if (jodit.o.link.processVideoLink) {
const embed = convertMediaUrlToVideoEmbed(html);
if (embed !== html) {
return jodit.createInside.fromHTML(
embed
) as HTMLAnchorElement;
}
}
const a = jodit.createInside.element('a');
a.setAttribute('href', html);
a.textContent = html;
return a;
}
}
);
}
if (jodit.o.link.removeLinkAfterFormat) {
jodit.e.on('afterCommand.link', (command: string) => {
const sel: Select = jodit.selection;
let newtag: Node, node: Nullable<Node>;
if (command === 'removeFormat') {
node = sel.current();
if (node && !Dom.isTag(node, 'a')) {
node = Dom.closest(node, 'a', jodit.editor);
}
if (Dom.isTag(node, 'a')) {
if (node.innerHTML === node.textContent) {
newtag = jodit.createInside.text(
(node as HTMLElement).innerHTML
);
} else {
newtag = jodit.createInside.element('span');
(newtag as HTMLElement).innerHTML = (node as HTMLElement).innerHTML;
}
if (node.parentNode) {
node.parentNode.replaceChild(newtag, node);
jodit.s.setCursorIn(newtag, true);
}
}
}
});
}
}