make format
This commit is contained in:
parent
24c18dd9a9
commit
c71728c713
18
Makefile
18
Makefile
|
@ -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
|
||||
|
|
|
@ -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> ↔ =>
|
||||
<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
14
cli.js
Executable 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;
|
|
@ -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
23
gmi-web.1
Normal 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
|
36
gmi.css
36
gmi.css
|
@ -13,21 +13,33 @@
|
|||
--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;
|
||||
--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;
|
||||
padding: 0.5rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
p, a, pre, h1, h2, h3, ul, blockquote, img, audio, video {
|
||||
p,
|
||||
a,
|
||||
pre,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
ul,
|
||||
blockquote,
|
||||
img,
|
||||
audio,
|
||||
video {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: anywhere
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
@ -48,8 +60,8 @@ a::before {
|
|||
font-size: var(--p-size);
|
||||
font-family: var(--mono);
|
||||
content: "⇒";
|
||||
padding-right: .25rem;
|
||||
vertical-align: middle
|
||||
padding-right: 0.25rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -90,19 +102,19 @@ li::before {
|
|||
font-family: var(--mono);
|
||||
content: "*";
|
||||
vertical-align: middle;
|
||||
padding-right: .5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: var(--p-size);
|
||||
font-family: var(--serif);
|
||||
line-height: 1.5;
|
||||
padding-left: .75rem;
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
pre + blockquote {
|
||||
padding-top: .5rem;
|
||||
padding-bottom: .5rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* foreground and background colors */
|
||||
|
@ -122,7 +134,7 @@ pre::-moz-selection {
|
|||
}
|
||||
|
||||
blockquote {
|
||||
border-left: .5rem solid var(--foreground);
|
||||
border-left: 0.5rem solid var(--foreground);
|
||||
}
|
||||
|
||||
pre,
|
||||
|
@ -151,7 +163,7 @@ a:hover {
|
|||
}
|
||||
|
||||
blockquote {
|
||||
border-left: .5rem solid var(--background);
|
||||
border-left: 0.5rem solid var(--background);
|
||||
}
|
||||
|
||||
pre,
|
||||
|
|
276
gmi.js
276
gmi.js
|
@ -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 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?$/, "")
|
||||
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,
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
const { href, content } = Gemini.link(dom.textContent);
|
||||
return `${href || dom.href} ${content}`;
|
||||
break;
|
||||
case "PRE":
|
||||
return dom.textContent
|
||||
break
|
||||
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))
|
||||
this.root.textContent = "";
|
||||
this.root.append(...lines.map((line) => line.dom));
|
||||
}
|
||||
get source() {
|
||||
return this.lines.map((line) => line.gmi).join("\n");
|
||||
}
|
||||
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) }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue