From f3f3f1b924ad58bd6ae26ba51276484ff6f124d9 Mon Sep 17 00:00:00 2001 From: Mathieu Marques Date: Wed, 20 Sep 2023 13:31:19 +0200 Subject: [PATCH 1/4] feat(search): add search input --- .lintstagedrc.json | 2 +- src/components/Header/Header.tsx | 2 ++ src/components/Layout/Layout.tsx | 1 - src/components/Search/Search.tsx | 29 +++++++++++++++++++++++ src/components/Sidebar/Sidebar.tsx | 4 +--- src/components/Sidebar/SidebarRosetta.tsx | 9 +++---- src/hooks/useDebounce.ts | 12 ++++++++++ 7 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 src/components/Search/Search.tsx create mode 100644 src/hooks/useDebounce.ts diff --git a/.lintstagedrc.json b/.lintstagedrc.json index f020ceed..057ce47a 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,5 +1,5 @@ { "*.{js,jsx,ts,tsx}": "eslint", - "*.{js,json,jsx,scss,ts,tsx}": "prettier --write", + "*.{md,js,json,jsx,scss,ts,tsx}": "prettier --write", "*.md": "markdownlint" } diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 28035de4..9988c1a6 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -4,6 +4,7 @@ import Icon from '@mdi/react'; import { AppBar, Box, IconButton, Toolbar } from '@mui/material'; import { alpha, useTheme } from '@mui/material/styles'; import { Progress } from '@/components/Progress/Progress'; +import { Search } from '@/components/Search/Search'; interface Props { isMobile: boolean; @@ -38,6 +39,7 @@ export const Header: FunctionComponent = ({ )} + mixins.toolbar} /> diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index b7533c47..0fd8f5cc 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -77,7 +77,6 @@ export const Layout: FunctionComponent = ({ withProgress={withProgress} /> { + const [input, setInput] = useState(''); + const query = useDebounce(input, 200); + + useEffect(() => {}, [query]); + + const onChange = ({ target }: ChangeEvent) => { + setInput(target.value); + }; + + return ( + + ); +}; diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index 399078dc..b9d959a3 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -20,7 +20,6 @@ import { darkTheme } from '@/theme/theme'; import type { MenuEntry } from '@/tools/markdown/types'; interface Props { - category: string | undefined; isClear: boolean; isMobile?: boolean; isOpen?: boolean; @@ -29,7 +28,6 @@ interface Props { } export const Sidebar: FunctionComponent = ({ - category, isClear, isMobile = false, isOpen, @@ -80,7 +78,7 @@ export const Sidebar: FunctionComponent = ({ /> - + ); diff --git a/src/components/Sidebar/SidebarRosetta.tsx b/src/components/Sidebar/SidebarRosetta.tsx index d363798f..4dfea23f 100644 --- a/src/components/Sidebar/SidebarRosetta.tsx +++ b/src/components/Sidebar/SidebarRosetta.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState } from 'react'; -import type { FunctionComponent } from 'react'; +import { useRouter } from 'next/router'; +import { useEffect, useState, type FunctionComponent } from 'react'; import { Table, TableBody, @@ -14,11 +14,12 @@ import { getRosetta } from '@/tools/game/getRosetta'; import type { Rosetta } from '@/tools/game/getRosetta'; interface Props { - category: string | undefined; sx?: SxProps; } -export const SidebarRosetta: FunctionComponent = ({ category, sx }) => { +export const SidebarRosetta: FunctionComponent = ({ sx }) => { + const { query } = useRouter(); + const category = query.category && (query.category as string); const [rosetta, setRosetta] = useState([]); useEffect(() => { diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts new file mode 100644 index 00000000..73b63472 --- /dev/null +++ b/src/hooks/useDebounce.ts @@ -0,0 +1,12 @@ +import { useEffect, useState } from 'react'; + +export const useDebounce = (input: T, delay: number): T => { + const [output, setOutput] = useState(input); + + useEffect(() => { + const timer = setTimeout(() => setOutput(input), delay); + return () => clearTimeout(timer); + }, [input, delay]); + + return output; +}; From b99cb087dd654648e907ace65f2e7c041c620d9f Mon Sep 17 00:00:00 2001 From: Mathieu Marques Date: Wed, 20 Sep 2023 13:31:37 +0200 Subject: [PATCH 2/4] feat(guff): add first draft wip --- guff/guff.ts | 9 +++++ guff/tsconfig.json | 9 +++++ guff/walk.ts | 38 +++++++++++++++++++ package.json | 7 +++- tsconfig.json | 2 +- yarn.lock | 93 +++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 guff/guff.ts create mode 100644 guff/tsconfig.json create mode 100644 guff/walk.ts diff --git a/guff/guff.ts b/guff/guff.ts new file mode 100644 index 00000000..02fb7c0d --- /dev/null +++ b/guff/guff.ts @@ -0,0 +1,9 @@ +import { walk } from './walk'; + +const guff = (path: string) => { + const files = walk(path); + console.log(path, files); +}; + +const [_binary, _script, ...paths] = process.argv; +paths.map((path) => path && guff(path)); diff --git a/guff/tsconfig.json b/guff/tsconfig.json new file mode 100644 index 00000000..8a59c1e7 --- /dev/null +++ b/guff/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "noUncheckedIndexedAccess": true, + "strict": true, + "target": "ESNext" + }, + "include": ["**/*.ts"] +} diff --git a/guff/walk.ts b/guff/walk.ts new file mode 100644 index 00000000..42524621 --- /dev/null +++ b/guff/walk.ts @@ -0,0 +1,38 @@ +import { existsSync, readdirSync } from 'fs'; +import { join, parse } from 'path'; + +interface WalkOptions { + depth?: number; + extension?: string; +} + +/** + * Generator to walk through `directory` and yield all files. + * Return a tuple containing all successive parent folders followed by the file + * with their extension. + * When `extension` is specified, only return files that match. + * Prefer synchronous exploration since order matters. + */ +function* walkIterator( + directory: string, + options: WalkOptions = {}, + accumulator: string[] = [], +): Generator { + const { depth, extension } = options; + if (accumulator.length === depth) return; + if (existsSync(directory)) { + // NOTE Looping over the stream requires the `--downlevelIteration` flag + for (const file of readdirSync(directory, { withFileTypes: true })) { + const { ext, name } = parse(file.name); + if (file.isDirectory()) { + const entry = join(directory, file.name); + yield* walkIterator(entry, options, [...accumulator, file.name]); + } else if (file.isFile() && (!extension || ext === extension)) { + yield [...accumulator, name]; + } + } + } +} + +export const walk = (directory: string, options?: WalkOptions): string[][] => + Array.from(walkIterator(directory, options)); diff --git a/package.json b/package.json index 2a568b43..9b417c6b 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "scripts": { "build": "concurrently 'next build' 'yarn scryfall' --names 'next,scryfall' --prefix-colors auto --kill-others --success first", "dev": "concurrently 'next dev' 'yarn scryfall:dev' --names 'next,scryfall' --prefix-colors auto", - "lint": "concurrently 'yarn:lint:*' --names --prefix-colors auto --success all", + "guff": "ts-node ./guff/guff.ts markdown/articles/ markdown/chapters/", + "guff:watch": "find guff/* | entr yarn guff", + "lint": "concurrently 'yarn:lint:*' --prefix-colors auto --success all", "lint:code": "eslint .", - "lint:format": "prettier --check --loglevel warn markdown/ puzzles/ scryfall/ src/", + "lint:format": "prettier --check --loglevel warn guff/ markdown/ puzzles/ scryfall/ src/", "lint:prune": "ts-prune --error", "lint:typings": "tsc", "lint:wiki": "markdownlint markdown/", @@ -51,6 +53,7 @@ "remark-toc": "8.0.1", "sharp": "0.32.5", "simple-icons": "9.10.0", + "ts-node": "10.9.1", "unified": "10.1.2", "unist-util-select": "5.0.0", "unist-util-visit": "5.0.0" diff --git a/tsconfig.json b/tsconfig.json index dce04f72..b25ff216 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,5 +20,5 @@ "target": "es5" }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "exclude": ["guff/", "node_modules/"] } diff --git a/yarn.lock b/yarn.lock index 75aed7d0..c50f9001 100644 --- a/yarn.lock +++ b/yarn.lock @@ -313,6 +313,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" @@ -710,6 +717,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" @@ -725,6 +737,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": version "0.3.18" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" @@ -1018,6 +1038,26 @@ mkdirp "^1.0.4" path-browserify "^1.0.1" +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + "@types/aria-query@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" @@ -1388,12 +1428,12 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.0.2: +acorn-walk@^8.0.2, acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.1.0, acorn@^8.8.1, acorn@^8.9.0: +acorn@^8.1.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.9.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== @@ -1472,6 +1512,11 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + arg@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" @@ -2143,6 +2188,11 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2329,6 +2379,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diff@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" @@ -4488,6 +4543,11 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -6871,6 +6931,25 @@ ts-morph@^13.0.1: "@ts-morph/common" "~0.12.3" code-block-writer "^11.0.0" +ts-node@10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + ts-prune@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/ts-prune/-/ts-prune-0.10.3.tgz#b6c71a525543b38dcf947a7d3adfb7f9e8b91f38" @@ -7143,6 +7222,11 @@ uvu@^0.5.0: kleur "^4.0.3" sade "^1.7.3" +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-to-istanbul@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" @@ -7406,6 +7490,11 @@ yargs@^17.3.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From b4181a0f8ecd5ed67337bc5eb8fe25b5b0b885ed Mon Sep 17 00:00:00 2001 From: Mathieu Marques Date: Wed, 20 Sep 2023 16:43:12 +0200 Subject: [PATCH 3/4] feat(guff): write entries to JSON --- .eslintrc.json | 2 +- guff/guff.ts | 27 ++++++++++++++++++++------- guff/models.ts | 11 +++++++++++ guff/read.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ guff/tsconfig.json | 6 +++++- guff/typings.d.ts | 3 +++ guff/walk.ts | 5 ++--- 7 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 guff/models.ts create mode 100644 guff/read.ts create mode 100644 guff/typings.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index f37c5892..84c6f809 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -77,7 +77,7 @@ ], "import/no-unresolved": "off", "import/prefer-default-export": "off", - "no-console": ["warn", { "allow": ["error"] }], + "no-console": ["warn", { "allow": ["error", "info"] }], "no-nested-ternary": "off", "no-param-reassign": ["error", { "props": false }], "no-restricted-syntax": [ diff --git a/guff/guff.ts b/guff/guff.ts index 02fb7c0d..42d20583 100644 --- a/guff/guff.ts +++ b/guff/guff.ts @@ -1,9 +1,22 @@ -import { walk } from './walk'; - -const guff = (path: string) => { - const files = walk(path); - console.log(path, files); -}; +import { writeFileSync } from 'fs'; +import type { Entry } from './models'; +import { read } from './read'; const [_binary, _script, ...paths] = process.argv; -paths.map((path) => path && guff(path)); + +const database = paths.reduce((accumulator, path) => { + if (path) { + const entry = read(path); + accumulator.push(...entry); + } + return accumulator; +}, []); + +console.info(`Writing ${database.length} entries...`); + +const json = process.env.GUFF_DEBUG + ? JSON.stringify(database, null, 2) + : JSON.stringify(database); +writeFileSync('guff.json', json, 'utf8'); + +console.info(`Write successful!`); diff --git a/guff/models.ts b/guff/models.ts new file mode 100644 index 00000000..606b8853 --- /dev/null +++ b/guff/models.ts @@ -0,0 +1,11 @@ +export interface Entry { + kind: Kind | null; + headings: string[]; + title: string; +} + +export enum Kind { + ARTICLE, + PRIMER, + REPORT, +} diff --git a/guff/read.ts b/guff/read.ts new file mode 100644 index 00000000..70e7ea22 --- /dev/null +++ b/guff/read.ts @@ -0,0 +1,44 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; +import matter from 'gray-matter'; +import { Kind, type Entry } from './models'; +import { walk } from './walk'; + +/** Regular expression to capture all headings in a Markdown buffer. */ +const HEADING_RE = /^#{1,6} (?.+)$/gm; + +function assertKind(value: unknown): asserts value is Kind | undefined { + if (value && !Object.keys(Kind).includes(value)) { + throw new Error(`Unknown kind "${value}"`); + } +} + +function assertTitle(value: unknown): asserts value is string { + if (typeof value !== 'string') { + throw new Error('Missing title'); + } +} + +export const read = (root: string): Entry[] => { + const files = walk(root, { extension: '.md' }); + console.info(`Parsing "${root}" (${files.length} files)...`); + return files.map((crumbs) => { + const path = join(root, ...crumbs); + const buffer = readFileSync(path, 'utf8'); + const { content, data } = matter(buffer); + const { kind, title } = data; + try { + assertKind(kind); + assertTitle(title); + const matches = [...content.matchAll(HEADING_RE)]; + const headings = matches.reduce((accumulator, match) => { + const heading = match.groups?.text; + return heading ? [...accumulator, heading] : accumulator; + }, []); + return { kind: kind ?? null, headings, title }; + } catch (error) { + const message = error instanceof Error ? error.message : `${error}`; + throw new Error(`${message} in "${path}"`); + } + }); +}; diff --git a/guff/tsconfig.json b/guff/tsconfig.json index 8a59c1e7..a49b79cc 100644 --- a/guff/tsconfig.json +++ b/guff/tsconfig.json @@ -1,9 +1,13 @@ { "compilerOptions": { "baseUrl": ".", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", "noUncheckedIndexedAccess": true, "strict": true, "target": "ESNext" }, - "include": ["**/*.ts"] + "include": ["**/*.ts"], + "ts-node": { "files": true } } diff --git a/guff/typings.d.ts b/guff/typings.d.ts new file mode 100644 index 00000000..343a315b --- /dev/null +++ b/guff/typings.d.ts @@ -0,0 +1,3 @@ +interface Array { + includes(item: unknown, index?: number): item is T; +} diff --git a/guff/walk.ts b/guff/walk.ts index 42524621..cfe0f3ed 100644 --- a/guff/walk.ts +++ b/guff/walk.ts @@ -21,14 +21,13 @@ function* walkIterator( const { depth, extension } = options; if (accumulator.length === depth) return; if (existsSync(directory)) { - // NOTE Looping over the stream requires the `--downlevelIteration` flag for (const file of readdirSync(directory, { withFileTypes: true })) { - const { ext, name } = parse(file.name); + const { base, ext } = parse(file.name); if (file.isDirectory()) { const entry = join(directory, file.name); yield* walkIterator(entry, options, [...accumulator, file.name]); } else if (file.isFile() && (!extension || ext === extension)) { - yield [...accumulator, name]; + yield [...accumulator, base]; } } } From 3fbaa1dff6c569dda8cb1d49b6008c74cfe7e5a7 Mon Sep 17 00:00:00 2001 From: Mathieu Marques Date: Sun, 19 Nov 2023 23:05:54 +0100 Subject: [PATCH 4/4] feat(search): add global hotkey --- package.json | 1 + src/components/Search/Search.tsx | 34 ++++++++++++++++++++++++++++++-- yarn.lock | 5 +++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9b417c6b..dc8d5736 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "plaiceholder": "3.0.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-hotkeys-hook": "4.4.1", "react-intersection-observer": "9.5.2", "react-markdown": "8.0.7", "react-syntax-highlighter": "15.5.0", diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx index 73e0490f..f2d40ffe 100644 --- a/src/components/Search/Search.tsx +++ b/src/components/Search/Search.tsx @@ -1,18 +1,31 @@ import { useEffect, + useRef, useState, type ChangeEvent, type FunctionComponent, } from 'react'; -import { InputBase } from '@mui/material'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { InputBase, Typography } from '@mui/material'; import { useDebounce } from '@/hooks/useDebounce'; export const Search: FunctionComponent = () => { + const root = useRef(null); const [input, setInput] = useState(''); const query = useDebounce(input, 200); useEffect(() => {}, [query]); + useHotkeys( + '/', + (event) => { + event.preventDefault(); // NOTE Prevents typing the `/` + root.current?.focus(); + root.current?.select(); + }, + [root.current], + ); + const onChange = ({ target }: ChangeEvent) => { setInput(target.value); }; @@ -20,9 +33,26 @@ export const Search: FunctionComponent = () => { return ( + / + + } value={input} /> ); diff --git a/yarn.lock b/yarn.lock index c50f9001..91cffdf8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6011,6 +6011,11 @@ react-dom@18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-hotkeys-hook@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.4.1.tgz#1f7a7a1c9c21d4fa3280bf340fcca8fd77d81994" + integrity sha512-sClBMBioFEgFGYLTWWRKvhxcCx1DRznd+wkFHwQZspnRBkHTgruKIHptlK/U/2DPX8BhHoRGzpMVWUXMmdZlmw== + react-intersection-observer@9.5.2: version "9.5.2" resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz#f68363a1ff292323c0808201b58134307a1626d0"