1
0
mirror of https://gitlab.com/MisterBiggs/brain-quartz.git synced 2026-06-03 21:10:34 +00:00
This commit is contained in:
2026-01-15 00:46:07 -05:00
26 changed files with 740 additions and 391 deletions
+1 -1
View File
@@ -294,7 +294,7 @@ export function renderPage(
</body>
{pageResources.js
.filter((resource) => resource.loadTime === "afterDOMReady")
.map((res) => JSResourceToScriptElement(res))}
.map((res) => JSResourceToScriptElement(res, true))}
</html>
)
@@ -111,6 +111,10 @@ function createFolderNode(
const folderPath = node.slug
folderContainer.dataset.folderpath = folderPath
if (currentSlug === folderPath) {
folderContainer.classList.add("active")
}
if (opts.folderClickBehavior === "link") {
// Replace button with link for link behavior
const button = titleContainer.querySelector(".folder-button") as HTMLElement
+45 -3
View File
@@ -29,17 +29,31 @@ class DiagramPanZoom {
const mouseDownHandler = this.onMouseDown.bind(this)
const mouseMoveHandler = this.onMouseMove.bind(this)
const mouseUpHandler = this.onMouseUp.bind(this)
// Touch drag events
const touchStartHandler = this.onTouchStart.bind(this)
const touchMoveHandler = this.onTouchMove.bind(this)
const touchEndHandler = this.onTouchEnd.bind(this)
const resizeHandler = this.resetTransform.bind(this)
this.container.addEventListener("mousedown", mouseDownHandler)
document.addEventListener("mousemove", mouseMoveHandler)
document.addEventListener("mouseup", mouseUpHandler)
this.container.addEventListener("touchstart", touchStartHandler, { passive: false })
document.addEventListener("touchmove", touchMoveHandler, { passive: false })
document.addEventListener("touchend", touchEndHandler)
window.addEventListener("resize", resizeHandler)
this.cleanups.push(
() => this.container.removeEventListener("mousedown", mouseDownHandler),
() => document.removeEventListener("mousemove", mouseMoveHandler),
() => document.removeEventListener("mouseup", mouseUpHandler),
() => this.container.removeEventListener("touchstart", touchStartHandler),
() => document.removeEventListener("touchmove", touchMoveHandler),
() => document.removeEventListener("touchend", touchEndHandler),
() => window.removeEventListener("resize", resizeHandler),
)
}
@@ -99,6 +113,30 @@ class DiagramPanZoom {
this.container.style.cursor = "grab"
}
private onTouchStart(e: TouchEvent) {
if (e.touches.length !== 1) return
this.isDragging = true
const touch = e.touches[0]
this.startPan = { x: touch.clientX - this.currentPan.x, y: touch.clientY - this.currentPan.y }
}
private onTouchMove(e: TouchEvent) {
if (!this.isDragging || e.touches.length !== 1) return
e.preventDefault() // Prevent scrolling
const touch = e.touches[0]
this.currentPan = {
x: touch.clientX - this.startPan.x,
y: touch.clientY - this.startPan.y,
}
this.updateTransform()
}
private onTouchEnd() {
this.isDragging = false
}
private zoom(delta: number) {
const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
@@ -120,11 +158,15 @@ class DiagramPanZoom {
}
private resetTransform() {
this.scale = 1
const svg = this.content.querySelector("svg")!
const rect = svg.getBoundingClientRect()
const width = rect.width / this.scale
const height = rect.height / this.scale
this.scale = 1
this.currentPan = {
x: svg.getBoundingClientRect().width / 2,
y: svg.getBoundingClientRect().height / 2,
x: (this.container.clientWidth - width) / 2,
y: (this.container.clientHeight - height) / 2,
}
this.updateTransform()
}
+43 -5
View File
@@ -16,11 +16,49 @@ interface Item {
type SearchType = "basic" | "tags"
let searchType: SearchType = "basic"
let currentSearchTerm: string = ""
const encoder = (str: string) => {
return str
.toLowerCase()
.split(/\s+/)
.filter((token) => token.length > 0)
const encoder = (str: string): string[] => {
const tokens: string[] = []
let bufferStart = -1
let bufferEnd = -1
const lower = str.toLowerCase()
let i = 0
for (const char of lower) {
const code = char.codePointAt(0)!
const isCJK =
(code >= 0x3040 && code <= 0x309f) ||
(code >= 0x30a0 && code <= 0x30ff) ||
(code >= 0x4e00 && code <= 0x9fff) ||
(code >= 0xac00 && code <= 0xd7af) ||
(code >= 0x20000 && code <= 0x2a6df)
const isWhitespace = code === 32 || code === 9 || code === 10 || code === 13
if (isCJK) {
if (bufferStart !== -1) {
tokens.push(lower.slice(bufferStart, bufferEnd))
bufferStart = -1
}
tokens.push(char)
} else if (isWhitespace) {
if (bufferStart !== -1) {
tokens.push(lower.slice(bufferStart, bufferEnd))
bufferStart = -1
}
} else {
if (bufferStart === -1) bufferStart = i
bufferEnd = i + char.length
}
i += char.length
}
if (bufferStart !== -1) {
tokens.push(lower.slice(bufferStart))
}
return tokens
}
let index = new FlexSearch.Document<Item>({
+163
View File
@@ -0,0 +1,163 @@
import test, { describe } from "node:test"
import assert from "node:assert"
// Inline the encoder function from search.inline.ts for testing
const encoder = (str: string): string[] => {
const tokens: string[] = []
let bufferStart = -1
let bufferEnd = -1
const lower = str.toLowerCase()
let i = 0
for (const char of lower) {
const code = char.codePointAt(0)!
const isCJK =
(code >= 0x3040 && code <= 0x309f) ||
(code >= 0x30a0 && code <= 0x30ff) ||
(code >= 0x4e00 && code <= 0x9fff) ||
(code >= 0xac00 && code <= 0xd7af) ||
(code >= 0x20000 && code <= 0x2a6df)
const isWhitespace = code === 32 || code === 9 || code === 10 || code === 13
if (isCJK) {
if (bufferStart !== -1) {
tokens.push(lower.slice(bufferStart, bufferEnd))
bufferStart = -1
}
tokens.push(char)
} else if (isWhitespace) {
if (bufferStart !== -1) {
tokens.push(lower.slice(bufferStart, bufferEnd))
bufferStart = -1
}
} else {
if (bufferStart === -1) bufferStart = i
bufferEnd = i + char.length
}
i += char.length
}
if (bufferStart !== -1) {
tokens.push(lower.slice(bufferStart))
}
return tokens
}
describe("search encoder", () => {
describe("English text", () => {
test("should tokenize simple English words", () => {
const result = encoder("hello world")
assert.deepStrictEqual(result, ["hello", "world"])
})
test("should handle multiple spaces", () => {
const result = encoder("hello world")
assert.deepStrictEqual(result, ["hello", "world"])
})
test("should handle tabs and newlines", () => {
const result = encoder("hello\tworld\ntest")
assert.deepStrictEqual(result, ["hello", "world", "test"])
})
test("should lowercase all text", () => {
const result = encoder("Hello WORLD Test")
assert.deepStrictEqual(result, ["hello", "world", "test"])
})
})
describe("CJK text", () => {
test("should tokenize Japanese Hiragana character by character", () => {
const result = encoder("こんにちは")
assert.deepStrictEqual(result, ["こ", "ん", "に", "ち", "は"])
})
test("should tokenize Japanese Katakana character by character", () => {
const result = encoder("コントロール")
assert.deepStrictEqual(result, ["コ", "ン", "ト", "ロ", "ー", "ル"])
})
test("should tokenize Japanese Kanji character by character", () => {
const result = encoder("日本語")
assert.deepStrictEqual(result, ["日", "本", "語"])
})
test("should tokenize Korean Hangul character by character", () => {
const result = encoder("안녕하세요")
assert.deepStrictEqual(result, ["안", "녕", "하", "세", "요"])
})
test("should tokenize Chinese characters character by character", () => {
const result = encoder("你好世界")
assert.deepStrictEqual(result, ["你", "好", "世", "界"])
})
test("should handle mixed Hiragana/Katakana/Kanji", () => {
const result = encoder("て以来")
assert.deepStrictEqual(result, ["て", "以", "来"])
})
})
describe("Mixed CJK and English", () => {
test("should handle Japanese with English words", () => {
const result = encoder("hello 世界")
assert.deepStrictEqual(result, ["hello", "世", "界"])
})
test("should handle English with Japanese words", () => {
const result = encoder("世界 hello world")
assert.deepStrictEqual(result, ["世", "界", "hello", "world"])
})
test("should handle complex mixed content", () => {
const result = encoder("これはtest文章です")
assert.deepStrictEqual(result, ["こ", "れ", "は", "test", "文", "章", "で", "す"])
})
test("should handle mixed Korean and English", () => {
const result = encoder("hello 안녕 world")
assert.deepStrictEqual(result, ["hello", "안", "녕", "world"])
})
test("should handle mixed Chinese and English", () => {
const result = encoder("你好 world")
assert.deepStrictEqual(result, ["你", "好", "world"])
})
})
describe("Edge cases", () => {
test("should handle empty string", () => {
const result = encoder("")
assert.deepStrictEqual(result, [])
})
test("should handle only whitespace", () => {
const result = encoder(" \t\n ")
assert.deepStrictEqual(result, [])
})
test("should handle single character", () => {
const result = encoder("a")
assert.deepStrictEqual(result, ["a"])
})
test("should handle single CJK character", () => {
const result = encoder("あ")
assert.deepStrictEqual(result, ["あ"])
})
test("should handle CJK with trailing whitespace", () => {
const result = encoder("日本語 ")
assert.deepStrictEqual(result, ["日", "本", "語"])
})
test("should handle English with trailing whitespace", () => {
const result = encoder("hello ")
assert.deepStrictEqual(result, ["hello"])
})
})
})
+2 -2
View File
@@ -115,9 +115,9 @@ async function _navigate(url: URL, isBack: boolean = false) {
}
// now, patch head, re-executing scripts
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
const elementsToRemove = document.head.querySelectorAll(":not([data-persist])")
elementsToRemove.forEach((el) => el.remove())
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
const elementsToAdd = html.head.querySelectorAll(":not([data-persist])")
elementsToAdd.forEach((el) => document.head.appendChild(el))
// delay setting the url until now
+1 -1
View File
@@ -5,7 +5,7 @@
background: none;
border: none;
width: 20px;
height: 20px;
height: 32px;
margin: 0;
text-align: inherit;
flex-shrink: 0;
@@ -65,7 +65,6 @@ pre {
overflow: hidden;
& > .mermaid-content {
padding: 2rem;
position: relative;
transform-origin: 0 0;
transition: transform 0.1s ease;
+1 -1
View File
@@ -5,7 +5,7 @@
background: none;
border: none;
width: 20px;
height: 20px;
height: 32px;
margin: 0;
text-align: inherit;
flex-shrink: 0;