Skip to content

Commit 8405625

Browse files
authored
feat: add support for nest categories (#138)
* refactor: rename fetchCategories to listCategories * feat: add support for nest categories * refactor: rename CategoriesSelector to CategoriesSelect * feat: nest category select * fix: open remote post with nest categories * feat: add support for category metadata of the post
1 parent 8706386 commit 8405625

20 files changed

+411
-85
lines changed

src/commands/pdf/post-pdf-template-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export namespace postPdfTemplateBuilder {
3636
};
3737

3838
const buildCategoryHtml = async (): Promise<string> => {
39-
const categories = await postCategoryService.fetchCategories();
39+
const categories = await postCategoryService.listCategories();
4040
const postCategories =
4141
post.categoryIds
4242
?.map(categoryId => categories.find(x => x.categoryId === categoryId))

src/commands/post-category/new-post-category.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const newPostCategory = async () => {
2525
increment: 90,
2626
});
2727
refreshPostCategoriesList();
28-
const newCategory = (await postCategoryService.fetchCategories()).find(x => x.title === input.title);
28+
const newCategory = (await postCategoryService.listCategories()).find(x => x.title === input.title);
2929
if (newCategory) await extensionViews.postCategoriesList.reveal(newCategory);
3030
} catch (err) {
3131
void window.showErrorMessage('新建博文分类时遇到了错误', {

src/commands/posts-list/open-post-in-vscode.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,20 @@ const buildLocalPostFileUri = async (post: Post, includePostId = false): Promise
1818
const postIdSegment = includePostId ? `.${post.id}` : '';
1919
const { text: postTitle } = await PostTitleSanitizer.sanitize(post);
2020
if (shouldCreateLocalPostFileWithCategory) {
21-
let categories = await postCategoryService.fetchCategories();
22-
categories = categories.filter(x => post.categoryIds?.includes(x.categoryId));
23-
const categoryTitle = sanitizeFileName(categories[0]?.title ?? '', {
24-
replacement: invalidChar => (invalidChar === '/' ? '_' : ''),
25-
});
21+
const firstCategoryId = post.categoryIds?.[0];
22+
const category = firstCategoryId ? await postCategoryService.find(firstCategoryId) : null;
23+
let i: typeof category | undefined = category;
24+
let categoryTitle = '';
25+
while (i != null) {
26+
categoryTitle = path.join(
27+
sanitizeFileName(i.title, {
28+
replacement: invalidChar => (invalidChar === '/' ? '_' : ''),
29+
}),
30+
categoryTitle
31+
);
32+
i = i.parent;
33+
}
34+
2635
return Uri.joinPath(workspaceUri, categoryTitle, `${postTitle}${postIdSegment}${ext}`);
2736
} else {
2837
return Uri.joinPath(workspaceUri, `${postTitle}${postIdSegment}${ext}`);

src/commands/show-local-file-to-post-info.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const showLocalFileToPostInfo = async (input: Uri | number): Promise<void
5252
const post = (await postService.fetchPostEditDto(postId))?.post;
5353
if (!post) return;
5454

55-
let categories = await postCategoryService.fetchCategories();
55+
let categories = await postCategoryService.listCategories();
5656
categories = categories.filter(x => post.categoryIds?.includes(x.categoryId));
5757
const categoryDesc = categories.length > 0 ? `博文分类: ${categories.map(c => c.title).join(', ')}\n` : '';
5858
const tagsDesc = post.tags?.length ?? 0 > 0 ? `博文标签: ${post.tags?.join(', ')}\n` : '';

src/models/post-category.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
export class PostCategory {
2+
parentId?: number | null;
23
categoryId = -1;
34
title = '';
45
visible = true;
56
description = '';
67
updateTime: Date = new Date();
78
count = 0;
89
order?: number;
10+
childCount = 0;
11+
visibleChildCount = 0;
12+
parent?: PostCategory | null;
13+
14+
flattenParents({ includeSelf = true }: { includeSelf?: boolean } = {}): PostCategories {
15+
// eslint-disable-next-line @typescript-eslint/no-this-alias
16+
let i: PostCategory | null | undefined = this;
17+
const result: PostCategories = [];
18+
while (i != null) {
19+
if (i !== this || includeSelf) result.unshift(i);
20+
if (i.parent && !(i.parent instanceof PostCategory)) i.parent = Object.assign(new PostCategory(), i.parent);
21+
i = i.parent;
22+
}
23+
24+
return result;
25+
}
926
}
1027

1128
export type PostCategoryAddDto = Pick<PostCategory, 'title' | 'visible' | 'description'>;

src/models/webview-commands.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { PostCategories } from '@/models/post-category';
2+
13
export namespace webviewCommands {
24
export enum UiCommands {
35
editPostConfiguration = 'editPostConfiguration',
@@ -6,13 +8,24 @@ export namespace webviewCommands {
68
updateImageUploadStatus = 'updateImageUploadStatus',
79
setFluentIconBaseUrl = 'setFluentIconBaseUrl',
810
updateTheme = 'updateTheme',
11+
updateChildCategories = 'updateChildCategories',
912
}
1013

1114
export enum ExtensionCommands {
1215
savePost = 'savePost',
1316
disposePanel = 'disposePanel',
1417
uploadImage = 'uploadImage',
1518
refreshPost = 'refreshPost',
19+
getChildCategories = 'getChildCategories',
20+
}
21+
22+
export interface GetChildCategoriesPayload {
23+
parentId: number;
24+
}
25+
26+
export interface UpdateChildCategoriesPayload {
27+
parentId: number;
28+
value: PostCategories;
1629
}
1730

1831
export namespace ingCommands {
@@ -35,8 +48,9 @@ export namespace webviewCommands {
3548
}
3649
}
3750

38-
interface WebviewCommonCommand<T> {
51+
export interface WebviewCommonCommand<T> {
3952
payload: T;
53+
command: unknown;
4054
}
4155

4256
export interface IngWebviewUiCommand<T extends Record<string, unknown> = Record<string, unknown>>

src/services/post-category.service.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import fetch from '@/utils/fetch-client';
22
import { PostCategories, PostCategory, PostCategoryAddDto } from '../models/post-category';
33
import { globalContext } from './global-state';
4+
import { URLSearchParams } from 'url';
45

56
export class PostCategoryService {
67
private static _instance: PostCategoryService;
78

8-
private _cached?: PostCategories;
9+
private _cache?: Map<number, PostCategories>;
910

1011
private constructor() {}
1112

@@ -19,19 +20,51 @@ export class PostCategoryService {
1920
ids = ids.filter(x => x > 0);
2021
if (ids.length <= 0) return [];
2122

22-
const categories = await this.fetchCategories(!useCache);
23+
const categories = await this.listCategories(!useCache);
2324
return categories.filter(({ categoryId }) => ids.includes(categoryId));
2425
}
2526

26-
async fetchCategories(forceRefresh = false): Promise<PostCategories> {
27-
if (this._cached && !forceRefresh) return this._cached;
28-
29-
const res = await fetch(`${globalContext.config.apiBaseUrl}/api/category/blog/1/edit`);
27+
listCategories(): Promise<PostCategories>;
28+
listCategories({
29+
forceRefresh = false,
30+
parentId = -1,
31+
}: {
32+
forceRefresh?: boolean | null;
33+
parentId?: number;
34+
}): Promise<PostCategories>;
35+
listCategories(forceRefresh: boolean): Promise<PostCategories>;
36+
async listCategories(
37+
option: boolean | { forceRefresh?: boolean | null; parentId?: number } = {}
38+
): Promise<PostCategories> {
39+
const parentId = typeof option === 'object' ? option.parentId ?? -1 : -1;
40+
const shouldForceRefresh =
41+
option === true || (typeof option === 'object' ? option.forceRefresh ?? false : false);
42+
const map = (this._cache ??= new Map<number, PostCategories>());
43+
const cachedCategories = map.get(parentId);
44+
if (cachedCategories && !shouldForceRefresh) return cachedCategories;
45+
46+
const res = await fetch(
47+
`${globalContext.config.apiBaseUrl}/api/v2/blog-category-types/1/categories?${new URLSearchParams([
48+
['parent', parentId <= 0 ? '' : `${parentId}`],
49+
]).toString()}`
50+
);
3051
if (!res.ok) throw Error(`Failed to fetch post categories\n${res.status}\n${await res.text()}`);
3152

32-
const categories = <PostCategories>await res.json();
33-
this._cached = categories.map(x => Object.assign(new PostCategory(), x));
34-
return this._cached;
53+
let { categories } = <{ parent?: PostCategory | null; categories: PostCategories }>await res.json();
54+
categories = categories.map(x => Object.assign(new PostCategory(), x));
55+
map.set(parentId, categories);
56+
return categories;
57+
}
58+
59+
async find(id: number) {
60+
const res = await fetch(
61+
`${globalContext.config.apiBaseUrl}/api/v2/blog-category-types/1/categories?${new URLSearchParams([
62+
['parent', id <= 0 ? '' : `${id}`],
63+
]).toString()}`
64+
);
65+
const { parent } = <{ parent?: PostCategory | null; categories: PostCategories }>await res.json();
66+
67+
return Object.assign(new PostCategory(), parent);
3568
}
3669

3770
async newCategory(categoryAddDto: PostCategoryAddDto) {
@@ -63,7 +96,7 @@ export class PostCategoryService {
6396
}
6497

6598
clearCache() {
66-
this._cached = undefined;
99+
this._cache = undefined;
67100
}
68101
}
69102

src/services/post-configuration-panel.service.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { postTagService } from './post-tag.service';
88
import { postService } from './post.service';
99
import { isErrorResponse } from '../models/error-response';
1010
import { webviewMessage } from '../models/webview-message';
11-
import { webviewCommands } from 'src/models/webview-commands';
11+
import { WebviewCommonCommand, webviewCommands } from 'src/models/webview-commands';
1212
import { uploadImage } from '../commands/upload-image/upload-image';
1313
import { ImageUploadStatusId } from '../models/image-upload-status';
1414
import { openPostFile } from '../commands/posts-list/open-post-file';
@@ -60,7 +60,7 @@ export namespace postConfigurationPanel {
6060
command: webviewCommands.UiCommands.editPostConfiguration,
6161
post: cloneDeep(post),
6262
activeTheme: vscode.window.activeColorTheme.kind,
63-
personalCategories: cloneDeep(await postCategoryService.fetchCategories()),
63+
personalCategories: cloneDeep(await postCategoryService.listCategories()),
6464
siteCategories: cloneDeep(await siteCategoryService.fetchAll()),
6565
tags: cloneDeep(await postTagService.fetchTags()),
6666
breadcrumbs,
@@ -205,6 +205,20 @@ export namespace postConfigurationPanel {
205205
case webviewCommands.ExtensionCommands.uploadImage:
206206
await onUploadImageCommand(panel, <webviewMessage.UploadImageMessage>message);
207207
break;
208+
case webviewCommands.ExtensionCommands.getChildCategories:
209+
{
210+
const { payload } = message as WebviewCommonCommand<webviewCommands.GetChildCategoriesPayload>;
211+
await webview.postMessage({
212+
command: webviewCommands.UiCommands.updateChildCategories,
213+
payload: {
214+
value: await postCategoryService
215+
.listCategories({ parentId: payload.parentId })
216+
.catch(() => []),
217+
parentId: payload.parentId,
218+
},
219+
} as WebviewCommonCommand<webviewCommands.UpdateChildCategoriesPayload>);
220+
}
221+
break;
208222
}
209223
});
210224
};

src/tree-view-providers/models/post-category-tree-item.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { BaseTreeItemSource } from './base-tree-item-source';
55
import { PostTreeItem } from './post-tree-item';
66

77
export class PostCategoryTreeItem extends BaseTreeItemSource {
8-
constructor(public readonly category: PostCategory, public children?: PostTreeItem<PostCategoryTreeItem>[]) {
8+
constructor(public readonly category: PostCategory, public children?: (PostCategoryTreeItem | PostTreeItem)[]) {
99
super();
1010
}
1111

src/tree-view-providers/models/post-metadata.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { postService } from '../../services/post.service';
1111
import { BaseEntryTreeItem } from './base-entry-tree-item';
1212
import { BaseTreeItemSource } from './base-tree-item-source';
1313
import { PostTreeItem } from './post-tree-item';
14+
import { PostCategory } from '@/models/post-category';
1415

1516
export enum RootPostMetadataType {
1617
categoryEntry = 'categoryEntry',
@@ -129,9 +130,19 @@ export class PostCategoryMetadata extends PostMetadata {
129130
const {
130131
post: { categoryIds },
131132
} = editDto;
132-
return (await postCategoryService.findCategories(categoryIds ?? [])).map(
133-
({ categoryId, title }) => new PostCategoryMetadata(parent, title, categoryId)
134-
);
133+
return (await Promise.all((categoryIds ?? []).map(categoryId => postCategoryService.find(categoryId))))
134+
.filter((x): x is PostCategory => x != null)
135+
.map(
136+
category =>
137+
new PostCategoryMetadata(
138+
parent,
139+
category
140+
.flattenParents()
141+
.map(({ title }) => title)
142+
.join('/'),
143+
category.categoryId
144+
)
145+
);
135146
}
136147

137148
toTreeItem = (): TreeItem =>

0 commit comments

Comments
 (0)