This commit is contained in:
Talon Poole 2021-01-29 20:00:53 +00:00
parent ab7f953a73
commit 13a885ee11
3 changed files with 3 additions and 235 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
node_modules/
capsule/
gmi.js
gmi.min.js

View file

@ -104,7 +104,7 @@ Or maybe by adding a style to the `<html>` element.
When using `gmi-web(1)` they can be customized by providing _OPTIONS_ of the same name as the CSS variable.
```sh
gmi-web --foreground "#FFFFF" --background "#00000" \*.gmi
gmi-web --foreground "#FFFFF" --background "#00000" *.gmi
```
# License

234
gmi.js
View file

@ -1,234 +0,0 @@
/* gmi.js is licensed under CC0 */
class Gemini {
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;
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?$/, "");
switch (this.type.toUpperCase()) {
case "PRE":
return `${syntax}\n${content}\n${syntax}`;
break;
default:
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);
},
};
}
static render(line) {
if (line.dom && line.dom.nodeName !== line.type) {
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,
};
} else {
line.dom = line.dom || document.createElement(line.type || "P");
}
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;
case "PRE":
line.dom.textContent = line.content;
break;
default:
line.dom.innerHTML = line.content.replace(/\n+/g, "<br>");
}
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;
default:
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;
}
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));
}
get lines() {
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");
}
// 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);
}
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);
}
}