diff --git a/package.json b/package.json index 7b27d495..7120556e 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,6 @@ "vite-plugin-dts": "^3.9.1" }, "dependencies": { - "@codexteam/icons": "^0.3.0" + "@codexteam/icons": "^0.3.2" } } diff --git a/src/index.ts b/src/index.ts index 7c7145aa..3e93f82c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,21 +30,21 @@ */ import type { TunesMenuConfig } from '@editorjs/editorjs/types/tools'; -import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockTool, BlockAPI, PasteEvent, PatternPasteEventDetail, FilePasteEventDetail } from '@editorjs/editorjs'; +import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockAPI, PasteEvent, PatternPasteEventDetail, FilePasteEventDetail, BlockTool } 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, HTMLPasteEventDetailExtended, ImageSetterParam } from './types/types'; +import type { ActionConfig, UploadResponseFormat, ImageToolData, ImageConfig, FileObjectData, HTMLPasteEventDetailExtended } from './types/types'; -type ImageToolConstructorOptions = BlockToolConstructorOptions; +type ImageToolConstructorOptions = BlockToolConstructorOptions, ImageConfig>; /** * Implementation of ImageTool class */ -export default class ImageTool implements BlockTool { +export default class ImageTool implements BlockTool { /** * Editor.js API instance */ @@ -63,22 +63,22 @@ export default class ImageTool implements BlockTool { /** * Configuration for the ImageTool */ - private config: ImageConfig; + private config: ImageConfig; /** * Uploader module instance */ - private uploader: Uploader; + private uploader: Uploader; /** * UI module instance */ - private ui: Ui; + private ui: Ui, AdditionalUploadResponse>; /** * Stores current block data internally */ - private _data: ImageToolData; + private _data: ImageToolData; /** * @param tool - tool properties got from editor.js @@ -88,7 +88,7 @@ export default class ImageTool implements BlockTool { * @param tool.readOnly - read-only mode flag * @param tool.block - current Block API */ - constructor({ data, config, api, readOnly, block }: ImageToolConstructorOptions) { + constructor({ data, config, api, readOnly, block }: ImageToolConstructorOptions) { this.api = api; this.readOnly = readOnly; this.block = block; @@ -111,16 +111,16 @@ export default class ImageTool implements BlockTool { /** * Module for file uploading */ - this.uploader = new Uploader({ + this.uploader = new Uploader({ config: this.config, - onUpload: (response: UploadResponseFormat) => this.onUpload(response), + onUpload: (response: UploadResponseFormat) => this.onUpload(response), onError: (error: string) => this.uploadingFailed(error), }); /** * Module for working with UI */ - this.ui = new Ui({ + this.ui = new Ui, AdditionalUploadResponse>({ api, config: this.config, onSelectFile: () => { @@ -144,7 +144,7 @@ export default class ImageTool implements BlockTool { file: { url: '', }, - }; + } as ImageToolData; this.data = data; } @@ -205,14 +205,14 @@ export default class ImageTool implements BlockTool { * @param savedData — data received after saving * @returns false if saved data is not correct, otherwise true */ - public validate(savedData: ImageToolData): boolean { + public validate(savedData: ImageToolData): boolean { return !!savedData.file.url; } /** * Return Block data */ - public save(): ImageToolData { + public save(): ImageToolData { const caption = this.ui.nodes.caption; this._data.caption = caption.innerHTML; @@ -234,7 +234,7 @@ export default class ImageTool implements BlockTool { label: this.api.i18n.t(tune.title), name: tune.name, toggle: tune.toggle, - isActive: this.data[tune.name as keyof ImageToolData] as boolean, + isActive: this.data[tune.name as keyof ImageToolData] as boolean, onActivate: () => { /** If it'a user defined tune, execute it's callback stored in action property */ if (typeof tune.action === 'function') { @@ -242,7 +242,7 @@ export default class ImageTool implements BlockTool { return; } - this.tuneToggled(tune.name as keyof ImageToolData); + this.tuneToggled(tune.name as keyof ImageToolData); }, })); } @@ -333,23 +333,28 @@ export default class ImageTool implements BlockTool { * Stores all Tool's data * @param data - data in Image Tool format */ - private set data(data: ImageToolData) { + private set data(data: ImageToolData) { this.image = data.file; this._data.caption = data.caption || ''; this.ui.fillCaption(this._data.caption); ImageTool.tunes.forEach(({ name: tune }) => { - const value = typeof data[tune as keyof ImageToolData] !== 'undefined' ? data[tune as keyof ImageToolData] === true || data[tune as keyof ImageToolData] === 'true' : false; + const tuneKey = tune as keyof ImageToolData; + const tuneValue = data[tuneKey]; + + const value = typeof tuneValue !== 'undefined' + ? tuneValue === true || tuneValue === 'true' + : false; - this.setTune(tune as keyof ImageToolData, value); + this.setTune(tune as keyof ImageToolData, value); }); } /** * Return Tool data */ - private get data(): ImageToolData { + private get data(): ImageToolData { return this._data; } @@ -357,8 +362,8 @@ export default class ImageTool implements BlockTool { * Set new image file * @param file - uploaded file data */ - private set image(file: ImageSetterParam | undefined) { - this._data.file = file || { url: '' }; + private set image(file: FileObjectData | undefined) { + this._data.file = file || ({ url: '' } as FileObjectData); if (file && file.url) { this.ui.fillImage(file.url); @@ -369,7 +374,7 @@ export default class ImageTool implements BlockTool { * File uploading callback * @param response - uploading server response */ - private onUpload(response: UploadResponseFormat): void { + private onUpload(response: UploadResponseFormat): void { if (response.success && Boolean(response.file)) { this.image = response.file; } else { @@ -395,7 +400,7 @@ export default class ImageTool implements BlockTool { * Callback fired when Block Tune is activated * @param tuneName - tune that has been clicked */ - private tuneToggled(tuneName: keyof ImageToolData): void { + private tuneToggled(tuneName: keyof ImageToolData): void { // inverse tune state this.setTune(tuneName, !(this._data[tuneName] as boolean)); } @@ -405,10 +410,10 @@ export default class ImageTool implements BlockTool { * @param tuneName - {@link Tunes.tunes} * @param value - tune state */ - private setTune(tuneName: keyof ImageToolData, value: boolean): void { + private setTune(tuneName: keyof ImageToolData, value: boolean): void { (this._data[tuneName] as boolean) = value; - this.ui.applyTune(tuneName, value); + this.ui.applyTune(String(tuneName), value); if (tuneName === 'stretched') { /** * Wait until the API is ready diff --git a/src/types/types.ts b/src/types/types.ts index 6b4da7e7..95516028 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -12,6 +12,16 @@ export interface UploadOptions { onPreview: (src: string) => void; } +/** + * Represents the format of a file object data with the additional data. + */ +export type FileObjectData = { + /** + * The URL of the file. + */ + url: string; +} & AdditionalFileData; + /** * User configuration of Image block tunes. Allows to add custom tunes through the config */ @@ -56,12 +66,7 @@ export interface UploadResponseFormat { * 'url' is required, * also can contain any additional data that will be saved and passed back */ - file: { - /** - * The URL of the uploaded image. - */ - url: string; - } & AdditionalFileData; + file: FileObjectData; } /** @@ -92,19 +97,14 @@ export type ImageToolData = { * Object containing the URL of the image file. * Also can contain any additional data. */ - file: { - /** - * The URL of the image. - */ - url: string; - } & AdditionalFileData; -} & (Actions extends Record ? Actions : {}); + file: FileObjectData; +} & Actions; /** * * @description Config supported by Tool */ -export interface ImageConfig { +export interface ImageConfig { /** * Endpoints for upload, whether using file or URL. */ @@ -159,12 +159,12 @@ export interface ImageConfig { /** * Method to upload an image by file. */ - uploadByFile?: (file: Blob) => Promise; + uploadByFile?: (file: Blob) => Promise>; /** * Method to upload an image by URL. */ - uploadByUrl?: (url: string) => Promise; + uploadByUrl?: (url: string) => Promise>; }; /** diff --git a/src/ui.ts b/src/ui.ts index 931bca72..0148d2a2 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,7 +1,7 @@ import { IconPicture } from '@codexteam/icons'; import { make } from './utils/dom'; import type { API } from '@editorjs/editorjs'; -import type { ImageToolData, ImageConfig } from './types/types'; +import type { ImageConfig, ImageToolData } from './types/types'; /** * Enumeration representing the different states of the UI. @@ -61,7 +61,7 @@ interface Nodes { /** * ConstructorParams interface representing parameters for the Ui class constructor. */ -interface ConstructorParams { +interface ConstructorParams { /** * Editor.js API. */ @@ -69,7 +69,7 @@ interface ConstructorParams { /** * Configuration for the image. */ - config: ImageConfig; + config: ImageConfig; /** * Callback function for selecting a file. */ @@ -86,7 +86,7 @@ interface ConstructorParams { * - show/hide preview * - apply tune view */ -export default class Ui { +export default class Ui { /** * Nodes representing various elements in the UI. */ @@ -100,7 +100,7 @@ export default class Ui { /** * Configuration for the image tool. */ - private config: ImageConfig; + private config: ImageConfig; /** * Callback function for selecting a file. @@ -119,7 +119,7 @@ export default class Ui { * @param ui.onSelectFile - callback for clicks on Select file button * @param ui.readOnly - read-only mode flag */ - constructor({ api, config, onSelectFile, readOnly }: ConstructorParams) { + constructor({ api, config, onSelectFile, readOnly }: ConstructorParams) { this.api = api; this.config = config; this.onSelectFile = onSelectFile; @@ -165,7 +165,7 @@ export default class Ui { * Renders tool UI * @param toolData - saved tool data */ - public render(toolData: ImageToolData): HTMLElement { + public render(toolData: ImageToolDataType): HTMLElement { if (toolData.file === undefined || Object.keys(toolData.file).length === 0) { this.toggleStatus(UiState.Empty); } else { diff --git a/src/uploader.ts b/src/uploader.ts index 19dabb29..9b258e8d 100644 --- a/src/uploader.ts +++ b/src/uploader.ts @@ -7,19 +7,17 @@ import type { UploadResponseFormat, ImageConfig } from './types/types'; /** * Params interface for Uploader constructor */ -interface UploaderParams { +interface UploaderParams { /** * Configuration for the uploader */ - config: ImageConfig; - + config: ImageConfig; /** * Handles the upload response. - * @param {UploadResponseFormat} response - Response format expected from the backend on file uploading. + * @param {UploadResponseFormat} response - Response format expected from the backend on file uploading. * @returns {void} */ - onUpload: (response: UploadResponseFormat) => void; - + onUpload: (response: UploadResponseFormat) => void; /** * * @param error : error type @@ -34,9 +32,9 @@ interface UploaderParams { * 2. Upload by pasting URL * 3. Upload by pasting file from Clipboard or by Drag'n'Drop */ -export default class Uploader { - private config: ImageConfig; - private onUpload: (response: UploadResponseFormat) => void; +export default class Uploader { + private config: ImageConfig; + private onUpload: (response: UploadResponseFormat) => void; private onError: (error: string) => void; /** * @param params - uploader module params @@ -44,7 +42,7 @@ export default class Uploader { * @param params.onUpload - one callback for all uploading (file, url, d-n-d, pasting) * @param params.onError - callback for uploading errors */ - constructor({ config, onUpload, onError }: UploaderParams) { + constructor({ config, onUpload, onError }: UploaderParams) { this.config = config; this.onUpload = onUpload; this.onError = onError; @@ -69,7 +67,7 @@ export default class Uploader { * Custom uploading * or default uploading */ - let upload: Promise; + let upload: Promise>; // custom uploading if (this.config.uploader && typeof this.config.uploader.uploadByFile === 'function') { @@ -78,9 +76,9 @@ export default class Uploader { upload = ajax.selectFiles({ accept: this.config.types ?? 'image/*' }).then((files: File[]) => { preparePreview(files[0]); - const customUpload = uploadByFile(files[0]); + const customUpload: Promise> = uploadByFile(files[0]); - if (!isPromise(customUpload)) { + if (!isPromise(customUpload)) { console.warn('Custom uploader method uploadByFile should return a Promise'); } @@ -92,13 +90,13 @@ export default class Uploader { upload = ajax.transport({ url: this.config.endpoints.byFile, data: this.config.additionalRequestData, - accept: this.config.types ?? 'image/*', + accept: this.config.types, headers: this.config.additionalRequestHeaders as Record, beforeSend: (files: File[]) => { preparePreview(files[0]); }, fieldName: this.config.field ?? 'image', - }).then((response: AjaxResponse) => response.body as UploadResponseFormat); + }).then((response: AjaxResponse) => response.body as UploadResponseFormat); } upload.then((response) => { @@ -114,7 +112,7 @@ export default class Uploader { * @param url - image source url */ public uploadByUrl(url: string): void { - let upload; + let upload: Promise>; /** * Custom uploading @@ -136,10 +134,10 @@ export default class Uploader { }, this.config.additionalRequestData), type: ajax.contentType.JSON, headers: this.config.additionalRequestHeaders as Record, - }).then((response: AjaxResponse) => response.body as UploadResponseFormat); + }).then((response: AjaxResponse) => response.body as UploadResponseFormat); } - upload.then((response: UploadResponseFormat) => { + upload.then((response: UploadResponseFormat) => { this.onUpload(response); }).catch((error: string) => { this.onError(error); @@ -163,7 +161,7 @@ export default class Uploader { onPreview((e.target as FileReader).result as string); }; - let upload: Promise; + let upload: Promise>; /** * Custom uploading @@ -193,7 +191,7 @@ export default class Uploader { data: formData, type: ajax.contentType.JSON, headers: this.config.additionalRequestHeaders as Record, - }).then((response: AjaxResponse) => response.body as UploadResponseFormat); + }).then((response: AjaxResponse) => response.body as UploadResponseFormat); } upload.then((response) => { diff --git a/src/utils/isPromise.ts b/src/utils/isPromise.ts index 70de7c6e..5eb679d1 100644 --- a/src/utils/isPromise.ts +++ b/src/utils/isPromise.ts @@ -1,10 +1,8 @@ -import type { UploadResponseFormat } from '../types/types'; - /** * Check if passed object is a Promise * @param object - object to check * @returns */ -export default function isPromise(object: Promise): object is Promise { +export default function isPromise(object: Promise<{}>): object is Promise<{}> { return object !== undefined && typeof object.then === 'function'; } diff --git a/yarn.lock b/yarn.lock index b1b136d1..2a3bf174 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@codexteam/ajax/-/ajax-4.2.0.tgz#f89faecbaf8cd496bfd77ef20a7fe99ee20ca9a3" integrity sha512-54r/HZirqBPEV8rM9gZh570RCwG6M/iDAXT9Q9eGuMo9KZU49tw1dEDHjYsResGckfCsaymDqg4GnhfrBtX9JQ== -"@codexteam/icons@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.3.0.tgz#62380b4053d487a257de443864b5c72dafab95e6" - integrity sha512-fJE9dfFdgq8xU+sbsxjH0Kt8Yeatw9xHBJWb77DhRkEXz3OCoIS6hrRC1ewHEryxzIjxD8IyQrRq2f+Gz3BcmA== +"@codexteam/icons@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.3.2.tgz#b7aed0ba7b344e07953101f5476cded570d4f150" + integrity sha512-P1ep2fHoy0tv4wx85eic+uee5plDnZQ1Qa6gDfv7eHPkCXorMtVqJhzMb75o1izogh6G7380PqmFDXV3bW3Pig== "@editorjs/editorjs@2.30.0-rc.12": version "2.30.0-rc.12"