diff --git a/cli.js b/cli.js index 9abfe0b..40631f0 100755 --- a/cli.js +++ b/cli.js @@ -9,7 +9,7 @@ import toHTML, { BASE_CSS, html } from "./html.js"; const cli = yargs(process.argv.slice(2)) .config("config", function (path) { - return JSON.parse(readFileSync(path)) + return JSON.parse(readFileSync(path)); }) .scriptName("gmi-web") .command("$0 [files..]", "Convert text/gemini to text/html.", (yargs) => @@ -30,15 +30,23 @@ const cli = yargs(process.argv.slice(2)) type: "string", requiresArg: true, }, - charset: { + author: { type: "string", - default: "utf-8", requiresArg: true, }, + descriptions: { + type: "boolean", + }, css: { choices: ["gmi.css", "base", "none"], default: "gmi.css", }, + charset: { + type: "string", + hidden: true, + default: "utf-8", + requiresArg: true, + }, }); cli.options({ @@ -85,10 +93,13 @@ const GMI_CSS_VARS = [ }); const argv = cli + .conflicts("author", "body") + .conflicts("description", "body") .conflicts("html", "body") - .group(["html", "css", "body"], "Core:") - .group(GMI_CSS_VARS, "gmi.css:") + .group(["html", "body"], "Core:") + .group(["author", "description", "css"], "HTML:") .group(["image", "audio", "video"], "Inline Media:") + .group(GMI_CSS_VARS, "gmi.css:") .alias("html", "language") .alias("html", "lang") .showHelpOnFail(true) @@ -100,7 +111,7 @@ if (argv.css === "gmi.css") { if (argv[key]) { style += `--${key}: ${argv[key]};`; } - return style + return style; }, styles); argv.css = new CleanCSS().minify( diff --git a/gmi-web.1 b/gmi-web.1 index c65e5ad..0e7cd7e 100644 --- a/gmi-web.1 +++ b/gmi-web.1 @@ -31,6 +31,18 @@ Generate a full HTML5 document with the provided \fILANG\fR. \fBgmi-web\fR --html en < doc.gmi .P .RE +\fB--descriptions\fR +.RS 4 +If this flag is set the first non-empty text line of each Gemini file will +be used for the description tag. This will be truncated to 200 +characters using an ellipsis. +.P +.RE +\fB--author\fR +.RS 4 +If provided this will be used for the author tag on every file. +.P +.RE \fB--body\fR .RS 4 Generate only the HTML for the lines of the Gemini document. diff --git a/gmi-web.1.scd b/gmi-web.1.scd index 65d3dcd..43bf99f 100644 --- a/gmi-web.1.scd +++ b/gmi-web.1.scd @@ -22,6 +22,14 @@ and mobile-friendly fashion! *gmi-web* --html en < doc.gmi +*--descriptions* + If this flag is set the first non-empty text line of each Gemini file will + be used for the description tag. This will be truncated to 200 + characters using an ellipsis. + +*--author* + If provided this will be used for the author tag on every file. + *--body* Generate only the HTML for the lines of the Gemini document. diff --git a/html.js b/html.js index 451fac4..484a38a 100644 --- a/html.js +++ b/html.js @@ -2,12 +2,20 @@ import escape from "escape-html"; export const GMI_REGEX = /^((=>\s?(?[^\s]+)(\s(?.+))?)|(?<pre>```\s?(?<alt>.+)?)|(###\s?(?<h3>.+))|(##\s?(?<h2>.+))|(#\s?(?<h1>.+))|(\*\s?(?<li>.+))|(>\s?(?<quote>.+))|(?<text>(.+)?))$/; +const truncate = (text, limit) => + text.length > limit ? `${text.substring(0, limit)}...` : text; export function html(file, options) { const tokens = file.contents .toString("utf8") .split("\n") .map((line) => GMI_REGEX.exec(line).groups); + let description = options.descriptions + ? tokens.find((token) => { + return token.text && token.text !== ""; + }) + : false; + if (options.body) return body(tokens, options); return `<!DOCTYPE html> @@ -15,7 +23,7 @@ export function html(file, options) { <head>${head( Object.assign(options, { title: tokens[0].h1, - // TODO: description, canonical + description: description && truncate(description.text, 200), }) )}</head> <body> @@ -27,6 +35,7 @@ ${body(tokens, options)} export const BASE_CSS = "p,a,pre,h1,h2,h3,ul,blockquote,img,audio,video{display:block;max-width:100%;margin:0;padding:0;overflow-wrap:anywhere;}"; + export function head(options) { return ` <meta charset="${options.charset}"> @@ -34,13 +43,15 @@ export function head(options) { ${options.css !== "" ? `<meta name="color-scheme" content="dark light">` : ""} ${options.css !== "" ? `<style>${options.css}</style>` : ""} <title>${options.title}${ + !options.author ? "" : `` + }${ !options.description ? "" - : `` + : `` }${ !options.canonical ? "" - : `` + : `` } `; }