From c71728c71374b0dca8da9612f1abd8459b4d4e96 Mon Sep 17 00:00:00 2001 From: Talon Poole Date: Thu, 28 Jan 2021 06:16:46 +0000 Subject: [PATCH] make format --- Makefile | 18 +++- README.md | 10 +- cli.js | 14 +++ example.html | 2 +- gmi-web.1 | 23 ++++ gmi.css | 194 +++++++++++++++++---------------- gmi.js | 294 +++++++++++++++++++++++++++++++++------------------ 7 files changed, 357 insertions(+), 198 deletions(-) create mode 100755 cli.js create mode 100644 gmi-web.1 diff --git a/Makefile b/Makefile index 78607b7..579db73 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index c10b1fa..c8e620c 100644 --- a/README.md +++ b/README.md @@ -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: -``` + +````


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".

- +


diff --git a/gmi-web.1 b/gmi-web.1 new file mode 100644 index 0000000..6f673f8 --- /dev/null +++ b/gmi-web.1 @@ -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 . Up-to-date sources can be +found at https://codeberg.org/talon/gmi-web diff --git a/gmi.css b/gmi.css index 241abea..50e8d8d 100644 --- a/gmi.css +++ b/gmi.css @@ -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); + } } diff --git a/gmi.js b/gmi.js index 95b503b..150a603 100644 --- a/gmi.js +++ b/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 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 ? - `
  • ${content}
  • ` : "" - ).join("\n") - break - case "BLOCKQUOTE": - line.dom.innerHTML = line.content.split("\n").map(content => - `
    ${content}
    ` - ).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 ? `
  • ${content}
  • ` : "")) + .join("\n"); + break; + case "BLOCKQUOTE": + line.dom.innerHTML = line.content + .split("\n") + .map((content) => `
    ${content}
    `) + .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, "
    ") + line.dom.innerHTML = line.content.replace(/\n+/g, "
    "); } - 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(/
    /g, "\n") + return dom.innerHTML.replace(/
    /g, "\n"); } } // TODO: rename/move/idk this is awk - static link(content = "") { return /((?[^\s]+\/\/[^\s]+)\s)?(?.+)/.exec(content).groups } + static link(content = "") { + return /((?[^\s]+\/\/[^\s]+)\s)?(?.+)/.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) } }