/**
* [[include:plugins/link/README.md]]
* @packageDocumentation
* @module plugins/link
*/
import type { IDictionary, IJodit, IUIForm, Nullable } from 'jodit/types';
import type { UIForm } from 'jodit/core/ui/form/form';
import { Dom } from 'jodit/core/dom';
import {
attr,
convertMediaUrlToVideoEmbed,
isString,
isURL,
refs,
stripTags
} from 'jodit/core/helpers';
import { Plugin } from 'jodit/core/plugin';
import { autobind } from 'jodit/core/decorators';
import { pluginSystem } from 'jodit/core/global';
// import getJoditLinkPreviewByUrl from './getJoditLinkPreviewByUrl';
import './config';
import './jodit-link-preview.less';
/**
* Process link. Insert, dblclick or remove format
*/
export class link extends Plugin {
/** @override */
override buttons: Plugin['buttons'] = [
{
name: 'link',
group: 'insert'
}
];
/** @override */
protected override afterInit(jodit: IJodit): void {
if (jodit.o.link.followOnDblClick) {
jodit.e.on('dblclick.link', this.onDblClickOnLink);
}
if (jodit.o.link.processPastedLink) {
jodit.e.on('processPaste.link', this.onProcessPasteLink);
}
jodit.e.on('generateLinkForm.link', this.generateForm);
jodit.registerCommand('openLinkDialog', {
exec: () => {
const dialog = jodit.dlg({
resizable: false
});
const htmlForm = this.generateForm(jodit.s.current(), () => {
dialog.close();
}) as UIForm;
htmlForm.container.classList.add('jodit-dialog_alert');
dialog.setContent(htmlForm);
dialog.open();
jodit.async.requestIdleCallback(() => {
const { url_input } = refs(htmlForm.container);
url_input?.focus();
});
},
hotkeys: jodit.o.link.hotkeys
});
}
@autobind
private onDblClickOnLink(e: MouseEvent): void {
if (!Dom.isTag(e.target, 'a')) {
return;
}
const href = attr(e.target, 'href');
if (href) {
location.href = href;
e.preventDefault();
}
}
@autobind
private onProcessPasteLink(
ignore: ClipboardEvent,
html: string
): HTMLAnchorElement | HTMLIFrameElement | void {
const { jodit } = this;
if (isURL(html)) {
if (jodit.o.link.processVideoLink) {
const embed = convertMediaUrlToVideoEmbed(html);
if (embed !== html) {
jodit.e.stopPropagation('processPaste');
return jodit.createInside.fromHTML(
embed
) as HTMLAnchorElement;
}
}
const iframe = jodit.c.fromHTML(
``
) as HTMLIFrameElement;
// const a = jodit.createInside.element('a');
// a.setAttribute('href', html);
// a.setAttribute('target', '_blank');
// a.setAttribute(
// 'style',
// 'width:fit-content; display: inline-block;'
// );
// a.textContent = html;
// getJoditLinkPreviewByUrl(jodit, html).then(el => {
// a.innerHTML = el;
// const enterKeyEvent = new KeyboardEvent('keydown', {
// key: 'Enter',
// code: 'Enter',
// keyCode: 13,
// which: 13,
// bubbles: true
// });
// jodit.editor.dispatchEvent(enterKeyEvent);
// });
jodit.e.stopPropagation('processPaste');
jodit.e.fire('applyLink', jodit, iframe, null);
return iframe;
}
}
@autobind
private generateForm(
current: Nullable,
close: Function
): HTMLElement | IUIForm {
const { jodit } = this;
const i18n = jodit.i18n.bind(jodit),
{
openInNewTabCheckbox,
noFollowCheckbox,
formTemplate,
formClassName,
modeClassName
} = jodit.o.link;
const html = formTemplate(jodit),
form = isString(html)
? (jodit.c.fromHTML(html, {
target_checkbox_box: openInNewTabCheckbox,
nofollow_checkbox_box: noFollowCheckbox
}) as HTMLFormElement)
: html,
htmlForm = Dom.isElement(form) ? form : form.container;
const elements = refs(htmlForm),
{ insert, unlink, content_input_box } = elements,
{ target_checkbox, nofollow_checkbox, url_input } =
elements as IDictionary,
currentElement = current,
isImageContent = Dom.isImage(currentElement);
let { content_input } = elements as IDictionary;
const { className_input } = elements as IDictionary,
{ className_select } = elements as IDictionary;
if (!content_input) {
content_input = jodit.c.element('input', {
type: 'hidden',
ref: 'content_input'
});
}
if (formClassName) {
htmlForm.classList.add(formClassName);
}
if (isImageContent) {
Dom.hide(content_input_box);
}
let link: false | HTMLAnchorElement;
const getSelectionText = (): string =>
link
? link.innerText
: stripTags(jodit.s.range.cloneContents(), jodit.ed);
if (current && Dom.closest(current, 'a', jodit.editor)) {
link = Dom.closest(current, 'a', jodit.editor) as HTMLAnchorElement;
} else {
link = false;
}
if (!isImageContent && current) {
content_input.value = getSelectionText();
}
if (link) {
url_input.value = attr(link, 'href') || '';
if (modeClassName) {
switch (modeClassName) {
case 'input':
if (className_input) {
className_input.value = attr(link, 'class') || '';
}
break;
case 'select':
if (className_select) {
for (
let i = 0;
i < className_select.selectedOptions.length;
i++
) {
const option = className_select.options.item(i);
if (option) {
option.selected = false;
}
}
const classNames = attr(link, 'class') || '';
classNames.split(' ').forEach(className => {
if (className) {
for (
let i = 0;
i < className_select.options.length;
i++
) {
const option =
className_select.options.item(i);
if (
option?.value &&
option.value === className
) {
option.selected = true;
}
}
}
});
}
break;
}
}
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);
}
jodit.editor.normalize();
const snapshot = jodit.history.snapshot.make();
if (unlink) {
jodit.e.on(unlink, 'click', (e: MouseEvent) => {
jodit.s.restore();
jodit.history.snapshot.restore(snapshot);
if (link) {
Dom.unwrap(link);
}
jodit.synchronizeValues();
close();
e.preventDefault();
});
}
const onSubmit = (): false => {
if (!url_input.value.trim().length) {
url_input.focus();
url_input.classList.add('jodit_error');
return false;
}
let links: HTMLAnchorElement[];
jodit.s.restore();
jodit.s.removeMarkers();
jodit.editor.normalize();
jodit.history.snapshot.restore(snapshot);
const textWasChanged =
getSelectionText() !== content_input.value.trim();
const ci = jodit.createInside;
if (!link) {
if (!jodit.s.isCollapsed()) {
const node = jodit.s.current();
if (Dom.isTag(node, ['img'])) {
links = [Dom.wrap(node, 'a', ci) as HTMLAnchorElement];
} else {
links = jodit.s.wrapInTag('a') as HTMLAnchorElement[];
}
} else {
const a = ci.element('a');
jodit.s.insertNode(a, false, false);
links = [a];
}
links.forEach(link => jodit.s.select(link));
} else {
links = [link];
}
links.forEach(a => {
attr(a, 'href', url_input.value);
if (modeClassName && (className_input ?? className_select)) {
if (modeClassName === 'input') {
if (
className_input.value === '' &&
a.hasAttribute('class')
) {
attr(a, 'class', null);
}
if (className_input.value !== '') {
attr(a, 'class', className_input.value);
}
} else if (modeClassName === 'select') {
if (a.hasAttribute('class')) {
attr(a, 'class', null);
}
for (
let i = 0;
i < className_select.selectedOptions.length;
i++
) {
const className =
className_select.selectedOptions.item(i)?.value;
if (className) {
a.classList.add(className);
}
}
}
}
if (!isImageContent) {
let newContent = a.textContent;
if (content_input.value.trim().length) {
if (textWasChanged) {
newContent = content_input.value;
}
} else {
newContent = url_input.value;
}
const content = a.textContent;
if (newContent !== content) {
a.textContent = newContent;
}
}
if (openInNewTabCheckbox && target_checkbox) {
attr(
a,
'target',
target_checkbox.checked ? '_blank' : null
);
}
if (noFollowCheckbox && nofollow_checkbox) {
attr(
a,
'rel',
nofollow_checkbox.checked ? 'nofollow' : null
);
}
jodit.e.fire('applyLink', jodit, a, form);
});
jodit.synchronizeValues();
close();
return false;
};
if (Dom.isElement(form)) {
jodit.e.on(form, 'submit', (event: Event) => {
event.preventDefault();
event.stopImmediatePropagation();
onSubmit();
return false;
});
} else {
form.onSubmit(onSubmit);
}
return form;
}
/** @override */
protected override beforeDestruct(jodit: IJodit): void {
jodit.e
.off('generateLinkForm.link', this.generateForm)
.off('dblclick.link', this.onDblClickOnLink)
.off('processPaste.link', this.onProcessPasteLink);
}
}
pluginSystem.add('link', link);