From 4431b46cedaa8d9f3d004cb5c5cb027ca03a6da9 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 15:54:12 +0900 Subject: [PATCH 01/15] =?UTF-8?q?feat:=20=E3=83=96=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _routes.json | 0 app/components/Tag/Tag.tsx | 9 +- app/routes.ts | 7 +- app/routes/blog/$id.module.scss | 112 ++++++++ app/routes/blog/$id.tsx | 104 +++++++ app/routes/blog/custom-highlight.scss | 46 ++++ app/routes/blog/index.module.css | 7 - app/routes/blog/index.module.scss | 104 +++++++ app/routes/blog/index.tsx | 62 ++++- app/utils/convert.ts | 37 +++ functions/blog/[id].ts | 25 ++ package-lock.json | 366 ++++++++++++++++++++++++- package.json | 5 +- public/assets/images/headers/blog.webp | Bin 0 -> 31734 bytes 14 files changed, 861 insertions(+), 23 deletions(-) create mode 100644 _routes.json create mode 100644 app/routes/blog/$id.module.scss create mode 100644 app/routes/blog/$id.tsx create mode 100644 app/routes/blog/custom-highlight.scss delete mode 100644 app/routes/blog/index.module.css create mode 100644 app/routes/blog/index.module.scss create mode 100644 app/utils/convert.ts create mode 100644 functions/blog/[id].ts create mode 100644 public/assets/images/headers/blog.webp diff --git a/_routes.json b/_routes.json new file mode 100644 index 0000000..e69de29 diff --git a/app/components/Tag/Tag.tsx b/app/components/Tag/Tag.tsx index 2916ae7..6f7a07e 100644 --- a/app/components/Tag/Tag.tsx +++ b/app/components/Tag/Tag.tsx @@ -2,18 +2,19 @@ import './tag.css'; import { tagColorMap } from './tag-color-map'; import {useTranslation} from "react-i18next"; -export type TagKind = "hackathon" | "recruitment" | "urgent"; +export type TagKind = "hackathon" | "recruitment" | "urgent" | string; export interface TagProps { kind: TagKind; + className?: string; } -export const Tag = ({ kind }: TagProps) => { +export const Tag = ({ kind, className }: TagProps) => { const {text,backgroundColor} = tagColorMap[kind] || '#9e9e9e'; const { t } = useTranslation(); return ( - - {t(text)} + + { text ? t(text) : kind } ); }; diff --git a/app/routes.ts b/app/routes.ts index dd551c7..4a1456a 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -4,5 +4,10 @@ export default [ index("routes/home.tsx"), { path: "blog", - file: "routes/blog/index.tsx" } + file: "routes/blog/index.tsx", + }, + { + path: "blog/:id", + file: "routes/blog/$id.tsx" + } ] satisfies RouteConfig; diff --git a/app/routes/blog/$id.module.scss b/app/routes/blog/$id.module.scss new file mode 100644 index 0000000..29bf2f8 --- /dev/null +++ b/app/routes/blog/$id.module.scss @@ -0,0 +1,112 @@ +.container { + width: 100%; + height: 100%; + min-height: 100vh; + display: flex; + flex-direction: row-reverse; + justify-content: center; + background-color: #F5F5F5; + + .article { + padding-top: 64px; + width: 100%; + max-width: 1200px; + } + + .side { + padding-top: 200px; + width: 20%; + } + + ul { + top: 200px; + list-style: none; + position: sticky; + } + + li { + font-size: 16px; + } + + .h1-table { + color: #616161; + } + .h2-table { + color: #8a8a8a; + li { + padding-left: 24px; + } + } + + .article-container { + padding-top: 100px; + padding-bottom: 200px; + width: 60%; + height: auto; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } + + .header-img { + object-fit: cover; + width: 100%; + max-width: 1200px; + border-radius: 12px; + box-shadow: 0 10px 10px #20202011; + } + + h1 { + color: var(--main-text-color); + font-weight: 900; + font-size: 52px; + } + + h2 { + color: var(--main-text-color); + font-weight: 700; + font-size: 32px; + } + + h3 { + color: var(--main-text-color); + font-weight: 600; + font-size: 24px; + } + + h1, h2, h3, h4, h5, h6 { + padding-left: 8px; + padding-bottom: 4px; + margin-bottom: 24px; + margin-top: 24px; + position: relative; + color: var(--main-text-color); + text-box-edge: cap alphabetic; + } + + h1::before, h2::before { + content: ""; + position: absolute; + display: block; + left: -24px; + width: 12px; + height: 100%; + background-color: var(--primery-color); + } + + p { + color: var(--main-text-color); + letter-spacing: 2px; + font-size: 20px; + margin: 20px 0; + } +} + +@media screen and (max-width: 700px) { + .container { + .article-container { + width: 90% !important; + } + } +} \ No newline at end of file diff --git a/app/routes/blog/$id.tsx b/app/routes/blog/$id.tsx new file mode 100644 index 0000000..a107714 --- /dev/null +++ b/app/routes/blog/$id.tsx @@ -0,0 +1,104 @@ +import hljs from "highlight.js"; +import 'highlight.js/styles/github-dark.css'; +import './custom-highlight.scss' +import { useEffect, useState, type JSX } from "react"; +import { Header } from "~/components/Header/Header"; +import styles from "./$id.module.scss" +import { useParams } from "react-router"; +import { useIsMobile } from "~/hooks/useIsMobile"; +import { custom_markdown_convert } from "~/utils/convert"; + +export default function Blog(): JSX.Element { + const { id } = useParams(); + const [thumbnail] = useState(null); + const [markdownSource, setMarkdownSource] = useState(""); + const [table, setTable] = useState([]); + const isMobile = useIsMobile(); + + const handleAnchorClick = (id: string) => (e: React.MouseEvent) => { + e.preventDefault(); + const target = document.getElementById(id); + if (target) { + window.scrollTo({ + top: target.getBoundingClientRect().top + window.scrollY - 80, + behavior: 'smooth', + }); + } + }; + + useEffect(() => { + const script = document.createElement("script"); + script.src = "https://cdn.jsdelivr.net/gh/rsms/markdown-wasm@v1.1.2/dist/markdown.js"; + script.async = true; + + document.body.appendChild(script); + + script.onload = async () => { + await markdown.ready; + await fetch(`https://raw.githubusercontent.com/object-t/object-t-blog/refs/heads/main/articles/${id}`) + .then((res) => res.text()) + .then((md) => { + const html = markdown.parse(custom_markdown_convert(md)); + const container = document.createElement("div"); + container.innerHTML = html; + + const headings = container.querySelectorAll("h1, h2"); + headings.forEach((heading, i) => { + const text = heading.textContent ?? `heading-${i}`; + const id = text.replace(/\s+/g, "-").toLowerCase(); + heading.id = id; + }); + setTable( + Array.from(headings).map((d, i) => { + const id = (d.textContent ?? `heading-${i}`).replace(/\s+/g, "-").toLowerCase(); + return ( + +
  • {d.textContent}
  • +
    + ) + } + ) + ); + + container.querySelectorAll("pre code").forEach((code) => { + hljs.highlightElement(code as HTMLElement); + }); + + setMarkdownSource(container.innerHTML); + }) + .catch((err) => console.error("読み込みエラー:", err)); + }; + }, [id]); + + return ( +
    +
    + { + !isMobile && +
    +
      + { + table + } +
    +
    + } +
    + +
    +
    +
    + ) +} \ No newline at end of file diff --git a/app/routes/blog/custom-highlight.scss b/app/routes/blog/custom-highlight.scss new file mode 100644 index 0000000..28f7460 --- /dev/null +++ b/app/routes/blog/custom-highlight.scss @@ -0,0 +1,46 @@ +pre { + display: block; + border-radius: 8px; + overflow-x: hidden; + font-size: 20px; + width: 100%; + + background-color: #0f1a2b; + box-shadow: 0 12px 30px #3a3a3a3f; +} + +code.hljs { + width: calc(100% - 24px); + font-size: 16px; + font-family: Menlo, monospace; + line-height: 1.8em; + padding: 0 24px; + + background-color: transparent; + + span.hljs-addition { + line-height: 0 !important; + padding: 1em 0; + display: inline-block; + // width: 100%; + } +} + +.filename-label { + background-color: #2a554c; + width: max-content; + padding: 4px 12px 4px 12px; + color: var(--accent-color); + display: flex; + justify-content: center; + align-items: center; + font-size: 12px; + letter-spacing: 2px; + + border-top-left-radius: 4px; + border-top-right-radius: 4px; + + & + pre { + border-top-left-radius: 0; + } +} \ No newline at end of file diff --git a/app/routes/blog/index.module.css b/app/routes/blog/index.module.css deleted file mode 100644 index d059682..0000000 --- a/app/routes/blog/index.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.container { - width: 100vw; - height: 100vh; - display: flex; - align-items: center; - justify-content: center; -} \ No newline at end of file diff --git a/app/routes/blog/index.module.scss b/app/routes/blog/index.module.scss new file mode 100644 index 0000000..d753a8e --- /dev/null +++ b/app/routes/blog/index.module.scss @@ -0,0 +1,104 @@ +.container { + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + h1 { + font-size: 42px; + margin-bottom: 24px; + } + + .articleList { + width: 80%; + max-width: 1000px; + + .articleItem { + width: 100%; + height: 200px; + display: flex; + border-radius: 12px; + + background-color: #b4b4b400; + box-shadow: 0 12px 24px #81818193; + + img { + height: 200px; + width: 200px; + object-fit: cover; + border-radius: 12px; + } + + .textBox { + position: relative; + width: calc(100% - 24px); + height: 100%; + display: flex; + flex-direction: column; + padding-left: 24px; + + & * { + color: var(--main-text-color); + } + + h2 { + margin-top: 24px; + font-size: 32px; + font-weight: 900; + } + + .tagContainer { + margin-top: 8px; + display: flex; + justify-content: end; + .tag { + color: var(--accent-color) !important; + background-color: #2199c9 !important; + } + } + .details { + display: flex; + flex-direction: column; + position: absolute; + bottom: 0; + right: 24px; + + & * { + font-size: 20px; + color: #888888; + margin-right: 12px; + } + + .info { + display: flex; + } + } + } + } + } +} + +.mobile { + .articleItem { + flex-direction: column; + height: 400px !important; + + img { + width: 100% !important; + } + + .details { + display: flex; + flex-direction: column; + position: absolute; + bottom: 0; + right: 24px; + + & * { + font-size: 12px !important; + } + } + } +} \ No newline at end of file diff --git a/app/routes/blog/index.tsx b/app/routes/blog/index.tsx index 40ba22d..07edc64 100644 --- a/app/routes/blog/index.tsx +++ b/app/routes/blog/index.tsx @@ -1,14 +1,64 @@ -import type { JSX } from "react"; +import { useEffect, useState, type JSX } from "react"; import { Header } from "~/components/Header/Header"; -import styles from "./index.module.css"; +import styles from "./index.module.scss"; +import { Tag } from "~/components/Tag/Tag"; +import { useIsMobile } from "~/hooks/useIsMobile"; + +type Article = { + id: string, + title: string, + thumbnail: string, + type: string, + topics: string[], + author: string, + created_at: string, + published: boolean +}; export default function Blog(): JSX.Element { + const isMobile = useIsMobile(); + const [articles, setArticles] = useState([]); + + useEffect(() => { + fetch("https://raw.githubusercontent.com/object-t/object-t-blog/refs/heads/main/articles.json") + .then((res) => res.json()) + .then((data: Article[]) => { + const publishedArticles = data.filter(article => article.published); + setArticles(publishedArticles); + }) + .catch(console.error); + }, []); + return ( -
    +
    - - ) + ); } \ No newline at end of file diff --git a/app/utils/convert.ts b/app/utils/convert.ts new file mode 100644 index 0000000..96897b8 --- /dev/null +++ b/app/utils/convert.ts @@ -0,0 +1,37 @@ +export const custom_markdown_convert = (markdown: string): string => { + let converted = markdown; + + converted = remove_frontmatter(converted); + converted = convert_custom_massage_block(converted); + converted = convert_labeled_code_block(converted); + converted = convert_link(converted); + + return converted; +} + +const remove_frontmatter = (markdown: string): string => { + return markdown.replace(/^---[\s\S]*?---\n/, ""); +} + +const convert_custom_massage_block = (markdown: string): string => { + return markdown.replace(/:::message\s*([\s\S]*?)\s*:::/g, (_match, content) => { + return `
    ${content.trim()}
    `; + }); +} + +const convert_labeled_code_block = (markdown: string): string => { + return markdown.replace(/```((?:\w+\s+)?)(\w+):([^\n]+)\n([\s\S]*?)```/g, + (_match, maybeLang1, lang2, filename, code) => { + const fileTitle = filename.trim(); + const langHeader = `${(maybeLang1 + lang2).trim()}`; + return `
    ${fileTitle}
    \n\n\`\`\`${langHeader}\n${code}\`\`\``; + } + ); +} + +const convert_link = (markdown: string): string => { + return markdown.replace( + /(?])\b(https?:\/\/[^\s<>"')\]]+)/g, + (url) => `${url}` + ); +} \ No newline at end of file diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts new file mode 100644 index 0000000..4a784aa --- /dev/null +++ b/functions/blog/[id].ts @@ -0,0 +1,25 @@ +import { Response, type PagesFunction } from '@cloudflare/workers-types' + +export const onRequestGet: PagesFunction = async (context) => { + const { id } = context.params + + const baseHtmlRes = await fetch('https://object-t.com/index.html') + let html = await baseHtmlRes.text() + + const ogTitle = `ブログ記事 ${id}` + const ogDescription = `これはブログ ${id} の説明です。` + const ogImage = `https://object-t.com/assets/logo.webp` + + html = html + .replace(/.*<\/title>/, `<title>${ogTitle}`) + .replace(/]*>/, ``) + .replace(/]*>/, ``) + .replace(/]*>/, ``) + .replace(/]*>/, ``) + + return new Response(html, { + headers: { + 'Content-Type': 'text/html', + }, + }) +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 074172d..12f3976 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@react-router/node": "^7.3.0", "@react-router/serve": "^7.3.0", "devicon": "^2.16.0", + "highlight.js": "^11.11.1", "i18next": "^24.2.3", "isbot": "^5.1.17", "plop": "^4.0.1", @@ -20,10 +21,12 @@ "react-dom": "^19.0.0", "react-i18next": "^15.4.1", "react-router": "^7.3.0", - "react-router-dom": "^7.4.0" + "react-router-dom": "^7.4.0", + "sass": "^1.86.3" }, "devDependencies": { "@chromatic-com/storybook": "^3.2.6", + "@cloudflare/workers-types": "^4.20250416.0", "@eslint/js": "^9.23.0", "@react-router/dev": "^7.3.0", "@storybook/addon-essentials": "^8.6.4", @@ -634,6 +637,13 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20250416.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250416.0.tgz", + "integrity": "sha512-i37TX0Clp+MrPdXMBdvKZM7JghCrWD9GtG7E+8ANOAPmtZjUkZfEy9qq46IG3XlNpagPaWDkY3SgJ3s01gPxCw==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -1797,6 +1807,315 @@ "dev": true, "license": "MIT" }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5306,7 +5625,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -7688,6 +8006,15 @@ "tslib": "^2.0.3" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -7857,6 +8184,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", + "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -9795,6 +10128,13 @@ "tslib": "^2.0.3" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, "node_modules/node-plop": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/node-plop/-/node-plop-0.32.0.tgz", @@ -11261,7 +11601,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -11679,6 +12018,26 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sass": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz", + "integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -12004,7 +12363,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 2bb5c11..f69cbbe 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@react-router/node": "^7.3.0", "@react-router/serve": "^7.3.0", "devicon": "^2.16.0", + "highlight.js": "^11.11.1", "i18next": "^24.2.3", "isbot": "^5.1.17", "plop": "^4.0.1", @@ -26,10 +27,12 @@ "react-dom": "^19.0.0", "react-i18next": "^15.4.1", "react-router": "^7.3.0", - "react-router-dom": "^7.4.0" + "react-router-dom": "^7.4.0", + "sass": "^1.86.3" }, "devDependencies": { "@chromatic-com/storybook": "^3.2.6", + "@cloudflare/workers-types": "^4.20250416.0", "@eslint/js": "^9.23.0", "@react-router/dev": "^7.3.0", "@storybook/addon-essentials": "^8.6.4", diff --git a/public/assets/images/headers/blog.webp b/public/assets/images/headers/blog.webp new file mode 100644 index 0000000000000000000000000000000000000000..d0f481876e172ef8e9a1dd7b255920ffa27aaa31 GIT binary patch literal 31734 zcmeFZWq2J)k~Vt8%xp0;Gc#H&S%OBPs67Bfrd%G1+5-FLc~mXJ8{0sz#-MHJN(xinw_007bJ?uQBJ1_MZo ziYg9)yjB4qLqCt;OaOqbowJj&qzI9QrWO&@IsoML_qWZ^_`Sm~rT_5w+UjQhm$tL? z|6z;&`Xr2r>3ieXCTFiZnbYgTU;BjqT1GejrA+m+Z1hW+?`PS~*}?g>jpEO;ld7`l zYuV(rOlAJNveEC##tu$D+eg2);j^`I`T4A$+t1O#o7%lodHoChy5j-f1C#-h0Fj^L zf33f6_W1w+*EIkD7XOcPM(F@Ra|i%{z4VWBWVrwUQZNAUY4RWE{vi_wLnp((-46Wq z2x?{q09=#+00^1@0NNA)0IU7CYp;j@q;Ev8m+)TuW&ip!2iO2i0Ym_4fE~aX!0=jP z0WblW0UWP0Oa$-*0GKc!vq5P5F?+xSn6bmfNQlFO$&f(;wY0ktROTGog|1mR)iiG4 z^+kC0RUz)cUuH7-6rQag7w#CZ^cTD2y!|c(57)j4b^=>pPJ#U!Yl8wgk84|lr|J(3 zZh9YH2r~%3__q17KAN9&-zlsxcIY?yRljUK*Ss+9sMmKV0I^;UpWJG(HuZ-E`~1wF z%YjIb9|q3b{RW?tZ$NLY_i}cCZNNm};tS(O|0ZzVFZ8+fmiNf~@$?i35Uk8O@auaK ze1==?zni?vxdx7U1AuHVH80CgNcV!@1dA26{Z4=}K+VU@V(7Tl;tQ-_rQE zGW>fj{M!`#+YFBp^kHz-9Q&&d3bbzG}(f24P$god)cbk#-qxk+`R|UK$ z+k449+;KmRuiSN#`FqEV;nh6+o@wn9p)KS`++CO7Tr%R8mQR?I<^St@>`{(u;8YX>Ie9{rdPa0ZBL)- zMJVeLkKCJ#P0wqHiYyVosu#tJ3NHP<@&6Zf^zZU&KKs2*IXFJ_DZ57j5h7nT-EzUu z9NSJ-`QH$rIVAkPFM}NGNN{DLFrrNu>W*!ZzcWAooLx+H4rB?B%d)Bg5i1TO_pjW2 z3*L+Mf5W`S(D3Du{1)CSju!5?;1dF7y?b>)|8Cm<1V!AvN@EQB5sLKPKLgWc+EmdK z57DJf5#Ih+7yudz&o+4`8NZ&)xw<()c(~Tg%9r#TJZ&?04~O)<;jv8C0BYuYYBY8Z zD5Cok(A5Md@Z~~ov}qa4j3W|h3NZq3Q0~tX?ckpt|FRQlk@~&xjm<&&IYd#M?1Ls3 zG$8+svpmER)c0GBoBd}5H{lH4iYvVPXm~a0ibsIz9TnJ1Mh&M)g+umdM}aGALiQrp zCT3mxIcGSyP3RXSWO3P@LcsmKD_>0Y%z-7Iv<4L5FUUQP-Nmk_v!7W`gl~!jhvTv) zpFCWL-4TeQE~6I52%Q5jR@Zuakk+=3f+w%$3TP2{X96)2^LvEwPgsi4fdiU`^kJ&v zzadKVq*?G%Ew4D+^kMQY*56M59d{N%%XgT0kBRHe92g$%KqRT+!)*$;)&JBHiE9;@ z%oMe<*k5r#$+i~9wbry~XZ_00#iu-Sc#HcQ7R%uLg1vO_p><&=)aE1_@9-!lf|WyF zC8M)14hoItM)7EaETm2sg7_pj&ZdZ)1sClv@#X#8Qo-;A*@CgB6H1nW)T(J|=U+rT zDK7rM_VIOQ+i=U{ot5|xEj*RZv;IWp@D*6%d)R@|A`3c$ekA?mo|PW&0MgdOE@};7 zAEF2d7B>GDM&@MvYq0+vB^oK5OkC|f*VzEFdSj(seg$_tWU-c&xq496D?~>4geljk zX|I}q%A`L_n0No!yYHc97~ZX8f6+rR7L;!IR@F)0LO;*rcx=5u^8%m~C+D6^wT;a> zM7odjS)%?DpsYj=SL+cA7VH+*h2ljXCX6xKMh;~ZteQ3u;!MLDkhHiG>}5*^oSEYN zi?I9d4<2Za+gbtR{zL~p9oy5q!sgH7$xW>db~J=N+!Q|wc-am1X%e;pM;G?}(7X)GE=_8W+OF

    (*g-4F&$uTa21RAI-QoeKm9`o2 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 XbHD>um^IV5%Pr|V Date: Wed, 16 Apr 2025 15:58:23 +0900 Subject: [PATCH 02/15] =?UTF-8?q?fix:=20Cloudflare=E4=B8=8A=E3=81=A7?= =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .githooks/pre-commit.go | 2 +- functions/blog/[id].ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.githooks/pre-commit.go b/.githooks/pre-commit.go index 8d141e9..f411583 100644 --- a/.githooks/pre-commit.go +++ b/.githooks/pre-commit.go @@ -25,7 +25,7 @@ func main() { os.Exit(0) } - fileList := strings.Join(files, " ") + fileList := strings.ReplaceAll(strings.Join(files, " "), `$`, `\$`) eslintCmdStr := fmt.Sprintf("npx eslint --fix %s", fileList) var eslintCmd *exec.Cmd if runtime.GOOS == "windows" { diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index 4a784aa..cebda3e 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -1,4 +1,4 @@ -import { Response, type PagesFunction } from '@cloudflare/workers-types' +import { type PagesFunction } from '@cloudflare/workers-types' export const onRequestGet: PagesFunction = async (context) => { const { id } = context.params From 866f99297c8a8fa8dd1c284f28a5acfb4792e054 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 16:12:19 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=20=5Froutes=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _routes.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/_routes.json b/_routes.json index e69de29..f5eea66 100644 --- a/_routes.json +++ b/_routes.json @@ -0,0 +1,4 @@ +{ + "include": ["/blog/*"], + "exclude": ["/**"] +} \ No newline at end of file From 2e06789440ab7ba3d6b6cb73611bd035202cb59b Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 16:47:34 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=5Froutes=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _routes.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/_routes.json b/_routes.json index f5eea66..dc96035 100644 --- a/_routes.json +++ b/_routes.json @@ -1,4 +1,7 @@ { "include": ["/blog/*"], - "exclude": ["/**"] + "exclude": [ + "/blog/*.*", + "/blog/assets/*" + ] } \ No newline at end of file From 0d462661cd2aec3ca143aeffe544142cb414f28f Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 16:54:30 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20=5Froutes=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routes/blog/index.tsx | 2 +- functions/blog/[id].ts | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/routes/blog/index.tsx b/app/routes/blog/index.tsx index 07edc64..d760dcc 100644 --- a/app/routes/blog/index.tsx +++ b/app/routes/blog/index.tsx @@ -36,7 +36,7 @@ export default function Blog(): JSX.Element {

    ブログ記事一覧

    {articles.map((article) => ( - + {article.title}

    {article.title}

    diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index cebda3e..bf8a300 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -1,25 +1,30 @@ -import { type PagesFunction } from '@cloudflare/workers-types' +import type { PagesFunction } from '@cloudflare/workers-types' export const onRequestGet: PagesFunction = async (context) => { - const { id } = context.params + const accept = context.request.headers.get('accept') || ''; - const baseHtmlRes = await fetch('https://object-t.com/index.html') - let html = await baseHtmlRes.text() + if (!accept.includes('text/html')) { + return fetch(context.request); + } - const ogTitle = `ブログ記事 ${id}` - const ogDescription = `これはブログ ${id} の説明です。` - const ogImage = `https://object-t.com/assets/logo.webp` + const { id } = context.params; + const baseHtmlRes = await fetch('https://object-t.com/index.html'); + let html = await baseHtmlRes.text(); + + const ogTitle = `ブログ記事 ${id}`; + const ogDescription = `これはブログ ${id} の説明です。`; + const ogImage = `https://object-t.com/assets/logo.webp`; html = html .replace(/.*<\/title>/, `<title>${ogTitle}`) .replace(/]*>/, ``) .replace(/]*>/, ``) .replace(/]*>/, ``) - .replace(/]*>/, ``) + .replace(/]*>/, ``); return new Response(html, { headers: { 'Content-Type': 'text/html', }, - }) -} \ No newline at end of file + }); +}; \ No newline at end of file From 983c6844dfe63c34eb82f732419ddc728f588a4c Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 17:01:57 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20=5Froutes=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _routes.json | 5 +---- app/routes/blog/$id.tsx | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/_routes.json b/_routes.json index dc96035..f5eea66 100644 --- a/_routes.json +++ b/_routes.json @@ -1,7 +1,4 @@ { "include": ["/blog/*"], - "exclude": [ - "/blog/*.*", - "/blog/assets/*" - ] + "exclude": ["/**"] } \ No newline at end of file diff --git a/app/routes/blog/$id.tsx b/app/routes/blog/$id.tsx index a107714..4595bf4 100644 --- a/app/routes/blog/$id.tsx +++ b/app/routes/blog/$id.tsx @@ -35,7 +35,7 @@ export default function Blog(): JSX.Element { script.onload = async () => { await markdown.ready; - await fetch(`https://raw.githubusercontent.com/object-t/object-t-blog/refs/heads/main/articles/${id}`) + await fetch(`https://raw.githubusercontent.com/object-t/object-t-blog/refs/heads/main/articles/${id}.md`) .then((res) => res.text()) .then((md) => { const html = markdown.parse(custom_markdown_convert(md)); From 7bc1869457d2c710c7c824a279e1deb4536feaca Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 17:10:06 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat:=20=5Froutes=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/blog/[id].ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index bf8a300..7f51727 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -1,9 +1,9 @@ import type { PagesFunction } from '@cloudflare/workers-types' export const onRequestGet: PagesFunction = async (context) => { - const accept = context.request.headers.get('accept') || ''; + const url = new URL(context.request.url); - if (!accept.includes('text/html')) { + if (/\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(url.pathname)) { return fetch(context.request); } From 84feda498a7473e873f58d71e65a4d0225a35e03 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 17:14:06 +0900 Subject: [PATCH 08/15] =?UTF-8?q?fix:=20router=E3=81=8C=E3=81=8A=E3=81=8B?= =?UTF-8?q?=E3=81=97=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/blog/[id].ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index 7f51727..4884e0a 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -2,8 +2,14 @@ import type { PagesFunction } from '@cloudflare/workers-types' export const onRequestGet: PagesFunction = async (context) => { const url = new URL(context.request.url); - - if (/\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(url.pathname)) { + const pathname = url.pathname; + + if (pathname.startsWith('/assets/') || /\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(pathname)) { + return fetch(context.request); + } + + const accept = context.request.headers.get('accept') || ''; + if (!accept.includes('text/html')) { return fetch(context.request); } From 74741928f18852b70494fa03cae400ca599647c1 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 17:28:40 +0900 Subject: [PATCH 09/15] =?UTF-8?q?fix:=20=5Froutes.json=E3=81=AE=E5=A0=B4?= =?UTF-8?q?=E6=89=80=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _routes.json => functions/_routes.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename _routes.json => functions/_routes.json (100%) diff --git a/_routes.json b/functions/_routes.json similarity index 100% rename from _routes.json rename to functions/_routes.json From a688b2774f06b0ed0aa6be36b147c80d57ff1cab Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 17:35:03 +0900 Subject: [PATCH 10/15] =?UTF-8?q?feat:=20AI=E6=A7=98=E3=81=AE=E3=81=84?= =?UTF-8?q?=E3=81=86=E9=80=9A=E3=82=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/_routes.json | 4 ++-- functions/blog/[id].ts | 43 +++++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/functions/_routes.json b/functions/_routes.json index f5eea66..cbe3cec 100644 --- a/functions/_routes.json +++ b/functions/_routes.json @@ -1,4 +1,4 @@ { "include": ["/blog/*"], - "exclude": ["/**"] -} \ No newline at end of file + "exclude": ["/assets/*", "/**/*.js", "/**/*.css"] +} diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index 4884e0a..835e66e 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -1,36 +1,45 @@ import type { PagesFunction } from '@cloudflare/workers-types' export const onRequestGet: PagesFunction = async (context) => { - const url = new URL(context.request.url); - const pathname = url.pathname; + const url = new URL(context.request.url) + const pathname = url.pathname + const accept = context.request.headers.get('accept') || '' + const userAgent = context.request.headers.get('user-agent') || '' - if (pathname.startsWith('/assets/') || /\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(pathname)) { - return fetch(context.request); + if ( + pathname.startsWith('/assets/') || + /\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(pathname) + ) { + return fetch(context.request) } - const accept = context.request.headers.get('accept') || ''; - if (!accept.includes('text/html')) { - return fetch(context.request); + if (accept.includes('text/html') && !/bot|crawler|facebook|twitter|slack|discord/i.test(userAgent)) { + return fetch(context.request) } - const { id } = context.params; - const baseHtmlRes = await fetch('https://object-t.com/index.html'); - let html = await baseHtmlRes.text(); + const { id } = context.params + const baseHtmlRes = await fetch('https://object-t.com/index.html', { + cf: { cacheTtl: 0 }, + headers: { 'Cache-Control': 'no-cache' } + }) - const ogTitle = `ブログ記事 ${id}`; - const ogDescription = `これはブログ ${id} の説明です。`; - const ogImage = `https://object-t.com/assets/logo.webp`; + let html = await baseHtmlRes.text() + + const ogTitle = `ブログ記事 ${id}` + const ogDescription = `これはブログ ${id} の説明です。` + const ogImage = `https://object-t.com/assets/logo.webp` html = html .replace(/.*<\/title>/, `<title>${ogTitle}`) .replace(/]*>/, ``) .replace(/]*>/, ``) .replace(/]*>/, ``) - .replace(/]*>/, ``); + .replace(/]*>/, ``) return new Response(html, { headers: { 'Content-Type': 'text/html', - }, - }); -}; \ No newline at end of file + 'Cache-Control': 'no-store' + } + }) +} From 4e37d02b3a6233a9f199364c874671e1058a9f0e Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 17:43:51 +0900 Subject: [PATCH 11/15] =?UTF-8?q?feat:=20AI=E3=81=B0=E3=81=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/blog/[id].ts | 43 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index 835e66e..7f51727 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -1,45 +1,30 @@ import type { PagesFunction } from '@cloudflare/workers-types' export const onRequestGet: PagesFunction = async (context) => { - const url = new URL(context.request.url) - const pathname = url.pathname - const accept = context.request.headers.get('accept') || '' - const userAgent = context.request.headers.get('user-agent') || '' - - if ( - pathname.startsWith('/assets/') || - /\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(pathname) - ) { - return fetch(context.request) - } - - if (accept.includes('text/html') && !/bot|crawler|facebook|twitter|slack|discord/i.test(userAgent)) { - return fetch(context.request) + const url = new URL(context.request.url); + + if (/\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(url.pathname)) { + return fetch(context.request); } - const { id } = context.params - const baseHtmlRes = await fetch('https://object-t.com/index.html', { - cf: { cacheTtl: 0 }, - headers: { 'Cache-Control': 'no-cache' } - }) - - let html = await baseHtmlRes.text() + const { id } = context.params; + const baseHtmlRes = await fetch('https://object-t.com/index.html'); + let html = await baseHtmlRes.text(); - const ogTitle = `ブログ記事 ${id}` - const ogDescription = `これはブログ ${id} の説明です。` - const ogImage = `https://object-t.com/assets/logo.webp` + const ogTitle = `ブログ記事 ${id}`; + const ogDescription = `これはブログ ${id} の説明です。`; + const ogImage = `https://object-t.com/assets/logo.webp`; html = html .replace(/.*<\/title>/, `<title>${ogTitle}`) .replace(/]*>/, ``) .replace(/]*>/, ``) .replace(/]*>/, ``) - .replace(/]*>/, ``) + .replace(/]*>/, ``); return new Response(html, { headers: { 'Content-Type': 'text/html', - 'Cache-Control': 'no-store' - } - }) -} + }, + }); +}; \ No newline at end of file From 6162d8ec798ff1c86cb2d192bf0bff60326beb73 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 18:18:18 +0900 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20=5Froutes.json=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/_routes.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/functions/_routes.json b/functions/_routes.json index cbe3cec..23f5b34 100644 --- a/functions/_routes.json +++ b/functions/_routes.json @@ -1,4 +1,15 @@ { "include": ["/blog/*"], - "exclude": ["/assets/*", "/**/*.js", "/**/*.css"] -} + "exclude": [ + "/assets/*", + "/**/*.js", + "/**/*.css", + "/**/*.map", + "/**/*.woff", + "/**/*.woff2", + "/**/*.ttf", + "/**/*.svg", + "/**/*.webp", + "/**/*.ico" + ] +} \ No newline at end of file From 69cc22efac07333d96a948429ae8e7d1b8725204 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 18:33:15 +0900 Subject: [PATCH 13/15] =?UTF-8?q?fix:=20=5Froutes.json=E3=81=AE=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/_routes.json => _routes.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) rename functions/_routes.json => _routes.json (93%) diff --git a/functions/_routes.json b/_routes.json similarity index 93% rename from functions/_routes.json rename to _routes.json index 23f5b34..7965bfe 100644 --- a/functions/_routes.json +++ b/_routes.json @@ -1,4 +1,5 @@ { + "version": 1, "include": ["/blog/*"], "exclude": [ "/assets/*", diff --git a/package.json b/package.json index f69cbbe..d3c16e9 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "type": "module", "scripts": { "build": "react-router build", + "postbuild": "cp _routes.json build/client/_routes.json", "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", "typecheck": "react-router typegen && tsc", From bb5c95334ec5186d1bf4ff8912b3772ad3dab766 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 18:47:42 +0900 Subject: [PATCH 14/15] =?UTF-8?q?fix:=20=E3=83=91=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/blog/[id].ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index 7f51727..771d456 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -8,9 +8,9 @@ export const onRequestGet: PagesFunction = async (context) => { } const { id } = context.params; - const baseHtmlRes = await fetch('https://object-t.com/index.html'); + const baseHtmlRes = await context.env.ASSETS.fetch('/index.html'); let html = await baseHtmlRes.text(); - + const ogTitle = `ブログ記事 ${id}`; const ogDescription = `これはブログ ${id} の説明です。`; const ogImage = `https://object-t.com/assets/logo.webp`; From 034a27c285b3507ce1e14ccb109dd1a7af91d5f3 Mon Sep 17 00:00:00 2001 From: "naoto.kido" Date: Wed, 16 Apr 2025 18:56:13 +0900 Subject: [PATCH 15/15] =?UTF-8?q?fix:=20=E3=83=91=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/blog/[id].ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/functions/blog/[id].ts b/functions/blog/[id].ts index 771d456..0c6a91e 100644 --- a/functions/blog/[id].ts +++ b/functions/blog/[id].ts @@ -2,17 +2,24 @@ import type { PagesFunction } from '@cloudflare/workers-types' export const onRequestGet: PagesFunction = async (context) => { const url = new URL(context.request.url); - + if (/\.(js|css|png|jpg|jpeg|webp|woff2?|ttf|svg|ico|map)$/.test(url.pathname)) { - return fetch(context.request); + return context.env.ASSETS.fetch(context.request); + } + + const indexUrl = new URL('/', url.origin); + + const indexRequest = new Request(indexUrl.toString(), context.request); + + const baseHtmlRes = await context.env.ASSETS.fetch(indexRequest); + if (!baseHtmlRes.ok) { + return new Response(`Failed to fetch index.html: ${baseHtmlRes.status} ${baseHtmlRes.statusText}`, { status: baseHtmlRes.status }); } - const { id } = context.params; - const baseHtmlRes = await context.env.ASSETS.fetch('/index.html'); let html = await baseHtmlRes.text(); - - const ogTitle = `ブログ記事 ${id}`; - const ogDescription = `これはブログ ${id} の説明です。`; + + const ogTitle = `ブログ記事 ${url.pathname}`; + const ogDescription = `これはブログ ${url.pathname} の説明です。`; const ogImage = `https://object-t.com/assets/logo.webp`; html = html @@ -20,11 +27,11 @@ export const onRequestGet: PagesFunction = async (context) => { .replace(/]*>/, ``) .replace(/]*>/, ``) .replace(/]*>/, ``) - .replace(/]*>/, ``); + .replace(/]*>/, ``); return new Response(html, { headers: { - 'Content-Type': 'text/html', + 'Content-Type': 'text/html; charset=utf-8', }, }); }; \ No newline at end of file