install via npm

This commit is contained in:
Talon Poole 2021-01-29 06:48:39 +00:00
parent 21c5e5bfef
commit d77cc84a06
5 changed files with 33 additions and 47 deletions

View file

@ -1,6 +1,5 @@
PREFIX=/usr/local
INSTALLDIR=$(DESTDIR)$(PREFIX)
MANDIR=$(INSTALLDIR)/share/man
format:
./node_modules/prettier/bin-prettier.js --write gmi.css
@ -12,11 +11,9 @@ gmi.min.css: gmi.css
gmi-web.1: gmi-web.1.scd
scdoc < $< > $@
install: gmi.min.css gmi-web.1
sudo npm link
install -Dm644 gmi-web.1 $(MANDIR)/man1/gmi-web.1
build: format gmi.min.css gmi-web.1
clean:
rm -rf gmi.min.* gmi-web.1
.PHONY: install clean
.PHONY: install clean format

View file

@ -8,48 +8,35 @@ 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> ↔ *
```
<ul><li></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).
Empty lines should simply be represented as `<p><br></p>` this sets up `contenteditable=true` to add content to the line compared to just `<br>`.
### inline media
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>`.
### contenteditable
Empty lines should simply be represented as `<p><br></p>` this sets up `contenteditable=true` to add content to the line compared to just `<br>`.
When `contenteditable=true`, `<p>`, `<ul>`, `<blockquote>` and `<pre>` may also have line-breaks which will be represented in `.innerHTML` like so:
```
<p><br>
<blockquote><div><br></div>
<ul><li><br></li>
<pre> ↔ \n (or <br>)
```
This is important mostly for DOM development (as opposed to static HTML generation) where it is likely to occur.
## gmi.css
Style your HTML generated Gemini content in a readable, predictable and mobile-friendly fashion!
```
```html
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="https://talon.computer/gmi.min.css"/>
<link rel="stylesheet" href="gmi.min.css"/>
```
The following variables (shown with their defaults) can be customized using the style attribute of your document's `<html>` tag.
```
```css
--foreground: black;
--background: white;
--p-size: 1.25rem;
@ -68,12 +55,21 @@ The following variables (shown with their defaults) can be customized using the
gmi.css will respect system dark mode preferences by inverting `--foreground` and `--background`.
```
```html
<html style="--foreground:#555555; --background:#9EEBCF;">
```
## gmi-web(1)
A reference implementation of what has been discussed above.
*You will need*:
- [node(1) w/ npm(1)](https://nodejs.org/en/)
```sh
npm install -g git+https://codeberg.org/talon/gmi-web.git
```
```
gmi-web [--css] [files..]
@ -91,13 +87,6 @@ Options:
--css Include gmi.css [boolean] [default: true]
```
*You will need*:
- [node(1) (w/ npm(1))](https://nodejs.org/en/) (to build/run the cli)
```
npm install -g git+https://codeberg.org/talon/gmi-web.git
```
<!-- TODO
### gmi.js

View file

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="gmi.min.css">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="gmi.min.css">
</head>
<body contenteditable="false">
<h1>gmi-web</h1>
@ -28,9 +28,9 @@ including newlines
</pre>
<p><br></p>
<ul>
<li>milk</li>
<li></li> <!-- empty list item -->
<li>bread</li>
<li>milk</li>
<li></li> <!-- empty list item -->
<li>bread</li>
</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>
@ -41,6 +41,7 @@ including newlines
<h3>contenteditable</h3>
<p>
While Gemini does not support newlines on a regular text line when editing a "p" tag in the browser
<br>
the default behavior for "enter" will create a "br" and not a new "p".
</p>
<p><br></p>

1
gmi.min.js vendored
View file

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

View file

@ -19,7 +19,7 @@ module.exports = (options) => (file, cb) => {
path.resolve(__dirname, "./gmi.min.css"),
"utf8"
)}</style>`
: ""
: "a, audio {display: block;}"
}
<body>
${toHTML(JSON.parse(file.contents.toString("utf8")), options)}