diff --git a/.tangled/workflows/lint.yml b/.tangled/workflows/lint.yml index 32ae1c9..a686c14 100644 --- a/.tangled/workflows/lint.yml +++ b/.tangled/workflows/lint.yml @@ -17,6 +17,6 @@ steps: - name: "Install dependencies" command: "bun install" - name: "Lint check" - command: "cd packages/cli && biome lint ." + command: "cd packages/cli && bun run biome lint ." - name: "Format check" - command: "cd packages/cli && biome format ." + command: "cd packages/cli && bun run biome format ." diff --git a/packages/cli/src/commands/inject.ts b/packages/cli/src/commands/inject.ts index 19e1684..0fea3dc 100644 --- a/packages/cli/src/commands/inject.ts +++ b/packages/cli/src/commands/inject.ts @@ -131,7 +131,7 @@ export const injectCommand = command({ let content = await fs.readFile(htmlPath, "utf-8"); // Inject the tags - let injected = injectLinkTags( + const injected = injectLinkTags( dryRun, relativePath, content, diff --git a/packages/cli/src/commands/publish.ts b/packages/cli/src/commands/publish.ts index c2e417d..6295237 100644 --- a/packages/cli/src/commands/publish.ts +++ b/packages/cli/src/commands/publish.ts @@ -24,7 +24,6 @@ import { createBlueskyPost, addBskyPostRefToDocument, COVER_IMAGE_MAX_SIZE, - getPublication, } from "../lib/atproto"; import { scanContentDirectory, diff --git a/packages/cli/src/components/sequoia-subscribe.js b/packages/cli/src/components/sequoia-subscribe.js index f5f2f0a..5e46ec7 100644 --- a/packages/cli/src/components/sequoia-subscribe.js +++ b/packages/cli/src/components/sequoia-subscribe.js @@ -161,6 +161,7 @@ function storeSubscriberDid(did) { const expires = new Date( Date.now() + 365 * 24 * 60 * 60 * 1000, ).toUTCString(); + // biome-ignore lint/suspicious/noDocumentCookie: back-compat with older browsers document.cookie = `sequoia_did=${encodeURIComponent(did)}; Expires=${expires}; Path=/; SameSite=Lax; Secure`; } catch { // Cookie write may fail in some embedded contexts @@ -200,6 +201,7 @@ function getStoredSubscriberDid() { */ function clearSubscriberDid() { try { + // biome-ignore lint/suspicious/noDocumentCookie: back-compat with older browsers document.cookie = "sequoia_did=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; SameSite=Lax; Secure"; } catch { @@ -229,7 +231,7 @@ function consumeReturnParams() { changed = true; } - if (did && did.startsWith("did:")) { + if (did?.startsWith("did:")) { storeSubscriberDid(did); url.searchParams.delete("sequoia_did"); changed = true; diff --git a/packages/cli/src/lib/markdown.ts b/packages/cli/src/lib/markdown.ts index 1cfaed5..b9ece3b 100644 --- a/packages/cli/src/lib/markdown.ts +++ b/packages/cli/src/lib/markdown.ts @@ -379,17 +379,20 @@ export function updateFrontmatterWithAtUri( } export function stripMarkdownForText(markdown: string): string { - return markdown - .replace(/#{1,6}\s/g, "") // Remove headers - .replace(/\*\*([^*]+)\*\*/g, "$1") // Remove bold - .replace(/\*([^*]+)\*/g, "$1") // Remove italic - .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Remove links, keep text - .replace(/`{3}[\s\S]*?`{3}/g, "") // Remove code blocks - .replace(/`([^`]+)`/g, "$1") // Remove inline code formatting - .replace(/!\[.*?\]\(.*?\)/g, "") // Remove images - .replace(/\n{3,}/g, "\n\n") // Normalize multiple newlines - .replace(/(~{1,2})([^~]+?)\1/g, "$2") // Remove strikethrough for ~text~ and ~~text~~ - .replace(//g, "") // Remove any html comments - .replace(/^\s*\[\^[^\]]+\]:[^\r\n]*(?:\r?\n[ \t]+[^\r\n]*)*/gm, "") // Remove any footnotes - .trim(); + return ( + markdown + .replace(/#{1,6}\s/g, "") // Remove headers + .replace(/\*\*([^*]+)\*\*/g, "$1") // Remove bold + .replace(/\*([^*]+)\*/g, "$1") // Remove italic + // Must run before link regex: image syntax ![alt](url) shares [alt](url) with links + .replace(/!\[.*?\]\(.*?\)/g, "") // Remove images + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Remove links, keep text + .replace(/`{3}[\s\S]*?`{3}/g, "") // Remove code blocks + .replace(/`([^`]+)`/g, "$1") // Remove inline code formatting + .replace(/\n{3,}/g, "\n\n") // Normalize multiple newlines + .replace(/(~{1,2})([^~]+?)\1/g, "$2") // Remove strikethrough for ~text~ and ~~text~~ + .replace(//g, "") // Remove any html comments + .replace(/^\s*\[\^[^\]]+\]:[^\r\n]*(?:\r?\n[ \t]+[^\r\n]*)*/gm, "") // Remove any footnotes + .trim() + ); } diff --git a/packages/cli/test/markdown.test.ts b/packages/cli/test/markdown.test.ts index 905df9d..2e50fe7 100644 --- a/packages/cli/test/markdown.test.ts +++ b/packages/cli/test/markdown.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "bun:test"; -import { parseFrontmatter, resolvePostPath } from "../src/lib/markdown"; +import { + parseFrontmatter, + resolvePostPath, + stripMarkdownForText, +} from "../src/lib/markdown"; import type { BlogPost } from "../src/lib/types"; describe("parseFrontmatter", () => { @@ -415,3 +419,76 @@ describe("resolvePostPath", () => { ); }); }); + +describe("stripMarkdownForText", () => { + it("removes h1 through h6 headers", () => { + expect( + stripMarkdownForText("# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6"), + ).toBe("H1\nH2\nH3\nH4\nH5\nH6"); + }); + + it("removes bold formatting", () => { + expect(stripMarkdownForText("This is **bold** text.")).toBe( + "This is bold text.", + ); + }); + + it("removes italic formatting", () => { + expect(stripMarkdownForText("This is *italic* text.")).toBe( + "This is italic text.", + ); + }); + + it("removes links but keeps link text", () => { + expect(stripMarkdownForText("[click here](https://example.com)")).toBe( + "click here", + ); + }); + + it("removes fenced code blocks", () => { + const input = "before\n```\nconst x = 1;\n```\nafter"; + expect(stripMarkdownForText(input)).toBe("before\n\nafter"); + }); + + it("removes inline code formatting", () => { + expect(stripMarkdownForText("Run `npm install` to install.")).toBe( + "Run npm install to install.", + ); + }); + + it("removes images", () => { + expect(stripMarkdownForText("before ![alt text](image.png) after")).toBe( + "before after", + ); + }); + + it("normalizes three or more consecutive newlines to two", () => { + expect(stripMarkdownForText("line 1\n\n\n\nline 2")).toBe( + "line 1\n\nline 2", + ); + }); + + it("removes single tilde strikethrough", () => { + expect(stripMarkdownForText("~strikethrough~")).toBe("strikethrough"); + }); + + it("removes double tilde strikethrough", () => { + expect(stripMarkdownForText("~~strikethrough~~")).toBe("strikethrough"); + }); + + it("removes HTML comments but preserves -- outside comments", () => { + expect(stripMarkdownForText("a -- b e -- f")).toBe( + "a -- b e -- f", + ); + }); + + it("removes footnote definitions but not inline references", () => { + const input = + "Text with[^1] a reference.\n\n[^1]: The footnote definition."; + expect(stripMarkdownForText(input)).toBe("Text with[^1] a reference."); + }); + + it("trims leading and trailing whitespace", () => { + expect(stripMarkdownForText("\n\nhello\n\n")).toBe("hello"); + }); +});