An article header
-Some content
+An article header
+Some content
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 9ac527d4a..69bad3c22 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -30,11 +30,11 @@ You can help speed up fixing the problem by either
**Desktop (please complete the following information):**
-- Quartz Version: [e.g. v4.1.2]
-- `node` Version: [e.g. v18.16]
-- `npm` version: [e.g. v10.1.0]
-- OS: [e.g. iOS]
-- Browser [e.g. chrome, safari]
+- Quartz Version: [e.g. v4.1.2]
+- `node` Version: [e.g. v18.16]
+- `npm` version: [e.g. v10.1.0]
+- OS: [e.g. iOS]
+- Browser [e.g. chrome, safari]
**Additional context**
Add any other context about the problem here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 42adb4474..f94fbb333 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,7 +5,7 @@
version: 2
updates:
- - package-ecosystem: "npm"
- directory: "/"
- schedule:
- interval: "weekly"
+ - package-ecosystem: "npm"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/build-only.yml b/.github/workflows/build-only.yml
index 3beb58d73..a9ad54c65 100644
--- a/.github/workflows/build-only.yml
+++ b/.github/workflows/build-only.yml
@@ -1,32 +1,32 @@
name: Quartz - Build only
on:
- workflow_dispatch:
- pull_request:
- # push:
- # branches:
- # - main
+ workflow_dispatch:
+ pull_request:
+ # push:
+ # branches:
+ # - main
permissions:
- contents: read
- pages: write
- id-token: write
+ contents: read
+ pages: write
+ id-token: write
concurrency:
- group: "pages"
- cancel-in-progress: false
+ group: "pages"
+ cancel-in-progress: false
jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Fetch all history for git info
- - uses: actions/setup-node@v4
- with:
- node-version: 18
- - name: Install Dependencies
- run: npm i
- - name: Build Quartz
- run: npx quartz build
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Fetch all history for git info
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ - name: Install Dependencies
+ run: npm i
+ - name: Build Quartz
+ run: npx quartz build
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 82ac46d23..b0fbc7472 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,71 +1,71 @@
name: Quartz - CI Test
on:
- pull_request:
- branches:
- - v4
- push:
- branches:
- - v4
+ pull_request:
+ branches:
+ - v4
+ push:
+ branches:
+ - v4
jobs:
- build-and-test:
- if: ${{ github.repository == 'jackyzha0/quartz' }}
- strategy:
- matrix:
- os: [windows-latest, macos-latest, ubuntu-latest]
- runs-on: ${{ matrix.os }}
- permissions:
- contents: write
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
+ build-and-test:
+ if: ${{ github.repository == 'jackyzha0/quartz' }}
+ strategy:
+ matrix:
+ os: [windows-latest, macos-latest, ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
- - name: Setup Node
- uses: actions/setup-node@v3
- with:
- node-version: 18
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
- - name: Cache dependencies
- uses: actions/cache@v3
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
+ - name: Cache dependencies
+ uses: actions/cache@v3
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
- - run: npm ci
+ - run: npm ci
- - name: Check types and style
- run: npm run check
+ - name: Check types and style
+ run: npm run check
- - name: Test
- run: npm test
+ - name: Test
+ run: npm test
- - name: Ensure Quartz builds, check bundle info
- run: npx quartz build --bundleInfo
+ - name: Ensure Quartz builds, check bundle info
+ run: npx quartz build --bundleInfo
- publish-tag:
- if: ${{ github.repository == 'jackyzha0/quartz' }}
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Setup Node
- uses: actions/setup-node@v3
- with:
- node-version: 18
- - name: Get package version
- run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
- - name: Create release tag
- uses: pkgdeps/git-tag-action@v2
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- github_repo: ${{ github.repository }}
- version: ${{ env.PACKAGE_VERSION }}
- git_commit_sha: ${{ github.sha }}
- git_tag_prefix: "v"
+ publish-tag:
+ if: ${{ github.repository == 'jackyzha0/quartz' }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - name: Get package version
+ run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
+ - name: Create release tag
+ uses: pkgdeps/git-tag-action@v2
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ github_repo: ${{ github.repository }}
+ version: ${{ env.PACKAGE_VERSION }}
+ git_commit_sha: ${{ github.sha }}
+ git_tag_prefix: "v"
diff --git a/.github/workflows/deploy-npm.yml b/.github/workflows/deploy-npm.yml
index 45bd6f7b5..b104af606 100644
--- a/.github/workflows/deploy-npm.yml
+++ b/.github/workflows/deploy-npm.yml
@@ -1,52 +1,52 @@
name: Quartz - Deploy to GitHub Pages (npm)
on:
- workflow_dispatch:
- # push:
- # branches:
- # - main
+ workflow_dispatch:
+ # push:
+ # branches:
+ # - main
permissions:
- contents: read
- pages: write
- id-token: write
+ contents: read
+ pages: write
+ id-token: write
concurrency:
- group: "pages"
- cancel-in-progress: false
+ group: "pages"
+ cancel-in-progress: false
defaults:
- run:
- shell: bash
+ run:
+ shell: bash
jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Fetch all history for git info
- - name: Setup Node
- uses: actions/setup-node@v4
- with:
- node-version: 18
- - name: Install Dependencies
- run: npm i
- - name: Build Quartz
- run: npm run build
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: public
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Fetch all history for git info
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ - name: Install Dependencies
+ run: npm i
+ - name: Build Quartz
+ run: npm run build
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: public
- deploy:
- needs: build
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
+ deploy:
+ needs: build
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/deploy-pnpm.yml b/.github/workflows/deploy-pnpm.yml
index a1a30afeb..0ba7df563 100644
--- a/.github/workflows/deploy-pnpm.yml
+++ b/.github/workflows/deploy-pnpm.yml
@@ -1,63 +1,63 @@
name: Quartz - Deploy to GitHub Pages (pnpm)
on:
- workflow_dispatch:
- push:
- branches:
- - main
+ workflow_dispatch:
+ push:
+ branches:
+ - main
permissions:
- contents: read
- pages: write
- id-token: write
+ contents: read
+ pages: write
+ id-token: write
concurrency:
- group: "pages"
- cancel-in-progress: false
+ group: "pages"
+ cancel-in-progress: false
defaults:
- run:
- shell: bash
+ run:
+ shell: bash
jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Fetch all history for git info
- - name: Setup PNPM
- uses: pnpm/action-setup@v3
- with:
- version: 8
- - name: Get pnpm store directory
- shell: bash
- run: |
- echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - name: Setup pnpm cache
- uses: actions/cache@v4
- with:
- path: ${{ env.STORE_PATH }}
- key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- ${{ runner.os }}-pnpm-store-
- - name: Install dependencies
- run: pnpm install
- - name: Build Quartz Site
- run: pnpm run build
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: public
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Fetch all history for git info
+ - name: Setup PNPM
+ uses: pnpm/action-setup@v3
+ with:
+ version: 8
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+ - name: Setup pnpm cache
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+ - name: Install dependencies
+ run: pnpm install
+ - name: Build Quartz Site
+ run: pnpm run build
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: public
- deploy:
- needs: build
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to GitHub Pages
- uses: actions/deploy-pages@v4
- id: deployment
+ deploy:
+ needs: build
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ uses: actions/deploy-pages@v4
+ id: deployment
diff --git a/.prettierrc b/.prettierrc
index 4a3a8865a..22d134197 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,7 +1,7 @@
{
- "printWidth": 80,
- "quoteProps": "as-needed",
- "trailingComma": "all",
- "tabWidth": 4,
- "semi": false
+ "printWidth": 80,
+ "quoteProps": "as-needed",
+ "trailingComma": "all",
+ "tabWidth": 4,
+ "semi": false
}
diff --git a/README.md b/README.md
index d4ed94a7c..18cf32421 100644
--- a/README.md
+++ b/README.md
@@ -15,21 +15,21 @@ It is powered by [Quartz](https://github.com/jackyzha0/quartz/) and [Obsidian](h
Show/Hide
-- [🌱 Forgetful Notes](#-forgetful-notes)
- - [Contents](#contents)
- - [Screenshots](#screenshots)
- - [Full Width](#full-width)
- - [Slim (light)](#slim-light)
- - [Slim (dark)](#slim-dark)
- - [Features](#features)
- - [Background](#background)
- - [Technology](#technology)
- - [Useful Commands](#useful-commands)
- - [Customization](#customization)
- - [Stylesheets](#stylesheets)
- - [Fonts](#fonts)
- - [Folder Structure](#folder-structure)
- - [License](#license)
+- [🌱 Forgetful Notes](#-forgetful-notes)
+ - [Contents](#contents)
+ - [Screenshots](#screenshots)
+ - [Full Width](#full-width)
+ - [Slim (light)](#slim-light)
+ - [Slim (dark)](#slim-dark)
+ - [Features](#features)
+ - [Background](#background)
+ - [Technology](#technology)
+ - [Useful Commands](#useful-commands)
+ - [Customization](#customization)
+ - [Stylesheets](#stylesheets)
+ - [Fonts](#fonts)
+ - [Folder Structure](#folder-structure)
+ - [License](#license)
+
Some content
+Some content
My favourite number is {opts.favouriteNumber}
} - returnMy favourite number is {opts.favouriteNumber}
- } - - return YourComponent + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -62,21 +62,21 @@ All Quartz components accept the same set of props: ```tsx title="quartz/components/types.ts" // simplified for sake of demonstration export type QuartzComponentProps = { - fileData: QuartzPluginData - cfg: GlobalConfiguration - tree: NodeExample Component
- } + function YourComponent() { + returnExample Component
+ } - YourComponent.css = ` + YourComponent.css = ` p.red-text { color: red; } ` - return YourComponent + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -107,12 +107,12 @@ Imported styles, however, can be from SCSS files: import styles from "./styles/YourComponent.scss" export default (() => { - function YourComponent() { - returnExample Component
- } + function YourComponent() { + returnExample Component
+ } - YourComponent.css = styles - return YourComponent + YourComponent.css = styles + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -125,20 +125,20 @@ What about interactivity? Suppose you want to add an-click handler for example. ```tsx title="quartz/components/YourComponent.tsx" export default (() => { - function YourComponent() { - return - } + function YourComponent() { + return + } - YourComponent.beforeDOM = ` + YourComponent.beforeDOM = ` console.log("hello from before the page loads!") ` - YourComponent.afterDOM = ` + YourComponent.afterDOM = ` document.getElementById('btn').onclick = () => { alert('button clicked!') } ` - return YourComponent + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -153,11 +153,11 @@ If you need to create an `afterDOMLoaded` script that depends on _page specific_ ```ts 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, { passive: true }) + // 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, { passive: true }) }) ``` @@ -176,12 +176,12 @@ Quartz supports importing component code through `.inline.ts` files. import script from "./scripts/graph.inline" export default (() => { - function YourComponent() { - return - } + function YourComponent() { + return + } - YourComponent.afterDOM = script - return YourComponent + YourComponent.afterDOM = script + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -190,7 +190,7 @@ export default (() => { import * as d3 from "d3" document.getElementById("btn").onclick = () => { - alert("button clicked!") + alert("button clicked!") } ``` @@ -217,16 +217,16 @@ As Quartz components are just functions that return React components, you can co import YourComponent from "./YourComponent" export default (() => { - function AnotherComponent(props: QuartzComponentProps) { - return ( -It's nested!
-It's nested!
+- {segmentsElements} -
- ) - } else { - return null + return ( ++ {segmentsElements} +
+ ) + } else { + return null + } } - } - ContentMetadata.css = style + ContentMetadata.css = style - return ContentMetadata + return ContentMetadata }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Darkmode.tsx b/quartz/components/Darkmode.tsx index 8ed7c99b0..cabf7f63f 100644 --- a/quartz/components/Darkmode.tsx +++ b/quartz/components/Darkmode.tsx @@ -3,48 +3,64 @@ // see: https://v8.dev/features/modules#defer import darkmodeScript from "./scripts/darkmode.inline" import styles from "./styles/darkmode.scss" -import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, +} from "./types" import { i18n } from "../i18n" import { classNames } from "../util/lang" -const Darkmode: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { - return ( -- {tag} + {i18n( + cfg.locale, + ).components.recentNotes.seeRemainingMore({ + remaining, + })} - - ))} - -
- - {i18n(cfg.locale).components.recentNotes.seeRemainingMore({ remaining })} - -
- )} -Search
*/} - -Search
*/} + +{i18n(cfg.locale).pages.error.notFound}
-{i18n(cfg.locale).pages.error.notFound}
+{content}
++ {i18n( + cfg.locale, + ).pages.folderContent.itemsUnderFolder({ + count: allPagesInFolder.length, + })} +
+ )} +{content}
-- {i18n(cfg.locale).pages.folderContent.itemsUnderFolder({ - count: allPagesInFolder.length, - })} -
- )} -{content}
-{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}
-{content}
} -- {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })} - {pages.length > numPages && ( - <> - {" "} - - {i18n(cfg.locale).pages.tagContent.showingFirst({ count: numPages })} - - > - )} -
-{content}
++ {i18n(cfg.locale).pages.tagContent.totalTags({ + count: tags.length, + })} +
+{content}
} ++ {i18n( + cfg.locale, + ).pages.tagContent.itemsUnderTag({ + count: pages.length, + })} + {pages.length > numPages && ( + <> + {" "} + + {i18n( + cfg.locale, + ).pages.tagContent.showingFirst( + { count: numPages }, + )} + + > + )} +
+{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}
-+ {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ + count: pages.length, + })} +
+#${tag}
#${tag}
${content}
` - }` - itemTile.addEventListener("click", (event) => { - if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return - hideSearch() - }) - - const handler = (event: MouseEvent) => { - if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return - hideSearch() } - async function onMouseEnter(ev: MouseEvent) { - if (!ev.target) return - const target = ev.target as HTMLInputElement - await displayPreview(target) + 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" + appendLayout(results) + + if (enablePreview) { + preview = document.createElement("div") + preview.id = "preview-container" + appendLayout(preview) } - itemTile.addEventListener("mouseenter", onMouseEnter) - window.addCleanup(() => itemTile.removeEventListener("mouseenter", onMouseEnter)) - itemTile.addEventListener("click", handler) - window.addCleanup(() => itemTile.removeEventListener("click", handler)) + function hideSearch() { + container?.classList.remove("active") + if (searchBar) { + searchBar.value = "" // clear the input when we dismiss the search + } + if (sidebar) { + sidebar.style.zIndex = "unset" + } + if (results) { + removeAllChildren(results) + } + if (preview) { + removeAllChildren(preview) + } + if (searchLayout) { + searchLayout.classList.remove("display-results") + } - return itemTile - } + searchType = "basic" // reset search type after closing + } - async function displayResults(finalResults: Item[]) { - if (!results) return + function showSearch(searchTypeNew: SearchType) { + searchType = searchTypeNew + if (sidebar) { + sidebar.style.zIndex = "1" + } + container?.classList.add("active") + searchBar?.focus() + } - removeAllChildren(results) - if (finalResults.length === 0) { - results.innerHTML = ` + let currentHover: HTMLInputElement | null = null + + async function shortcutHandler(e: HTMLElementEventMap["keydown"]) { + if (e.key === "k" && (e.ctrlKey || e.metaKey) && !e.shiftKey) { + 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() + const searchBarOpen = container?.classList.contains("active") + searchBarOpen ? hideSearch() : showSearch("tags") + + // add "#" prefix for tag search + if (searchBar) searchBar.value = "#" + return + } + + 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 + 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 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 = currentHover + ? currentHover + : (document.activeElement as HTMLInputElement | null) + const prevResult = + currentResult?.previousElementSibling as HTMLInputElement | null + currentResult?.classList.remove("focus") + prevResult?.focus() + if (prevResult) currentHover = prevResult + await displayPreview(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 (document.activeElement === searchBar || currentHover !== null) { + const firstResult = currentHover + ? currentHover + : (document.getElementsByClassName( + "result-card", + )[0] as HTMLInputElement | null) + const secondResult = + firstResult?.nextElementSibling as HTMLInputElement | null + firstResult?.classList.remove("focus") + secondResult?.focus() + if (secondResult) currentHover = secondResult + await displayPreview(secondResult) + } + } + } + + const formatForDisplay = (term: string, id: number) => { + const slug = idDataMap[id] + return { + id, + slug, + title: + searchType === "tags" + ? data[slug].title + : highlight(term, data[slug].title ?? ""), + 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") { + return [] + } + + return tags + .map((tag) => { + if (tag.toLowerCase().includes(term.toLowerCase())) { + return `#${tag}
#${tag}
${content}
` + }` + itemTile.addEventListener("click", (event) => { + if ( + event.altKey || + event.ctrlKey || + event.metaKey || + event.shiftKey + ) + return + hideSearch() + }) + + const handler = (event: MouseEvent) => { + if ( + event.altKey || + event.ctrlKey || + event.metaKey || + event.shiftKey + ) + return + hideSearch() + } + + async function onMouseEnter(ev: MouseEvent) { + if (!ev.target) return + const target = ev.target as HTMLInputElement + await displayPreview(target) + } + + itemTile.addEventListener("mouseenter", onMouseEnter) + window.addCleanup(() => + itemTile.removeEventListener("mouseenter", onMouseEnter), + ) + itemTile.addEventListener("click", handler) + window.addCleanup(() => itemTile.removeEventListener("click", handler)) + + return itemTile + } + + async function displayResults(finalResults: Item[]) { + if (!results) return + + removeAllChildren(results) + if (finalResults.length === 0) { + results.innerHTML = `Try another search term?
` - } else { - results.append(...finalResults.map(resultToHTML)) - } - - 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 - firstChild.classList.add("focus") - currentHover = firstChild as HTMLInputElement - await displayPreview(firstChild) - } - } - - async function fetchContent(slug: FullSlug): Promise