/* ============================================================ blog-detail.jsx — レリゴーブログ 記事詳細(?id=xxx) ============================================================ */ /* ============================================================ 本文の書式(情報=info.jsx の renderInline と同じ規則をブログにも) **文字** … 強調 [文字](URL) … 好きな文字でリンク https://… … そのまま書いてもリンクになる ※ ![説明](URL) の画像は、行単位で先にサーバー側がブロックへ分けるので ここには来ない(405-blog.php の fjf_b_body_to_array)。 ============================================================ */ const MD_INLINE = /(\[[^\]]+\]\([^)\s]+\)|\*\*[^*]+\*\*|https?:\/\/[^\s]+)/; function renderInline(text, kp) { return String(text).split(MD_INLINE).map((p, i) => { if (!p) return null; const k = kp + "-" + i; const link = p.match(/^\[([^\]]+)\]\(([^)\s]+)\)$/); if (link) return {link[1]}; if (/^https?:\/\//.test(p)) return {p}; const em = p.match(/^\*\*([^*]+)\*\*$/); if (em) return {em[1]}; return p; }); } const BPID = new URLSearchParams(location.search).get("id"); const bload = (k, d) => { try { return JSON.parse(localStorage.getItem(k)) ?? d; } catch (e) { return d; } }; const bsave = (k, v) => { try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} }; function AuthorFace({ author }) { const a = window.BLOG_AUTHORS[author]; const C = a.comp(); return (
{C ? : null}
); } function ReadButton({ post }) { const cat = window.BLOG_CATS[post.cat]; const [read, setRead] = React.useState(() => bload("rg_blog_" + post.id, { n: 1 + Math.floor(Math.random() * 25), mine: false })); const [pop, setPop] = React.useState(false); const [toast, setToast] = React.useState(false); const tap = () => { setRead((prev) => { const next = { n: prev.n + (prev.mine ? -1 : 1), mine: !prev.mine }; bsave("rg_blog_" + post.id, next); return next; }); setPop(true); setTimeout(() => setPop(false), 250); }; return (
); } function NotFound() { return (
◀ ブログ一覧へ
記事が見つからない…
URLが正しいか確認してね。
); } function BlogDetail() { const [posts, setPosts] = React.useState(window.BLOG_POSTS || []); const [loaded, setLoaded] = React.useState(!(window.GardenAPI && window.GardenAPI.enabled)); React.useEffect(() => { if (window.GardenAPI && window.GardenAPI.enabled && window.GardenAPI.blogPosts) { window.GardenAPI.blogPosts().then((sv) => { if (Array.isArray(sv)) setPosts(sv); setLoaded(true); }); } }, []); if (!loaded && posts.length === 0) { return
よみこみ中…
; } let idx = posts.findIndex((x) => x.id === BPID); if (idx < 0) idx = 0; // id が無い / 不正なときは最初の記事を表示 if (posts.length === 0) return ; const post = posts[idx]; const cat = window.BLOG_CATS[post.cat]; const author = window.BLOG_AUTHORS[post.author] || window.BLOG_AUTHORS.panda; const prev = posts[idx - 1]; const next = posts[idx + 1]; return (
{window.HudBar ? React.createElement(window.HudBar) : null}
◀ ブログ一覧へ POST
{cat.label} {post.date}

{post.title}

{post.image && }
{author.name}
{author.role}
{post.body.map((blk, i) => { if (typeof blk === "string") return

{renderInline(blk, "b" + i)}

; if (blk.img) return {blk.alt; return

{blk.h2}

; })}
{window.SHOW_REACTIONS && }
B 一覧へもどる
© 2026 405 JUST FOUND — INSERT COIN
); } ReactDOM.createRoot(document.getElementById("root")).render();