Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 26 additions & 33 deletions src/components/pages/Events.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
---
import assert from "assert";
import type { MDXInstance } from "astro";
import type { Frontmatter } from "@layouts/Layout.astro";
import { getCollection } from "astro:content";
import If from "@components/utils/If.astro";
import type { Lang } from "@components/types";
import { getLocaleDateString } from "@data/utils/notices";
import { getLocaleDateString } from "src/lib/util";

interface EventInformation {
key: string;
Expand All @@ -21,36 +19,31 @@ interface Props {

const { show, lang, alternative } = Astro.props;

const allEventsList: Map<string, EventInformation> = new Map(
[
await Astro.glob<MDXInstance<Frontmatter>>(
"/src/pages/events/*/index.{md,mdx}"
),
await Astro.glob<MDXInstance<Frontmatter>>("/src/pages/events/*.{md,mdx}"),
]
.flat()
.map((instance) => {
const dateMatch = instance.file.match(
/\/(([0-9]{4}-[0-9]{2}-[0-9]{2})?[^/]*)(?:\/index)?\.mdx?$/
);
assert(dateMatch !== null);
assert(instance.url !== undefined);
const events = await getCollection("events")
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at the end of the statement. This is inconsistent with the coding style used in the rest of the file.

Copilot uses AI. Check for mistakes.

return [
dateMatch[1],
{
key: dateMatch[1],
date: dateMatch[2] ? new Date(dateMatch[2]) : null,
title:
instance.frontmatter.title?.replace(
/^[0-9]{4}([/-])[0-9]{1,2}\1[0-9]{1,2}\s*/,
""
) ?? "",
href: instance.url,
},
];
})
);
const allEventsList: Map<string, EventInformation> = new Map(
events.map((event) => {
const dateMatch = event.filePath?.match(
/(([0-9]{4}-[0-9]{2}-[0-9]{2})?[^/]*)(?:\/index)?\.mdx?$/
);
if (!dateMatch) {
throw new Error(`Invalid file path: ${event.filePath}`);
}
return [
dateMatch[1],
{
key: dateMatch[1],
date: dateMatch[2] ? new Date(dateMatch[2]) : null,
title:
event.data.title?.replace(
/^[0-9]{4}([/-])[0-9]{1,2}\1[0-9]{1,2}\s*/,
""
) ?? "",
href: `/${event.id}`,
},
];
})
)
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at the end of the statement. This is inconsistent with the coding style used in the rest of the file.

Copilot uses AI. Check for mistakes.

const eventsList: EventInformation[] = show.map((key) => {
const eventInfo = allEventsList.get(key);
Expand Down
21 changes: 13 additions & 8 deletions src/components/pages/GoodPractice.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { useReducer } from "react";
import type { Format, Number, Tool, Keyword } from "src/content.config";

interface Filter {
label: string;
value: Lowercase<string>;
}

const FORMATS = [
export const FORMATS = [
{ label: "リアルタイムオンライン", value: "realtime_online" },
{ label: "オンデマンド", value: "ondemand" },
] satisfies Filter[];
type Format = (typeof FORMATS)[number]["value"];

const NUMBERS = [
export const NUMBERS = [
{ label: "101名以上", value: "mt100" },
{ label: "30名以上100名未満", value: "mt30-lt100" },
{ label: "30名未満", value: "lt30" },
] satisfies Filter[];
type Number = (typeof NUMBERS)[number]["value"];

const TOOLS = [
export const TOOLS = [
{ label: "ITC-LMS", value: "itc-lms" },
{ label: "Google Classroom", value: "google-classroom" },
{ label: "Slack", value: "slack" },
Expand All @@ -45,16 +44,22 @@ const TOOLS = [
{ label: "GoodNotes", value: "goodnotes" },
"br",
{ label: "Comment Screen", value: "comment-screen" },
{ label: "TeX", value: "tex" },
{ label: "Mathematica", value: "mathematica" },
{ label: "UTAS", value: "utas" },
{ label: "Google Drive", value: "google-drive" },
{ label: "Scrapbox", value: "scrapbox" },
{ label: "Mixlr", value: "mixlr" },
{ label: "TwitCasting", value: "twitcasting" },
] satisfies (Filter | "br")[];
type Tool = Extract<(typeof TOOLS)[number], Filter>["value"];

const KEYWORDS = [
export const KEYWORDS = [
{ label: "板書", value: "hand-writing" },
{ label: "実験・実習", value: "experiment" },
{ label: "グループワーク", value: "group-work" },
{ label: "TA", value: "ta" },
{ label: "テキスト", value: "text"}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space before closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Suggested change
{ label: "テキスト", value: "text"}
{ label: "テキスト", value: "text" }

Copilot uses AI. Check for mistakes.
] satisfies Filter[];
type Keyword = (typeof KEYWORDS)[number]["value"];

export interface Interview {
path: string;
Expand Down
5 changes: 4 additions & 1 deletion src/components/pages/Notice.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
import { getISODateString, getLocaleDateString, noticesWithId } from "@data/utils/notices";
import { getEntry } from "astro:content";
import Markdown from "@components/utils/Markdown.astro";
import type { Lang } from "@components/types";
import { getISODateString, getLocaleDateString } from "src/lib/util";

/**
* `Markdown`の利用例として紹介されています.
Expand All @@ -27,6 +28,8 @@ interface NoticeContent {

const { lang, strip, localize } = Astro.props;

const { data: noticesWithId } = await getEntry("notices", "notice")!;

const contents: NoticeContent[] = [];

for (const notice of noticesWithId) {
Expand Down
27 changes: 12 additions & 15 deletions src/components/pages/Sitemap.astro
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
---
import en from "@data/nav/en.yml";
import ja from "@data/nav/ja.yml";
import { basename } from "path";
import type { MDXInstance, MarkdownInstance } from "astro";
import { getCollection, getEntry } from "astro:content";
import Slugger from "github-slugger";
import Layout, { type Frontmatter } from "@layouts/Layout.astro";
import Layout from "@layouts/Layout.astro";
import type { Lang } from "@components/types";
import type { Navigation } from "@data/schemas/nav";
import Markdown from "@components/utils/Markdown.astro";
import { unified } from "unified";
import remarkParse from "remark-parse";
Expand All @@ -18,7 +15,7 @@ interface Props {
}

const { lang } = Astro.props;
const navigations: Navigation[] = { ja, en }[lang];
const { data: navigations } = await getEntry("nav", lang)!
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at the end of the statement. This is inconsistent with the coding style used in the rest of the file where other statements have semicolons.

Suggested change
const { data: navigations } = await getEntry("nav", lang)!
const { data: navigations } = await getEntry("nav", lang)!;

Copilot uses AI. Check for mistakes.

interface SectionNode {
name: string;
Expand Down Expand Up @@ -126,26 +123,26 @@ function resolveSections(
const sections = resolveSections(root);
sections.shift(); // remove root

type Page = MarkdownInstance<Frontmatter> | MDXInstance<Frontmatter>;
const pages = await Astro.glob<Page>("/src/pages/**/*.{md,mdx}");
const pages = await getCollection("pages");

const indexSectionByPrefixes = sections.flatMap((section) =>
section.prefixes.map((prefix) => ({ prefix, section })),
);
// longest prefix first
indexSectionByPrefixes.sort((a, b) => b.prefix.length - a.prefix.length);
for (const page of pages) {
if (!page.url) continue;
if (basename(page.url).startsWith("_")) continue;
const { layout, sitemap, redirect_to } = page.frontmatter;
if (layout === false || sitemap === false || redirect_to) continue;
if (page.url.startsWith("/en/") !== (lang === "en")) continue;
const url = `/${page.id}`;
if (!url) continue;
if (basename(url).startsWith("_")) continue;
const { layout, sitemap, redirect_to, title } = page.data;
if (layout === false || sitemap === false || redirect_to || !title) continue;
if (url.startsWith("/en/") !== (lang === "en")) continue;

const { section } = indexSectionByPrefixes.find(
(item) =>
page.url!.startsWith(item.prefix) || page.url + "/" === item.prefix,
url!.startsWith(item.prefix) || url + "/" === item.prefix,
)!; // the "Others" section should catch
section.entries.push({ title: page.frontmatter.title, url: page.url });
section.entries.push({ title, url });
}
for (const section of sections) {
// https://stackoverflow.com/questions/979256/sorting-an-array-of-objects-by-property-values#comment48111034_979289
Expand Down
6 changes: 4 additions & 2 deletions src/components/pages/rss.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { getEntry } from "astro:content";
import type { RSSFeedItem } from "@astrojs/rss";
import getRssResponse from "@astrojs/rss";
import remarkParse from "remark-parse";
import { unified } from "unified";

import { noticesWithIdReversed } from "@data/utils/notices";
import { toHast } from "mdast-util-to-hast";
import { toText } from "hast-util-to-text";
import { select } from "hast-util-select";
import type { Lang } from "@components/types";

const { data: noticesWithId } = await getEntry("notices", "notice")!
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at the end of the statement. This is inconsistent with the coding style used in the rest of the file where other statements have semicolons.

Suggested change
const { data: noticesWithId } = await getEntry("notices", "notice")!
const { data: noticesWithId } = await getEntry("notices", "notice")!;

Copilot uses AI. Check for mistakes.

const parser = unified().use(remarkParse);

interface RssParams {
Expand All @@ -21,7 +23,7 @@ interface RssParams {
export async function rss({ title, description, url, lang }: RssParams) {
const itemsMap = new Map<string, RSSFeedItem>();

for (const notice of noticesWithIdReversed) {
for (const notice of noticesWithId.toReversed()) {
const content = notice.content[lang] ?? notice.content.ja;
if (!content) continue;

Expand Down
144 changes: 144 additions & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { styleText } from "node:util";
import { defineCollection } from "astro:content";
import { z } from "astro/zod"
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at the end of the import statement. This is inconsistent with the other import statements in the file which all end with semicolons.

Suggested change
import { z } from "astro/zod"
import { z } from "astro/zod";

Copilot uses AI. Check for mistakes.
import { glob } from "astro/loaders";
import { FORMATS, NUMBERS, TOOLS, KEYWORDS } from "@components/pages/GoodPractice";
import { getISODateString } from "src/lib/util";

const emergencies = defineCollection({
loader: glob({pattern: "*.{md,mdx}", base: "./src/emergencies" }),
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after the opening brace in the object literal. This is inconsistent with formatting used elsewhere in the file and across the codebase.

Copilot uses AI. Check for mistakes.
schema: z.object({
pattern: z.string().refine((v) => {
try {
new RegExp(v);
return true;
}
catch (e) {
return false;
}
})
.transform(
(v) => new RegExp(v)
)
})
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Copilot uses AI. Check for mistakes.
})
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Suggested change
})
} )

Copilot uses AI. Check for mistakes.

const events = defineCollection({
loader: glob({pattern: ["events/*/index.{md,mdx}", "events/*.{md,mdx}"], base: "./src/pages" }),
schema: z.object({
title: z.string()
})
})
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Copilot uses AI. Check for mistakes.

const notices = defineCollection({
// 本来は file() を使うべきだが、file() は各エントリに id / slug を含めることを要求するため面倒
loader: glob({pattern: "notice.yml", base: "./src/data"}),
schema: z.array(
z.object({
date: z.date(),
content: z.object({
// TODO: すべてのお知らせが翻訳されたら .optional() を外す
ja: z.string().optional(),
en: z.string().optional()
}),
important: z.boolean().optional()
})
// 少なくとも日英いずれかのタイトルが存在することを保証
// (現時点では一方のみが欠けている場合には警告を出し、エラーにはしない)
.refine((notice) => {
if (!notice.content.ja || !notice.content.en) {
console.error(
`${
styleText(['yellow'], '警告: ')
}お知らせには日本語・英語双方のタイトルが必要です: ${
(notice.content.ja || notice.content.en || "").slice(0, 40)
}...`);
}
return notice.content.ja || notice.content.en;
})
)
.transform((notices) => {
const dateAutoincrementMap = new Map<string, number>();
return notices.toReversed().map((notice) => {
const date = getISODateString(notice.date);
const i = dateAutoincrementMap.get(date) ?? 0;
dateAutoincrementMap.set(date, i + 1);
const id = `${date}-${i}`;
return { ...notice, id };
}).toReversed();
})
})
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Copilot uses AI. Check for mistakes.

const nav = defineCollection({
loader: glob({ pattern: "*.yml", base: "./src/data/nav" }),
schema: z.array(
z.object({
name: z.string(),
sitemap: z.object({
section: z.boolean().optional()
}).optional(),
contents: z.array(
z.object({
name: z.string(),
url: z.string(),
sitemap: z.object({
section: z.boolean().optional(),
parent: z.string().optional()
}).optional(),
hidden: z.boolean().optional()
})
)
})
)
})
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Copilot uses AI. Check for mistakes.

const pages = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/pages" }),
schema: z.object({
layout: z.boolean().optional(),
sitemap: z.boolean().optional(),
redirect_to: z.string().optional(),
title: z.string().optional()
})
})
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Copilot uses AI. Check for mistakes.

function createEnumFromFilters<T extends string>(
filters: readonly { value: T; label: string }[]
) {
const values = filters.map((f) => f.value);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createEnumFromFilters function doesn't handle the case where the input array is empty. If an empty array is passed, accessing values[0] will be undefined, causing a runtime error. Consider adding validation to ensure the array has at least one element.

Suggested change
const values = filters.map((f) => f.value);
const values = filters.map((f) => f.value);
if (values.length === 0) {
throw new Error("createEnumFromFilters requires at least one filter value.");
}

Copilot uses AI. Check for mistakes.
// z.enum() は [string, ...string[]] 型を要求する
return z.enum([values[0], ...values.slice(1)]);
}

const formatSchema = createEnumFromFilters(FORMATS);
type Format = z.infer<typeof formatSchema>;

const numberSchema = createEnumFromFilters(NUMBERS);
type Number = z.infer<typeof numberSchema>;

const toolSchema = createEnumFromFilters(TOOLS.filter((f) => f !== "br"));
type Tool = z.infer<typeof toolSchema>;

const keywordSchema = createEnumFromFilters(KEYWORDS);
type Keyword = z.infer<typeof keywordSchema>;

const interviews = defineCollection({
loader: glob({ pattern: "good-practice/interview/*.md", base: "./src/pages" }),
schema: z.object({
title: z.string(),
filters: z.object({
format: z.array(formatSchema),
number: numberSchema,
tools: z.array(toolSchema),
keywords: z.array(keywordSchema)
}),
componentProps: z.object({
title: z.string(),
point: z.string(),
tools: z.string()
})
})
})
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after closing brace in the object literal. This is inconsistent with the formatting used elsewhere in the file.

Suggested change
})
} )

Copilot uses AI. Check for mistakes.

export const collections = { emergencies, events, notices, nav, pages, interviews };
export type { Format, Number, Tool, Keyword };
16 changes: 0 additions & 16 deletions src/data/schemas/nav.d.ts

This file was deleted.

Loading