er
This commit is contained in:
parent
ab7f953a73
commit
13a885ee11
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
node_modules/
|
||||
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.
|
||||
|
||||
```sh
|
||||
gmi-web --foreground "#FFFFF" --background "#00000" \*.gmi
|
||||
gmi-web --foreground "#FFFFF" --background "#00000" *.gmi
|
||||
```
|
||||
|
||||
# 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