The Trans Directory
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(explorer): dont rely on data to get slug, compute it in trie

+75 -72
+1 -1
quartz/components/Explorer.tsx
··· 23 23 24 24 const defaultOptions: Options = { 25 25 folderDefaultState: "collapsed", 26 - folderClickBehavior: "collapse", 26 + folderClickBehavior: "link", 27 27 useSavedState: true, 28 28 mapFn: (node) => { 29 29 return node
+5 -5
quartz/components/scripts/explorer.inline.ts
··· 78 78 const clone = template.content.cloneNode(true) as DocumentFragment 79 79 const li = clone.querySelector("li") as HTMLLIElement 80 80 const a = li.querySelector("a") as HTMLAnchorElement 81 - a.href = resolveRelative(currentSlug, node.data?.slug!) 82 - a.dataset.for = node.data?.slug 81 + a.href = resolveRelative(currentSlug, node.slug) 82 + a.dataset.for = node.slug 83 83 a.textContent = node.displayName 84 84 85 - if (currentSlug === node.data?.slug) { 85 + if (currentSlug === node.slug) { 86 86 a.classList.add("active") 87 87 } 88 88 ··· 102 102 const folderOuter = li.querySelector(".folder-outer") as HTMLElement 103 103 const ul = folderOuter.querySelector("ul") as HTMLUListElement 104 104 105 - const folderPath = node.data?.slug! 105 + const folderPath = node.slug 106 106 folderContainer.dataset.folderpath = folderPath 107 107 108 108 if (opts.folderClickBehavior === "link") { ··· 110 110 const button = titleContainer.querySelector(".folder-button") as HTMLElement 111 111 const a = document.createElement("a") 112 112 a.href = resolveRelative(currentSlug, folderPath) 113 - a.dataset.for = node.data?.slug 113 + a.dataset.for = folderPath 114 114 a.className = "folder-title" 115 115 a.textContent = node.displayName 116 116 button.replaceWith(a)
+20 -16
quartz/util/fileTrie.test.ts
··· 11 11 let trie: FileTrieNode<TestData> 12 12 13 13 beforeEach(() => { 14 - trie = new FileTrieNode<TestData>("") 14 + trie = new FileTrieNode<TestData>([]) 15 15 }) 16 16 17 17 describe("constructor", () => { 18 18 test("should create an empty trie", () => { 19 19 assert.deepStrictEqual(trie.children, []) 20 - assert.strictEqual(trie.slugSegment, "") 20 + assert.strictEqual(trie.slug, "") 21 21 assert.strictEqual(trie.displayName, "") 22 22 assert.strictEqual(trie.data, null) 23 - assert.strictEqual(trie.depth, 0) 24 23 }) 25 24 26 25 test("should set displayName from data title", () => { ··· 43 42 44 43 trie.add(data) 45 44 assert.strictEqual(trie.children.length, 1) 46 - assert.strictEqual(trie.children[0].slugSegment, "test") 45 + assert.strictEqual(trie.children[0].slug, "test") 47 46 assert.strictEqual(trie.children[0].data, data) 48 47 }) 49 48 ··· 72 71 trie.add(data1) 73 72 trie.add(data2) 74 73 assert.strictEqual(trie.children.length, 2) 75 - assert.strictEqual(trie.children[0].slugSegment, "folder") 74 + assert.strictEqual(trie.children[0].slug, "folder/index") 76 75 assert.strictEqual(trie.children[0].children.length, 1) 77 - assert.strictEqual(trie.children[0].children[0].slugSegment, "test") 76 + assert.strictEqual(trie.children[0].children[0].slug, "folder/test") 78 77 assert.strictEqual(trie.children[0].children[0].data, data1) 79 78 80 - assert.strictEqual(trie.children[1].slugSegment, "a") 79 + assert.strictEqual(trie.children[1].slug, "a/index") 81 80 assert.strictEqual(trie.children[1].children.length, 1) 82 81 assert.strictEqual(trie.children[1].data, null) 83 82 84 - assert.strictEqual(trie.children[1].children[0].slugSegment, "b") 83 + assert.strictEqual(trie.children[1].children[0].slug, "a/b/index") 85 84 assert.strictEqual(trie.children[1].children[0].children.length, 1) 86 85 assert.strictEqual(trie.children[1].children[0].data, null) 87 86 88 - assert.strictEqual(trie.children[1].children[0].children[0].slugSegment, "c") 87 + assert.strictEqual(trie.children[1].children[0].children[0].slug, "a/b/c/index") 89 88 assert.strictEqual(trie.children[1].children[0].children[0].data, data2) 90 89 assert.strictEqual(trie.children[1].children[0].children[0].children.length, 0) 91 90 }) ··· 99 98 trie.add(data1) 100 99 trie.add(data2) 101 100 102 - trie.filter((node) => node.slugSegment !== "test1") 101 + trie.filter((node) => node.slug !== "test1") 103 102 assert.strictEqual(trie.children.length, 1) 104 - assert.strictEqual(trie.children[0].slugSegment, "test2") 103 + assert.strictEqual(trie.children[0].slug, "test2") 105 104 }) 106 105 }) 107 106 ··· 115 114 116 115 trie.map((node) => { 117 116 if (node.data) { 118 - node.displayName = "Modified" 117 + node.data.title = "Modified" 119 118 } 120 119 }) 121 120 ··· 136 135 assert.deepStrictEqual( 137 136 entries.map(([path, node]) => [path, node.data]), 138 137 [ 139 - ["", trie.data], 138 + ["index", trie.data], 140 139 ["test1", data1], 141 140 ["a/index", null], 142 141 ["a/b/index", null], ··· 166 165 trie.add(data3) 167 166 const paths = trie.getFolderPaths() 168 167 169 - assert.deepStrictEqual(paths, ["folder/index", "folder/subfolder/index", "abc/index"]) 168 + assert.deepStrictEqual(paths, [ 169 + "index", 170 + "folder/index", 171 + "folder/subfolder/index", 172 + "abc/index", 173 + ]) 170 174 }) 171 175 }) 172 176 ··· 180 184 trie.add(data1) 181 185 trie.add(data2) 182 186 183 - trie.sort((a, b) => a.slugSegment.localeCompare(b.slugSegment)) 187 + trie.sort((a, b) => a.slug.localeCompare(b.slug)) 184 188 assert.deepStrictEqual( 185 - trie.children.map((n) => n.slugSegment), 189 + trie.children.map((n) => n.slug), 186 190 ["a", "b", "c"], 187 191 ) 188 192 })
+49 -50
quartz/util/fileTrie.ts
··· 7 7 } 8 8 9 9 export class FileTrieNode<T extends FileTrieData = ContentDetails> { 10 + isFolder: boolean 10 11 children: Array<FileTrieNode<T>> 11 - slugSegment: string 12 - displayName: string 12 + 13 + private slugSegments: string[] 13 14 data: T | null 14 - depth: number 15 - isFolder: boolean 16 15 17 - constructor(segment: string, data?: T, depth: number = 0) { 16 + constructor(segments: string[], data?: T) { 18 17 this.children = [] 19 - this.slugSegment = segment 20 - this.displayName = data?.title ?? segment 18 + this.slugSegments = segments 21 19 this.data = data ?? null 22 - this.depth = depth 23 - this.isFolder = segment === "index" 20 + this.isFolder = false 24 21 } 25 22 26 - private insert(path: string[], file: T) { 27 - if (path.length === 0) return 23 + get displayName(): string { 24 + return this.data?.title ?? this.slugSegment ?? "" 25 + } 28 26 29 - const nextSegment = path[0] 27 + get slug(): FullSlug { 28 + const path = joinSegments(...this.slugSegments) as FullSlug 29 + if (this.isFolder) { 30 + return joinSegments(path, "index") as FullSlug 31 + } 30 32 31 - // base case, insert here 33 + return path 34 + } 35 + 36 + get slugSegment(): string { 37 + return this.slugSegments[this.slugSegments.length - 1] 38 + } 39 + 40 + private makeChild(path: string[], file?: T) { 41 + const fullPath = [...this.slugSegments, path[0]] 42 + const child = new FileTrieNode<T>(fullPath, file) 43 + this.children.push(child) 44 + return child 45 + } 46 + 47 + private insert(path: string[], file: T) { 48 + if (path.length === 0) { 49 + throw new Error("path is empty") 50 + } 51 + 52 + // if we are inserting, we are a folder 53 + this.isFolder = true 54 + const segment = path[0] 32 55 if (path.length === 1) { 33 - if (nextSegment === "index") { 34 - // index case (we are the root and we just found index.md) 56 + // base case, we are at the end of the path 57 + if (segment === "index") { 35 58 this.data ??= file 36 - const title = file.title 37 - if (title !== "index") { 38 - this.displayName = title 39 - } 40 59 } else { 41 - // direct child 42 - this.children.push(new FileTrieNode(nextSegment, file, this.depth + 1)) 43 - this.isFolder = true 60 + this.makeChild(path, file) 44 61 } 45 - 46 - return 62 + } else if (path.length > 1) { 63 + // recursive case, we are not at the end of the path 64 + const child = 65 + this.children.find((c) => c.slugSegment === segment) ?? this.makeChild(path, undefined) 66 + child.insert(path.slice(1), file) 47 67 } 48 - 49 - // find the right child to insert into, creating it if it doesn't exist 50 - path = path.splice(1) 51 - let child = this.children.find((c) => c.slugSegment === nextSegment) 52 - if (!child) { 53 - child = new FileTrieNode<T>(nextSegment, undefined, this.depth + 1) 54 - this.children.push(child) 55 - child.isFolder = true 56 - } 57 - 58 - child.insert(path, file) 59 68 } 60 69 61 70 // Add new file to trie ··· 88 97 } 89 98 90 99 static fromEntries<T extends FileTrieData>(entries: [FullSlug, T][]) { 91 - const trie = new FileTrieNode<T>("") 100 + const trie = new FileTrieNode<T>([]) 92 101 entries.forEach(([, entry]) => trie.add(entry)) 93 102 return trie 94 103 } ··· 98 107 * in the a flat array including the full path and the node 99 108 */ 100 109 entries(): [FullSlug, FileTrieNode<T>][] { 101 - const traverse = ( 102 - node: FileTrieNode<T>, 103 - currentPath: string, 104 - ): [FullSlug, FileTrieNode<T>][] => { 105 - const segments = [currentPath, node.slugSegment] 106 - const fullPath = joinSegments(...segments) as FullSlug 107 - 108 - const indexQualifiedPath = 109 - node.isFolder && node.depth > 0 ? (joinSegments(fullPath, "index") as FullSlug) : fullPath 110 - 111 - const result: [FullSlug, FileTrieNode<T>][] = [[indexQualifiedPath, node]] 112 - 113 - return result.concat(...node.children.map((child) => traverse(child, fullPath))) 110 + const traverse = (node: FileTrieNode<T>): [FullSlug, FileTrieNode<T>][] => { 111 + const result: [FullSlug, FileTrieNode<T>][] = [[node.slug, node]] 112 + return result.concat(...node.children.map(traverse)) 114 113 } 115 114 116 - return traverse(this, "") 115 + return traverse(this) 117 116 } 118 117 119 118 /**