Skip to content

Commit 410d7b9

Browse files
committed
fix(tree): do not store collection in directory id
1 parent 4f81f27 commit 410d7b9

File tree

6 files changed

+157
-48
lines changed

6 files changed

+157
-48
lines changed

src/app/src/utils/draft.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,15 @@ export function findDescendantsFromId(list: DraftItem[], id: string): DraftItem[
7676

7777
const descendants: DraftItem[] = []
7878
for (const item of list) {
79-
if (item.id === id || item.id.startsWith(id + '/')) {
79+
// Only file type are stored in the draft list, if id is exact match, return the file
80+
if (item.id === id) {
81+
return [item]
82+
}
83+
84+
// Else the parent is a directory, add we need to browse the list and find all descendants
85+
// Descendants means id without collection prefix starts with the parent id
86+
const idWithoutCollectionPrefix = item.id.split('/').slice(1).join('/') || item.id
87+
if (idWithoutCollectionPrefix.startsWith(id + '/')) {
8088
descendants.push(item)
8189
}
8290
}

src/app/src/utils/tree.ts

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
type DraftItem,
77
type TreeItem,
88
} from '../types'
9-
import { withLeadingSlash } from 'ufo'
109
import type { RouteLocationNormalized } from 'vue-router'
1110
import type { BaseItem } from '../types/item'
1211
import { isEqual } from './database'
@@ -69,11 +68,6 @@ TreeItem[] {
6968
const directorySegments = fsPathSegments.slice(0, -1)
7069
let fileName = fsPathSegments[fsPathSegments.length - 1].replace(/\.[^/.]+$/, '')
7170

72-
let routePathSegments: string[] | undefined
73-
if (itemHasPathField) {
74-
routePathSegments = (dbItem.path as string).split('/').slice(0, -1).filter(Boolean)
75-
}
76-
7771
/*****************
7872
Generate root file
7973
******************/
@@ -109,8 +103,9 @@ TreeItem[] {
109103
/*****************
110104
Generate directory
111105
******************/
106+
// Directory id do not start with collection prefix since files from different collections can be part of the same directory
112107
function dirIdBuilder(index: number) {
113-
const idSegments = dbItem.id.split('/')
108+
const idSegments = dbItem.id.split('/').slice(1)
114109
const stemVsIdGap = idSegments.length - fsPathSegments.length
115110
return idSegments.slice(0, index + stemVsIdGap + 1).join('/')
116111
}
@@ -119,10 +114,6 @@ TreeItem[] {
119114
return directorySegments.slice(0, index + 1).join('/')
120115
}
121116

122-
function dirRoutePathBuilder(index: number) {
123-
return withLeadingSlash(routePathSegments!.slice(0, index + 1).join('/'))
124-
}
125-
126117
let directoryChildren = tree
127118
for (let i = 0; i < directorySegments.length; i++) {
128119
const { name: dirName, prefix: dirPrefix } = parseName(directorySegments[i])
@@ -141,10 +132,6 @@ TreeItem[] {
141132
prefix: dirPrefix,
142133
}
143134

144-
if (itemHasPathField) {
145-
directory.routePath = dirRoutePathBuilder(i)
146-
}
147-
148135
directoryMap.set(dirId, directory)
149136

150137
if (!directoryChildren.find(child => child.id === dirId)) {
@@ -282,20 +269,26 @@ export function findDescendantsFileItemsFromId(tree: TreeItem[], id: string): Tr
282269

283270
function traverse(items: TreeItem[]) {
284271
for (const item of items) {
285-
// Check if this item matches the id or is a descendant of it
286-
if (item.id === id || item.id.startsWith(id + '/')) {
287-
if (item.type === 'file') {
272+
// File type
273+
if (item.type === 'file') {
274+
const itemIdWithoutCollectionPrefix = item.id.split('/').slice(1).join('/')
275+
const isExactItem = item.id === id
276+
// Descendants means id without collection prefix starts with the parent id
277+
const isDescendant = itemIdWithoutCollectionPrefix.startsWith(id + '/')
278+
if (isExactItem || isDescendant) {
288279
descendants.push(item)
289280
}
290-
291-
// If this item has children, add all of them as descendants
292-
if (item.children) {
293-
getAllDescendants(item.children, descendants)
294-
}
295281
}
296-
else if (item.children) {
297-
// Continue searching in children
298-
traverse(item.children)
282+
// Directory type
283+
else {
284+
// Directory found, add all children as descendants
285+
if (item.id === id) {
286+
getAllDescendants(item.children!, descendants)
287+
}
288+
// Keep browsing children
289+
else if (item.children) {
290+
traverse(item.children)
291+
}
299292
}
300293
}
301294
}

src/app/test/mocks/database.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,62 @@ export const nestedDbItemsList: (DatabaseItem & { fsPath: string })[] = [
100100
__hash__: 'EXAMPLE_HASH_FOR_ADVANCED',
101101
},
102102
]
103+
104+
export const languagePrefixedDbItemsList: (DatabaseItem & { fsPath: string })[] = [
105+
{
106+
id: 'landing_en/en/index.md',
107+
title: '',
108+
description: '',
109+
extension: 'md',
110+
meta: {},
111+
navigation: true,
112+
path: '/en',
113+
seo: {
114+
title: 'Write beautiful docs with Markdown',
115+
description: 'Ship fast, flexible, and SEO-optimized documentation with beautiful design out of the box. Docus brings together the best of the Nuxt ecosystem. Powered by Nuxt UI.',
116+
},
117+
stem: 'en/index',
118+
__hash__: 'BxOHluwGJlv20AmP_1_rJpaQGU9M2op7_lgbEag-cGQ',
119+
fsPath: 'en/index.md',
120+
},
121+
{
122+
id: 'docs_en/en/1.getting-started/2.introduction.md',
123+
title: 'Introduction',
124+
body: { type: 'minimark', value: [] },
125+
description: 'Welcome to Docus theme documentation.',
126+
extension: 'md',
127+
links: null,
128+
meta: {},
129+
navigation: {
130+
icon: 'i-lucide-house',
131+
},
132+
path: '/en/getting-started/introduction',
133+
seo: {
134+
title: 'Introduction',
135+
description: 'Discover how to create, manage, and publish documentation effortlessly with Docus.',
136+
},
137+
stem: 'en/1.getting-started/2.introduction',
138+
__hash__: '1qEHmeHop1z0_uM95iH2NGHdd1vQN1Z7TVntMd7T9t0',
139+
fsPath: 'en/1.getting-started/2.introduction.md',
140+
},
141+
{
142+
id: 'docs_en/en/1.getting-started/3.installation.md',
143+
title: 'Installation',
144+
body: { type: 'minimark', value: [] },
145+
description: 'Get started with Docus.',
146+
extension: 'md',
147+
links: null,
148+
meta: {},
149+
navigation: {
150+
icon: 'i-lucide-download',
151+
},
152+
path: '/en/getting-started/installation',
153+
seo: {
154+
description: 'Get started with Docus documentation theme.',
155+
title: 'Installation',
156+
},
157+
stem: 'en/1.getting-started/3.installation',
158+
__hash__: 'TRjVoVTW0M-RLnWMVSJx8rkuxElTCVibSwgu4QbOLJ0',
159+
fsPath: 'en/1.getting-started/3.installation.md',
160+
},
161+
]

src/app/test/mocks/tree.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const tree: TreeItem[] = [
99
routePath: '/',
1010
},
1111
{
12-
id: 'docs/1.getting-started',
12+
id: '1.getting-started',
1313
name: 'getting-started',
1414
fsPath: 'getting-started',
1515
type: 'directory',
@@ -29,7 +29,7 @@ export const tree: TreeItem[] = [
2929
routePath: '/getting-started/installation',
3030
},
3131
{
32-
id: 'docs/1.getting-started/1.advanced',
32+
id: '1.getting-started/1.advanced',
3333
name: 'advanced',
3434
fsPath: '1.getting-started/1.advanced',
3535
type: 'directory',

src/app/test/unit/utils/draft.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('findDescendantsFromId', () => {
1818
})
1919

2020
it('returns all descendants files for a directory path', () => {
21-
const descendants = findDescendantsFromId(draftItemsList, 'docs/1.getting-started')
21+
const descendants = findDescendantsFromId(draftItemsList, '1.getting-started')
2222

2323
expect(descendants).toHaveLength(5)
2424

@@ -30,7 +30,7 @@ describe('findDescendantsFromId', () => {
3030
})
3131

3232
it('returns all descendants for a nested directory path', () => {
33-
const descendants = findDescendantsFromId(draftItemsList, 'docs/1.getting-started/1.advanced')
33+
const descendants = findDescendantsFromId(draftItemsList, '1.getting-started/1.advanced')
3434

3535
expect(descendants).toHaveLength(2)
3636

src/app/test/unit/utils/tree.test.ts

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'
22
import { buildTree, findParentFromId, findItemFromRoute, findItemFromId, findDescendantsFileItemsFromId, getTreeStatus } from '../../../src/utils/tree'
33
import { tree } from '../../../test/mocks/tree'
44
import type { TreeItem } from '../../../src/types/tree'
5-
import { dbItemsList, nestedDbItemsList } from '../../../test/mocks/database'
5+
import { dbItemsList, languagePrefixedDbItemsList, nestedDbItemsList } from '../../../test/mocks/database'
66
import type { DraftItem } from '../../../src/types/draft'
77
import type { MediaItem } from '../../../src/types'
88
import { DraftStatus, TreeRootId, TreeStatus } from '../../../src/types'
@@ -22,10 +22,9 @@ describe('buildTree of documents with one level of depth', () => {
2222
prefix: null,
2323
},
2424
{
25-
id: 'docs/1.getting-started',
25+
id: '1.getting-started',
2626
name: 'getting-started',
2727
fsPath: '1.getting-started',
28-
routePath: '/getting-started',
2928
type: 'directory',
3029
prefix: 1,
3130
children: [
@@ -310,10 +309,9 @@ describe('buildTree of documents with one level of depth', () => {
310309
describe('buildTree of documents with two levels of depth', () => {
311310
const result: TreeItem[] = [
312311
{
313-
id: 'docs/1.essentials',
312+
id: '1.essentials',
314313
name: 'essentials',
315314
fsPath: '1.essentials',
316-
routePath: '/essentials',
317315
type: 'directory',
318316
prefix: 1,
319317
children: [
@@ -326,10 +324,9 @@ describe('buildTree of documents with two levels of depth', () => {
326324
prefix: 2,
327325
},
328326
{
329-
id: 'docs/1.essentials/1.nested',
327+
id: '1.essentials/1.nested',
330328
name: 'nested',
331329
fsPath: '1.essentials/1.nested',
332-
routePath: '/essentials/nested',
333330
type: 'directory',
334331
prefix: 1,
335332
children: [
@@ -460,7 +457,59 @@ describe('buildTree of documents with two levels of depth', () => {
460457
})
461458
})
462459

463-
describe('buildTree od medias', () => {
460+
describe('buildTree of documents with language prefixed', () => {
461+
const result: TreeItem[] = [
462+
{
463+
id: 'en',
464+
name: 'en',
465+
fsPath: 'en',
466+
type: 'directory',
467+
prefix: null,
468+
children: [
469+
{
470+
id: 'landing_en/en/index.md',
471+
name: 'index',
472+
fsPath: 'en/index.md',
473+
prefix: null,
474+
type: 'file',
475+
routePath: '/en',
476+
},
477+
{
478+
id: 'en/1.getting-started',
479+
name: 'getting-started',
480+
fsPath: 'en/1.getting-started',
481+
type: 'directory',
482+
prefix: 1,
483+
children: [
484+
{
485+
id: 'docs_en/en/1.getting-started/2.introduction.md',
486+
name: 'introduction',
487+
fsPath: 'en/1.getting-started/2.introduction.md',
488+
type: 'file',
489+
routePath: '/en/getting-started/introduction',
490+
prefix: 2,
491+
},
492+
{
493+
id: 'docs_en/en/1.getting-started/3.installation.md',
494+
name: 'installation',
495+
fsPath: 'en/1.getting-started/3.installation.md',
496+
type: 'file',
497+
routePath: '/en/getting-started/installation',
498+
prefix: 3,
499+
},
500+
],
501+
},
502+
],
503+
},
504+
]
505+
506+
it('Without draft', () => {
507+
const tree = buildTree(languagePrefixedDbItemsList, null)
508+
expect(tree).toStrictEqual(result)
509+
})
510+
})
511+
512+
describe('buildTree of medias', () => {
464513
it('With .gitkeep file in directory (file is marked as hidden)', () => {
465514
const mediaFolderName = 'media-folder'
466515
const gitKeepId = joinURL(TreeRootId.Media, mediaFolderName, '.gitkeep')
@@ -493,7 +542,7 @@ describe('buildTree od medias', () => {
493542
const tree = buildTree([gitkeepDbItem, mediaDbItem], draftList)
494543

495544
expect(tree).toHaveLength(1)
496-
expect(tree[0]).toHaveProperty('id', joinURL(TreeRootId.Media, mediaFolderName))
545+
expect(tree[0]).toHaveProperty('id', mediaFolderName)
497546
expect(tree[0].children).toHaveLength(2)
498547

499548
const gitkeepFile = tree[0].children!.find(item => item.id === gitKeepId)
@@ -566,13 +615,13 @@ describe('findParentFromId', () => {
566615
it('should find direct parent of a child', () => {
567616
const parent = findParentFromId(tree, 'docs/1.getting-started/2.introduction.md')
568617
expect(parent).toBeDefined()
569-
expect(parent?.id).toBe('docs/1.getting-started')
618+
expect(parent?.id).toBe('1.getting-started')
570619
})
571620

572621
it('should find nested parent', () => {
573622
const parent = findParentFromId(tree, 'docs/1.getting-started/1.advanced/1.studio.md')
574623
expect(parent).toBeDefined()
575-
expect(parent?.id).toBe('docs/1.getting-started/1.advanced')
624+
expect(parent?.id).toBe('1.getting-started/1.advanced')
576625
})
577626

578627
it('should return null for root level items', () => {
@@ -649,9 +698,9 @@ describe('findItemFromId', () => {
649698
})
650699

651700
it('should find directory by id', () => {
652-
const item = findItemFromId(tree, 'docs/1.getting-started')
701+
const item = findItemFromId(tree, '1.getting-started')
653702
expect(item).toBeDefined()
654-
expect(item?.id).toBe('docs/1.getting-started')
703+
expect(item?.id).toBe('1.getting-started')
655704
expect(item?.name).toBe('getting-started')
656705
expect(item?.type).toBe('directory')
657706
expect(item?.children).toBeDefined()
@@ -666,9 +715,9 @@ describe('findItemFromId', () => {
666715
})
667716

668717
it('should find nested directory by id', () => {
669-
const item = findItemFromId(tree, 'docs/1.getting-started/1.advanced')
718+
const item = findItemFromId(tree, '1.getting-started/1.advanced')
670719
expect(item).toBeDefined()
671-
expect(item?.id).toBe('docs/1.getting-started/1.advanced')
720+
expect(item?.id).toBe('1.getting-started/1.advanced')
672721
expect(item?.name).toBe('advanced')
673722
expect(item?.type).toBe('directory')
674723
})
@@ -707,7 +756,7 @@ describe('findDescendantsFileItemsFromId', () => {
707756
})
708757

709758
it('returns all descendants files for directory id', () => {
710-
const descendants = findDescendantsFileItemsFromId(tree, 'docs/1.getting-started')
759+
const descendants = findDescendantsFileItemsFromId(tree, '1.getting-started')
711760

712761
expect(descendants).toHaveLength(3)
713762

@@ -717,7 +766,7 @@ describe('findDescendantsFileItemsFromId', () => {
717766
})
718767

719768
it('returns all descendants files for nested directory id', () => {
720-
const descendants = findDescendantsFileItemsFromId(tree, 'docs/1.getting-started/1.advanced')
769+
const descendants = findDescendantsFileItemsFromId(tree, '1.getting-started/1.advanced')
721770

722771
expect(descendants).toHaveLength(1)
723772

0 commit comments

Comments
 (0)