/* ============================================================
blog-detail.jsx — レリゴーブログ 記事詳細(?id=xxx)
============================================================ */
/* ============================================================
本文の書式(情報=info.jsx の renderInline と同じ規則をブログにも)
**文字** … 強調
[文字](URL) … 好きな文字でリンク
https://… … そのまま書いてもリンクになる
※  の画像は、行単位で先にサーバー側がブロックへ分けるので
ここには来ない(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 (
);
}
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}
{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

;
return
{blk.h2}
;
})}
{window.SHOW_REACTIONS &&
}
© 2026 405 JUST FOUND — INSERT COIN
);
}
ReactDOM.createRoot(document.getElementById("root")).render();