From 7f9e227f6b3a43e2755ccb0de0cf8177ce293714 Mon Sep 17 00:00:00 2001 From: thomasabishop Date: Thu, 11 Dec 2025 19:17:37 +0000 Subject: [PATCH] feat: add search functionality --- package-lock.json | 424 +++++++++++++++++++++--- package.json | 8 +- src/App.css | 30 +- src/components/AppHeader.tsx | 3 +- src/components/BodyLink.tsx | 6 +- src/components/EntriesSearchResult.tsx | 37 +++ src/components/EntryBody.tsx | 188 ++++++----- src/components/EntryLoadingSkeleton.tsx | 18 + src/components/SearchHistory.tsx | 21 ++ src/components/SearchInput.tsx | 42 +++ src/components/SearchResults.tsx | 109 ++++++ src/components/TagsSearchResult.tsx | 0 src/components/ui/field.tsx | 246 ++++++++++++++ src/components/ui/kbd.tsx | 28 ++ src/components/ui/sheet.tsx | 189 +++++------ src/containers/Search.tsx | 77 +++++ src/containers/Settings.tsx | 10 +- 17 files changed, 1188 insertions(+), 248 deletions(-) create mode 100644 src/components/EntriesSearchResult.tsx create mode 100644 src/components/EntryLoadingSkeleton.tsx create mode 100644 src/components/SearchHistory.tsx create mode 100644 src/components/SearchInput.tsx create mode 100644 src/components/SearchResults.tsx create mode 100644 src/components/TagsSearchResult.tsx create mode 100644 src/components/ui/field.tsx create mode 100644 src/components/ui/kbd.tsx create mode 100644 src/containers/Search.tsx diff --git a/package-lock.json b/package-lock.json index 5611b84..daaff42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,10 @@ "version": "0.0.0", "dependencies": { "@radix-ui/react-collapsible": "^1.1.7", - "@radix-ui/react-dialog": "^1.1.10", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-hover-card": "^1.1.15", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tabs": "^1.1.12", @@ -20,6 +20,7 @@ "@react-sigma/core": "^5.0.4", "@react-sigma/layout-forceatlas2": "^5.0.6", "@tailwindcss/vite": "^4.1.4", + "@tanstack/react-form": "^1.27.1", "@tanstack/react-query": "^5.83.0", "@tanstack/react-query-devtools": "^5.83.0", "@tanstack/react-table": "^8.21.3", @@ -37,6 +38,7 @@ "react-resizable-panels": "^3.0.4", "react-router": "^7.7.0", "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "shiki": "^3.9.1", @@ -1175,21 +1177,22 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.10.tgz", - "integrity": "sha512-m6pZb0gEM5uHPSb+i2nKKGQi/HMSVjARMsLMWQfKDP+eJ6B+uqryHnXhpnohTWElw+vEcMk/o4wJODtdRKHwqg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.2", + "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.7", - "@radix-ui/react-focus-guards": "1.1.2", - "@radix-ui/react-focus-scope": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.6", - "@radix-ui/react-presence": "1.1.3", - "@radix-ui/react-primitive": "2.1.0", - "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" @@ -1209,21 +1212,107 @@ } } }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", - "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true } } }, @@ -1269,9 +1358,10 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", - "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1283,12 +1373,13 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.4.tgz", - "integrity": "sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { @@ -1306,6 +1397,29 @@ } } }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-hover-card": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", @@ -1514,12 +1628,12 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", - "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1537,12 +1651,12 @@ } }, "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", @@ -1559,6 +1673,24 @@ } } }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.4.tgz", @@ -1731,12 +1863,12 @@ } }, "node_modules/@radix-ui/react-separator": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.4.tgz", - "integrity": "sha512-2fTm6PSiUm8YPq9W0E4reYuv01EE3aFSzt8edBiXqPHshF8N9+Kymt/k0/R+F3dkY5lQyB/zPtrP82phskLi7w==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.0" + "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1753,6 +1885,47 @@ } } }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -2904,6 +3077,51 @@ "vite": "^5.2.0 || ^6" } }, + "node_modules/@tanstack/devtools-event-client": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.3.5.tgz", + "integrity": "sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/form-core": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@tanstack/form-core/-/form-core-1.27.1.tgz", + "integrity": "sha512-hPM+0tUnZ2C2zb2TE1lar1JJ0S0cbnQHlUwFcCnVBpMV3rjtUzkoM766gUpWrlmTGCzNad0GbJ0aTxVsjT6J8g==", + "license": "MIT", + "dependencies": { + "@tanstack/devtools-event-client": "^0.3.5", + "@tanstack/pacer": "^0.15.3", + "@tanstack/store": "^0.7.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/pacer": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@tanstack/pacer/-/pacer-0.15.4.tgz", + "integrity": "sha512-vGY+CWsFZeac3dELgB6UZ4c7OacwsLb8hvL2gLS6hTgy8Fl0Bm/aLokHaeDIP+q9F9HUZTnp360z9uv78eg8pg==", + "license": "MIT", + "dependencies": { + "@tanstack/devtools-event-client": "^0.3.2", + "@tanstack/store": "^0.7.5" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/query-core": { "version": "5.83.0", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz", @@ -2924,6 +3142,28 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/react-form": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-form/-/react-form-1.27.1.tgz", + "integrity": "sha512-HKP0Ew2ae9AL5vU1PkJ+oAC2p+xBtA905u0fiNLzlfn1vLkBxenfg5L6TOA+rZITHpQsSo10tqwc5Yw6qn8Mpg==", + "license": "MIT", + "dependencies": { + "@tanstack/form-core": "1.27.1", + "@tanstack/react-store": "^0.8.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@tanstack/react-start": { + "optional": true + } + } + }, "node_modules/@tanstack/react-query": { "version": "5.83.0", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.83.0.tgz", @@ -2958,6 +3198,34 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-store": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.8.0.tgz", + "integrity": "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.8.0", + "use-sync-external-store": "^1.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/react-store/node_modules/@tanstack/store": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.8.0.tgz", + "integrity": "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-table": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", @@ -2978,6 +3246,16 @@ "react-dom": ">=16.8" } }, + "node_modules/@tanstack/store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.7.tgz", + "integrity": "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/table-core": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", @@ -4641,6 +4919,31 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-html": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", @@ -4691,6 +4994,25 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-text": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", @@ -6687,6 +7009,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -7362,6 +7699,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", diff --git a/package.json b/package.json index 26caa8d..a4f0bc7 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ }, "dependencies": { "@radix-ui/react-collapsible": "^1.1.7", - "@radix-ui/react-dialog": "^1.1.10", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-hover-card": "^1.1.15", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tabs": "^1.1.12", @@ -22,6 +22,7 @@ "@react-sigma/core": "^5.0.4", "@react-sigma/layout-forceatlas2": "^5.0.6", "@tailwindcss/vite": "^4.1.4", + "@tanstack/react-form": "^1.27.1", "@tanstack/react-query": "^5.83.0", "@tanstack/react-query-devtools": "^5.83.0", "@tanstack/react-table": "^8.21.3", @@ -39,6 +40,7 @@ "react-resizable-panels": "^3.0.4", "react-router": "^7.7.0", "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "shiki": "^3.9.1", diff --git a/src/App.css b/src/App.css index 73a76d1..6c6c7af 100644 --- a/src/App.css +++ b/src/App.css @@ -2,30 +2,34 @@ @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap"); code { - font-family: "JetBrains Mono"; + font-family: "JetBrains Mono"; } -pre > code { - padding: 0.5rem; - font-size: 14px; +pre>code { + padding: 0.5rem; + font-size: 14px; } -h2 > code { - font-size: 1rem; +h2>code { + font-size: 1rem; } .btn[data-state="active"] { - box-shadow: none !important; + box-shadow: none !important; } button[data-state="active"] { - --tw-shadow: none; - --tw-shadow-colored: none; - box-shadow: - var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + --tw-shadow: none; + --tw-shadow-colored: none; + box-shadow: + var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } .sigma-container { - width: 100%; - height: 100%; + width: 100%; + height: 100%; +} + +match { + color: "red" !important; } diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index a0cb29c..98ab531 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -1,5 +1,6 @@ import { SidebarTrigger } from "./ui/sidebar" import { Separator } from "./ui/separator" +import Search from "@/containers/Search" export default function AppHeader({ pageTitle }: { pageTitle: string }) { return ( @@ -7,7 +8,7 @@ export default function AppHeader({ pageTitle }: { pageTitle: string }) {
-

{pageTitle}

+
) diff --git a/src/components/BodyLink.tsx b/src/components/BodyLink.tsx index 44e4b65..60584b9 100644 --- a/src/components/BodyLink.tsx +++ b/src/components/BodyLink.tsx @@ -12,7 +12,7 @@ export default function BodyLink({ link, children }) { const cachedEntry = queryClient.getQueryData([`entry_${path}`]) if (cachedEntry) { setEntryExists(true) - console.info("INFO: Entry exists in cache.") + // console.info("INFO: Entry exists in cache.") } else { try { const remoteEntry = await queryClient.fetchQuery({ @@ -21,9 +21,9 @@ export default function BodyLink({ link, children }) { }) setEntryExists(true) - console.info("INFO: Entry exists on remote.") + // console.info("INFO: Entry exists on remote.") } catch (error) { - console.log(`INFO: Could not fetch entry ${path} ${error}`) + // console.log(`INFO: Could not fetch entry ${path} ${error}`) setEntryExists(false) } } diff --git a/src/components/EntriesSearchResult.tsx b/src/components/EntriesSearchResult.tsx new file mode 100644 index 0000000..b91b080 --- /dev/null +++ b/src/components/EntriesSearchResult.tsx @@ -0,0 +1,37 @@ +import ReactMarkdown from "react-markdown" +import rehypeRaw from "rehype-raw" +import remarkGfm from "remark-gfm" +import remarkMath from "remark-math" +import { Link } from "react-router" + +const stripMarkdownLinks = (text) => { + return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") +} + +export default function EntriesSearchResult({ entry, match, searchParams }) { + return ( + + {" "} +
+ ( + {children} + ), + code: ({ children }) => ( + {children} + ), + pre: ({ children }) => ( + {children} + ), + }} + > + {stripMarkdownLinks(match)} + +
{`${entry.replace(/_/g, " ")}`}
+
+ + ) +} diff --git a/src/components/EntryBody.tsx b/src/components/EntryBody.tsx index 1a99f64..d4ec541 100644 --- a/src/components/EntryBody.tsx +++ b/src/components/EntryBody.tsx @@ -4,26 +4,9 @@ import CodeBlock from "@/components/CodeBlock" import remarkMath from "remark-math" import rehypeKatex from "rehype-katex" import "katex/dist/katex.min.css" -import { Skeleton } from "@/components/ui/skeleton" import BodyLink from "./BodyLink" - -const EntryLoadingSkeleton = () => { - return ( -
- {/* - - - */} - - - - - - - -
- ) -} +import EntryLoadingSkeleton from "./EntryLoadingSkeleton" +import { useSearchParams } from "react-router" const ImagePreprocessor = (src) => { const filename = src.src.split("/").pop() @@ -31,73 +14,108 @@ const ImagePreprocessor = (src) => { return } +const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + +const highlighter = (children, highlight) => { + if (!highlight || typeof children !== "string") return children + const words = highlight.trim().split(/\s+/) + const pattern = words.length > 1 ? escapeRegex(highlight) : escapeRegex(words[0]) + const regex = new RegExp(`\\b(${pattern})\\b`, "gi") + const parts = children.split(regex) + + return parts.map((part, i) => + regex.test(part) ? ( + + {part} + + ) : ( + part + ), + ) +} + export default function EntryBody({ body, isLoading }) { + const [searchParams] = useSearchParams() + + const highlight = searchParams.get("highlight") + if (isLoading) { return - } else - return ( -
- null, - h2: ({ children }) => ( -

{children}

- ), - h3: ({ children }) => ( -

{children}

- ), - h4: ({ children }) => ( -

{children}

- ), - p: ({ children }) => ( -

{children}

- ), - ul: ({ children }) => ( -
    {children}
- ), - ol: ({ children }) => ( -
    {children}
- ), - table: ({ children }) => ( - {children}
- ), - tr: ({ children }) => ( - {children} - ), - th: ({ children }) => ( - - {children} - - ), - td: ({ children }) => ( - - {children} - - ), - blockquote: ({ children }) => ( -
- {children} -
- ), - pre: ({ children }) => { - const child = children.props - return {child.children} - }, - code: ({ children }) => ( - - {children} - - ), - img: ({ src }) => , - a: ({ href, children }) => { - return - }, - }} - > - {body} -
-
- ) + } + return ( +
+ null, + h2: ({ children }) => ( +

+ {highlighter(children, highlight)} +

+ ), + h3: ({ children }) => ( +

+ {highlighter(children, highlight)} +

+ ), + h4: ({ children }) => ( +

+ {highlighter(children, highlight)} +

+ ), + p: ({ children }) => ( +

+ {highlighter(children, highlight)} +

+ ), + ul: ({ children }) =>
    {children}
, + ol: ({ children }) => ( +
    {children}
+ ), + li: ({ children }) => ( +
  • + {highlighter(children, highlight)} +
  • + ), + table: ({ children }) => {children}
    , + tr: ({ children }) => ( + + {highlighter(children, highlight)} + + ), + th: ({ children }) => ( + + {highlighter(children, highlight)} + + ), + td: ({ children }) => ( + + {highlighter(children, highlight)} + + ), + blockquote: ({ children }) => ( +
    + {highlighter(children, highlight)} +
    + ), + pre: ({ children }) => { + const child = children.props + return {child.children} + }, + code: ({ children }) => ( + + {children} + + ), + img: ({ src }) => , + a: ({ href, children }) => { + return + }, + }} + > + {body} +
    +
    + ) } diff --git a/src/components/EntryLoadingSkeleton.tsx b/src/components/EntryLoadingSkeleton.tsx new file mode 100644 index 0000000..fd8e553 --- /dev/null +++ b/src/components/EntryLoadingSkeleton.tsx @@ -0,0 +1,18 @@ +import { Skeleton } from "@/components/ui/skeleton" +export default function EntryLoadingSkeleton() { + return ( +
    + {/* + + + */} + + + + + + + +
    + ) +} diff --git a/src/components/SearchHistory.tsx b/src/components/SearchHistory.tsx new file mode 100644 index 0000000..945e04f --- /dev/null +++ b/src/components/SearchHistory.tsx @@ -0,0 +1,21 @@ +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table" +import { useQueryClient } from "@tanstack/react-query" +export default function SearchHistory({ }) { + const queryClient = useQueryClient() + const history = queryClient.getQueriesData({ queryKey: ["search_results"] }) + console.log(history) + return ( + + {/* */} + {/* */} + {/* Search term */} + {/* */} + {/* */} + + + Paid + + +
    + ) +} diff --git a/src/components/SearchInput.tsx b/src/components/SearchInput.tsx new file mode 100644 index 0000000..6398ae2 --- /dev/null +++ b/src/components/SearchInput.tsx @@ -0,0 +1,42 @@ +import { Field } from "./ui/field" +import { Input } from "./ui/input" +import { Kbd } from "./ui/kbd" + +export default function SearchInput({ form, inputRef }) { + return ( + <> +
    + { + return ( + +
    + field.handleChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault() + form.handleSubmit() + } + }} + type="search" + placeholder="Search" + /> +
    + {field.state.value ? Enter : Ctrl + K} +
    +
    +
    + ) + }} + /> + + + ) +} diff --git a/src/components/SearchResults.tsx b/src/components/SearchResults.tsx new file mode 100644 index 0000000..c008285 --- /dev/null +++ b/src/components/SearchResults.tsx @@ -0,0 +1,109 @@ +import { + Sheet, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from "./ui/sheet" +import { Badge } from "./ui/badge" +import EntriesSearchResult from "./EntriesSearchResult" +import { Link } from "react-router" +import { Skeleton } from "./ui/skeleton" + +export default function SearchResults({ + form, + entriesResults, + tagResults, + sheetOpen, + loading, + error, + setSheetOpen, + searchParams, + setSearchParams, +}) { + return ( + { + setSheetOpen(open) + if (!open) { + form.reset() + setSearchParams(null) + } + }} + > + + + +
    Search Results
    +
    + +
    Search term:
    + {searchParams} +
    +
    + + {error ? ( +
    +
    + Error fetching search results. +
    {" "} +
    + ) : ( + <> +
    +

    Tags

    + + {tagResults?.length || 0} + +
    + {loading ? ( +
    + + +
    + ) : ( +
    + {tagResults.map((tagResult) => ( + + + {tagResult} + + + ))} +
    + )} +
    +

    Entries

    + + {entriesResults?.count || 0} + +
    + {loading ? ( +
    + + + + +
    + ) : ( +
    + {entriesResults?.data.map((result, i) => ( + + ))} +
    + )} + + )} + + +
    +
    + ) +} diff --git a/src/components/TagsSearchResult.tsx b/src/components/TagsSearchResult.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx new file mode 100644 index 0000000..db0dc12 --- /dev/null +++ b/src/components/ui/field.tsx @@ -0,0 +1,246 @@ +import { useMemo } from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" +import { Separator } from "@/components/ui/separator" + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
    [data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...props} + /> + ) +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ) +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
    [data-slot=field-group]]:gap-4", + className + )} + {...props} + /> + ) +} + +const fieldVariants = cva( + "group/field flex w-full gap-3 data-[invalid=true]:text-destructive", + { + variants: { + orientation: { + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], + horizontal: [ + "flex-row items-center", + "[&>[data-slot=field-label]]:flex-auto", + "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + responsive: [ + "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto", + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + }, + }, + defaultVariants: { + orientation: "vertical", + }, + } +) + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
    + ) +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
    + ) +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +