');
}
lines.push('');
}
return lines.join("\n");
}
function stateIcon(state) {
const s = String(state).toLowerCase();
const icons = {
"seed": "bx bxs-coffee-bean",
"seedling": "bx bxs-coffee-bean",
"sprout": "bx bxs-leaf",
"sprouting": "bx bxs-leaf",
"bud": "bx bxs-leaf",
"evergreen": "bx bxs-tree-alt",
"draft": "bx bx-edit",
"review": "bx bx-search-alt"
};
return icons[s] || "bx bxs-circle";
}
function stateClass(state) {
const s = String(state).toLowerCase();
const growth = ["seed", "seedling", "sprout", "sprouting", "bud", "evergreen"];
if (growth.includes(s)) return "state-growth";
if (s === "draft") return "state-draft";
if (s === "review") return "state-review";
return "state-unknown";
}
function buildGardenTable(catNote, list) {
let html = '
\n
Title
Type
State
Date
\n';
for (const a of list) {
const link = SHARE_BASE + "/" + encodeURIComponent(a.getLabelValue("shareAlias") || a.noteId);
const type = a.type || "text";
const state = a.getLabelValue("state") || "";
const date = (a.getLabelValue("date") || a.dateCreated).slice(0, 10);
html += `
';
return html;
}
function pageUrl(aliasBase, p) {
return SHARE_BASE + "/" + (p <= 1 ? aliasBase : aliasBase + "-" + p);
}
function paginationHtml(page, totalPages, aliasBase) {
if (totalPages <= 1) return "";
let html = '';
return html;
}
function syncPageNotes(pageLabel, aliasBase, totalPages, list, garden, hideTitle) {
const systemNote = api.getNote(BLOG_SYSTEM_NOTE_ID);
if (!systemNote) return;
const existing = systemNote.getChildNotes().filter(n => n.getLabelValue("blogPagedFor") === pageLabel);
for (let p = 2; p <= totalPages; p++) {
let note = existing.find(n => n.getLabelValue("blogPagedNumber") === String(p));
const created = !note;
if (!note) {
const res = api.createTextNote(systemNote.noteId, "Blog page " + p + " (" + pageLabel + ")", "");
note = res.note ? res.note : res;
}
const slice = list.slice((p - 1) * POSTS_PER_PAGE, p * POSTS_PER_PAGE);
let body = garden ? buildGardenTable(note, slice) : (renderArticles(slice) || '
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 ` \n ${escapeHtml(a.title)}\n ${link}\n ${escapeHtml(a.noteId)}\n ${pubDate}\n ${escapeHtml(summary)}\n `;
}).join("\n");
const now = new Date().toUTCString();
return `\n\n \n ${escapeHtml(SITE_TITLE)}\n ${BASE_URL}\n \n ${escapeHtml(SITE_DESCRIPTION)}\n en\n ${now}\n${items}\n \n`;
}
function slugifyTag(t) {
return t.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
}
function buildStateMap() {
const systemNote = api.getNote(BLOG_SYSTEM_NOTE_ID);
if (!systemNote) return;
const mapNote = systemNote.getChildNotes().find(n => n.title === "state_map");
if (!mapNote) return;
const map = {};
const seen = new Set();
(function walk(note, depth) {
if (depth > 20) return;
for (const child of note.getChildNotes()) {
if (seen.has(child.noteId)) continue;
seen.add(child.noteId);
const state = child.getLabelValue("state");
if (child.getLabelValue("publish") === "true" && state) {
map[child.noteId] = state;
}
walk(child, depth + 1);
}
})(blogRoot, 0);
const html = ``;
if (mapNote.getContent() !== html) {
mapNote.setContent(html);
mapNote.save();
api.log("blog_generator: state map updated");
}
}
function collectAllPublished() {
const out = [];
const seen = new Set();
(function walk(note, depth) {
if (depth > 20) return;
for (const child of note.getChildNotes()) {
if (seen.has(child.noteId)) continue;
seen.add(child.noteId);
if (child.getLabelValue("publish") === "true") out.push(child);
walk(child, depth + 1);
}
})(blogRoot, 0);
out.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);
});
return out;
}
function buildSitemap() {
const systemNote = api.getNote(BLOG_SYSTEM_NOTE_ID);
if (!systemNote) return;
const sitemapNote = systemNote.getChildNotes().find(n => n.title === "sitemap.xml");
if (!sitemapNote) return;
const urls = [SHARE_BASE + "/" + BLOG_ALIAS];
for (const a of collectAllPublished()) {
urls.push(SHARE_BASE + "/" + encodeURIComponent(a.getLabelValue("shareAlias") || a.noteId));
}
for (const cat of collectCategoryNotes()) {
urls.push(SHARE_BASE + "/" + encodeURIComponent(cat.getLabelValue("shareAlias") || cat.noteId));
}
const tagsNote = blogRoot.getChildNotes().find(n => n.title === TAGS_SECTION_TITLE);
if (tagsNote) {
for (const t of tagsNote.getChildNotes()) {
urls.push(SHARE_BASE + "/" + encodeURIComponent(t.getLabelValue("shareAlias") || t.noteId));
}
}
const body = urls.map(u => ` ${escapeHtml(u)}`).join("\n");
const xml = `\n\n${body}\n`;
if (sitemapNote.getContent() !== xml) {
sitemapNote.setContent(xml);
sitemapNote.save();
api.log("blog_generator: sitemap updated (" + urls.length + " urls)");
}
}
function ensureShareDescription() {
const seen = new Set();
(function walk(note, depth) {
if (depth > 20) return;
for (const child of note.getChildNotes()) {
if (seen.has(child.noteId)) continue;
seen.add(child.noteId);
if (child.getLabelValue("publish") === "true") {
const desc = child.getLabelValue("summary") || SITE_DESCRIPTION;
if (child.getLabelValue("shareDescription") !== desc) {
child.setLabel("shareDescription", desc);
child.save();
api.log("blog_generator: set shareDescription on " + child.title);
}
}
walk(child, depth + 1);
}
})(blogRoot, 0);
}
function buildBlogData() {
const systemNote = api.getNote(BLOG_SYSTEM_NOTE_ID);
if (!systemNote) return;
const dataNote = systemNote.getChildNotes().find(n => n.title === "blog_data");
if (!dataNote) return;
const list = collectAllPublished().map(a => ({
id: a.noteId,
title: a.title,
url: SHARE_BASE + "/" + encodeURIComponent(a.getLabelValue("shareAlias") || a.noteId),
summary: a.getLabelValue("summary") || "",
tags: tagList(a),
category: a.getLabelValue("category") || "",
date: (a.getLabelValue("date") || a.dateCreated).slice(0, 10)
}));
const html = ``;
if (dataNote.getContent() !== html) {
dataNote.setContent(html);
dataNote.save();
api.log("blog_generator: blog_data updated (" + list.length + " articles)");
}
}
function buildTags() {
const tagsNote = blogRoot.getChildNotes().find(n => n.title === TAGS_SECTION_TITLE);
if (!tagsNote) return;
const map = new Map();
for (const a of articles) {
for (const t of tagList(a)) {
if (!map.has(t)) map.set(t, []);
map.get(t).push(a);
}
}
const tagNames = Array.from(map.keys()).sort();
const existing = tagsNote.getChildNotes();
const byTitle = {};
for (const n of existing) byTitle[n.title] = n;
for (const tagName of tagNames) {
let note = byTitle[tagName];
if (!note) {
const created = api.createTextNote(tagsNote.noteId, tagName, "");
note = created.note ? created.note : created;
}
const tagged = map.get(tagName).slice();
tagged.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);
});
const html = `
Tag: ${escapeHtml(tagName)}
\n` + (renderArticles(tagged) || '
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 = '
';
for (const t of tagChildren) {
const url = SHARE_BASE + "/" + encodeURIComponent(t.getLabelValue("shareAlias") || t.noteId);
idxHtml += `#${escapeHtml(t.title)}`;
}
idxHtml += '
';
if (tagsNote.getContent() !== idxHtml) {
tagsNote.setContent(idxHtml);
tagsNote.save();
api.log("blog_generator: tags overview updated");
}
for (const n of existing) {
if (!map.has(n.title)) {
api.log("blog_generator: removing obsolete tag note " + n.title);
n.deleteNote();
}
}
}
function isCategoryNote(note) {
if (note.hasLabel("blogCategory")) return true;
const parents = note.getParentNotes();
const isTopLevel = parents.some(p => p.noteId === BLOG_ROOT_NOTE_ID);
if (!isTopLevel) return false;
const sysTitles = ["Tags", ".blog", "Impressum", "About Me", "Search"];
if (sysTitles.includes(note.title)) return false;
return true;
}
function collectCategoryNotes() {
const out = [];
(function walk(note, depth) {
if (depth > 20) return;
if (note.noteId !== BLOG_ROOT_NOTE_ID && isCategoryNote(note)) {
out.push(note);
}
for (const child of note.getChildNotes()) {
walk(child, depth + 1);
}
})(blogRoot, 0);
return out;
}
function buildCategories() {
const byCategoryLabel = new Map();
for (const a of articles) {
const c = a.getLabelValue("category") || "";
if (!c) continue;
if (!byCategoryLabel.has(c)) byCategoryLabel.set(c, []);
byCategoryLabel.get(c).push(a);
}
const marker = "";
for (const catNote of collectCategoryNotes()) {
const current = catNote.getContent() || "";
let userText = "";
const markerIdx = current.indexOf(marker);
if (markerIdx >= 0) {
userText = current.slice(0, markerIdx);
} else if (!current.trim().startsWith(" c.getLabelValue("publish") !== "true");
const merged = new Map();
for (const c of children) {
if (c.getLabelValue("publish") === "true") merged.set(c.noteId, c);
}
for (const a of (byCategoryLabel.get(catNote.title) || [])) {
merged.set(a.noteId, a);
}
const articlesList = Array.from(merged.values()).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);
});
let html = userText;
if (html && !html.endsWith("\n")) html += "\n";
html += marker + "\n";
if (subs.length) {
html += '
';
for (const s of subs) {
const url = SHARE_BASE + "/" + encodeURIComponent(s.getLabelValue("shareAlias") || s.noteId);
const icon = s.getLabelValue("iconClass");
html += ``;
if (icon) html += ``;
html += `${escapeHtml(s.title)}`;
}
html += "