Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "readm3-desktop",
"productName": "readm3",
"version": "0.13.1",
"version": "0.13.2",
"private": true,
"description": "readm3 as a desktop app: a plain, fast text editor for Markdown and code.",
"author": "Profullstack, Inc. <hello@profullstack.com>",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/readm3",
"version": "0.13.1",
"version": "0.13.2",
"description": "Markdown editing and sharing for people and teams. Web/PWA, CLI, API and MCP with version history, view/edit links, and a terminal reader.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -43,6 +43,7 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.30.0",
"@profullstack/footer": "latest",
"@profullstack/hqtui": "^0.5.0",
"@profullstack/synconfig": "^0.1.3",
"highlight.js": "^11.12.0",
Expand Down
2 changes: 2 additions & 0 deletions site/assets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ pre.tree { color: var(--fg); }
.site-foot nav { display: flex; flex-wrap: wrap; gap: 0.4rem 1.1rem; }
.site-foot a { color: var(--page-dim); text-decoration: none; }
.site-foot a:hover { color: var(--page-fg); text-decoration: underline; }
/* More specific than the footer package's own .pfs-footer rules, which come later in the page. */
footer.pfs-footer { --pfs-footer-muted: var(--page-dim); border-top-color: var(--page-line); }

/* --------------------------------------------------------------- narrow -- */

Expand Down
14 changes: 10 additions & 4 deletions site/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { escape, toHtml } from "./html.ts";
import { createHash } from "node:crypto";
import { flatten, type Entry } from "../src/tree.ts";
import { VERSION } from "../src/cli.ts";
import { footerHtml } from "@profullstack/footer";
import { LANGUAGES } from "../src/code.ts";

const here = dirname(fileURLToPath(import.meta.url));
Expand All @@ -30,11 +31,15 @@ const CP_PROJECT = "528372c9-c79a-425f-9f80-f9fe44c5865e";
const CP_SLOT = "b918cc2d-4bd9-45e4-9b9e-52955b4e2e54";

/*
* The site's own footer: one line. The network links (search, ring, publish)
* come from the pit.world global footer, which site/server.ts appends to every
* page at request time.
* The site's own footer: one line, then the shared Profullstack footer
* (@profullstack/footer: copyright and the webring), baked in here and
* re-rendered from the package's @latest template on each request by
* site/server.ts (from dist/pfs-footer.json). The pit.world global footer is
* appended after both at request time.
*/
const FOOTER = `<footer class="site-foot"><span>&copy; ${new Date().getFullYear()} readm3 &middot; MIT</span><nav aria-label="Footer"><a href="/docs">Docs</a><a href="/sharing">Sharing &amp; API</a><a href="${REPO}">Source</a><a href="${NPM}">npm</a></nav></footer>`;
const FOOTER_OPTIONS = { site: "https://readm3.com/", links: [] };
const PFS_FOOTER = await footerHtml(FOOTER_OPTIONS);
const FOOTER = `<footer class="site-foot"><span>readm3 &middot; MIT</span><nav aria-label="Footer"><a href="/docs">Docs</a><a href="/sharing">Sharing &amp; API</a><a href="${REPO}">Source</a><a href="${NPM}">npm</a></nav></footer>\n${PFS_FOOTER}`;

/* -------------------------------------------------------------- colors --- */

Expand Down Expand Up @@ -485,6 +490,7 @@ write("robots.txt", robots);
write("sitemap.xml", sitemap);
write("llms.txt", llms);
write("favicon.svg", favicon);
write("pfs-footer.json", JSON.stringify(FOOTER_OPTIONS));
write(".well-known/openwebring.json", JSON.stringify({
openwebring: "0.1",
site: { url: "https://readm3.com/", name: "readm3" },
Expand Down
29 changes: 26 additions & 3 deletions site/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* Public pages stay static. Account APIs use verified email identities and
* sessions persisted on the mounted database volume.
*/
import { existsSync, statSync } from "node:fs";
import { existsSync, readFileSync, statSync } from "node:fs";
import { footerHtml } from "@profullstack/footer";
import { dirname, join, normalize, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { Accounts } from "../server/accounts.ts";
Expand Down Expand Up @@ -67,14 +68,36 @@ async function pitFooterHtml(): Promise<string> {
return pitFooter.html;
}

/*
* The shared Profullstack footer (@profullstack/footer). site/build.ts bakes it
* into each page and writes its options to dist/pfs-footer.json; here the baked
* copy is swapped for one rendered from the package's @latest template (cached
* an hour inside the package). Any failure keeps the baked copy.
*/
const footerOptionsFile = join(dist, "pfs-footer.json");
const footerOptions = existsSync(footerOptionsFile)
? (JSON.parse(readFileSync(footerOptionsFile, "utf8")) as Parameters<typeof footerHtml>[0])
: null;
const BAKED_FOOTER = /<footer class="pfs-footer"[\s\S]*?<\/footer>/;

async function latestPfsFooter(html: string): Promise<string> {
if (!footerOptions || !BAKED_FOOTER.test(html)) return html;
try {
const footer = await footerHtml(footerOptions);
return html.replace(BAKED_FOOTER, () => footer);
} catch {
return html;
}
}

async function pageBody(file: string, withPitFooter: boolean): Promise<{ body: BodyInit; type?: string }> {
// The curl installer reads as text in a browser instead of downloading.
if (file.endsWith(".sh")) return { body: Bun.file(file), type: "text/plain; charset=utf-8" };
if (!withPitFooter || !file.endsWith(".html")) return { body: Bun.file(file) };
const footer = await pitFooterHtml();
const html = await Bun.file(file).text();
const html = await latestPfsFooter(await Bun.file(file).text());
const at = html.lastIndexOf("</body>");
if (!footer || at < 0 || html.includes("data-pit-footer")) return { body: Bun.file(file) };
if (!footer || at < 0 || html.includes("data-pit-footer")) return { body: html, type: "text/html; charset=utf-8" };
return { body: html.slice(0, at) + footer + html.slice(at), type: "text/html; charset=utf-8" };
}

Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { run, type ViewerOptions } from "./viewer.ts";
import { isCloudCommand, cloudMain, CLOUD_USAGE } from "./cloud-cli.ts";
import { isDesktopCommand, desktopMain, installedDesktop, prefersDesktop, DESKTOP_USAGE } from "./desktop-cli.ts";

export const VERSION = "0.13.1";
export const VERSION = "0.13.2";

/** The help text, exported so a test can hold it to the flags it documents. */
export const USAGE = `readm3 — a plain text editor for Markdown and code
Expand Down
Loading