summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFushihara <1039534+fushihara@users.noreply.github.com>2024-09-25 21:21:07 +0900
committerFushihara <1039534+fushihara@users.noreply.github.com>2024-09-25 21:21:07 +0900
commit914d46215fd955bc9045909d6e42bfb2f9a99331 (patch)
treeccba56f02391527a389c84bc0cc70644a9ec088e /src
parent4ccee1438e3c341075fdcf1d62e6900eb29d1908 (diff)
記事データの持ち方を修正
ビルド時に1ページづつインスタンスが破棄されると思いこんでいて毎回ファイルの読み込みからやり直していた。
Diffstat (limited to 'src')
-rw-r--r--src/app/article/_components/articleListElement.tsx2
-rw-r--r--src/app/article/all/[pageId]/page.tsx6
-rw-r--r--src/app/article/tag/[tagName]/page.tsx12
-rw-r--r--src/app/article/tag/page.tsx38
-rw-r--r--src/util/articleLoader.ts179
5 files changed, 155 insertions, 82 deletions
diff --git a/src/app/article/_components/articleListElement.tsx b/src/app/article/_components/articleListElement.tsx
index 0fe3d70..5437c04 100644
--- a/src/app/article/_components/articleListElement.tsx
+++ b/src/app/article/_components/articleListElement.tsx
@@ -5,7 +5,7 @@ dateformat.i18n.dayNames = [
'日', '月', '火', '水', '木', '金', '土',
'日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'
];
-type DisplayData = Awaited<ReturnType<ArticleLoader["loadData"]>>[number];
+type DisplayData = Awaited<ReturnType<ArticleLoader["loadData"]>>["articles"][number];
export function ArticleListElement(displayData: DisplayData[]) {
return (
<table className="w-full">
diff --git a/src/app/article/all/[pageId]/page.tsx b/src/app/article/all/[pageId]/page.tsx
index fda6fbb..e076aa0 100644
--- a/src/app/article/all/[pageId]/page.tsx
+++ b/src/app/article/all/[pageId]/page.tsx
@@ -22,12 +22,12 @@ export async function generateMetadata(context: PageType) {
export default async function Page(context: PageType) {
const pageId = getPageIdNumber(context.params.pageId);
const loadedData = await ArticleLoader.instance.loadData();
- const chunkdData = chunk(loadedData, PPV);
+ const chunkdData = chunk(loadedData.articles, PPV);
const displayData = chunkdData[pageId - 1];
return (
<div className="p-1 gap-16">
{pagenationElement(pageId, chunkdData.length)}
- <div className="text-right">全:{loadedData.length}件</div>
+ <div className="text-right">全:{loadedData.articles.length}件</div>
{ArticleListElement(displayData)}
{pagenationElement(pageId, chunkdData.length)}
</div>
@@ -142,7 +142,7 @@ if (!Number.isInteger(PPV)) {
//export const dynamicParams = true;
export async function generateStaticParams() {
const loadedData = await ArticleLoader.instance.loadData();
- const chunkdData = chunk(loadedData, PPV);
+ const chunkdData = chunk(loadedData.articles, PPV);
return chunkdData.map((data, index) => {
return { pageId: `page-${index + 1}`, data: data };
});
diff --git a/src/app/article/tag/[tagName]/page.tsx b/src/app/article/tag/[tagName]/page.tsx
index 84c1677..991ebfc 100644
--- a/src/app/article/tag/[tagName]/page.tsx
+++ b/src/app/article/tag/[tagName]/page.tsx
@@ -14,7 +14,7 @@ export async function generateMetadata(context: PageType) {
export default async function Page(context: PageType) {
const nowPageTagName = decodeURIComponent(context.params.tagName);
const loadedData = await ArticleLoader.instance.loadData().then(articles => {
- const filterd = articles.filter(article => {
+ const filterd = articles.articles.filter(article => {
if (article.tags.includes(nowPageTagName)) {
return true;
}
@@ -34,8 +34,10 @@ export default async function Page(context: PageType) {
);
}
export async function generateStaticParams() {
- const tagList = await ArticleLoader.instance.getTagList();
- return tagList.map((data, index) => {
- return { tagName: data.tag };
- });
+ const articleData = await ArticleLoader.instance.loadData();
+ const result: { tagName: string }[] = [];
+ for (const tag of articleData.categoryTag.data.keys()) {
+ result.push({ tagName: tag });
+ }
+ return result;
}
diff --git a/src/app/article/tag/page.tsx b/src/app/article/tag/page.tsx
index 4f6ae0b..1d6efc3 100644
--- a/src/app/article/tag/page.tsx
+++ b/src/app/article/tag/page.tsx
@@ -13,7 +13,7 @@ export async function generateMetadata(context: PageType) {
}
}
export default async function Page(context: PageType) {
- const tagList = await ArticleLoader.instance.getTagList();
+ const articleData = await ArticleLoader.instance.loadData();
type TAG = { tag: string, count: number, primary?: boolean };
const elementListPcPart: TAG[] = [];
const elementListAkiba: TAG[] = [];
@@ -22,25 +22,25 @@ export default async function Page(context: PageType) {
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 });
+ for (const [tag, categoryAndCount] of articleData.categoryTag.data.entries()) {
+ if (categoryAndCount.category == "PCパーツ") {
+ elementListPcPart.push({ tag: tag, count: categoryAndCount.count, primary: tag == "PCパーツ" });
+ } else if (categoryAndCount.category == "アキバ") {
+ elementListAkiba.push({ tag: tag, count: categoryAndCount.count, primary: tag == "アキバ" });
+ } else if (categoryAndCount.category == "アニメ") {
+ elementListAnime.push({ tag: tag, count: categoryAndCount.count, primary: tag == "アニメ" });
+ } else if (categoryAndCount.category == "ゲーム") {
+ elementListGame.push({ tag: tag, count: categoryAndCount.count, primary: tag == "ゲーム" });
+ } else if (categoryAndCount.category == "ホビー") {
+ elementListHobby.push({ tag: tag, count: categoryAndCount.count, primary: tag == "ホビー" });
+ } else if (tag.match(/^\d+(春|夏|秋|冬)?アニメ$/)) {
+ elementListAnimeSeason.push({ tag: tag, count: categoryAndCount.count });
+ } else if (tag == "G.E.M.シリーズ") {
+ elementListHobby.push({ tag: tag, count: categoryAndCount.count });
+ } else if (["3DS", "PS4ゲームレビュー", "PS Vita", "PS5ゲームレビュー", "Switchインディーズ", "ポケモンGO", "Steamゲームレビュー"].includes(tag)) {
+ elementListGame.push({ tag: tag, count: categoryAndCount.count });
} else {
- elementListOther.push({ tag: tag.tag, count: tag.count });
+ elementListOther.push({ tag: tag, count: categoryAndCount.count });
}
}
return (
diff --git a/src/util/articleLoader.ts b/src/util/articleLoader.ts
index 860593d..1ae0448 100644
--- a/src/util/articleLoader.ts
+++ b/src/util/articleLoader.ts
@@ -14,7 +14,10 @@ const MAX_ITEM_LIMIT = process.env["AKIBA_SOUKEN_MAX_ITEM_LIMIT"];
export class ArticleLoader {
static instance = new ArticleLoader();
private constructor() { }
- private dataCache: Awaited<ReturnType<ArticleLoader["_loadData"]>> | null = null;
+ private dataCache: {
+ articles: Article[],
+ categoryTag: CategoryTag,
+ } | null = null;
async loadData() {
if (this.dataCache != null) {
return this.dataCache;
@@ -29,82 +32,150 @@ export class ArticleLoader {
const jsonObj = JSON.parse(text);
return jsonObj;
});
- const parsedObj = zodType.parse(jsonStr);
+ const parsedObj = zodType.parse(jsonStr).map(v => new Article(v));
parsedObj.sort((a, b) => {
return b.timestampMs - a.timestampMs;
});
if (MAX_ITEM_LIMIT != null) {
parsedObj.length = Math.min(Number(MAX_ITEM_LIMIT), parsedObj.length);
}
- return parsedObj;
- }
- async getCategoryList() {
- const loadedData = await this.loadData();
- // key:カテゴリ名 , val:回数
- const categoryCount = new Map<string, number>();
- for (const a of loadedData) {
- if (a.breadLinks.length == 0) {
- continue;
+ const categoryTagData = new CategoryTag();
+ parsedObj.forEach(v => {
+ for (const tag of v.tags2) {
+ categoryTagData.put(tag.category, tag.name);
}
- const category = a.breadLinks[0];
- if (categoryCount.has(category)) {
- categoryCount.set(category, categoryCount.get(category)! + 1);
- } else {
- categoryCount.set(category, 1);
- }
- }
- const categoryList: { name: string, count: number }[] = [];
- for (const [name, count] of categoryCount) {
- categoryList.push({ name: name, count });
- }
- categoryList.sort((a, b) => {
- return b.count - a.count;
});
- return categoryList;
+ return {
+ articles: parsedObj,
+ categoryTag: categoryTagData,
+ };
}
/**
*
* @returns [{tag:"タグ名",category:string,count:100}] の値。カテゴリに属していないタグはカテゴリが空文字。
* カテゴリ自体を指す場合は {tag:"ホビー",category:"ホビー",count:100} の様に同じ値が入る事がある。
* ソート順は未定義
+ * @deprecated
+ */
+ // async getTagList() {
+ // const loadedData = await this.loadData();
+ // // 先にパンくずリストを全部見る
+ // const breadcrumb = new Breadcrumb();
+ // for (const a of loadedData.articles) {
+ // breadcrumb.push(a.breadLinks);
+ // }
+ // // key:タグ名 , val:回数
+ // const tagCount = new Map<string, number>();
+ // // パンくずリストに含まれないタグを調査
+ // for (const a of loadedData.articles) {
+ // for (const tag of a.tags) {
+ // if (breadcrumb.strExists(tag)) {
+ // continue;
+ // }
+ // const tagData = tagCount.get(tag);
+ // if (tagData != null) {
+ // tagCount.set(tag, tagData + 1);
+ // } else {
+ // tagCount.set(tag, 1);
+ // }
+ // }
+ // }
+ // const result: { tag: string, category: string, count: number }[] = [];
+ // for (const [name, count] of tagCount) {
+ // result.push({ tag: name, count: count, category: "" });
+ // }
+ // 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 TagName = string;
+class CategoryTag {
+ /**
+ * key:タグ名
+ * val:カテゴリ名
*/
- async getTagList() {
- const loadedData = await this.loadData();
- // 先にパンくずリストを全部見る
- const breadcrumb = new Breadcrumb();
- for (const a of loadedData) {
- breadcrumb.push(a.breadLinks);
+ readonly data = new Map<TagName, { category: string | null, count: number }>();
+ put(category: string, tag: string) {
+ if (tag == "") {
+ throw new Error(`タグが空文字です`)
}
- // key:タグ名 , val:回数
- const tagCount = new Map<string, number>();
- // パンくずリストに含まれないタグを調査
- for (const a of loadedData) {
- for (const tag of a.tags) {
- if (breadcrumb.strExists(tag)) {
- continue;
- }
- const tagData = tagCount.get(tag);
- if (tagData != null) {
- tagCount.set(tag, tagData + 1);
- } else {
- tagCount.set(tag, 1);
- }
+ const storeCategory = this.data.get(tag);
+ if (storeCategory == null) {
+ this.data.set(tag, { category: category, count: 1 });
+ } else if (storeCategory.category != category) {
+ if (category == "" || storeCategory.category == "") {
+ storeCategory.count += 1;
+ } else if (category != "" || storeCategory.category == "") {
+ storeCategory.count += 1;
+ storeCategory.category = category;
+ } else if (category == "" || storeCategory.category != "") {
+ storeCategory.count += 1;
+ //} else if(category != "" || storeCategory.category != ""){
+ } else {
+ throw new Error(`カテゴリが不一致. tag:${tag} , storeCategory:${storeCategory.category} , newCategory:${category}`);
}
+ } else {
+ storeCategory.count += 1;
}
- const result: { tag: string, category: string, count: number }[] = [];
- for (const [name, count] of tagCount) {
- result.push({ tag: name, count: count, category: "" });
+ }
+}
+class Article {
+ constructor(
+ private readonly data: z.infer<typeof zodType>[number]
+ ) {
+ this.validate();
+ }
+ get timestampMs() {
+ return this.data.timestampMs;
+ }
+ get breadLinks() {
+ return this.data.breadLinks;
+ }
+ get tags() {
+ return this.data.tags;
+ }
+ get maxPageNumber() {
+ return this.data.maxPageNumber;
+ }
+ get articleId() {
+ return this.data.articleId;
+ }
+ get title() {
+ return this.data.title;
+ }
+ get tags2() {
+ const result = this.getTags();
+ return result;
+ }
+ private tagsCache: { name: string, category: string | "" }[] | null = null;
+ private getTags() {
+ if (this.tagsCache != null) {
+ return this.tagsCache;
}
- 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 });
+ const tags: { name: string, category: string | "" }[] = [];
+ for (const v of this.data.tags) {
+ tags.push({ name: v, category: "" })
+ };
+ const category = this.data.breadLinks[0];
+ for (const v of this.data.breadLinks) {
+ tags.push({ name: v, category: category });
+ };
+ this.tagsCache = tags;
+ return tags;
+ }
+ private validate() {
+ // パンくずリストは必ず1件以上あること
+ if (1 <= this.breadLinks.length) {
+ } else {
+ throw new Error(`パンくずリストの個数が不足しています`)
}
- return result;
}
}
-
type BreadcrumbInternal = { name: string, count: number, child: BreadcrumbInternal[] };
/**
* パンくずリストを管理