diff --git a/src/index.ts b/src/index.ts index bb3b6a52..ad9c76d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,9 +7,9 @@ * * To developers. * To simplify Tool structure, we split it to 4 parts: - * 1) index.js — main Tool's interface, public API and methods for working with data - * 2) uploader.js — module that has methods for sending files via AJAX: from device, by URL or File pasting - * 3) ui.js — module for UI manipulations: render, showing preloader, etc + * 1) index.ts — main Tool's interface, public API and methods for working with data + * 2) uploader.ts — module that has methods for sending files via AJAX: from device, by URL or File pasting + * 3) ui.ts — module for UI manipulations: render, showing preloader, etc * 4) tunes.js — working with Block Tunes: render buttons, handle clicks * * For debug purposes there is a testing server @@ -31,155 +31,19 @@ */ import type { TunesMenuConfig } from "@editorjs/editorjs/types/tools"; -import type { API, ToolboxConfig, PasteConfig, BaseTool } from '@editorjs/editorjs'; +import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockTool } from '@editorjs/editorjs'; import './index.css'; import Ui from './ui'; import Uploader from './uploader'; import { IconAddBorder, IconStretch, IconAddBackground, IconPicture } from '@codexteam/icons'; -import { TunesConfig } from './types/types'; +import { ActionConfig, UploadResponseFormat, ImageToolData, ImageToolConfig } from './types/types'; -/** - * ImageToolData interface representing the input and output data format for the image tool. - */ -export interface ImageToolData { - /** - * Caption for the image. - */ - caption: string; - /** - * Flag indicating whether the image has a border. - */ - withBorder: boolean; - /** - * Flag indicating whether the image has a background. - */ - withBackground: boolean; - /** - * Flag indicating whether the image is stretched. - */ - stretched: boolean; - /** - * Object containing the URL of the image file. - */ - file: { - url: string; - }; -} - -/** - * - * @description Config supported by Tool - */ -export interface ImageToolConfig { - /** - * Endpoints for upload, whether using file or URL. - */ - endpoints: { - /** - * Endpoint for file upload. - */ - byFile?: string; - /** - * Endpoints for URL upload. - */ - byUrl?: string; - }; - /** - * Field name for the uploaded image. - */ - field?: string; - /** - * Allowed mime-types for the uploaded image. - */ - types?: string; - /** - * Placeholder text for the caption field. - */ - captionPlaceholder?: string; - /** - * Additional data to send with requests. - */ - additionalRequestData?: object; - /** - * Additional headers to send with requests. - */ - additionalRequestHeaders?: object; - /** - * Custom content for the select file button. - */ - buttonContent?: string; - /** - * Optional custom uploader. - */ - uploader?: { - /** - * Method to upload an image by file. - */ - uploadByFile?: (file: Blob) => Promise; - /** - * Method to upload an image by URL. - */ - uploadByUrl?: (url: string) => Promise; - }; - /** - * Additional actions for the tool. - */ - actions?: Array; -} -/** - * @typedef {object} UploadResponseFormat - * @description This format expected from backend on file uploading - * @property {number} success - 1 for successful uploading, 0 for failure - * @property {object} file - Object with file data. - * 'url' is required, - * also can contain any additional data that will be saved and passed back - * @property {string} file.url - [Required] image source URL - */ -interface UploadResponseFormat { - /** - * success - 1 for successful uploading, 0 for failure - */ - success: number; - /** - * Object with file data. - * '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; - }; -} - -interface BlockToolConstructorOptions { - /** - * Previously saved data as ImageTool data. - */ - data: ImageToolData; - /** - * User config for Tool. - */ - config: ImageToolConfig; - /** - * Editor.js API. - */ - api: API; - /** - * Flag indicating read-only mode. - */ - readOnly: boolean; - /** - * Current Block API. - */ - block: any; -} +type ImageToolConstructorOptions = BlockToolConstructorOptions -export default class ImageTool implements BaseTool{ +export default class ImageTool implements BlockTool { /** * Editor.js API instance */ @@ -205,7 +69,7 @@ export default class ImageTool implements BaseTool{ */ private ui: Ui; /** - * Partial data for the ImageTool + * Stores current block data internally */ private _data: ImageToolData; @@ -217,7 +81,7 @@ export default class ImageTool implements BaseTool{ * @param {boolean} tool.readOnly - read-only mode flag * @param {BlockAPI|{}} tool.block - current Block API */ - constructor({ data, config, api, readOnly, block }: BlockToolConstructorOptions) { + constructor({ data, config, api, readOnly, block }: ImageToolConstructorOptions) { this.api = api; this.readOnly = readOnly; this.block = block; @@ -226,15 +90,15 @@ export default class ImageTool implements BaseTool{ * Tool's initial config */ this.config = { - endpoints: config.endpoints || '', - additionalRequestData: config.additionalRequestData || {}, - additionalRequestHeaders: config.additionalRequestHeaders || {}, - field: config.field || 'image', - types: config.types || 'image/*', - captionPlaceholder: this.api.i18n.t(config.captionPlaceholder || 'Caption'), - buttonContent: config.buttonContent || '', - uploader: config.uploader || undefined, - actions: config.actions || [], + endpoints: config? config.endpoints : {}, + additionalRequestData: config? config.additionalRequestData: {}, + additionalRequestHeaders: config? config.additionalRequestHeaders: {}, + field: config? config.field: 'image', + types: config? config.types: 'image/*', + captionPlaceholder: this.api.i18n.t(config && config.captionPlaceholder? config.captionPlaceholder: 'Caption'), + buttonContent: config? config.buttonContent : '', + uploader: config? config.uploader : undefined, + actions: config? config.actions: [], }; /** @@ -304,7 +168,7 @@ export default class ImageTool implements BaseTool{ * * @returns {Array} */ - static get tunes(): Array { + static get tunes(): Array { return [ { name: 'withBorder', @@ -346,7 +210,7 @@ export default class ImageTool implements BaseTool{ * @public */ validate(savedData: ImageToolData): boolean { - return savedData.file?.url !== undefined; + return !!savedData.file.url; } /** @@ -369,9 +233,9 @@ export default class ImageTool implements BaseTool{ * * @public * - * @returns {Array} + * @returns HTMLElement | TunesMenuConfig */ - renderSettings(): Array { + renderSettings(): HTMLElement | TunesMenuConfig { // Merge default tunes with the ones that might be added by user // @see https://github.com/editor-js/image/pull/49 const tunes = ImageTool.tunes.concat(this.config.actions || []); diff --git a/src/types/types.ts b/src/types/types.ts index d6a2ed3f..4fe144fc 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -8,14 +8,114 @@ export interface UploadOptions { /** * User configuration of Image block tunes. Allows to add custom tunes through the config */ -export type TunesConfig = { name: string; icon: string; title: string; toggle: boolean, action?: Function }; +export interface ActionConfig { name: string; icon: string; title: string; toggle: boolean, action?: Function }; /** * UploadResponseFormat interface representing the response format expected from the backend on file uploading. */ export interface UploadResponseFormat { + /** + * success - 1 for successful uploading, 0 for failure + */ success: number; + /** + * Object with file data. + * '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; }; } + +/** + * ImageToolData interface representing the input and output data format for the image tool. + */ +export interface ImageToolData { + /** + * Caption for the image. + */ + caption: string; + /** + * Flag indicating whether the image has a border. + */ + withBorder: boolean; + /** + * Flag indicating whether the image has a background. + */ + withBackground: boolean; + /** + * Flag indicating whether the image is stretched. + */ + stretched: boolean; + /** + * Object containing the URL of the image file. + */ + file: { + url: string; + }; +} + +/** + * + * @description Config supported by Tool + */ +export interface ImageToolConfig { + /** + * Endpoints for upload, whether using file or URL. + */ + endpoints: { + /** + * Endpoint for file upload. + */ + byFile?: string; + /** + * Endpoints for URL upload. + */ + byUrl?: string; + }; + /** + * Field name for the uploaded image. + */ + field?: string; + /** + * Allowed mime-types for the uploaded image. + */ + types?: string; + /** + * Placeholder text for the caption field. + */ + captionPlaceholder?: string; + /** + * Additional data to send with requests. + */ + additionalRequestData?: object; + /** + * Additional headers to send with requests. + */ + additionalRequestHeaders?: object; + /** + * Custom content for the select file button. + */ + buttonContent?: string; + /** + * Optional custom uploader. + */ + uploader?: { + /** + * Method to upload an image by file. + */ + uploadByFile?: (file: Blob) => Promise; + /** + * Method to upload an image by URL. + */ + uploadByUrl?: (url: string) => Promise; + }; + /** + * Additional actions for the tool. + */ + actions?: ActionConfig[]; +} \ No newline at end of file diff --git a/src/ui.ts b/src/ui.ts index 342cd4dc..69c2429b 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,13 +1,12 @@ import { IconPicture } from '@codexteam/icons'; import { make } from './utils/dom'; -import { ImageToolConfig } from './index'; import type { API } from '@editorjs/editorjs'; -import { ImageToolData } from './index'; +import { ImageToolData, ImageToolConfig } from './types/types'; enum UiState { - EMPTY = "empty", - UPLOADING = 'uploading', - FILLED = 'filled' + Empty = "EMPTY", + Uploading = "UPLOADING", + Filled = "FILLED" }; /** @@ -27,7 +26,7 @@ interface Nodes { */ fileButton: HTMLElement; /** - * The image element in the UI, if available. + * Represents the image element in the UI, if one is present; otherwise, it's undefined. */ imageEl?: HTMLElement; /** @@ -82,7 +81,7 @@ private config: ImageToolConfig; /** * Callback function for selecting a file. */ -private onSelectFile: Function; +private onSelectFile: () => void; /** * Flag indicating if the UI is in read-only mode. @@ -156,18 +155,6 @@ public nodes: Nodes; }; }; - /** - * Ui statuses: - * - empty - * - uploading - * - filled - * - * @returns {{EMPTY: string, UPLOADING: string, FILLED: string}} - */ - static get status(): typeof UiState { - return UiState; - } - /** * Renders tool UI * @@ -176,9 +163,9 @@ public nodes: Nodes; */ render(toolData: ImageToolData): HTMLElement { if (!toolData.file || Object.keys(toolData.file).length === 0) { - this.toggleStatus(Ui.status.EMPTY); + this.toggleStatus(UiState.Empty); } else { - this.toggleStatus(Ui.status.UPLOADING); + this.toggleStatus(UiState.Uploading); } return this.nodes.wrapper; } @@ -209,7 +196,7 @@ public nodes: Nodes; showPreloader(src: string): void { this.nodes.imagePreloader.style.backgroundImage = `url(${src})`; - this.toggleStatus(Ui.status.UPLOADING); + this.toggleStatus(UiState.Uploading); } /** @@ -219,7 +206,7 @@ public nodes: Nodes; */ hidePreloader(): void { this.nodes.imagePreloader.style.backgroundImage = ''; - this.toggleStatus(Ui.status.EMPTY); + this.toggleStatus(UiState.Empty); } /** @@ -280,7 +267,7 @@ public nodes: Nodes; * Add load event listener */ this.nodes.imageEl.addEventListener(eventName, () => { - this.toggleStatus(Ui.status.FILLED); + this.toggleStatus(UiState.Filled); /** * Preloader does not exists on first rendering with presaved data @@ -312,9 +299,9 @@ public nodes: Nodes; * @returns {void} */ toggleStatus(status: UiState): void { - for (const statusType in Ui.status) { - if (Object.prototype.hasOwnProperty.call(Ui.status, statusType)) { - this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${Ui.status[statusType as keyof typeof UiState]}`, status === Ui.status[statusType as keyof typeof UiState]); + 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]); } } } diff --git a/src/uploader.ts b/src/uploader.ts index 7c5ecbf2..d8a7472b 100644 --- a/src/uploader.ts +++ b/src/uploader.ts @@ -1,8 +1,7 @@ import ajax from '@codexteam/ajax'; import isPromise from './utils/isPromise'; -import { ImageToolConfig } from './index'; import { UploadOptions } from './types/types'; -import { UploadResponseFormat } from './types/types'; +import { UploadResponseFormat, ImageToolConfig } from './types/types'; /** * Params interface for Uploader constructor @@ -87,7 +86,7 @@ export default class Uploader { // default uploading } else { upload = ajax.transport({ - url: this.config.endpoints.byFile || '', + url: this.config.endpoints.byFile!, data: this.config.additionalRequestData, accept: this.config.types, headers: this.config.additionalRequestHeaders as Record, @@ -128,7 +127,7 @@ export default class Uploader { * Default uploading */ upload = ajax.post({ - url: this.config.endpoints.byUrl || '', + url: this.config.endpoints.byUrl!, data: Object.assign({ url: url, }, this.config.additionalRequestData), @@ -181,9 +180,7 @@ export default class Uploader { */ const formData = new FormData(); - if (this.config.field) { - formData.append(this.config.field, file); - } + formData.append(this.config.field || '', file); if (this.config.additionalRequestData && Object.keys(this.config.additionalRequestData).length) { Object.entries(this.config.additionalRequestData).forEach(([name, value]: [string, any]) => { @@ -192,7 +189,7 @@ export default class Uploader { } upload = ajax.post({ - url: this.config.endpoints.byFile || '', + url: this.config.endpoints.byFile!, data: formData, type: ajax.contentType.JSON, headers: this.config.additionalRequestHeaders as Record,