Update dependencies and enhance ESLint config

This commit is contained in:
Ludy87 2025-09-26 11:28:41 +02:00
parent fd52dc0226
commit 4a8e2e477c
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
38 changed files with 2196 additions and 402 deletions

1
.gitignore vendored
View File

@ -210,3 +210,4 @@ node_modules/
test_batch.json
*.backup.*.json
frontend/public/locales/*/translation.backup*.json
/frontend

View File

@ -1,19 +1,39 @@
// @ts-check
import { fileURLToPath } from 'node:url';
import eslint from '@eslint/js';
import globals from 'globals';
import { defineConfig } from 'eslint/config';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import reactPlugin from 'eslint-plugin-react';
import tseslint from 'typescript-eslint';
const tsconfigRootDir = fileURLToPath(new URL('./', import.meta.url));
const srcGlobs = ['{src,frontend/src}/**/*.{ts,tsx,js,jsx}'];
const srcTsGlobs = ['{src,frontend/src}/**/*.{ts,tsx}'];
const nodeGlobs = [
'scripts/**/*.{js,ts}',
'vite.config.ts',
'vitest.config.ts',
'vitest.minimal.config.ts',
'playwright.config.ts',
'tailwind.config.js',
'postcss.config.js',
'eslint.config.mjs',
];
export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommended,
{
ignores: [
"dist", // Contains 3rd party code
"public", // Contains 3rd party code
],
},
eslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
extends: [...tseslint.configs.recommended],
rules: {
"no-undef": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-empty-object-type": [
@ -26,17 +46,112 @@ export default defineConfig(
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-require-imports": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-unused-vars": [
"error",
"warn",
{
"args": "all", // All function args must be used (or explicitly ignored)
"argsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"caughtErrors": "all", // Caught errors must be used (or explicitly ignored)
"caughtErrorsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"destructuredArrayIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"varsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"ignoreRestSiblings": true, // Allow unused variables when removing attributes from objects (otherwise this requires explicit renaming like `({ x: _x, ...y }) => y`, which is clunky)
args: 'all', // All function args must be used (or explicitly ignored)
argsIgnorePattern: '^_', // Allow unused variables beginning with an underscore
caughtErrors: 'all', // Caught errors must be used (or explicitly ignored)
caughtErrorsIgnorePattern: '^_', // Allow unused variables beginning with an underscore
destructuredArrayIgnorePattern: '^_', // Allow unused variables beginning with an underscore
varsIgnorePattern: '^_', // Allow unused variables beginning with an underscore
ignoreRestSiblings: true, // Allow unused variables when removing attributes from objects (otherwise this requires explicit renaming like `({ x: _x, ...y }) => y`, which is clunky)
},
],
},
},
{
files: srcTsGlobs,
extends: [
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir,
},
},
},
{
files: srcGlobs,
extends: [reactPlugin.configs.flat.recommended],
languageOptions: {
globals: {
...globals.browser,
},
},
plugins: {
react: reactPlugin,
'react-hooks': reactHooksPlugin,
},
settings: {
react: {
version: 'detect',
},
},
rules: {
'react/react-in-jsx-scope': 'off', // Not needed with React 17+
'react-hooks/exhaustive-deps': 'off', // Temporarily disabled until codebase conformant
'react-hooks/rules-of-hooks': 'warn',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-explicit-any': 'off', // Temporarily disabled until codebase conformant
"@typescript-eslint/no-inferrable-types": "off", // Temporarily disabled until codebase conformant
'@typescript-eslint/prefer-nullish-coalescing': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-unsafe-assignment': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-unsafe-return': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-unsafe-call': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-unsafe-arguments': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-unsafe-argument': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/require-await': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/only-throw-error': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-floating-promises': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/prefer-promise-reject-errors': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/prefer-optional-chain': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-unnecessary-type-assertions': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/unbound-method': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-base-to-string': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-misused-promises': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-unnecessary-type-assertion': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/restrict-template-expressions': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/dot-notation': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/prefer-regexp-exec': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/prefer-includes': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/consistent-indexed-object-style': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/non-nullable-type-assertion-style': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/consistent-generic-constructors': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/class-literal-property-style': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/consistent-type-definitions': 'off', // Temporarily disabled until codebase conformant
'@typescript-eslint/no-redundant-type-constituents': 'off', // Temporarily disabled until codebase conformant
'react/no-children-prop': 'warn', // Children should be passed as actual children, not via the children prop
'react/prop-types': 'off', // We use TypeScript's types for props instead
'react/display-name': 'off', // Temporarily disabled until codebase conformant
'react/no-unescaped-entities': 'off', // Temporarily disabled until codebase conformant
},
},
{
files: [
'src/**/*.{test,spec}.{ts,tsx}',
'src/tests/**/*.{ts,tsx}',
],
extends: [tseslint.configs.disableTypeChecked],
languageOptions: {
globals: {
...globals.browser,
...globals.vitest,
},
},
},
{
files: nodeGlobs,
extends: [tseslint.configs.disableTypeChecked],
languageOptions: {
globals: {
...globals.node,
},
},
}
);

File diff suppressed because it is too large Load Diff

View File

@ -6,32 +6,36 @@
"proxy": "http://localhost:8080",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
"@embedpdf/core": "^1.2.1",
"@embedpdf/engines": "^1.2.1",
"@embedpdf/plugin-interaction-manager": "^1.2.1",
"@embedpdf/plugin-loader": "^1.2.1",
"@embedpdf/plugin-pan": "^1.2.1",
"@embedpdf/plugin-render": "^1.2.1",
"@embedpdf/plugin-rotate": "^1.2.1",
"@embedpdf/plugin-scroll": "^1.2.1",
"@embedpdf/plugin-search": "^1.2.1",
"@embedpdf/plugin-selection": "^1.2.1",
"@embedpdf/plugin-spread": "^1.2.1",
"@embedpdf/plugin-thumbnail": "^1.2.1",
"@embedpdf/plugin-tiling": "^1.2.1",
"@embedpdf/plugin-viewport": "^1.2.1",
"@embedpdf/plugin-zoom": "^1.2.1",
"@embedpdf/core": "^1.3.1",
"@embedpdf/engines": "^1.3.1",
"@embedpdf/plugin-interaction-manager": "^1.3.1",
"@embedpdf/plugin-loader": "^1.3.1",
"@embedpdf/plugin-pan": "^1.3.1",
"@embedpdf/plugin-render": "^1.3.1",
"@embedpdf/plugin-rotate": "^1.3.1",
"@embedpdf/plugin-scroll": "^1.3.1",
"@embedpdf/plugin-search": "^1.3.1",
"@embedpdf/plugin-selection": "^1.3.1",
"@embedpdf/plugin-spread": "^1.3.1",
"@embedpdf/plugin-thumbnail": "^1.3.1",
"@embedpdf/plugin-tiling": "^1.3.1",
"@embedpdf/plugin-viewport": "^1.3.1",
"@embedpdf/plugin-zoom": "^1.3.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@iconify/react": "^6.0.2",
"@mantine/core": "^8.3.1",
"@mantine/dates": "^8.3.1",
"@mantine/dropzone": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"@mantine/core": "^8.3.2",
"@mantine/dates": "^8.3.2",
"@mantine/dropzone": "^8.3.2",
"@mantine/hooks": "^8.3.2",
"@mui/icons-material": "^7.3.2",
"@mui/material": "^7.3.2",
"@tailwindcss/postcss": "^4.1.13",
"@tanstack/react-virtual": "^3.13.12",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"autoprefixer": "^10.4.21",
"axios": "^1.12.2",
"i18next": "^25.5.2",
@ -41,11 +45,11 @@
"license-report": "^6.8.0",
"pdf-lib": "^1.17.1",
"pdfjs-dist": "^5.4.149",
"posthog-js": "^1.268.0",
"posthog-js": "^1.268.5",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-i18next": "^15.7.3",
"react-router-dom": "^7.9.1",
"react-i18next": "^16.0.0",
"react-router-dom": "^7.9.2",
"tailwindcss": "^4.1.13",
"web-vitals": "^5.1.0"
},
@ -54,6 +58,7 @@
"dev": "npm run typecheck && vite",
"prebuild": "npm run generate-icons",
"lint": "eslint",
"lint:fix": "eslint --fix",
"build": "npm run typecheck && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
@ -94,9 +99,9 @@
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@iconify-json/material-symbols": "^1.2.37",
"@iconify-json/material-symbols": "^1.2.39",
"@iconify/utils": "^3.0.2",
"@playwright/test": "^1.55.0",
"@playwright/test": "^1.55.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
@ -109,7 +114,9 @@
"@vitejs/plugin-react-swc": "^4.1.0",
"@vitest/coverage-v8": "^3.2.4",
"eslint": "^9.36.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.4.0",
"jsdom": "^27.0.0",
"license-checker": "^25.0.1",
"madge": "^8.0.0",

View File

@ -14,7 +14,7 @@ interface DragDropGridProps<T extends DragDropItem> {
selectionMode: boolean;
isAnimating: boolean;
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
renderItem: (item: T, index: number, refs: React.MutableRefObject<Map<string, HTMLDivElement>>) => React.ReactNode;
renderItem: (item: T, index: number, refs: React.RefObject<Map<string, HTMLDivElement>>) => React.ReactNode;
renderSplitMarker?: (item: T, index: number) => React.ReactNode;
}

View File

@ -23,7 +23,7 @@ interface PageThumbnailProps {
selectionMode: boolean;
movingPage: number | null;
isAnimating: boolean;
pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
pageRefs: React.RefObject<Map<string, HTMLDivElement>>;
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
onTogglePage: (pageId: string) => void;
onAnimateReorder: () => void;

View File

@ -8,7 +8,7 @@ import { StirlingFileStub } from "../../types/fileContext";
import { FileId } from "../../types/file";
interface FileGridProps {
files: Array<{ file: File; record?: StirlingFileStub }>;
files: { file: File; record?: StirlingFileStub }[];
onRemove?: (index: number) => void;
onDoubleClick?: (item: { file: File; record?: StirlingFileStub }) => void;
onView?: (item: { file: File; record?: StirlingFileStub }) => void;

View File

@ -8,7 +8,7 @@ export { useToast, ToastProvider, ToastRenderer };
let _api: ReturnType<typeof createImperativeApi> | null = null;
function createImperativeApi() {
const subscribers: Array<(fn: any) => void> = [];
const subscribers: ((fn: any) => void)[] = [];
let api: any = null;
return {
provide(instance: any) {

View File

@ -9,7 +9,7 @@ import NoToolsFound from './shared/NoToolsFound';
import "./toolPicker/ToolPicker.css";
interface SearchResultsProps {
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
filteredTools: { item: [string, ToolRegistryEntry]; matchedText?: string }[];
onSelect: (id: string) => void;
searchQuery?: string;
}
@ -40,13 +40,13 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect,
{group.tools.map(({ id, tool }) => {
const matchedText = matchedTextMap.get(id);
// Check if the match was from synonyms and show the actual synonym that matched
const isSynonymMatch = matchedText && tool.synonyms?.some(synonym =>
const isSynonymMatch = matchedText && tool.synonyms?.some(synonym =>
matchedText.toLowerCase().includes(synonym.toLowerCase())
);
const matchedSynonym = isSynonymMatch ? tool.synonyms?.find(synonym =>
const matchedSynonym = isSynonymMatch ? tool.synonyms?.find(synonym =>
matchedText.toLowerCase().includes(synonym.toLowerCase())
) : undefined;
return (
<ToolButton
key={id}

View File

@ -10,7 +10,7 @@ import { renderToolButtons } from "./shared/renderToolButtons";
interface ToolPickerProps {
selectedToolKey: string | null;
onSelect: (id: string) => void;
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
filteredTools: { item: [string, ToolRegistryEntry]; matchedText?: string }[];
isSearching?: boolean;
}

View File

@ -34,7 +34,7 @@ describe('ChangePermissionsSettings', () => {
);
// Should render checkboxes for all permission types
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
const permissionKeys = Object.keys(defaultParameters) as (keyof ChangePermissionsParameters)[];
const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes).toHaveLength(permissionKeys.length);
@ -55,7 +55,7 @@ describe('ChangePermissionsSettings', () => {
</TestWrapper>
);
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
const permissionKeys = Object.keys(defaultParameters) as (keyof ChangePermissionsParameters)[];
permissionKeys.forEach(permission => {
expect(screen.getByText(`mock-changePermissions.permissions.${permission}.label`)).toBeInTheDocument();
@ -183,13 +183,13 @@ describe('ChangePermissionsSettings', () => {
</TestWrapper>
);
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
const permissionKeys = Object.keys(defaultParameters) as (keyof ChangePermissionsParameters)[];
permissionKeys.forEach(permission => {
expect(mockT).toHaveBeenCalledWith(`changePermissions.permissions.${permission}.label`, permission);
});
});
test.each(Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>)('should handle %s permission type individually', (permission) => {
test.each(Object.keys(defaultParameters) as (keyof ChangePermissionsParameters)[])('should handle %s permission type individually', (permission) => {
const testParameters: ChangePermissionsParameters = {
...defaultParameters,
[permission]: true

View File

@ -14,7 +14,7 @@ const ChangePermissionsSettings = ({ parameters, onParameterChange, disabled = f
return (
<Stack gap="sm">
<Stack gap="xs">
{(Object.keys(parameters) as Array<keyof ChangePermissionsParameters>).map((key) => (
{(Object.keys(parameters) as (keyof ChangePermissionsParameters)[]).map((key) => (
<Checkbox
key={key}
label={t(`changePermissions.permissions.${key}.label`, key)}

View File

@ -27,7 +27,7 @@ import { StirlingFile } from "../../../types/fileContext";
interface ConvertSettingsProps {
parameters: ConvertParameters;
onParameterChange: <K extends keyof ConvertParameters>(key: K, value: ConvertParameters[K]) => void;
getAvailableToExtensions: (fromExtension: string) => Array<{value: string, label: string, group: string}>;
getAvailableToExtensions: (fromExtension: string) => {value: string, label: string, group: string}[];
selectedFiles: StirlingFile[];
disabled?: boolean;
}

View File

@ -11,7 +11,7 @@ interface SanitizeSettingsProps {
const SanitizeSettings = ({ parameters, onParameterChange, disabled = false }: SanitizeSettingsProps) => {
const { t } = useTranslation();
const options = (Object.keys(defaultParameters) as Array<keyof SanitizeParameters>).map((key) => ({
const options = (Object.keys(defaultParameters) as (keyof SanitizeParameters)[]).map((key) => ({
key: key,
label: t(`sanitize.options.${key}`, key),
description: t(`sanitize.options.${key}.desc`, `${key} from the PDF`),

View File

@ -14,7 +14,7 @@ export const renderToolButtons = (
onSelect: (id: string) => void,
showSubcategoryHeader: boolean = true,
disableNavigation: boolean = false,
searchResults?: Array<{ item: [string, any]; matchedText?: string }>
searchResults?: { item: [string, any]; matchedText?: string }[]
) => {
// Create a map of matched text for quick lookup
const matchedTextMap = new Map<string, string>();
@ -32,7 +32,7 @@ export const renderToolButtons = (
<div>
{subcategory.tools.map(({ id, tool }) => {
const matchedSynonym = matchedTextMap.get(id);
return (
<ToolButton
key={id}

View File

@ -1,62 +0,0 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
/**
* Reusable tooltip for page selection functionality.
* Can be used by any tool that uses the GeneralUtils.parsePageList syntax.
*/
export const usePageSelectionTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("pageSelection.tooltip.header.title", "Page Selection Guide")
},
tips: [
{
description: t("pageSelection.tooltip.description", "Choose which pages to use for the operation. Supports single pages, ranges, formulas, and the all keyword.")
},
{
title: t("pageSelection.tooltip.individual.title", "Individual Pages"),
description: t("pageSelection.tooltip.individual.description", "Enter numbers separated by commas."),
bullets: [
t("pageSelection.tooltip.individual.bullet1", "<strong>1,3,5</strong> → selects pages 1, 3, 5"),
t("pageSelection.tooltip.individual.bullet2", "<strong>2,7,12</strong> → selects pages 2, 7, 12")
]
},
{
title: t("pageSelection.tooltip.ranges.title", "Page Ranges"),
description: t("pageSelection.tooltip.ranges.description", "Use - for consecutive pages."),
bullets: [
t("pageSelection.tooltip.ranges.bullet1", "<strong>3-6</strong> → selects pages 36"),
t("pageSelection.tooltip.ranges.bullet2", "<strong>10-15</strong> → selects pages 1015"),
t("pageSelection.tooltip.ranges.bullet3", "<strong>5-</strong> → selects pages 5 to end")
]
},
{
title: t("pageSelection.tooltip.mathematical.title", "Mathematical Functions"),
description: t("pageSelection.tooltip.mathematical.description", "Use n in formulas for patterns."),
bullets: [
t("pageSelection.tooltip.mathematical.bullet2", "<strong>2n-1</strong> → all odd pages (1, 3, 5…)"),
t("pageSelection.tooltip.mathematical.bullet1", "<strong>2n</strong> → all even pages (2, 4, 6…)"),
t("pageSelection.tooltip.mathematical.bullet3", "<strong>3n</strong> → every 3rd page (3, 6, 9…)"),
t("pageSelection.tooltip.mathematical.bullet4", "<strong>4n-1</strong> → pages 3, 7, 11, 15…")
]
},
{
title: t("pageSelection.tooltip.special.title", "Special Keywords"),
bullets: [
t("pageSelection.tooltip.special.bullet1", "<strong>all</strong> → selects all pages"),
]
},
{
title: t("pageSelection.tooltip.complex.title", "Complex Combinations"),
description: t("pageSelection.tooltip.complex.description", "Mix different types."),
bullets: [
t("pageSelection.tooltip.complex.bullet1", "<strong>1,3-5,8,2n</strong> → pages 1, 35, 8, plus evens"),
t("pageSelection.tooltip.complex.bullet2", "<strong>10-,2n-1</strong> → from page 10 to end + odd pages")
]
}
]
};
};

View File

@ -14,19 +14,19 @@ interface SearchLayerProps {
}
interface SearchResultState {
results: Array<{
results: {
pageIndex: number;
rects: Array<{
rects: {
origin: { x: number; y: number };
size: { width: number; height: number };
}>;
}>;
}[];
}[];
activeResultIndex?: number;
}
export function CustomSearchLayer({
pageIndex,
scale,
export function CustomSearchLayer({
pageIndex,
scale,
highlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.BACKGROUND,
activeHighlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.ACTIVE_BACKGROUND,
opacity = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.OPACITY,
@ -42,17 +42,17 @@ export function CustomSearchLayer({
if (!searchProvides) {
return;
}
const unsubscribe = searchProvides.onSearchResultStateChange?.((state: SearchResultState) => {
// Auto-scroll to active search result
if (state?.results && state.activeResultIndex !== undefined && state.activeResultIndex >= 0) {
const activeResult = state.results[state.activeResultIndex];
if (activeResult) {
if (activeResult) {
const pageNumber = activeResult.pageIndex + 1; // Convert to 1-based page number
scrollActions.scrollToPage(pageNumber);
}
}
setSearchResultState(state);
});
@ -69,7 +69,7 @@ export function CustomSearchLayer({
const filtered = searchResultState.results
.map((result, originalIndex) => ({ result, originalIndex }))
.filter(({ result }) => result.pageIndex === pageIndex);
return filtered;
}, [searchResultState, pageIndex]);
@ -78,7 +78,7 @@ export function CustomSearchLayer({
}
return (
<div style={{
<div style={{
position: 'absolute',
top: 0,
left: 0,
@ -117,4 +117,4 @@ export function CustomSearchLayer({
))}
</div>
);
}
}

View File

@ -4,10 +4,10 @@ import { useViewer } from '../../contexts/ViewerContext';
interface SearchResult {
pageIndex: number;
rects: Array<{
rects: {
origin: { x: number; y: number };
size: { width: number; height: number };
}>;
}[];
}
/**
@ -17,7 +17,7 @@ interface SearchResult {
export function SearchAPIBridge() {
const { provides: search } = useSearch();
const { registerBridge } = useViewer();
const [localState, setLocalState] = useState({
results: null as SearchResult[] | null,
activeIndex: 0
@ -32,7 +32,7 @@ export function SearchAPIBridge() {
results: state?.results || null,
activeIndex: (state?.activeResultIndex || 0) + 1 // Convert to 1-based index
};
setLocalState(prevState => {
// Only update if state actually changed
if (prevState.results !== newState.results || prevState.activeIndex !== newState.activeIndex) {

View File

@ -101,7 +101,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
handleReaderToggle: () => void;
// Computed values
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>; // Filtered by search
filteredTools: { item: [string, ToolRegistryEntry]; matchedText?: string }[]; // Filtered by search
isPanelVisible: boolean;
}

View File

@ -82,10 +82,10 @@ interface RotationState {
interface SearchResult {
pageIndex: number;
rects: Array<{
rects: {
origin: { x: number; y: number };
size: { width: number; height: number };
}>;
}[];
}
interface SearchState {
@ -179,7 +179,7 @@ interface ViewerContextType {
clear: () => void;
};
// Bridge registration - internal use by bridges
// Bridge registration - internal use by bridges
registerBridge: (type: string, ref: BridgeRef) => void;
}

View File

@ -25,7 +25,7 @@ const DEBUG = process.env.NODE_ENV === 'development';
*/
class SimpleMutex {
private locked = false;
private queue: Array<() => void> = [];
private queue: (() => void)[] = [];
async lock(): Promise<void> {
if (!this.locked) {
@ -151,7 +151,7 @@ interface AddFileOptions {
files?: File[];
// For 'processed' files
filesWithThumbnails?: Array<{ file: File; thumbnail?: string; pageCount?: number }>;
filesWithThumbnails?: { file: File; thumbnail?: string; pageCount?: number }[];
// Insertion position
insertAfterPageId?: string;
@ -165,8 +165,8 @@ interface AddFileOptions {
*/
export async function addFiles(
options: AddFileOptions,
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>,
stateRef: React.RefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>,
lifecycleManager: FileLifecycleManager,
enablePersistence: boolean = false
@ -278,7 +278,7 @@ export async function consumeFiles(
inputFileIds: FileId[],
outputStirlingFiles: StirlingFile[],
outputStirlingFileStubs: StirlingFileStub[],
filesRef: React.MutableRefObject<Map<FileId, File>>,
filesRef: React.RefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>
): Promise<FileId[]> {
if (DEBUG) console.log(`📄 consumeFiles: Processing ${inputFileIds.length} input files, ${outputStirlingFiles.length} output files with pre-created stubs`);
@ -355,9 +355,9 @@ export async function consumeFiles(
* Helper function to restore files to filesRef and manage IndexedDB cleanup
*/
async function restoreFilesAndCleanup(
filesToRestore: Array<{ file: File; record: StirlingFileStub }>,
filesToRestore: { file: File; record: StirlingFileStub }[],
fileIdsToRemove: FileId[],
filesRef: React.MutableRefObject<Map<FileId, File>>,
filesRef: React.RefObject<Map<FileId, File>>,
indexedDB?: { deleteFile: (fileId: FileId) => Promise<void> } | null
): Promise<void> {
// Remove files from filesRef
@ -406,7 +406,7 @@ export async function undoConsumeFiles(
inputFiles: File[],
inputStirlingFileStubs: StirlingFileStub[],
outputFileIds: FileId[],
filesRef: React.MutableRefObject<Map<FileId, File>>,
filesRef: React.RefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>,
indexedDB?: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any>; deleteFile: (fileId: FileId) => Promise<void> } | null
): Promise<void> {
@ -468,8 +468,8 @@ export async function undoConsumeFiles(
export async function addStirlingFileStubs(
stirlingFileStubs: StirlingFileStub[],
options: { insertAfterPageId?: string; selectFiles?: boolean } = {},
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>,
stateRef: React.RefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>,
_lifecycleManager: FileLifecycleManager
): Promise<StirlingFile[]> {

View File

@ -15,8 +15,8 @@ import {
* Create stable selectors using stateRef and filesRef
*/
export function createFileSelectors(
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>
stateRef: React.RefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>>
): FileContextSelectors {
return {
getFile: (id: FileId) => {
@ -111,7 +111,7 @@ export function buildQuickKeySet(stirlingFileStubs: Record<FileId, StirlingFileS
/**
* Helper for building quickKey sets from IndexedDB metadata
*/
export function buildQuickKeySetFromMetadata(metadata: Array<{ name: string; size: number; lastModified: number }>): Set<string> {
export function buildQuickKeySetFromMetadata(metadata: { name: string; size: number; lastModified: number }[]): Set<string> {
const quickKeys = new Set<string>();
metadata.forEach(meta => {
// Format: name|size|lastModified (same as createQuickKey)
@ -125,8 +125,8 @@ export function buildQuickKeySetFromMetadata(metadata: Array<{ name: string; siz
* Get primary file (first in list) - commonly used pattern
*/
export function getPrimaryFile(
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>
stateRef: React.RefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>>
): { file?: File; record?: StirlingFileStub } {
const primaryFileId = stateRef.current.files.ids[0];
if (!primaryFileId) return {};

View File

@ -16,7 +16,7 @@ export class FileLifecycleManager {
private fileGenerations = new Map<string, number>(); // Generation tokens to prevent stale cleanup
constructor(
private filesRef: React.MutableRefObject<Map<FileId, File>>,
private filesRef: React.RefObject<Map<FileId, File>>,
private dispatch: React.Dispatch<FileContextAction>
) {}
@ -34,7 +34,7 @@ export class FileLifecycleManager {
/**
* Clean up resources for a specific file (with stateRef access for complete cleanup)
*/
cleanupFile = (fileId: FileId, stateRef?: React.MutableRefObject<any>): void => {
cleanupFile = (fileId: FileId, stateRef?: React.RefObject<any>): void => {
// Use comprehensive cleanup (same as removeFiles)
this.cleanupAllResourcesForFile(fileId, stateRef);
@ -68,7 +68,7 @@ export class FileLifecycleManager {
/**
* Schedule delayed cleanup for a file with generation token to prevent stale cleanup
*/
scheduleCleanup = (fileId: FileId, delay: number = 30000, stateRef?: React.MutableRefObject<any>): void => {
scheduleCleanup = (fileId: FileId, delay: number = 30000, stateRef?: React.RefObject<any>): void => {
// Cancel existing timer
const existingTimer = this.cleanupTimers.get(fileId);
if (existingTimer) {
@ -101,7 +101,7 @@ export class FileLifecycleManager {
/**
* Remove a file immediately with complete resource cleanup
*/
removeFiles = (fileIds: FileId[], stateRef?: React.MutableRefObject<any>): void => {
removeFiles = (fileIds: FileId[], stateRef?: React.RefObject<any>): void => {
fileIds.forEach(fileId => {
// Clean up all resources for this file
this.cleanupAllResourcesForFile(fileId, stateRef);
@ -114,7 +114,7 @@ export class FileLifecycleManager {
/**
* Complete resource cleanup for a single file
*/
private cleanupAllResourcesForFile = (fileId: FileId, stateRef?: React.MutableRefObject<any>): void => {
private cleanupAllResourcesForFile = (fileId: FileId, stateRef?: React.RefObject<any>): void => {
// Remove from files ref
this.filesRef.current.delete(fileId);
@ -166,7 +166,7 @@ export class FileLifecycleManager {
/**
* Update file record with race condition guards
*/
updateStirlingFileStub = (fileId: FileId, updates: Partial<StirlingFileStub>, stateRef?: React.MutableRefObject<any>): void => {
updateStirlingFileStub = (fileId: FileId, updates: Partial<StirlingFileStub>, stateRef?: React.RefObject<any>): void => {
// Guard against updating removed files (race condition protection)
if (!this.filesRef.current.has(fileId)) {
if (DEBUG) console.warn(`🗂️ Attempted to update removed file (filesRef): ${fileId}`);

View File

@ -125,7 +125,7 @@ describe('useAddPasswordParameters', () => {
expect(result.current.validateParameters()).toBe(true);
});
test.each(Object.keys(defaultChangePermissionsParameters) as Array<keyof ChangePermissionsParameters>)('should handle boolean restriction parameter %s', (param) => {
test.each(Object.keys(defaultChangePermissionsParameters) as (keyof ChangePermissionsParameters)[])('should handle boolean restriction parameter %s', (param) => {
const { result } = renderHook(() => useAddPasswordParameters());
act(() => {

View File

@ -96,7 +96,7 @@ describe('useChangePermissionsOperation', () => {
// Verify the form data contains the file
expect(formData.get('fileInput')).toBe(testFile);
(Object.keys(testParameters) as Array<keyof ChangePermissionsParameters>).forEach(key => {
(Object.keys(testParameters) as (keyof ChangePermissionsParameters)[]).forEach(key => {
expect(formData.get(key), `Parameter ${key} should be set correctly`).toBe(testParameters[key].toString());
});
});

View File

@ -30,7 +30,7 @@ describe('useChangePermissionsParameters', () => {
test('should update all permission parameters', () => {
const { result } = renderHook(() => useChangePermissionsParameters());
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
const permissionKeys = Object.keys(defaultParameters) as (keyof ChangePermissionsParameters)[];
// Set all to true
act(() => {
@ -99,7 +99,7 @@ describe('useChangePermissionsParameters', () => {
// Set all restrictions - should still be valid
act(() => {
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
const permissionKeys = Object.keys(defaultParameters) as (keyof ChangePermissionsParameters)[];
permissionKeys.forEach(key => {
result.current.updateParameter(key, true);
});

View File

@ -42,8 +42,8 @@ export interface ConvertParameters extends BaseParameters {
export interface ConvertParametersHook extends BaseParametersHook<ConvertParameters> {
getEndpoint: () => string;
getAvailableToExtensions: (fromExtension: string) => Array<{value: string, label: string, group: string}>;
analyzeFileTypes: (files: Array<{name: string}>) => void;
getAvailableToExtensions: (fromExtension: string) => {value: string, label: string, group: string}[];
analyzeFileTypes: (files: {name: string}[]) => void;
}
export const defaultParameters: ConvertParameters = {
@ -157,7 +157,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
const getAvailableToExtensions = getAvailableToExtensionsUtil;
const analyzeFileTypes = useCallback((files: Array<{name: string}>) => {
const analyzeFileTypes = useCallback((files: {name: string}[]) => {
if (files.length === 0) {
// No files - only reset smart detection, keep user's format choices
baseHook.setParameters(prev => {

View File

@ -345,7 +345,7 @@ describe('useConvertParameters - Auto Detection & Smart Conversion', () => {
test('should handle malformed file objects', () => {
const { result } = renderHook(() => useConvertParameters());
const malformedFiles: Array<{name: string}> = [
const malformedFiles: {name: string}[] = [
{ name: 'valid.pdf' },
// @ts-expect-error - Testing runtime resilience
{ name: null },

View File

@ -4,7 +4,7 @@ import { SUBCATEGORY_ORDER, SubcategoryId, ToolCategoryId, ToolRegistryEntry } f
import { useTranslation } from 'react-i18next';
type SubcategoryIdMap = {
[subcategoryId in SubcategoryId]: Array<{ id: string /* FIX ME: Should be ToolId */; tool: ToolRegistryEntry }>;
[subcategoryId in SubcategoryId]: { id: string /* FIX ME: Should be ToolId */; tool: ToolRegistryEntry }[];
}
type GroupedTools = {
@ -28,7 +28,7 @@ export interface ToolSection {
};
export function useToolSections(
filteredTools: Array<{ item: [string /* FIX ME: Should be ToolId */, ToolRegistryEntry]; matchedText?: string }>,
filteredTools: { item: [string /* FIX ME: Should be ToolId */, ToolRegistryEntry]; matchedText?: string }[],
searchQuery?: string
) {
const { t } = useTranslation();
@ -37,7 +37,7 @@ export function useToolSections(
if (!filteredTools || !Array.isArray(filteredTools)) {
return {} as GroupedTools;
}
const grouped = {} as GroupedTools;
filteredTools.forEach(({ item: [id, tool] }) => {
const categoryId = tool.categoryId;
@ -102,7 +102,7 @@ export function useToolSections(
if (!filteredTools || !Array.isArray(filteredTools)) {
return [];
}
const subMap = {} as SubcategoryIdMap;
const seen = new Set<string /* FIX ME: Should be ToolId */>();
filteredTools.forEach(({ item: [id, tool] }) => {

View File

@ -6,10 +6,10 @@ export interface AutomationConfig {
id: string;
name: string;
description?: string;
operations: Array<{
operations: {
operation: string;
parameters: any;
}>;
}[];
createdAt: string;
updatedAt: string;
}
@ -35,7 +35,7 @@ class AutomationStorage {
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(this.storeName)) {
const store = db.createObjectStore(this.storeName, { keyPath: 'id' });
store.createIndex('name', 'name', { unique: false });
@ -49,18 +49,18 @@ class AutomationStorage {
if (!this.db) {
await this.init();
}
if (!this.db) {
throw new Error('Database not initialized');
}
return this.db;
}
async saveAutomation(automation: Omit<AutomationConfig, 'id' | 'createdAt' | 'updatedAt'>): Promise<AutomationConfig> {
const db = await this.ensureDB();
const timestamp = new Date().toISOString();
const automationWithMeta: AutomationConfig = {
id: `automation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
...automation,
@ -85,7 +85,7 @@ class AutomationStorage {
async updateAutomation(automation: AutomationConfig): Promise<AutomationConfig> {
const db = await this.ensureDB();
const updatedAutomation: AutomationConfig = {
...automation,
updatedAt: new Date().toISOString()
@ -165,13 +165,13 @@ class AutomationStorage {
async searchAutomations(query: string): Promise<AutomationConfig[]> {
const automations = await this.getAllAutomations();
if (!query.trim()) {
return automations;
}
const lowerQuery = query.toLowerCase();
return automations.filter(automation =>
return automations.filter(automation =>
automation.name.toLowerCase().includes(lowerQuery) ||
(automation.description && automation.description.toLowerCase().includes(lowerQuery)) ||
automation.operations.some(op => op.operation.toLowerCase().includes(lowerQuery))
@ -180,4 +180,4 @@ class AutomationStorage {
}
// Export singleton instance
export const automationStorage = new AutomationStorage();
export const automationStorage = new AutomationStorage();

View File

@ -10,12 +10,12 @@ import { FileId } from '../types/file';
export interface ProcessedFileMetadata {
totalPages: number;
pages: Array<{
pages: {
pageNumber: number;
thumbnail?: string;
rotation: number;
splitBefore: boolean;
}>;
}[];
thumbnailUrl?: string; // Page 1 thumbnail for FileEditor
lastProcessed: number;
}

View File

@ -8,8 +8,8 @@ export class ProcessingErrorHandler {
* Create a ProcessingError from an unknown error
*/
static createProcessingError(
error: unknown,
retryCount: number = 0,
error: unknown,
retryCount: number = 0,
maxRetries: number = this.DEFAULT_MAX_RETRIES
): ProcessingError {
const originalError = error instanceof Error ? error : new Error(String(error));
@ -17,7 +17,7 @@ export class ProcessingErrorHandler {
// Determine error type based on error message and properties
const errorType = this.determineErrorType(originalError, message);
// Determine if error is recoverable
const recoverable = this.isRecoverable(errorType, retryCount, maxRetries);
@ -38,7 +38,7 @@ export class ProcessingErrorHandler {
const lowerMessage = message.toLowerCase();
// Network-related errors
if (lowerMessage.includes('network') ||
if (lowerMessage.includes('network') ||
lowerMessage.includes('fetch') ||
lowerMessage.includes('connection')) {
return 'network';
@ -83,8 +83,8 @@ export class ProcessingErrorHandler {
* Determine if an error is recoverable based on type and retry count
*/
private static isRecoverable(
errorType: ProcessingError['type'],
retryCount: number,
errorType: ProcessingError['type'],
retryCount: number,
maxRetries: number
): boolean {
// Never recoverable
@ -113,22 +113,22 @@ export class ProcessingErrorHandler {
switch (errorType) {
case 'network':
return 'Network connection failed. Please check your internet connection and try again.';
case 'memory':
return 'Insufficient memory to process this file. Try closing other applications or processing a smaller file.';
case 'timeout':
return 'Processing timed out. This file may be too large or complex to process.';
case 'cancelled':
return 'Processing was cancelled by user.';
case 'corruption':
return 'This PDF file appears to be corrupted or encrypted. Please try a different file.';
case 'parsing':
return `Failed to process PDF: ${originalMessage}`;
default:
return `Processing failed: ${originalMessage}`;
}
@ -149,7 +149,7 @@ export class ProcessingErrorHandler {
return await operation();
} catch (error) {
lastError = this.createProcessingError(error, attempt, maxRetries);
// Notify error handler
if (onError) {
onError(lastError);
@ -168,7 +168,7 @@ export class ProcessingErrorHandler {
// Wait before retry with progressive backoff
const delay = this.RETRY_DELAYS[Math.min(attempt, this.RETRY_DELAYS.length - 1)];
await this.delay(delay);
console.log(`Retrying operation (attempt ${attempt + 2}/${maxRetries + 1}) after ${delay}ms delay`);
}
}
@ -207,7 +207,7 @@ export class ProcessingErrorHandler {
*/
static createTimeoutController(timeoutMs: number): AbortController {
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, timeoutMs);
@ -233,7 +233,7 @@ export class ProcessingErrorHandler {
'Try refreshing the page',
'Try again in a few moments'
];
case 'memory':
return [
'Close other browser tabs or applications',
@ -241,14 +241,14 @@ export class ProcessingErrorHandler {
'Restart your browser',
'Use a device with more memory'
];
case 'timeout':
return [
'Try processing a smaller file',
'Break large files into smaller sections',
'Check your internet connection speed'
];
case 'corruption':
return [
'Verify the PDF file opens in other applications',
@ -256,14 +256,14 @@ export class ProcessingErrorHandler {
'Try a different PDF file',
'Contact the file creator if it appears corrupted'
];
case 'parsing':
return [
'Verify this is a valid PDF file',
'Try a different PDF file',
'Contact support if the problem persists'
];
default:
return [
'Try refreshing the page',
@ -279,4 +279,4 @@ export class ProcessingErrorHandler {
private static delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
}

View File

@ -13,7 +13,7 @@ import {
type ConversionEndpoint
} from '../helpers/conversionEndpointDiscovery';
import * as path from 'path';
import * as fs from 'fs';
import fs from 'fs';
// Test configuration
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
@ -238,7 +238,7 @@ async function testConversion(page: Page, conversion: ConversionEndpoint) {
// Save and verify file is not empty
const path = await download.path();
if (path) {
const fs = require('fs');
// fs is already imported at the top of the file
const stats = fs.statSync(path);
expect(stats.size).toBeGreaterThan(0);

View File

@ -20,9 +20,9 @@ export function createTestStirlingFile(
* Create multiple StirlingFile objects for testing
*/
export function createTestFilesWithId(
files: Array<{ name: string; content?: string; type?: string }>
files: { name: string; content?: string; type?: string }[]
): StirlingFile[] {
return files.map(({ name, content = 'test content', type = 'application/pdf' }) =>
createTestStirlingFile(name, content, type)
);
}
}

View File

@ -1,4 +1,4 @@
import {
import {
CONVERSION_ENDPOINTS,
ENDPOINT_NAMES,
EXTENSION_TO_ENDPOINT,
@ -11,15 +11,15 @@ import {
*/
export const getEndpointName = (fromExtension: string, toExtension: string): string => {
if (!fromExtension || !toExtension) return '';
let endpointKey = EXTENSION_TO_ENDPOINT[fromExtension]?.[toExtension];
// If no explicit mapping exists and we're converting to PDF,
// If no explicit mapping exists and we're converting to PDF,
// fall back to 'any' which uses file-to-pdf endpoint
if (!endpointKey && toExtension === 'pdf' && fromExtension !== 'any') {
endpointKey = EXTENSION_TO_ENDPOINT['any']?.[toExtension];
}
return endpointKey || '';
};
@ -29,7 +29,7 @@ export const getEndpointName = (fromExtension: string, toExtension: string): str
export const getEndpointUrl = (fromExtension: string, toExtension: string): string => {
const endpointName = getEndpointName(fromExtension, toExtension);
if (!endpointName) return '';
// Find the endpoint URL from CONVERSION_ENDPOINTS using the endpoint name
for (const [key, endpoint] of Object.entries(CONVERSION_ENDPOINTS)) {
if (ENDPOINT_NAMES[key as keyof typeof ENDPOINT_NAMES] === endpointName) {
@ -64,7 +64,7 @@ export const isWebFormat = (extension: string): boolean => {
* Gets available target extensions for a given source extension
* Extracted from useConvertParameters to be reusable in automation settings
*/
export const getAvailableToExtensions = (fromExtension: string): Array<{value: string, label: string, group: string}> => {
export const getAvailableToExtensions = (fromExtension: string): {value: string, label: string, group: string}[] => {
if (!fromExtension) return [];
// Handle dynamic format identifiers (file-<extension>)
@ -87,4 +87,4 @@ export const getAvailableToExtensions = (fromExtension: string): Array<{value: s
return TO_FORMAT_OPTIONS.filter(option =>
supportedExtensions.includes(option.value)
);
};
};

View File

@ -70,9 +70,9 @@ export function scoreMatch(queryRaw: string, targetRaw: string): number {
export function minScoreForQuery(query: string): number {
const len = normalizeText(query).length;
if (len <= 3) return 40;
if (len <= 6) return 30;
return 25;
if (len <= 3) return 40;
if (len <= 6) return 30;
return 25;
}
// Decide if a target matches a query based on a threshold
@ -82,8 +82,8 @@ export function isFuzzyMatch(query: string, target: string, minScore?: number):
}
// Convenience: rank a list of items by best score across provided getters
export function rankByFuzzy<T>(items: T[], query: string, getters: Array<(item: T) => string>, minScore?: number): Array<{ item: T; score: number; matchedText?: string }>{
const results: Array<{ item: T; score: number; matchedText?: string }> = [];
export function rankByFuzzy<T>(items: T[], query: string, getters: ((item: T) => string)[], minScore?: number): { item: T; score: number; matchedText?: string }[]{
const results: { item: T; score: number; matchedText?: string }[] = [];
const threshold = typeof minScore === 'number' ? minScore : minScoreForQuery(query);
for (const item of items) {
let best = 0;

View File

@ -18,10 +18,10 @@ export function filterToolRegistryByQuery(
const nq = normalizeForSearch(query);
const threshold = minScoreForQuery(query);
const exactName: Array<{ id: string; tool: ToolRegistryEntry; pos: number }> = [];
const exactSyn: Array<{ id: string; tool: ToolRegistryEntry; text: string; pos: number }> = [];
const fuzzyName: Array<{ id: string; tool: ToolRegistryEntry; score: number; text: string }> = [];
const fuzzySyn: Array<{ id: string; tool: ToolRegistryEntry; score: number; text: string }> = [];
const exactName: { id: string; tool: ToolRegistryEntry; pos: number }[] = [];
const exactSyn: { id: string; tool: ToolRegistryEntry; text: string; pos: number }[] = [];
const fuzzyName: { id: string; tool: ToolRegistryEntry; score: number; text: string }[] = [];
const fuzzySyn: { id: string; tool: ToolRegistryEntry; score: number; text: string }[] = [];
for (const [id, tool] of entries) {
const nameNorm = normalizeForSearch(tool.name || '');

View File

@ -13,7 +13,7 @@
/* Language and Environment */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react-jsx", /* Specify what JSX code is generated. */
"jsx": "react-jsx", /* Specify what JSX code is generated. */
// "libReplacement": true, /* Enable lib replacement. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
@ -24,13 +24,12 @@
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "esnext", /* Specify what module code is generated. */
"module": "esnext", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
},
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
@ -43,7 +42,7 @@
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
"resolveJsonModule": true, /* Enable importing .json files. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
@ -113,6 +112,7 @@
},
"include": [
"src",
"src/global.d.ts"
, "vite.config.ts" ]
"src/global.d.ts",
"vite.config.ts"
]
}