diff --git a/packages/kg-unsplash-selector/eslint.config.js b/packages/kg-unsplash-selector/eslint.config.js index cde364ef2f..932e36b6fa 100644 --- a/packages/kg-unsplash-selector/eslint.config.js +++ b/packages/kg-unsplash-selector/eslint.config.js @@ -1,45 +1,40 @@ -import {fixupPluginRules} from '@eslint/compat'; +import {defineConfig} from 'eslint/config'; import eslint from '@eslint/js'; import ghostPlugin from 'eslint-plugin-ghost'; +import tseslint from 'typescript-eslint'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import reactRefreshPlugin from 'eslint-plugin-react-refresh'; -import tailwindcssPlugin from 'eslint-plugin-tailwindcss'; -import tseslint from 'typescript-eslint'; - -const ghost = fixupPluginRules(ghostPlugin); +import tailwindPlugin from 'eslint-plugin-tailwindcss'; -export default tseslint.config( - {ignores: ['dist/**']}, +export default defineConfig([ + {ignores: ['dist/**', 'types/**']}, { - files: ['src/**/*.{ts,tsx}'], + files: ['**/*.{ts,tsx}'], extends: [ eslint.configs.recommended, - tseslint.configs.recommended, - reactPlugin.configs.flat.recommended, - reactPlugin.configs.flat['jsx-runtime'] + tseslint.configs.recommended ], + languageOptions: { + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: {jsx: true} + } + }, plugins: { - ghost, + ghost: ghostPlugin, + react: reactPlugin, 'react-hooks': reactHooksPlugin, 'react-refresh': reactRefreshPlugin, - tailwindcss: tailwindcssPlugin + tailwindcss: tailwindPlugin }, settings: { - react: { - version: 'detect' - } + react: {version: 'detect'} }, rules: { ...ghostPlugin.configs.ts.rules, - // disable rules not in the original config - '@typescript-eslint/no-unused-expressions': 'off', - - // react-hooks - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - // sort multiple import lines into alphabetical groups 'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', { memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple'] @@ -47,17 +42,13 @@ export default tseslint.config( 'prefer-const': 'off', 'react-refresh/only-export-components': 'off', - - // suppress errors for missing 'import React' in JSX files, as we don't need it 'react/react-in-jsx-scope': 'off', - // ignore prop-types for now 'react/prop-types': 'off', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-empty-function': 'off', - // custom react rules 'react/jsx-sort-props': ['error', { reservedFirst: true, callbacksLast: true, @@ -76,5 +67,15 @@ export default tseslint.config( 'tailwindcss/no-custom-classname': 'off', 'tailwindcss/no-contradicting-classname': ['error', {config: 'tailwind.config.cjs'}] } + }, + { + files: ['test/**/*.ts'], + rules: { + ...ghostPlugin.configs['ts-test'].rules, + 'ghost/mocha/no-global-tests': 'off', + 'ghost/mocha/handle-done-callback': 'off', + 'ghost/mocha/no-mocha-arrows': 'off', + 'ghost/mocha/max-top-level-suites': 'off' + } } -); +]); diff --git a/packages/kg-unsplash-selector/package.json b/packages/kg-unsplash-selector/package.json index 62a8fea273..c9f870e3fa 100644 --- a/packages/kg-unsplash-selector/package.json +++ b/packages/kg-unsplash-selector/package.json @@ -21,7 +21,8 @@ "build": "tsc && vite build", "preview": "vite preview", "prepare": "yarn build", - "lint": "eslint src --cache", + "pretest": "tsc -p tsconfig.test.json", + "lint": "eslint", "test": "yarn test:unit && yarn test:acceptance", "test:unit": "yarn nx build && vitest run", "test:acceptance": "playwright test", @@ -33,6 +34,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@eslint/js": "9.39.4", "@playwright/test": "^1.58.0", "@types/react": "18.3.28", "@types/react-dom": "18.3.7", @@ -51,6 +53,7 @@ "typescript": "5.9.3", "typescript-eslint": "8.57.0", "vite": "8.0.0", + "vite-plugin-css-injected-by-js": "3.5.2", "vite-plugin-dts": "4.5.4", "vite-plugin-svgr": "4.5.0", "vitest": "4.1.0" diff --git a/packages/kg-unsplash-selector/src/UnsplashSearchModal.tsx b/packages/kg-unsplash-selector/src/UnsplashSearchModal.tsx index 3edd012bc3..7224da0abe 100644 --- a/packages/kg-unsplash-selector/src/UnsplashSearchModal.tsx +++ b/packages/kg-unsplash-selector/src/UnsplashSearchModal.tsx @@ -4,14 +4,14 @@ import UnsplashGallery from './ui/UnsplashGallery'; import UnsplashSelector from './ui/UnsplashSelector'; import {DefaultHeaderTypes} from './UnsplashTypes'; import {InMemoryUnsplashProvider} from './api/InMemoryUnsplashProvider'; -import {Photo} from './UnsplashTypes'; +import {InsertImagePayload, Photo} from './UnsplashTypes'; import {PhotoUseCases} from './api/PhotoUseCase'; import {UnsplashProvider} from './api/UnsplashProvider'; import {UnsplashService} from './api/UnsplashService'; interface UnsplashModalProps { onClose: () => void; - onImageInsert: (image: Photo) => void; + onImageInsert: (image: InsertImagePayload) => void; unsplashProviderConfig: DefaultHeaderTypes | null; } @@ -154,7 +154,7 @@ export const UnsplashSearchModal : React.FC = ({onClose, onI } }, [galleryRef, loadMorePhotos, zoomedImg]); - const selectImg = (payload:Photo) => { + const selectImg = (payload:Photo | null) => { if (payload) { setZoomedImg(payload); setLastScrollPos(scrollPos); @@ -168,7 +168,7 @@ export const UnsplashSearchModal : React.FC = ({onClose, onI } }; - async function insertImage(image:Photo) { + async function insertImage(image:InsertImagePayload) { if (image.src) { UnsplashLib.triggerDownload(image); onImageInsert(image); diff --git a/packages/kg-unsplash-selector/src/UnsplashTypes.ts b/packages/kg-unsplash-selector/src/UnsplashTypes.ts index 3c78aa223f..92dcbe6618 100644 --- a/packages/kg-unsplash-selector/src/UnsplashTypes.ts +++ b/packages/kg-unsplash-selector/src/UnsplashTypes.ts @@ -71,6 +71,18 @@ export type Photo = { src? : string; }; +export interface InsertImagePayload { + src: string; + caption: string; + height: number; + width: number; + alt: string; + links: Links; +} + +export type InsertImageFn = (payload: InsertImagePayload) => void; +export type SelectImgFn = (payload: Photo | null) => void; + export type DefaultHeaderTypes = { Authorization: string; 'Accept-Version': string; diff --git a/packages/kg-unsplash-selector/src/api/IUnsplashProvider.ts b/packages/kg-unsplash-selector/src/api/IUnsplashProvider.ts index f92143649f..86e27d6e6b 100644 --- a/packages/kg-unsplash-selector/src/api/IUnsplashProvider.ts +++ b/packages/kg-unsplash-selector/src/api/IUnsplashProvider.ts @@ -4,6 +4,6 @@ export interface IUnsplashProvider { fetchPhotos(): Promise; fetchNextPage(): Promise; searchPhotos(term: string): Promise; - triggerDownload(photo: Photo): Promise | void; + triggerDownload(photo: Pick): Promise | void; searchIsRunning(): boolean; } diff --git a/packages/kg-unsplash-selector/src/api/InMemoryUnsplashProvider.ts b/packages/kg-unsplash-selector/src/api/InMemoryUnsplashProvider.ts index 1821154ad2..bcfe3bc5a5 100644 --- a/packages/kg-unsplash-selector/src/api/InMemoryUnsplashProvider.ts +++ b/packages/kg-unsplash-selector/src/api/InMemoryUnsplashProvider.ts @@ -36,7 +36,7 @@ export class InMemoryUnsplashProvider implements IUnsplashProvider { public async searchPhotos(term: string): Promise { this.SEARCH_IS_RUNNING = true; - const filteredPhotos = this.photos.filter(photo => (photo.description && photo.description.toLowerCase().includes(term.toLowerCase())) || + const filteredPhotos = this.photos.filter(photo => (photo.description && photo.description.toLowerCase().includes(term.toLowerCase())) || (photo.alt_description && photo.alt_description.toLowerCase().includes(term.toLowerCase())) ); this.SEARCH_IS_RUNNING = false; @@ -47,9 +47,8 @@ export class InMemoryUnsplashProvider implements IUnsplashProvider { return this.SEARCH_IS_RUNNING; } - triggerDownload(photo: Photo): void { - () => { - photo; - }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + triggerDownload(_photo: Pick): void { + // no-op for in-memory provider } } diff --git a/packages/kg-unsplash-selector/src/api/PhotoUseCase.ts b/packages/kg-unsplash-selector/src/api/PhotoUseCase.ts index 8fe140999c..5b3b5abb5d 100644 --- a/packages/kg-unsplash-selector/src/api/PhotoUseCase.ts +++ b/packages/kg-unsplash-selector/src/api/PhotoUseCase.ts @@ -16,7 +16,7 @@ export class PhotoUseCases { return await this._provider.searchPhotos(term); } - async triggerDownload(photo: Photo): Promise { + async triggerDownload(photo: Pick): Promise { this._provider.triggerDownload(photo); } diff --git a/packages/kg-unsplash-selector/src/api/UnsplashProvider.ts b/packages/kg-unsplash-selector/src/api/UnsplashProvider.ts index 6ff22d4c16..3c7ca49b8d 100644 --- a/packages/kg-unsplash-selector/src/api/UnsplashProvider.ts +++ b/packages/kg-unsplash-selector/src/api/UnsplashProvider.ts @@ -116,7 +116,7 @@ export class UnsplashProvider implements IUnsplashProvider { return []; } - public async triggerDownload(photo: Photo): Promise { + public async triggerDownload(photo: Pick): Promise { if (photo.links.download_location) { await this.makeRequest(photo.links.download_location); } diff --git a/packages/kg-unsplash-selector/src/api/UnsplashService.ts b/packages/kg-unsplash-selector/src/api/UnsplashService.ts index 685e16990f..611036b211 100644 --- a/packages/kg-unsplash-selector/src/api/UnsplashService.ts +++ b/packages/kg-unsplash-selector/src/api/UnsplashService.ts @@ -9,7 +9,7 @@ export interface IUnsplashService { updateSearch(term: string): Promise; loadNextPage(): Promise; clearPhotos(): void; - triggerDownload(photo: Photo): void; + triggerDownload(photo: Pick): void; photos: Photo[]; searchIsRunning(): boolean; } @@ -61,7 +61,7 @@ export class UnsplashService implements IUnsplashService { this.photos = []; } - triggerDownload(photo: Photo) { + triggerDownload(photo: Pick) { this.photoUseCases.triggerDownload(photo); } diff --git a/packages/kg-unsplash-selector/src/ui/UnsplashGallery.tsx b/packages/kg-unsplash-selector/src/ui/UnsplashGallery.tsx index 6b45ce27e1..acce3c4f36 100644 --- a/packages/kg-unsplash-selector/src/ui/UnsplashGallery.tsx +++ b/packages/kg-unsplash-selector/src/ui/UnsplashGallery.tsx @@ -1,7 +1,7 @@ import React, {ReactNode, RefObject} from 'react'; import UnsplashImage from './UnsplashImage'; import UnsplashZoomed from './UnsplashZoomed'; -import {Photo} from '../UnsplashTypes'; +import {InsertImageFn, Photo, SelectImgFn} from '../UnsplashTypes'; interface MasonryColumnProps { children: ReactNode; @@ -9,8 +9,8 @@ interface MasonryColumnProps { interface UnsplashGalleryColumnsProps { columns?: Photo[][] | []; - insertImage?: any; - selectImg?: any; + insertImage: InsertImageFn; + selectImg: SelectImgFn; zoomed?: Photo | null; } @@ -24,8 +24,8 @@ interface GalleryLayoutProps { interface UnsplashGalleryProps extends GalleryLayoutProps { error?: string | null; dataset?: Photo[][] | []; - selectImg?: any; - insertImage?: any; + selectImg: SelectImgFn; + insertImage: InsertImageFn; } const UnsplashGalleryLoading: React.FC = () => { diff --git a/packages/kg-unsplash-selector/src/ui/UnsplashImage.tsx b/packages/kg-unsplash-selector/src/ui/UnsplashImage.tsx index f4b151d49d..174e9a02c7 100644 --- a/packages/kg-unsplash-selector/src/ui/UnsplashImage.tsx +++ b/packages/kg-unsplash-selector/src/ui/UnsplashImage.tsx @@ -1,11 +1,11 @@ import UnsplashButton from './UnsplashButton'; import {FC, MouseEvent} from 'react'; -import {Links, Photo, User} from '../UnsplashTypes'; +import {InsertImageFn, Photo, SelectImgFn, User} from '../UnsplashTypes'; export interface UnsplashImageProps { payload: Photo; srcUrl: string; - links: Links; + links: Photo['links']; likes: number; user: User; alt: string; @@ -13,15 +13,8 @@ export interface UnsplashImageProps { height: number; width: number; zoomed: Photo | null; - insertImage: (options: { - src: string, - caption: string, - height: number, - width: number, - alt: string, - links: Links - }) => void; - selectImg: (payload: Photo | null) => void; + insertImage: InsertImageFn; + selectImg: SelectImgFn; } const UnsplashImage: FC = ({payload, srcUrl, links, likes, user, alt, urls, height, width, zoomed, insertImage, selectImg}) => { diff --git a/packages/kg-unsplash-selector/src/ui/UnsplashZoomed.tsx b/packages/kg-unsplash-selector/src/ui/UnsplashZoomed.tsx index 88860d3b18..c7b45784dd 100644 --- a/packages/kg-unsplash-selector/src/ui/UnsplashZoomed.tsx +++ b/packages/kg-unsplash-selector/src/ui/UnsplashZoomed.tsx @@ -1,10 +1,10 @@ import UnsplashImage, {UnsplashImageProps} from './UnsplashImage'; import {FC} from 'react'; -import {Photo} from '../UnsplashTypes'; +import {Photo, SelectImgFn} from '../UnsplashTypes'; interface UnsplashZoomedProps extends Omit { zoomed: Photo | null; - selectImg: (photo: Photo | null) => void; + selectImg: SelectImgFn; } const UnsplashZoomed: FC = ({payload, insertImage, selectImg, zoomed}) => { diff --git a/packages/kg-unsplash-selector/tsconfig.json b/packages/kg-unsplash-selector/tsconfig.json index 327050c11b..984ef82d2b 100644 --- a/packages/kg-unsplash-selector/tsconfig.json +++ b/packages/kg-unsplash-selector/tsconfig.json @@ -1,29 +1,21 @@ { "compilerOptions": { "target": "ESNext", - "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "bundler", "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, - "allowSyntheticDefaultImports": true, + "jsx": "react-jsx", "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", + "skipLibCheck": true, "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", - "skipLibCheck": true, - "esModuleInterop": true, - "declarationMap": true, - "declaration": true, - "emitDeclarationOnly": true, - "declarationDir": "./types", - "baseUrl": ".", - "paths": { - "kg-unsplash-selector": ["src/index.ts"], - }, - "typeRoots": ["node_modules/@types", "types/index.d.ts"], + "noEmit": true }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src/**/*"], + "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/packages/kg-unsplash-selector/tsconfig.test.json b/packages/kg-unsplash-selector/tsconfig.test.json new file mode 100644 index 0000000000..b092804ad8 --- /dev/null +++ b/packages/kg-unsplash-selector/tsconfig.test.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": null, + "noEmit": true, + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "incremental": false + }, + "include": [ + "src/**/*", + "test/**/*" + ] +} diff --git a/yarn.lock b/yarn.lock index 76fcd3d447..2c1d86cd5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1427,6 +1427,11 @@ minimatch "^3.1.5" strip-json-comments "^3.1.1" +"@eslint/js@9.37.0": + version "9.37.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.37.0.tgz#0cfd5aa763fe5d1ee60bedf84cd14f54bcf9e21b" + integrity sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg== + "@eslint/js@9.39.4": version "9.39.4" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.4.tgz#a3f83bfc6fd9bf33a853dfacd0b49b398eb596c1" @@ -15411,6 +15416,11 @@ vinyl@^3.0.0, vinyl@^3.0.1: replace-ext "^2.0.0" teex "^1.0.1" +vite-plugin-css-injected-by-js@3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz#1f75d16ad5c05b6b49bf18018099a189ec2e46ad" + integrity sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ== + vite-plugin-dts@4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz#51b60aaaa760d9cf5c2bb3676c69d81910d6b08c"