mirror of
https://github.com/glassflame/glassflame.github.io.git
synced 2024-11-24 00:54:26 +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">
|
||||
<option name="myName" value="Project Default" />
|
||||
<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="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="8">
|
||||
<list size="9">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
|
@ -15,6 +16,7 @@
|
|||
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||
<item index="6" class="java.lang.String" itemvalue="x-card" />
|
||||
<item index="7" class="java.lang.String" itemvalue="x-hashtag" />
|
||||
<item index="8" class="java.lang.String" itemvalue="x-node-file" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
|
@ -26,7 +28,7 @@
|
|||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</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_ignoreAnnotatedVariables" value="false" />
|
||||
</inspection_tool>
|
||||
|
|
64
index.html
64
index.html
|
@ -9,51 +9,77 @@
|
|||
@import "style/dark.css";
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script type="module" src="src/index.mjs"></script>
|
||||
</head>
|
||||
<body>
|
||||
<template id="template-card">
|
||||
<template id="template-node-file">
|
||||
<style>
|
||||
.card {
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
.node {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.node-file {
|
||||
outline-width: 2px;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
|
||||
overflow-x: clip;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.node-empty {
|
||||
outline-style: dashed;
|
||||
}
|
||||
|
||||
.node-full {
|
||||
outline-style: solid;
|
||||
}
|
||||
</style>
|
||||
<div class="card">
|
||||
<article class="node node-file">
|
||||
<h1>
|
||||
<slot name="card-name">Card name</slot>
|
||||
<slot name="node-title">Node title</slot>
|
||||
</h1>
|
||||
<div>
|
||||
<slot name="card-contents">Card contents</slot>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="node-contents">Node contents</slot>
|
||||
</article>
|
||||
</template>
|
||||
<template id="template-markdown">
|
||||
<slot name="markdown-contents"></slot>
|
||||
</template>
|
||||
<template id="template-canvas">
|
||||
<style>
|
||||
.canvas {
|
||||
|
||||
}
|
||||
</style>
|
||||
<div class="canvas"></div>
|
||||
</template>
|
||||
<template id="template-wikilink">
|
||||
<style>
|
||||
.wikilink {
|
||||
text-decoration: underline 1px solid var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline 1px solid currentColor;
|
||||
cursor: pointer;
|
||||
}
|
||||
</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 id="template-hashtag">
|
||||
<style>
|
||||
.hashtag {
|
||||
background-color: rgba(var(--color-accent), 0.2);
|
||||
color: rgb(var(--color-accent))
|
||||
background-color: color-mix(in srgb, var(--color-accent) 20%, transparent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
</style>
|
||||
<span class="hashtag"><slot name="hashtag-text">#Hashtag</slot></span>
|
||||
</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>
|
||||
<x-card href="https://raw.githubusercontent.com/Steffo99/appunti-magistrali/main/8%20-%20Sistemi%20complessi/1%20-%20Sistemi%20dinamici/condizione%20iniziale.md"></x-card>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<x-canvas contents=""/>
|
||||
</body>
|
||||
</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 {WikilinkElement} from "./elements/wikilink.mjs";
|
||||
import {HashtagElement} from "./elements/hashtag.mjs";
|
||||
import { CanvasElement, HashtagElement, MarkdownElement, NodeFileElement, WikilinkElement } from "./elements/index.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-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