diff --git a/eslint.config.mjs b/eslint.config.mjs index 193d8c62..4a1567df 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -20,9 +20,6 @@ export default [ 'jsdoc/require-returns-description': ['off'], 'jsdoc/check-tag-names': ['off'], 'jsdoc/require-jsdoc': ['off'], - '@typescript-eslint/no-unsafe-assignment': ['off'], - '@typescript-eslint/no-unsafe-member-access': ['off'], - '@typescript-eslint/no-explicit-any': ['off'], "@typescript-eslint/naming-convention": [ "error", { diff --git a/src/index.ts b/src/index.ts index 11b30e15..9208da01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,14 +30,14 @@ */ import type { TunesMenuConfig } from '@editorjs/editorjs/types/tools'; -import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockTool, BlockAPI } from '@editorjs/editorjs'; +import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockTool, BlockAPI, PasteEvent, PatternPasteEventDetail, FilePasteEventDetail } from '@editorjs/editorjs'; import './index.css'; import Ui from './ui'; import Uploader from './uploader'; import { IconAddBorder, IconStretch, IconAddBackground, IconPicture } from '@codexteam/icons'; -import type { ActionConfig, UploadResponseFormat, ImageToolData, ImageConfig } from './types/types'; +import type { ActionConfig, UploadResponseFormat, ImageToolData, ImageConfig, HTMLPasteEventDetailExtended } from './types/types'; type ImageToolConstructorOptions = BlockToolConstructorOptions; @@ -102,7 +102,7 @@ export default class ImageTool implements BlockTool { additionalRequestHeaders: config.additionalRequestHeaders, field: config.field, types: config.types, - captionPlaceholder: this.api.i18n.t(config.captionPlaceholder != undefined ? config.captionPlaceholder : 'Caption'), + captionPlaceholder: this.api.i18n.t(config.captionPlaceholder ?? 'Caption'), buttonContent: config.buttonContent, uploader: config.uploader, actions: config.actions, @@ -304,10 +304,10 @@ export default class ImageTool implements BlockTool { * {@link https://github.com/codex-team/editor.js/blob/master/types/tools/paste-events.d.ts} * @returns {void} */ - public async onPaste(event: CustomEvent): Promise { + public async onPaste(event: PasteEvent): Promise { switch (event.type) { case 'tag': { - const image = event.detail.data as { src: string }; + const image = (event.detail as HTMLPasteEventDetailExtended).data; /** Images from PDF */ if (/^blob:/.test(image.src)) { @@ -323,13 +323,13 @@ export default class ImageTool implements BlockTool { break; } case 'pattern': { - const url = event.detail.data as string; + const url = (event.detail as PatternPasteEventDetail).data; this.uploadUrl(url); break; } case 'file': { - const file = event.detail.file as Blob; + const file = (event.detail as FilePasteEventDetail).file; this.uploadFile(file); break; diff --git a/src/types/codexteam__ajax.d.ts b/src/types/codexteam__ajax.d.ts index 347900f6..063fc395 100644 --- a/src/types/codexteam__ajax.d.ts +++ b/src/types/codexteam__ajax.d.ts @@ -1,7 +1,7 @@ declare module '@codexteam/ajax' { export interface AjaxOptions { url?: string; - data?: any; + data?: object; accept?: string; headers?: object; beforeSend?: (files: File[]) => void; @@ -9,7 +9,7 @@ declare module '@codexteam/ajax' { type?: string; } - export interface AjaxResponse { + export interface AjaxResponse { body: T; } diff --git a/src/types/types.ts b/src/types/types.ts index 0a139bd1..f162b428 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,3 +1,5 @@ +import type { HTMLPasteEventDetail } from '@editorjs/editorjs'; + /** * Represents options for uploading, including a function to handle previewing. */ @@ -165,3 +167,19 @@ export interface ImageConfig { */ actions?: ActionConfig[]; } + +/** + * Interface representing the details of a paste event for HTML elements. + * Extends the `HTMLPasteEventDetail` interface to include additional data properties. + */ +export interface HTMLPasteEventDetailExtended extends HTMLPasteEventDetail { + /** + * The data property containing the source of the image and HTML element details. + */ + data: { + /** + * The source URL of the pasted image. + */ + src: string; + } & HTMLElement; +} diff --git a/src/ui.ts b/src/ui.ts index 5eb9c18b..5096d025 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -153,26 +153,14 @@ export default class Ui { } /** - * CSS classes - * @returns {object} + * Apply visual representation of activated tune + * @param {string} tuneName - one of available tunes {@link Tunes.tunes} + * @param {boolean} status - true for enable, false for disable + * @returns {void} */ - public get CSS(): Record { - return { - baseClass: this.api.styles.block, - loading: this.api.styles.loader, - input: this.api.styles.input, - button: this.api.styles.button, - - /** - * Tool's classes - */ - wrapper: 'image-tool', - imageContainer: 'image-tool__image', - imagePreloader: 'image-tool__image-preloader', - imageEl: 'image-tool__image-picture', - caption: 'image-tool__caption', - }; - }; + public applyTune(tuneName: string, status: boolean): void { + this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${tuneName}`, status); + } /** * Renders tool UI @@ -189,22 +177,6 @@ export default class Ui { return this.nodes.wrapper; } - /** - * Creates upload-file button - * @returns {Element} - */ - public createFileButton(): HTMLElement { - const button = make('div', [this.CSS.button]); - - button.innerHTML = this.config.buttonContent != undefined ? this.config.buttonContent : `${IconPicture} ${this.api.i18n.t('Select an Image')}`; - - button.addEventListener('click', () => { - this.onSelectFile(); - }); - - return button; - } - /** * Shows uploading preloader * @param {string} src - preview source @@ -236,7 +208,7 @@ export default class Ui { */ const tag = /\.mp4$/.test(url) ? 'VIDEO' : 'IMG'; - const attributes: { [key: string]: any } = { + const attributes: { [key: string]: string | boolean } = { src: url, }; @@ -302,26 +274,54 @@ export default class Ui { } } + /** + * CSS classes + * @returns {object} + */ + private get CSS(): Record { + return { + baseClass: this.api.styles.block, + loading: this.api.styles.loader, + input: this.api.styles.input, + button: this.api.styles.button, + + /** + * Tool's classes + */ + wrapper: 'image-tool', + imageContainer: 'image-tool__image', + imagePreloader: 'image-tool__image-preloader', + imageEl: 'image-tool__image-picture', + caption: 'image-tool__caption', + }; + }; + + /** + * Creates upload-file button + * @returns {Element} + */ + private createFileButton(): HTMLElement { + const button = make('div', [this.CSS.button]); + + button.innerHTML = this.config.buttonContent ?? `${IconPicture} ${this.api.i18n.t('Select an Image')}`; + + button.addEventListener('click', () => { + this.onSelectFile(); + }); + + return button; + } + /** * Changes UI status * @param {string} status - see {@link Ui.status} constants * @returns {void} */ - public toggleStatus(status: UiState): void { + private toggleStatus(status: UiState): void { for (const statusType in UiState) { if (Object.prototype.hasOwnProperty.call(UiState, statusType)) { this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${UiState[statusType as keyof typeof UiState]}`, status === UiState[statusType as keyof typeof UiState]); } } } - - /** - * Apply visual representation of activated tune - * @param {string} tuneName - one of available tunes {@link Tunes.tunes} - * @param {boolean} status - true for enable, false for disable - * @returns {void} - */ - public applyTune(tuneName: string, status: boolean): void { - this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${tuneName}`, status); - } } diff --git a/src/uploader.ts b/src/uploader.ts index 9d904c0d..ecc0ceaa 100644 --- a/src/uploader.ts +++ b/src/uploader.ts @@ -1,4 +1,5 @@ import ajax from '@codexteam/ajax'; +import type { AjaxResponse } from '@codexteam/ajax'; import isPromise from './utils/isPromise'; import type { UploadOptions } from './types/types'; import type { UploadResponseFormat, ImageConfig } from './types/types'; @@ -24,7 +25,7 @@ interface UploaderParams { * @param error : error type * @returns void */ - onError: (error: any) => void; + onError: (error: string) => void; } /** @@ -36,7 +37,7 @@ interface UploaderParams { export default class Uploader { private config: ImageConfig; private onUpload: (response: UploadResponseFormat) => void; - private onError: (error: any) => void; + private onError: (error: string) => void; /** * @param {object} params - uploader module params * @param {ImageConfig} params.config - image tool config @@ -74,7 +75,7 @@ export default class Uploader { if (this.config.uploader && typeof this.config.uploader.uploadByFile === 'function') { const uploadByFile = this.config.uploader.uploadByFile; - upload = ajax.selectFiles({ accept: this.config.types != undefined ? this.config.types : 'image/*' }).then((files: File[]) => { + upload = ajax.selectFiles({ accept: this.config.types ?? 'image/*' }).then((files: File[]) => { preparePreview(files[0]); const customUpload = uploadByFile(files[0]); @@ -91,18 +92,18 @@ export default class Uploader { upload = ajax.transport({ url: this.config.endpoints.byFile, data: this.config.additionalRequestData, - accept: this.config.types != undefined ? this.config.types : 'image/*', + accept: this.config.types ?? 'image/*', headers: this.config.additionalRequestHeaders as Record, beforeSend: (files: File[]) => { preparePreview(files[0]); }, - fieldName: this.config.field != undefined ? this.config.field : 'image', - }).then((response: any) => response.body as UploadResponseFormat); + fieldName: this.config.field ?? 'image', + }).then((response: AjaxResponse) => response.body as UploadResponseFormat); } upload.then((response) => { this.onUpload(response); - }).catch((error) => { + }).catch((error: string) => { this.onError(error); }); } @@ -135,12 +136,12 @@ export default class Uploader { }, this.config.additionalRequestData), type: ajax.contentType.JSON, headers: this.config.additionalRequestHeaders as Record, - }).then((response: any) => response.body as UploadResponseFormat); + }).then((response: AjaxResponse) => response.body as UploadResponseFormat); } upload.then((response: UploadResponseFormat) => { this.onUpload(response); - }).catch((error: any) => { + }).catch((error: string) => { this.onError(error); }); } @@ -180,7 +181,7 @@ export default class Uploader { */ const formData = new FormData(); - formData.append(this.config.field != undefined ? this.config.field : 'image', file); + formData.append(this.config.field ?? 'image', file); if (this.config.additionalRequestData && Object.keys(this.config.additionalRequestData).length) { Object.entries(this.config.additionalRequestData).forEach(([name, value]: [string, string | Blob]) => { @@ -193,12 +194,12 @@ export default class Uploader { data: formData, type: ajax.contentType.JSON, headers: this.config.additionalRequestHeaders as Record, - }).then((response: any) => response.body as UploadResponseFormat); + }).then((response: AjaxResponse) => response.body as UploadResponseFormat); } upload.then((response) => { this.onUpload(response); - }).catch((error) => { + }).catch((error: string) => { this.onError(error); }); } diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 7cb6deb8..6e3b959f 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -5,7 +5,7 @@ * @param attributes - any attributes * @returns */ -export function make(tagName: string, classNames: string[] | string | null = null, attributes: { [key: string]: any } = {}): HTMLElement { +export function make(tagName: string, classNames: string[] | string | null = null, attributes: { [key: string]: string | boolean } = {}): HTMLElement { const el = document.createElement(tagName); if (Array.isArray(classNames)) { @@ -16,7 +16,7 @@ export function make(tagName: string, classNames: string[] | string | null = nul for (const attrName in attributes) { if (attributes.hasOwnProperty(attrName)) { - (el as any)[attrName] = attributes[attrName]; + (el as unknown as { [key: string]: string | boolean })[attrName] = attributes[attrName]; } } diff --git a/src/utils/isPromise.ts b/src/utils/isPromise.ts index fc04ce3b..3e198bb0 100644 --- a/src/utils/isPromise.ts +++ b/src/utils/isPromise.ts @@ -1,8 +1,10 @@ +import type { UploadResponseFormat } from '../types/types'; + /** * Check if passed object is a Promise * @param object - object to check * @returns */ -export default function isPromise(object: any): object is Promise { +export default function isPromise(object: Promise): object is Promise { return object != undefined && typeof object.then === 'function'; }