From ead7ee2f5031691f5e093745d6664f13f932d8d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:03:33 -0800 Subject: [PATCH 01/45] chore(deps-dev): bump prettier from 3.1.1 to 3.2.4 (#768) * chore(deps-dev): bump prettier from 3.1.1 to 3.2.4 Bumps [prettier](https://github.com/prettier/prettier) from 3.1.1 to 3.2.4. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.1.1...3.2.4) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * format --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacky Zhao --- package-lock.json | 8 ++++---- package.json | 2 +- tsconfig.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22dc37f1f..62b3693d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ "@types/ws": "^8.5.10", "@types/yargs": "^17.0.32", "esbuild": "^0.19.9", - "prettier": "^3.1.1", + "prettier": "^3.2.4", "tsx": "^4.7.0", "typescript": "^5.3.3" }, @@ -4470,9 +4470,9 @@ } }, "node_modules/prettier": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", - "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/package.json b/package.json index ff315a0d0..22a7301af 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@types/ws": "^8.5.10", "@types/yargs": "^17.0.32", "esbuild": "^0.19.9", - "prettier": "^3.1.1", + "prettier": "^3.2.4", "tsx": "^4.7.0", "typescript": "^5.3.3" } diff --git a/tsconfig.json b/tsconfig.json index 784ab231b..306204b5d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,8 +13,8 @@ "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "jsx": "react-jsx", - "jsxImportSource": "preact" + "jsxImportSource": "preact", }, "include": ["**/*.ts", "**/*.tsx", "./package.json"], - "exclude": ["build/**/*.d.ts"] + "exclude": ["build/**/*.d.ts"], } From f3c7211bf0a8a07565615b4373239bafdc01316e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:03:54 -0800 Subject: [PATCH 02/45] chore(deps-dev): bump @types/node from 20.3.3 to 20.11.11 (#767) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.3.3 to 20.11.11. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 19 ++++++++++++++----- package.json | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62b3693d3..b84577fa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.3", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.1.2", + "@types/node": "^20.11.11", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.10", @@ -1088,10 +1088,13 @@ } }, "node_modules/@types/node": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", - "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", - "dev": true + "version": "20.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.11.tgz", + "integrity": "sha512-PlJCXfb57Jrman0H1BxO2+Q7qwih2Mwk7T6Gvixj+SK4mqs4RWOGMMoP6p/LFa3UrP2CZOO6ai6otd7J/TB6Ug==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/pretty-time": { "version": "1.1.5", @@ -5674,6 +5677,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unherit": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz", diff --git a/package.json b/package.json index 22a7301af..a3a9c0f8a 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.3", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.1.2", + "@types/node": "^20.11.11", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.10", From e21d50c711aae2e55d1296ec7c1d98fb7045ccf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:04:53 -0800 Subject: [PATCH 03/45] chore(deps): bump @floating-ui/dom from 1.5.3 to 1.6.1 (#766) Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.5.3 to 1.6.1. - [Release notes](https://github.com/floating-ui/floating-ui/releases) - [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md) - [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.6.1/packages/dom) --- updated-dependencies: - dependency-name: "@floating-ui/dom" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 26 +++++++++++++------------- package.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index b84577fa3..c8722eedd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", - "@floating-ui/dom": "^1.5.3", + "@floating-ui/dom": "^1.6.1", "@napi-rs/simple-git": "0.1.14", "async-mutex": "^0.4.0", "chalk": "^5.3.0", @@ -478,26 +478,26 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz", - "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "dependencies": { - "@floating-ui/utils": "^0.1.3" + "@floating-ui/utils": "^0.2.1" } }, "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz", + "integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==", "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.1" } }, "node_modules/@floating-ui/utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", - "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", diff --git a/package.json b/package.json index a3a9c0f8a..05bd3476b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@clack/prompts": "^0.7.0", - "@floating-ui/dom": "^1.5.3", + "@floating-ui/dom": "^1.6.1", "@napi-rs/simple-git": "0.1.14", "async-mutex": "^0.4.0", "chalk": "^5.3.0", From 90043cd58272f454b2513c338804ba0be5a36b10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:05:17 -0800 Subject: [PATCH 04/45] chore(deps): bump lightningcss from 1.22.1 to 1.23.0 (#765) Bumps [lightningcss](https://github.com/parcel-bundler/lightningcss) from 1.22.1 to 1.23.0. - [Release notes](https://github.com/parcel-bundler/lightningcss/releases) - [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.22.1...v1.23.0) --- updated-dependencies: - dependency-name: lightningcss dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 80 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index c8722eedd..1bf6e9431 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "hast-util-to-string": "^3.0.0", "is-absolute-url": "^4.0.1", "js-yaml": "^4.1.0", - "lightningcss": "^1.22.1", + "lightningcss": "^1.23.0", "mdast-util-find-and-replace": "^3.0.1", "mdast-util-to-hast": "^13.0.2", "mdast-util-to-string": "^4.0.0", @@ -3030,9 +3030,9 @@ } }, "node_modules/lightningcss": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.22.1.tgz", - "integrity": "sha512-Fy45PhibiNXkm0cK5FJCbfO8Y6jUpD/YcHf/BtuI+jvYYqSXKF4muk61jjE8YxCR9y+hDYIWSzHTc+bwhDE6rQ==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz", + "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==", "dependencies": { "detect-libc": "^1.0.3" }, @@ -3044,21 +3044,21 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.22.1", - "lightningcss-darwin-x64": "1.22.1", - "lightningcss-freebsd-x64": "1.22.1", - "lightningcss-linux-arm-gnueabihf": "1.22.1", - "lightningcss-linux-arm64-gnu": "1.22.1", - "lightningcss-linux-arm64-musl": "1.22.1", - "lightningcss-linux-x64-gnu": "1.22.1", - "lightningcss-linux-x64-musl": "1.22.1", - "lightningcss-win32-x64-msvc": "1.22.1" + "lightningcss-darwin-arm64": "1.23.0", + "lightningcss-darwin-x64": "1.23.0", + "lightningcss-freebsd-x64": "1.23.0", + "lightningcss-linux-arm-gnueabihf": "1.23.0", + "lightningcss-linux-arm64-gnu": "1.23.0", + "lightningcss-linux-arm64-musl": "1.23.0", + "lightningcss-linux-x64-gnu": "1.23.0", + "lightningcss-linux-x64-musl": "1.23.0", + "lightningcss-win32-x64-msvc": "1.23.0" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.22.1.tgz", - "integrity": "sha512-ldvElu+R0QimNTjsKpaZkUv3zf+uefzLy/R1R19jtgOfSRM+zjUCUgDhfEDRmVqJtMwYsdhMI2aJtJChPC6Osg==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz", + "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==", "cpu": [ "arm64" ], @@ -3075,9 +3075,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.22.1.tgz", - "integrity": "sha512-5p2rnlVTv6Gpw4PlTLq925nTVh+HFh4MpegX8dPDYJae+NFVjQ67gY7O6iHIzQjLipDiYejFF0yHrhjU3XgLBQ==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz", + "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==", "cpu": [ "x64" ], @@ -3094,9 +3094,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.22.1.tgz", - "integrity": "sha512-1FaBtcFrZqB2hkFbAxY//Pnp8koThvyB6AhjbdVqKD4/pu13Rl91fKt2N9qyeQPUt3xy7ORUvSO+dPk3J6EjXg==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.23.0.tgz", + "integrity": "sha512-xhnhf0bWPuZxcqknvMDRFFo2TInrmQRWZGB0f6YoAsZX8Y+epfjHeeOIGCfAmgF0DgZxHwYc8mIR5tQU9/+ROA==", "cpu": [ "x64" ], @@ -3113,9 +3113,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.22.1.tgz", - "integrity": "sha512-6rub98tYGfE5I5j0BP8t/2d4BZyu1S7Iz9vUkm0H26snAFHYxLfj3RbQn0xHHIePSetjLnhcg3QlfwUAkD/FYg==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz", + "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==", "cpu": [ "arm" ], @@ -3132,9 +3132,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.22.1.tgz", - "integrity": "sha512-nYO5qGtb/1kkTZu3FeTiM+2B2TAb7m2DkLCTgQIs2bk2o9aEs7I96fwySKcoHWQAiQDGR9sMux9vkV4KQXqPaQ==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz", + "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==", "cpu": [ "arm64" ], @@ -3151,9 +3151,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.22.1.tgz", - "integrity": "sha512-MCV6RuRpzXbunvzwY644iz8cw4oQxvW7oer9xPkdadYqlEyiJJ6wl7FyJOH7Q6ZYH4yjGAUCvxDBxPbnDu9ZVg==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz", + "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==", "cpu": [ "arm64" ], @@ -3170,9 +3170,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.22.1.tgz", - "integrity": "sha512-RjNgpdM20VUXgV7us/VmlO3Vn2ZRiDnc3/bUxCVvySZWPiVPprpqW/QDWuzkGa+NCUf6saAM5CLsZLSxncXJwg==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz", + "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==", "cpu": [ "x64" ], @@ -3189,9 +3189,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.22.1.tgz", - "integrity": "sha512-ZgO4C7Rd6Hv/5MnyY2KxOYmIlzk4rplVolDt3NbkNR8DndnyX0Q5IR4acJWNTBICQ21j3zySzKbcJaiJpk/4YA==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz", + "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==", "cpu": [ "x64" ], @@ -3208,9 +3208,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.22.1.tgz", - "integrity": "sha512-4pozV4eyD0MDET41ZLHAeBo+H04Nm2UEYIk5w/ts40231dRFV7E0cjwbnZvSoc1DXFgecAhiC0L16ruv/ZDCpg==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz", + "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 05bd3476b..c779b119f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "hast-util-to-string": "^3.0.0", "is-absolute-url": "^4.0.1", "js-yaml": "^4.1.0", - "lightningcss": "^1.22.1", + "lightningcss": "^1.23.0", "mdast-util-find-and-replace": "^3.0.1", "mdast-util-to-hast": "^13.0.2", "mdast-util-to-string": "^4.0.0", From 072ee6412780acf001d040dad652f911e2f676c9 Mon Sep 17 00:00:00 2001 From: LUCASTUCIOUS Date: Wed, 31 Jan 2024 07:10:13 +0100 Subject: [PATCH 05/45] feat: Feature/custom callout icon (#727) * Add icons as masks To handle a simple way to add custom icons, i made it pure css. Icon are now a mask for the callout-icon div, so they always follow the --color form the current callout. Now to add a custom icon, you simply add ```css .callout { &[data-callout="custom"] { --color: #customcolor; --border: #custombordercolor; --bg: #custombg; --callout-icon: url('data:image/svg+xml; utf8, '); } ``` to custom.scss * remove now unused code * Make callouts an enum * docs: update instructions for custom callouts * Prettier & run format * dynamic matching For maintainability, make dynamic mathching. If we or Obsidian want to support more callouts, we simply add it to the enum * callout mapping const Getting ride of the enum entierly as it's not worth here? * fix callout icon styling * Add forgotten icons * Rebase * harmonize callout icon and fold icon * fix docs + prettier * Update docs/features/callouts.md Co-authored-by: Jacky Zhao * Update quartz/plugins/transformers/ofm.ts Co-authored-by: Jacky Zhao * Suggestions fix * remove unecessary rules * comment is always nice * Update docs/features/callouts.md --------- Co-authored-by: Jacky Zhao --- docs/features/callouts.md | 19 +++++++- quartz/plugins/transformers/ofm.ts | 52 ++++---------------- quartz/styles/callouts.scss | 76 +++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 58 deletions(-) diff --git a/docs/features/callouts.md b/docs/features/callouts.md index 27de687eb..64a51718e 100644 --- a/docs/features/callouts.md +++ b/docs/features/callouts.md @@ -24,7 +24,24 @@ See [documentation on supported types and syntax here](https://help.obsidian.md ## Customization - Disable callouts: simply pass `callouts: false` to the plugin: `Plugin.ObsidianFlavoredMarkdown({ callouts: false })` -- Editing icons: `quartz/plugins/transformers/ofm.ts` +- Editing icons: `quartz/styles/callouts.scss` + +### Add custom callouts + +By default, custom callouts are handled by applying the `note` style. To make fancy ones, you have to add these lines to `custom.scss`. + +```scss title="quartz/styles/custom.scss" +.callout { + &[data-callout="custom"] { + --color: #customcolor; + --border: #custombordercolor; + --bg: #custombg; + --callout-icon: url('data:image/svg+xml; utf8, '); //SVG icon code +} +``` + +> [!warning] +> Don't forget to ensure that the SVG is URL encoded before putting it in the CSS. You can use tools like [this one](https://yoksel.github.io/url-encoder/) to help you do that. ## Showcase diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index fc98bb2a3..8c405bf17 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -44,39 +44,7 @@ const defaultOptions: Options = { enableVideoEmbed: true, } -const icons = { - infoIcon: ``, - pencilIcon: ``, - clipboardListIcon: ``, - checkCircleIcon: ``, - flameIcon: ``, - checkIcon: ``, - helpCircleIcon: ``, - alertTriangleIcon: ``, - xIcon: ``, - zapIcon: ``, - bugIcon: ``, - listIcon: ``, - quoteIcon: ``, -} - -const callouts = { - note: icons.pencilIcon, - abstract: icons.clipboardListIcon, - info: icons.infoIcon, - todo: icons.checkCircleIcon, - tip: icons.flameIcon, - success: icons.checkIcon, - question: icons.helpCircleIcon, - warning: icons.alertTriangleIcon, - failure: icons.xIcon, - danger: icons.zapIcon, - bug: icons.bugIcon, - example: icons.listIcon, - quote: icons.quoteIcon, -} - -const calloutMapping: Record = { +const calloutMapping = { note: "note", abstract: "abstract", summary: "abstract", @@ -104,12 +72,12 @@ const calloutMapping: Record = { example: "example", quote: "quote", cite: "quote", -} +} as const -function canonicalizeCallout(calloutName: string): keyof typeof callouts { - let callout = calloutName.toLowerCase() as keyof typeof calloutMapping +function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping { + const normalizedCallout = calloutName.toLowerCase() as keyof typeof calloutMapping // if callout is not recognized, make it a custom one - return calloutMapping[callout] ?? calloutName + return calloutMapping[normalizedCallout] ?? calloutName } export const externalLinkRegex = /^https?:\/\//i @@ -411,9 +379,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin const match = firstLine.match(calloutRegex) if (match && match.input) { const [calloutDirective, typeString, collapseChar] = match - const calloutType = canonicalizeCallout( - typeString.toLowerCase() as keyof typeof calloutMapping, - ) + const calloutType = canonicalizeCallout(typeString.toLowerCase()) const collapse = collapseChar === "+" || collapseChar === "-" const defaultState = collapseChar === "-" ? "collapsed" : "expanded" const titleContent = @@ -427,16 +393,14 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin } const title = mdastToHtml(titleNode) - const toggleIcon = ` - - ` + const toggleIcon = `
` const titleHtml: Html = { type: "html", value: `
-
${callouts[calloutType] ?? callouts.note}
+
${title}
${collapse ? toggleIcon : ""}
`, diff --git a/quartz/styles/callouts.scss b/quartz/styles/callouts.scss index 34d3a4560..ee62e39c5 100644 --- a/quartz/styles/callouts.scss +++ b/quartz/styles/callouts.scss @@ -13,16 +13,33 @@ margin-top: 0; } + --callout-icon-note: url('data:image/svg+xml; utf8, '); + --callout-icon-abstract: url('data:image/svg+xml; utf8, '); + --callout-icon-info: url('data:image/svg+xml; utf8, '); + --callout-icon-todo: url('data:image/svg+xml; utf8, '); + --callout-icon-tip: url('data:image/svg+xml; utf8, '); + --callout-icon-success: url('data:image/svg+xml; utf8, '); + --callout-icon-question: url('data:image/svg+xml; utf8, '); + --callout-icon-warning: url('data:image/svg+xml; utf8, '); + --callout-icon-failure: url('data:image/svg+xml; utf8, '); + --callout-icon-danger: url('data:image/svg+xml; utf8, '); + --callout-icon-bug: url('data:image/svg+xml; utf8, '); + --callout-icon-example: url('data:image/svg+xml; utf8, '); + --callout-icon-quote: url('data:image/svg+xml; utf8, '); + --callout-icon-fold: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"%3E%3Cpolyline points="6 9 12 15 18 9"%3E%3C/polyline%3E%3C/svg%3E'); + &[data-callout] { --color: #448aff; --border: #448aff44; --bg: #448aff10; + --callout-icon: var(--callout-icon-note); } &[data-callout="abstract"] { --color: #00b0ff; --border: #00b0ff44; --bg: #00b0ff10; + --callout-icon: var(--callout-icon-abstract); } &[data-callout="info"], @@ -30,30 +47,39 @@ --color: #00b8d4; --border: #00b8d444; --bg: #00b8d410; + --callout-icon: var(--callout-icon-info); + } + + &[data-callout="todo"] { + --callout-icon: var(--callout-icon-todo); } &[data-callout="tip"] { --color: #00bfa5; --border: #00bfa544; --bg: #00bfa510; + --callout-icon: var(--callout-icon-tip); } &[data-callout="success"] { --color: #09ad7a; --border: #09ad7144; --bg: #09ad7110; + --callout-icon: var(--callout-icon-success); } &[data-callout="question"] { --color: #dba642; --border: #dba64244; --bg: #dba64210; + --callout-icon: var(--callout-icon-question); } &[data-callout="warning"] { --color: #db8942; --border: #db894244; --bg: #db894210; + --callout-icon: var(--callout-icon-warning); } &[data-callout="failure"], @@ -62,50 +88,74 @@ --color: #db4242; --border: #db424244; --bg: #db424210; + --callout-icon: var(--callout-icon-failure); + } + + &[data-callout="bug"] { + --callout-icon: var(--callout-icon-bug); + } + + &[data-callout="danger"] { + --callout-icon: var(--callout-icon-danger); } &[data-callout="example"] { --color: #7a43b5; --border: #7a43b544; --bg: #7a43b510; + --callout-icon: var(--callout-icon-example); } &[data-callout="quote"] { --color: var(--secondary); --border: var(--lightgray); + --callout-icon: var(--callout-icon-quote); } - &.is-collapsed > .callout-title > .fold { + &.is-collapsed > .callout-title > .fold-callout-icon { transform: rotateZ(-90deg); } } .callout-title { display: flex; + align-items: center; gap: 5px; padding: 1rem 0; color: var(--color); - & .fold { - margin-left: 0.5rem; - transition: transform 0.3s ease; + --icon-size: 18px; + + & .fold-callout-icon { + transition: transform 0.15s ease; opacity: 0.8; cursor: pointer; + width: var(--icon-size); + height: var(--icon-size); + --callout-icon: var(--callout-icon-fold); } & > .callout-title-inner > p { color: var(--color); margin: 0; } -} -.callout-icon { - width: 18px; - height: 18px; - flex: 0 0 18px; - padding-top: 4px; -} + .callout-icon, + & .fold-callout-icon { + width: var(--icon-size); + height: var(--icon-size); -.callout-title-inner { - font-weight: 700; + // icon support + background-size: var(--icon-size) var(--icon-size); + background-position: center; + background-color: var(--color); + mask-image: var(--callout-icon); + mask-size: var(--icon-size) var(--icon-size); + mask-position: center; + mask-repeat: no-repeat; + } + + .callout-title-inner { + font-weight: 700; + } } From 4e5643fb492415ac496b7b3f35cc0ae04e5a43c0 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Tue, 30 Jan 2024 23:51:21 -0800 Subject: [PATCH 06/45] fix: properly parse tags in body --- quartz/plugins/transformers/ofm.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 8c405bf17..d58eaf6b4 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -290,8 +290,9 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin } tag = slugTag(tag) - if (file.data.frontmatter?.tags?.includes(tag)) { - file.data.frontmatter.tags.push(tag) + if (file.data.frontmatter) { + const noteTags = file.data.frontmatter.tags ?? [] + file.data.frontmatter.tags = [...new Set([...noteTags, tag])] } return { From a29fadb0460e020ed6e5600fffa7af031d0b5eca Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Wed, 31 Jan 2024 04:16:14 -0500 Subject: [PATCH 07/45] feat(search): experimental telescope layout (closes #718) (#722) * feat(search): telescope-style search Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore(search): cleanup some basis and borders Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix(search): make sure to set overflow-y Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * feat(search): shows preview on desktop only search Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * perf: add options to control layout through config cache memoize results to avoid fetching Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: use the default configuration * fix: correct minor type for search Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix: use datasets to query for preview Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: layout changes show preview on normal layout, and only show previous layout in list page. * fix(type): annotate search with types Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: apply jacky's suggestion Co-authored-by: Jacky Zhao * chore: using map API and scss Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix: styling on search container view on phones Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * Update quartz.layout.ts Co-authored-by: Jacky Zhao --------- Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> Co-authored-by: Jacky Zhao --- quartz/components/Search.tsx | 14 +- quartz/components/scripts/search.inline.ts | 91 +++++++++- quartz/components/styles/search.scss | 187 ++++++++++++--------- 3 files changed, 208 insertions(+), 84 deletions(-) diff --git a/quartz/components/Search.tsx b/quartz/components/Search.tsx index 92684ae76..239bc033b 100644 --- a/quartz/components/Search.tsx +++ b/quartz/components/Search.tsx @@ -4,8 +4,18 @@ import style from "./styles/search.scss" import script from "./scripts/search.inline" import { classNames } from "../util/lang" -export default (() => { +export interface SearchOptions { + enablePreview: boolean +} + +const defaultOptions: SearchOptions = { + enablePreview: true, +} + +export default ((userOpts?: Partial) => { function Search({ displayClass }: QuartzComponentProps) { + const opts = { ...defaultOptions, ...userOpts } + return (
@@ -36,7 +46,7 @@ export default (() => { aria-label="Search for something" placeholder="Search for something" /> -
+
diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 941d35bb3..cbcc9ab5a 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -1,7 +1,7 @@ import FlexSearch from "flexsearch" import { ContentDetails } from "../../plugins/emitters/contentIndex" import { registerEscapeHandler, removeAllChildren } from "./util" -import { FullSlug, resolveRelative } from "../../util/path" +import { FullSlug, normalizeRelativeURLs, resolveRelative } from "../../util/path" interface Item { id: number @@ -71,20 +71,44 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { }` } +const p = new DOMParser() const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/) let prevShortcutHandler: ((e: HTMLElementEventMap["keydown"]) => void) | undefined = undefined -document.addEventListener("nav", async (e: unknown) => { - const currentSlug = (e as CustomEventMap["nav"]).detail.url + +const fetchContentCache: Map = new Map() + +document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { + const currentSlug = e.detail.url const data = await fetchData const container = document.getElementById("search-container") const sidebar = container?.closest(".sidebar") as HTMLElement const searchIcon = document.getElementById("search-icon") const searchBar = document.getElementById("search-bar") as HTMLInputElement | null - const results = document.getElementById("results-container") + const searchLayout = document.getElementById("search-layout") const resultCards = document.getElementsByClassName("result-card") const idDataMap = Object.keys(data) as FullSlug[] + const appendLayout = (el: HTMLElement) => { + if (searchLayout?.querySelector(`#${el.id}`) === null) { + searchLayout?.appendChild(el) + } + } + + const enablePreview = searchLayout?.dataset?.preview === "true" + let preview: HTMLDivElement | undefined = undefined + const results = document.createElement("div") + results.id = "results-container" + results.style.flexBasis = enablePreview ? "30%" : "100%" + appendLayout(results) + + if (enablePreview) { + preview = document.createElement("div") + preview.id = "preview-container" + preview.style.flexBasis = "70%" + appendLayout(preview) + } + function hideSearch() { container?.classList.remove("active") if (searchBar) { @@ -96,6 +120,9 @@ document.addEventListener("nav", async (e: unknown) => { if (results) { removeAllChildren(results) } + if (preview) { + removeAllChildren(preview) + } searchType = "basic" // reset search type after closing } @@ -109,7 +136,7 @@ document.addEventListener("nav", async (e: unknown) => { searchBar?.focus() } - function shortcutHandler(e: HTMLElementEventMap["keydown"]) { + async function shortcutHandler(e: HTMLElementEventMap["keydown"]) { if (e.key === "k" && (e.ctrlKey || e.metaKey) && !e.shiftKey) { e.preventDefault() const searchBarOpen = container?.classList.contains("active") @@ -139,6 +166,9 @@ document.addEventListener("nav", async (e: unknown) => { if (results?.contains(document.activeElement)) { // If an element in results-container already has focus, focus previous one const prevResult = document.activeElement?.previousElementSibling as HTMLInputElement | null + if (enablePreview && prevResult?.id) { + await displayPreview(prevResult?.id as FullSlug) + } prevResult?.focus() } } else if (e.key === "ArrowDown" || e.key === "Tab") { @@ -146,10 +176,16 @@ document.addEventListener("nav", async (e: unknown) => { // When first pressing ArrowDown, results wont contain the active element, so focus first element if (!results?.contains(document.activeElement)) { const firstResult = resultCards[0] as HTMLInputElement | null + if (enablePreview && firstResult?.id) { + await displayPreview(firstResult?.id as FullSlug) + } firstResult?.focus() } else { // If an element in results-container already has focus, focus next one const nextResult = document.activeElement?.nextElementSibling as HTMLInputElement | null + if (enablePreview && nextResult?.id) { + await displayPreview(nextResult?.id as FullSlug) + } nextResult?.focus() } } @@ -220,13 +256,17 @@ document.addEventListener("nav", async (e: unknown) => { } } + function resolveUrl(slug: FullSlug): URL { + return new URL(resolveRelative(currentSlug, slug), location.toString()) + } + const resultToHTML = ({ slug, title, content, tags }: Item) => { const htmlTags = tags.length > 0 ? `
    ${tags.join("")}
` : `` const itemTile = document.createElement("a") itemTile.classList.add("result-card") itemTile.id = slug - itemTile.href = new URL(resolveRelative(currentSlug, slug), location.toString()).toString() - itemTile.innerHTML = `

${title}

${htmlTags}

${content}

` + itemTile.href = resolveUrl(slug).toString() + itemTile.innerHTML = `

${title}

${htmlTags}${enablePreview && window.innerWidth > 600 ? "" : `

${content}

`}` itemTile.addEventListener("click", (event) => { if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return hideSearch() @@ -248,10 +288,47 @@ document.addEventListener("nav", async (e: unknown) => { } } + async function fetchContent(slug: FullSlug): Promise { + if (fetchContentCache.has(slug)) { + return fetchContentCache.get(slug) as Element[] + } + + const targetUrl = resolveUrl(slug).toString() + const contents = await fetch(targetUrl) + .then((res) => res.text()) + .then((contents) => { + if (contents === undefined) { + throw new Error(`Could not fetch ${targetUrl}`) + } + const html = p.parseFromString(contents ?? "", "text/html") + normalizeRelativeURLs(html, targetUrl) + return [...html.getElementsByClassName("popover-hint")] + }) + + fetchContentCache.set(slug, contents) + return contents + } + + async function displayPreview(slug: FullSlug) { + if (!searchLayout || !enablePreview) return + + removeAllChildren(preview as HTMLElement) + const contentDetails = await fetchContent(slug) + + const previewInner = document.createElement("div") + previewInner.classList.add("preview-inner") + preview?.appendChild(previewInner) + contentDetails?.forEach((elt) => previewInner.appendChild(elt)) + } + async function onType(e: HTMLElementEventMap["input"]) { let term = (e.target as HTMLInputElement).value let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] + if (searchLayout) { + searchLayout.style.opacity = "1" + } + if (term.toLowerCase().startsWith("#")) { searchType = "tags" } else { diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index d1d271637..f88803b4b 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -55,7 +55,7 @@ & > #search-space { width: 50%; - margin-top: 15vh; + margin-top: 12vh; margin-left: auto; margin-right: auto; @@ -86,93 +86,130 @@ } } - & > #results-container { - & .result-card { - padding: 1em; - cursor: pointer; - transition: background 0.2s ease; - border: 1px solid var(--lightgray); - border-bottom: none; - width: 100%; + & > #search-layout { + display: flex; + flex-direction: row; + justify-content: space-between; + opacity: 0; + + & > * { + height: calc(75vh - 20em); + background: none; + border-radius: 5px; + border: 1px solid var(--lightgray); // Border to define the box + } + + @media all and (max-width: $mobileBreakpoint) { + display: block; + & > *:not(#results-container) { + display: none !important; + } + + & > #results-container { + width: 100%; + height: auto; + } + } + + & > #preview-container { display: block; box-sizing: border-box; + overflow: hidden; - // normalize card props - font-family: inherit; - font-size: 100%; - line-height: 1.15; - margin: 0; - text-transform: none; - text-align: left; - background: var(--light); - outline: none; - font-weight: inherit; - - & .highlight { - color: var(--secondary); - font-weight: 700; + & .preview-inner { + padding: 1em; + height: 100%; + box-sizing: border-box; + overflow-y: auto; + font-family: inherit; + font-size: 1.1em; + color: var(--dark); + line-height: 1.5em; + font-weight: 400; + background: var(--light); + border-radius: 5px; + box-shadow: + 0 14px 50px rgba(27, 33, 48, 0.12), + 0 10px 30px rgba(27, 33, 48, 0.16); } + } - &:hover, - &:focus { - background: var(--lightgray); - } + & > #results-container { + overflow-y: auto; - &:first-of-type { - border-top-left-radius: 5px; - border-top-right-radius: 5px; - } + & .result-card { + padding: 1em; + cursor: pointer; + transition: background 0.2s ease; + width: 100%; + display: block; + box-sizing: border-box; - &:last-of-type { - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - border-bottom: 1px solid var(--lightgray); - } - - & > h3 { + // normalize card props + font-family: inherit; + font-size: 100%; + line-height: 1.15; margin: 0; - } + text-transform: none; + text-align: left; + background: var(--light); + outline: none; + font-weight: inherit; - & > ul > li { - margin: 0; - display: inline-block; - white-space: nowrap; - margin: 0; - overflow-wrap: normal; - } + & .highlight { + color: var(--secondary); + font-weight: 700; + } - & > ul { - list-style: none; - display: flex; - padding-left: 0; - gap: 0.4rem; - margin: 0; - margin-top: 0.45rem; - // Offset border radius - margin-left: -2px; - overflow: hidden; - background-clip: border-box; - } + &:hover, + &:focus { + background: var(--lightgray); + } - & > ul > li > p { - border-radius: 8px; - background-color: var(--highlight); - overflow: hidden; - background-clip: border-box; - padding: 0.03rem 0.4rem; - margin: 0; - color: var(--secondary); - opacity: 0.85; - } + & > h3 { + margin: 0; + } - & > ul > li > .match-tag { - color: var(--tertiary); - font-weight: bold; - opacity: 1; - } + & > ul > li { + margin: 0; + display: inline-block; + white-space: nowrap; + margin: 0; + overflow-wrap: normal; + } - & > p { - margin-bottom: 0; + & > ul { + list-style: none; + display: flex; + padding-left: 0; + gap: 0.4rem; + margin: 0; + margin-top: 0.45rem; + box-sizing: border-box; + overflow: hidden; + background-clip: border-box; + } + + & > ul > li > p { + border-radius: 8px; + background-color: var(--highlight); + overflow: hidden; + background-clip: border-box; + padding: 0.03rem 0.4rem; + margin: 0; + color: var(--secondary); + opacity: 0.85; + } + + & > ul > li > .match-tag { + color: var(--tertiary); + font-weight: bold; + opacity: 1; + } + + & > p { + margin-bottom: 0; + } } } } From fee3ef9b3a0135fd98c412f50609b0d1009ff55c Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:25:16 -0500 Subject: [PATCH 08/45] chore(deps): bump katex to 0.16.9 (#772) Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- quartz/plugins/transformers/latex.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quartz/plugins/transformers/latex.ts b/quartz/plugins/transformers/latex.ts index cb459f743..ab10a4fbb 100644 --- a/quartz/plugins/transformers/latex.ts +++ b/quartz/plugins/transformers/latex.ts @@ -26,12 +26,12 @@ export const Latex: QuartzTransformerPlugin = (opts?: Options) => { return { css: [ // base css - "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css", + "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css", ], js: [ { // fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md - src: "https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/contrib/copy-tex.min.js", + src: "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/copy-tex.min.js", loadTime: "afterDOMReady", contentType: "external", }, From 50bb1ffd8aa00d85d3a17bc1a4e52d26187afa7e Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:38:42 -0500 Subject: [PATCH 09/45] feat(usability): update functions for search (#774) * feat(usability): update functions for search Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * perf: slightly cleaner variables Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --------- Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- quartz/components/scripts/search.inline.ts | 109 +++++++++++++++------ quartz/components/styles/search.scss | 3 +- 2 files changed, 80 insertions(+), 32 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index cbcc9ab5a..0124e1f1d 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -86,7 +86,6 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const searchIcon = document.getElementById("search-icon") const searchBar = document.getElementById("search-bar") as HTMLInputElement | null const searchLayout = document.getElementById("search-layout") - const resultCards = document.getElementsByClassName("result-card") const idDataMap = Object.keys(data) as FullSlug[] const appendLayout = (el: HTMLElement) => { @@ -151,41 +150,50 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { if (searchBar) searchBar.value = "#" } + const resultCards = document.getElementsByClassName("result-card") + + // If search is active, then we will render the first result and display accordingly if (!container?.classList.contains("active")) return - else if (e.key === "Enter") { - // If result has focus, navigate to that one, otherwise pick first result - if (results?.contains(document.activeElement)) { - const active = document.activeElement as HTMLInputElement + else if (results?.contains(document.activeElement)) { + const active = document.activeElement as HTMLInputElement + await displayPreview(active) + if (e.key === "Enter") { active.click() - } else { - const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null + } + } else { + const anchor = resultCards[0] as HTMLInputElement | null + await displayPreview(anchor) + if (e.key === "Enter") { anchor?.click() } - } else if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) { + } + + if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) { e.preventDefault() if (results?.contains(document.activeElement)) { // If an element in results-container already has focus, focus previous one - const prevResult = document.activeElement?.previousElementSibling as HTMLInputElement | null - if (enablePreview && prevResult?.id) { - await displayPreview(prevResult?.id as FullSlug) - } + const currentResult = document.activeElement as HTMLInputElement | null + const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null + currentResult?.classList.remove("focus") + await displayPreview(prevResult) prevResult?.focus() } } else if (e.key === "ArrowDown" || e.key === "Tab") { e.preventDefault() - // When first pressing ArrowDown, results wont contain the active element, so focus first element + // The results should already been focused, so we need to find the next one. + // The activeElement is the search bar, so we need to find the first result and focus it. if (!results?.contains(document.activeElement)) { const firstResult = resultCards[0] as HTMLInputElement | null - if (enablePreview && firstResult?.id) { - await displayPreview(firstResult?.id as FullSlug) - } - firstResult?.focus() + const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null + firstResult?.classList.remove("focus") + await displayPreview(secondResult) + secondResult?.focus() } else { // If an element in results-container already has focus, focus next one - const nextResult = document.activeElement?.nextElementSibling as HTMLInputElement | null - if (enablePreview && nextResult?.id) { - await displayPreview(nextResult?.id as FullSlug) - } + const active = document.activeElement as HTMLInputElement | null + active?.classList.remove("focus") + const nextResult = active?.nextElementSibling as HTMLInputElement | null + await displayPreview(nextResult) nextResult?.focus() } } @@ -262,19 +270,50 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const resultToHTML = ({ slug, title, content, tags }: Item) => { const htmlTags = tags.length > 0 ? `
    ${tags.join("")}
` : `` + const resultContent = enablePreview && window.innerWidth > 600 ? "" : `

${content}

` + const itemTile = document.createElement("a") itemTile.classList.add("result-card") - itemTile.id = slug - itemTile.href = resolveUrl(slug).toString() - itemTile.innerHTML = `

${title}

${htmlTags}${enablePreview && window.innerWidth > 600 ? "" : `

${content}

`}` - itemTile.addEventListener("click", (event) => { - if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return - hideSearch() + Object.assign(itemTile, { + id: slug, + href: resolveUrl(slug).toString(), + innerHTML: `

${title}

${htmlTags}${resultContent}`, }) + + async function onMouseEnter(ev: MouseEvent) { + // When search is active, the first element is in focus, so we need to remove focus if given target is not the first element + const firstEl = document.getElementsByClassName("result-card")[0] as HTMLAnchorElement | null + const target = ev.target as HTMLAnchorElement + if (firstEl !== target) { + firstEl?.classList.remove("focus") + } + target.classList.add("focus") + await displayPreview(target) + } + + async function onMouseLeave(ev: MouseEvent) { + const target = ev.target as HTMLAnchorElement + target.classList.remove("focus") + } + + const events = [ + ["mouseenter", onMouseEnter], + ["mouseleave", onMouseLeave], + [ + "click", + (event: MouseEvent) => { + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return + hideSearch() + }, + ], + ] as [keyof HTMLElementEventMap, (this: HTMLElement) => void][] + + events.forEach(([event, handler]) => itemTile.addEventListener(event, handler)) + return itemTile } - function displayResults(finalResults: Item[]) { + async function displayResults(finalResults: Item[]) { if (!results) return removeAllChildren(results) @@ -286,6 +325,11 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { } else { results.append(...finalResults.map(resultToHTML)) } + // focus on first result, then also dispatch preview immediately + if (results?.firstElementChild) { + results?.firstElementChild?.classList.add("focus") + await displayPreview(results?.firstElementChild as HTMLElement) + } } async function fetchContent(slug: FullSlug): Promise { @@ -309,8 +353,11 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { return contents } - async function displayPreview(slug: FullSlug) { - if (!searchLayout || !enablePreview) return + async function displayPreview(el: HTMLElement | null) { + if (!searchLayout || !enablePreview || !el) return + + const slug = el.id as FullSlug + el.classList.add("focus") removeAllChildren(preview as HTMLElement) const contentDetails = await fetchContent(slug) @@ -366,7 +413,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { ...getByField("tags"), ]) const finalResults = [...allIds].map((id) => formatForDisplay(term, id)) - displayResults(finalResults) + await displayResults(finalResults) } if (prevShortcutHandler) { diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index f88803b4b..53046189b 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -162,7 +162,8 @@ } &:hover, - &:focus { + &:focus, + &.focus { background: var(--lightgray); } From e1f12e6cb7ec5d2bfc430c15319d14c6d87a2c70 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 31 Jan 2024 09:55:23 -0800 Subject: [PATCH 10/45] fix(style): search preview consistency --- quartz/components/scripts/search.inline.ts | 4 ++-- quartz/components/styles/search.scss | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 0124e1f1d..5f2da36ee 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -20,8 +20,8 @@ type SearchType = "basic" | "tags" let searchType: SearchType = "basic" const contextWindowWords = 30 -const numSearchResults = 5 -const numTagResults = 3 +const numSearchResults = 8 +const numTagResults = 5 function highlight(searchTerm: string, text: string, trim?: boolean) { // try to highlight longest tokens first const tokenizedTerms = searchTerm diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 53046189b..fb7dd7458 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -91,12 +91,22 @@ flex-direction: row; justify-content: space-between; opacity: 0; + border: 1px solid var(--lightgray); - & > * { + & > div { height: calc(75vh - 20em); background: none; - border-radius: 5px; - border: 1px solid var(--lightgray); // Border to define the box + + &:first-child { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + border-right: 1px solid var(--lightgray); + } + + &:last-child { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } } @media all and (max-width: $mobileBreakpoint) { @@ -122,7 +132,6 @@ box-sizing: border-box; overflow-y: auto; font-family: inherit; - font-size: 1.1em; color: var(--dark); line-height: 1.5em; font-weight: 400; @@ -141,6 +150,7 @@ padding: 1em; cursor: pointer; transition: background 0.2s ease; + border-bottom: 1px solid var(--lightgray); width: 100%; display: block; box-sizing: border-box; From 22de92f6c46fd8aae3e803faaea734f28277cfae Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 31 Jan 2024 10:01:40 -0800 Subject: [PATCH 11/45] pkg: bump to 4.1.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bf6e9431..3fd24e3f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackyzha0/quartz", - "version": "4.1.5", + "version": "4.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.1.5", + "version": "4.1.6", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", diff --git a/package.json b/package.json index c779b119f..ee1ab1619 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@jackyzha0/quartz", "description": "🌱 publish your digital garden and notes as a website", "private": true, - "version": "4.1.5", + "version": "4.1.6", "type": "module", "author": "jackyzha0 ", "license": "MIT", From 7cb1c291c802b74dc47d235f094e5d9134f95283 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 31 Jan 2024 11:41:27 -0800 Subject: [PATCH 12/45] fix: allow formatting in callout titles --- docs/features/callouts.md | 13 +++++++------ quartz/plugins/transformers/ofm.ts | 5 +---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/features/callouts.md b/docs/features/callouts.md index 64a51718e..d73979284 100644 --- a/docs/features/callouts.md +++ b/docs/features/callouts.md @@ -32,11 +32,12 @@ By default, custom callouts are handled by applying the `note` style. To make fa ```scss title="quartz/styles/custom.scss" .callout { - &[data-callout="custom"] { - --color: #customcolor; - --border: #custombordercolor; - --bg: #custombg; - --callout-icon: url('data:image/svg+xml; utf8, '); //SVG icon code + &[data-callout="custom"] { + --color: #customcolor; + --border: #custombordercolor; + --bg: #custombg; + --callout-icon: url("data:image/svg+xml; utf8, "); //SVG icon code + } } ``` @@ -48,7 +49,7 @@ By default, custom callouts are handled by applying the `note` style. To make fa > [!info] > Default title -> [!question]+ Can callouts be nested? +> [!question]+ Can callouts be _nested_? > > > [!todo]- Yes!, they can. And collapsed! > > diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index d58eaf6b4..7cd94993b 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -387,10 +387,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin match.input.slice(calloutDirective.length).trim() || capitalize(calloutType) const titleNode: Paragraph = { type: "paragraph", - children: - restOfTitle.length === 0 - ? [{ type: "text", value: titleContent + " " }] - : restOfTitle, + children: [{ type: "text", value: titleContent + " " }, ...restOfTitle], } const title = mdastToHtml(titleNode) From 355aa2231879468bf9b08d1322710619c8d1fdb1 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 31 Jan 2024 11:52:10 -0800 Subject: [PATCH 13/45] docs: fix outdated comment on rebuild debounce behaviour --- quartz/build.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/quartz/build.ts b/quartz/build.ts index b78ff2bc6..a021f7425 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -135,7 +135,6 @@ async function rebuildFromEntrypoint( toRebuild, toRemove, trackedAssets, - lastBuildMs, } = buildData const { argv } = ctx @@ -164,12 +163,12 @@ async function rebuildFromEntrypoint( toRemove.add(filePath) } - // debounce rebuilds every 250ms - const buildStart = new Date().getTime() buildData.lastBuildMs = buildStart const release = await mut.acquire() - if (lastBuildMs > buildStart) { + + // there's another build after us, release and let them do it + if (buildData.lastBuildMs > buildStart) { release() return } From 75d64eac9135e63cda0c2beb51791109070b2575 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 31 Jan 2024 11:58:54 -0800 Subject: [PATCH 14/45] fix: fmt --- quartz/build.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/quartz/build.ts b/quartz/build.ts index a021f7425..1f90301e9 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -126,16 +126,8 @@ async function rebuildFromEntrypoint( clientRefresh: () => void, buildData: BuildData, // note: this function mutates buildData ) { - const { - ctx, - ignored, - mut, - initialSlugs, - contentMap, - toRebuild, - toRemove, - trackedAssets, - } = buildData + const { ctx, ignored, mut, initialSlugs, contentMap, toRebuild, toRemove, trackedAssets } = + buildData const { argv } = ctx From 422986c98babf19f00fc65078e3167eba5aaf7f7 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:00:19 -0500 Subject: [PATCH 15/45] fix(search): remove background with mouseEvent (#775) * fix(search): remove background with mouseEvent make sure when mouseenter we remove all existing background Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: update logics from suggestions Co-authored-by: Jacky Zhao * revert: class is evicted * fix: address correct type Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --------- Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> Co-authored-by: Jacky Zhao --- quartz/components/scripts/search.inline.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 5f2da36ee..170a8f014 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -281,12 +281,14 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { }) async function onMouseEnter(ev: MouseEvent) { - // When search is active, the first element is in focus, so we need to remove focus if given target is not the first element - const firstEl = document.getElementsByClassName("result-card")[0] as HTMLAnchorElement | null - const target = ev.target as HTMLAnchorElement - if (firstEl !== target) { - firstEl?.classList.remove("focus") + // Actually when we hover, we need to clean all highlights within the result childs + for (const el of document.getElementsByClassName( + "result-card", + ) as HTMLCollectionOf) { + el.classList.remove("focus") + el.blur() } + const target = ev.target as HTMLAnchorElement target.classList.add("focus") await displayPreview(target) } From bfd877133b406eda3a46c2e5ac682262cca3e2c2 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 31 Jan 2024 12:08:57 -0800 Subject: [PATCH 16/45] fix: regression in formatted callout titles --- quartz/plugins/transformers/ofm.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 7cd94993b..44df3fa9e 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -383,11 +383,17 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin const calloutType = canonicalizeCallout(typeString.toLowerCase()) const collapse = collapseChar === "+" || collapseChar === "-" const defaultState = collapseChar === "-" ? "collapsed" : "expanded" - const titleContent = - match.input.slice(calloutDirective.length).trim() || capitalize(calloutType) + const titleContent = match.input.slice(calloutDirective.length).trim() + const useDefaultTitle = titleContent === "" && restOfTitle.length === 0 const titleNode: Paragraph = { type: "paragraph", - children: [{ type: "text", value: titleContent + " " }, ...restOfTitle], + children: [ + { + type: "text", + value: useDefaultTitle ? capitalize(calloutType) : titleContent + " ", + }, + ...restOfTitle, + ], } const title = mdastToHtml(titleNode) From 25e6869d38bc8702ff73f3cd3747b544a24759cd Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 31 Jan 2024 12:24:25 -0800 Subject: [PATCH 17/45] deps: reduce dependabot frequency --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dc108f27f..42adb4474 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,4 +8,4 @@ updates: - package-ecosystem: "npm" directory: "/" schedule: - interval: "daily" + interval: "weekly" From f2e93c3314765a46aea8710acd20e7e8a6556c4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:26:34 -0500 Subject: [PATCH 18/45] chore(deps-dev): bump @types/node from 20.11.11 to 20.11.14 (#779) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.11 to 20.11.14. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3fd24e3f1..cfd7b843c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.3", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.11", + "@types/node": "^20.11.14", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.10", @@ -1088,9 +1088,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.11.tgz", - "integrity": "sha512-PlJCXfb57Jrman0H1BxO2+Q7qwih2Mwk7T6Gvixj+SK4mqs4RWOGMMoP6p/LFa3UrP2CZOO6ai6otd7J/TB6Ug==", + "version": "20.11.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.14.tgz", + "integrity": "sha512-w3yWCcwULefjP9DmDDsgUskrMoOy5Z8MiwKHr1FvqGPtx7CvJzQvxD7eKpxNtklQxLruxSXWddyeRtyud0RcXQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" diff --git a/package.json b/package.json index ee1ab1619..4483e8f70 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.3", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.11", + "@types/node": "^20.11.14", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", "@types/ws": "^8.5.10", From 7b2ce8b4a312a84eabb9aa263189b22c5bdb973a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:26:57 -0500 Subject: [PATCH 19/45] chore(deps): bump async-mutex from 0.4.0 to 0.4.1 (#777) Bumps [async-mutex](https://github.com/DirtyHairy/async-mutex) from 0.4.0 to 0.4.1. - [Changelog](https://github.com/DirtyHairy/async-mutex/blob/master/CHANGELOG.md) - [Commits](https://github.com/DirtyHairy/async-mutex/compare/v0.4.0...v0.4.1) --- updated-dependencies: - dependency-name: async-mutex dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfd7b843c..14ce46fe2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@clack/prompts": "^0.7.0", "@floating-ui/dom": "^1.6.1", "@napi-rs/simple-git": "0.1.14", - "async-mutex": "^0.4.0", + "async-mutex": "^0.4.1", "chalk": "^5.3.0", "chokidar": "^3.5.3", "cli-spinner": "^0.2.10", @@ -1208,9 +1208,9 @@ } }, "node_modules/async-mutex": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", - "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", + "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", "dependencies": { "tslib": "^2.4.0" } diff --git a/package.json b/package.json index 4483e8f70..07d5d1a0c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@clack/prompts": "^0.7.0", "@floating-ui/dom": "^1.6.1", "@napi-rs/simple-git": "0.1.14", - "async-mutex": "^0.4.0", + "async-mutex": "^0.4.1", "chalk": "^5.3.0", "chokidar": "^3.5.3", "cli-spinner": "^0.2.10", From 1c175b2d095eed9478192129802a7491b13201a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:27:30 -0500 Subject: [PATCH 20/45] chore(deps): bump mdast-util-to-hast from 13.0.2 to 13.1.0 (#776) Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.0.2 to 13.1.0. - [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases) - [Commits](https://github.com/syntax-tree/mdast-util-to-hast/compare/13.0.2...13.1.0) --- updated-dependencies: - dependency-name: mdast-util-to-hast dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 11 ++++++----- package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14ce46fe2..8ee7b4a05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "js-yaml": "^4.1.0", "lightningcss": "^1.23.0", "mdast-util-find-and-replace": "^3.0.1", - "mdast-util-to-hast": "^13.0.2", + "mdast-util-to-hast": "^13.1.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", "preact": "^10.19.3", @@ -3610,9 +3610,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.0.2.tgz", - "integrity": "sha512-U5I+500EOOw9e3ZrclN3Is3fRpw8c19SMyNZlZ2IS+7vLsNzb2Om11VpIVOR+/0137GhZsFEF6YiKD5+0Hr2Og==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz", + "integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -3621,7 +3621,8 @@ "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0" + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", diff --git a/package.json b/package.json index 07d5d1a0c..441be236f 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "js-yaml": "^4.1.0", "lightningcss": "^1.23.0", "mdast-util-find-and-replace": "^3.0.1", - "mdast-util-to-hast": "^13.0.2", + "mdast-util-to-hast": "^13.1.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", "preact": "^10.19.3", From 444e05ee21687473c17c19e1d52d7da39694971c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:35:29 -0800 Subject: [PATCH 21/45] chore(deps-dev): bump @types/hast from 3.0.3 to 3.0.4 (#780) Bumps [@types/hast](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/hast) from 3.0.3 to 3.0.4. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/hast) --- updated-dependencies: - dependency-name: "@types/hast" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ee7b4a05..ef9313491 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,7 +71,7 @@ "devDependencies": { "@types/cli-spinner": "^0.2.3", "@types/d3": "^7.4.3", - "@types/hast": "^3.0.3", + "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", "@types/node": "^20.11.14", "@types/pretty-time": "^1.1.5", @@ -1043,9 +1043,9 @@ "dev": true }, "node_modules/@types/hast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.3.tgz", - "integrity": "sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "dependencies": { "@types/unist": "*" } diff --git a/package.json b/package.json index 441be236f..ee4810f50 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "devDependencies": { "@types/cli-spinner": "^0.2.3", "@types/d3": "^7.4.3", - "@types/hast": "^3.0.3", + "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", "@types/node": "^20.11.14", "@types/pretty-time": "^1.1.5", From 9aa6a18be2eae0d84c7897470a46ede19d5ac191 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:56:42 -0500 Subject: [PATCH 22/45] fix(search): improve more general usability (closes #781) (#782) * fix(search): improve more general usability Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix: revert naming Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix: correct check for enter event on no-match cases Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * Update quartz/components/scripts/search.inline.ts Co-authored-by: Jacky Zhao * chore: remove unecessary class for tracking mouse Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --------- Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> Co-authored-by: Jacky Zhao --- quartz/components/scripts/search.inline.ts | 66 ++++++++++++++-------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 170a8f014..43332a6d8 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -122,6 +122,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { if (preview) { removeAllChildren(preview) } + if (searchLayout) { + searchLayout.style.opacity = "0" + } searchType = "basic" // reset search type after closing } @@ -135,6 +138,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { searchBar?.focus() } + let currentHover: HTMLInputElement | null = null + async function shortcutHandler(e: HTMLElementEventMap["keydown"]) { if (e.key === "k" && (e.ctrlKey || e.metaKey) && !e.shiftKey) { e.preventDefault() @@ -150,51 +155,61 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { if (searchBar) searchBar.value = "#" } - const resultCards = document.getElementsByClassName("result-card") + if (currentHover) { + currentHover.classList.remove("focus") + } // If search is active, then we will render the first result and display accordingly if (!container?.classList.contains("active")) return - else if (results?.contains(document.activeElement)) { - const active = document.activeElement as HTMLInputElement - await displayPreview(active) - if (e.key === "Enter") { + else if (e.key === "Enter") { + // If result has focus, navigate to that one, otherwise pick first result + if (results?.contains(document.activeElement)) { + const active = document.activeElement as HTMLInputElement + if (active.classList.contains("no-match")) return + await displayPreview(active) active.click() + } else { + const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null + if (!anchor || anchor?.classList.contains("no-match")) return + await displayPreview(anchor) + anchor.click() } - } else { - const anchor = resultCards[0] as HTMLInputElement | null - await displayPreview(anchor) - if (e.key === "Enter") { - anchor?.click() - } - } - - if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) { + } else if (e.key === "ArrowUp" || (e.shiftKey && e.key === "Tab")) { e.preventDefault() if (results?.contains(document.activeElement)) { // If an element in results-container already has focus, focus previous one - const currentResult = document.activeElement as HTMLInputElement | null + const currentResult = currentHover + ? currentHover + : (document.activeElement as HTMLInputElement | null) const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null currentResult?.classList.remove("focus") await displayPreview(prevResult) prevResult?.focus() + currentHover = prevResult } } else if (e.key === "ArrowDown" || e.key === "Tab") { e.preventDefault() // The results should already been focused, so we need to find the next one. // The activeElement is the search bar, so we need to find the first result and focus it. if (!results?.contains(document.activeElement)) { - const firstResult = resultCards[0] as HTMLInputElement | null + const firstResult = currentHover + ? currentHover + : (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null) const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null firstResult?.classList.remove("focus") await displayPreview(secondResult) secondResult?.focus() + currentHover = secondResult } else { // If an element in results-container already has focus, focus next one - const active = document.activeElement as HTMLInputElement | null + const active = currentHover + ? currentHover + : (document.activeElement as HTMLInputElement | null) active?.classList.remove("focus") const nextResult = active?.nextElementSibling as HTMLInputElement | null await displayPreview(nextResult) nextResult?.focus() + currentHover = nextResult } } } @@ -282,15 +297,17 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { async function onMouseEnter(ev: MouseEvent) { // Actually when we hover, we need to clean all highlights within the result childs + if (!ev.target) return for (const el of document.getElementsByClassName( "result-card", ) as HTMLCollectionOf) { el.classList.remove("focus") el.blur() } - const target = ev.target as HTMLAnchorElement - target.classList.add("focus") + const target = ev.target as HTMLInputElement await displayPreview(target) + currentHover = target + currentHover.classList.remove("focus") } async function onMouseLeave(ev: MouseEvent) { @@ -320,7 +337,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { removeAllChildren(results) if (finalResults.length === 0) { - results.innerHTML = ` + results.innerHTML = `

No results.

Try another search term?

` @@ -329,8 +346,13 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { } // focus on first result, then also dispatch preview immediately if (results?.firstElementChild) { - results?.firstElementChild?.classList.add("focus") - await displayPreview(results?.firstElementChild as HTMLElement) + const firstChild = results.firstElementChild as HTMLElement + if (firstChild.classList.contains("no-match")) { + removeAllChildren(preview as HTMLElement) + } else { + firstChild.classList.add("focus") + await displayPreview(firstChild) + } } } From 756acc7f975f313f9d9139b42be9d57805014454 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Thu, 1 Feb 2024 16:48:27 -0500 Subject: [PATCH 23/45] feat(search): highlight on preview (#783) * feat: primitive full-text search on preview Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix: remove invalid regex and unused code path Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --------- Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- quartz/components/scripts/search.inline.ts | 67 +++++++++++++++++++--- quartz/components/styles/search.scss | 10 ++-- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 43332a6d8..3bbfa7bf2 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -11,23 +11,29 @@ interface Item { tags: string[] } -let index: FlexSearch.Document | undefined = undefined - // Can be expanded with things like "term" in the future type SearchType = "basic" | "tags" // Current searchType let searchType: SearchType = "basic" +// Current search term // TODO: exact match +let currentSearchTerm: string = "" +// index for search +let index: FlexSearch.Document | undefined = undefined const contextWindowWords = 30 const numSearchResults = 8 const numTagResults = 5 -function highlight(searchTerm: string, text: string, trim?: boolean) { - // try to highlight longest tokens first - const tokenizedTerms = searchTerm + +const tokenizeTerm = (term: string) => + term .split(/\s+/) .filter((t) => t !== "") .sort((a, b) => b.length - a.length) + +function highlight(searchTerm: string, text: string, trim?: boolean) { + // try to highlight longest tokens first + const tokenizedTerms = tokenizeTerm(searchTerm) let tokenizedText = text.split(/\s+/).filter((t) => t !== "") let startIndex = 0 @@ -64,6 +70,7 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { } return tok }) + .slice(startIndex, endIndex + 1) .join(" ") return `${startIndex === 0 ? "" : "..."}${slice}${ @@ -71,6 +78,45 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { }` } +function highlightHTML(searchTerm: string, el: HTMLElement) { + // try to highlight longest tokens first + const p = new DOMParser() + const tokenizedTerms = tokenizeTerm(searchTerm) + const html = p.parseFromString(el.innerHTML, "text/html") + + const createHighlightSpan = (text: string) => { + const span = document.createElement("span") + span.className = "highlight" + span.textContent = text + return span + } + + const highlightTextNodes = (node: Node) => { + if (node.nodeType === Node.TEXT_NODE) { + let nodeText = node.nodeValue || "" + tokenizedTerms.forEach((term) => { + const regex = new RegExp(term.toLowerCase(), "gi") + const matches = nodeText.match(regex) + const spanContainer = document.createElement("span") + let lastIndex = 0 + matches?.forEach((match) => { + const matchIndex = nodeText.indexOf(match, lastIndex) + spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex, matchIndex))) + spanContainer.appendChild(createHighlightSpan(match)) + lastIndex = matchIndex + match.length + }) + spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex))) + node.parentNode?.replaceChild(spanContainer, node) + }) + } else if (node.nodeType === Node.ELEMENT_NODE) { + Array.from(node.childNodes).forEach(highlightTextNodes) + } + } + + highlightTextNodes(html.body) + return html.body +} + const p = new DOMParser() const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/) let prevShortcutHandler: ((e: HTMLElementEventMap["keydown"]) => void) | undefined = undefined @@ -96,6 +142,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const enablePreview = searchLayout?.dataset?.preview === "true" let preview: HTMLDivElement | undefined = undefined + let previewInner: HTMLDivElement | undefined = undefined const results = document.createElement("div") results.id = "results-container" results.style.flexBasis = enablePreview ? "30%" : "100%" @@ -384,17 +431,21 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { el.classList.add("focus") removeAllChildren(preview as HTMLElement) - const contentDetails = await fetchContent(slug) - const previewInner = document.createElement("div") + previewInner = document.createElement("div") previewInner.classList.add("preview-inner") preview?.appendChild(previewInner) - contentDetails?.forEach((elt) => previewInner.appendChild(elt)) + + const innerDiv = await fetchContent(slug).then((contents) => + contents.map((el) => highlightHTML(currentSearchTerm, el as HTMLElement)), + ) + previewInner.append(...innerDiv) } async function onType(e: HTMLElementEventMap["input"]) { let term = (e.target as HTMLInputElement).value let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] + currentSearchTerm = (e.target as HTMLInputElement).value if (searchLayout) { searchLayout.style.opacity = "1" diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index fb7dd7458..e84172e35 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -121,6 +121,11 @@ } } + & .highlight { + color: var(--secondary); + font-weight: 700; + } + & > #preview-container { display: block; box-sizing: border-box; @@ -166,11 +171,6 @@ outline: none; font-weight: inherit; - & .highlight { - color: var(--secondary); - font-weight: 700; - } - &:hover, &:focus, &.focus { From 295b8fc9149e6702629717e3b71b2eff7d726a19 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:44:33 -0500 Subject: [PATCH 24/45] fix(search): increase size on fullPageWidth viewport (#784) * fix(search): increase size on fullPageWidth viewport Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: fix width size to be consistent on multiple views Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: set layout to 0 if there is no term remove flashing by setting max-height Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --------- Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- quartz/components/scripts/search.inline.ts | 4 ++++ quartz/components/styles/search.scss | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 3bbfa7bf2..7871b39d6 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -451,6 +451,10 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { searchLayout.style.opacity = "1" } + if (term === "" && searchLayout) { + searchLayout.style.opacity = "0" + } + if (term.toLowerCase().startsWith("#")) { searchType = "tags" } else { diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index e84172e35..11e7c4e14 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -54,15 +54,11 @@ } & > #search-space { - width: 50%; + width: 75%; margin-top: 12vh; margin-left: auto; margin-right: auto; - @media all and (max-width: $fullPageWidth) { - width: 90%; - } - & > * { width: 100%; border-radius: 5px; @@ -94,7 +90,8 @@ border: 1px solid var(--lightgray); & > div { - height: calc(75vh - 20em); + // vh - #search-space.margin-top + height: calc(75vh - 12vh); background: none; &:first-child { @@ -146,6 +143,10 @@ 0 14px 50px rgba(27, 33, 48, 0.12), 0 10px 30px rgba(27, 33, 48, 0.16); } + + a.internal { + background-color: none; + } } & > #results-container { From f78b512436ebc293d10e9ebdd0fc5fbd1705dde4 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Thu, 1 Feb 2024 22:25:45 -0500 Subject: [PATCH 25/45] chore(search): check for input type and assignment of focus (#785) Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- quartz/components/scripts/search.inline.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 7871b39d6..8ead5c952 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -238,7 +238,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { e.preventDefault() // The results should already been focused, so we need to find the next one. // The activeElement is the search bar, so we need to find the first result and focus it. - if (!results?.contains(document.activeElement)) { + if (document.activeElement === searchBar || currentHover !== null) { const firstResult = currentHover ? currentHover : (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null) @@ -398,6 +398,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { removeAllChildren(preview as HTMLElement) } else { firstChild.classList.add("focus") + currentHover = firstChild as HTMLInputElement await displayPreview(firstChild) } } From 8a6ebd193933c2879c2a36e1b2f164889575d3bc Mon Sep 17 00:00:00 2001 From: Justin Fowler Date: Thu, 1 Feb 2024 22:17:21 -0600 Subject: [PATCH 26/45] docs: clarity for `RecentNotes` (#786) - Removed a word for clarity - added reference to layout file --- docs/features/recent notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/recent notes.md b/docs/features/recent notes.md index 439d6d050..9236b7ce2 100644 --- a/docs/features/recent notes.md +++ b/docs/features/recent notes.md @@ -3,7 +3,7 @@ title: Recent Notes tags: component --- -Quartz can generate a list of recent notes for based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Component.RecentNotes`. +Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Component.RecentNotes` in `quartz.layout.ts`. ## Customization From c00089bd5728188ce554303b5b18754467c97c85 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Feb 2024 20:07:14 -0800 Subject: [PATCH 27/45] chore: add window.addCleanup() for cleaning up handlers --- docs/advanced/creating components.md | 5 ++-- globals.d.ts | 1 + quartz/components/scripts/callout.inline.ts | 24 ++++++++--------- quartz/components/scripts/darkmode.inline.ts | 2 +- quartz/components/scripts/explorer.inline.ts | 6 ++--- quartz/components/scripts/graph.inline.ts | 2 +- quartz/components/scripts/popover.inline.ts | 2 +- quartz/components/scripts/search.inline.ts | 27 +++++-------------- quartz/components/scripts/spa.inline.ts | 7 +++++ quartz/components/scripts/toc.inline.ts | 2 +- quartz/components/scripts/util.ts | 4 +-- quartz/plugins/emitters/componentResources.ts | 14 +++++----- 12 files changed, 47 insertions(+), 49 deletions(-) diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md index 1496b15b2..27369abf2 100644 --- a/docs/advanced/creating components.md +++ b/docs/advanced/creating components.md @@ -156,12 +156,13 @@ document.addEventListener("nav", () => { // do page specific logic here // e.g. attach event listeners const toggleSwitch = document.querySelector("#switch") as HTMLInputElement - toggleSwitch.removeEventListener("change", switchTheme) toggleSwitch.addEventListener("change", switchTheme) + window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) }) ``` -It is best practice to also unmount any existing event handlers to prevent memory leaks. +It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. +This will get called on page navigation. #### Importing Code diff --git a/globals.d.ts b/globals.d.ts index 0509f2665..ee13005c9 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -8,5 +8,6 @@ export declare global { } interface Window { spaNavigate(url: URL, isBack: boolean = false) + addCleanup(fn: (...args: any[]) => void) } } diff --git a/quartz/components/scripts/callout.inline.ts b/quartz/components/scripts/callout.inline.ts index d8cf5180a..8f63df36f 100644 --- a/quartz/components/scripts/callout.inline.ts +++ b/quartz/components/scripts/callout.inline.ts @@ -1,21 +1,21 @@ function toggleCallout(this: HTMLElement) { const outerBlock = this.parentElement! - outerBlock.classList.toggle(`is-collapsed`) - const collapsed = outerBlock.classList.contains(`is-collapsed`) + outerBlock.classList.toggle("is-collapsed") + const collapsed = outerBlock.classList.contains("is-collapsed") const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight - outerBlock.style.maxHeight = height + `px` + outerBlock.style.maxHeight = height + "px" // walk and adjust height of all parents let current = outerBlock let parent = outerBlock.parentElement while (parent) { - if (!parent.classList.contains(`callout`)) { + if (!parent.classList.contains("callout")) { return } - const collapsed = parent.classList.contains(`is-collapsed`) + const collapsed = parent.classList.contains("is-collapsed") const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight - parent.style.maxHeight = height + `px` + parent.style.maxHeight = height + "px" current = parent parent = parent.parentElement @@ -30,15 +30,15 @@ function setupCallout() { const title = div.firstElementChild if (title) { - title.removeEventListener(`click`, toggleCallout) - title.addEventListener(`click`, toggleCallout) + title.addEventListener("click", toggleCallout) + window.addCleanup(() => title.removeEventListener("click", toggleCallout)) - const collapsed = div.classList.contains(`is-collapsed`) + const collapsed = div.classList.contains("is-collapsed") const height = collapsed ? title.scrollHeight : div.scrollHeight - div.style.maxHeight = height + `px` + div.style.maxHeight = height + "px" } } } -document.addEventListener(`nav`, setupCallout) -window.addEventListener(`resize`, setupCallout) +document.addEventListener("nav", setupCallout) +window.addEventListener("resize", setupCallout) diff --git a/quartz/components/scripts/darkmode.inline.ts b/quartz/components/scripts/darkmode.inline.ts index 86735e396..4c671aa41 100644 --- a/quartz/components/scripts/darkmode.inline.ts +++ b/quartz/components/scripts/darkmode.inline.ts @@ -19,8 +19,8 @@ document.addEventListener("nav", () => { // Darkmode toggle const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement - toggleSwitch.removeEventListener("change", switchTheme) toggleSwitch.addEventListener("change", switchTheme) + window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) if (currentTheme === "dark") { toggleSwitch.checked = true } diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index 12546bbb0..3eb25ead4 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -57,20 +57,20 @@ function setupExplorer() { for (const item of document.getElementsByClassName( "folder-button", ) as HTMLCollectionOf) { - item.removeEventListener("click", toggleFolder) item.addEventListener("click", toggleFolder) + window.addCleanup(() => item.removeEventListener("click", toggleFolder)) } } - explorer.removeEventListener("click", toggleExplorer) explorer.addEventListener("click", toggleExplorer) + window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer)) // Set up click handlers for each folder (click handler on folder "icon") for (const item of document.getElementsByClassName( "folder-icon", ) as HTMLCollectionOf) { - item.removeEventListener("click", toggleFolder) item.addEventListener("click", toggleFolder) + window.addCleanup(() => item.removeEventListener("click", toggleFolder)) } // Get folder state from local storage diff --git a/quartz/components/scripts/graph.inline.ts b/quartz/components/scripts/graph.inline.ts index a76409c13..c991e163e 100644 --- a/quartz/components/scripts/graph.inline.ts +++ b/quartz/components/scripts/graph.inline.ts @@ -325,6 +325,6 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { await renderGraph("graph-container", slug) const containerIcon = document.getElementById("global-graph-icon") - containerIcon?.removeEventListener("click", renderGlobalGraph) containerIcon?.addEventListener("click", renderGlobalGraph) + window.addCleanup(() => containerIcon?.removeEventListener("click", renderGlobalGraph)) }) diff --git a/quartz/components/scripts/popover.inline.ts b/quartz/components/scripts/popover.inline.ts index 4d51e2a6f..0251834cb 100644 --- a/quartz/components/scripts/popover.inline.ts +++ b/quartz/components/scripts/popover.inline.ts @@ -76,7 +76,7 @@ async function mouseEnterHandler( document.addEventListener("nav", () => { const links = [...document.getElementsByClassName("internal")] as HTMLLinkElement[] for (const link of links) { - link.removeEventListener("mouseenter", mouseEnterHandler) link.addEventListener("mouseenter", mouseEnterHandler) + window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler)) } }) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 8ead5c952..797685adf 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -13,14 +13,13 @@ interface Item { // Can be expanded with things like "term" in the future type SearchType = "basic" | "tags" - -// Current searchType let searchType: SearchType = "basic" -// Current search term // TODO: exact match let currentSearchTerm: string = "" -// index for search let index: FlexSearch.Document | undefined = undefined +const p = new DOMParser() +const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/) +const fetchContentCache: Map = new Map() const contextWindowWords = 30 const numSearchResults = 8 const numTagResults = 5 @@ -79,7 +78,6 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { } function highlightHTML(searchTerm: string, el: HTMLElement) { - // try to highlight longest tokens first const p = new DOMParser() const tokenizedTerms = tokenizeTerm(searchTerm) const html = p.parseFromString(el.innerHTML, "text/html") @@ -117,12 +115,6 @@ function highlightHTML(searchTerm: string, el: HTMLElement) { return html.body } -const p = new DOMParser() -const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/) -let prevShortcutHandler: ((e: HTMLElementEventMap["keydown"]) => void) | undefined = undefined - -const fetchContentCache: Map = new Map() - document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const currentSlug = e.detail.url @@ -496,16 +488,12 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { await displayResults(finalResults) } - if (prevShortcutHandler) { - document.removeEventListener("keydown", prevShortcutHandler) - } - document.addEventListener("keydown", shortcutHandler) - prevShortcutHandler = shortcutHandler - searchIcon?.removeEventListener("click", () => showSearch("basic")) + window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler)) searchIcon?.addEventListener("click", () => showSearch("basic")) - searchBar?.removeEventListener("input", onType) + window.addCleanup(() => searchIcon?.removeEventListener("click", () => showSearch("basic"))) searchBar?.addEventListener("input", onType) + window.addCleanup(() => searchBar?.removeEventListener("input", onType)) // setup index if it hasn't been already if (!index) { @@ -546,13 +534,12 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { async function fillDocument(index: FlexSearch.Document, data: any) { let id = 0 for (const [slug, fileData] of Object.entries(data)) { - await index.addAsync(id, { + await index.addAsync(id++, { id, slug: slug as FullSlug, title: fileData.title, content: fileData.content, tags: fileData.tags, }) - id++ } } diff --git a/quartz/components/scripts/spa.inline.ts b/quartz/components/scripts/spa.inline.ts index c2a44c9a8..1790bcabc 100644 --- a/quartz/components/scripts/spa.inline.ts +++ b/quartz/components/scripts/spa.inline.ts @@ -39,6 +39,9 @@ function notifyNav(url: FullSlug) { document.dispatchEvent(event) } +const cleanupFns: Set<(...args: any[]) => void> = new Set() +window.addCleanup = (fn) => cleanupFns.add(fn) + let p: DOMParser async function navigate(url: URL, isBack: boolean = false) { p = p || new DOMParser() @@ -57,6 +60,10 @@ async function navigate(url: URL, isBack: boolean = false) { if (!contents) return + // cleanup old + cleanupFns.forEach((fn) => fn()) + cleanupFns.clear() + const html = p.parseFromString(contents, "text/html") normalizeRelativeURLs(html, url) diff --git a/quartz/components/scripts/toc.inline.ts b/quartz/components/scripts/toc.inline.ts index 2e1e52b0e..546859ed3 100644 --- a/quartz/components/scripts/toc.inline.ts +++ b/quartz/components/scripts/toc.inline.ts @@ -29,8 +29,8 @@ function setupToc() { const content = toc.nextElementSibling as HTMLElement | undefined if (!content) return content.style.maxHeight = collapsed ? "0px" : content.scrollHeight + "px" - toc.removeEventListener("click", toggleToc) toc.addEventListener("click", toggleToc) + window.addCleanup(() => toc.removeEventListener("click", toggleToc)) } } diff --git a/quartz/components/scripts/util.ts b/quartz/components/scripts/util.ts index 5fcabadc1..4ffff29e2 100644 --- a/quartz/components/scripts/util.ts +++ b/quartz/components/scripts/util.ts @@ -12,10 +12,10 @@ export function registerEscapeHandler(outsideContainer: HTMLElement | null, cb: cb() } - outsideContainer?.removeEventListener("click", click) outsideContainer?.addEventListener("click", click) - document.removeEventListener("keydown", esc) + window.addCleanup(() => outsideContainer?.removeEventListener("click", click)) document.addEventListener("keydown", esc) + window.addCleanup(() => document.removeEventListener("keydown", esc)) } export function removeAllChildren(node: HTMLElement) { diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index accc2611e..5eb9718a9 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -131,9 +131,11 @@ function addGlobalPageResources( componentResources.afterDOMLoaded.push(spaRouterScript) } else { componentResources.afterDOMLoaded.push(` - window.spaNavigate = (url, _) => window.location.assign(url) - const event = new CustomEvent("nav", { detail: { url: document.body.dataset.slug } }) - document.dispatchEvent(event)`) + window.spaNavigate = (url, _) => window.location.assign(url) + window.addCleanup = () => {} + const event = new CustomEvent("nav", { detail: { url: document.body.dataset.slug } }) + document.dispatchEvent(event) + `) } let wsUrl = `ws://localhost:${ctx.argv.wsPort}` @@ -147,9 +149,9 @@ function addGlobalPageResources( loadTime: "afterDOMReady", contentType: "inline", script: ` - const socket = new WebSocket('${wsUrl}') - socket.addEventListener('message', () => document.location.reload()) - `, + const socket = new WebSocket('${wsUrl}') + socket.addEventListener('message', () => document.location.reload()) + `, }) } } From c0c0b24138c6718a7bc91926c7e4dd074845e620 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Feb 2024 21:15:28 -0800 Subject: [PATCH 28/45] feat: improve search preview styling and tokenization --- quartz/components/scripts/search.inline.ts | 62 ++++++++++++---------- quartz/components/styles/search.scss | 11 ++-- quartz/styles/base.scss | 2 +- quartz/styles/variables.scss | 2 +- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 797685adf..82fdf826b 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -24,14 +24,20 @@ const contextWindowWords = 30 const numSearchResults = 8 const numTagResults = 5 -const tokenizeTerm = (term: string) => - term - .split(/\s+/) - .filter((t) => t !== "") - .sort((a, b) => b.length - a.length) +const tokenizeTerm = (term: string) => { + const tokens = term.split(/\s+/).filter((t) => t.trim() !== "") + + const tokenLen = tokens.length + if (tokenLen > 1) { + for (let i = 1; i < tokenLen; i++) { + tokens.push(tokens.slice(0, i + 1).join(" ")) + } + } + + return tokens.sort((a, b) => b.length - a.length) // always highlight longest terms first +} function highlight(searchTerm: string, text: string, trim?: boolean) { - // try to highlight longest tokens first const tokenizedTerms = tokenizeTerm(searchTerm) let tokenizedText = text.split(/\s+/).filter((t) => t !== "") @@ -69,7 +75,6 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { } return tok }) - .slice(startIndex, endIndex + 1) .join(" ") return `${startIndex === 0 ? "" : "..."}${slice}${ @@ -89,29 +94,32 @@ function highlightHTML(searchTerm: string, el: HTMLElement) { return span } - const highlightTextNodes = (node: Node) => { + const highlightTextNodes = (node: Node, term: string) => { if (node.nodeType === Node.TEXT_NODE) { - let nodeText = node.nodeValue || "" - tokenizedTerms.forEach((term) => { - const regex = new RegExp(term.toLowerCase(), "gi") - const matches = nodeText.match(regex) - const spanContainer = document.createElement("span") - let lastIndex = 0 - matches?.forEach((match) => { - const matchIndex = nodeText.indexOf(match, lastIndex) - spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex, matchIndex))) - spanContainer.appendChild(createHighlightSpan(match)) - lastIndex = matchIndex + match.length - }) - spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex))) - node.parentNode?.replaceChild(spanContainer, node) - }) + const nodeText = node.nodeValue ?? "" + const regex = new RegExp(term.toLowerCase(), "gi") + const matches = nodeText.match(regex) + if (!matches || matches.length === 0) return + const spanContainer = document.createElement("span") + let lastIndex = 0 + for (const match of matches) { + const matchIndex = nodeText.indexOf(match, lastIndex) + spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex, matchIndex))) + spanContainer.appendChild(createHighlightSpan(match)) + lastIndex = matchIndex + match.length + } + spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex))) + node.parentNode?.replaceChild(spanContainer, node) } else if (node.nodeType === Node.ELEMENT_NODE) { - Array.from(node.childNodes).forEach(highlightTextNodes) + if ((node as HTMLElement).classList.contains("highlight")) return + Array.from(node.childNodes).forEach((child) => highlightTextNodes(child, term)) } } - highlightTextNodes(html.body) + for (const term of tokenizedTerms) { + highlightTextNodes(html.body, term) + } + return html.body } @@ -137,13 +145,13 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { let previewInner: HTMLDivElement | undefined = undefined const results = document.createElement("div") results.id = "results-container" - results.style.flexBasis = enablePreview ? "30%" : "100%" + results.style.flexBasis = enablePreview ? "min(30%, 450px)" : "100%" appendLayout(results) if (enablePreview) { preview = document.createElement("div") preview.id = "preview-container" - preview.style.flexBasis = "70%" + preview.style.flexBasis = "100%" appendLayout(preview) } diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 11e7c4e14..0a763ec55 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -54,7 +54,7 @@ } & > #search-space { - width: 75%; + width: 65%; margin-top: 12vh; margin-left: auto; margin-right: auto; @@ -85,7 +85,6 @@ & > #search-layout { display: flex; flex-direction: row; - justify-content: space-between; opacity: 0; border: 1px solid var(--lightgray); @@ -106,7 +105,7 @@ } } - @media all and (max-width: $mobileBreakpoint) { + @media all and (max-width: $tabletBreakpoint) { display: block; & > *:not(#results-container) { display: none !important; @@ -119,8 +118,8 @@ } & .highlight { - color: var(--secondary); - font-weight: 700; + background: color-mix(in srgb, var(--tertiary) 60%, transparent); + border-radius: 5px; } & > #preview-container { @@ -129,8 +128,10 @@ overflow: hidden; & .preview-inner { + margin: 0 auto; padding: 1em; height: 100%; + width: 100%; box-sizing: border-box; overflow-y: auto; font-family: inherit; diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index fc1db9581..0fa7a5567 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -26,7 +26,7 @@ section { } ::selection { - background: color-mix(in srgb, var(--tertiary) 75%, transparent); + background: color-mix(in srgb, var(--tertiary) 60%, transparent); color: var(--darkgray); } diff --git a/quartz/styles/variables.scss b/quartz/styles/variables.scss index 30004aa7b..db43f8ed9 100644 --- a/quartz/styles/variables.scss +++ b/quartz/styles/variables.scss @@ -1,6 +1,6 @@ $pageWidth: 750px; $mobileBreakpoint: 600px; -$tabletBreakpoint: 1200px; +$tabletBreakpoint: 1000px; $sidePanelWidth: 380px; $topSpacing: 6rem; $fullPageWidth: $pageWidth + 2 * $sidePanelWidth; From e9fb0ecb96a2de53cf5f060c4e151f539ca4b089 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Feb 2024 21:19:48 -0800 Subject: [PATCH 29/45] fix: border radius on search preview --- quartz/components/styles/search.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 0a763ec55..784c114c1 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -139,7 +139,8 @@ line-height: 1.5em; font-weight: 400; background: var(--light); - border-radius: 5px; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; box-shadow: 0 14px 50px rgba(27, 33, 48, 0.12), 0 10px 30px rgba(27, 33, 48, 0.16); From 45b93a80f4538b43bf71993d05902308db786051 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Feb 2024 22:22:06 -0800 Subject: [PATCH 30/45] fix: index setup, styling fixes --- quartz/components/scripts/search.inline.ts | 63 +++++++++++----------- quartz/components/styles/search.scss | 2 +- quartz/plugins/emitters/contentIndex.ts | 1 - 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 82fdf826b..55919cdc6 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -128,6 +128,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const data = await fetchData const container = document.getElementById("search-container") + const searchSpace = document.getElementById("search-space") const sidebar = container?.closest(".sidebar") as HTMLElement const searchIcon = document.getElementById("search-icon") const searchBar = document.getElementById("search-bar") as HTMLInputElement | null @@ -170,7 +171,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { removeAllChildren(preview) } if (searchLayout) { - searchLayout.style.opacity = "0" + searchLayout.style.visibility = "hidden" } searchType = "basic" // reset search type after closing @@ -449,11 +450,11 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { currentSearchTerm = (e.target as HTMLInputElement).value if (searchLayout) { - searchLayout.style.opacity = "1" + searchLayout.style.visibility = "visible" } if (term === "" && searchLayout) { - searchLayout.style.opacity = "0" + searchLayout.style.visibility = "hidden" } if (term.toLowerCase().startsWith("#")) { @@ -503,35 +504,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { searchBar?.addEventListener("input", onType) window.addCleanup(() => searchBar?.removeEventListener("input", onType)) - // setup index if it hasn't been already - if (!index) { - index = new FlexSearch.Document({ - charset: "latin:extra", - encode: encoder, - document: { - id: "id", - index: [ - { - field: "title", - tokenize: "forward", - }, - { - field: "content", - tokenize: "forward", - }, - { - field: "tags", - tokenize: "forward", - }, - ], - }, - }) - - fillDocument(index, data) - } - - // register handlers - registerEscapeHandler(container, hideSearch) + index ??= await fillDocument(data) + registerEscapeHandler(searchSpace, hideSearch) }) /** @@ -539,7 +513,28 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { * @param index index to fill * @param data data to fill index with */ -async function fillDocument(index: FlexSearch.Document, data: any) { +async function fillDocument(data: { [key: FullSlug]: ContentDetails }) { + const index = new FlexSearch.Document({ + charset: "latin:extra", + encode: encoder, + document: { + id: "id", + index: [ + { + field: "title", + tokenize: "forward", + }, + { + field: "content", + tokenize: "forward", + }, + { + field: "tags", + tokenize: "forward", + }, + ], + }, + }) let id = 0 for (const [slug, fileData] of Object.entries(data)) { await index.addAsync(id++, { @@ -550,4 +545,6 @@ async function fillDocument(index: FlexSearch.Document, data: any) tags: fileData.tags, }) } + + return index } diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 784c114c1..0e6ecb580 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -85,7 +85,7 @@ & > #search-layout { display: flex; flex-direction: row; - opacity: 0; + visibility: hidden; border: 1px solid var(--lightgray); & > div { diff --git a/quartz/plugins/emitters/contentIndex.ts b/quartz/plugins/emitters/contentIndex.ts index 31e1d3e2a..5a0bed914 100644 --- a/quartz/plugins/emitters/contentIndex.ts +++ b/quartz/plugins/emitters/contentIndex.ts @@ -5,7 +5,6 @@ import { escapeHTML } from "../../util/escape" import { FilePath, FullSlug, SimpleSlug, joinSegments, simplifySlug } from "../../util/path" import { QuartzEmitterPlugin } from "../types" import { toHtml } from "hast-util-to-html" -import path from "path" import { write } from "./helpers" export type ContentIndex = Map From 9b8e0c9d1aa5857db3d27bfae229c03b2c8a8b59 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Feb 2024 23:55:11 -0800 Subject: [PATCH 31/45] chore(cleanup): misc refactoring for cleanup, fix some search bugs --- quartz/components/scripts/clipboard.inline.ts | 6 +- quartz/components/scripts/darkmode.inline.ts | 21 +- quartz/components/scripts/search.inline.ts | 201 ++++++------------ quartz/components/styles/search.scss | 47 ++-- 4 files changed, 102 insertions(+), 173 deletions(-) diff --git a/quartz/components/scripts/clipboard.inline.ts b/quartz/components/scripts/clipboard.inline.ts index c604c9bc5..87182a154 100644 --- a/quartz/components/scripts/clipboard.inline.ts +++ b/quartz/components/scripts/clipboard.inline.ts @@ -14,7 +14,7 @@ document.addEventListener("nav", () => { button.type = "button" button.innerHTML = svgCopy button.ariaLabel = "Copy source" - button.addEventListener("click", () => { + function onClick() { navigator.clipboard.writeText(source).then( () => { button.blur() @@ -26,7 +26,9 @@ document.addEventListener("nav", () => { }, (error) => console.error(error), ) - }) + } + button.addEventListener("click", onClick) + window.addCleanup(() => button.removeEventListener("click", onClick)) els[i].prepend(button) } } diff --git a/quartz/components/scripts/darkmode.inline.ts b/quartz/components/scripts/darkmode.inline.ts index 4c671aa41..48e0aa1f5 100644 --- a/quartz/components/scripts/darkmode.inline.ts +++ b/quartz/components/scripts/darkmode.inline.ts @@ -10,13 +10,21 @@ const emitThemeChangeEvent = (theme: "light" | "dark") => { } document.addEventListener("nav", () => { - const switchTheme = (e: any) => { - const newTheme = e.target.checked ? "dark" : "light" + const switchTheme = (e: Event) => { + const newTheme = (e.target as HTMLInputElement)?.checked ? "dark" : "light" document.documentElement.setAttribute("saved-theme", newTheme) localStorage.setItem("theme", newTheme) emitThemeChangeEvent(newTheme) } + const themeChange = (e: MediaQueryListEvent) => { + const newTheme = e.matches ? "dark" : "light" + document.documentElement.setAttribute("saved-theme", newTheme) + localStorage.setItem("theme", newTheme) + toggleSwitch.checked = e.matches + emitThemeChangeEvent(newTheme) + } + // Darkmode toggle const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement toggleSwitch.addEventListener("change", switchTheme) @@ -27,11 +35,6 @@ document.addEventListener("nav", () => { // Listen for changes in prefers-color-scheme const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)") - colorSchemeMediaQuery.addEventListener("change", (e) => { - const newTheme = e.matches ? "dark" : "light" - document.documentElement.setAttribute("saved-theme", newTheme) - localStorage.setItem("theme", newTheme) - toggleSwitch.checked = e.matches - emitThemeChangeEvent(newTheme) - }) + colorSchemeMediaQuery.addEventListener("change", themeChange) + window.addCleanup(() => colorSchemeMediaQuery.removeEventListener("change", themeChange)) }) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 55919cdc6..c960f5e47 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -26,7 +26,6 @@ const numTagResults = 5 const tokenizeTerm = (term: string) => { const tokens = term.split(/\s+/).filter((t) => t.trim() !== "") - const tokenLen = tokens.length if (tokenLen > 1) { for (let i = 1; i < tokenLen; i++) { @@ -77,15 +76,14 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { }) .join(" ") - return `${startIndex === 0 ? "" : "..."}${slice}${ - endIndex === tokenizedText.length - 1 ? "" : "..." - }` + return `${startIndex === 0 ? "" : "..."}${slice}${endIndex === tokenizedText.length - 1 ? "" : "..." + }` } -function highlightHTML(searchTerm: string, el: HTMLElement) { +function highlightHTML(searchTerm: string, innerHTML: string) { const p = new DOMParser() const tokenizedTerms = tokenizeTerm(searchTerm) - const html = p.parseFromString(el.innerHTML, "text/html") + const html = p.parseFromString(innerHTML, "text/html") const createHighlightSpan = (text: string) => { const span = document.createElement("span") @@ -125,10 +123,8 @@ function highlightHTML(searchTerm: string, el: HTMLElement) { document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const currentSlug = e.detail.url - const data = await fetchData const container = document.getElementById("search-container") - const searchSpace = document.getElementById("search-space") const sidebar = container?.closest(".sidebar") as HTMLElement const searchIcon = document.getElementById("search-icon") const searchBar = document.getElementById("search-bar") as HTMLInputElement | null @@ -193,6 +189,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { e.preventDefault() const searchBarOpen = container?.classList.contains("active") searchBarOpen ? hideSearch() : showSearch("basic") + return } else if (e.shiftKey && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") { // Hotkey to open tag search e.preventDefault() @@ -201,6 +198,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { // add "#" prefix for tag search if (searchBar) searchBar.value = "#" + return } if (currentHover) { @@ -262,69 +260,29 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { } } - function trimContent(content: string) { - // works without escaping html like in `description.ts` - const sentences = content.replace(/\s+/g, " ").split(".") - let finalDesc = "" - let sentenceIdx = 0 - - // Roughly estimate characters by (words * 5). Matches description length in `description.ts`. - const len = contextWindowWords * 5 - while (finalDesc.length < len) { - const sentence = sentences[sentenceIdx] - if (!sentence) break - finalDesc += sentence + "." - sentenceIdx++ - } - - // If more content would be available, indicate it by finishing with "..." - if (finalDesc.length < content.length) { - finalDesc += ".." - } - - return finalDesc - } - const formatForDisplay = (term: string, id: number) => { const slug = idDataMap[id] return { id, slug, title: searchType === "tags" ? data[slug].title : highlight(term, data[slug].title ?? ""), - // if searchType is tag, display context from start of file and trim, otherwise use regular highlight - content: - searchType === "tags" - ? trimContent(data[slug].content) - : highlight(term, data[slug].content ?? "", true), - tags: highlightTags(term, data[slug].tags), + content: highlight(term, data[slug].content ?? "", true), + tags: highlightTags(term.substring(1), data[slug].tags), } } function highlightTags(term: string, tags: string[]) { - if (tags && searchType === "tags") { - // Find matching tags - const termLower = term.toLowerCase() - let matching = tags.filter((str) => str.includes(termLower)) - - // Subtract matching from original tags, then push difference - if (matching.length > 0) { - let difference = tags.filter((x) => !matching.includes(x)) - - // Convert to html (cant be done later as matches/term dont get passed to `resultToHTML`) - matching = matching.map((tag) => `
  • #${tag}

  • `) - difference = difference.map((tag) => `
  • #${tag}

  • `) - matching.push(...difference) - } - - // Only allow max of `numTagResults` in preview - if (tags.length > numTagResults) { - matching.splice(numTagResults) - } - - return matching - } else { + if (!tags || searchType !== "tags") { return [] } + + return tags.map(tag => { + if (tag.toLowerCase().includes(term.toLowerCase())) { + return `
  • #${tag}

  • ` + } else { + return `
  • #${tag}

  • ` + } + }).slice(0, numTagResults) } function resolveUrl(slug: FullSlug): URL { @@ -332,34 +290,26 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { } const resultToHTML = ({ slug, title, content, tags }: Item) => { - const htmlTags = tags.length > 0 ? `
      ${tags.join("")}
    ` : `` - const resultContent = enablePreview && window.innerWidth > 600 ? "" : `

    ${content}

    ` - + const htmlTags = tags.length > 0 ? `
      ${tags.join("")}
    ` : `` const itemTile = document.createElement("a") itemTile.classList.add("result-card") - Object.assign(itemTile, { - id: slug, - href: resolveUrl(slug).toString(), - innerHTML: `

    ${title}

    ${htmlTags}${resultContent}`, - }) + itemTile.id = slug + itemTile.href = resolveUrl(slug).toString() + itemTile.innerHTML = `

    ${title}

    ${htmlTags}

    ${content}

    ` async function onMouseEnter(ev: MouseEvent) { - // Actually when we hover, we need to clean all highlights within the result childs if (!ev.target) return - for (const el of document.getElementsByClassName( - "result-card", - ) as HTMLCollectionOf) { - el.classList.remove("focus") - el.blur() - } + currentHover?.classList.remove('focus') + currentHover?.blur() const target = ev.target as HTMLInputElement await displayPreview(target) currentHover = target - currentHover.classList.remove("focus") + currentHover.classList.add("focus") } async function onMouseLeave(ev: MouseEvent) { - const target = ev.target as HTMLAnchorElement + if (!ev.target) return + const target = ev.target as HTMLElement target.classList.remove("focus") } @@ -373,9 +323,12 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { hideSearch() }, ], - ] as [keyof HTMLElementEventMap, (this: HTMLElement) => void][] + ] as const - events.forEach(([event, handler]) => itemTile.addEventListener(event, handler)) + events.forEach(([event, handler]) => { + itemTile.addEventListener(event, handler) + window.addCleanup(() => itemTile.removeEventListener(event, handler)) + }) return itemTile } @@ -386,22 +339,22 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { removeAllChildren(results) if (finalResults.length === 0) { results.innerHTML = ` -

    No results.

    -

    Try another search term?

    -
    ` +

    No results.

    +

    Try another search term?

    + ` } else { results.append(...finalResults.map(resultToHTML)) } - // focus on first result, then also dispatch preview immediately - if (results?.firstElementChild) { + + if (finalResults.length === 0 && preview) { + // no results, clear previous preview + removeAllChildren(preview) + } else { + // focus on first result, then also dispatch preview immediately const firstChild = results.firstElementChild as HTMLElement - if (firstChild.classList.contains("no-match")) { - removeAllChildren(preview as HTMLElement) - } else { - firstChild.classList.add("focus") - currentHover = firstChild as HTMLInputElement - await displayPreview(firstChild) - } + firstChild.classList.add("focus") + currentHover = firstChild as HTMLInputElement + await displayPreview(firstChild) } } @@ -427,59 +380,41 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { } async function displayPreview(el: HTMLElement | null) { - if (!searchLayout || !enablePreview || !el) return - + if (!searchLayout || !enablePreview || !el || !preview) return const slug = el.id as FullSlug el.classList.add("focus") - - removeAllChildren(preview as HTMLElement) - previewInner = document.createElement("div") previewInner.classList.add("preview-inner") - preview?.appendChild(previewInner) - const innerDiv = await fetchContent(slug).then((contents) => - contents.map((el) => highlightHTML(currentSearchTerm, el as HTMLElement)), + contents.map((el) => highlightHTML(currentSearchTerm, el.innerHTML)), ) previewInner.append(...innerDiv) + preview.replaceChildren(previewInner) + + // scroll to longest + const highlights = [...preview.querySelectorAll(".highlight")].sort((a, b) => b.innerHTML.length - a.innerHTML.length) + highlights[0]?.scrollIntoView() } async function onType(e: HTMLElementEventMap["input"]) { - let term = (e.target as HTMLInputElement).value - let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] + if (!searchLayout || !index) return currentSearchTerm = (e.target as HTMLInputElement).value + searchLayout.style.visibility = currentSearchTerm === "" ? "hidden" : "visible" + searchType = currentSearchTerm.startsWith("#") ? "tags" : "basic" - if (searchLayout) { - searchLayout.style.visibility = "visible" - } - - if (term === "" && searchLayout) { - searchLayout.style.visibility = "hidden" - } - - if (term.toLowerCase().startsWith("#")) { - searchType = "tags" - } else { - searchType = "basic" - } - - switch (searchType) { - case "tags": { - term = term.substring(1) - searchResults = - (await index?.searchAsync({ query: term, limit: numSearchResults, index: ["tags"] })) ?? - [] - break - } - case "basic": - default: { - searchResults = - (await index?.searchAsync({ - query: term, - limit: numSearchResults, - index: ["title", "content"], - })) ?? [] - } + let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] + if (searchType === "tags") { + searchResults = await index.searchAsync({ + query: currentSearchTerm.substring(1), + limit: numSearchResults, + index: ["tags"], + }) + } else if (searchType === "basic") { + searchResults = await index.searchAsync({ + query: currentSearchTerm, + limit: numSearchResults, + index: ["title", "content"], + }) } const getByField = (field: string): number[] => { @@ -493,7 +428,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { ...getByField("content"), ...getByField("tags"), ]) - const finalResults = [...allIds].map((id) => formatForDisplay(term, id)) + const finalResults = [...allIds].map((id) => formatForDisplay(currentSearchTerm, id)) await displayResults(finalResults) } @@ -505,7 +440,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { window.addCleanup(() => searchBar?.removeEventListener("input", onType)) index ??= await fillDocument(data) - registerEscapeHandler(searchSpace, hideSearch) + registerEscapeHandler(container, hideSearch) }) /** diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 0e6ecb580..df4f5bab5 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -88,6 +88,14 @@ visibility: hidden; border: 1px solid var(--lightgray); + @media all and (min-width: $tabletBreakpoint) { + &[data-preview] { + & .result-card > p.preview { + display: none; + } + } + } + & > div { // vh - #search-space.margin-top height: calc(75vh - 12vh); @@ -174,7 +182,6 @@ outline: none; font-weight: inherit; - &:hover, &:focus, &.focus { background: var(--lightgray); @@ -184,41 +191,23 @@ margin: 0; } - & > ul > li { - margin: 0; - display: inline-block; - white-space: nowrap; - margin: 0; - overflow-wrap: normal; - } - - & > ul { - list-style: none; - display: flex; - padding-left: 0; - gap: 0.4rem; - margin: 0; + & > ul.tags { margin-top: 0.45rem; - box-sizing: border-box; - overflow: hidden; - background-clip: border-box; + margin-bottom: 0; } & > ul > li > p { border-radius: 8px; background-color: var(--highlight); - overflow: hidden; - background-clip: border-box; - padding: 0.03rem 0.4rem; - margin: 0; - color: var(--secondary); - opacity: 0.85; - } - - & > ul > li > .match-tag { - color: var(--tertiary); + padding: 0.2rem 0.4rem; + margin: 0 0.1rem; + line-height: 1.4rem; font-weight: bold; - opacity: 1; + color: var(--secondary); + + &.match-tag { + color: var(--tertiary); + } } & > p { From dc62aeb213aa68051aaaf3ddc2f25be4e4d6d466 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Feb 2024 23:55:40 -0800 Subject: [PATCH 32/45] pkg: bump to 4.2.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef9313491..2d0468c4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackyzha0/quartz", - "version": "4.1.6", + "version": "4.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.1.6", + "version": "4.2.0", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", diff --git a/package.json b/package.json index ee4810f50..fb4175e63 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@jackyzha0/quartz", "description": "🌱 publish your digital garden and notes as a website", "private": true, - "version": "4.1.6", + "version": "4.2.0", "type": "module", "author": "jackyzha0 ", "license": "MIT", From 970a30a139953c8d58705474b7910a64153e9466 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Feb 2024 23:57:17 -0800 Subject: [PATCH 33/45] chore: fmt --- quartz/components/scripts/search.inline.ts | 27 +++++++++++++--------- quartz/components/styles/search.scss | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index c960f5e47..17f3e1ba8 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -76,8 +76,9 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { }) .join(" ") - return `${startIndex === 0 ? "" : "..."}${slice}${endIndex === tokenizedText.length - 1 ? "" : "..." - }` + return `${startIndex === 0 ? "" : "..."}${slice}${ + endIndex === tokenizedText.length - 1 ? "" : "..." + }` } function highlightHTML(searchTerm: string, innerHTML: string) { @@ -276,13 +277,15 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { return [] } - return tags.map(tag => { - if (tag.toLowerCase().includes(term.toLowerCase())) { - return `
  • #${tag}

  • ` - } else { - return `
  • #${tag}

  • ` - } - }).slice(0, numTagResults) + return tags + .map((tag) => { + if (tag.toLowerCase().includes(term.toLowerCase())) { + return `
  • #${tag}

  • ` + } else { + return `
  • #${tag}

  • ` + } + }) + .slice(0, numTagResults) } function resolveUrl(slug: FullSlug): URL { @@ -299,7 +302,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { async function onMouseEnter(ev: MouseEvent) { if (!ev.target) return - currentHover?.classList.remove('focus') + currentHover?.classList.remove("focus") currentHover?.blur() const target = ev.target as HTMLInputElement await displayPreview(target) @@ -392,7 +395,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { preview.replaceChildren(previewInner) // scroll to longest - const highlights = [...preview.querySelectorAll(".highlight")].sort((a, b) => b.innerHTML.length - a.innerHTML.length) + const highlights = [...preview.querySelectorAll(".highlight")].sort( + (a, b) => b.innerHTML.length - a.innerHTML.length, + ) highlights[0]?.scrollIntoView() } diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index df4f5bab5..b4bb64a00 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -204,7 +204,7 @@ line-height: 1.4rem; font-weight: bold; color: var(--secondary); - + &.match-tag { color: var(--tertiary); } From 3b596c9311fd5fe552d6c53e9b27841932a26587 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 00:19:19 -0800 Subject: [PATCH 34/45] fix: flatmap children when highlighting rich preview to avoid body --- quartz/components/scripts/search.inline.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 17f3e1ba8..57067d1e3 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -81,10 +81,10 @@ function highlight(searchTerm: string, text: string, trim?: boolean) { }` } -function highlightHTML(searchTerm: string, innerHTML: string) { +function highlightHTML(searchTerm: string, el: HTMLElement) { const p = new DOMParser() const tokenizedTerms = tokenizeTerm(searchTerm) - const html = p.parseFromString(innerHTML, "text/html") + const html = p.parseFromString(el.innerHTML, "text/html") const createHighlightSpan = (text: string) => { const span = document.createElement("span") @@ -389,7 +389,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { previewInner = document.createElement("div") previewInner.classList.add("preview-inner") const innerDiv = await fetchContent(slug).then((contents) => - contents.map((el) => highlightHTML(currentSearchTerm, el.innerHTML)), + contents.flatMap((el) => [...highlightHTML(currentSearchTerm, el as HTMLElement).children]), ) previewInner.append(...innerDiv) preview.replaceChildren(previewInner) From 0416c03ae646acf72422fe615445d07a327cc580 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 00:25:05 -0800 Subject: [PATCH 35/45] fix: be more eager about constructing search index --- quartz/components/scripts/search.inline.ts | 49 ++++++++++------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 57067d1e3..2924f398e 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -15,10 +15,30 @@ interface Item { type SearchType = "basic" | "tags" let searchType: SearchType = "basic" let currentSearchTerm: string = "" -let index: FlexSearch.Document | undefined = undefined -const p = new DOMParser() const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/) +let index = new FlexSearch.Document({ + charset: "latin:extra", + encode: encoder, + document: { + id: "id", + index: [ + { + field: "title", + tokenize: "forward", + }, + { + field: "content", + tokenize: "forward", + }, + { + field: "tags", + tokenize: "forward", + }, + ], + }, +}) +const p = new DOMParser() const fetchContentCache: Map = new Map() const contextWindowWords = 30 const numSearchResults = 8 @@ -444,7 +464,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { searchBar?.addEventListener("input", onType) window.addCleanup(() => searchBar?.removeEventListener("input", onType)) - index ??= await fillDocument(data) + await fillDocument(data) registerEscapeHandler(container, hideSearch) }) @@ -454,27 +474,6 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { * @param data data to fill index with */ async function fillDocument(data: { [key: FullSlug]: ContentDetails }) { - const index = new FlexSearch.Document({ - charset: "latin:extra", - encode: encoder, - document: { - id: "id", - index: [ - { - field: "title", - tokenize: "forward", - }, - { - field: "content", - tokenize: "forward", - }, - { - field: "tags", - tokenize: "forward", - }, - ], - }, - }) let id = 0 for (const [slug, fileData] of Object.entries(data)) { await index.addAsync(id++, { @@ -485,6 +484,4 @@ async function fillDocument(data: { [key: FullSlug]: ContentDetails }) { tags: fileData.tags, }) } - - return index } From 5a36e5b68d9d49ddcbfc87421216bfa6b9d913cf Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 00:29:45 -0800 Subject: [PATCH 36/45] fix(style): reasonable page width for rich search preview --- quartz/components/styles/search.scss | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index b4bb64a00..87033998c 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -134,24 +134,20 @@ display: block; box-sizing: border-box; overflow: hidden; + box-sizing: border-box; + font-family: inherit; + color: var(--dark); + line-height: 1.5em; + font-weight: 400; + background: var(--light); + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + overflow-y: auto; + padding: 1rem; & .preview-inner { margin: 0 auto; - padding: 1em; - height: 100%; - width: 100%; - box-sizing: border-box; - overflow-y: auto; - font-family: inherit; - color: var(--dark); - line-height: 1.5em; - font-weight: 400; - background: var(--light); - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - box-shadow: - 0 14px 50px rgba(27, 33, 48, 0.12), - 0 10px 30px rgba(27, 33, 48, 0.16); + width: min($pageWidth, 100%); } a.internal { From ee868b2d792559171fec6ad7426a196289cd5824 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Fri, 2 Feb 2024 03:35:53 -0500 Subject: [PATCH 37/45] fix(search): set correct attribute on hover icon (#787) Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- quartz/components/styles/search.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 87033998c..aac01bed5 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -151,7 +151,7 @@ } a.internal { - background-color: none; + background-color: inherit; } } From 18cd58617dbe6a1b887ab08e4d29694bb1b3d0e0 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 00:45:02 -0800 Subject: [PATCH 38/45] fix: parallelize search indexing --- quartz/components/scripts/search.inline.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 2924f398e..769483d4e 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -464,8 +464,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { searchBar?.addEventListener("input", onType) window.addCleanup(() => searchBar?.removeEventListener("input", onType)) - await fillDocument(data) registerEscapeHandler(container, hideSearch) + await fillDocument(data) }) /** @@ -475,13 +475,18 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { */ async function fillDocument(data: { [key: FullSlug]: ContentDetails }) { let id = 0 + const promises: Array> = [] for (const [slug, fileData] of Object.entries(data)) { - await index.addAsync(id++, { - id, - slug: slug as FullSlug, - title: fileData.title, - content: fileData.content, - tags: fileData.tags, - }) + promises.push( + index.addAsync(id++, { + id, + slug: slug as FullSlug, + title: fileData.title, + content: fileData.content, + tags: fileData.tags, + }), + ) } + + return await Promise.all(promises) } From 2b57a68e1f9528de3c08633251f37d5755c75698 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 00:53:05 -0800 Subject: [PATCH 39/45] fix: font weight consistency --- quartz/components/styles/explorer.scss | 6 ++++-- quartz/components/styles/search.scss | 4 ++-- quartz/styles/base.scss | 2 +- quartz/styles/callouts.scss | 3 ++- quartz/styles/variables.scss | 2 ++ 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/quartz/components/styles/explorer.scss b/quartz/components/styles/explorer.scss index 304fd4500..34f180cf2 100644 --- a/quartz/components/styles/explorer.scss +++ b/quartz/components/styles/explorer.scss @@ -1,3 +1,5 @@ +@use "../../styles/variables.scss" as *; + button#explorer { all: unset; background-color: transparent; @@ -85,7 +87,7 @@ svg { color: var(--secondary); font-family: var(--headerFont); font-size: 0.95rem; - font-weight: 600; + font-weight: $boldWeight; line-height: 1.5rem; display: inline-block; } @@ -110,7 +112,7 @@ svg { font-size: 0.95rem; display: inline-block; color: var(--secondary); - font-weight: 600; + font-weight: $boldWeight; margin: 0; line-height: 1.5rem; pointer-events: none; diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index aac01bed5..1471a77cb 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -138,7 +138,7 @@ font-family: inherit; color: var(--dark); line-height: 1.5em; - font-weight: 400; + font-weight: $normalWeight; background: var(--light); border-top-right-radius: 5px; border-bottom-right-radius: 5px; @@ -198,7 +198,7 @@ padding: 0.2rem 0.4rem; margin: 0 0.1rem; line-height: 1.4rem; - font-weight: bold; + font-weight: $boldWeight; color: var(--secondary); &.match-tag { diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index 0fa7a5567..a4fde0639 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -54,7 +54,7 @@ ul, } a { - font-weight: 600; + font-weight: $boldWeight; text-decoration: none; transition: color 0.2s ease; color: var(--secondary); diff --git a/quartz/styles/callouts.scss b/quartz/styles/callouts.scss index ee62e39c5..d4f7069a0 100644 --- a/quartz/styles/callouts.scss +++ b/quartz/styles/callouts.scss @@ -1,3 +1,4 @@ +@use "./variables.scss" as *; @use "sass:color"; .callout { @@ -156,6 +157,6 @@ } .callout-title-inner { - font-weight: 700; + font-weight: $boldWeight; } } diff --git a/quartz/styles/variables.scss b/quartz/styles/variables.scss index db43f8ed9..8384b9c4e 100644 --- a/quartz/styles/variables.scss +++ b/quartz/styles/variables.scss @@ -4,3 +4,5 @@ $tabletBreakpoint: 1000px; $sidePanelWidth: 380px; $topSpacing: 6rem; $fullPageWidth: $pageWidth + 2 * $sidePanelWidth; +$boldWeight: 700; +$normalWeight: 400; From d11a0e71a86eae41735d825d88c3bf7d8dcf949e Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 01:01:04 -0800 Subject: [PATCH 40/45] fix: font smoothing defaults --- quartz/styles/base.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index a4fde0639..7bc799ae3 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -7,6 +7,8 @@ html { text-size-adjust: none; overflow-x: hidden; width: 100vw; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } body, From 5ab922f3163a53418cb7d1c72cf7546c848159bc Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 01:15:10 -0800 Subject: [PATCH 41/45] fix(revert): font aliasing --- quartz/styles/base.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index 7bc799ae3..a4fde0639 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -7,8 +7,6 @@ html { text-size-adjust: none; overflow-x: hidden; width: 100vw; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; } body, From a0b927da4aa9bb540b50c875e77f97bd4a7c279a Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 01:24:40 -0800 Subject: [PATCH 42/45] fix: use display instead of visibility for click handling pasthrough --- quartz/components/scripts/search.inline.ts | 4 ++-- quartz/components/styles/search.scss | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 769483d4e..abdef06da 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -188,7 +188,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { removeAllChildren(preview) } if (searchLayout) { - searchLayout.style.visibility = "hidden" + searchLayout.classList.remove("display-results") } searchType = "basic" // reset search type after closing @@ -424,7 +424,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { async function onType(e: HTMLElementEventMap["input"]) { if (!searchLayout || !index) return currentSearchTerm = (e.target as HTMLInputElement).value - searchLayout.style.visibility = currentSearchTerm === "" ? "hidden" : "visible" + searchLayout.classList.toggle("display-results", currentSearchTerm !== "") searchType = currentSearchTerm.startsWith("#") ? "tags" : "basic" let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 1471a77cb..7ede3595c 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -83,11 +83,14 @@ } & > #search-layout { - display: flex; + display: none; flex-direction: row; - visibility: hidden; border: 1px solid var(--lightgray); + &.display-results { + display: flex; + } + @media all and (min-width: $tabletBreakpoint) { &[data-preview] { & .result-card > p.preview { From 3231ce6e7970dd0bc79c33aa8bc09cfa9ab59d09 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 01:36:17 -0800 Subject: [PATCH 43/45] fix: search async ordering, scroll offset --- quartz/components/scripts/search.inline.ts | 16 ++++++++-------- quartz/components/styles/search.scss | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index abdef06da..ec55f96b5 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -224,6 +224,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { if (currentHover) { currentHover.classList.remove("focus") + currentHover.blur() } // If search is active, then we will render the first result and display accordingly @@ -250,9 +251,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { : (document.activeElement as HTMLInputElement | null) const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null currentResult?.classList.remove("focus") - await displayPreview(prevResult) prevResult?.focus() currentHover = prevResult + await displayPreview(prevResult) } } else if (e.key === "ArrowDown" || e.key === "Tab") { e.preventDefault() @@ -264,9 +265,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { : (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null) const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null firstResult?.classList.remove("focus") - await displayPreview(secondResult) secondResult?.focus() currentHover = secondResult + await displayPreview(secondResult) } else { // If an element in results-container already has focus, focus next one const active = currentHover @@ -274,9 +275,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { : (document.activeElement as HTMLInputElement | null) active?.classList.remove("focus") const nextResult = active?.nextElementSibling as HTMLInputElement | null - await displayPreview(nextResult) nextResult?.focus() currentHover = nextResult + await displayPreview(nextResult) } } } @@ -325,9 +326,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { currentHover?.classList.remove("focus") currentHover?.blur() const target = ev.target as HTMLInputElement - await displayPreview(target) currentHover = target currentHover.classList.add("focus") + await displayPreview(target) } async function onMouseLeave(ev: MouseEvent) { @@ -405,12 +406,11 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { async function displayPreview(el: HTMLElement | null) { if (!searchLayout || !enablePreview || !el || !preview) return const slug = el.id as FullSlug - el.classList.add("focus") - previewInner = document.createElement("div") - previewInner.classList.add("preview-inner") const innerDiv = await fetchContent(slug).then((contents) => contents.flatMap((el) => [...highlightHTML(currentSearchTerm, el as HTMLElement).children]), ) + previewInner = document.createElement("div") + previewInner.classList.add("preview-inner") previewInner.append(...innerDiv) preview.replaceChildren(previewInner) @@ -418,7 +418,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { const highlights = [...preview.querySelectorAll(".highlight")].sort( (a, b) => b.innerHTML.length - a.innerHTML.length, ) - highlights[0]?.scrollIntoView() + highlights[0]?.scrollIntoView({ block: "start" }) } async function onType(e: HTMLElementEventMap["input"]) { diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index 7ede3595c..d6202b5a8 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -131,6 +131,7 @@ & .highlight { background: color-mix(in srgb, var(--tertiary) 60%, transparent); border-radius: 5px; + scroll-margin-top: 2rem; } & > #preview-container { From 44da82467ee7077a22f0054b7bc4d0f2a008e2e0 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 01:45:15 -0800 Subject: [PATCH 44/45] fix(style): remove redundant selector --- quartz/components/styles/search.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/quartz/components/styles/search.scss b/quartz/components/styles/search.scss index d6202b5a8..23289d2c1 100644 --- a/quartz/components/styles/search.scss +++ b/quartz/components/styles/search.scss @@ -153,10 +153,6 @@ margin: 0 auto; width: min($pageWidth, 100%); } - - a.internal { - background-color: inherit; - } } & > #results-container { From 34a8dfcd55789090d1a9b019539b8770c6ce98e5 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 2 Feb 2024 01:45:28 -0800 Subject: [PATCH 45/45] pkg: bump to 4.2.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d0468c4c..66a772cde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackyzha0/quartz", - "version": "4.2.0", + "version": "4.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackyzha0/quartz", - "version": "4.2.0", + "version": "4.2.1", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", diff --git a/package.json b/package.json index fb4175e63..d9f0f7bf2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@jackyzha0/quartz", "description": "🌱 publish your digital garden and notes as a website", "private": true, - "version": "4.2.0", + "version": "4.2.1", "type": "module", "author": "jackyzha0 ", "license": "MIT",