Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34a8dfcd55 | ||
|
|
44da82467e | ||
|
|
3231ce6e79 | ||
|
|
a0b927da4a | ||
|
|
5ab922f316 | ||
|
|
d11a0e71a8 | ||
|
|
2b57a68e1f | ||
|
|
18cd58617d | ||
|
|
ee868b2d79 | ||
|
|
5a36e5b68d | ||
|
|
0416c03ae6 | ||
|
|
3b596c9311 |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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 <j.zhao2k19@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -15,10 +15,30 @@ interface Item {
|
||||
type SearchType = "basic" | "tags"
|
||||
let searchType: SearchType = "basic"
|
||||
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])/)
|
||||
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 contextWindowWords = 30
|
||||
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 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")
|
||||
@@ -168,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
|
||||
@@ -204,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
|
||||
@@ -230,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()
|
||||
@@ -244,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
|
||||
@@ -254,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,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) {
|
||||
@@ -385,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")
|
||||
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")
|
||||
const innerDiv = await fetchContent(slug).then((contents) =>
|
||||
contents.map((el) => highlightHTML(currentSearchTerm, el.innerHTML)),
|
||||
)
|
||||
previewInner.append(...innerDiv)
|
||||
preview.replaceChildren(previewInner)
|
||||
|
||||
@@ -398,13 +418,13 @@ 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"]) {
|
||||
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[]
|
||||
@@ -444,8 +464,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
||||
searchBar?.addEventListener("input", onType)
|
||||
window.addCleanup(() => searchBar?.removeEventListener("input", onType))
|
||||
|
||||
index ??= await fillDocument(data)
|
||||
registerEscapeHandler(container, hideSearch)
|
||||
await fillDocument(data)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -454,37 +474,19 @@ 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<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
|
||||
const promises: Array<Promise<unknown>> = []
|
||||
for (const [slug, fileData] of Object.entries<ContentDetails>(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 index
|
||||
return await Promise.all(promises)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
@@ -128,34 +131,27 @@
|
||||
& .highlight {
|
||||
background: color-mix(in srgb, var(--tertiary) 60%, transparent);
|
||||
border-radius: 5px;
|
||||
scroll-margin-top: 2rem;
|
||||
}
|
||||
|
||||
& > #preview-container {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
a.internal {
|
||||
background-color: none;
|
||||
width: min($pageWidth, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,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 {
|
||||
|
||||
@@ -54,7 +54,7 @@ ul,
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 600;
|
||||
font-weight: $boldWeight;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
color: var(--secondary);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,5 @@ $tabletBreakpoint: 1000px;
|
||||
$sidePanelWidth: 380px;
|
||||
$topSpacing: 6rem;
|
||||
$fullPageWidth: $pageWidth + 2 * $sidePanelWidth;
|
||||
$boldWeight: 700;
|
||||
$normalWeight: 400;
|
||||
|
||||
Reference in New Issue
Block a user