make format

This commit is contained in:
Talon Poole 2021-01-28 06:16:46 +00:00
parent 24c18dd9a9
commit c71728c713
7 changed files with 357 additions and 198 deletions

View file

@ -1,3 +1,19 @@
build:
PREFIX=/usr/local
INSTALLDIR=$(DESTDIR)$(PREFIX)
MANDIR=$(INSTALLDIR)/share/man
format:
./node_modules/prettier/bin-prettier.js --write gmi.css
./node_modules/prettier/bin-prettier.js --write cli.js
./node_modules/prettier/bin-prettier.js --write gmi.js
gmi-web.1: gmi-web.1.scd
scdoc < $< > $@
install: gmi-web.1
sudo npm link
install -Dm644 gmi-web.1 $(MANDIR)/man1/gmi-web.1
minify:
cat gmi.css | ./node_modules/minify/bin/minify.js --css > gmi.min.css
cat gmi.js | ./node_modules/minify/bin/minify.js --js > gmi.min.js

View file

@ -1,4 +1,5 @@
# gmi-web
![CC0](https://licensebuttons.net/p/zero/1.0/80x15.png)
**Vision**: Provide the lowest common denominator between HTML/CSS/JS and Gemini.
@ -8,14 +9,15 @@
Check out the annotated [example.html](https://gmi.eattherich.club/example.html)!
Due to the ambiguity of HTML several translations from Gemini exist in the wild. I propose the following standard:
```
````
<ul> ↔ *
<blockquote> ↔ >
<pre> ↔ ```
<a> ↔ =>
<pre> ↔ ```
<a> ↔ =>
<h[1-3]> ↔ #[##]
<p>
```
````
The `<a>` for a link should be presented without any parent elements. Many implementations use `<div>` to enforce "block" styling as opposed to the default "inline" which renders the link next to the previous block instead of below it. But the nested markup adds an unnecessary layer of indirection in semantics and when parsing. `<a style="display: block;">` has the same effect (gmi.css uses this).

14
cli.js Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env node
require("yargs")
.scriptName("gmi-web")
.command(
"$0",
"A bridge between Gemini and HTML",
(yargs) => yargs,
// TODO
(argv) => {
// TODO
}
)
.help().argv;

View file

@ -34,7 +34,7 @@ including newlines
</ul>
<p><br></p>
<p>Optionally, if a link is consumable by "img", "audio" or "video" you may insert the media inline. Images and video should be styled to have "max-width: 100%;" so they don't overflow the body. It's a good idea to also include the "controls" attribute for videos and audio. "audio" tags require "display: block;" just like "a".</p>
<img src="https://www.learningcontainer.com/wp-content/uploads/2020/08/Large-Sample-png-Image-download-for-Testing.png"></img>
<img src="https://www.learningcontainer.com/wp-content/uploads/2020/08/Large-Sample-png-Image-download-for-Testing.png"/>
<video controls src="https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4"></video>
<audio controls src="https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3"></audio>
<p><br></p>

23
gmi-web.1 Normal file
View file

@ -0,0 +1,23 @@
.\" Generated by scdoc 1.10.1
.\" Complete documentation for this program is not available as a GNU info page
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.\" Begin generated content:
.TH "gmi-web" "1" "2021-01-28"
.P
.SH NAME
.P
gmi-web - a bridge between Gemini and HTML
.P
.SH SYNOPSIS
.P
.SH DESCRIPTION
.P
.SH SEE ALSO
.P
.SH AUTHORS
.P
Maintained by Talon Poole <code@talon.computer>. Up-to-date sources can be
found at https://codeberg.org/talon/gmi-web

194
gmi.css
View file

@ -1,112 +1,124 @@
/* gmi.css is CCO */
:root {
--foreground: black;
--background: white;
--p-size: 1.25rem;
--p-indent: 0rem;
--a-size: var(--p-size);
--pre-size: 1rem;
--h1-size: 3rem;
--h2-size: 2.25rem;
--h3-size: 1.5rem;
--ul-size: var(--p-size);
--blockquote-size: var(--p-size);
--mono: Consolas, monaco, monospace;
--serif: georgia, times, serif;
--sans-serif: -apple-system, BlinkMacSystemFont, 'avenir next', avenir, helvetica, 'helvetica neue', ubuntu, roboto, noto, 'segoe ui', arial, sans-serif;
--foreground: black;
--background: white;
--p-size: 1.25rem;
--p-indent: 0rem;
--a-size: var(--p-size);
--pre-size: 1rem;
--h1-size: 3rem;
--h2-size: 2.25rem;
--h3-size: 1.5rem;
--ul-size: var(--p-size);
--blockquote-size: var(--p-size);
--mono: Consolas, monaco, monospace;
--serif: georgia, times, serif;
--sans-serif: -apple-system, BlinkMacSystemFont, "avenir next", avenir,
helvetica, "helvetica neue", ubuntu, roboto, noto, "segoe ui", arial,
sans-serif;
}
body {
max-width: 48rem;
padding: .5rem;
margin: 0 auto;
max-width: 48rem;
padding: 0.5rem;
margin: 0 auto;
}
p, a, pre, h1, h2, h3, ul, blockquote, img, audio, video {
display: block;
max-width: 100%;
margin: 0;
padding: 0;
overflow-wrap: anywhere
p,
a,
pre,
h1,
h2,
h3,
ul,
blockquote,
img,
audio,
video {
display: block;
max-width: 100%;
margin: 0;
padding: 0;
overflow-wrap: anywhere;
}
h1,
h2,
h3 {
font-family: var(--sans-serif);
line-height: 1.25;
font-family: var(--sans-serif);
line-height: 1.25;
}
p {
font-size: var(--p-size);
font-family: var(--serif);
text-indent: var(--p-indent);
line-height: 1.5;
font-size: var(--p-size);
font-family: var(--serif);
text-indent: var(--p-indent);
line-height: 1.5;
}
a::before {
font-size: var(--p-size);
font-family: var(--mono);
content: "⇒";
padding-right: .25rem;
vertical-align: middle
font-size: var(--p-size);
font-family: var(--mono);
content: "⇒";
padding-right: 0.25rem;
vertical-align: middle;
}
a {
font-size: var(--p-size);
font-family: var(--serif);
text-decoration: none;
font-size: var(--p-size);
font-family: var(--serif);
text-decoration: none;
}
pre {
font-size: var(--pre-size);
font-family: var(--mono);
line-height: 1;
padding: 1.25rem;
overflow-y: auto;
font-size: var(--pre-size);
font-family: var(--mono);
line-height: 1;
padding: 1.25rem;
overflow-y: auto;
}
h1 {
font-size: var(--h1-size);
font-size: var(--h1-size);
}
h2 {
font-size: var(--h2-size);
font-size: var(--h2-size);
}
h3 {
font-size: var(--h3-size);
font-size: var(--h3-size);
}
ul {
font-size: var(--p-size);
font-family: var(--serif);
line-height: 1.25;
list-style-type: none;
font-size: var(--p-size);
font-family: var(--serif);
line-height: 1.25;
list-style-type: none;
}
li::before {
font-size: var(--p-size);
font-family: var(--mono);
content: "*";
vertical-align: middle;
padding-right: .5rem;
font-size: var(--p-size);
font-family: var(--mono);
content: "*";
vertical-align: middle;
padding-right: 0.5rem;
}
blockquote {
font-size: var(--p-size);
font-family: var(--serif);
line-height: 1.5;
padding-left: .75rem;
font-size: var(--p-size);
font-family: var(--serif);
line-height: 1.5;
padding-left: 0.75rem;
}
pre+blockquote {
padding-top: .5rem;
padding-bottom: .5rem;
pre + blockquote {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
/* foreground and background colors */
html,
html,
body,
h1,
h2,
@ -117,48 +129,48 @@ ul,
blockquote,
pre::selection,
pre::-moz-selection {
color: var(--foreground);
background-color: var(--background);
color: var(--foreground);
background-color: var(--background);
}
blockquote {
border-left: .5rem solid var(--foreground);
border-left: 0.5rem solid var(--foreground);
}
pre,
::selection,
::-moz-selection,
a:hover {
color: var(--background);
background-color: var(--foreground);
color: var(--background);
background-color: var(--foreground);
}
/* when system preference is "dark" invert foreground and background colors */
@media (prefers-color-scheme: dark) {
html,
body,
h1,
h2,
h3,
p,
a,
ul,
blockquote,
pre::selection,
pre::-moz-selection {
color: var(--background);
background-color: var(--foreground);
}
html,
body,
h1,
h2,
h3,
p,
a,
ul,
blockquote,
pre::selection,
pre::-moz-selection {
color: var(--background);
background-color: var(--foreground);
}
blockquote {
border-left: .5rem solid var(--background);
}
blockquote {
border-left: 0.5rem solid var(--background);
}
pre,
::selection,
::-moz-selection,
a:hover {
color: var(--foreground);
background-color: var(--background);
}
pre,
::selection,
::-moz-selection,
a:hover {
color: var(--foreground);
background-color: var(--background);
}
}

294
gmi.js
View file

@ -1,142 +1,234 @@
/* gmi.js is licensed under CC0 */
class Gemini {
static syntax = { P: "", A: "=>", UL: "*", BLOCKQUOTE: ">", PRE: "```", H1: "#", H2: "##", H3: "###", }
static syntax = {
P: "",
A: "=>",
UL: "*",
BLOCKQUOTE: ">",
PRE: "```",
H1: "#",
H2: "##",
H3: "###",
};
static line(line, type) {
if (typeof line === "string") line = {content: line, type: type || "P"}
let dom = Gemini.render(line).dom
if (typeof line === "string") line = { content: line, type: type || "P" };
let dom = Gemini.render(line).dom;
return {
get dom() { return dom },
get type() { return this.dom.nodeName },
set type(type) { dom = Gemini.render({dom: this.dom, type, content: Gemini.contentFrom(this.dom)}).dom },
get content() { return Gemini.contentFrom(dom) },
set content(content) { Gemini.render({dom, type: dom.nodeName, content}) },
get editable() { return this.dom.contentEditable === "true" },
set editable(value) { Gemini.render({dom: this.dom, type: this.type, content: this.content, editable: value}) },
delete() { return this.dom.remove() },
get gmi() {
const syntax = Gemini.syntax[this.type]
const content = Gemini.contentFrom(this.dom).replace(/\n?$/, "")
get dom() {
return dom;
},
get type() {
return this.dom.nodeName;
},
set type(type) {
dom = Gemini.render({
dom: this.dom,
type,
content: Gemini.contentFrom(this.dom),
}).dom;
},
get content() {
return Gemini.contentFrom(dom);
},
set content(content) {
Gemini.render({ dom, type: dom.nodeName, content });
},
get editable() {
return this.dom.contentEditable === "true";
},
set editable(value) {
Gemini.render({
dom: this.dom,
type: this.type,
content: this.content,
editable: value,
});
},
delete() {
return this.dom.remove();
},
get gmi() {
const syntax = Gemini.syntax[this.type];
const content = Gemini.contentFrom(this.dom).replace(/\n?$/, "");
switch (this.type.toUpperCase()) {
case "PRE":
return `${syntax}\n${content}\n${syntax}`
break
return `${syntax}\n${content}\n${syntax}`;
break;
default:
return content.split("\n").map(line =>
`${syntax !== "" ? syntax + " " : ""}${line}`
).join("\n")
return content
.split("\n")
.map((line) => `${syntax !== "" ? syntax + " " : ""}${line}`)
.join("\n");
}
},
get before() { return Gemini.line(this.dom.previousElementSibling) },
set before(line) { this.before.dom.after(line.dom) },
get after() { return Gemini.line(this.dom.nextElementSibling) },
set after(line) { this.after.dom.before(line.dom) },
}
get before() {
return Gemini.line(this.dom.previousElementSibling);
},
set before(line) {
this.before.dom.after(line.dom);
},
get after() {
return Gemini.line(this.dom.nextElementSibling);
},
set after(line) {
this.after.dom.before(line.dom);
},
};
}
static render(line) {
if (line.dom && line.dom.nodeName !== line.type) {
const replacement = document.createElement(line.type)
line.dom.replaceWith(replacement)
line.dom = replacement
const replacement = document.createElement(line.type);
line.dom.replaceWith(replacement);
line.dom = replacement;
} else if (line.nodeName) {
line = {
dom: line,
type: line.nodeName,
dom: line,
type: line.nodeName,
content: Gemini.contentFrom(line),
editable: line.contentEditable
}
editable: line.contentEditable,
};
} else {
line.dom = line.dom || document.createElement(line.type || "P")
line.dom = line.dom || document.createElement(line.type || "P");
}
line.dom.contentEditable = line.editable || "inherit"
line.dom.contentEditable = line.editable || "inherit";
switch (line.type.toUpperCase()) {
case "A":
const {href, content} = Gemini.link(line.content)
line.dom.innerHTML = line.editable && href !== content ? `${href} ${content}` : content
line.dom.href = href
break
case "UL":
line.dom.innerHTML = line.content.split("\n").map(content => content.length > 0 ?
`<li>${content}</li>` : ""
).join("\n")
break
case "BLOCKQUOTE":
line.dom.innerHTML = line.content.split("\n").map(content =>
`<div>${content}</div>`
).join("\n")
break
const { href, content } = Gemini.link(line.content);
line.dom.innerHTML =
line.editable && href !== content ? `${href} ${content}` : content;
line.dom.href = href;
break;
case "UL":
line.dom.innerHTML = line.content
.split("\n")
.map((content) => (content.length > 0 ? `<li>${content}</li>` : ""))
.join("\n");
break;
case "BLOCKQUOTE":
line.dom.innerHTML = line.content
.split("\n")
.map((content) => `<div>${content}</div>`)
.join("\n");
break;
case "PRE":
line.dom.textContent = line.content
break
line.dom.textContent = line.content;
break;
default:
line.dom.innerHTML = line.content.replace(/\n+/g, "<br>")
line.dom.innerHTML = line.content.replace(/\n+/g, "<br>");
}
return line
return line;
}
static contentFrom(dom) {
switch (dom.nodeName.toUpperCase()) {
case "BLOCKQUOTE":
return Array.from(dom.childNodes).map(child =>
child.textContent
).join("\n")
break
case "UL":
return Array.from(dom.children).map(child =>
child.textContent
).join("\n")
break
case "A":
const {href, content} = Gemini.link(dom.textContent)
return `${href || dom.href} ${content}`
break
case "PRE":
return dom.textContent
break
return Array.from(dom.childNodes)
.map((child) => child.textContent)
.join("\n");
break;
case "UL":
return Array.from(dom.children)
.map((child) => child.textContent)
.join("\n");
break;
case "A":
const { href, content } = Gemini.link(dom.textContent);
return `${href || dom.href} ${content}`;
break;
case "PRE":
return dom.textContent;
break;
default:
return dom.innerHTML.replace(/<br>/g, "\n")
return dom.innerHTML.replace(/<br>/g, "\n");
}
}
// TODO: rename/move/idk this is awk
static link(content = "") { return /((?<href>[^\s]+\/\/[^\s]+)\s)?(?<content>.+)/.exec(content).groups }
static link(content = "") {
return /((?<href>[^\s]+\/\/[^\s]+)\s)?(?<content>.+)/.exec(content).groups;
}
constructor(root) { this.root = root }
get editable() { return this.root.contentEditable === "true" }
constructor(root) {
this.root = root;
}
get editable() {
return this.root.contentEditable === "true";
}
set editable(value) {
this.root.contentEditable = value
this.lines.forEach(line => line.editable = value)
this.root.contentEditable = value;
this.lines.forEach((line) => (line.editable = value));
}
get lines() {
return Array.from(this.root.children).filter(el => [
"P", "BLOCKQUOTE", "A", "PRE", "UL", "H1", "H2", "H3"
].includes(el.nodeName)).map(Gemini.line)
return Array.from(this.root.children)
.filter((el) =>
["P", "BLOCKQUOTE", "A", "PRE", "UL", "H1", "H2", "H3"].includes(
el.nodeName
)
)
.map(Gemini.line);
}
set lines(lines) {
this.root.textContent = ""
this.root.append(...lines.map(line => line.dom))
}
get source() { return this.lines.map(line => line.gmi).join("\n") }
this.root.textContent = "";
this.root.append(...lines.map((line) => line.dom));
}
get source() {
return this.lines.map((line) => line.gmi).join("\n");
}
// TODO: set source is a DOM thing, but parsing should be isomorphic
// set source(gmi) {}
download() {
const el = document.createElement('a')
el.setAttribute('href', 'data:text/gemini;charset=utf-8,'
+ encodeURIComponent(this.source))
el.setAttribute('download',
`${this.lines[0].content.replace(/\s/g, "_")}.gmi`)
el.style.display = 'none'
document.body.appendChild(el); el.click(); document.body.removeChild(el)
const el = document.createElement("a");
el.setAttribute(
"href",
"data:text/gemini;charset=utf-8," + encodeURIComponent(this.source)
);
el.setAttribute(
"download",
`${this.lines[0].content.replace(/\s/g, "_")}.gmi`
);
el.style.display = "none";
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
}
get foreground() {
return getComputedStyle(this.root).getPropertyValue("--foreground");
}
set foreground(value) {
return this.root.style.setProperty("--foreground", value);
}
get background() {
return getComputedStyle(this.root).getPropertyValue("--background");
}
set background(value) {
return this.root.style.setProperty("--background", value);
}
get size() {
return getComputedStyle(this.root).getPropertyValue("--font-size");
}
set size(value) {
return this.root.style.setProperty("--font-size", value);
}
get lineHeight() {
return getComputedStyle(this.root).getPropertyValue("--line-height");
}
set lineHeight(value) {
return this.root.style.setProperty("--line-height", value);
}
get serif() {
return getComputedStyle(this.root).getPropertyValue("--serif");
}
set serif(value) {
return this.root.style.setProperty("--serif", value);
}
get sans() {
return getComputedStyle(this.root).getPropertyValue("--sans");
}
set sans(value) {
return this.root.style.setProperty("--sans", value);
}
get mono() {
return getComputedStyle(this.root).getPropertyValue("--mono");
}
set mono(value) {
return this.root.style.setProperty("--mono", value);
}
get foreground() { return getComputedStyle(this.root).getPropertyValue("--foreground") }
set foreground(value) { return this.root.style.setProperty("--foreground", value) }
get background() { return getComputedStyle(this.root).getPropertyValue("--background") }
set background(value) { return this.root.style.setProperty("--background", value) }
get size() { return getComputedStyle(this.root).getPropertyValue("--font-size") }
set size(value) { return this.root.style.setProperty("--font-size", value) }
get lineHeight() { return getComputedStyle(this.root).getPropertyValue("--line-height") }
set lineHeight(value) { return this.root.style.setProperty("--line-height", value) }
get serif() { return getComputedStyle(this.root).getPropertyValue("--serif") }
set serif(value) { return this.root.style.setProperty("--serif", value) }
get sans() { return getComputedStyle(this.root).getPropertyValue("--sans") }
set sans(value) { return this.root.style.setProperty("--sans", value) }
get mono() { return getComputedStyle(this.root).getPropertyValue("--mono") }
set mono(value) { return this.root.style.setProperty("--mono", value) }
}