/**
* [[include:plugins/clean-html/README.md]]
* @packageDocumentation
* @module plugins/clean-html
*/
import type { IJodit, Nullable } from 'jodit/types';
import { safeHTML } from 'jodit/core/helpers/html/safe-html';
import { Plugin } from 'jodit/core/plugin/plugin';
import { watch, hook } from 'jodit/core/decorators';
import { LazyWalker } from 'jodit/core/dom/lazy-walker';
import { pluginSystem } from 'jodit/core/global';
import { Dom } from 'jodit/src/core/dom/dom';
import {
getHash,
removeFormatForCollapsedSelection,
removeFormatForSelection,
visitNodeWalker
} from './helpers';
import './config';
/**
* Clean HTML after removeFormat and insertHorizontalRule command
*/
export class cleanHtml extends Plugin {
/** @override */
override buttons: Plugin['buttons'] = [
{
name: 'eraser',
group: 'font-style'
}
];
/** @override */
protected override afterInit(jodit: IJodit): void {}
private get isEditMode(): boolean {
return !(
this.j.isInDestruct ||
!this.j.isEditorMode() ||
this.j.getReadOnly()
);
}
/**
* Clean HTML code on every change
*/
@watch([':change', ':afterSetMode', ':afterInit', ':mousedown', ':keydown'])
protected onChangeCleanHTML(): void {
if (!this.isEditMode) {
return;
}
const editor = this.j;
this.walker.setWork(editor.editor);
this.currentSelectionNode = editor.s.current();
}
private currentSelectionNode: Nullable = null;
private walker: LazyWalker = new LazyWalker(this.j.async, {
timeout: this.j.o.cleanHTML.timeout
});
@hook('ready')
protected startWalker(): void {
const { jodit } = this;
const allow = getHash(this.j.o.cleanHTML.allowTags);
const deny = getHash(this.j.o.cleanHTML.denyTags);
this.walker
.on('visit', (node: Node) =>
visitNodeWalker(
jodit,
node,
allow,
deny,
this.currentSelectionNode
)
)
.on('end', (affected: boolean): void => {
this.j.e.fire(
affected
? 'internalChange finishedCleanHTMLWorker'
: 'finishedCleanHTMLWorker'
);
});
}
@watch(':beforeCommand')
protected beforeCommand(command: string): void | false {
if (command.toLowerCase() === 'removeformat') {
if (this.j.s.isCollapsed()) {
removeFormatForCollapsedSelection(this.j);
} else {
removeFormatForSelection(this.j);
}
return false;
}
}
/**
* Event handler when manually assigning a value to the HTML editor.
*/
@watch(':beforeSetNativeEditorValue')
protected onBeforeSetNativeEditorValue(data: { value: string }): boolean {
const sandBox = this.j.o.cleanHTML.useIframeSandbox
? this.j.createInside.sandbox()
: this.j.createInside.div();
sandBox.innerHTML = data.value;
this.onSafeHTML(sandBox);
data.value = sandBox.innerHTML;
safeHTML(sandBox, { safeJavaScriptLink: true, removeOnError: true });
Dom.safeRemove(sandBox);
return false;
}
@watch(':safeHTML')
protected onSafeHTML(sandBox: HTMLElement): void {
safeHTML(sandBox, this.j.o.cleanHTML);
}
/** @override */
protected override beforeDestruct(): void {
this.walker.destruct();
}
}
pluginSystem.add('cleanHtml', cleanHtml);