Compare commits

..

12 Commits

Author SHA1 Message Date
Jacky Zhao
34a8dfcd55 pkg: bump to 4.2.1 2024-02-02 01:45:28 -08:00
Jacky Zhao
44da82467e fix(style): remove redundant selector 2024-02-02 01:45:15 -08:00
Jacky Zhao
3231ce6e79 fix: search async ordering, scroll offset 2024-02-02 01:36:17 -08:00
Jacky Zhao
a0b927da4a fix: use display instead of visibility for click handling pasthrough 2024-02-02 01:24:40 -08:00
Jacky Zhao
5ab922f316 fix(revert): font aliasing 2024-02-02 01:15:10 -08:00
Jacky Zhao
d11a0e71a8 fix: font smoothing defaults 2024-02-02 01:01:04 -08:00
Jacky Zhao
2b57a68e1f fix: font weight consistency 2024-02-02 00:53:09 -08:00
Jacky Zhao
18cd58617d fix: parallelize search indexing 2024-02-02 00:53:09 -08:00
Aaron Pham
ee868b2d79 fix(search): set correct attribute on hover icon (#787)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-02 00:35:53 -08:00
Jacky Zhao
5a36e5b68d fix(style): reasonable page width for rich search preview 2024-02-02 00:29:45 -08:00
Jacky Zhao
0416c03ae6 fix: be more eager about constructing search index 2024-02-02 00:25:05 -08:00
Jacky Zhao
3b596c9311 fix: flatmap children when highlighting rich preview to avoid body 2024-02-02 00:19:19 -08:00
8 changed files with 77 additions and 74 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@jackyzha0/quartz", "name": "@jackyzha0/quartz",
"version": "4.2.0", "version": "4.2.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@jackyzha0/quartz", "name": "@jackyzha0/quartz",
"version": "4.2.0", "version": "4.2.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@clack/prompts": "^0.7.0", "@clack/prompts": "^0.7.0",

View File

@@ -2,7 +2,7 @@
"name": "@jackyzha0/quartz", "name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website", "description": "🌱 publish your digital garden and notes as a website",
"private": true, "private": true,
"version": "4.2.0", "version": "4.2.1",
"type": "module", "type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>", "author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT", "license": "MIT",

View File

@@ -15,10 +15,30 @@ interface Item {
type SearchType = "basic" | "tags" type SearchType = "basic" | "tags"
let searchType: SearchType = "basic" let searchType: SearchType = "basic"
let currentSearchTerm: string = "" let currentSearchTerm: string = ""
let index: FlexSearch.Document<Item> | undefined = undefined
const p = new DOMParser()
const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/) const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/)
let index = new FlexSearch.Document<Item>({
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<FullSlug, Element[]> = new Map() const fetchContentCache: Map<FullSlug, Element[]> = new Map()
const contextWindowWords = 30 const contextWindowWords = 30
const numSearchResults = 8 const numSearchResults = 8
@@ -81,10 +101,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 p = new DOMParser()
const tokenizedTerms = tokenizeTerm(searchTerm) const tokenizedTerms = tokenizeTerm(searchTerm)
const html = p.parseFromString(innerHTML, "text/html") const html = p.parseFromString(el.innerHTML, "text/html")
const createHighlightSpan = (text: string) => { const createHighlightSpan = (text: string) => {
const span = document.createElement("span") const span = document.createElement("span")
@@ -168,7 +188,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
removeAllChildren(preview) removeAllChildren(preview)
} }
if (searchLayout) { if (searchLayout) {
searchLayout.style.visibility = "hidden" searchLayout.classList.remove("display-results")
} }
searchType = "basic" // reset search type after closing searchType = "basic" // reset search type after closing
@@ -204,6 +224,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
if (currentHover) { if (currentHover) {
currentHover.classList.remove("focus") currentHover.classList.remove("focus")
currentHover.blur()
} }
// If search is active, then we will render the first result and display accordingly // If search is active, then we will render the first result and display accordingly
@@ -230,9 +251,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
: (document.activeElement as HTMLInputElement | null) : (document.activeElement as HTMLInputElement | null)
const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null
currentResult?.classList.remove("focus") currentResult?.classList.remove("focus")
await displayPreview(prevResult)
prevResult?.focus() prevResult?.focus()
currentHover = prevResult currentHover = prevResult
await displayPreview(prevResult)
} }
} else if (e.key === "ArrowDown" || e.key === "Tab") { } else if (e.key === "ArrowDown" || e.key === "Tab") {
e.preventDefault() e.preventDefault()
@@ -244,9 +265,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
: (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null) : (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null)
const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null
firstResult?.classList.remove("focus") firstResult?.classList.remove("focus")
await displayPreview(secondResult)
secondResult?.focus() secondResult?.focus()
currentHover = secondResult currentHover = secondResult
await displayPreview(secondResult)
} else { } else {
// If an element in results-container already has focus, focus next one // If an element in results-container already has focus, focus next one
const active = currentHover const active = currentHover
@@ -254,9 +275,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
: (document.activeElement as HTMLInputElement | null) : (document.activeElement as HTMLInputElement | null)
active?.classList.remove("focus") active?.classList.remove("focus")
const nextResult = active?.nextElementSibling as HTMLInputElement | null const nextResult = active?.nextElementSibling as HTMLInputElement | null
await displayPreview(nextResult)
nextResult?.focus() nextResult?.focus()
currentHover = nextResult currentHover = nextResult
await displayPreview(nextResult)
} }
} }
} }
@@ -305,9 +326,9 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
currentHover?.classList.remove("focus") currentHover?.classList.remove("focus")
currentHover?.blur() currentHover?.blur()
const target = ev.target as HTMLInputElement const target = ev.target as HTMLInputElement
await displayPreview(target)
currentHover = target currentHover = target
currentHover.classList.add("focus") currentHover.classList.add("focus")
await displayPreview(target)
} }
async function onMouseLeave(ev: MouseEvent) { async function onMouseLeave(ev: MouseEvent) {
@@ -385,12 +406,11 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
async function displayPreview(el: HTMLElement | null) { async function displayPreview(el: HTMLElement | null) {
if (!searchLayout || !enablePreview || !el || !preview) return if (!searchLayout || !enablePreview || !el || !preview) return
const slug = el.id as FullSlug const slug = el.id as FullSlug
el.classList.add("focus") const innerDiv = await fetchContent(slug).then((contents) =>
contents.flatMap((el) => [...highlightHTML(currentSearchTerm, el as HTMLElement).children]),
)
previewInner = document.createElement("div") previewInner = document.createElement("div")
previewInner.classList.add("preview-inner") previewInner.classList.add("preview-inner")
const innerDiv = await fetchContent(slug).then((contents) =>
contents.map((el) => highlightHTML(currentSearchTerm, el.innerHTML)),
)
previewInner.append(...innerDiv) previewInner.append(...innerDiv)
preview.replaceChildren(previewInner) preview.replaceChildren(previewInner)
@@ -398,13 +418,13 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const highlights = [...preview.querySelectorAll(".highlight")].sort( const highlights = [...preview.querySelectorAll(".highlight")].sort(
(a, b) => b.innerHTML.length - a.innerHTML.length, (a, b) => b.innerHTML.length - a.innerHTML.length,
) )
highlights[0]?.scrollIntoView() highlights[0]?.scrollIntoView({ block: "start" })
} }
async function onType(e: HTMLElementEventMap["input"]) { async function onType(e: HTMLElementEventMap["input"]) {
if (!searchLayout || !index) return if (!searchLayout || !index) return
currentSearchTerm = (e.target as HTMLInputElement).value currentSearchTerm = (e.target as HTMLInputElement).value
searchLayout.style.visibility = currentSearchTerm === "" ? "hidden" : "visible" searchLayout.classList.toggle("display-results", currentSearchTerm !== "")
searchType = currentSearchTerm.startsWith("#") ? "tags" : "basic" searchType = currentSearchTerm.startsWith("#") ? "tags" : "basic"
let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[] let searchResults: FlexSearch.SimpleDocumentSearchResultSetUnit[]
@@ -444,8 +464,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
searchBar?.addEventListener("input", onType) searchBar?.addEventListener("input", onType)
window.addCleanup(() => searchBar?.removeEventListener("input", onType)) window.addCleanup(() => searchBar?.removeEventListener("input", onType))
index ??= await fillDocument(data)
registerEscapeHandler(container, hideSearch) registerEscapeHandler(container, hideSearch)
await fillDocument(data)
}) })
/** /**
@@ -454,37 +474,19 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
* @param data data to fill index with * @param data data to fill index with
*/ */
async function fillDocument(data: { [key: FullSlug]: ContentDetails }) { async function fillDocument(data: { [key: FullSlug]: ContentDetails }) {
const index = new FlexSearch.Document<Item>({
charset: "latin:extra",
encode: encoder,
document: {
id: "id",
index: [
{
field: "title",
tokenize: "forward",
},
{
field: "content",
tokenize: "forward",
},
{
field: "tags",
tokenize: "forward",
},
],
},
})
let id = 0 let id = 0
const promises: Array<Promise<unknown>> = []
for (const [slug, fileData] of Object.entries<ContentDetails>(data)) { for (const [slug, fileData] of Object.entries<ContentDetails>(data)) {
await index.addAsync(id++, { promises.push(
id, index.addAsync(id++, {
slug: slug as FullSlug, id,
title: fileData.title, slug: slug as FullSlug,
content: fileData.content, title: fileData.title,
tags: fileData.tags, content: fileData.content,
}) tags: fileData.tags,
}),
)
} }
return index return await Promise.all(promises)
} }

View File

@@ -1,3 +1,5 @@
@use "../../styles/variables.scss" as *;
button#explorer { button#explorer {
all: unset; all: unset;
background-color: transparent; background-color: transparent;
@@ -85,7 +87,7 @@ svg {
color: var(--secondary); color: var(--secondary);
font-family: var(--headerFont); font-family: var(--headerFont);
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 600; font-weight: $boldWeight;
line-height: 1.5rem; line-height: 1.5rem;
display: inline-block; display: inline-block;
} }
@@ -110,7 +112,7 @@ svg {
font-size: 0.95rem; font-size: 0.95rem;
display: inline-block; display: inline-block;
color: var(--secondary); color: var(--secondary);
font-weight: 600; font-weight: $boldWeight;
margin: 0; margin: 0;
line-height: 1.5rem; line-height: 1.5rem;
pointer-events: none; pointer-events: none;

View File

@@ -83,11 +83,14 @@
} }
& > #search-layout { & > #search-layout {
display: flex; display: none;
flex-direction: row; flex-direction: row;
visibility: hidden;
border: 1px solid var(--lightgray); border: 1px solid var(--lightgray);
&.display-results {
display: flex;
}
@media all and (min-width: $tabletBreakpoint) { @media all and (min-width: $tabletBreakpoint) {
&[data-preview] { &[data-preview] {
& .result-card > p.preview { & .result-card > p.preview {
@@ -128,34 +131,27 @@
& .highlight { & .highlight {
background: color-mix(in srgb, var(--tertiary) 60%, transparent); background: color-mix(in srgb, var(--tertiary) 60%, transparent);
border-radius: 5px; border-radius: 5px;
scroll-margin-top: 2rem;
} }
& > #preview-container { & > #preview-container {
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
box-sizing: border-box;
font-family: inherit;
color: var(--dark);
line-height: 1.5em;
font-weight: $normalWeight;
background: var(--light);
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
overflow-y: auto;
padding: 1rem;
& .preview-inner { & .preview-inner {
margin: 0 auto; margin: 0 auto;
padding: 1em; width: min($pageWidth, 100%);
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);
}
a.internal {
background-color: none;
} }
} }
@@ -202,7 +198,7 @@
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
margin: 0 0.1rem; margin: 0 0.1rem;
line-height: 1.4rem; line-height: 1.4rem;
font-weight: bold; font-weight: $boldWeight;
color: var(--secondary); color: var(--secondary);
&.match-tag { &.match-tag {

View File

@@ -54,7 +54,7 @@ ul,
} }
a { a {
font-weight: 600; font-weight: $boldWeight;
text-decoration: none; text-decoration: none;
transition: color 0.2s ease; transition: color 0.2s ease;
color: var(--secondary); color: var(--secondary);

View File

@@ -1,3 +1,4 @@
@use "./variables.scss" as *;
@use "sass:color"; @use "sass:color";
.callout { .callout {
@@ -156,6 +157,6 @@
} }
.callout-title-inner { .callout-title-inner {
font-weight: 700; font-weight: $boldWeight;
} }
} }

View File

@@ -4,3 +4,5 @@ $tabletBreakpoint: 1000px;
$sidePanelWidth: 380px; $sidePanelWidth: 380px;
$topSpacing: 6rem; $topSpacing: 6rem;
$fullPageWidth: $pageWidth + 2 * $sidePanelWidth; $fullPageWidth: $pageWidth + 2 * $sidePanelWidth;
$boldWeight: 700;
$normalWeight: 400;