1
Fork 0
mirror of https://github.com/glassflame/glassflame.github.io.git synced 2024-11-21 15:44:27 +00:00

Quick iteration with no commits!

This commit is contained in:
Steffo 2023-10-20 03:44:56 +02:00
parent 6bb92bc00d
commit 45c3afd7d4
Signed by: steffo
GPG key ID: 2A24051445686895
14 changed files with 430 additions and 338 deletions

1
.gitignore vendored
View file

@ -0,0 +1 @@
/examples

View file

@ -2,11 +2,12 @@
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="CheckEmptyScriptTag" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="CheckEmptyScriptTag" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssUnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> <inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues"> <option name="myValues">
<value> <value>
<list size="8"> <list size="9">
<item index="0" class="java.lang.String" itemvalue="nobr" /> <item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" /> <item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" /> <item index="2" class="java.lang.String" itemvalue="comment" />
@ -15,6 +16,7 @@
<item index="5" class="java.lang.String" itemvalue="script" /> <item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="x-card" /> <item index="6" class="java.lang.String" itemvalue="x-card" />
<item index="7" class="java.lang.String" itemvalue="x-hashtag" /> <item index="7" class="java.lang.String" itemvalue="x-hashtag" />
<item index="8" class="java.lang.String" itemvalue="x-node-file" />
</list> </list>
</value> </value>
</option> </option>
@ -26,7 +28,7 @@
<option name="processLiterals" value="true" /> <option name="processLiterals" value="true" />
<option name="processComments" value="true" /> <option name="processComments" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES"> <inspection_tool class="UnnecessaryLocalVariableJS" enabled="false" level="WEAK WARNING" enabled_by_default="false" editorAttributes="INFO_ATTRIBUTES">
<option name="m_ignoreImmediatelyReturnedVariables" value="false" /> <option name="m_ignoreImmediatelyReturnedVariables" value="false" />
<option name="m_ignoreAnnotatedVariables" value="false" /> <option name="m_ignoreAnnotatedVariables" value="false" />
</inspection_tool> </inspection_tool>

View file

@ -9,51 +9,77 @@
@import "style/dark.css"; @import "style/dark.css";
body { body {
margin: 0;
background-color: var(--color-background); background-color: var(--color-background);
color: var(--color-foreground); color: var(--color-foreground);
} }
</style> </style>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script type="module" src="src/index.mjs"></script> <script type="module" src="src/index.mjs"></script>
</head> <template id="template-node-file">
<body>
<template id="template-card">
<style> <style>
.card { .node {
border-style: solid; position: absolute;
border-width: 2px; box-sizing: border-box;
}
.node-file {
outline-width: 2px;
border-radius: 8px; border-radius: 8px;
padding: 12px; padding: 12px;
overflow-x: clip;
overflow-y: scroll;
}
.node-empty {
outline-style: dashed;
}
.node-full {
outline-style: solid;
} }
</style> </style>
<div class="card"> <article class="node node-file">
<h1> <h1>
<slot name="card-name">Card name</slot> <slot name="node-title">Node title</slot>
</h1> </h1>
<div> <slot name="node-contents">Node contents</slot>
<slot name="card-contents">Card contents</slot> </article>
</div> </template>
</div> <template id="template-markdown">
<slot name="markdown-contents"></slot>
</template>
<template id="template-canvas">
<style>
.canvas {
}
</style>
<div class="canvas"></div>
</template> </template>
<template id="template-wikilink"> <template id="template-wikilink">
<style> <style>
.wikilink { .wikilink {
text-decoration: underline 1px solid var(--color-accent); color: var(--color-accent);
text-decoration: underline 1px solid currentColor;
cursor: pointer;
} }
</style> </style>
<abbr class="wikilink"><slot name="wikilink-text">Wikilink text</slot></abbr> <a class="wikilink"><slot name="wikilink-text">Wikilink text</slot></a>
</template> </template>
<template id="template-hashtag"> <template id="template-hashtag">
<style> <style>
.hashtag { .hashtag {
background-color: rgba(var(--color-accent), 0.2); background-color: color-mix(in srgb, var(--color-accent) 20%, transparent);
color: rgb(var(--color-accent)) color: var(--color-accent);
} }
</style> </style>
<span class="hashtag"><slot name="hashtag-text">#Hashtag</slot></span> <span class="hashtag"><slot name="hashtag-text">#Hashtag</slot></span>
</template> </template>
<x-card href="https://raw.githubusercontent.com/Steffo99/appunti-magistrali/main/8%20-%20Sistemi%20complessi/1%20-%20Sistemi%20dinamici/convezione%20di%20Rayleigh-B%C3%A9nard.md"></x-card> </head>
<x-card href="https://raw.githubusercontent.com/Steffo99/appunti-magistrali/main/8%20-%20Sistemi%20complessi/1%20-%20Sistemi%20dinamici/condizione%20iniziale.md"></x-card> <body>
<x-card href="https://raw.githubusercontent.com/Steffo99/appunti-magistrali/main/8%20-%20Sistemi%20complessi/1%20-%20Sistemi%20dinamici/metastabilit%C3%A0.md"></x-card> <x-canvas contents=""/>
</body> </body>
</html> </html>

View file

@ -1,101 +0,0 @@
import {nameFromFileURL, getVaultFile} from "./fetch.mjs";
export class CardElement extends HTMLElement {
/**
* The element containing the card's name.
*/
nameElement
/**
* The element containing the card's contents.
*/
contentsElement
/**
* The element consisting of a button which requests the load of the card.
*/
loadElement
/**
* Get the {@link URL} this card is available at via the `href` attribute.
*
* @returns {URL} The URL in question.
*/
getCardHref() {
return new URL(this.getAttribute("href"))
}
/**
* Get the human-readable name of this card via the `href` attribute.
*
* @returns {string} The name in question.
*/
getCardName() {
return nameFromFileURL(this.getCardHref())
}
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
const templateElement = document.getElementById("template-card")
const instanceElement = templateElement.content.cloneNode(true)
this.nameElement = document.createElement("span")
this.nameElement.setAttribute("slot", "card-name")
this.nameElement.innerText = this.getCardName()
this.contentsElement = document.createElement("div")
this.contentsElement.setAttribute("slot", "card-contents")
this.loadElement = document.createElement("button")
this.loadElement.innerText = "Load"
this.loadElement.addEventListener("click", () => this.loadContents())
this.contentsElement.appendChild(this.loadElement)
this.appendChild(this.nameElement)
this.appendChild(this.contentsElement)
const shadow = this.attachShadow({ mode: "open" })
shadow.appendChild(instanceElement)
}
/**
* Get the card's {@link VaultFile} via the `href` attribute.
*
* @returns {Promise<VaultFile>} The VaultFile.
*/
async getCardVaultFile() {
// noinspection ES6RedundantAwait
return await getVaultFile(this.getCardHref())
}
/**
* Get the card's contents rendered in HTML.
*
* @returns {Promise<String>} The unsanitized HTML.
*/
async getCardContents() {
const file = await this.getCardVaultFile()
switch(file.kind) {
case "page":
return `<x-page markdown="${file.contents}"></x-page>`
case "canvas":
return `<x-canvas json="${file.contents}"></x-canvas>"`
default:
return ""
}
}
/**
* Load the card's contents.
*
* @returns {Promise<void>} Nothing.
*/
async loadContents() {
this.loadElement.disable()
const contents = await this.getCardContents()
this.loadElement.removeEventListener("click")
this.contentsElement.innerHTML = contents
}
}

67
src/elements/canvas.mjs Normal file
View file

@ -0,0 +1,67 @@
export class CanvasElement extends HTMLElement {
static getTemplate() {
return document.getElementById("template-canvas")
}
parsedJSON
canvasElement
nodeElements = []
edgeElements = []
constructor() {
super()
this.parsedJSON = JSON.parse(this.getAttribute("contents"))
}
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
const instanceDocument = CanvasElement.getTemplate().content.cloneNode(true)
const shadow = this.attachShadow({ mode: "open" })
this.canvasElement = instanceDocument.querySelector(".canvas")
let minX = { x: Infinity, width: 0 }
let minY = { y: Infinity, height: 0 }
let maxX = { x: -Infinity, width: 0 }
let maxY = { y: -Infinity, height: 0 }
for(const node of this.parsedJSON["nodes"]) {
if(node["type"] === "file") {
if(node["x"] < minX["x"]) minX = node
if(node["y"] < minY["y"]) minY = node
if(node["x"] + node["width"] > maxX["x"] + node["width"]) maxX = node
if(node["y"] + node["height"] > maxY["y"] + node["height"]) maxY = node
}
else {
console.warn("Encountered node of unimplemented type: ", node["type"])
}
}
console.debug("minX:", minX, "| minY:", minY, "| maxX:", maxX, "| maxY:", maxY)
for(const node of this.parsedJSON["nodes"]) {
if(node["type"] === "file") {
const element = document.createElement("x-node-file")
element.setAttribute("file", node["file"])
element.setAttribute("id", node["id"])
element.setAttribute("x", node["x"] - minX["x"])
element.setAttribute("y", node["y"] - minY["y"])
element.setAttribute("width", node["width"])
element.setAttribute("height", node["height"])
element.setAttribute("color", node["color"])
this.nodeElements.push(element)
this.canvasElement.appendChild(element)
}
else {
console.warn("Encountered node of unimplemented type: ", node["type"])
}
}
this.canvasElement.style["width"] = `${maxX["x"] + maxX["width"] - minX["x"]}px`
this.canvasElement.style["height"] = `${maxY["y"] + maxY["height"] - minY["y"]}px`
shadow.appendChild(instanceDocument)
}
}

View file

@ -1,9 +0,0 @@
export class HashtagElement extends HTMLElement {
connectedCallback() {
const templateElement = document.getElementById("template-hashtag")
const instanceElement = templateElement.content.cloneNode(true)
const shadow = this.attachShadow({ mode: "open" })
shadow.appendChild(instanceElement)
}
}

3
src/elements/index.mjs Normal file
View file

@ -0,0 +1,3 @@
export {NodeFileElement} from "./node.mjs"
export {MarkdownElement, HashtagElement, WikilinkElement} from "./markdown.mjs"
export {CanvasElement} from "./canvas.mjs"

145
src/elements/markdown.mjs Normal file
View file

@ -0,0 +1,145 @@
import { Marked } from "https://unpkg.com/marked@9.1.2/lib/marked.esm.js";
/**
* Element rendering the Markdown contents of an Obsidian page.
*/
export class MarkdownElement extends HTMLElement {
static marked = new Marked({
extensions: [
{
name: "frontmatter",
level: "block",
start(src) {
return src.match(/^---/)?.index
},
tokenizer(src, tokens) {
const match = src.match(/^---(.+)?\n(.+)\n---\n/)
if(match) {
return {
type: "frontmatter",
raw: match[0],
lang: match[1],
data: match[2],
}
}
},
renderer(token) {
return `<pre><code lang="${token.lang}">${token.data}</code></pre>`;
}
},
{
name: "wikilink",
level: "inline",
start(src) {
return src.match(/^\[\[/)?.index
},
tokenizer(src, tokens) {
const match = src.match(/^\[\[([^|\]]+)(?:\|([^\]]+))?]]/)
if(match) {
return {
type: "wikilink",
raw: match[0],
wref: match[1],
text: match[2],
}
}
},
renderer(token) {
return `<x-wikilink wref="${token.wref}"><span slot="wikilink-text">${token.text ?? token.wref}</span></x-wikilink>`
},
},
{
name: "hashtag",
level: "inline",
start(src) {
return src.match(/^#/)?.index
},
tokenizer(src, tokens) {
const match = src.match(/^#([A-Za-z0-9]+)/)
if(match) {
return {
type: "hashtag",
raw: match[0],
tag: match[1],
}
}
},
renderer(token) {
return `<x-hashtag><span slot="hashtag-text">#${token.tag}</span></x-hashtag>`
}
}
]
})
contentsElement
static getTemplate() {
return document.getElementById("template-markdown")
}
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
const instanceDocument = MarkdownElement.getTemplate().content.cloneNode(true)
const shadow = this.attachShadow({ mode: "open" })
const markdown = this.getAttribute("contents")
this.contentsElement = document.createElement("div")
this.contentsElement.setAttribute("slot", "markdown-contents")
this.contentsElement.innerHTML = MarkdownElement.marked.parse(markdown)
this.appendChild(this.contentsElement)
shadow.appendChild(instanceDocument)
}
}
/**
* Element rendering an Obsidian Hashtag.
*/
export class HashtagElement extends HTMLElement {
static getTemplate() {
return document.getElementById("template-hashtag")
}
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
const instanceDocument = HashtagElement.getTemplate().content.cloneNode(true)
const shadow = this.attachShadow({ mode: "open" })
shadow.appendChild(instanceDocument)
}
}
/**
* Element rendering an Obsidian Wikilink.
*/
export class WikilinkElement extends HTMLElement {
static getTemplate() {
return document.getElementById("template-wikilink")
}
/**
* Get the card this Wikilink points to via the `wref` attribute.
*
* @returns {String} The card target.
*/
getWikilinkWref() {
return this.getAttribute("wref")
}
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
const instanceDocument = WikilinkElement.getTemplate().content.cloneNode(true)
const shadow = this.attachShadow({ mode: "open" })
const wref = this.getWikilinkWref()
this.addEventListener("click", function() {
console.warn("Would move to", wref, ", but navigation is not yet implemented.")
})
shadow.appendChild(instanceDocument)
}
}

161
src/elements/node.mjs Normal file
View file

@ -0,0 +1,161 @@
import { configFromWindow } from "../config.mjs";
/**
* Element representing the generic skeleton of an Obsidian Canvas node.
*/
export class NodeElement extends HTMLElement {
x
y
width
height
color
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
this.id = this.getAttribute("id")
this.x = this.getAttribute("x")
this.y = this.getAttribute("y")
this.width = this.getAttribute("width")
this.height = this.getAttribute("height")
this.color = this.getAttribute("color")
}
colorToHex() {
if(this?.color?.startsWith("#")) {
// This is an hex color
return this.color
}
else {
// TODO: Check which colors correspond to what
return {}[this.color]
}
}
}
/**
* Error in the fetching of a file.
*/
export class FetchError extends Error {
/**
* The {@link Response} object of the failed request.
*/
response
constructor(response, message) {
super(message)
this.response = response
}
}
/**
* Element representing the skeleton of an Obsidian Canvas node pointing to a file.
*
* Requires the following attributes:
* - `file`: wref to the target file
* - `id`: id unique to the node
* - `x`: horizontal translation
* - `y`: vertical translation
* - `width`: width of the card in px
* - `height`: height of the card in px
* - (optional) `color`: custom Obsidian color of the card
*/
export class NodeFileElement extends NodeElement {
static type = "file"
file
fileName
fileExtension
fileLeaf() {
return this.file.split("/").at(-1)
}
fileDetails() {
const split = this.fileLeaf().split(".")
const name = split.slice(0, -1)
const extension = split.at(-1)
return [name, extension]
}
static getTemplate() {
return document.getElementById("template-node-file")
}
instanceElement
nameSlotted
placeholderSlotted
contentsSlotted
loadButton
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
super.connectedCallback()
this.file = this.getAttribute("file")
const [fileName, fileExtension] = this.fileDetails()
this.fileName = fileName
this.fileExtension = fileExtension
const instanceDocument = NodeFileElement.getTemplate().content.cloneNode(true)
const shadow = this.attachShadow({ mode: "open" })
this.instanceElement = instanceDocument.querySelector(".node.node-file")
this.instanceElement.style["left"] = `${this.x}px`
this.instanceElement.style["top"] = `${this.y}px`
this.instanceElement.style["width"] = `${this.width}px`
this.instanceElement.style["height"] = `${this.height}px`
this.instanceElement.style["--node-color"] = this.colorToHex()
this.instanceElement.classList.add("node-empty")
this.instanceElement.classList.remove("node-full")
this.nameSlotted = document.createElement("span")
this.nameSlotted.slot = "node-title"
this.nameSlotted.innerText = this.fileName
this.appendChild(this.nameSlotted)
this.placeholderSlotted = document.createElement("div")
this.placeholderSlotted.slot = "node-contents"
this.loadButton = document.createElement("button")
this.loadButton.innerText = "Load"
this.loadButton.addEventListener("click", this.fillNode.bind(this))
this.placeholderSlotted.appendChild(this.loadButton)
this.appendChild(this.placeholderSlotted)
shadow.appendChild(instanceDocument)
}
contents
async fetchContents() {
console.info("Fetching:", this.file)
const url = new URL(this.file, configFromWindow()["vault"])
const response = await fetch(url, {})
if(!response.ok) throw new FetchError(response, "Fetch response is not ok")
this.contents = await response.text()
}
async fillNode() {
this.loadButton.disabled = true
this.placeholderSlotted.remove()
this.placeholderSlotted = null
await this.fetchContents()
this.instanceElement.classList.remove("node-empty")
this.instanceElement.classList.add("node-full")
this.contentsSlotted = document.createElement({
"md": "x-markdown",
"canvas": "x-canvas",
}[this.fileExtension] ?? "div")
this.contentsSlotted.slot = "node-contents"
this.contentsSlotted.setAttribute("contents", this.contents)
this.appendChild(this.contentsSlotted)
}
}

View file

@ -1,30 +0,0 @@
export class WikilinkElement extends HTMLElement {
/**
* Get the card this Wikilink points to via the `wref` attribute.
*
* @returns {String} The card target.
*/
getWikilinkWref() {
return this.getAttribute("wref")
}
/**
* The clickable anchor resolving the Wikilink.
*/
anchorElement
connectedCallback() {
const templateElement = document.getElementById("template-wikilink")
const instanceElement = templateElement.content.cloneNode(true)
const wref = this.getWikilinkWref()
this.anchorElement = this.querySelector('[slot="wikilink-text"]')
this.anchorElement.addEventListener("click", function() {
console.warn("Would move to", wref, "but navigation is not yet implemented.")
})
const shadow = this.attachShadow({ mode: "open" })
shadow.appendChild(instanceElement)
}
}

View file

@ -1,110 +0,0 @@
/**
* No valid kind was determined during the execution of {@link kindFromExtension}.
*/
export class UnknownFileKindError extends Error {}
/**
* Try to determine the {@link VaultFile.kind} from a file's extension.
*
* @param extension {string} The file's extension, with no leading colon.
* @returns {"page"|"canvas"} The successfully determined file type.
*/
function kindFromExtension(extension) {
if(extension === "md") return "page"
else if(extension === ".canvas") return "canvas"
throw UnknownFileKindError("No file type matched the given file extension.")
}
/**
* A file contained in an Obsidian Vault.
*/
export class VaultFile {
/**
* The type of file.
*
* @type {"page"|"canvas"}
*/
kind
/**
* The contents of the file.
*
* To be interpreted differently depending on the {@link kind} of the object.
*
* @type {any}
*/
contents
constructor({ kind, contents }) {
this.kind = kind
this.contents = contents
}
}
/**
* An error which occoured during a {@link fetch} request.
*/
export class VaultFetchError extends Error {
/**
* The {@link Response} object of the failed request.
*/
response
constructor(response, message) {
super(message);
}
}
/**
* Get the name of a file from its {@link URL}.
*
* @param fileURL {URL} The URL to read.
* @returns {string} The name of the file.
*/
export function nameFromFileURL(fileURL) {
return decodeURIComponent(fileURL.pathname.split("/").at(-1).split(".").slice(0, -1).join("."))
}
/**
* Fetch a {@link VaultFile} from the given {@link URL}.
*
* @param fileURL {URL} The URL where the file is accessible at.
* @returns {VaultFile} The fetched {@link VaultFile}.
*/
async function fetchVaultFile(fileURL) {
console.info("Fetching", fileURL.href, "...")
const response = await fetch(fileURL, {})
if(!response.ok) throw new VaultFetchError(response, "Fetch response is not ok")
const contents = await response.text()
const kind = kindFromExtension(fileURL.pathname.split(".").at(-1))
return new VaultFile({kind, contents})
}
/**
* A cache mapping file URLs to {@link VaultFile}s.
*
* @type {{[fileURL: URL]: VaultFile}}
*/
const VAULT_CACHE = {}
/**
* Try to get a {@link VaultFile} from the {@link VAULT_CACHE}, then, if it isn't available, {@link fetchVaultFile} it.
*
* @param fileURL {URL} The URL where the file should be accessible at.
* @returns {VaultFile} The fetched {@link VaultFile}
*/
export async function getVaultFile(fileURL) {
const cached = VAULT_CACHE[fileURL]
if(cached !== undefined) return cached
const vaultFile = await fetchVaultFile(fileURL)
VAULT_CACHE[fileURL] = vaultFile
return vaultFile
}

View file

@ -1,7 +1,7 @@
import {CardElement} from "./card.mjs" import { CanvasElement, HashtagElement, MarkdownElement, NodeFileElement, WikilinkElement } from "./elements/index.mjs";
import {WikilinkElement} from "./elements/wikilink.mjs";
import {HashtagElement} from "./elements/hashtag.mjs";
customElements.define("x-card", CardElement) customElements.define("x-node-file", NodeFileElement)
customElements.define("x-markdown", MarkdownElement)
customElements.define("x-wikilink", WikilinkElement) customElements.define("x-wikilink", WikilinkElement)
customElements.define("x-hashtag", HashtagElement) customElements.define("x-hashtag", HashtagElement)
customElements.define("x-canvas", CanvasElement)

View file

@ -1,62 +0,0 @@
import {Marked} from "https://unpkg.com/marked@9.1.2/lib/marked.esm.js"
/**
* The {@link Marked} instance to use for parsing page contents.
*
* @type {Marked}
*/
const marked = new Marked({
extensions: [
{
name: "wikilink",
level: "inline",
start(src) {
return src.match(/^\[\[/)?.index
},
tokenizer(src, tokens) {
const match = src.match(/^\[\[([^|\]]+)(?:\|([^\]]+))?]]/)
if(match) {
return {
type: "wikilink",
raw: match[0],
wref: match[1],
text: match[2],
}
}
},
renderer(token) {
return `<x-wikilink wref="${token.wref}"><span slot="wikilink-text">${token.text ?? token.wref}</span></x-wikilink>`
},
},
{
name: "hashtag",
level: "inline",
start(src) {
return src.match(/^#/)?.index
},
tokenizer(src, tokens) {
const match = src.match(/^#([A-Za-z0-9]+)/)
if(match) {
return {
type: "hashtag",
raw: match[0],
tag: match[1],
}
}
},
renderer(token) {
return `<x-hashtag><span slot="hashtag-text">#${token.tag}</span></x-hashtag>`
}
}
]
})
/**
* Parse the given text string as Markdown using {@link marked}, emitting HTML.
*
* @param contents The text string to parsed.
* @returns {String} The resulting HTML.
*/
export function parsePageContents(contents) {
return marked.parse(contents)
}

View file

@ -1 +0,0 @@