diff --git a/.changeset/clean-crabs-know.md b/.changeset/clean-crabs-know.md new file mode 100644 index 000000000..4f47028ec --- /dev/null +++ b/.changeset/clean-crabs-know.md @@ -0,0 +1,6 @@ +--- +'@myst-theme/site': patch +'@myst-theme/book': patch +--- + +Update the search template options diff --git a/.changeset/curly-dryers-occur.md b/.changeset/curly-dryers-occur.md new file mode 100644 index 000000000..bf5688ec2 --- /dev/null +++ b/.changeset/curly-dryers-occur.md @@ -0,0 +1,6 @@ +--- +'@myst-theme/article': minor +'@myst-theme/book': minor +--- + +Add `myst.search.json` routing diff --git a/package-lock.json b/package-lock.json index 9b41a8e89..f65deed33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7707,6 +7707,14 @@ "resolved": "packages/providers", "link": true }, + "node_modules/@myst-theme/search": { + "resolved": "packages/search", + "link": true + }, + "node_modules/@myst-theme/search-minisearch": { + "resolved": "packages/search-minisearch", + "link": true + }, "node_modules/@myst-theme/site": { "resolved": "packages/site", "link": true @@ -8232,6 +8240,41 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "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-direction": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", @@ -8507,12 +8550,69 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.0.tgz", + "integrity": "sha512-yv+oiLaicYMBpqgfpSPw6q+RyXlLdIpQWDHZbUKURxe+nEh53hFXPPlfhfQQtYkS5MMK/5IWIa76SksleQZSzw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "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-radio-group/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "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-radio-group/node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "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-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", - "dev": true, - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-collection": "1.1.0", @@ -8543,7 +8643,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.0", @@ -8570,7 +8669,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -9017,6 +9115,30 @@ } } }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", + "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select/node_modules/@radix-ui/rect": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", @@ -9081,7 +9203,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, @@ -9335,64 +9456,17 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", - "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", - "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", - "dev": true, - "license": "MIT", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" + "@radix-ui/react-primitive": "2.0.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -9403,26 +9477,6 @@ } } }, - "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", @@ -16335,7 +16389,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.5", @@ -16527,7 +16580,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -19687,7 +19739,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.6", @@ -19705,7 +19756,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -19723,7 +19773,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.6", @@ -20017,7 +20066,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -20872,7 +20920,6 @@ "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -21008,7 +21055,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -21021,7 +21067,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4", @@ -21046,7 +21091,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.1.4", @@ -24172,7 +24216,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -24191,7 +24234,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -24339,7 +24381,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.5", @@ -24648,7 +24689,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -25000,7 +25040,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -26146,7 +26185,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -26349,7 +26387,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -26388,7 +26425,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "license": "MIT", "dependencies": { "has-bigints": "^1.0.1" @@ -26414,7 +26450,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -26517,7 +26552,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, "license": "MIT", "dependencies": { "is-typed-array": "^1.1.13" @@ -26533,7 +26567,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" @@ -26756,7 +26789,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -26778,7 +26810,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" @@ -26857,7 +26888,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -26887,7 +26917,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7" @@ -26915,7 +26944,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" @@ -26956,7 +26984,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.2" @@ -27013,7 +27040,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2" @@ -29212,9 +29238,9 @@ "license": "MIT" }, "node_modules/markdown-it-myst": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/markdown-it-myst/-/markdown-it-myst-1.0.9.tgz", - "integrity": "sha512-LY7uv4sooZ0N+48SZvWKmXFri3aYrRd/q5EUF9NpEtBlzEL64/5fw2EdR+Q8onpQnHc21yzTwRYyGJ0aXkN7RQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/markdown-it-myst/-/markdown-it-myst-1.0.10.tgz", + "integrity": "sha512-u3xgQqrtQrX01dqwbRywwEgBPywbFczvYdCLYXjpqE0Aqzb/NaQmxYqu3ZkUTRG20G1po1QK3bcf/xnvt1h9xg==", "license": "MIT", "dependencies": { "js-yaml": "^4.1.0", @@ -30944,6 +30970,12 @@ "dev": true, "license": "ISC" }, + "node_modules/minisearch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz", + "integrity": "sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==", + "license": "MIT" + }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -31179,13 +31211,13 @@ } }, "node_modules/myst-common": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/myst-common/-/myst-common-1.6.0.tgz", - "integrity": "sha512-k94q1MVLbP+IDL/VL7dxU2rXI9uJNMAcVmrWyAv+II6UL++8z+KGBO6enuqtGxlopB7ZB/ue8DZc21UVJT0zTQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/myst-common/-/myst-common-1.7.0.tgz", + "integrity": "sha512-/75on6T4CMGQGDkWS4pXEg2X4RqVh/rd5Kj40Y0qRxyAJvUeV06iJsST86k/+6lPFNoeIRzB/c7XCIJxusUshQ==", "license": "MIT", "dependencies": { "mdast": "^3.0.0", - "myst-frontmatter": "^1.6.0", + "myst-frontmatter": "^1.7.0", "myst-spec": "^0.0.5", "nanoid": "^4.0.0", "unified": "^10.1.2", @@ -31212,12 +31244,12 @@ } }, "node_modules/myst-config": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/myst-config/-/myst-config-1.6.0.tgz", - "integrity": "sha512-YkI/ewH/fG5hgllnVIhbMRHY7IC7MiWuS6AfEywibi+SWVpcGjPGRpWX7EGZ/TK6kmFuQ8y1eBiQJl9ULS1tjQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/myst-config/-/myst-config-1.7.0.tgz", + "integrity": "sha512-soKeUozg4+F6NuqiwyKRpKUgwldSTYYZNn4UkoXcyPom1Hk43/Ll9NNo5grrQMrg4AG1e6kF1pbDcnk6VBylKg==", "license": "MIT", "dependencies": { - "myst-frontmatter": "^1.6.0", + "myst-frontmatter": "^1.7.0", "simple-validators": "^1.1.0" } }, @@ -31226,9 +31258,9 @@ "link": true }, "node_modules/myst-directives": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/myst-directives/-/myst-directives-1.5.5.tgz", - "integrity": "sha512-pewOKEW3ijnEDUQdlnFQl56PZttgd0dJEOK8qGZ9OtGjmeSd12V0/33DPWZxfHe95KO5rr3nXMI6Sm+biws+iQ==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/myst-directives/-/myst-directives-1.5.6.tgz", + "integrity": "sha512-EDkD5T32m+3pd81JFLS3VZNgxkWfn1p8JLfPdBdOGojD6SbBfSpNYtDXmOaxhF13M/KSeyKt3ZlHL7a5CeKwXA==", "license": "MIT", "dependencies": { "classnames": "^2.3.2", @@ -31260,9 +31292,9 @@ } }, "node_modules/myst-ext-card": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/myst-ext-card/-/myst-ext-card-1.0.7.tgz", - "integrity": "sha512-qLdgqsYSx9eESWW7//O4+TVo0m5NH6cf672P3A0hEYa8oSCb2UI6xvLYwu6d18qqv9jBrGTmEK6PSfqlSN00Iw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/myst-ext-card/-/myst-ext-card-1.0.8.tgz", + "integrity": "sha512-PytOUwM6hnv3tWxVnsBX25NtZiqMQM3lIGLhRPylPaNte6EvSFfV3myq/jotUjz9I4qLPaeaM1O2IhvIscS0Zw==", "license": "MIT", "dependencies": { "myst-common": "^1.5.1" @@ -31306,9 +31338,9 @@ } }, "node_modules/myst-frontmatter": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/myst-frontmatter/-/myst-frontmatter-1.6.0.tgz", - "integrity": "sha512-TWicuNldY4pUJ+HiJa+cfTqd7leZdjyhYVM3xv/I3uX1NKi7bc8CMB4E/QEUOFGormn0fYyTz+Chs+NF3h4lJQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/myst-frontmatter/-/myst-frontmatter-1.7.0.tgz", + "integrity": "sha512-u8FWp+sXzGEZVVg+Q3KtxfU/VyfR+oQGW2M13ON963uled8n681W8go74aJsNNl/+EN0aK7xKtLpFPNgLFxvjA==", "license": "MIT", "dependencies": { "credit-roles": "^2.1.0", @@ -31320,9 +31352,9 @@ } }, "node_modules/myst-parser": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/myst-parser/-/myst-parser-1.5.5.tgz", - "integrity": "sha512-WTPi3Yyv75JNaYzqKjuq5EvQoYJ2aHonlwkv4kYHZsZuGOJbJhuAp5L0vvvMOFd8rUTZbDJFZ4YpaHSW+ML+6A==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/myst-parser/-/myst-parser-1.5.6.tgz", + "integrity": "sha512-ZUDrTn8YRbfKoB5LX2KayYug2lnWzwg7Ggt+CzsPlosip+UWKonzVT/yYDCJisZOk7kpOxxsHo8vKuSwfqPhaA==", "license": "MIT", "dependencies": { "he": "^1.2.0", @@ -31332,12 +31364,12 @@ "markdown-it-dollarmath": "^0.5.0", "markdown-it-footnote": "^3.0.3", "markdown-it-front-matter": "^0.2.3", - "markdown-it-myst": "1.0.9", + "markdown-it-myst": "1.0.10", "markdown-it-myst-extras": "0.3.0", "markdown-it-task-lists": "^2.1.1", "myst-common": "^1.6.0", - "myst-directives": "^1.5.5", - "myst-roles": "^1.5.5", + "myst-directives": "^1.5.6", + "myst-roles": "^1.5.6", "myst-spec": "^0.0.5", "unified": "^10.1.1", "unist-builder": "^3.0.0", @@ -31363,9 +31395,9 @@ } }, "node_modules/myst-roles": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/myst-roles/-/myst-roles-1.5.5.tgz", - "integrity": "sha512-iZwV6uobIvoKqN4tmoKR316WbL75UMIS4SgaDqFjsQb7XE35LHL0ZodngbOSbqhyE0xnsLiKMpOhHUoNu1I7vg==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/myst-roles/-/myst-roles-1.5.6.tgz", + "integrity": "sha512-iHlGOz4KpgeEAhRt0EVWIRP8Zavs8FgVcbGvhAkZkldFiKoel8l4cuppipTNp1wFbIj4a63/ug81tqxMsZ6ZYQ==", "license": "MIT", "dependencies": { "myst-common": "^1.6.0", @@ -31379,9 +31411,9 @@ "license": "MIT" }, "node_modules/myst-spec-ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/myst-spec-ext/-/myst-spec-ext-1.6.0.tgz", - "integrity": "sha512-Ipfu+u//q+Oser9Pd57c4UfJ0OYmdcX4IO4myWx9RTdnxXWg4KDW9IG8SqWHkiE3djXkauE+ndw2ltwZIwCvkg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/myst-spec-ext/-/myst-spec-ext-1.7.0.tgz", + "integrity": "sha512-HrSGD9H8qENEd4ABJZsnsARm9VjqbC211IygYF6XyGpooEHpyw+E7fWc7P9/apVBtH39/Lkdkie9+oVXDRwyRg==", "license": "MIT", "dependencies": { "myst-spec": "^0.0.5" @@ -31403,9 +31435,9 @@ } }, "node_modules/myst-to-html": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/myst-to-html/-/myst-to-html-1.5.5.tgz", - "integrity": "sha512-fkGaxQYgFM/sK0n/Zl9yiyhoLMDELi4wo3zrUYeHAG2nNjmIQCCTzTwFJLxSX79HTTODDKBRrM/F4Gx8kHf2NQ==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/myst-to-html/-/myst-to-html-1.5.6.tgz", + "integrity": "sha512-d7exN4Y1f7AMGklwBaeXTNZWI26vEDw35zEs6PjazuI1yF0boG1Pxp9ktM/Zsljx6ndW4CE3T6gJQ5FltNqGUw==", "license": "MIT", "dependencies": { "classnames": "^2.3.2", @@ -31532,15 +31564,15 @@ "link": true }, "node_modules/myst-to-tex": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/myst-to-tex/-/myst-to-tex-1.0.35.tgz", - "integrity": "sha512-7BZ0zD0dy78lpXVk5SPwssixiriVtFeZPK9LPFS2U9x4kqjDmGAaDgfxgy7VYmDEGHT0OYKxLf+XvN87EP5u8Q==", + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/myst-to-tex/-/myst-to-tex-1.0.37.tgz", + "integrity": "sha512-oWo16UM5bfCTtlL3OHcaQG4l5tuRi4z+olWkJi2czwcRUSMX3GQrj2948evOpIXPvSJzp7lsZ7Fy5hJ+Wb1kBg==", "license": "MIT", "dependencies": { - "myst-common": "^1.5.4", + "myst-common": "^1.6.1", "myst-ext-proof": "^1.0.10", - "myst-frontmatter": "^1.5.4", - "myst-spec-ext": "^1.5.4", + "myst-frontmatter": "^1.6.1", + "myst-spec-ext": "^1.6.1", "unist-util-remove": "^3.1.0", "unist-util-select": "^4.0.3", "vfile-reporter": "^7.0.4" @@ -31562,14 +31594,14 @@ } }, "node_modules/myst-to-typst": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/myst-to-typst/-/myst-to-typst-0.0.21.tgz", - "integrity": "sha512-ZghJaGeROz650C9BjovXgnioTIPK7YrLwMawUAzczr6/qp9Fk1UHafiHtNUSUcP+LjE4RjVNgtbgJrQwKpfZiA==", + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/myst-to-typst/-/myst-to-typst-0.0.22.tgz", + "integrity": "sha512-3wV3uvW7n39eDAd2DdmbiEdfEzetC6KKsLmdhO8TvBXPsK/udnA7RIj9rrv4+RxUbyKZtcAR2qvR+u7IGjH8eA==", "license": "MIT", "dependencies": { - "myst-common": "^1.5.3", - "myst-frontmatter": "^1.5.3", - "myst-spec-ext": "^1.5.3", + "myst-common": "^1.7.0", + "myst-frontmatter": "^1.7.0", + "myst-spec-ext": "^1.7.0", "tex-to-typst": "^0.0.7", "unist-util-select": "^4.0.3", "vfile-reporter": "^7.0.4" @@ -31585,9 +31617,9 @@ } }, "node_modules/myst-transforms": { - "version": "1.3.24", - "resolved": "https://registry.npmjs.org/myst-transforms/-/myst-transforms-1.3.24.tgz", - "integrity": "sha512-p2zuCJbY9OPSCEoZyF7NReYyyHUDQ/xYj+cc9PEtkUq0xtijxThMzEBgeIeD0XDb3RzdQqGNIgRiv47coFX3cA==", + "version": "1.3.25", + "resolved": "https://registry.npmjs.org/myst-transforms/-/myst-transforms-1.3.25.tgz", + "integrity": "sha512-wtUaIwcpqQtWomlfOmiQ+Z789MrGJJ8zPXCjtRr3B3+o4Kg3if7sEG2i0gZ8vmihdvp7EszPfWnS3sDHgnPjIw==", "license": "MIT", "dependencies": { "doi-utils": "^2.0.0", @@ -31601,7 +31633,7 @@ "myst-frontmatter": "^1.6.0", "myst-spec": "^0.0.5", "myst-spec-ext": "^1.6.0", - "myst-to-html": "1.5.5", + "myst-to-html": "1.5.6", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", "unified": "^10.0.0", @@ -32397,7 +32429,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -32427,7 +32458,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -32437,7 +32467,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.5", @@ -34535,6 +34564,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-merge-refs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-2.1.1.tgz", + "integrity": "sha512-jLQXJ/URln51zskhgppGJ2ub7b2WFKGq3cl3NYKtlHoTG+dN2q7EzWrn3hN3EgPsTMvpR9tpq5ijdp7YwFZkag==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -35044,7 +35082,6 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.6", @@ -35986,7 +36023,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -36011,7 +36047,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.6", @@ -36236,7 +36271,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -36342,7 +36376,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -37065,8 +37098,6 @@ "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", - "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -37122,7 +37153,6 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -37141,7 +37171,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -37156,7 +37185,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -38755,7 +38783,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -38770,7 +38797,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -38790,7 +38816,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -38811,7 +38836,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -38920,7 +38944,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -40432,7 +40455,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "license": "MIT", "dependencies": { "is-bigint": "^1.0.1", @@ -40967,9 +40989,10 @@ "version": "0.12.0", "license": "MIT", "dependencies": { - "myst-common": "^1.6.0", - "myst-config": "^1.5.0", - "myst-spec-ext": "^1.6.0", + "@myst-theme/search": "^0.0.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", + "myst-spec-ext": "^1.7.0", "nbtx": "^0.2.3", "unist-util-select": "^4.0.3" } @@ -41058,11 +41081,11 @@ "buffer": "^6.0.3", "classnames": "^2.5.1", "jupyterlab-plotly": "^5.24.0", - "myst-common": "^1.6.0", + "myst-common": "^1.7.0", "myst-config": "^1.6.0", - "myst-frontmatter": "^1.6.0", + "myst-frontmatter": "^1.7.0", "myst-spec": "^0.0.5", - "myst-spec-ext": "^1.6.0", + "myst-spec-ext": "^1.7.0", "myst-to-react": "^0.12.0", "nanoid": "^4.0.2", "nbtx": "^0.2.3", @@ -41114,24 +41137,24 @@ "@heroicons/react": "^2.0.18", "classnames": "^2.3.2", "js-yaml": "^4.1.0", - "myst-common": "^1.6.0", + "myst-common": "^1.7.0", "myst-config": "^1.5.0", - "myst-directives": "^1.5.5", - "myst-ext-card": "^1.0.7", + "myst-directives": "^1.5.6", + "myst-ext-card": "^1.0.8", "myst-ext-exercise": "^1.0.7", "myst-ext-grid": "^1.0.7", "myst-ext-proof": "^1.0.10", "myst-ext-tabs": "^1.0.7", - "myst-frontmatter": "^1.5.0", - "myst-parser": "^1.5.5", + "myst-frontmatter": "^1.7.0", + "myst-parser": "^1.5.6", "myst-spec": "^0.0.5", "myst-to-docx": "^1.0.11", - "myst-to-html": "^1.5.5", + "myst-to-html": "^1.5.6", "myst-to-jats": "^1.0.27", "myst-to-react": "^0.12.0", - "myst-to-tex": "^1.0.35", - "myst-to-typst": "^0.0.21", - "myst-transforms": "^1.3.24", + "myst-to-tex": "^1.0.37", + "myst-to-typst": "^0.0.22", + "myst-transforms": "^1.3.25", "unified": "^10.1.2", "unist-util-remove": "^4.0.0", "unist-util-visit": "^4.1.2", @@ -41179,8 +41202,8 @@ "@scienceicons/react": "^0.0.6", "buffer": "^6.0.3", "classnames": "^2.3.2", - "myst-common": "^1.6.0", - "myst-config": "^1.5.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", "myst-spec": "^0.0.5", "nanoid": "^4.0.2", "react-syntax-highlighter": "15.5.0", @@ -41248,13 +41271,27 @@ "peerDependencies": { "@types/react": "^16.8 || ^17.0 || ^18.0", "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "myst-common": "^1.6.0", - "myst-config": "^1.6.0", - "myst-frontmatter": "^1.6.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", + "myst-frontmatter": "^1.7.0", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "packages/search": { + "name": "@myst-theme/search", + "version": "0.0.0", + "license": "MIT" + }, + "packages/search-minisearch": { + "name": "@myst-theme/search-minisearch", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@myst-theme/search": "^0.0.0", + "minisearch": "^7.1.0" + } + }, "packages/site": { "name": "@myst-theme/site", "version": "0.12.0", @@ -41267,17 +41304,25 @@ "@myst-theme/frontmatter": "^0.12.0", "@myst-theme/jupyter": "^0.12.0", "@myst-theme/providers": "^0.12.0", + "@myst-theme/search": "^0.0.0", "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.3", + "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-roving-focus": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-visually-hidden": "^1.1.0", "classnames": "^2.3.2", "lodash.throttle": "^4.1.1", - "myst-common": "^1.6.0", - "myst-config": "^1.6.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", "myst-demo": "^0.12.0", - "myst-spec-ext": "^1.6.0", + "myst-spec-ext": "^1.7.0", "myst-to-react": "^0.12.0", "nbtx": "^0.2.3", "node-cache": "^5.1.2", "node-fetch": "^2.6.11", + "react-merge-refs": "^2.1.1", + "string.prototype.matchall": "^4.0.11", "thebe-react": "0.4.10", "unist-util-select": "^4.0.1" }, @@ -41332,8 +41377,8 @@ "@remix-run/node": "~1.17.0", "@remix-run/react": "~1.17.0", "@remix-run/vercel": "~1.17.0", - "myst-common": "^1.6.0", - "myst-config": "^1.5.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", "node-fetch": "^2.6.11", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -41365,8 +41410,8 @@ "@remix-run/node": "~1.17.0", "@remix-run/react": "~1.17.0", "@remix-run/vercel": "~1.17.0", - "myst-common": "^1.6.0", - "myst-config": "^1.6.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", "node-fetch": "^2.6.11", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/package.json b/package.json index b94d33f6c..abf02970f 100644 --- a/package.json +++ b/package.json @@ -51,5 +51,7 @@ "npm": ">=7.0.0", "node": ">=14.0.0" }, - "packageManager": "npm@8.10.0" + "packageManager": "npm@8.10.0", + "dependencies": { + } } diff --git a/packages/common/package.json b/packages/common/package.json index b1d48871c..b3b8cd10b 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -19,9 +19,9 @@ "build": "npm-run-all -l clean -p build:esm" }, "dependencies": { - "myst-common": "^1.6.0", - "myst-config": "^1.5.0", - "myst-spec-ext": "^1.6.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", + "myst-spec-ext": "^1.7.0", "nbtx": "^0.2.3", "unist-util-select": "^4.0.3" } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index db1ce01b5..ff6fa68ec 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -1,4 +1,4 @@ -import type { Dependency, SourceFileKind } from 'myst-spec-ext'; +import type { Dependency, SourceFileKind, MystSearchIndex } from 'myst-spec-ext'; import type { GenericParent, References } from 'myst-common'; import type { SiteAction, SiteExport, SiteManifest } from 'myst-config'; import type { PageFrontmatter } from 'myst-frontmatter'; @@ -25,6 +25,7 @@ export type Heading = { export type SiteLoader = { theme?: Theme; config?: SiteManifest; + searchIndex?: MystSearchIndex; CONTENT_CDN_PORT?: string | number; MODE?: 'app' | 'static'; BASE_URL?: string; diff --git a/packages/jupyter/package.json b/packages/jupyter/package.json index ea50cad6a..9af008359 100644 --- a/packages/jupyter/package.json +++ b/packages/jupyter/package.json @@ -30,11 +30,11 @@ "buffer": "^6.0.3", "classnames": "^2.5.1", "jupyterlab-plotly": "^5.24.0", - "myst-common": "^1.6.0", + "myst-common": "^1.7.0", "myst-config": "^1.6.0", - "myst-frontmatter": "^1.6.0", + "myst-frontmatter": "^1.7.0", "myst-spec": "^0.0.5", - "myst-spec-ext": "^1.6.0", + "myst-spec-ext": "^1.7.0", "myst-to-react": "^0.12.0", "nanoid": "^4.0.2", "nbtx": "^0.2.3", diff --git a/packages/myst-demo/package.json b/packages/myst-demo/package.json index fd41e8e79..f5d06de6c 100644 --- a/packages/myst-demo/package.json +++ b/packages/myst-demo/package.json @@ -23,24 +23,24 @@ "@heroicons/react": "^2.0.18", "classnames": "^2.3.2", "js-yaml": "^4.1.0", - "myst-common": "^1.6.0", + "myst-common": "^1.7.0", "myst-config": "^1.5.0", - "myst-directives": "^1.5.5", - "myst-ext-card": "^1.0.7", + "myst-directives": "^1.5.6", + "myst-ext-card": "^1.0.8", "myst-ext-exercise": "^1.0.7", "myst-ext-grid": "^1.0.7", "myst-ext-proof": "^1.0.10", "myst-ext-tabs": "^1.0.7", - "myst-frontmatter": "^1.5.0", - "myst-parser": "^1.5.5", + "myst-frontmatter": "^1.7.0", + "myst-parser": "^1.5.6", "myst-spec": "^0.0.5", "myst-to-docx": "^1.0.11", - "myst-to-html": "^1.5.5", + "myst-to-html": "^1.5.6", "myst-to-jats": "^1.0.27", "myst-to-react": "^0.12.0", - "myst-to-tex": "^1.0.35", - "myst-to-typst": "^0.0.21", - "myst-transforms": "^1.3.24", + "myst-to-tex": "^1.0.37", + "myst-to-typst": "^0.0.22", + "myst-transforms": "^1.3.25", "unified": "^10.1.2", "unist-util-remove": "^4.0.0", "unist-util-visit": "^4.1.2", diff --git a/packages/myst-to-react/package.json b/packages/myst-to-react/package.json index f3bdc50a6..c144d644a 100644 --- a/packages/myst-to-react/package.json +++ b/packages/myst-to-react/package.json @@ -26,8 +26,8 @@ "@radix-ui/react-hover-card": "^1.0.6", "buffer": "^6.0.3", "classnames": "^2.3.2", - "myst-common": "^1.6.0", - "myst-config": "^1.5.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", "myst-spec": "^0.0.5", "nanoid": "^4.0.2", "react-syntax-highlighter": "15.5.0", diff --git a/packages/providers/package.json b/packages/providers/package.json index 725587934..ad756cb93 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -27,9 +27,9 @@ "peerDependencies": { "@types/react": "^16.8 || ^17.0 || ^18.0", "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "myst-common": "^1.6.0", - "myst-config": "^1.6.0", - "myst-frontmatter": "^1.6.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", + "myst-frontmatter": "^1.7.0", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" }, diff --git a/packages/providers/src/index.tsx b/packages/providers/src/index.tsx index 8c68d21e6..2b5564e54 100644 --- a/packages/providers/src/index.tsx +++ b/packages/providers/src/index.tsx @@ -5,6 +5,7 @@ export * from './references.js'; export * from './baseurl.js'; export * from './ui.js'; export * from './site.js'; +export * from './search.js'; export * from './tabs.js'; export * from './xref.js'; export * from './renderers.js'; diff --git a/packages/providers/src/search.tsx b/packages/providers/src/search.tsx new file mode 100644 index 000000000..939ffdcc8 --- /dev/null +++ b/packages/providers/src/search.tsx @@ -0,0 +1,20 @@ +import React, { useContext } from 'react'; +import type { ISearch, MystSearchIndex } from '@myst-theme/search'; + +type SearchFactory = (index: MystSearchIndex) => ISearch; +const SearchFactoryContext = React.createContext(undefined); + +export function SearchFactoryProvider({ + factory, + children, +}: { + factory?: SearchFactory; + children: React.ReactNode; +}) { + return {children}; +} + +export function useSearchFactory() { + const config = useContext(SearchFactoryContext); + return config; +} diff --git a/packages/search-minisearch/.eslintrc.cjs b/packages/search-minisearch/.eslintrc.cjs new file mode 100644 index 000000000..f9a3f7ae7 --- /dev/null +++ b/packages/search-minisearch/.eslintrc.cjs @@ -0,0 +1,5 @@ +module.exports = { + root: true, + extends: ['curvenote'], + ignorePatterns: ['src/**/*.spec.ts'], +}; diff --git a/packages/search-minisearch/README.md b/packages/search-minisearch/README.md new file mode 100644 index 000000000..54edb14c9 --- /dev/null +++ b/packages/search-minisearch/README.md @@ -0,0 +1,3 @@ +# @myst-theme/search-minisearch + +A minisearch implementation for client-side searching in MyST sites. diff --git a/packages/search-minisearch/package.json b/packages/search-minisearch/package.json new file mode 100644 index 000000000..02fb299f1 --- /dev/null +++ b/packages/search-minisearch/package.json @@ -0,0 +1,25 @@ +{ + "name": "@myst-theme/search-minisearch", + "version": "0.0.0", + "type": "module", + "exports": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "license": "MIT", + "sideEffects": false, + "scripts": { + "clean": "rimraf dist", + "lint": "eslint \"src/**/*.ts*\" -c ./.eslintrc.cjs", + "lint:format": "prettier --check \"src/**/*.{ts,tsx,md}\"", + "test": "vitest run", + "test:watch": "vitest watch", + "build:esm": "tsc --project ./tsconfig.json --module Node16 --outDir dist --declaration", + "build": "npm-run-all -l clean -p build:esm" + }, + "dependencies": { + "@myst-theme/search": "^0.0.0", + "minisearch": "^7.1.0" + } +} diff --git a/packages/search-minisearch/src/index.ts b/packages/search-minisearch/src/index.ts new file mode 100644 index 000000000..ef35d81a7 --- /dev/null +++ b/packages/search-minisearch/src/index.ts @@ -0,0 +1,90 @@ +import MiniSearch, { type Options, type SearchResult as MiniSearchResult } from 'minisearch'; +import type { SearchRecord, SearchResult, ISearch } from '@myst-theme/search'; +import { extractField } from '@myst-theme/search'; + +export type ExtendedOptions = Options & Required>; + +export function prepareOptions(options: Options): ExtendedOptions { + return { + ...options, + tokenize: MiniSearch.getDefault('tokenize'), + processTerm: MiniSearch.getDefault('processTerm'), + extractField, + }; +} + +export type RawSearchResult = SearchRecord & MiniSearchResult; + +export function combineResults(results: Map>): SearchResult[] { + const [firstEntry, ...restEntries] = results.entries(); + if (firstEntry === undefined) { + return []; + } + // Transform from { terms, queryTerms, match} to [ { term, matches} ] + const firstRawResults = firstEntry[1]; + const initialValue = new Map( + Array.from(firstRawResults.entries(), ([id, rawResult]) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id: _, score, terms, queryTerms, match, ...rest } = rawResult; + return [ + id, + { + id, + queries: [ + { + term: queryTerms[0], + matches: match, + }, + ], + ...rest, + }, + ]; + }), + ); + // Reduce all entries with this transform + const mergedResults = restEntries.reduce( + (accumulator: Map, value: [string, Map]) => { + const nextAccumulator = new Map(); + + const rawResults = value[1]; + rawResults.forEach((rawResult, docID) => { + const existing = accumulator.get(docID); + if (existing == null) { + return; + } + const { queryTerms, match } = rawResult; + existing.queries.push({ + term: queryTerms[0], + matches: match, + }); + nextAccumulator.set(docID, existing); + }); + return nextAccumulator; + }, + initialValue, + ); + return Array.from(mergedResults.values()); +} + +export function createSearch(documents: SearchRecord[], options: Options): ISearch { + const extendedOptions = prepareOptions(options); + const search = new MiniSearch(extendedOptions); + search.addAll(documents.map((doc, index) => ({ ...doc, id: index }))); + return async (query: string) => { + // Implement executeQuery whilst retaining distinction between terms + // TODO: should we check for unique terms? + const terms = extendedOptions.tokenize(query).filter((token) => !!token); + if (!terms.length) { + return undefined; + } else { + const termResults = new Map( + terms.map((term) => [ + term, + new Map(search.search(term).map((doc) => [doc.id, doc as RawSearchResult])), + ]), + ); + + return combineResults(termResults); + } + }; +} diff --git a/packages/search-minisearch/tsconfig.json b/packages/search-minisearch/tsconfig.json new file mode 100644 index 000000000..ffd179e41 --- /dev/null +++ b/packages/search-minisearch/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig/esm.json", + "include": ["."], + "exclude": ["dist", "node_modules", "src/**/*.spec.ts", "tests"] +} diff --git a/packages/search/.eslintrc.cjs b/packages/search/.eslintrc.cjs new file mode 100644 index 000000000..f9a3f7ae7 --- /dev/null +++ b/packages/search/.eslintrc.cjs @@ -0,0 +1,5 @@ +module.exports = { + root: true, + extends: ['curvenote'], + ignorePatterns: ['src/**/*.spec.ts'], +}; diff --git a/packages/search/README.md b/packages/search/README.md new file mode 100644 index 000000000..e28bb3fb9 --- /dev/null +++ b/packages/search/README.md @@ -0,0 +1,3 @@ +# @myst-theme/search + +An implementation and spec for client-side searching in MyST sites. diff --git a/packages/search/package.json b/packages/search/package.json new file mode 100644 index 000000000..20f7ccf48 --- /dev/null +++ b/packages/search/package.json @@ -0,0 +1,22 @@ +{ + "name": "@myst-theme/search", + "version": "0.0.0", + "type": "module", + "exports": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "license": "MIT", + "sideEffects": false, + "scripts": { + "clean": "rimraf dist", + "lint": "eslint \"src/**/*.ts*\" -c ./.eslintrc.cjs", + "lint:format": "prettier --check \"src/**/*.{ts,tsx,md}\"", + "test": "vitest run", + "test:watch": "vitest watch", + "build:esm": "tsc --project ./tsconfig.json --module Node16 --outDir dist --declaration", + "build": "npm-run-all -l clean -p build:esm" + }, + "dependencies": {} +} diff --git a/packages/search/src/index.ts b/packages/search/src/index.ts new file mode 100644 index 000000000..773f6599d --- /dev/null +++ b/packages/search/src/index.ts @@ -0,0 +1,3 @@ +export * from './types.js'; +export * from './rank.js'; +export * from './search.js'; diff --git a/packages/search/src/rank.ts b/packages/search/src/rank.ts new file mode 100644 index 000000000..ca696e37d --- /dev/null +++ b/packages/search/src/rank.ts @@ -0,0 +1,301 @@ +import type { Query, SearchResult, RankedSearchResult, AttributeType } from './types.js'; +import { SEARCH_ATTRIBUTES_ORDERED } from './types.js'; +import { extractField, SPACE_OR_PUNCTUATION } from './search.js'; + +export const POSITIONAL_SEARCH_ATTRIBUTES: AttributeType[] = ['content'] as const; + +// Weights that prioritise headings over content +const TYPE_WEIGHTS = new Map([ + ['lvl1', 90], + ['lvl2', 80], + ['lvl3', 70], + ['lvl4', 60], + ['lvl5', 50], + ['lvl6', 40], + ['content', 0], +]); + +/* + * Generic `cmp` helper function + * + * @param left - left value + * @param right - right value + */ +function cmp(left: number, right: number): number { + if (left < right) { + return -1; + } else if (left > right) { + return +1; + } else { + return 0; + } +} + +/** + * Build a RegExp that matches a single TOKEN bounded by SPACE_OR_PUNCTUATION, or string boundaries + * + * @param text - text to match, e.g. ` foo `, ` foo bar `, `foo bar` + */ +function buildRegExpToken(token: string): RegExp { + return new RegExp( + `(?:(?:${SPACE_OR_PUNCTUATION.source})|^)${token}(?:(?:${SPACE_OR_PUNCTUATION.source})|$)`, + `${SPACE_OR_PUNCTUATION.flags}i`, + ); +} + +/** + * Compute the proximity between two queries, bounded by a limit + * + * @param record - parent search record + * @param left - first query + * @param right - second query + * @param bound - upper limit on computed proximity + */ +function queryPairProximity( + record: SearchResult, + left: Query, + right: Query, + bound: number, +): number { + // TODO: this is highly-nested, and probably slow + // it should be re-written for performance + let bestProximity = bound; + + // For each term in the left query + for (const [leftTerm, leftFields] of Object.entries(left.matches)) { + const leftPattern = buildRegExpToken(leftTerm); + + // For each field matched with this left term + for (const leftField of leftFields) { + // Pull out the (left) field content + const content = extractField(record, leftField); + + // For each term in the right query + for (const [rightTerm, rightFields] of Object.entries(right.matches)) { + const rightPattern = buildRegExpToken(rightTerm); + // For each field matched with this right term + for (const rightField of rightFields) { + // Terms matching different fields can never be better than the bound + if (leftField !== rightField) { + continue; + } + + // Find all of the matches in the content for each pattern + const leftMatches = content.matchAll(leftPattern); + const rightMatches = content.matchAll(rightPattern); + + // Iterate over match pairs + for (const leftMatch of leftMatches) { + for (const rightMatch of rightMatches) { + // Find the ordered (start, stop) pairs for these two matches + const [start, stop] = + leftMatch.index < rightMatch.index + ? [leftMatch.index, rightMatch.index] + : [rightMatch.index, leftMatch.index]; + + // Identify how many token separators there are in this range + const numSeparators = Array.from( + content.slice(start, stop).matchAll(SPACE_OR_PUNCTUATION), + ).length; + + // Fast-path, can never beat 1! + if (numSeparators === 1) { + return 1; + } + + // Does this result improve our current proximity? + if (numSeparators < bestProximity) { + bestProximity = numSeparators; + } + } + } + } + } + } + } + return bestProximity; +} + +/** + * Compute the associative pair-wise proximity of a search result + * + * @param result - search result + * @param bound - upper bound on final proximity + */ +function wordsProximity(result: SearchResult, bound: number) { + const { queries } = result; + let proximity = 0; + for (let i = 0; i < queries.length - 1; i++) { + const left = queries[i]; + const right = queries[i + 1]; + + proximity += queryPairProximity(result, left, right, bound); + } + return Math.min(proximity, bound); +} + +/** + * Identify the best-matched attribute and the match position + * + * @param result - search result + */ +function matchedAttributePosition(result: SearchResult): { + attribute: AttributeType; + position: number | undefined; +} { + // Build mapping from fields to terms matching that field + // i.e. invert and flatten `result.queries[...].matches` + const fieldToTerms = new Map(); + result.queries.forEach((query) => { + Object.entries(query.matches).forEach(([term, fields]) => { + fields.forEach((field) => { + let terms = fieldToTerms.get(field); + if (!terms) { + terms = []; + fieldToTerms.set(field, terms); + } + terms.push(term); + }); + }); + }); + + // Find first field that we matched + const attribute = SEARCH_ATTRIBUTES_ORDERED.find((field) => fieldToTerms.has(field))!; + + let position; + // If this field is positional, find the start of the text match + if (POSITIONAL_SEARCH_ATTRIBUTES.includes(attribute)) { + // Find the terms that this field matches + const attributeTerms = fieldToTerms.get(attribute)!; + // Extract the field value + const value = extractField(result, attribute); + // Match each term against the field value, and extract the match position + const matchPositions = attributeTerms + .flatMap( + (term) => + Array.from(value.matchAll(buildRegExpToken(term))) as { + index: number; + }[], + ) + .map((match) => match.index); + // Find the smallest (earliest) match position + position = Math.min(...matchPositions); + } + // Otherwise, we don't care about the position + else { + position = undefined; + } + + return { attribute, position }; +} +/** + * Determine how many terms matched the corpus exactly + * + * @param result - search result + */ +function matchedExactWords(result: SearchResult) { + const allMatches = result.queries.flatMap( + // For each query (foo bar baz -> foo, then bar, then baz) + (query) => + Object.entries(query.matches) + .flatMap( + // For each (match, matched fields) pair in the query matches + ([match, fields]) => { + const pattern = buildRegExpToken(match); + return fields.flatMap( + // For each matched field + (field) => { + // Retrieve corpus and test for pattern + const value = extractField(result, field); + return Array.from(value.matchAll(pattern)).map((m) => (m ? query.term : undefined)); + }, + ); + }, + ) + .filter((item) => item), + ); + const uniqueMatches = new Set(allMatches); + return uniqueMatches.size; +} + +/** + * Determine the number of fuzzy matches in a search result + * + * @param result - search result + */ +function numberOfTypos(result: SearchResult): number { + return result.queries + .map((query) => { + const typoTerms = Object.keys(query.matches).filter((match) => match !== query.term); + return typoTerms.length; + }) + .reduce((sum, value) => sum + value); +} + +/** + * Rank a search result using Algolia-derived metrics + * + * @param result - search result + */ +function rankSearchResult(result: SearchResult): RankedSearchResult { + return { + ...result, + ranking: { + typos: numberOfTypos(result), + ...matchedAttributePosition(result), + proximity: wordsProximity(result, 8), // TODO + exact: matchedExactWords(result), + level: TYPE_WEIGHTS.get(result.type)!, + appearance: result.position, + }, + }; +} + +/** + * Compare ranked search results to prioritise higher rankings + * + * @param left - ranked search result + * @param right - ranked search result + */ +function cmpRankedSearchResults(left: RankedSearchResult, right: RankedSearchResult) { + const leftRank = left.ranking; + const rightRank = right.ranking; + + if (leftRank.typos !== rightRank.typos) { + return cmp(leftRank.typos, rightRank.typos); + } + if (leftRank.attribute !== rightRank.attribute) { + const i = SEARCH_ATTRIBUTES_ORDERED.findIndex((item) => item === leftRank.attribute); + const j = SEARCH_ATTRIBUTES_ORDERED.findIndex((item) => item === rightRank.attribute); + + return cmp(i, j); + } + if ( + leftRank.position != null && + rightRank.position != null && + leftRank.position !== rightRank.position + ) { + return cmp(leftRank.position, rightRank.position); + } + if (leftRank.proximity !== rightRank.proximity) { + return cmp(leftRank.proximity, rightRank.proximity); + } + if (leftRank.exact !== rightRank.exact) { + return cmp(rightRank.exact, leftRank.exact); + } + if (leftRank.level !== rightRank.level) { + return cmp(rightRank.level, leftRank.level); + } + if (leftRank.appearance !== rightRank.appearance) { + return cmp(leftRank.appearance, rightRank.appearance); + } + + return 0; +} + +/** + * Rank and then filter raw search results + */ +export function rankResults(results: SearchResult[]): RankedSearchResult[] { + return results.map(rankSearchResult).sort(cmpRankedSearchResults); +} diff --git a/packages/search/src/search.ts b/packages/search/src/search.ts new file mode 100644 index 000000000..36a322344 --- /dev/null +++ b/packages/search/src/search.ts @@ -0,0 +1,7 @@ +export const SPACE_OR_PUNCTUATION = /[\n\r\p{Z}\p{P}]+/gu; +export function extractField(document: Record, fieldName: string) { + // Access nested fields + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return fieldName.split('.').reduce((doc, key) => doc && doc[key], document); +} diff --git a/packages/search/src/types.ts b/packages/search/src/types.ts new file mode 100644 index 000000000..db14bd9aa --- /dev/null +++ b/packages/search/src/types.ts @@ -0,0 +1,47 @@ +import type { SearchRecord, DocumentHierarchy } from 'myst-spec-ext'; +export type { MystSearchIndex, SearchRecord } from 'myst-spec-ext'; + +export type HeadingLevel = keyof DocumentHierarchy; + +export type Query = { + term: string; // Raw search query term + matches: Record; // Match results (match token -> fields[]) +}; + +export type SearchResult = SearchRecord & { + id: string | number; + queries: Query[]; +}; + +export interface ISearch { + (query: string): Promise; +} + +/// Search ranking +export const SEARCH_ATTRIBUTES_ORDERED = [ + 'hierarchy.lvl1', + 'hierarchy.lvl2', + 'hierarchy.lvl3', + 'hierarchy.lvl4', + 'hierarchy.lvl5', + 'hierarchy.lvl6', + 'content', +] as const; + +export type AttributeType = (typeof SEARCH_ATTRIBUTES_ORDERED)[number]; + +/** + * Type describing a seach result that has ranking + */ +export type RankedSearchResult = SearchResult & { + ranking: { + // words: number; (Aloglia supports dropping words, we don't) + typos: number; + attribute: AttributeType; + position?: number; + proximity: number; + exact: number; + level: number; + appearance: number; + }; +}; diff --git a/packages/search/tsconfig.json b/packages/search/tsconfig.json new file mode 100644 index 000000000..ffd179e41 --- /dev/null +++ b/packages/search/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig/esm.json", + "include": ["."], + "exclude": ["dist", "node_modules", "src/**/*.spec.ts", "tests"] +} diff --git a/packages/site/package.json b/packages/site/package.json index 2154ae1c6..b00fb68aa 100644 --- a/packages/site/package.json +++ b/packages/site/package.json @@ -26,17 +26,25 @@ "@myst-theme/frontmatter": "^0.12.0", "@myst-theme/jupyter": "^0.12.0", "@myst-theme/providers": "^0.12.0", + "@myst-theme/search": "^0.0.0", "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.3", + "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-roving-focus": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-visually-hidden": "^1.1.0", "classnames": "^2.3.2", "lodash.throttle": "^4.1.1", - "myst-common": "^1.6.0", - "myst-config": "^1.6.0", + "myst-common": "^1.7.0", + "myst-config": "^1.7.0", "myst-demo": "^0.12.0", - "myst-spec-ext": "^1.6.0", + "myst-spec-ext": "^1.7.0", "myst-to-react": "^0.12.0", "nbtx": "^0.2.3", "node-cache": "^5.1.2", "node-fetch": "^2.6.11", + "react-merge-refs": "^2.1.1", + "string.prototype.matchall": "^4.0.11", "thebe-react": "0.4.10", "unist-util-select": "^4.0.1" }, diff --git a/packages/site/src/components/Navigation/Search.tsx b/packages/site/src/components/Navigation/Search.tsx new file mode 100644 index 000000000..aa713f300 --- /dev/null +++ b/packages/site/src/components/Navigation/Search.tsx @@ -0,0 +1,630 @@ +import { useEffect, useState, useMemo, useCallback, useRef, forwardRef } from 'react'; +import type { KeyboardEventHandler, Dispatch, SetStateAction, FormEvent, MouseEvent } from 'react'; +import { useNavigate, useFetcher } from '@remix-run/react'; +import { + ArrowTurnDownLeftIcon, + MagnifyingGlassIcon, + HashtagIcon, + Bars3BottomLeftIcon, + XCircleIcon, +} from '@heroicons/react/24/solid'; +import { DocumentIcon } from '@heroicons/react/24/outline'; +import classNames from 'classnames'; +import * as Dialog from '@radix-ui/react-dialog'; +import * as VisuallyHidden from '@radix-ui/react-visually-hidden'; +import type { RankedSearchResult, HeadingLevel, MystSearchIndex } from '@myst-theme/search'; +import { SPACE_OR_PUNCTUATION, rankResults } from '@myst-theme/search'; +import { + useThemeTop, + useSearchFactory, + useLinkProvider, + withBaseurl, + useBaseurl, +} from '@myst-theme/providers'; + +/** + * Shim for string.matchAll + * + * @param text - text to repeatedly match with pattern + * @param pattern - global pattern + */ +function matchAll(text: string, pattern: RegExp) { + const matches = []; + let match; + while ((match = pattern.exec(text))) { + matches.push(match); + } + return matches; +} + +/** + * Highlight a text string with an array of match words + * + * @param text - text to highlight + * @param result - search result to use for highlighting + * @param limit - limit to the number of tokens after first match + */ +function MarkedText({ text, matches, limit }: { text: string; matches: string[]; limit?: number }) { + // Split by delimeter, but _keep_ delimeter! + const splits = matchAll(text, SPACE_OR_PUNCTUATION); + const tokens: string[] = []; + let start = 0; + for (const splitMatch of splits) { + tokens.push(text.slice(start, splitMatch.index)); + tokens.push(splitMatch[0]); + start = splitMatch.index + splitMatch[0].length; + } + tokens.push(text.slice(start)); + + // Build RegExp matching all highlight matches + const allTerms = matches.join('|'); + const pattern = new RegExp(`^(${allTerms})`, 'i'); // Match prefix and total pattern, case-insensitively + const renderToken = (token: string) => + pattern.test(token) ? ( + <> + + {token} + + + ) : ( + token + ); + let firstIndex: number; + let lastIndex: number; + const hasLimit = limit !== undefined; + + if (!hasLimit) { + firstIndex = 0; + lastIndex = tokens.length; + } else { + firstIndex = tokens.findIndex((token) => pattern.test(token)); + lastIndex = firstIndex + limit; + } + + if (tokens.length === 0) { + return <>{...tokens}; + } else { + const firstRenderer = renderToken(tokens[firstIndex]); + const remainingTokens = tokens.slice(firstIndex + 1, lastIndex); + const remainingRenderers = remainingTokens.map((token) => renderToken(token)); + + return ( + <> + {hasLimit && '... '} + {firstRenderer} + {...remainingRenderers} + {hasLimit && ' ...'} + + ); + } +} + +/** + * Return true if the client is a Mac, false if not, or undefined if running on the server + */ +function isMac(): boolean | undefined { + if (typeof window === 'undefined') { + return undefined; + } else { + const hostIsMac = /mac/i.test( + (window.navigator as any).userAgentData?.platform ?? window.navigator.userAgent, + ); + return hostIsMac; + } +} + +// Blocking code to ensure that the pre-hydration state on the client matches the post-hydration state +// The server with SSR cannot determine the client platform +const clientThemeCode = ` +;(() => { +const script = document.currentScript; +const root = script.parentElement; + +const isMac = /mac/i.test( + window.navigator.userAgentData?.platform ?? window.navigator.userAgent, + ); +root.querySelectorAll(".hide-mac").forEach(node => {node.classList.add(isMac ? "hidden" : "block")}); +root.querySelectorAll(".show-mac").forEach(node => {node.classList.add(!isMac ? "hidden" : "block")}); +})()`; + +function BlockingPlatformLoader() { + return