${escapeHtml(summary)}
`); if (tags.length) { lines.push(' '); } lines.push('const BLOG_ROOT_NOTE_ID = "__BLOG_ROOT_ID__";
const BLOG_SYSTEM_NOTE_ID = "__BLOG_SYSTEM_ID__";
const BLOG_ALIAS = "blog";
const SHARE_BASE = "https://YOUR-INSTANCE.example.com";
const BASE_URL = SHARE_BASE + "/" + BLOG_ALIAS;
const SITE_TITLE = "Your Blog";
const SITE_DESCRIPTION = "Project documentation & technical blog by the author";
const TAGS_SECTION_TITLE = "Tags";
const HIDE_HOME_TITLE_NOTE_ID = "__HIDE_HOME_TITLE_ID__";
const POSTS_PER_PAGE = 10;
function isInSubtree(note, targetId) {
try {
if (note.noteId === targetId) return true;
for (const child of note.getChildNotes()) {
if (isInSubtree(child, targetId)) return true;
}
} catch (e) {
api.log("blog_generator: isInSubtree error: " + e);
}
return false;
}
function isGeneratedNoteId(id) {
try {
if (id === BLOG_ROOT_NOTE_ID) return true;
const n = api.getNote(id);
if (n && n.hasLabel("blogPagedFor")) return true;
const systemNote = api.getNote(BLOG_SYSTEM_NOTE_ID);
if (systemNote) {
const feedNote = systemNote.getChildNotes().find(n => n.title === "feed.xml");
if (feedNote && id === feedNote.noteId) return true;
}
const blogRoot = api.getNote(BLOG_ROOT_NOTE_ID);
if (blogRoot) {
const tagsNote = blogRoot.getChildNotes().find(n => n.title === TAGS_SECTION_TITLE);
if (tagsNote && isInSubtree(tagsNote, id)) return true;
}
} catch (e) {
api.log("blog_generator: isGeneratedNoteId error: " + e);
}
return false;
}
if (api.originEntity && isGeneratedNoteId(api.originEntity.noteId)) {
api.log("blog_generator: ignoring self-trigger from generated note");
return;
}
// ---- P2: only rebuild on events that can affect the generated pages ----
// (avoids full rebuild on every snippet/CSS edit, image upload, self-set shareAlias, ...)
const GEN_RELEVANT_LABELS = new Set([
"publish", "date", "category", "tags", "summary", "state",
"blogGarden", "blogCategory", "blogNoArticles", "iconClass"
]);
const GEN_SKIP_RELATIONS = new Set([
"shareHtml", "shareCss", "shareTemplate", "shareFavicon", "shareOpenGraphImage",
"runOnNoteContentChange", "runOnAttributeChange", "runOnAttributeCreation",
"runOnNoteTitleChange", "runOnNoteCreation", "runOnChildNoteCreation",
"runOnBranchDeletion", "runOnEntityChange"
]);
function shouldRebuild() {
const o = api.originEntity;
const ev = api.originEvent || "";
if (!o) return true;
if (/contentChange/i.test(ev)) {
// Article body edits don't change the index/feed/tag pages (only summary labels do).
// Keep the refresh only for thumbnail image replacements (index shows the thumb).
const title = o.title || "";
if (!/^(?:thumb|.+_thumb)\.[A-Za-z0-9]+$/i.test(title)) return false;
}
if (o.type === "label" && !GEN_RELEVANT_LABELS.has(o.name)) return false;
if (o.type === "relation" && GEN_SKIP_RELATIONS.has(o.name)) return false;
if (o.type === "branch") return false; // publish/unpublish covers clone+move via label events
return true;
}
if (!shouldRebuild()) {
api.log("blog_generator: event skipped (no index impact)");
return;
}
try {
run();
} catch (e) {
api.log("blog_generator: ERROR " + (e && e.stack ? e.stack : e));
}
function run() {
const blogRoot = api.getNote(BLOG_ROOT_NOTE_ID);
if (!blogRoot) {
api.log("blog_generator: root note not found");
return;
}
const articles = [];
function visit(note, depth) {
if (depth > 20) return;
if (note.noteId !== BLOG_ROOT_NOTE_ID && note.getLabelValue("blogGarden") !== null) {
return;
}
const publish = note.getLabelValue("publish");
if (note.noteId !== BLOG_ROOT_NOTE_ID && publish === "true") {
articles.push(note);
return;
}
for (const child of note.getChildNotes()) {
visit(child, depth + 1);
}
}
visit(blogRoot, 0);
articles.sort((a, b) => {
const da = a.getLabelValue("date") || a.dateCreated;
const db = b.getLabelValue("date") || b.dateCreated;
return (db < da) ? -1 : (db > da ? 1 : 0);
});
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function tagList(a) {
return (a.getLabelValue("tags") || "").split(/\s+/).filter(Boolean).map(t => t.replace(/^!+/, ""));
}
function tagHref(t) {
return SHARE_BASE + "/" + encodeURIComponent("tag-" + slugifyTag(t));
}
function thumbSrc(a) {
try {
const thumbs = (a.getChildNotes() || []).filter(n => /^(?:thumb|.+_thumb)\.[A-Za-z0-9]+$/i.test(n.title));
if (thumbs.length) return "api/notes/" + thumbs[0].noteId + "/download";
} catch (e) {}
try {
const m = String(a.getContent() || "").match(/ ${escapeHtml(summary)}]+src="([^"]+)"/);
return m ? m[1] : "";
} catch (e) {
return "";
}
}
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
function dateText(raw) {
const iso = String(raw || "").slice(0, 10);
if (!iso) return "";
const d = new Date(iso);
if (isNaN(d)) return iso;
return WEEKDAYS[d.getUTCDay()] + " " + iso;
}
function renderArticles(list) {
const lines = [];
for (const a of list) {
const date = dateText(a.getLabelValue("date") || a.dateCreated);
const cat = a.getLabelValue("category") || "";
const summary = a.getLabelValue("summary") || "";
const link = `${SHARE_BASE}/${encodeURIComponent(a.getLabelValue("shareAlias") || a.noteId)}`;
const tags = tagList(a);
const thumb = thumbSrc(a);
lines.push('
${escapeHtml(a.title)}
`);
lines.push(' ');
lines.push('
| Title | Type | State | Date |
|---|---|---|---|
| ${escapeHtml(a.title)} | ${escapeHtml(type)} | ${state ? `${escapeHtml(state)}` : ""} | ${escapeHtml(date)} |
No articles.
'); body += paginationHtml(p, totalPages, aliasBase); if (note.getContent() !== body) { note.setContent(body); note.save(); } note.setLabel("blogPagedFor", pageLabel); note.setLabel("blogPagedNumber", String(p)); note.setLabel("shareAlias", aliasBase + "-" + p); note.setLabel("shareHiddenFromTree", ""); if (hideTitle && !note.hasRelation("shareHtml", HIDE_HOME_TITLE_NOTE_ID)) { note.setRelation("shareHtml", HIDE_HOME_TITLE_NOTE_ID); } note.save(); api.log("blog_generator: page " + pageLabel + " #" + p + (created ? " created" : " updated")); } for (const n of existing) { const num = parseInt(n.getLabelValue("blogPagedNumber") || "999", 10); if (num > totalPages) { api.log("blog_generator: removing obsolete page note " + n.title); n.deleteNote(); } } } function buildIndex() { const pageCount = Math.max(1, Math.ceil(articles.length / POSTS_PER_PAGE)); const body = (renderArticles(articles.slice(0, POSTS_PER_PAGE)) || 'No articles published yet.
') + paginationHtml(1, pageCount, BLOG_ALIAS); if (blogRoot.getContent() !== body) { blogRoot.setContent(body); blogRoot.save(); api.log("blog_generator: index updated (" + articles.length + " articles, " + pageCount + " pages)"); } else { api.log("blog_generator: index unchanged"); } syncPageNotes("index", BLOG_ALIAS, pageCount, articles, false, true); } function buildFeedXml() { const items = articles.map(a => { const date = a.getLabelValue("date") || a.dateCreated; const alias = a.getLabelValue("shareAlias") || a.noteId; const summary = a.getLabelValue("summary") || ""; const pubDate = new Date(date).toUTCString(); const link = `${BASE_URL}/${encodeURIComponent(alias)}`; return `No articles.
'); if (note.getContent() !== html) { note.setContent(html); note.save(); } if (!note.hasRelation("shareHtml", HIDE_HOME_TITLE_NOTE_ID)) { note.setRelation("shareHtml", HIDE_HOME_TITLE_NOTE_ID); note.save(); } const alias = "tag-" + slugifyTag(tagName); if (note.getLabelValue("shareAlias") !== alias) { note.setLabel("shareAlias", alias); note.save(); } if (!note.hasLabel("shareHiddenFromTree")) { note.setLabel("shareHiddenFromTree", ""); note.save(); } } // Tags overview page (list all tags as clickable chips) const tagChildren = tagsNote.getChildNotes().slice().sort((a, b) => a.title.localeCompare(b.title)); let idxHtml = 'No articles yet.
'; } html += paginationHtml(1, pageCount, aliasBase); if (catNote.getContent() !== html) { catNote.setContent(html); catNote.save(); api.log("blog_generator: category page updated: " + catNote.title + " (" + pageCount + " pages)"); } syncPageNotes("cat:" + catNote.title, aliasBase, pageCount, articlesList, catNote.hasLabel("blogGarden"), false); } } function removeEmptyCategories() { const blogRoot = api.getNote(BLOG_ROOT_NOTE_ID); if (!blogRoot) return; const toCheck = []; (function collect(note, depth) { if (depth > 20) return; if (note.noteId !== blogRoot.noteId && note.hasLabel("blogCategory")) { toCheck.push(note); } for (const child of note.getChildNotes()) { collect(child, depth + 1); } })(blogRoot, 0); for (const note of toCheck) { if (note.getChildNotes().length === 0) { api.log("blog_generator: removing empty category " + note.title); note.deleteNote(); } } } function hideThumbChildren() { for (const a of articles) { try { for (const c of a.getChildNotes()) { if (/^(?:thumb|.+_thumb)\.[A-Za-z0-9]+$/i.test(c.title) && !c.hasLabel("shareHiddenFromTree")) { c.setLabel("shareHiddenFromTree", ""); c.save(); } } } catch (e) {} } } buildIndex(); const systemNote = api.getNote(BLOG_SYSTEM_NOTE_ID); const feedNote = systemNote.getChildNotes().find(n => n.title === "feed.xml"); if (feedNote) { const xml = buildFeedXml(); if (feedNote.getContent() !== xml) { feedNote.setContent(xml); feedNote.save(); api.log("blog_generator: feed updated"); } } buildTags(); buildCategories(); buildStateMap(); buildSitemap(); ensureSeoMeta(); buildBlogData(); hideThumbChildren(); removeEmptyCategories(); }