/** * @module plugins/representative-setting */ import { debounce } from 'jodit/core/decorators'; import { pluginSystem } from 'jodit/src/core/global'; import { $$, css } from 'jodit/src/core/helpers'; import { Dom, Plugin } from 'jodit/src/modules'; import { IJodit } from 'jodit/src/types'; import './config'; import './representative-setting.less'; export class RepresentativeSetting extends Plugin { private isRepresentative(element: HTMLElement | null): boolean { return element?.hasAttribute('thumbnail') ?? false; } private representButtonTemplate = ``; private prevMediaListLength: number = 0; private getAllowRepresentMedias(): HTMLElement[] { const allowRepresentMedias = $$( this.j.o.allowRepresentTags.join(','), this.j.editor ); allowRepresentMedias.forEach((element, index) => { element.setAttribute('data-representative-index', index.toString()); }); return allowRepresentMedias; } private representButtons: HTMLElement[] = []; private renderRepresentButtons(): void { const allowRepresentMedias = this.getAllowRepresentMedias(); if (!allowRepresentMedias.length) return; const slicedAllowRepresentMedias = this.prevMediaListLength === 0 ? allowRepresentMedias : allowRepresentMedias.slice( this.prevMediaListLength, allowRepresentMedias.length ); slicedAllowRepresentMedias.forEach((element: any) => { const button = this.j.c.fromHTML(this.representButtonTemplate); const index = Number(element.dataset.representativeIndex); button?.setAttribute('data-representative-index', index.toString()); this.toggleClassName(button, element); this.j.workplace.appendChild(button); this.representButtons[index] = button; }); this.prevMediaListLength = allowRepresentMedias.length; this.onSetRepresentative(); } private onSetRepresentative = (): void => { // 'thumbnail' attribute를 하나의 이미지 태그에만 적용시키기 위해 추가 let hasThumbnail = false; this.getAllowRepresentMedias().forEach((element, index) => { if (element.hasAttribute('thumbnail')) { if (hasThumbnail) { element.removeAttribute('thumbnail'); return; } this.representButtons[index]?.classList.add('active'); hasThumbnail = true; } else { this.representButtons[index]?.classList.remove('active'); } }); }; private setRepresentative = (e: Event): void => { const element = e.target as HTMLElement; const target = this.getAllowRepresentMedias()[ Number(element.dataset.representativeIndex) ]; if (this.isRepresentative(target)) return; this.getAllowRepresentMedias().forEach((element: HTMLElement) => { element.removeAttribute('thumbnail'); }); target.setAttribute('thumbnail', ''); this.toggleClassName(element, target); this.j.e.fire('setRepresentative'); this.j.e.fire('change'); this.j.e.fire('focus'); this.j.e.fire('blur'); }; /** @override */ protected afterInit(editor: IJodit): void { if (this.j.o.readonly) return; editor.e .on( 'change afterInit afterSetMode collage-item-delete synchro', this.onChangeEditor.bind(this) ) .on('setRepresentative', this.onSetRepresentative.bind(this)); this.onChangeEditor(); } private onMouseEnter(e: MouseEvent): void { const target = e.target as HTMLElement; const index = target.dataset.representativeIndex; const button = this.representButtons?.[Number(index)]; this.showButton(button); } private onMouseLeave(e: MouseEvent): void { const target = e.target as HTMLElement; const index = target.dataset.representativeIndex; const button = this.representButtons?.[Number(index)]; this.hideButton(button); } protected setButtonPosition(): void { const { editor } = this.j; if (!this.getAllowRepresentMedias().length) return; this.representButtons.forEach(button => { const target = this.getAllowRepresentMedias()[ Number(button.dataset.representativeIndex) ]; if (!target) return; css(button, { position: 'absolute', top: target.offsetTop - editor.offsetTop + 8 + 'px', left: target.offsetLeft - editor.offsetLeft + 8 + 'px' }); }); } private toggleClassName(element: HTMLElement, target: HTMLElement): void { element?.firstElementChild?.classList.toggle( 'active', this.isRepresentative(target) ); } private showButton(element: HTMLElement): void { element?.classList.remove('hide'); } private hideButton(element: HTMLElement): void { element?.classList.add('hide'); } protected override beforeDestruct(jodit: IJodit): void { this.representButtons.forEach(element => { Dom.safeRemove(element); }); } private setFirstElementAsRepresentative(): void { if (!this.getAllowRepresentMedias().length) return; if ( !this.getAllowRepresentMedias().some((element: HTMLElement) => element.hasAttribute('thumbnail') ) ) { this.getAllowRepresentMedias()[0].setAttribute('thumbnail', ''); this.onSetRepresentative(); } } @debounce() private onChangeEditor(): void { this.setFirstElementAsRepresentative(); if (this.prevMediaListLength < this.getAllowRepresentMedias().length) { this.renderRepresentButtons(); } if (this.prevMediaListLength > this.getAllowRepresentMedias().length) { const deleteIndex = this.prevMediaListLength - this.getAllowRepresentMedias().length; for (let i = 1; i < deleteIndex; i++) { const lastIndex = this.representButtons.length - 1; Dom.safeRemove(this.representButtons[lastIndex]); this.representButtons = this.representButtons.slice( 0, lastIndex ); } const lastIndex = this.representButtons.length - 1; Dom.safeRemove(this.representButtons[lastIndex]); this.representButtons = this.representButtons.slice(0, lastIndex); this.prevMediaListLength = this.getAllowRepresentMedias().length; this.getAllowRepresentMedias().forEach( (element: HTMLElement, index: number) => { if (element.hasAttribute('thumbnail')) { this.representButtons[index]?.classList.add('active'); } } ); } this.representButtons.forEach(element => { this.j.e.on(element, 'click', this.setRepresentative); this.j.e.off(element, 'mouseenter', this.onMouseEnter.bind(this)); this.j.e.off(element, 'mouseleave', this.onMouseLeave.bind(this)); this.j.e.on(element, 'mouseenter', this.onMouseEnter.bind(this)); this.j.e.on(element, 'mouseleave', this.onMouseLeave.bind(this)); }); this.setButtonPosition(); this.getAllowRepresentMedias().forEach(element => { this.j.e.off(element, 'mouseenter', this.onMouseEnter.bind(this)); this.j.e.off(element, 'mouseleave', this.onMouseLeave.bind(this)); this.j.e.on(element, 'mouseenter', this.onMouseEnter.bind(this)); this.j.e.on(element, 'mouseleave', this.onMouseLeave.bind(this)); }); this.onSetRepresentative(); } } pluginSystem.add('representative-setting', RepresentativeSetting);