fix: lint/typescript errors

This commit is contained in:
JackUait 2025-11-06 18:47:00 +03:00
commit 29c087bf08
12 changed files with 8330 additions and 5527 deletions

BIN
.yarn/install-state.gz Normal file

Binary file not shown.

1
.yarnrc.yml Normal file
View file

@ -0,0 +1 @@
nodeLinker: node-modules

View file

@ -0,0 +1,12 @@
/**
* Fired when block settings is closed
*/
export const BlockSettingsClosed = 'block-settings-closed';
/**
* Payload that will be passed with the event
*/
export interface BlockSettingsClosedPayload {
// No payload needed for this event
}

View file

@ -0,0 +1,12 @@
/**
* Fired when block settings is opened
*/
export const BlockSettingsOpened = 'block-settings-opened';
/**
* Payload that will be passed with the event
*/
export interface BlockSettingsOpenedPayload {
// No payload needed for this event
}

View file

@ -38,8 +38,8 @@ export default class DragNDrop extends Module {
private enableModuleBindings(): void {
const { UI } = this.Editor;
this.readOnlyMutableListeners.on(UI.nodes.holder, 'drop', async (dropEvent: DragEvent) => {
await this.processDrop(dropEvent);
this.readOnlyMutableListeners.on(UI.nodes.holder, 'drop', (dropEvent: Event) => {
void this.processDrop(dropEvent as DragEvent);
}, true);
this.readOnlyMutableListeners.on(UI.nodes.holder, 'dragstart', () => {
@ -49,8 +49,8 @@ export default class DragNDrop extends Module {
/**
* Prevent default browser behavior to allow drop on non-contenteditable elements
*/
this.readOnlyMutableListeners.on(UI.nodes.holder, 'dragover', (dragEvent: DragEvent) => {
this.processDragOver(dragEvent);
this.readOnlyMutableListeners.on(UI.nodes.holder, 'dragover', (dragEvent: Event) => {
this.processDragOver(dragEvent as DragEvent);
}, true);
}

View file

@ -171,8 +171,9 @@ export default class Paste extends Module {
/**
* In Microsoft Edge types is DOMStringList. So 'contains' is used to check if 'Files' type included
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const includesFiles = types.includes ? types.includes('Files') : (types as any).contains('Files');
const includesFiles = typeof types.includes === 'function'
? types.includes('Files')
: (types as unknown as DOMStringList).contains('Files');
if (includesFiles && !_.isEmpty(this.toolsFiles)) {
await this.processFiles(dataTransfer.files);
@ -211,7 +212,7 @@ export default class Paste extends Module {
result[tag.toLowerCase()] = this.toolsTags[tag].sanitizationConfig ?? {};
return result;
}, {});
}, {} as SanitizerConfig);
const customConfig = Object.assign({}, toolsTags, Tools.getAllInlineToolsSanitizeConfig(), { br: {} });
const cleanData = clean(htmlData, customConfig);
@ -240,16 +241,16 @@ export default class Paste extends Module {
if (dataToInsert.length === 1) {
if (!dataToInsert[0].isBlock) {
this.processInlinePaste(dataToInsert.pop());
this.processInlinePaste(dataToInsert[0]);
} else {
this.processSingleBlock(dataToInsert.pop());
this.processSingleBlock(dataToInsert[0]);
}
return;
}
const isCurrentBlockDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
const isCurrentBlockDefault = Boolean(BlockManager.currentBlock?.tool.isDefault);
const needToReplaceCurrentBlock = isCurrentBlockDefault && Boolean(BlockManager.currentBlock?.isEmpty);
dataToInsert.map(
async (content, i) => this.insertBlock(content, i === 0 && needToReplaceCurrentBlock)
@ -260,18 +261,27 @@ export default class Paste extends Module {
}
}
/**
* Wrapper handler for paste event that matches listeners.on signature
*
* @param {Event} event - paste event
*/
private handlePasteEventWrapper = (event: Event): void => {
this.handlePasteEvent(event as ClipboardEvent);
};
/**
* Set onPaste callback handler
*/
private setCallback(): void {
this.listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
this.listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEventWrapper);
}
/**
* Unset onPaste callback handler
*/
private unsetCallback(): void {
this.listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
this.listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEventWrapper);
}
/**
@ -351,7 +361,7 @@ export default class Paste extends Module {
}
const tagsOrSanitizeConfigs = tool.pasteConfig.tags || [];
const toolTags = [];
const toolTags: string[] = [];
tagsOrSanitizeConfigs.forEach((tagOrSanitizeConfig) => {
const tags = this.collectTagNames(tagOrSanitizeConfig);
@ -373,7 +383,7 @@ export default class Paste extends Module {
/**
* Get sanitize config for tag.
*/
const sanitizationConfig = _.isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : null;
const sanitizationConfig = _.isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : undefined;
this.toolsTags[tag.toUpperCase()] = {
tool,
@ -486,7 +496,7 @@ export default class Paste extends Module {
/** If target is native input or is not Block, use browser behaviour */
if (
!currentBlock || (this.isNativeBehaviour(event.target) && !event.clipboardData.types.includes('Files'))
!currentBlock || (event.target && this.isNativeBehaviour(event.target) && event.clipboardData && !event.clipboardData.types.includes('Files'))
) {
return;
}
@ -494,12 +504,14 @@ export default class Paste extends Module {
/**
* If Tools is in list of errors, skip processing of paste event
*/
if (currentBlock && this.exceptionList.includes(currentBlock.name)) {
if (this.exceptionList.includes(currentBlock.name)) {
return;
}
event.preventDefault();
this.processDataTransfer(event.clipboardData);
if (event.clipboardData) {
this.processDataTransfer(event.clipboardData);
}
Toolbar.close();
};
@ -512,17 +524,15 @@ export default class Paste extends Module {
private async processFiles(items: FileList): Promise<void> {
const { BlockManager } = this.Editor;
let dataToInsert: { type: string; event: PasteEvent }[];
dataToInsert = await Promise.all(
const processedFiles = await Promise.all(
Array
.from(items)
.map((item) => this.processFile(item))
);
dataToInsert = dataToInsert.filter((data) => !!data);
const dataToInsert = processedFiles.filter((data): data is { type: string; event: PasteEvent } => data != null);
const isCurrentBlockDefault = BlockManager.currentBlock.tool.isDefault;
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
const isCurrentBlockDefault = Boolean(BlockManager.currentBlock?.tool.isDefault);
const needToReplaceCurrentBlock = isCurrentBlockDefault && Boolean(BlockManager.currentBlock?.isEmpty);
dataToInsert.forEach(
(data, i) => {
@ -536,7 +546,7 @@ export default class Paste extends Module {
*
* @param {File} file - file to process
*/
private async processFile(file: File): Promise<{ event: PasteEvent; type: string }> {
private async processFile(file: File): Promise<{ event: PasteEvent; type: string } | undefined> {
const extension = _.getFileExtension(file);
const foundConfig = Object
@ -552,7 +562,7 @@ export default class Paste extends Module {
return type === fileType && (subtype === fileSubtype || subtype === '*');
});
return !!foundExt || !!foundMimeType;
return foundExt !== undefined || foundMimeType !== undefined;
});
if (!foundConfig) {
@ -598,7 +608,7 @@ export default class Paste extends Module {
return nodes
.map((node) => {
let content, tool = Tools.defaultTool, isBlock = false;
let content: HTMLElement | null | undefined, tool = Tools.defaultTool, isBlock = false;
switch (node.nodeType) {
/** If node is a document fragment, use temp wrapper to get innerHTML */
@ -612,16 +622,27 @@ export default class Paste extends Module {
content = node as HTMLElement;
isBlock = true;
if (this.toolsTags[content.tagName]) {
tool = this.toolsTags[content.tagName].tool;
const tagSubstitute = this.toolsTags[content.tagName];
if (tagSubstitute !== undefined) {
tool = tagSubstitute.tool;
}
break;
}
/**
* Skip if content is not an HTMLElement
*/
if (!content || !(content instanceof HTMLElement)) {
return null;
}
/**
* Returns empty array if there is no paste config
*/
const { tags: tagsOrSanitizeConfigs } = tool.pasteConfig || { tags: [] };
const tagsOrSanitizeConfigs = tool.pasteConfig === false
? []
: (tool.pasteConfig?.tags || []);
/**
* Reduce the tags or sanitize configs to a single array of sanitize config.
@ -652,17 +673,17 @@ export default class Paste extends Module {
* 'tr':{height: true}
* ]
*/
const toolTags = tagsOrSanitizeConfigs.reduce((result, tagOrSanitizeConfig) => {
const toolTags = tagsOrSanitizeConfigs.reduce((result: SanitizerConfig, tagOrSanitizeConfig) => {
const tags = this.collectTagNames(tagOrSanitizeConfig);
tags.forEach((tag) => {
const sanitizationConfig = _.isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : null;
const sanitizationConfig = _.isObject(tagOrSanitizeConfig) ? (tagOrSanitizeConfig as SanitizerConfig)[tag] : null;
result[tag.toLowerCase()] = sanitizationConfig || {};
result[tag.toLowerCase()] = sanitizationConfig ?? {};
});
return result;
}, {});
}, {} as SanitizerConfig);
const customConfig = Object.assign({}, toolTags, tool.baseSanitizeConfig);
@ -676,7 +697,12 @@ export default class Paste extends Module {
innerHTML: cleanTableHTML,
});
content = tmpWrapper.firstChild;
const firstChild = tmpWrapper.firstChild;
if (!firstChild || !(firstChild instanceof HTMLElement)) {
return null;
}
content = firstChild;
} else {
content.innerHTML = clean(content.innerHTML, customConfig);
}
@ -692,7 +718,10 @@ export default class Paste extends Module {
event,
};
})
.filter((data) => {
.filter((data): data is PasteData => {
if (!data) {
return false;
}
const isEmpty = $.isEmpty(data.content);
const isSingleTag = $.isSingleTag(data.content);
@ -753,7 +782,7 @@ export default class Paste extends Module {
dataToInsert.tool !== currentBlock.name ||
!$.containsOnlyInlineElements(dataToInsert.content.innerHTML)
) {
this.insertBlock(dataToInsert, currentBlock?.tool.isDefault && currentBlock.isEmpty);
this.insertBlock(dataToInsert, currentBlock ? (currentBlock.tool.isDefault && currentBlock.isEmpty) : false);
return;
}
@ -773,10 +802,11 @@ export default class Paste extends Module {
const { BlockManager, Caret } = this.Editor;
const { content } = dataToInsert;
const currentBlockIsDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;
const currentBlockIsDefault = BlockManager.currentBlock?.tool.isDefault ?? false;
const textContent = content.textContent;
if (currentBlockIsDefault && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
const blockData = await this.processPattern(content.textContent);
if (currentBlockIsDefault && textContent !== null && textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
const blockData = await this.processPattern(textContent);
if (blockData) {
const needToReplaceCurrentBlock = BlockManager.currentBlock &&
@ -795,9 +825,7 @@ export default class Paste extends Module {
if (BlockManager.currentBlock && BlockManager.currentBlock.currentInput) {
const currentToolSanitizeConfig = BlockManager.currentBlock.tool.baseSanitizeConfig;
document.execCommand(
'insertHTML',
false,
Caret.insertContentAtCaretPosition(
clean(content.innerHTML, currentToolSanitizeConfig)
);
} else {
@ -811,7 +839,7 @@ export default class Paste extends Module {
* @param {string} text - text to process
* @returns {Promise<{event: PasteEvent, tool: string}>}
*/
private async processPattern(text: string): Promise<{ event: PasteEvent; tool: string }> {
private async processPattern(text: string): Promise<{ event: PasteEvent; tool: string } | undefined> {
const pattern = this.toolsPatterns.find((substitute) => {
const execResult = substitute.pattern.exec(text);
@ -870,16 +898,16 @@ export default class Paste extends Module {
private insertEditorJSData(blocks: Pick<SavedData, 'id' | 'data' | 'tool'>[]): void {
const { BlockManager, Caret, Tools } = this.Editor;
const sanitizedBlocks = sanitizeBlocks(blocks, (name) =>
Tools.blockTools.get(name).sanitizeConfig
Tools.blockTools.get(name)?.sanitizeConfig ?? {}
);
sanitizedBlocks.forEach(({ tool, data }, i) => {
let needToReplaceCurrentBlock = false;
if (i === 0) {
const isCurrentBlockDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;
const isCurrentBlockDefault = Boolean(BlockManager.currentBlock?.tool.isDefault);
needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
needToReplaceCurrentBlock = isCurrentBlockDefault && Boolean(BlockManager.currentBlock?.isEmpty);
}
const block = BlockManager.insert({
@ -904,8 +932,9 @@ export default class Paste extends Module {
const element = node as HTMLElement;
const { tool } = this.toolsTags[element.tagName] || {};
const toolTags = this.tagsByTool[tool?.name] || [];
const tagSubstitute = this.toolsTags[element.tagName];
const tool = tagSubstitute?.tool;
const toolTags = this.tagsByTool[tool?.name ?? ''] ?? [];
const isSubstitutable = tags.includes(element.tagName);
const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase());
@ -955,8 +984,9 @@ export default class Paste extends Module {
let destNode: Node = new DocumentFragment();
if (lastNode && $.isFragment(lastNode)) {
destNode = nodes.pop();
if (lastNode !== undefined && $.isFragment(lastNode)) {
destNode = lastNode;
nodes.pop();
}
switch (node.nodeType) {

View file

@ -48,7 +48,7 @@ export default class ReadOnly extends Module {
this.toolsDontSupportReadOnly = toolsDontSupportReadOnly;
if (this.config.readOnly && toolsDontSupportReadOnly.length > 0) {
if (this.config.readOnly === true && toolsDontSupportReadOnly.length > 0) {
this.throwCriticalError();
}
@ -71,18 +71,22 @@ export default class ReadOnly extends Module {
this.readOnlyEnabled = state;
for (const name in this.Editor) {
for (const module of Object.values(this.Editor)) {
/**
* Verify module has method `toggleReadOnly` method
*/
if (!this.Editor[name].toggleReadOnly) {
if (module === null || module === undefined) {
continue;
}
if (typeof (module as { toggleReadOnly?: unknown }).toggleReadOnly !== 'function') {
continue;
}
/**
* set or toggle read-only state
*/
this.Editor[name].toggleReadOnly(state);
(module as { toggleReadOnly: (state: boolean) => void }).toggleReadOnly(state);
}
/**

48
src/types-internal/shortcuts.d.ts vendored Normal file
View file

@ -0,0 +1,48 @@
/**
* Declaration for external JS module
* After that we can use it at the TS modules
*/
declare module '@codexteam/shortcuts' {
interface ShortcutOptions {
/**
* Shortcut name (e.g., 'CMD+K', 'CMD+B')
*/
name: string;
/**
* Element to attach the shortcut to
*/
on: HTMLElement | Document;
/**
* Callback function to execute when shortcut is triggered
*/
callback: (event: KeyboardEvent) => void;
}
/**
* Shortcut class for handling keyboard shortcuts
*/
export class Shortcut {
/**
* Shortcut name
*/
public name: string;
/**
* Creates a new Shortcut instance
*/
constructor(options: ShortcutOptions);
/**
* Removes the shortcut event listener
*/
public remove(): void;
}
/**
* Default export
*/
export default Shortcut;
}

28
test/cypress/code.d.ts vendored Normal file
View file

@ -0,0 +1,28 @@
/**
* Declaration for external JS module @editorjs/code
*/
declare module '@editorjs/code' {
interface CodeConfig {
data?: Record<string, unknown>;
readOnly?: boolean;
api?: unknown;
block?: unknown;
}
interface CodeInstance {
render(): HTMLElement;
save(block: HTMLElement): Record<string, unknown>;
}
interface CodeTool {
toolbox?: unknown;
pasteConfig?: unknown;
conversionConfig?: unknown;
isReadOnlySupported?: boolean;
new (config: CodeConfig): CodeInstance;
}
const Code: CodeTool;
export default Code;
}

28
test/cypress/delimiter.d.ts vendored Normal file
View file

@ -0,0 +1,28 @@
/**
* Declaration for external JS module @editorjs/delimiter
*/
declare module '@editorjs/delimiter' {
interface DelimiterConfig {
data?: Record<string, unknown>;
readOnly?: boolean;
api?: unknown;
block?: unknown;
}
interface DelimiterInstance {
render(): HTMLElement;
save(block: HTMLElement): Record<string, unknown>;
}
interface DelimiterTool {
toolbox?: unknown;
pasteConfig?: unknown;
conversionConfig?: unknown;
isReadOnlySupported?: boolean;
new (config: DelimiterConfig): DelimiterInstance;
}
const Delimiter: DelimiterTool;
export default Delimiter;
}

7
vite-env.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.css?inline' {
const content: string;
export default content;
}

13577
yarn.lock

File diff suppressed because it is too large Load diff