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:
parent
6bb92bc00d
commit
45c3afd7d4
14 changed files with 430 additions and 338 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -0,0 +1 @@
|
||||||
|
/examples
|
|
@ -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>
|
||||||
|
|
64
index.html
64
index.html
|
@ -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>
|
101
src/card.mjs
101
src/card.mjs
|
@ -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
67
src/elements/canvas.mjs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
3
src/elements/index.mjs
Normal 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
145
src/elements/markdown.mjs
Normal 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
161
src/elements/node.mjs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
110
src/fetch.mjs
110
src/fetch.mjs
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
|
62
src/page.mjs
62
src/page.mjs
|
@ -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)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
Loading…
Reference in a new issue