er
This commit is contained in:
parent
ab7f953a73
commit
13a885ee11
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
capsule/
|
capsule/
|
||||||
|
gmi.js
|
||||||
|
gmi.min.js
|
||||||
|
|
|
@ -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.
|
When using `gmi-web(1)` they can be customized by providing _OPTIONS_ of the same name as the CSS variable.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
gmi-web --foreground "#FFFFF" --background "#00000" \*.gmi
|
gmi-web --foreground "#FFFFF" --background "#00000" *.gmi
|
||||||
```
|
```
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
234
gmi.js
234
gmi.js
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue