Skip to content

Commit d5269fd

Browse files
committed
Remove all any types and eslint-disable comments from koenig-lexical
1 parent e52f0f3 commit d5269fd

30 files changed

Lines changed: 123 additions & 131 deletions

packages/koenig-lexical/eslint.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import js from '@eslint/js';
2+
import {defineConfig} from 'eslint/config';
23
import reactHooks from 'eslint-plugin-react-hooks';
34
import reactRefresh from 'eslint-plugin-react-refresh';
45
import tseslint from 'typescript-eslint';
56
import ghostPlugin from 'eslint-plugin-ghost';
67

7-
export default tseslint.config([
8+
export default defineConfig([
89
{ignores: ['dist/**', 'build/**', '.storybook/**']},
910
{
1011
files: ['**/*.{ts,tsx}'],

packages/koenig-lexical/src/components/ui/LinkInputWithSearch.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ export function LinkInputWithSearch({href, update, cancel}: LinkInputWithSearchP
1818

1919
// store the href/query in state so we can update it without affecting the saved editor value
2020
const [_href, setHref] = React.useState(href);
21-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22-
const {isSearching, listOptions} = useSearchLinks(_href || '', searchLinks as any);
21+
const {isSearching, listOptions} = useSearchLinks(_href || '', searchLinks as (term?: string) => Promise<unknown>);
2322

2423
// add refs for input and container
2524
const containerRef = React.useRef<HTMLDivElement>(null);

packages/koenig-lexical/src/components/ui/cards/MarkdownCard.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ export function MarkdownCard({markdown = '', updateMarkdown, isEditing, imageUpl
3232
);
3333
}
3434

35-
<<<<<<< HEAD
36-
function MarkdownDisplay({markdown}) {
37-
=======
3835
function MarkdownDisplay({markdown}: {markdown: string}) {
39-
>>>>>>> 4b9c6da1fd (Migrate koenig-lexical to TypeScript)
4036
const markdownHtml = markdownRender(markdown);
4137
const sanitizedHtml = sanitizeHtml(markdownHtml, {replaceJS: true});
4238

packages/koenig-lexical/src/components/ui/cards/MarkdownCard/MarkdownEditor.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ import {useLayoutEffect, useRef, useState} from 'react';
77
import ctrlOrCmd from '../../../../utils/ctrlOrCmd';
88
import useMarkdownImageUploader from './useMarkdownImageUploader';
99

10+
interface CodeMirrorInstance {
11+
on(event: string, handler: (...args: unknown[]) => void): void;
12+
off(event: string, handler: (...args: unknown[]) => void): void;
13+
focus(): void;
14+
execCommand(command: string): void;
15+
getOption(name: string): unknown;
16+
setOption(name: string, value: unknown): void;
17+
}
18+
19+
interface SimpleMDEInstance {
20+
value(val?: string): string;
21+
codemirror: CodeMirrorInstance;
22+
toTextArea(): void;
23+
toolbarElements: Record<string, HTMLElement>;
24+
}
25+
1026
interface MarkdownEditorProps {
1127
markdown?: string;
1228
updateMarkdown?: (value: string) => void;
@@ -25,8 +41,7 @@ export default function MarkdownEditor({
2541
placeholder = ''
2642
}: MarkdownEditorProps) {
2743
const editorRef = useRef<HTMLTextAreaElement>(null);
28-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
29-
const markdownEditor = useRef<any>(null);
44+
const markdownEditor = useRef<SimpleMDEInstance | null>(null);
3045
const [isHelpDialogOpen, setHelpDialogOpen] = useState(false);
3146
const [isUnsplashDialogOpen, setUnsplashDialogOpen] = useState(false);
3247
const {
@@ -116,8 +131,9 @@ export default function MarkdownEditor({
116131
});
117132

118133
// add non-breaking space as a special char
119-
// eslint-disable-next-line no-control-regex
120-
editorInstance.codemirror.setOption('specialChars', /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\xa0]/g);
134+
// control characters are intentional - this regex detects special chars for CodeMirror
135+
const specialCharsPattern = `[${String.fromCharCode(0)}-${String.fromCharCode(0x1f)}${String.fromCharCode(0x7f)}-${String.fromCharCode(0x9f)}\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\xa0]`;
136+
editorInstance.codemirror.setOption('specialChars', new RegExp(specialCharsPattern, 'g'));
121137

122138
if (autofocus) {
123139
editorInstance.codemirror.execCommand('goDocEnd');
@@ -172,7 +188,7 @@ export default function MarkdownEditor({
172188
}
173189

174190
function toggleButtonClass() {
175-
const spellcheckButton = (markdownEditor.current as unknown as {toolbarElements: Record<string, HTMLElement>}).toolbarElements.spellcheck;
191+
const spellcheckButton = markdownEditor.current!.toolbarElements.spellcheck;
176192

177193
if (spellcheckButton) {
178194
if (markdownEditor.current!.codemirror.getOption('mode') === 'spell-checker') {

packages/koenig-lexical/src/hooks/useSearchLinks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function convertSearchResultsToListOptions(results: SearchResult[] | undefined,
8484
});
8585
}
8686

87-
export const useSearchLinks = (query: string, searchLinks: (term?: string) => Promise<SearchResult[] | undefined>, {noResultOptions}: {noResultOptions?: () => ListOption[]} = {}) => {
87+
export const useSearchLinks = (query: string, searchLinks: (term?: string) => Promise<unknown>, {noResultOptions}: {noResultOptions?: () => ListOption[]} = {}) => {
8888
const [defaultListOptions, setDefaultListOptions] = React.useState<ListOption[]>([]);
8989
const [listOptions, setListOptions] = React.useState<ListOption[]>([]);
9090
const [isSearching, setIsSearching] = React.useState(false);
@@ -97,7 +97,7 @@ export const useSearchLinks = (query: string, searchLinks: (term?: string) => Pr
9797
}
9898

9999
setIsSearching(true);
100-
const results = await searchLinks(term);
100+
const results = await searchLinks(term) as SearchResult[] | undefined;
101101

102102
// can return undefined if the search was cancelled, avoid updating
103103
// in that scenario because we can end up in a race condition where
@@ -125,7 +125,7 @@ export const useSearchLinks = (query: string, searchLinks: (term?: string) => Pr
125125
if (!query) {
126126
setIsSearching(true);
127127
}
128-
const results = await searchLinks();
128+
const results = await searchLinks() as SearchResult[] | undefined;
129129
setDefaultListOptions(convertSearchResultsToListOptions(results, {type: 'default'}));
130130
if (!query) {
131131
setIsSearching(false);

packages/koenig-lexical/src/plugins/AtLinkPlugin.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
KEY_ESCAPE_COMMAND,
2828
PASTE_COMMAND
2929
} from 'lexical';
30-
import type {LexicalNode} from 'lexical';
30+
import type {ElementFormatType, LexicalNode} from 'lexical';
3131
import {$insertFirst, mergeRegister} from '@lexical/utils';
3232
import {AtLinkResultsPopup} from '../components/ui/AtLinkResultsPopup';
3333
import {isInternalUrl} from '../utils/isInternalUrl';
@@ -63,13 +63,12 @@ function noResultOptions() {
6363
}
6464

6565
// Manages at-link search nodes and display of the search results panel when appropriate
66-
export const KoenigAtLinkPlugin = ({searchLinks, siteUrl}: {searchLinks: (...args: unknown[]) => Promise<unknown>; siteUrl?: string}) => {
66+
export const KoenigAtLinkPlugin = ({searchLinks, siteUrl}: {searchLinks: (term?: string) => Promise<unknown>; siteUrl?: string}) => {
6767
const [editor] = useLexicalComposerContext();
6868
const [focusedAtLinkNode, setFocusedAtLinkNode] = React.useState<AtLinkNode | null>(null);
6969
const [query, setQuery] = React.useState('');
7070
const searchOptions = React.useMemo(() => ({noResultOptions}), []);
71-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
72-
const {isSearching, listOptions} = useSearchLinks(query, searchLinks as any, searchOptions);
71+
const {isSearching, listOptions} = useSearchLinks(query, searchLinks, searchOptions);
7372

7473
// register an event listener to detect '@' character being typed
7574
// - we only ever want to convert an '@' to an at-link node when it's typed
@@ -424,8 +423,7 @@ export const KoenigAtLinkPlugin = ({searchLinks, siteUrl}: {searchLinks: (...arg
424423
}, [editor]);
425424

426425
// when a search result is selected, replace the at-link node with a link node
427-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
428-
const onItemSelect = React.useCallback((item: any) => {
426+
const onItemSelect = React.useCallback((item: {value?: string; label?: string; type?: string}) => {
429427
editor.update(() => {
430428
if (!item?.value || !focusedAtLinkNode) {
431429
$removeAtLink(focusedAtLinkNode, {focus: true});
@@ -445,8 +443,7 @@ export const KoenigAtLinkPlugin = ({searchLinks, siteUrl}: {searchLinks: (...arg
445443
const linkNode = $createLinkNode(item.value!);
446444
const textNode = $createTextNode(item.label ?? '');
447445
linkNode.append(textNode);
448-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
449-
linkNode.setFormat(focusedAtLinkNode.getLinkFormat() as any);
446+
linkNode.setFormat(focusedAtLinkNode.getLinkFormat() as unknown as ElementFormatType);
450447

451448
focusedAtLinkNode.replace(linkNode);
452449
linkNode.selectEnd();
@@ -507,7 +504,7 @@ export const AtLinkPlugin = () => {
507504
return null;
508505
}
509506

510-
return <KoenigAtLinkPlugin searchLinks={cardConfig.searchLinks as (...args: unknown[]) => Promise<unknown>} siteUrl={cardConfig.siteUrl} />;
507+
return <KoenigAtLinkPlugin searchLinks={cardConfig.searchLinks as (term?: string) => Promise<unknown>} siteUrl={cardConfig.siteUrl} />;
511508
};
512509

513510
export default AtLinkPlugin;

packages/koenig-lexical/src/plugins/DragDropReorderPlugin.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ function useDragDropReorder(editor: LexicalEditor, _isEditable: boolean) {
6262

6363
const createCardDragElement = React.useRef((draggableInfo: DraggableInfo) => {
6464
const {cardName} = draggableInfo;
65-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
66-
const Icon = draggableInfo.Icon as React.FC<any> | undefined;
65+
const Icon = draggableInfo.Icon as React.FC<{className?: string}> | undefined;
6766

6867
if (!cardName || cardName === 'image' || !Icon) {
6968
return;

packages/koenig-lexical/src/plugins/EmojiPickerPlugin.tsx

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
1111

1212
init({data: emojiData});
1313

14-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
15-
const EmojiMenuItem = function ({index, isSelected, onClick, onMouseEnter, emoji}: {index: number; isSelected: boolean; onClick: (e: React.MouseEvent) => void; onMouseEnter: () => void; emoji: any}) {
14+
interface Emoji {
15+
id: string;
16+
skins: Array<{native: string}>;
17+
ref?: React.RefObject<HTMLLIElement | null>;
18+
}
19+
20+
const EmojiMenuItem = function ({index, isSelected, onClick, onMouseEnter, emoji}: {index: number; isSelected: boolean; onClick: (e: React.MouseEvent) => void; onMouseEnter: () => void; emoji: Emoji}) {
1621
// we need to manually set this unless we import the MenuOption type and extend it (see LexicalTypeaheadMenuPlugin)
1722
const ref = React.useRef(null);
1823
emoji.ref = ref;
@@ -38,8 +43,7 @@ const EmojiMenuItem = function ({index, isSelected, onClick, onMouseEnter, emoji
3843
export function EmojiPickerPlugin() {
3944
const [editor] = useLexicalComposerContext();
4045
const [queryString, setQueryString] = React.useState<string | null>(null);
41-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
42-
const [searchResults, setSearchResults] = React.useState<any[] | null>(null);
46+
const [searchResults, setSearchResults] = React.useState<Emoji[] | null>(null);
4347

4448
const checkForTriggerMatch = useTypeaheadTriggerMatch(':', {minLength: 1});
4549

@@ -71,15 +75,13 @@ export function EmojiPickerPlugin() {
7175
if (cursorInInlineCodeBlock() === true) {
7276
return false;
7377
}
74-
SearchIndex.search(queryString).then((emojis: unknown[]) => {
78+
SearchIndex.search(queryString).then((emojis: Emoji[]) => {
7579
if (emojis.length === 0) {
7680
return;
7781
}
78-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
79-
const emojiMatch = (emojis as any)?.[0].id === queryString;
82+
const emojiMatch = emojis[0].id === queryString;
8083
if (emojiMatch) {
81-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
82-
handleCompletionInsertion((emojis as any)[0]);
84+
handleCompletionInsertion(emojis[0]);
8385
}
8486
});
8587
event.preventDefault();
@@ -92,8 +94,7 @@ export function EmojiPickerPlugin() {
9294
);
9395
});
9496

95-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
96-
const handleCompletionInsertion = React.useCallback((emoji: any) => {
97+
const handleCompletionInsertion = React.useCallback((emoji: Emoji) => {
9798
editor.update(() => {
9899
const selection = $getSelection();
99100

@@ -118,8 +119,7 @@ export function EmojiPickerPlugin() {
118119
}
119120

120121
async function searchEmojis() {
121-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
122-
let filteredEmojis: any[] = [];
122+
let filteredEmojis: Emoji[] = [];
123123
if ([')','-)'].includes(queryString!)) {
124124
filteredEmojis = await SearchIndex.search('smile');
125125
} else if (['(','-('].includes(queryString!)) {
@@ -133,8 +133,7 @@ export function EmojiPickerPlugin() {
133133
searchEmojis();
134134
}, [queryString]);
135135

136-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
137-
const onEmojiSelect = React.useCallback((selectedOption: any, nodeToRemove: any, closeMenu: () => void) => {
136+
const onEmojiSelect = React.useCallback((selectedOption: Emoji, nodeToRemove: {remove: () => void} | null, closeMenu: () => void) => {
138137
editor.update(() => {
139138
const selection = $getSelection();
140139

packages/koenig-lexical/src/plugins/PlusCardMenuPlugin.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import KoenigComposerContext from '../context/KoenigComposerContext.jsx';
22
import React from 'react';
33
import {$getSelection, $isParagraphNode, $isRangeSelection, $setSelection} from 'lexical';
4-
import type {LexicalEditor} from 'lexical';
4+
import type {LexicalCommand, LexicalEditor} from 'lexical';
55
import {CardMenu} from '../components/ui/CardMenu';
66
import {PlusButton, PlusMenu} from '../components/ui/PlusMenu';
77
import {buildCardMenu, type CardMenuItem} from '../utils/buildCardMenu';
@@ -111,8 +111,7 @@ function usePlusCardMenu(editor: LexicalEditor) {
111111
});
112112
}, [editor, showButton, hideButton]);
113113

114-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
115-
const insert = React.useCallback((insertCommand: any, {insertParams = {}}: {insertParams?: Record<string, unknown>} = {}) => {
114+
const insert = React.useCallback((insertCommand: LexicalCommand<Record<string, unknown>>, {insertParams = {}}: {insertParams?: Record<string, unknown>} = {}) => {
116115
const commandParams = {...insertParams};
117116
editor.dispatchCommand(insertCommand, commandParams);
118117
closeMenu();

packages/koenig-lexical/src/plugins/SlashCardMenuPlugin.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import KoenigComposerContext from '../context/KoenigComposerContext.jsx';
22
import React from 'react';
33
import {$createParagraphNode, $getSelection, $isParagraphNode, $isRangeSelection, COMMAND_PRIORITY_HIGH, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ENTER_COMMAND} from 'lexical';
4-
import type {LexicalEditor} from 'lexical';
4+
import type {LexicalCommand, LexicalEditor} from 'lexical';
55
import {CardMenu} from '../components/ui/CardMenu';
66
import {SlashMenu} from '../components/ui/SlashMenu';
77
import {buildCardMenu, type CardMenuItem} from '../utils/buildCardMenu';
@@ -89,8 +89,7 @@ function useSlashCardMenu(editor: LexicalEditor) {
8989
cachedRange.current = null;
9090
}, [setIsShowingMenu, commandParams]);
9191

92-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
93-
const insert = React.useCallback((insertCommand: any, {insertParams = {}, queryParams = []} : {insertParams?: Record<string, unknown>; queryParams?: string[]} = {}) => {
92+
const insert = React.useCallback((insertCommand: LexicalCommand<Record<string, unknown>>, {insertParams = {}, queryParams = []} : {insertParams?: Record<string, unknown>; queryParams?: string[]} = {}) => {
9493
const dataset: Record<string, unknown> = {...insertParams};
9594

9695
for (let i = 0; i < queryParams.length; i++) {

0 commit comments

Comments
 (0)