summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFushihara <1039534+fushihara@users.noreply.github.com>2024-09-23 21:26:42 +0900
committerFushihara <1039534+fushihara@users.noreply.github.com>2024-09-23 21:26:42 +0900
commitc1cc8ef47e4a405697cd9855667e3df5593bd557 (patch)
treedc0fb7319d8386614ca883405fdfe83a93eb5d72 /src
parentba0c8d73e585d7ff249dd90684254bc7fa025544 (diff)
タグ一覧のデータの仕組み変更
Diffstat (limited to 'src')
-rw-r--r--src/app/anime/[animeId]/page.tsx2
-rw-r--r--src/app/article/all/[pageId]/page.tsx3
-rw-r--r--src/app/article/tag/[tagName]/page.tsx4
-rw-r--r--src/app/article/tag/page.tsx70
-rw-r--r--src/util/articleLoader.ts132
5 files changed, 176 insertions, 35 deletions
diff --git a/src/app/anime/[animeId]/page.tsx b/src/app/anime/[animeId]/page.tsx
index 968ce20..0f96f30 100644
--- a/src/app/anime/[animeId]/page.tsx
+++ b/src/app/anime/[animeId]/page.tsx
@@ -228,4 +228,4 @@ export async function generateStaticParams() {
return loadedData.map(c => {
return { animeId: String(c.animeId) };
}) satisfies PageType["params"][];
-} \ No newline at end of file
+}
diff --git a/src/app/article/all/[pageId]/page.tsx b/src/app/article/all/[pageId]/page.tsx
index fd72772..0374401 100644
--- a/src/app/article/all/[pageId]/page.tsx
+++ b/src/app/article/all/[pageId]/page.tsx
@@ -149,9 +149,6 @@ export async function generateStaticParams() {
return { pageId: `page-${index + 1}`, data: data };
});
}
-// export const generateStaticParams = async () => {
-// return [{ articleid: "123" }];
-// };
function chunk<T = any>(list: T[], len: number) {
if (len <= 0) {
diff --git a/src/app/article/tag/[tagName]/page.tsx b/src/app/article/tag/[tagName]/page.tsx
index 3df1f1e..b783e82 100644
--- a/src/app/article/tag/[tagName]/page.tsx
+++ b/src/app/article/tag/[tagName]/page.tsx
@@ -8,7 +8,7 @@ type PageType = {
}
export async function generateMetadata(context: PageType) {
return {
- title: `アキバ総研アーカイブ:ページ ${context.params.tagName}`,
+ title: `アキバ総研アーカイブ:タグの記事一覧 ${context.params.tagName}`,
}
}
export default async function Page(context: PageType) {
@@ -35,6 +35,6 @@ export default async function Page(context: PageType) {
export async function generateStaticParams() {
const tagList = await new ArticleLoader().getTagList();
return tagList.map((data, index) => {
- return { tagName: data.name };
+ return { tagName: encodeURIComponent(data.tag) };
});
}
diff --git a/src/app/article/tag/page.tsx b/src/app/article/tag/page.tsx
index 211ca94..f1d161b 100644
--- a/src/app/article/tag/page.tsx
+++ b/src/app/article/tag/page.tsx
@@ -14,17 +14,67 @@ export async function generateMetadata(context: PageType) {
}
export default async function Page(context: PageType) {
const tagList = await new ArticleLoader().getTagList();
- const tagsElement: JSX.Element[] = [];
- tagList.forEach(t => {
- tagsElement.push(<span key={t.name}><Link href={`/article/tag/${t.name}`}>{t.name}({t.count})</Link></span>)
- })
+ type TAG = { tag: string, count: number, primary?: boolean };
+ const elementListPcPart: TAG[] = [];
+ const elementListAkiba: TAG[] = [];
+ const elementListAnime: TAG[] = [];
+ const elementListAnimeSeason: TAG[] = [];
+ const elementListGame: TAG[] = [];
+ const elementListHobby: TAG[] = [];
+ const elementListOther: TAG[] = [];
+ for (const tag of tagList) {
+ if (tag.category == "PCパーツ") {
+ elementListPcPart.push({ tag: tag.tag, count: tag.count, primary: tag.tag == "PCパーツ" });
+ } else if (tag.category == "アキバ") {
+ elementListAkiba.push({ tag: tag.tag, count: tag.count, primary: tag.tag == "アキバ" });
+ } else if (tag.category == "アニメ") {
+ elementListAnime.push({ tag: tag.tag, count: tag.count, primary: tag.tag == "アニメ" });
+ } else if (tag.category == "ゲーム") {
+ elementListGame.push({ tag: tag.tag, count: tag.count, primary: tag.tag == "ゲーム" });
+ } else if (tag.category == "ホビー") {
+ elementListHobby.push({ tag: tag.tag, count: tag.count, primary: tag.tag == "ホビー" });
+ } else if (tag.tag.match(/^\d+(春|夏|秋|冬)?アニメ$/)) {
+ elementListAnimeSeason.push({ tag: tag.tag, count: tag.count });
+ } else if (tag.tag == "G.E.M.シリーズ") {
+ elementListHobby.push({ tag: tag.tag, count: tag.count });
+ } else if (["3DS", "PS4ゲームレビュー", "PS Vita", "PS5ゲームレビュー", "Switchインディーズ", "ポケモンGO", "Steamゲームレビュー"].includes(tag.tag)) {
+ elementListGame.push({ tag: tag.tag, count: tag.count });
+ } else {
+ elementListOther.push({ tag: tag.tag, count: tag.count });
+ }
+ }
return (
- <div className="p-8 pb-20 gap-16 sm:p-20">
- <h1>著名なタグ一覧</h1>
- <h1>記事にセットされているタグの一覧</h1>
- <div className="flex gap-2 flex-wrap">
- {tagsElement}
- </div>
+ <div className="p-1">
+ {createList("PCパーツ", elementListPcPart)}
+ {createList("アキバ", elementListAkiba)}
+ {createList("ゲーム", elementListGame)}
+ {createList("ホビー", elementListHobby)}
+ {createList("カテゴリなし", elementListOther)}
+ {createList("アニメ", elementListAnime)}
+ {createList("アニメ(時期別)", elementListAnimeSeason)}
</div>
);
}
+function createList(headerLabel: string, tagList: { tag: string, count: number, primary?: boolean }[]) {
+ const sortedList = tagList.toSorted((a, b) => {
+ if (a.primary == true) {
+ return -1;
+ } else if (b.primary == true) {
+ return 1;
+ } else {
+ return b.count - a.count;
+ };
+ })
+ const elementList: JSX.Element[] = [];
+ sortedList.forEach(t => {
+ elementList.push(
+ <Link href={`/article/tag/${t.tag}`} key={`list-${tagList.indexOf(t)}`} className="original-href">{t.tag}({t.count})</Link>
+ )
+ })
+ return (<>
+ <h1>{headerLabel}</h1>
+ <div className="flex gap-2 flex-wrap">
+ {elementList}
+ </div>
+ </>);
+} \ No newline at end of file
diff --git a/src/util/articleLoader.ts b/src/util/articleLoader.ts
index 787725a..c957e3a 100644
--- a/src/util/articleLoader.ts
+++ b/src/util/articleLoader.ts
@@ -15,7 +15,6 @@ export class ArticleLoader {
constructor() { }
async loadData() {
const articleJsonPath = process.env["AKIBA_SOUKEN_ARTICLE_JSON"]!;
- //console.log(`[${articleJsonPath}]`);
const jsonStr = await readFile(articleJsonPath, { encoding: "utf-8" }).then(text => {
const jsonObj = JSON.parse(text);
return jsonObj;
@@ -29,6 +28,7 @@ export class ArticleLoader {
}
return parsedObj;
}
+
async getCategoryList() {
const loadedData = await this.loadData();
// key:カテゴリ名 , val:回数
@@ -53,36 +53,130 @@ export class ArticleLoader {
});
return categoryList;
}
+ /**
+ *
+ * @returns [{tag:"タグ名",category:string,count:100}] の値。カテゴリに属していないタグはカテゴリが空文字。
+ * カテゴリ自体を指す場合は {tag:"ホビー",category:"ホビー",count:100} の様に同じ値が入る事がある。
+ * ソート順は未定義
+ */
async getTagList() {
const loadedData = await this.loadData();
+ // 先にパンくずリストを全部見る
+ const breadcrumb = new Breadcrumb();
+ for (const a of loadedData) {
+ breadcrumb.push(a.breadLinks);
+ }
// key:タグ名 , val:回数
const tagCount = new Map<string, number>();
+ // パンくずリストに含まれないタグを調査
for (const a of loadedData) {
- const tags = new Set<string>();
- a.tags.forEach(t => {
- tags.add(t);
- });
- // パンくずリストの2件目以降はタグ扱いになっている
- a.breadLinks.forEach((b, index) => {
- if (index != 0) {
- tags.add(b);
+ for (const tag of a.tags) {
+ if (breadcrumb.strExists(tag)) {
+ continue;
}
- })
- for (const tag of tags) {
- if (tagCount.has(tag)) {
- tagCount.set(tag, tagCount.get(tag)! + 1);
+ const tagData = tagCount.get(tag);
+ if (tagData != null) {
+ tagCount.set(tag, tagData + 1);
} else {
tagCount.set(tag, 1);
}
}
}
- const tagList: { name: string, count: number }[] = [];
+ const result: { tag: string, category: string, count: number }[] = [];
for (const [name, count] of tagCount) {
- tagList.push({ name: name, count });
+ result.push({ tag: name, count: count, category: "" });
}
- tagList.sort((a, b) => {
- return b.count - a.count;
- });
- return tagList;
+ for (const b of breadcrumb.getFlatArray()) {
+ const breadcrumbName = b.breadcrumb[b.breadcrumb.length - 1];//パンくずリストの最後の項目
+ const category = b.breadcrumb[0];//カテゴリ名
+ result.push({ tag: breadcrumbName, count: b.count, category: category });
+ }
+ return result;
}
}
+
+type BreadcrumbInternal = { name: string, count: number, child: BreadcrumbInternal[] };
+/**
+ * パンくずリストを管理
+ * ・各記事には最低2個以上のパンくずリストがある
+ * ・ホビー>バンダイ>S.H.Figuarts の様に親子関係がある
+ * ・文字は一箇所でしか使われない。例えば、バンダイという文字はホビーの下にしか存在しない。
+ */
+class Breadcrumb {
+ private set = new Set<string>();
+ private data: BreadcrumbInternal[] = [];
+ strExists(str: string) {
+ return this.set.has(str);
+ }
+ push(breadLinks: string[]) {
+ if (breadLinks.length == 0) {
+ throw new Error(`配列が0個です`);
+ }
+ breadLinks.forEach(b => {
+ this.set.add(b);
+ })
+ this.setChild(
+ this.data,
+ breadLinks,
+ );
+ }
+ getFlatArray() {
+ function hoge(dataList: BreadcrumbInternal[], parentBreadcrumbList: string[]) {
+ const sortedDataList = [...dataList].toSorted((a, b) => {
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
+ });
+ const result: { breadcrumb: string[], count: number }[] = [];
+ for (const v of sortedDataList) {
+ if (v.child.length == 0) {
+ if (v.count == 0) {
+ throw new Error(`子が0個で、個数も0はありえない`);
+ } else {
+ result.push({ breadcrumb: [...parentBreadcrumbList, v.name], count: v.count });
+ }
+ } else {
+ if (v.count != 0) {
+ result.push({ breadcrumb: [...parentBreadcrumbList, v.name], count: v.count });
+ }
+ const childResult = hoge(v.child, [...parentBreadcrumbList, v.name]);
+ childResult.forEach(c => {
+ result.push(c);
+ })
+ }
+ }
+ return result;
+ }
+ const result = hoge(this.data, []);
+ return result;
+ }
+
+ private setChild(pushTarget: BreadcrumbInternal[], breadLinks: string[]) {
+ const [top, ...nextBreadLinks] = breadLinks;
+ const isLast = breadLinks.length == 1;
+ const data = pushTarget.find(d => d.name == top);
+ if (data) {
+ if (isLast) {
+ data.count += 1;
+ } else {
+ this.setChild(data.child, nextBreadLinks)
+ }
+ } else {
+ if (isLast) {
+ pushTarget.push({ name: top, child: [], count: 1 });
+ } else {
+ const newItem: BreadcrumbInternal = this.createChild(nextBreadLinks)
+ pushTarget.push({ name: top, child: [newItem], count: 0 });
+ }
+ }
+ }
+ private createChild(nextBreadLinks: string[]): BreadcrumbInternal {
+ if (nextBreadLinks.length == 0) {
+ throw new Error();
+ } else if (nextBreadLinks.length == 1) {
+ return { child: [], count: 1, name: nextBreadLinks[0] } satisfies BreadcrumbInternal;
+ } else {
+ const [nowBreadLink, ...nextNextBreadLinks] = nextBreadLinks;
+ const child = this.createChild(nextNextBreadLinks);
+ return { child: [child], count: 0, name: nowBreadLink } satisfies BreadcrumbInternal;
+ }
+ }
+} \ No newline at end of file