yamlover 0.3.52 → 0.3.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -39804,7 +39804,7 @@ var require_builder = __commonJS({
39804
39804
  }
39805
39805
  render = /* @__PURE__ */ function(_this) {
39806
39806
  return function(element, obj) {
39807
- var attr, child, entry, index, key, value;
39807
+ var attr2, child, entry, index, key, value;
39808
39808
  if (typeof obj !== "object") {
39809
39809
  if (_this.options.cdata && requiresCDATA(obj)) {
39810
39810
  element.raw(wrapCDATA(obj));
@@ -39826,9 +39826,9 @@ var require_builder = __commonJS({
39826
39826
  child = obj[key];
39827
39827
  if (key === attrkey) {
39828
39828
  if (typeof child === "object") {
39829
- for (attr in child) {
39830
- value = child[attr];
39831
- element = element.att(attr, value);
39829
+ for (attr2 in child) {
39830
+ value = child[attr2];
39831
+ element = element.att(attr2, value);
39832
39832
  }
39833
39833
  }
39834
39834
  } else if (key === charkey) {
@@ -69277,6 +69277,90 @@ function allowedInReadOnly(method, pathname) {
69277
69277
  }
69278
69278
  var READ_ONLY_ERROR = { error: "server is read-only", readOnly: true };
69279
69279
 
69280
+ // src/server/seo.ts
69281
+ function isChapter(format) {
69282
+ return format === "x-yamlover-chapter" || format === "x-yamlover-task";
69283
+ }
69284
+ function slashOfStorePath(p2) {
69285
+ return segsOfPath(p2).map((s2) => encodeURIComponent(segToken(s2))).join("/");
69286
+ }
69287
+ var SITEMAP_MAX_URLS = 5e4;
69288
+ function collectPages(s2, meta, max = SITEMAP_MAX_URLS) {
69289
+ const pages = [];
69290
+ const page = (path12) => ({
69291
+ path: path12,
69292
+ slash: slashOfStorePath(path12),
69293
+ title: meta.title(path12),
69294
+ description: meta.description(path12)
69295
+ });
69296
+ pages.push(page(":"));
69297
+ let truncated = false;
69298
+ const walk = (rows, inBook) => {
69299
+ for (const r2 of rows) {
69300
+ if (pages.length >= max) {
69301
+ truncated = true;
69302
+ return;
69303
+ }
69304
+ const chapter = isChapter(r2.format);
69305
+ if (chapter) pages.push(page(r2.path));
69306
+ if (chapter || !inBook) walk(r2.children, inBook || chapter);
69307
+ }
69308
+ };
69309
+ walk(s2.toc(":"), isChapter(meta.format(":")));
69310
+ return { pages, truncated };
69311
+ }
69312
+ var XML_ESC = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;" };
69313
+ var xml = (t3) => t3.replace(/[&<>"']/g, (c3) => XML_ESC[c3]);
69314
+ function pageUrl(origin, basePath, slash) {
69315
+ return `${origin}${basePath}/${slash}`.replace(/\/+$/, "/");
69316
+ }
69317
+ function sitemapXml(origin, basePath, pages, truncated = false) {
69318
+ const urls = pages.map((p2) => ` <url><loc>${xml(pageUrl(origin, basePath, p2.slash))}</loc></url>`).join("\n");
69319
+ const note = truncated ? `
69320
+ <!-- truncated at ${SITEMAP_MAX_URLS} URLs, the sitemap protocol's per-file limit -->` : "";
69321
+ return `<?xml version="1.0" encoding="UTF-8"?>
69322
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
69323
+ ${urls}${note}
69324
+ </urlset>
69325
+ `;
69326
+ }
69327
+ function robotsTxt(sitemapUrl) {
69328
+ return [
69329
+ "# Do NOT disallow /api/ \u2014 this is a single-page app and its text is fetched from",
69330
+ "# /api/content at render time. A crawler denied that endpoint indexes blank pages.",
69331
+ "User-agent: *",
69332
+ "Allow: /",
69333
+ "",
69334
+ `Sitemap: ${sitemapUrl}`,
69335
+ ""
69336
+ ].join("\n");
69337
+ }
69338
+ var HTML_ESC = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" };
69339
+ var attr = (t3) => t3.replace(/[&<>"]/g, (c3) => HTML_ESC[c3]);
69340
+ var DESC_MAX = 300;
69341
+ function pageTitle(title, tokens, siteName) {
69342
+ const own = title?.trim() || tokens[tokens.length - 1] || siteName;
69343
+ if (tokens.length === 0) return own;
69344
+ return `${own} - ${siteName}${tokens.slice(0, -1).map((t3) => `: ${t3}`).join("")}`;
69345
+ }
69346
+ function headTags(opts) {
69347
+ const full = opts.title;
69348
+ const desc = opts.description?.trim().replace(/\s+/g, " ").slice(0, DESC_MAX);
69349
+ const tags = [
69350
+ `<title>${attr(full)}</title>`,
69351
+ `<link rel="canonical" href="${attr(opts.canonical)}">`,
69352
+ `<meta property="og:title" content="${attr(full)}">`,
69353
+ `<meta property="og:url" content="${attr(opts.canonical)}">`,
69354
+ `<meta property="og:type" content="article">`,
69355
+ `<meta property="og:site_name" content="${attr(opts.siteName)}">`
69356
+ ];
69357
+ if (desc) {
69358
+ tags.splice(1, 0, `<meta name="description" content="${attr(desc)}">`);
69359
+ tags.push(`<meta property="og:description" content="${attr(desc)}">`);
69360
+ }
69361
+ return tags.join("");
69362
+ }
69363
+
69280
69364
  // src/server/engine-api.ts
69281
69365
  var LINK_KEY = "$yamloverLink";
69282
69366
  var REF_KEY = "$yamloverRef";
@@ -70073,8 +70157,41 @@ function createHandlers(dataRoot, opts = {}) {
70073
70157
  sendJson(res, 400, { error: exc.message || String(exc) });
70074
70158
  }
70075
70159
  };
70160
+ const pageMeta = {
70161
+ title: (p2) => titleOf(store0, p2),
70162
+ description: (p2) => descriptionOf(store0, p2),
70163
+ format: (p2) => store0.node(p2)?.format ?? null
70164
+ };
70165
+ const seo = {
70166
+ robots: (sitemapUrl) => robotsTxt(sitemapUrl),
70167
+ sitemap: (origin, basePath) => {
70168
+ const { pages, truncated } = collectPages(store0, pageMeta);
70169
+ return sitemapXml(origin, basePath, pages, truncated);
70170
+ },
70171
+ /** The `<head>` tags for ONE client route (a slash URL path, base already stripped), or ""
70172
+ * when the path names no node — the common case, and not an error: the shell answers every
70173
+ * path, `/.browser/settings.yo` included, which has no node by design. */
70174
+ head: (pathname, canonical) => {
70175
+ try {
70176
+ const segs = canonSegs(store0, slashToSegs(pathname), false);
70177
+ const p2 = storePath(segs);
70178
+ if (!store0.node(p2)) return "";
70179
+ const siteName = titleOf(store0, ":") || rootName;
70180
+ return headTags({
70181
+ // the same tokens the client's own title effect walks — segToken, not the raw segment
70182
+ title: pageTitle(titleOf(store0, p2), segs.map((g) => segToken(g)), siteName),
70183
+ description: descriptionOf(store0, p2),
70184
+ canonical,
70185
+ siteName
70186
+ });
70187
+ } catch {
70188
+ return "";
70189
+ }
70190
+ }
70191
+ };
70076
70192
  return Object.assign(handler, {
70077
70193
  ready,
70194
+ seo,
70078
70195
  close: () => {
70079
70196
  if (closed) return;
70080
70197
  closed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamlover",
3
- "version": "0.3.52",
3
+ "version": "0.3.54",
4
4
  "description": "A local-first, editable knowledge graph for humans and LLMs: npx yamlover <root> serves a browser UI over a plain-text directory, with an editor, live queries, pointers, and an LLM-agent API.",
5
5
  "type": "module",
6
6
  "license": "MIT",