1
Fork 0
mirror of https://github.com/glassflame/glassflame.github.io.git synced 2024-11-22 16:14:26 +00:00

Complete refactoring?

This commit is contained in:
Steffo 2023-10-27 02:44:49 +02:00
parent 610c849264
commit 803b61f434
Signed by: steffo
GPG key ID: 2A24051445686895
12 changed files with 252 additions and 116 deletions

View file

@ -24,12 +24,15 @@
--color-blue: #53DFDD; --color-blue: #53DFDD;
--color-purple: #A882FF; --color-purple: #A882FF;
--color-node: var(--color-gray);
--edge-width: 2px; --edge-width: 2px;
--node-group-border-width: 2px; --node-group-border-width: 2px;
--node-file-border-width: 2px; --node-file-border-width: 2px;
} }
body { body {
margin: 0;
padding: 128px; padding: 128px;
width: max-content; width: max-content;
height: max-content; height: max-content;
@ -37,11 +40,18 @@
background-color: var(--color-background); background-color: var(--color-background);
color: var(--color-foreground); color: var(--color-foreground);
} }
svg line {
stroke: var(--color-node);
stroke-width: var(--edge-width);
}
</style> </style>
<!-- Templates --> <!-- Templates -->
<template id="template-vault"> <template id="template-vault">
<style> <style>
.vault {
}
</style> </style>
<div class="vault"> <div class="vault">
<slot name="vault-child"></slot> <slot name="vault-child"></slot>
@ -57,19 +67,6 @@
width: max-content; width: max-content;
height: max-content; height: max-content;
} }
.canvas * {
left: 0;
top: 0;
}
.canvas *:first-child {
position: relative;
}
.canvas *:not(:first-child) {
position: absolute;
}
</style> </style>
<div class="canvas"> <div class="canvas">
<slot name="canvas-edges">{Canvas edges}</slot> <slot name="canvas-edges">{Canvas edges}</slot>
@ -159,18 +156,19 @@
</template> </template>
<template id="template-edge"> <template id="template-edge">
<style> <style>
.edge svg line { .edge {
stroke: var(--color-node);
stroke-width: var(--edge-width);
} }
</style> </style>
<div class="edge"> <div class="canvas-item edge">
<slot name="edge-svg">{Edge SVG}</slot> <slot name="edge-svg">{Edge SVG}</slot>
</div> </div>
</template> </template>
<template id="template-markdown"> <template id="template-markdown">
<style> <style>
.markdown .markdown {
}
</style> </style>
<div class="markdown"> <div class="markdown">
<slot name="markdown-document">{Markdown text}</slot> <slot name="markdown-document">{Markdown text}</slot>
@ -203,9 +201,8 @@
cursor: pointer; cursor: pointer;
} }
</style> </style>
<a class="wikilink"><slot name="wikilink-anchor">{Wikilink text}</slot></a> <a class="wikilink"></a>
</template> </template>
</head> </head>
<body> <body is="x-browse"></body>
</body>
</html> </html>

View file

@ -1,11 +0,0 @@
export function configFromURL(location) {
const queryString = new URLSearchParams(location.search)
const vref = queryString.get("vref")
const wref = queryString.get("wref")
return {vref, wref}
}
export function configFromWindow() {
return configFromURL(window.location)
}

64
src/elements/browse.mjs Normal file
View file

@ -0,0 +1,64 @@
/**
* The body element for the pages viewer, handling most low-level things.
*/
export class BrowseElement extends HTMLBodyElement {
/**
* Parameters to be used to display *things*.
* @type {{vault: string, path: string, highlight: string}}
*/
parameters
/**
* Recalculate the value of {@link parameters} using the current {@link window.location}.
* @returns {void}
*/
recalculateParameters() {
const location = window.location
const params = new URLSearchParams(location.search)
const vault = params.get("vault")
const path = params.get("path")
const highlight = location.hash.replace(/^#/, "")
this.parameters = {vault, path, highlight}
}
// TODO: Add a landing page
/**
* The vault element, describing to its descendants how to handle various situations.
* @type {VaultElement}
*/
vaultElement
/**
* The display element showing the contents of the specified file.
* @type {DisplayElement}
*/
rootDisplayElement
/**
* Recreate all contents of this element to match the current value of {@link parameters}.
*/
recreateContents() {
if(this.vaultElement) {
this.vaultElement.remove()
this.vaultElement = null
this.rootDisplayElement = null
}
this.vaultElement = document.createElement("x-vault")
this.vaultElement.base = this.parameters.vault
this.vaultElement.cooldownMs = 200
this.rootDisplayElement = document.createElement("x-display")
this.rootDisplayElement.path = this.parameters.path
this.rootDisplayElement.slot = "vault-child"
this.vaultElement.appendChild(this.rootDisplayElement)
this.appendChild(this.vaultElement)
}
connectedCallback() {
this.recalculateParameters()
this.recreateContents()
}
}

View file

@ -40,10 +40,10 @@ export class CanvasElement extends CustomElement {
/** /**
* Update the values of {@link document} and {@link parsedDocument} from the `document` attribute of the element. * Update the values of {@link document} and {@link parsedDocument} from the `document` attribute of the element.
* @throws SyntaxError If `contents` is not valid JSON. * @throws SyntaxError If `document` is not valid JSON.
*/ */
recalculateContents() { reparseDocument() {
this.#document = this.getAttribute("contents") this.#document = this.getAttribute("document")
this.#parsedDocument = JSON.parse(this.#document) this.#parsedDocument = JSON.parse(this.#document)
} }
@ -155,31 +155,28 @@ export class CanvasElement extends CustomElement {
this.nodesContainer.slot = this.constructor.NODES_SLOT_NAME this.nodesContainer.slot = this.constructor.NODES_SLOT_NAME
for(const node of this.parsedDocument["nodes"]) { for(const node of this.parsedDocument["nodes"]) {
let {id, type, color, x, y, width, height} = node const {id, type, color, x, y, width, height} = node
x, y, width, height = Number(x), Number(y), Number(width), Number(height) const [nodeX, nodeY, nodeWidth, nodeHeight] = [Number(x), Number(y), Number(width), Number(height)]
const element = document.createElement(`${this.constructor.NODE_ELEMENT_NAME_PREFIX}${type}`) const element = document.createElement(`${this.constructor.NODE_ELEMENT_NAME_PREFIX}${type}`)
element.x = nodeX - this.minX.x
element.setAttribute("id", `node-${id}`) element.y = nodeY - this.minY.y
element.setAttribute("x", `${x - this.minX.x}`) element.width = nodeWidth
element.setAttribute("y", `${y - this.minY.y}`) element.height = nodeHeight
element.setAttribute("width", `${width}`) if(color) element.obsidianColor = color
element.setAttribute("height", `${height}`)
if(color) element.setAttribute("color", color)
this.nodeElementsById[id] = element this.nodeElementsById[id] = element
switch(type) { switch(type) {
case "text": case "text":
const {text} = node const {text} = node
element.setAttribute("text", text) element.setAttribute("document", text)
break break
case "file": case "file":
const {file} = node const {file} = node
const {name} = fileDetails(file) const {name} = fileDetails(file)
element.setAttribute("file", file) element.setAttribute("path", file)
element.setAttribute("file-name", name)
this.nodeElementsByPath[file] = element this.nodeElementsByPath[file] = element
this.nodeElementsByName[name] = element this.nodeElementsByName[name] = element
break break
@ -199,6 +196,7 @@ export class CanvasElement extends CustomElement {
this.nodesContainer.style["width"] = `${this.maxX.x + this.maxX.width - this.minX.x}px` this.nodesContainer.style["width"] = `${this.maxX.x + this.maxX.width - this.minX.x}px`
this.nodesContainer.style["height"] = `${this.maxY.y + this.maxY.height - this.minY.y}px` this.nodesContainer.style["height"] = `${this.maxY.y + this.maxY.height - this.minY.y}px`
this.nodesContainer.style["position"] = "relative"
this.appendChild(this.nodesContainer) this.appendChild(this.nodesContainer)
} }
@ -219,7 +217,7 @@ export class CanvasElement extends CustomElement {
* Name of the slot where the edge container should be placed. * Name of the slot where the edge container should be placed.
* @type {string} * @type {string}
*/ */
static EDGES_SLOT_NAME = "canvas-nodes" static EDGES_SLOT_NAME = "canvas-edges"
/** /**
* Prefix to the name of the element to create for each edge. * Prefix to the name of the element to create for each edge.
@ -258,13 +256,14 @@ export class CanvasElement extends CustomElement {
this.edgesContainer.style["width"] = `${this.maxX.x + this.maxX.width - this.minX.x}px` this.edgesContainer.style["width"] = `${this.maxX.x + this.maxX.width - this.minX.x}px`
this.edgesContainer.style["height"] = `${this.maxY.y + this.maxY.height - this.minY.y}px` this.edgesContainer.style["height"] = `${this.maxY.y + this.maxY.height - this.minY.y}px`
this.edgesContainer.style["position"] = "absolute"
this.appendChild(this.edgesContainer) this.appendChild(this.edgesContainer)
} }
onConnect() { onConnect() {
super.onConnect() super.onConnect()
this.recalculateContents() this.reparseDocument()
this.recalculateMinMax() this.recalculateMinMax()
this.recreateNodes() this.recreateNodes()
this.recreateEdges() this.recreateEdges()

View file

@ -1,4 +1,5 @@
import { CustomElement } from "../base.mjs"; import { CustomElement } from "../base.mjs";
import {MalformedError} from "../../utils/errors.mjs";
/** /**
@ -12,6 +13,9 @@ export class CanvasItemElement extends CustomElement {
get x() { get x() {
return Number(this.getAttribute("x")) return Number(this.getAttribute("x"))
} }
set x(value) {
this.setAttribute("x", value.toString())
}
/** /**
* @returns {number} The Y coordinate of the top left vertex of this element. * @returns {number} The Y coordinate of the top left vertex of this element.
@ -19,6 +23,9 @@ export class CanvasItemElement extends CustomElement {
get y() { get y() {
return Number(this.getAttribute("y")) return Number(this.getAttribute("y"))
} }
set y(value) {
this.setAttribute("y", value.toString())
}
/** /**
* @returns {number} The horizontal width of this element. * @returns {number} The horizontal width of this element.
@ -26,6 +33,9 @@ export class CanvasItemElement extends CustomElement {
get width() { get width() {
return Number(this.getAttribute("width")) return Number(this.getAttribute("width"))
} }
set width(value) {
this.setAttribute("width", value.toString())
}
/** /**
* @returns {number} The vertical height of this element. * @returns {number} The vertical height of this element.
@ -33,6 +43,9 @@ export class CanvasItemElement extends CustomElement {
get height() { get height() {
return Number(this.getAttribute("height")) return Number(this.getAttribute("height"))
} }
set height(value) {
this.setAttribute("height", value.toString())
}
/** /**
* The color of this element, as stored in Obsidian Canvas files. * The color of this element, as stored in Obsidian Canvas files.
@ -48,6 +61,14 @@ export class CanvasItemElement extends CustomElement {
return color // Hex color specified return color // Hex color specified
} }
set obsidianColor(value) {
if(value === null) {
this.removeAttribute("color")
}
else {
this.setAttribute("color", value.toString())
}
}
/** /**
* Given an Obsidian Canvas color, return its corresponding CSS color. * Given an Obsidian Canvas color, return its corresponding CSS color.
@ -58,18 +79,23 @@ export class CanvasItemElement extends CustomElement {
if(color === null || color === "") { if(color === null || color === "") {
return "var(--color-gray)" return "var(--color-gray)"
} }
else if(color.match(/^#[0-9A-F]{3}$|^#[0-9A-F]{6}$/i)) { else if(typeof color === "string") {
return color if(color.match(/^#[0-9A-F]{3}$|^#[0-9A-F]{6}$/i)) {
return color
}
else {
throw new MalformedError("String obisidianColor is not an hex code.")
}
} }
else { else {
return { return {
"0": "var(--color-gray)", 0: "var(--color-gray)",
"1": "var(--color-red)", 1: "var(--color-red)",
"2": "var(--color-orange)", 2: "var(--color-orange)",
"3": "var(--color-yellow)", 3: "var(--color-yellow)",
"4": "var(--color-green)", 4: "var(--color-green)",
"5": "var(--color-blue)", 5: "var(--color-blue)",
"6": "var(--color-purple)", 6: "var(--color-purple)",
}[color] }[color]
} }
} }
@ -80,6 +106,14 @@ export class CanvasItemElement extends CustomElement {
get cssColor() { get cssColor() {
return this.constructor.obsidianColorToCssColor(this.obsidianColor) return this.constructor.obsidianColorToCssColor(this.obsidianColor)
} }
set cssColor(value) {
if(value === null) {
this.removeAttribute("color")
}
else {
this.setAttribute("color", value)
}
}
/** /**
* The CSS selector of the element in the template representing the canvas item. * The CSS selector of the element in the template representing the canvas item.
@ -108,11 +142,11 @@ export class CanvasItemElement extends CustomElement {
resetCanvasItemCssProperties() { resetCanvasItemCssProperties() {
this.canvasItemElement.style.setProperty("box-sizing", "border-box") this.canvasItemElement.style.setProperty("box-sizing", "border-box")
this.canvasItemElement.style.setProperty("position", "absolute") this.canvasItemElement.style.setProperty("position", "absolute")
this.canvasItemElement.style.setProperty("left", `${this.getAttribute("x")}px`) this.canvasItemElement.style.setProperty("left", `${this.x}px`)
this.canvasItemElement.style.setProperty("top", `${this.getAttribute("y")}px`) this.canvasItemElement.style.setProperty("top", `${this.y}px`)
this.canvasItemElement.style.setProperty("width", `${this.getAttribute("width")}px`) this.canvasItemElement.style.setProperty("width", `${this.width}px`)
this.canvasItemElement.style.setProperty("height", `${this.getAttribute("height")}px`) this.canvasItemElement.style.setProperty("height", `${this.height}px`)
this.canvasItemElement.style.setProperty("--color-node", this.constructor.obsidianColorToCssColor(this.getAttribute("color"))) this.canvasItemElement.style.setProperty("--color-node", this.cssColor)
} }
onConnect() { onConnect() {

View file

@ -76,8 +76,7 @@ export class NodeFileElement extends NodeElement {
this.contentsElement = document.createElement("x-display") this.contentsElement = document.createElement("x-display")
this.contentsElement.slot = this.constructor.CONTENTS_ELEMENT_SLOT this.contentsElement.slot = this.constructor.CONTENTS_ELEMENT_SLOT
this.contentsElement.setAttribute("vault", findFirstAncestor(this, DisplayElement).vault) // TODO: Add a vault attribute to DisplayElement this.contentsElement.path = this.pathRelativeToVault
this.contentsElement.setAttribute("path", this.pathRelativeToVault) // TODO: Add a path attribute to DisplayElement
this.appendChild(this.contentsElement) this.appendChild(this.contentsElement)
} }

View file

@ -30,8 +30,11 @@ export class DisplayElement extends CustomElement {
* Get the path or name of the file this node points to. * Get the path or name of the file this node points to.
* @returns {string} The value in question. * @returns {string} The value in question.
*/ */
get target() { get path() {
return this.getAttribute("target") return this.getAttribute("path")
}
set path(value) {
return this.setAttribute("path", value)
} }
/** /**
@ -84,7 +87,7 @@ export class DisplayElement extends CustomElement {
this.contentsElement = null this.contentsElement = null
} }
const {extension} = fileDetails(this.target) const {extension} = fileDetails(this.path)
switch(extension) { switch(extension) {
case "md": case "md":
@ -100,30 +103,30 @@ export class DisplayElement extends CustomElement {
this.contentsElement.setAttribute("document", this.document) this.contentsElement.setAttribute("document", this.document)
this.contentsElement.slot = this.constructor.CONTAINER_ELEMENT_SLOT this.contentsElement.slot = this.constructor.CONTAINER_ELEMENT_SLOT
this.appendChild(this.loadingElement) this.appendChild(this.contentsElement)
} }
/** /**
* The plaintext contents of the {@link target} document. * The plaintext contents of the {@link path} document.
* @type {string} * @type {string}
*/ */
document document
/** /**
* Reload the {@link target} {@link document}. * Reload the {@link path} {@link document}.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async reloadDocument() { async reloadDocument() {
const response = await this.vault.fetchCooldown(this.target) const response = await this.vault.fetchCooldown(this.path)
// TODO: Add a check that the request was successful // TODO: Add a check that the request was successful
this.document = await response.text() this.document = await response.text()
} }
onConnect() { async onConnect() {
super.onConnect() super.onConnect()
this.recalculateVault() this.recalculateVault()
this.recreateLoadingElement() this.recreateLoadingElement()
// noinspection JSIgnoredPromiseFromCall await this.reloadDocument()
this.reloadDocument().then(this.recreateContentsElement) this.recreateContentsElement()
} }
} }

View file

@ -2,3 +2,4 @@ export {CanvasElement, NodeFileElement, NodeGroupElement, NodeTextElement, EdgeE
export {MarkdownElement, HashtagElement, WikilinkElement, FrontMatterElement} from "./markdown/index.mjs" export {MarkdownElement, HashtagElement, WikilinkElement, FrontMatterElement} from "./markdown/index.mjs"
export {DisplayElement} from "./display.mjs" export {DisplayElement} from "./display.mjs"
export {VaultElement} from "./vault.mjs" export {VaultElement} from "./vault.mjs"
export {BrowseElement} from "./browse.mjs"

View file

@ -6,7 +6,7 @@ import { CustomElement } from "../base.mjs";
*/ */
export class WikilinkElement extends CustomElement { export class WikilinkElement extends CustomElement {
static get template() { static get template() {
return document.getElementById("template-hashtag") return document.getElementById("template-wikilink")
} }
/** /**
@ -22,9 +22,21 @@ export class WikilinkElement extends CustomElement {
* @returns {string} The text in question. * @returns {string} The text in question.
*/ */
get text() { get text() {
return this.getAttribute("text") ?? this.target // TODO: Dirty hack to hide "undefined"
const text = this.getAttribute("text")
// noinspection EqualityComparisonWithCoercionJS
if(text == "undefined") {
return this.target
}
return text
} }
/**
* The CSS selector of the anchor element.
* @type {string}
*/
static ANCHOR_SELECTOR = "a.wikilink"
/** /**
* The element displaying the wikilink. * The element displaying the wikilink.
* Can be recreated with {@link recreateTagElement}. * Can be recreated with {@link recreateTagElement}.
@ -33,30 +45,21 @@ export class WikilinkElement extends CustomElement {
anchorElement anchorElement
/** /**
* The name of the slot where {@link anchorElement} should be placed in. Update the value of the {@link canvasItemElement} by querying the current {@link instance} with {@link ANCHOR_SELECTOR}.
* @type {string}
*/
static ANCHOR_ELEMENT_SLOT = "wikilink-anchor"
/**
* Recreate {@link anchorElement} with the current value of {@link target} and {@link text}.
* @returns {void} * @returns {void}
*/ */
recreateTagElement() { recalculateAnchorElement() {
if(this.anchorElement) { this.anchorElement = this.instance.querySelector(this.constructor.ANCHOR_SELECTOR)
this.anchorElement.remove() }
this.anchorElement = null
}
this.anchorElement = document.createElement("a") resetAnchorElementProperties() {
this.anchorElement.slot = this.constructor.ANCHOR_ELEMENT_SLOT this.anchorElement.href = this.target
this.anchorElement.href = "#" // TODO: Add href behaviour to the anchor.
this.anchorElement.innerText = this.text this.anchorElement.innerText = this.text
this.appendChild(this.anchorElement)
} }
onConnect() { onConnect() {
super.onConnect() super.onConnect()
this.recreateTagElement() this.recalculateAnchorElement()
this.resetAnchorElementProperties()
} }
} }

View file

@ -17,6 +17,9 @@ export class VaultElement extends CustomElement {
get base() { get base() {
return this.getAttribute("base") return this.getAttribute("base")
} }
set base(value) {
this.setAttribute("base", value)
}
/** /**
* {@link fetch} the file at the given path ignoring cooldowns. * {@link fetch} the file at the given path ignoring cooldowns.
@ -32,33 +35,59 @@ export class VaultElement extends CustomElement {
* Cooldown between two {@link fetchCooldown} requests in milliseconds, as obtained from the `cooldown` parameter. * Cooldown between two {@link fetchCooldown} requests in milliseconds, as obtained from the `cooldown` parameter.
*/ */
get cooldownMs() { get cooldownMs() {
return Number(this.getAttribute("cooldown")) return Number(this.getAttribute("cooldown") ?? 5000)
}
set cooldownMs(value) {
this.setAttribute("cooldown", value.toString())
} }
/** /**
* Queue containing the `resolve` functions necessary to make the calls to {@link fetchCooldown} proceed beyond the waiting phase. * FIFO queue of {@link fetch} promises to be awaited, with a {@link cooldownMs} pause between each of them.
* To be called by the preceding {@link fetchCooldown} if possible. * @type {((v: undefined) => void)[]}
* @type {((v: unknown) => void)[]}
*/ */
#fetchQueue = [] #fetchQueue = []
/**
* Promise that can be called to resume {@link #fetchQueueScheduler} if {@link #fetchQueue} is no longer empty.
* @type {((v: undefined) => void)|null}
*/
#somethingInFetchQueue
/** /**
* @returns {Promise<void>} A Promise that will wait for this caller's turn in the {@link #fetchQueue}. * @returns {Promise<void>} A Promise that will wait for this caller's turn in the {@link #fetchQueue}.
*/ */
fetchQueueTurn() { async fetchQueueTurn() {
return new Promise(resolve => { return new Promise(resolve => {
this.#fetchQueue.push(resolve) this.#fetchQueue.push(resolve)
console.debug("[Fetch queue] Waiting for my turn...")
if(this.#somethingInFetchQueue !== null) {
console.debug("[Fetch queue] Asking scheduler to resume...")
this.#somethingInFetchQueue(undefined)
this.#somethingInFetchQueue = null
}
}) })
} }
/** /**
* A promise that will advance the fetch queue after {@link cooldownMs}. * Resolves promises in the {@link #fetchQueue} with a cooldown.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async #scheduleNextFetchQueueTurn() { async #fetchQueueScheduler() {
await sleep(this.cooldownMs) while(this.isConnected) {
const resolve = this.#fetchQueue.shift() console.debug("[Fetch scheduler] Scheduler running one iteration...")
resolve() if(this.#fetchQueue.length === 0) {
const somethingInFetchQueue = new Promise(resolve => {
this.#somethingInFetchQueue = resolve
console.debug("[Fetch scheduler] Nothing to do, waiting...")
})
await somethingInFetchQueue
}
const promise = this.#fetchQueue.shift()
console.debug("[Fetch scheduler] Advancing...")
promise()
console.debug("[Fetch scheduler] Cooling down for:", this.cooldownMs)
await sleep(this.cooldownMs)
}
} }
/** /**
@ -67,20 +96,19 @@ export class VaultElement extends CustomElement {
* @returns {Promise<Response>} The resulting HTTP response. * @returns {Promise<Response>} The resulting HTTP response.
*/ */
async fetchCooldown(path) { async fetchCooldown(path) {
// Sit waiting in queue // Await for this request's turn in the fetchQueue
if(this.#fetchQueue.length > 0) { await this.fetchQueueTurn()
await this.fetchQueueTurn()
}
// Perform the request // Perform the request
const result = await this.fetchImmediately(path) const result = await this.fetchImmediately(path)
// Start the next item in queue // Start the next item in queue
// noinspection ES6MissingAwait
this.#scheduleNextFetchQueueTurn()
// Return the request's result // Return the request's result
return result return result
} }
onConnect() { onConnect() {
super.onConnect() super.onConnect()
// noinspection JSIgnoredPromiseFromCall
this.#fetchQueueScheduler()
} }
} }

View file

@ -1,12 +1,26 @@
import { CanvasElement, HashtagElement, NodeFileElement, WikilinkElement, DisplayElement, EdgeElement, NodeGroupElement, NodeTextElement, FrontMatterElement, MarkdownElement } from "./elements/index.mjs"; import {
CanvasElement,
HashtagElement,
NodeFileElement,
WikilinkElement,
DisplayElement,
EdgeElement,
NodeGroupElement,
NodeTextElement,
FrontMatterElement,
MarkdownElement,
VaultElement, BrowseElement
} from "./elements/index.mjs";
customElements.define("x-vault", VaultElement)
customElements.define("x-display", DisplayElement)
customElements.define("x-canvas", CanvasElement)
customElements.define("x-node-group", NodeGroupElement)
customElements.define("x-node-file", NodeFileElement) customElements.define("x-node-file", NodeFileElement)
customElements.define("x-node-text", NodeTextElement) customElements.define("x-node-text", NodeTextElement)
customElements.define("x-node-group", NodeGroupElement) customElements.define("x-edge", EdgeElement)
customElements.define("x-markdown", MarkdownElement) customElements.define("x-markdown", MarkdownElement)
customElements.define("x-frontmatter", FrontMatterElement) customElements.define("x-frontmatter", FrontMatterElement)
customElements.define("x-wikilink", WikilinkElement)
customElements.define("x-hashtag", HashtagElement) customElements.define("x-hashtag", HashtagElement)
customElements.define("x-canvas", CanvasElement) customElements.define("x-wikilink", WikilinkElement)
customElements.define("x-display", DisplayElement) customElements.define("x-browse", BrowseElement, {extends: "body"})
customElements.define("x-edge", EdgeElement)

View file

@ -21,3 +21,8 @@ export class FetchError extends Error {
this.response = response this.response = response
} }
} }
/**
* A file is maliciously malformed.
*/
export class MalformedError extends Error {}