update --css
This commit is contained in:
parent
74e84b1ab4
commit
77e4ecb2fb
122
cli.js
122
cli.js
|
@ -1,24 +1,24 @@
|
|||
#!/usr/bin/env node
|
||||
import { readFileSync } from "fs";
|
||||
import { readFileSync, existsSync } from "fs";
|
||||
import path from "path";
|
||||
import fs from "vinyl-fs";
|
||||
import map from "map-stream";
|
||||
import yargs from "yargs";
|
||||
import CleanCSS from "clean-css";
|
||||
import toHTML, { BASE_CSS, html } from "./html.js";
|
||||
import * as CSS from "./css.js";
|
||||
import toHTML, { 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), "utf-8");
|
||||
})
|
||||
.scriptName("gmi-web")
|
||||
.command("$0 [files..]", "Convert text/gemini to text/html.", (yargs) =>
|
||||
yargs
|
||||
.example("$0 --body < ~/my-capsule/index.gmi")
|
||||
.example("$0 --html en $(find ~/my-capsule -name '*.gmi')")
|
||||
.example(
|
||||
"$0 --foreground '#000000' --background '#EEEEEE' --html en < doc.gmi"
|
||||
)
|
||||
.example("$0 --body < ~/my-capsule/index.gmi")
|
||||
.example("$0 --image jpg --audio mp3 --image png --body < doc.gmi")
|
||||
.epilog("See the gmi-web(1) man page for more information")
|
||||
)
|
||||
|
@ -44,8 +44,7 @@ const cli = yargs(process.argv.slice(2))
|
|||
type: "boolean",
|
||||
},
|
||||
css: {
|
||||
choices: ["gmi.css", "base", "none"],
|
||||
default: "gmi.css",
|
||||
default: "full",
|
||||
requiresArg: true,
|
||||
},
|
||||
charset: {
|
||||
|
@ -54,99 +53,70 @@ const cli = yargs(process.argv.slice(2))
|
|||
default: "utf-8",
|
||||
requiresArg: true,
|
||||
},
|
||||
image: {
|
||||
type: "array",
|
||||
requiresArg: true,
|
||||
},
|
||||
audio: {
|
||||
type: "array",
|
||||
requiresArg: true,
|
||||
},
|
||||
video: {
|
||||
type: "array",
|
||||
requiresArg: true,
|
||||
},
|
||||
});
|
||||
|
||||
cli.options({
|
||||
image: {
|
||||
type: "array",
|
||||
requiresArg: true,
|
||||
},
|
||||
audio: {
|
||||
type: "array",
|
||||
requiresArg: true,
|
||||
},
|
||||
video: {
|
||||
type: "array",
|
||||
requiresArg: true,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: automatically pull these in from gmi.css (also for gmi.css(5))
|
||||
const GMI_CSS_VARS = [
|
||||
"body-width",
|
||||
"foreground",
|
||||
"background",
|
||||
"p-size",
|
||||
"p-indent",
|
||||
"p-line-height",
|
||||
"a-size",
|
||||
"pre-size",
|
||||
"pre-line-height",
|
||||
"h1-size",
|
||||
"h2-size",
|
||||
"h3-size",
|
||||
"heading-line-height",
|
||||
"ul-size",
|
||||
"ul-line-height",
|
||||
"blockquote-size",
|
||||
"blockquote-line-height",
|
||||
"mono",
|
||||
"serif",
|
||||
"sans-serif",
|
||||
].map((key) => {
|
||||
cli.option(key);
|
||||
const CSS_VARS = CSS.rootVariables(CSS.FULL);
|
||||
Object.keys(CSS_VARS).map((key) => {
|
||||
cli.option(key, { default: CSS_VARS[key] });
|
||||
cli.conflicts(key, "body");
|
||||
return key;
|
||||
});
|
||||
|
||||
const argv = cli
|
||||
.conflicts("author", "body")
|
||||
.conflicts("description", "body")
|
||||
.conflicts("descriptions", "body")
|
||||
.conflicts("html", "body")
|
||||
.group(["html", "body"], "Core:")
|
||||
.group(["author", "description", "css", "dir"], "HTML:")
|
||||
.group(["author", "descriptions", "css", "dir"], "HTML:")
|
||||
.group(["image", "audio", "video"], "Inline Media:")
|
||||
.group(GMI_CSS_VARS, "gmi.css:")
|
||||
.group(Object.keys(CSS_VARS), "CSS:")
|
||||
.alias("html", "language")
|
||||
.alias("html", "lang")
|
||||
.coerce("css", (arg) => {
|
||||
if (arg === "full") {
|
||||
return CSS.stringify(CSS.FULL, { compress: true });
|
||||
} else if (arg === "core") {
|
||||
return CSS.stringify(CSS.CORE, { compress: true });
|
||||
} else if (arg !== "none" && arg !== "") {
|
||||
if (existsSync(path.resolve(arg))) {
|
||||
return CSS.resolve(arg, { compress: true });
|
||||
} else {
|
||||
return CSS.parse(arg);
|
||||
}
|
||||
}
|
||||
})
|
||||
.showHelpOnFail(true)
|
||||
.help().argv;
|
||||
|
||||
let styles = "";
|
||||
if (argv.css === "gmi.css") {
|
||||
styles = GMI_CSS_VARS.reduce((style, key) => {
|
||||
if (argv[key]) {
|
||||
style += `--${key}: ${argv[key]};`;
|
||||
}
|
||||
return style;
|
||||
}, styles);
|
||||
|
||||
argv.css = new CleanCSS().minify(
|
||||
readFileSync(
|
||||
// TODO import.meta.resolve is supposed to accomplish this without "path"
|
||||
path.resolve(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
"./gmi.css"
|
||||
),
|
||||
"utf-8"
|
||||
)
|
||||
).styles;
|
||||
} else if (argv.css === "base") {
|
||||
argv.css = BASE_CSS;
|
||||
} else {
|
||||
argv.css = "";
|
||||
}
|
||||
|
||||
if (!argv.lang && !argv.body) {
|
||||
if (!argv.html && !argv.body) {
|
||||
cli.showHelp();
|
||||
console.error(`\nMissing required argument: --html or --body`);
|
||||
cli.exit(1);
|
||||
}
|
||||
|
||||
let styles = Object.keys(CSS_VARS).reduce((style, key) => {
|
||||
if (argv[key]) {
|
||||
style += `--${key}: ${argv[key]};`;
|
||||
}
|
||||
return style;
|
||||
}, "");
|
||||
|
||||
if (!argv.files) {
|
||||
let gemtext;
|
||||
try {
|
||||
gemtext = readFileSync(process.stdin.fd);
|
||||
gemtext = readFileSync(process.stdin.fd, "utf-8");
|
||||
} catch (e) {
|
||||
cli.showHelp();
|
||||
console.error("\nMissing files: pipe from stdin or provide [files..]");
|
||||
|
|
48
css.js
Normal file
48
css.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { stringify, parse } from "css";
|
||||
export { stringify, parse };
|
||||
|
||||
// TODO import.meta.resolve is supposed to accomplish this without "path"
|
||||
const GMI_CSS = path.resolve(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
"./gmi.css"
|
||||
);
|
||||
|
||||
export const CORE = parse(`p,
|
||||
a,
|
||||
pre,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
ul,
|
||||
blockquote,
|
||||
img,
|
||||
audio,
|
||||
video {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: anywhere;
|
||||
max-width: 100%;
|
||||
}`);
|
||||
|
||||
export const FULL = parse(readFileSync(GMI_CSS, "utf-8"));
|
||||
|
||||
export function rootVariables({ stylesheet }) {
|
||||
return stylesheet.rules
|
||||
.find(
|
||||
({ type, selectors }) => type === "rule" && selectors.includes(":root")
|
||||
)
|
||||
.declarations.reduce(
|
||||
(obj, { property, value }) =>
|
||||
!/^\-\-/.test(property)
|
||||
? obj
|
||||
: Object.assign(obj, { [property.replace("--", "")]: value }),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
export function resolve(arg, options) {
|
||||
return stringify(parse(readFileSync(path.resolve(arg), "utf-8")), options);
|
||||
}
|
28
gmi-web.1
28
gmi-web.1
|
@ -48,16 +48,10 @@ be used.
|
|||
Use \fB--author\fR \fINAME\fR to set the author <meta> tag on every file.
|
||||
.P
|
||||
.RE
|
||||
\fB--css\fR \fIMODE\fR
|
||||
\fB--css\fR [\fIMODE\fR|\fIFILE\fR|\fICSS\fR]
|
||||
.RS 4
|
||||
By default this will be set to \fBgmi.css\fR. Choosing \fBbase\fR will use just what
|
||||
is needed to fix issues with CSS 2.1's Normal Flow. Choosing \fBnone\fR will not
|
||||
include any style information and inline elements will be wrapped in <p>.
|
||||
.P
|
||||
.RE
|
||||
\fBgmi.css\fR
|
||||
.RS 4
|
||||
See --help or gmi.css(5) for the complete list of customizable styles.
|
||||
By default this will be set to \fBfull\fR enabling a handful of customizable
|
||||
variables to be set. See --help for the complete list.
|
||||
.P
|
||||
.RE
|
||||
.nf
|
||||
|
@ -68,7 +62,17 @@ gmi-web --html en \\
|
|||
.fi
|
||||
.RE
|
||||
.P
|
||||
\fB[--image|--audio|--video]\fR \fIEXENSIONS\fR
|
||||
.RS 4
|
||||
Choosing \fBcore\fR will use just what is needed to fix vertical layout issues
|
||||
with CSS 2.1's Normal Flow and inline elements.
|
||||
.P
|
||||
Pointing to a .css \fIFILE\fR or providing a valid \fICSS\fR string will use those
|
||||
styles.
|
||||
.P
|
||||
Choosing \fBnone\fR will not include any style information.
|
||||
.P
|
||||
.RE
|
||||
\fB[--image|--audio|--video]\fR \fIEXTENSIONS\fR
|
||||
.RS 4
|
||||
Include media extensions inline. You can provide multiple extensions per flag
|
||||
or multiple flags per extension.
|
||||
|
@ -89,10 +93,6 @@ All the options documented here and by \fB--help\fR may be captured in a .json
|
|||
file and passed to \fB--config\fR instead of as flags on the command-line.
|
||||
.P
|
||||
.RE
|
||||
.SH SEE ALSO
|
||||
.P
|
||||
\fBgmi.css\fR(5)
|
||||
.P
|
||||
.SH AUTHORS
|
||||
.P
|
||||
Maintained by Talon Poole <code@talon.computer>. Up-to-date sources can be
|
||||
|
|
|
@ -20,10 +20,6 @@ mobile-friendly fashion!
|
|||
*--body*
|
||||
Generate only the HTML for the lines of the Gemini document.
|
||||
|
||||
```
|
||||
gmi-web --body < doc.gmi
|
||||
```
|
||||
|
||||
*--html* _LANG_
|
||||
Generate a full HTML5 document with the provided _LANG_. *--dir* can be used
|
||||
to adjust the document text direction from "ltr" to "rtl".
|
||||
|
@ -35,13 +31,9 @@ gmi-web --body < doc.gmi
|
|||
|
||||
Use *--author* _NAME_ to set the author <meta> tag on every file.
|
||||
|
||||
*--css* _MODE_
|
||||
By default this will be set to *gmi.css*. Choosing *base* will use just what
|
||||
is needed to fix issues with CSS 2.1's Normal Flow. Choosing *none* will not
|
||||
include any style information and inline elements will be wrapped in <p>.
|
||||
|
||||
*gmi.css*
|
||||
See --help or gmi.css(5) for the complete list of customizable styles.
|
||||
*--css* [_MODE_|_FILE_|_CSS_]
|
||||
By default this will be set to *full* enabling a handful of customizable
|
||||
variables to be set. See --help for the complete list.
|
||||
|
||||
```
|
||||
gmi-web --html en \\
|
||||
|
@ -49,7 +41,15 @@ gmi-web --html en \\
|
|||
--background "#9EEBCF" < doc.gmi
|
||||
```
|
||||
|
||||
*[--image|--audio|--video]* _EXENSIONS_
|
||||
Choosing *core* will use just what is needed to fix vertical layout issues
|
||||
with CSS 2.1's Normal Flow and inline elements.
|
||||
|
||||
Pointing to a .css _FILE_ or providing a valid _CSS_ string will use those
|
||||
styles.
|
||||
|
||||
Choosing *none* will not include any style information.
|
||||
|
||||
*[--image|--audio|--video]* _EXTENSIONS_
|
||||
Include media extensions inline. You can provide multiple extensions per flag
|
||||
or multiple flags per extension.
|
||||
|
||||
|
@ -64,10 +64,6 @@ gmi-web --html en \\
|
|||
All the options documented here and by *--help* may be captured in a .json
|
||||
file and passed to *--config* instead of as flags on the command-line.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*gmi.css*(5)
|
||||
|
||||
# AUTHORS
|
||||
|
||||
Maintained by Talon Poole <code@talon.computer>. Up-to-date sources can be
|
||||
|
|
17
html.js
17
html.js
|
@ -33,15 +33,14 @@ ${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}">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
${options.css !== "" ? `<meta name="color-scheme" content="dark light">` : ""}
|
||||
${options.css !== "" ? `<style>${options.css}</style>` : ""}
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">${
|
||||
options.css && options.css !== ""
|
||||
? `<meta name="color-scheme" content="dark light">`
|
||||
: ""
|
||||
}${options.css && options.css !== "" ? `<style>${options.css}</style>` : ""}
|
||||
<title>${options.title}</title>${
|
||||
!options.author ? "" : `<meta name="author" content="${options.author}">`
|
||||
}${
|
||||
|
@ -70,17 +69,11 @@ function line(
|
|||
return `<img src="${href}"${titleProp}/>`;
|
||||
}
|
||||
if (audio && matchesExt(href, audio)) {
|
||||
if (css === "") {
|
||||
return `<p><audio controls src="${href}"${titleProp}></audio><p>`;
|
||||
}
|
||||
return `<audio controls src="${href}"${titleProp}></audio>`;
|
||||
}
|
||||
if (video && matchesExt(href, video))
|
||||
return `<video controls src="${href}"${titleProp}/></video>`;
|
||||
|
||||
if (css === "") {
|
||||
return `<p><a href="${href}">${title ? escape(title) : href}</a></p>`;
|
||||
}
|
||||
return `<a href="${href}">${title ? escape(title) : href}</a>`;
|
||||
}
|
||||
|
||||
|
|
29
package-lock.json
generated
29
package-lock.json
generated
|
@ -25,6 +25,11 @@
|
|||
"buffer-equal": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
|
@ -127,6 +132,21 @@
|
|||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"css": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
|
||||
"integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.4",
|
||||
"source-map": "^0.6.1",
|
||||
"source-map-resolve": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
|
||||
},
|
||||
"define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
|
@ -549,6 +569,15 @@
|
|||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"source-map-resolve": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
|
||||
"integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
|
||||
"requires": {
|
||||
"atob": "^2.1.2",
|
||||
"decode-uri-component": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"stream-shift": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"clean-css": "^5.0.1",
|
||||
"css": "^3.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"map-stream": "0.0.7",
|
||||
"vinyl-fs": "^3.0.3",
|
||||
|
|
Loading…
Reference in a new issue