@cmo-so/cli 0.12.14 → 0.12.15

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.
Files changed (2) hide show
  1. package/dist/cmo.mjs +66 -7
  2. package/package.json +1 -1
package/dist/cmo.mjs CHANGED
@@ -15205,6 +15205,47 @@ async function partSet(site, slug, change) {
15205
15205
  counts.social = (counts.social ?? 0) + links2.length;
15206
15206
  }
15207
15207
  }
15208
+ if (change.footer_links && slug === "footer") {
15209
+ const isRow = (b) => String(b.attrs.className ?? "").split(/\s+/).includes("maggie-footer-links");
15210
+ const items3 = change.footer_links.filter((l) => l?.label && l?.url).slice(0, 20).map(
15211
+ (l) => `<!-- wp:paragraph {"fontSize":"small"} -->
15212
+ <p class="has-small-font-size"><a href="${escAttr(l.url)}">${escAttr(l.label)}</a></p>
15213
+ <!-- /wp:paragraph -->`
15214
+ ).join("\n\n");
15215
+ const row = items3 ? blockOf(
15216
+ `<!-- wp:group {"className":"maggie-footer-links","style":{"spacing":{"margin":{"bottom":"var:preset|spacing|40"}}},"layout":{"type":"flex","flexWrap":"wrap"}} -->
15217
+ <div class="wp-block-group maggie-footer-links" style="margin-bottom:var(--wp--preset--spacing--40)">${items3}</div>
15218
+ <!-- /wp:group -->`
15219
+ ) : null;
15220
+ const old = find(blocks, isRow);
15221
+ if (old) {
15222
+ const top = blocks.indexOf(old);
15223
+ if (top >= 0) blocks.splice(top, 1, ...row ? [row] : []);
15224
+ else {
15225
+ const parent = find(blocks, (b) => b.inner.includes(old));
15226
+ const k = parent.inner.indexOf(old);
15227
+ if (row) parent.inner[k] = row;
15228
+ else {
15229
+ parent.inner.splice(k, 1);
15230
+ let seen = -1;
15231
+ const at = parent.content.findIndex((c) => c === null && ++seen === k);
15232
+ parent.content.splice(at, parent.content[at + 1] === "\n\n" ? 2 : 1);
15233
+ }
15234
+ touchPath(blocks, parent);
15235
+ }
15236
+ } else if (row) {
15237
+ const outer = blocks.find((b) => b.name === "core/group");
15238
+ if (outer?.inner.length) {
15239
+ const k = outer.inner.length - 1;
15240
+ outer.inner.splice(k, 0, row);
15241
+ let seen = -1;
15242
+ const at = outer.content.findIndex((c) => c === null && ++seen === k);
15243
+ outer.content.splice(at, 0, null, "\n\n");
15244
+ touchPath(blocks, outer);
15245
+ } else blocks.push(row);
15246
+ } else throw new Error("footer_links: give at least one { label, url }.");
15247
+ counts.footer_links = change.footer_links.length;
15248
+ }
15208
15249
  const content = serialize(blocks);
15209
15250
  if (content === p.content) return { slug, changed: false, counts };
15210
15251
  await call(site, { method: "POST", path: `/wp/v2/template-parts/${p.id}`, body: { content } });
@@ -17433,6 +17474,11 @@ async function linksPlan(site, o = {}) {
17433
17474
  );
17434
17475
  const footer = await partGet(site, "footer").catch(() => null);
17435
17476
  const footerPaths = footer ? contentLinks(footer.content, base) : /* @__PURE__ */ new Set();
17477
+ const rowHtml = /maggie-footer-links[^>]*>([\s\S]*?)<\/div>\s*<!-- \/wp:group -->/.exec(footer?.content ?? "")?.[1] ?? "";
17478
+ const footerRow = [...rowHtml.matchAll(/<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g)].map((m) => ({
17479
+ url: m[1],
17480
+ label: strip(m[2])
17481
+ }));
17436
17482
  const header = await partGet(site, "header").catch(() => null);
17437
17483
  const headerPaths = header ? contentLinks(header.content, base) : /* @__PURE__ */ new Set();
17438
17484
  const chrome = /* @__PURE__ */ new Set([...menuPaths, ...footerPaths, ...headerPaths]);
@@ -17495,14 +17541,16 @@ async function linksPlan(site, o = {}) {
17495
17541
  type: "menu",
17496
17542
  why: "Main pages every visitor should reach in one click.",
17497
17543
  add: wantMenu.slice(0, 6).map((d) => ({ label: d.title, url: d.url })),
17498
- tool: "wp_menu_set"
17544
+ tool: "wp_menu_set",
17545
+ args: { add: wantMenu.slice(0, 6).map((d) => ({ label: d.title, url: d.path })) }
17499
17546
  });
17500
17547
  if (postDocs.length && blogPage && !chrome.has(blogPage.path))
17501
17548
  actions.push({
17502
17549
  type: "menu",
17503
17550
  why: "The blog isn't reachable from the menu.",
17504
17551
  add: [{ label: blogPage.title || "Blog", url: blogPage.url }],
17505
- tool: "wp_menu_set"
17552
+ tool: "wp_menu_set",
17553
+ args: { add: [{ label: blogPage.title || "Blog", url: blogPage.path }] }
17506
17554
  });
17507
17555
  const legal = pageDocs.filter(
17508
17556
  (d) => /privacy|terms|cookie|legal|impressum|datenschutz|accessib/i.test(`${d.path} ${d.title}`)
@@ -17514,7 +17562,14 @@ async function linksPlan(site, o = {}) {
17514
17562
  type: "footer",
17515
17563
  why: "Legal, contact and about pages belong in the footer of every page (trust, and Google's quality raters look for them).",
17516
17564
  add: footerMissing.slice(0, 8).map((d) => ({ label: d.title, url: d.url })),
17517
- tool: "wp_footer_set"
17565
+ tool: "wp_footer_set",
17566
+ // The footer's own row (if this tool made one) plus the missing pages, ready for wp_footer_set.
17567
+ args: {
17568
+ footer_links: [
17569
+ ...footerRow,
17570
+ ...footerMissing.slice(0, 8).filter((d) => !footerRow.some((r) => sitePath(r.url, base) === d.path)).map((d) => ({ label: d.title, url: d.path }))
17571
+ ]
17572
+ }
17518
17573
  });
17519
17574
  for (const hub of hubs) {
17520
17575
  const kids = pageDocs.filter((c) => c.parent === hub.id);
@@ -17637,8 +17692,8 @@ function relatedBlock(heading2, links2) {
17637
17692
  const items3 = links2.map((l) => `<!-- wp:list-item -->
17638
17693
  <li><a href="${esc(l.url)}">${esc(l.label)}</a></li>
17639
17694
  <!-- /wp:list-item -->`).join("\n");
17640
- return `<!-- wp:group {"metadata":{"name":"Related links","maggie":{"type":"related-links","version":1}},"align":"wide","className":"maggie-related-links","layout":{"type":"constrained"}} -->
17641
- <div class="wp-block-group alignwide maggie-related-links"><!-- wp:heading {"level":2} -->
17695
+ return `<!-- wp:group {"metadata":{"name":"Related links","maggie":{"type":"related-links","version":1}},"align":"wide","className":"maggie-related-links","style":{"spacing":{"padding":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"}}},"layout":{"type":"constrained","contentSize":"var(--wp--style--global--wide-size)"}} -->
17696
+ <div class="wp-block-group alignwide maggie-related-links" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"><!-- wp:heading {"level":2} -->
17642
17697
  <h2 class="wp-block-heading">${esc(heading2)}</h2>
17643
17698
  <!-- /wp:heading -->
17644
17699
 
@@ -19601,12 +19656,16 @@ var elementTools = [
19601
19656
  }),
19602
19657
  tool({
19603
19658
  name: "wp_footer_set",
19604
- description: "Change the footer: any text (address, hours, copyright), link or picture, social profile links by service; reset goes back to the theme's own footer.",
19659
+ description: "Change the footer: any text (address, hours, copyright), link or picture, social profile links by service, the row of page links (footer_links); reset goes back to the theme's own footer.",
19605
19660
  parameters: {
19606
19661
  type: "object",
19607
19662
  properties: {
19608
19663
  ...EDITS.properties ?? {},
19609
19664
  social: obj('Social links by service: { "facebook": "https://\u2026", "instagram": "" (remove) }.'),
19665
+ footer_links: arr(
19666
+ "The footer's row of page links, in order (replaces the row set before; [] removes it): legal, contact, about, main pages.",
19667
+ obj("", { label: str2(""), url: str2("Relative for own pages: /contact/.") })
19668
+ ),
19610
19669
  reset: bool("Drop the site's changes: back to the theme's footer.")
19611
19670
  }
19612
19671
  },
@@ -23900,7 +23959,7 @@ var program = new Command();
23900
23959
  // package.json
23901
23960
  var package_default = {
23902
23961
  name: "@cmo-so/cli",
23903
- version: "0.12.14",
23962
+ version: "0.12.15",
23904
23963
  description: "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
23905
23964
  type: "module",
23906
23965
  license: "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/cli",
3
- "version": "0.12.14",
3
+ "version": "0.12.15",
4
4
  "description": "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",