mirror of
https://github.com/glassflame/glassflame.github.io.git
synced 2024-11-22 08:04:27 +00:00
Continue refactoring #2
This commit is contained in:
parent
8b45e78f4c
commit
610c849264
26 changed files with 663 additions and 288 deletions
|
@ -1,5 +1,5 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<state>
|
<state>
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
</state>
|
</state>
|
||||||
</component>
|
</component>
|
|
@ -5,6 +5,7 @@
|
||||||
<inspection_tool class="CommaExpressionJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="CommaExpressionJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="CssUnusedSymbol" 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="ES6PreferShortImport" enabled="false" level="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>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="JavaScriptLibraryMappings">
|
<component name="JavaScriptLibraryMappings">
|
||||||
<file url="file://$PROJECT_DIR$" libraries="{marked}" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
226
index.html
226
index.html
|
@ -4,6 +4,9 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>WIP: Obsiview</title>
|
<title>WIP: Obsiview</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<!-- Interaction scripts -->
|
||||||
|
<script type="module" src="src/index.mjs"></script>
|
||||||
|
<!-- Global style -->
|
||||||
<style>
|
<style>
|
||||||
@import "style/light.css";
|
@import "style/light.css";
|
||||||
@import "style/dark.css";
|
@import "style/dark.css";
|
||||||
|
@ -35,103 +38,14 @@
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" src="src/index.mjs"></script>
|
<!-- Templates -->
|
||||||
<template id="template-display">
|
<template id="template-vault">
|
||||||
<slot name="display-container">{Displayed content}</slot>
|
|
||||||
</template>
|
|
||||||
<template id="template-node-group">
|
|
||||||
<style>
|
<style>
|
||||||
.node-group {
|
|
||||||
outline: var(--node-group-border-width) solid var(--color-node);
|
|
||||||
background-color: color-mix(in srgb, var(--color-node) 20%, var(--color-background));
|
|
||||||
border-radius: 0 8px 8px 8px;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-group-label {
|
|
||||||
position: relative;
|
|
||||||
bottom: 14px;
|
|
||||||
left: -12px;
|
|
||||||
transform: translateY(-100%);
|
|
||||||
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
outline: var(--node-group-border-width) solid var(--color-node);
|
|
||||||
background-color: color-mix(in srgb, var(--color-node) 20%, var(--color-background));
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-group-label h1 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<aside class="canvas-item node node-group">
|
<div class="vault">
|
||||||
<h1><slot name="node-group-label">{Group label}</slot></h1>
|
<slot name="vault-child"></slot>
|
||||||
</aside>
|
</div>
|
||||||
</template>
|
|
||||||
<template id="template-node-file">
|
|
||||||
<style>
|
|
||||||
.node-file {
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
--color-node: var(--color-gray);
|
|
||||||
outline: var(--node-file-border-width) solid var(--color-node);
|
|
||||||
background-color: color-mix(in srgb, var(--color-node) 10%, var(--color-background));
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
|
|
||||||
overflow-x: clip;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-file-title {
|
|
||||||
font-size: 2em;
|
|
||||||
margin-block-start: .67em;
|
|
||||||
margin-block-end: .67em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<article class="node-file">
|
|
||||||
<h1 class="node-file-title">
|
|
||||||
<slot name="node-title">{Node title}</slot>
|
|
||||||
</h1>
|
|
||||||
<slot name="node-contents">{Node contents}</slot>
|
|
||||||
</article>
|
|
||||||
</template>
|
|
||||||
<template id="template-node-text">
|
|
||||||
<style>
|
|
||||||
.node-text {
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
--color-node: var(--color-gray);
|
|
||||||
outline: var(--node-file-border-width) solid var(--color-node);
|
|
||||||
background-color: color-mix(in srgb, var(--color-node) 10%, var(--color-background));
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
|
|
||||||
overflow-x: clip;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<article class="node-text">
|
|
||||||
<slot name="node-contents">{Node contents}</slot>
|
|
||||||
</article>
|
|
||||||
</template>
|
|
||||||
<template id="template-edge">
|
|
||||||
<slot name="edge-svg">{Edge SVG}</slot>
|
|
||||||
</template>
|
|
||||||
<template id="template-markdown">
|
|
||||||
<slot name="markdown-contents">{Markdown text}</slot>
|
|
||||||
</template>
|
|
||||||
<template id="template-frontmatter">
|
|
||||||
<style>
|
|
||||||
.frontmatter {
|
|
||||||
opacity: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<pre class="frontmatter"><slot name="frontmatter-contents">{Markdown text}</slot></pre>
|
|
||||||
</template>
|
</template>
|
||||||
<template id="template-canvas">
|
<template id="template-canvas">
|
||||||
<style>
|
<style>
|
||||||
|
@ -162,15 +76,115 @@
|
||||||
<slot name="canvas-nodes">{Canvas nodes}</slot>
|
<slot name="canvas-nodes">{Canvas nodes}</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template id="template-wikilink">
|
<template id="template-display">
|
||||||
|
<slot name="display-container">{Displayed content}</slot>
|
||||||
|
</template>
|
||||||
|
<template id="template-node-group">
|
||||||
<style>
|
<style>
|
||||||
.wikilink {
|
.node {
|
||||||
color: var(--color-accent);
|
outline: var(--node-group-border-width) solid var(--color-node);
|
||||||
text-decoration: underline 1px solid currentColor;
|
background-color: color-mix(in srgb, var(--color-node) 20%, var(--color-background));
|
||||||
cursor: pointer;
|
border-radius: 0 8px 8px 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-group-label {
|
||||||
|
position: relative;
|
||||||
|
bottom: 14px;
|
||||||
|
left: -12px;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
outline: var(--node-group-border-width) solid var(--color-node);
|
||||||
|
background-color: color-mix(in srgb, var(--color-node) 20%, var(--color-background));
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-group-label-title {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<a class="wikilink"><slot name="wikilink-text">{Wikilink text}</slot></a>
|
<div class="canvas-item node node-group">
|
||||||
|
<aside class="node-group-label">
|
||||||
|
<h1 class="node-group-label-title"><slot name="node-group-label">{Group label}</slot></h1>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template id="template-node-file">
|
||||||
|
<style>
|
||||||
|
.node {
|
||||||
|
outline: var(--node-file-border-width) solid var(--color-node);
|
||||||
|
background-color: color-mix(in srgb, var(--color-node) 10%, var(--color-background));
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-file {
|
||||||
|
overflow-x: clip;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-file-label {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-block-start: .67em;
|
||||||
|
margin-block-end: .67em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<article class="canvas-item node node-file">
|
||||||
|
<h1 class="node-file-label">
|
||||||
|
<slot name="node-file-label">{Node title}</slot>
|
||||||
|
</h1>
|
||||||
|
<slot name="node-file-contents">{Node contents}</slot>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
<template id="template-node-text">
|
||||||
|
<style>
|
||||||
|
.node {
|
||||||
|
outline: var(--node-file-border-width) solid var(--color-node);
|
||||||
|
background-color: color-mix(in srgb, var(--color-node) 10%, var(--color-background));
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-text {
|
||||||
|
overflow-x: clip;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<article class="canvas-item node node-text">
|
||||||
|
<slot name="node-text-contents">{Node contents}</slot>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
<template id="template-edge">
|
||||||
|
<style>
|
||||||
|
.edge svg line {
|
||||||
|
stroke: var(--color-node);
|
||||||
|
stroke-width: var(--edge-width);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="edge">
|
||||||
|
<slot name="edge-svg">{Edge SVG}</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template id="template-markdown">
|
||||||
|
<style>
|
||||||
|
.markdown
|
||||||
|
</style>
|
||||||
|
<div class="markdown">
|
||||||
|
<slot name="markdown-document">{Markdown text}</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template id="template-frontmatter">
|
||||||
|
<style>
|
||||||
|
.frontmatter {
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="frontmatter">
|
||||||
|
<slot name="frontmatter-contents">{Markdown text}</slot>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template id="template-hashtag">
|
<template id="template-hashtag">
|
||||||
<style>
|
<style>
|
||||||
|
@ -179,7 +193,17 @@
|
||||||
color: 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-tag">{#Hashtag}</slot></span>
|
||||||
|
</template>
|
||||||
|
<template id="template-wikilink">
|
||||||
|
<style>
|
||||||
|
.wikilink {
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: underline 1px solid currentColor;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<a class="wikilink"><slot name="wikilink-anchor">{Wikilink text}</slot></a>
|
||||||
</template>
|
</template>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -41,12 +41,10 @@ export class CustomElement extends HTMLElement {
|
||||||
* Callback automatically called when this element is added to the DOM.
|
* Callback automatically called when this element is added to the DOM.
|
||||||
*/
|
*/
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
// The template to duplicate.
|
|
||||||
const template = this.constructor.getTemplate()
|
|
||||||
// The shadow root, the inner contents of the element..
|
// The shadow root, the inner contents of the element..
|
||||||
const shadow = this.attachShadow({ mode: "open" })
|
const shadow = this.attachShadow({ mode: "open" })
|
||||||
// The element contained inside the shadow root..
|
// The element contained inside the shadow root..
|
||||||
this.#instance = template.content.cloneNode(true)
|
this.#instance = this.constructor.template.content.cloneNode(true)
|
||||||
// Call the custom callback.
|
// Call the custom callback.
|
||||||
this.onConnect()
|
this.onConnect()
|
||||||
// Add the instance to the DOM.
|
// Add the instance to the DOM.
|
||||||
|
|
|
@ -14,43 +14,43 @@ export class CanvasElement extends CustomElement {
|
||||||
* The contents of the Canvas, as they were the last time they were updated.
|
* The contents of the Canvas, as they were the last time they were updated.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
#contents
|
#document
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The contents of the Canvas, as they were the last time they were updated.
|
* The contents of the Canvas, as they were the last time they were updated.
|
||||||
* @returns {string} The raw contents.
|
* @returns {string} The raw contents.
|
||||||
*/
|
*/
|
||||||
get contents() {
|
get document() {
|
||||||
return this.#contents
|
return this.#document
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parsed contents of the Canvas, as they were the last time they were updated.
|
* The parsed contents of the Canvas, as they were the last time they were updated.
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
#parsedContents
|
#parsedDocument
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parsed contents of the Canvas, as they were the last time they were updated.
|
* The parsed contents of the Canvas, as they were the last time they were updated.
|
||||||
* @returns {Object} The parsed contents.
|
* @returns {Object} The parsed contents.
|
||||||
*/
|
*/
|
||||||
get parsedContents() {
|
get parsedDocument() {
|
||||||
return this.#parsedContents
|
return this.#parsedDocument
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the values of {@link contents} and {@link parsedContents} from the `contents` 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 `contents` is not valid JSON.
|
||||||
*/
|
*/
|
||||||
recalculateContents() {
|
recalculateContents() {
|
||||||
this.#contents = this.getAttribute("contents")
|
this.#document = this.getAttribute("contents")
|
||||||
this.#parsedContents = JSON.parse(this.#contents)
|
this.#parsedDocument = JSON.parse(this.#document)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum X node found in the items of this Canvas.
|
* The minimum X node found in the items of this Canvas.
|
||||||
* Used to compute this element's rect.
|
* Used to compute this element's rect.
|
||||||
* Can be computed from {@link contents} with {@link recalculateMinMax}.
|
* Can be computed from {@link document} with {@link recalculateMinMax}.
|
||||||
* @type {{x: number, width: number}}
|
* @type {{x: number, width: number}}
|
||||||
*/
|
*/
|
||||||
minX
|
minX
|
||||||
|
@ -58,7 +58,7 @@ export class CanvasElement extends CustomElement {
|
||||||
/**
|
/**
|
||||||
* The minimum Y node found in the items of this Canvas.
|
* The minimum Y node found in the items of this Canvas.
|
||||||
* Used to compute this element's rect.
|
* Used to compute this element's rect.
|
||||||
* Can be computed from {@link contents} with {@link recalculateMinMax}.
|
* Can be computed from {@link document} with {@link recalculateMinMax}.
|
||||||
* @type {{y: number, height: number}}
|
* @type {{y: number, height: number}}
|
||||||
*/
|
*/
|
||||||
minY
|
minY
|
||||||
|
@ -66,7 +66,7 @@ export class CanvasElement extends CustomElement {
|
||||||
/**
|
/**
|
||||||
* The maximum X node found in the items of this Canvas.
|
* The maximum X node found in the items of this Canvas.
|
||||||
* Used to compute this element's rect.
|
* Used to compute this element's rect.
|
||||||
* Can be computed from {@link contents} with {@link recalculateMinMax}.
|
* Can be computed from {@link document} with {@link recalculateMinMax}.
|
||||||
* @type {{x: number, width: number}}
|
* @type {{x: number, width: number}}
|
||||||
*/
|
*/
|
||||||
maxX
|
maxX
|
||||||
|
@ -74,13 +74,13 @@ export class CanvasElement extends CustomElement {
|
||||||
/**
|
/**
|
||||||
* The maximum Y node found in the items of this Canvas.
|
* The maximum Y node found in the items of this Canvas.
|
||||||
* Used to compute this element's rect.
|
* Used to compute this element's rect.
|
||||||
* Can be computed from {@link contents} with {@link recalculateMinMax}.
|
* Can be computed from {@link document} with {@link recalculateMinMax}.
|
||||||
* @type {{y: number, height: number}}
|
* @type {{y: number, height: number}}
|
||||||
*/
|
*/
|
||||||
maxY
|
maxY
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute {@link minX}, {@link minY}, {@link maxX}, {@link maxY} from {@link contents}.
|
* Compute {@link minX}, {@link minY}, {@link maxX}, {@link maxY} from {@link document}.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
recalculateMinMax() {
|
recalculateMinMax() {
|
||||||
|
@ -90,7 +90,7 @@ export class CanvasElement extends CustomElement {
|
||||||
this.maxX = { x: -Infinity, width: 0 }
|
this.maxX = { x: -Infinity, width: 0 }
|
||||||
this.maxY = { y: -Infinity, height: 0 }
|
this.maxY = { y: -Infinity, height: 0 }
|
||||||
// Iterate over nodes.
|
// Iterate over nodes.
|
||||||
for(const node of this.parsedContents["nodes"]) {
|
for(const node of this.parsedDocument["nodes"]) {
|
||||||
// Convert node values from strings to numbers.
|
// Convert node values from strings to numbers.
|
||||||
let {x, y, width, height} = node
|
let {x, y, width, height} = node
|
||||||
x, y, width, height = Number(x), Number(y), Number(width), Number(height)
|
x, y, width, height = Number(x), Number(y), Number(width), Number(height)
|
||||||
|
@ -142,7 +142,7 @@ export class CanvasElement extends CustomElement {
|
||||||
static NODE_ELEMENT_NAME_PREFIX = "x-node-"
|
static NODE_ELEMENT_NAME_PREFIX = "x-node-"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy and recreate the {@link nodesContainer} with the current {@link parsedContents}, {@link minX}, {@link minY}, {@link maxX}, {@link maxY}.
|
* Destroy and recreate the {@link nodesContainer} with the current {@link parsedDocument}, {@link minX}, {@link minY}, {@link maxX}, {@link maxY}.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
recreateNodes() {
|
recreateNodes() {
|
||||||
|
@ -154,7 +154,7 @@ export class CanvasElement extends CustomElement {
|
||||||
this.nodesContainer = document.createElement("div")
|
this.nodesContainer = document.createElement("div")
|
||||||
this.nodesContainer.slot = this.constructor.NODES_SLOT_NAME
|
this.nodesContainer.slot = this.constructor.NODES_SLOT_NAME
|
||||||
|
|
||||||
for(const node of this.parsedContents["nodes"]) {
|
for(const node of this.parsedDocument["nodes"]) {
|
||||||
let {id, type, color, x, y, width, height} = node
|
let {id, type, color, x, y, width, height} = node
|
||||||
x, y, width, height = Number(x), Number(y), Number(width), Number(height)
|
x, y, width, height = Number(x), Number(y), Number(width), Number(height)
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ export class CanvasElement extends CustomElement {
|
||||||
static EDGE_ELEMENT_NAME = "x-edge"
|
static EDGE_ELEMENT_NAME = "x-edge"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy and recreate the {@link edgesContainer} with the current {@link parsedContents}, {@link minX}, {@link minY}, {@link maxX}, {@link maxY}.
|
* Destroy and recreate the {@link edgesContainer} with the current {@link parsedDocument}, {@link minX}, {@link minY}, {@link maxX}, {@link maxY}.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
recreateEdges() {
|
recreateEdges() {
|
||||||
|
@ -240,7 +240,7 @@ export class CanvasElement extends CustomElement {
|
||||||
this.edgesContainer = document.createElement("div")
|
this.edgesContainer = document.createElement("div")
|
||||||
this.edgesContainer.slot = this.constructor.EDGES_SLOT_NAME
|
this.edgesContainer.slot = this.constructor.EDGES_SLOT_NAME
|
||||||
|
|
||||||
for(const edge of this.parsedContents["edges"]) {
|
for(const edge of this.parsedDocument["edges"]) {
|
||||||
let {id, fromNode, fromSide, toNode, toSide, color, toEnd: arrows} = edge
|
let {id, fromNode, fromSide, toNode, toSide, color, toEnd: arrows} = edge
|
||||||
|
|
||||||
const element = document.createElement(this.constructor.EDGE_ELEMENT_NAME)
|
const element = document.createElement(this.constructor.EDGE_ELEMENT_NAME)
|
||||||
|
@ -263,6 +263,7 @@ export class CanvasElement extends CustomElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnect() {
|
onConnect() {
|
||||||
|
super.onConnect()
|
||||||
this.recalculateContents()
|
this.recalculateContents()
|
||||||
this.recalculateMinMax()
|
this.recalculateMinMax()
|
||||||
this.recreateNodes()
|
this.recreateNodes()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CustomElement } from "src/elements/base.mjs";
|
import { CustomElement } from "../base.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { CanvasElement } from "src/elements/canvas/canvas.mjs";
|
import { CanvasElement } from "../canvas.mjs";
|
||||||
import { CanvasItemElement } from "src/elements/canvas/canvasitem.mjs";
|
import { CanvasItemElement } from "../canvasitem.mjs";
|
||||||
import { findFirstAncestor } from "src/utils/trasversal.mjs";
|
import { findFirstAncestor } from "../../../utils/trasversal.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An edge of a {@link CanvasElement}.
|
* An edge of a {@link CanvasElement}.
|
||||||
*/
|
*/
|
||||||
export class EdgeElement extends CanvasItemElement {
|
export class EdgeElement extends CanvasItemElement {
|
||||||
static getTemplate() {
|
static get template() {
|
||||||
return document.getElementById("template-edge")
|
return document.getElementById("template-edge")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ export class EdgeElement extends CanvasItemElement {
|
||||||
* Recalculate the value of {@link canvas}.
|
* Recalculate the value of {@link canvas}.
|
||||||
*/
|
*/
|
||||||
recalculateCanvas() {
|
recalculateCanvas() {
|
||||||
findFirstAncestor(this, CanvasElement)
|
this.canvas = findFirstAncestor(this, CanvasElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,15 +94,37 @@ export class EdgeElement extends CanvasItemElement {
|
||||||
*/
|
*/
|
||||||
lineElement
|
lineElement
|
||||||
|
|
||||||
// TODO: Last time, you were here!
|
static SVG_ELEMENT_SLOT = "edge-svg"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recreate {@link svgElement} and {@link lineElement} with the current values of the element.
|
* Recreate {@link svgElement} and {@link lineElement} with the current values of the element.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
recreateSvgElement() {
|
recreateSvgElement() {
|
||||||
|
if(this.svgElement) {
|
||||||
|
this.svgElement.remove()
|
||||||
|
this.svgElement = null
|
||||||
|
this.lineElement = null
|
||||||
|
}
|
||||||
|
|
||||||
const [x1, y1] = this.fromNode.edgeHandle(this.nodeFromSide)
|
const [x1, y1] = this.fromNode.edgeHandle(this.nodeFromSide)
|
||||||
const [x2, y2] = this.toNode.edgeHandle(this.nodeToSide)
|
const [x2, y2] = this.toNode.edgeHandle(this.nodeToSide)
|
||||||
|
|
||||||
|
this.lineElement = document.createElementNS("http://www.w3.org/2000/svg", "line")
|
||||||
|
this.lineElement.setAttribute("x1", x1.toString())
|
||||||
|
this.lineElement.setAttribute("y1", y1.toString())
|
||||||
|
this.lineElement.setAttribute("x2", x2.toString())
|
||||||
|
this.lineElement.setAttribute("y2", y2.toString())
|
||||||
|
|
||||||
|
this.svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
||||||
|
this.svgElement.slot = this.constructor.SVG_ELEMENT_SLOT
|
||||||
|
this.svgElement.style.setProperty("position", "absolute")
|
||||||
|
this.svgElement.style.setProperty("left", "0")
|
||||||
|
this.svgElement.style.setProperty("top", "0")
|
||||||
|
this.svgElement.style.setProperty("overflow", "visible")
|
||||||
|
|
||||||
|
this.svgElement.appendChild(this.lineElement)
|
||||||
|
this.appendChild(this.svgElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnect() {
|
onConnect() {
|
||||||
|
@ -110,31 +132,5 @@ export class EdgeElement extends CanvasItemElement {
|
||||||
this.recalculateCanvas()
|
this.recalculateCanvas()
|
||||||
this.recalculateFromTo()
|
this.recalculateFromTo()
|
||||||
this.recreateSvgElement()
|
this.recreateSvgElement()
|
||||||
|
|
||||||
const fromNode = canvas.nodeElements[this.getAttribute("node-from")]
|
|
||||||
const fromSide = this.getAttribute("node-from-side")
|
|
||||||
const [x1, y1] = fromNode.getCenterCoordinatesOfSide(fromSide)
|
|
||||||
|
|
||||||
const toNode = canvas.nodeElements[this.getAttribute("node-to")]
|
|
||||||
const toSide = this.getAttribute("node-to-side")
|
|
||||||
const [x2, y2] = toNode.getCenterCoordinatesOfSide(toSide)
|
|
||||||
|
|
||||||
this.svgSlotted = document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
|
||||||
this.svgSlotted.slot = "edge-svg"
|
|
||||||
this.svgSlotted.style.setProperty("position", "absolute")
|
|
||||||
this.svgSlotted.style.setProperty("left", "0")
|
|
||||||
this.svgSlotted.style.setProperty("top", "0")
|
|
||||||
this.svgSlotted.style.setProperty("overflow", "visible")
|
|
||||||
|
|
||||||
this.lineElement = document.createElementNS("http://www.w3.org/2000/svg", "line")
|
|
||||||
this.lineElement.setAttribute("x1", x1)
|
|
||||||
this.lineElement.setAttribute("y1", y1)
|
|
||||||
this.lineElement.setAttribute("x2", x2)
|
|
||||||
this.lineElement.setAttribute("y2", y2)
|
|
||||||
this.lineElement.style.setProperty("stroke", this.constructor.colorToCSS(this.getAttribute("color")))
|
|
||||||
this.lineElement.style.setProperty("stroke-width", "var(--edge-width)")
|
|
||||||
|
|
||||||
this.svgSlotted.appendChild(this.lineElement)
|
|
||||||
this.appendChild(this.svgSlotted)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
src/elements/canvas/edge/index.mjs
Normal file
1
src/elements/canvas/edge/index.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export {EdgeElement} from "./base.mjs"
|
3
src/elements/canvas/index.mjs
Normal file
3
src/elements/canvas/index.mjs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export {CanvasElement} from "./canvas.mjs"
|
||||||
|
export {EdgeElement} from "./edge/index.mjs"
|
||||||
|
export {NodeFileElement, NodeGroupElement, NodeTextElement} from "./node/index.mjs"
|
|
@ -1,4 +1,4 @@
|
||||||
import { CanvasItemElement } from "src/elements/canvas/canvasitem.mjs";
|
import { CanvasItemElement } from "../canvasitem.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { NodeElement } from "src/elements/canvas/node/base.mjs";
|
import { NodeElement } from "./base.mjs";
|
||||||
import { DisplayElement } from "src/elements/display.mjs";
|
import { DisplayElement } from "../../display.mjs";
|
||||||
import { fileDetails } from "src/utils/file.mjs";
|
import { fileDetails } from "../../../utils/file.mjs";
|
||||||
import { findFirstAncestor } from "src/utils/trasversal.mjs";
|
import { findFirstAncestor } from "../../../utils/trasversal.mjs";
|
||||||
|
|
||||||
|
|
||||||
export class NodeFileElement extends NodeElement {
|
export class NodeFileElement extends NodeElement {
|
||||||
static getTemplate() {
|
static get template() {
|
||||||
return document.getElementById("template-node-file")
|
return document.getElementById("template-node-file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { NodeElement } from "src/elements/canvas/node/base.mjs";
|
import { NodeElement } from "./base.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,6 +6,10 @@ import { NodeElement } from "src/elements/canvas/node/base.mjs";
|
||||||
* Visual only, does not actually contain any other nodes.
|
* Visual only, does not actually contain any other nodes.
|
||||||
*/
|
*/
|
||||||
export class NodeGroupElement extends NodeElement {
|
export class NodeGroupElement extends NodeElement {
|
||||||
|
static get template() {
|
||||||
|
return document.getElementById("template-node-group")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The label text of the group.
|
* The label text of the group.
|
||||||
* Obtained from the `label` attribute of the element.
|
* Obtained from the `label` attribute of the element.
|
||||||
|
@ -41,10 +45,6 @@ export class NodeGroupElement extends NodeElement {
|
||||||
this.appendChild(this.labelSlotted)
|
this.appendChild(this.labelSlotted)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTemplate() {
|
|
||||||
return document.getElementById("template-node-group")
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnect() {
|
onConnect() {
|
||||||
super.onConnect()
|
super.onConnect()
|
||||||
this.recreateLabelElement()
|
this.recreateLabelElement()
|
||||||
|
|
3
src/elements/canvas/node/index.mjs
Normal file
3
src/elements/canvas/node/index.mjs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export {NodeFileElement} from "./file.mjs"
|
||||||
|
export {NodeGroupElement} from "./group.mjs"
|
||||||
|
export {NodeTextElement} from "./text.mjs"
|
|
@ -1,12 +1,11 @@
|
||||||
import { NodeElement } from "src/elements/canvas/node/base.mjs";
|
import { NodeElement } from "./base.mjs"
|
||||||
import { DisplayElement } from "src/elements/display.mjs";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link NodeElement} directly rendering a Markdown document.
|
* A {@link NodeElement} directly rendering a Markdown document.
|
||||||
*/
|
*/
|
||||||
export class NodeTextElement extends NodeElement {
|
export class NodeTextElement extends NodeElement {
|
||||||
static getTemplate() {
|
static get template() {
|
||||||
return document.getElementById("template-node-text")
|
return document.getElementById("template-node-text")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +13,12 @@ export class NodeTextElement extends NodeElement {
|
||||||
* Get the Markdown source of this node from the `document` attribute.
|
* Get the Markdown source of this node from the `document` attribute.
|
||||||
*/
|
*/
|
||||||
get markdownDocument() {
|
get markdownDocument() {
|
||||||
return this.getAttribute("text")
|
return this.getAttribute("document")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The element displaying the contents of the node.
|
* The element displaying the contents of the node.
|
||||||
|
* Can be recreated with {@link recreateContentsElement}.
|
||||||
* @type {MarkdownElement}
|
* @type {MarkdownElement}
|
||||||
*/
|
*/
|
||||||
contentsElement
|
contentsElement
|
||||||
|
@ -27,7 +27,7 @@ export class NodeTextElement extends NodeElement {
|
||||||
* The name of the slot where {@link contentsElement} should be placed in.
|
* The name of the slot where {@link contentsElement} should be placed in.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
static CONTENTS_ELEMENT_SLOT = "node-file-contents"
|
static CONTENTS_ELEMENT_SLOT = "node-text-contents"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recreate {@link labelElement} with the current value of {@link fileName}.
|
* Recreate {@link labelElement} with the current value of {@link fileName}.
|
||||||
|
|
|
@ -1,58 +1,129 @@
|
||||||
|
import { VaultElement } from "./vault.mjs";
|
||||||
|
import { findFirstAncestor } from "../utils/trasversal.mjs";
|
||||||
import { fileDetails } from "../utils/file.mjs";
|
import { fileDetails } from "../utils/file.mjs";
|
||||||
import { CanvasElement } from "./canvas/canvas.mjs";
|
|
||||||
import { MarkdownElement } from "./markdown.mjs";
|
|
||||||
import { FetchError } from "src/elements/canvas/node/base.mjs";
|
|
||||||
import { CustomElement } from "./base.mjs";
|
import { CustomElement } from "./base.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element loading and displaying the contents of a remote file.
|
||||||
|
*/
|
||||||
export class DisplayElement extends CustomElement {
|
export class DisplayElement extends CustomElement {
|
||||||
static getTemplate() {
|
static get template() {
|
||||||
return document.getElementById("template-display")
|
return document.getElementById("template-display")
|
||||||
}
|
}
|
||||||
|
|
||||||
containerSlotted
|
/**
|
||||||
loadButton
|
* The vault this element is displaying content from.
|
||||||
|
* Can be recalculated via {@link recalculateVault}.
|
||||||
|
* @type {VaultElement}
|
||||||
|
*/
|
||||||
|
vault
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recalculate the value of {@link vault}.
|
||||||
|
*/
|
||||||
|
recalculateVault() {
|
||||||
|
this.vault = findFirstAncestor(this, VaultElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path or name of the file this node points to.
|
||||||
|
* @returns {string} The value in question.
|
||||||
|
*/
|
||||||
|
get target() {
|
||||||
|
return this.getAttribute("target")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An element displaying the loading status of the current element.
|
||||||
|
* @type {HTMLDivElement|null}
|
||||||
|
*/
|
||||||
|
loadingElement = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An element displaying the contents of the current element.
|
||||||
|
* @type {HTMLDivElement|null}
|
||||||
|
*/
|
||||||
|
contentsElement = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slot shared by both {@link loadingElement} and {@link contentsElement}.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static CONTAINER_ELEMENT_SLOT = "display-container"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate {@link loadingElement}, removing {@link contentsElement} if it exists.
|
||||||
|
*/
|
||||||
|
recreateLoadingElement() {
|
||||||
|
if(this.loadingElement !== null) {
|
||||||
|
this.loadingElement.remove()
|
||||||
|
this.loadingElement = null
|
||||||
|
}
|
||||||
|
if(this.contentsElement !== null) {
|
||||||
|
this.contentsElement.remove()
|
||||||
|
this.contentsElement = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingElement = document.createElement("div")
|
||||||
|
this.loadingElement.slot = this.constructor.CONTAINER_ELEMENT_SLOT
|
||||||
|
this.loadingElement.innerText = "Loading..."
|
||||||
|
this.appendChild(this.loadingElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate {@link contentsElement}, removing {@link loadingElement} if it exists.
|
||||||
|
*/
|
||||||
|
recreateContentsElement() {
|
||||||
|
if(this.loadingElement !== null) {
|
||||||
|
this.loadingElement.remove()
|
||||||
|
this.loadingElement = null
|
||||||
|
}
|
||||||
|
if(this.contentsElement !== null) {
|
||||||
|
this.contentsElement.remove()
|
||||||
|
this.contentsElement = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const {extension} = fileDetails(this.target)
|
||||||
|
|
||||||
|
switch(extension) {
|
||||||
|
case "md":
|
||||||
|
this.contentsElement = document.createElement("x-markdown")
|
||||||
|
break
|
||||||
|
case "canvas":
|
||||||
|
this.contentsElement = document.createElement("x-canvas")
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.warn("Encountered a file with an unknown extension:", extension)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contentsElement.setAttribute("document", this.document)
|
||||||
|
this.contentsElement.slot = this.constructor.CONTAINER_ELEMENT_SLOT
|
||||||
|
this.appendChild(this.loadingElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The plaintext contents of the {@link target} document.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
document
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the {@link target} {@link document}.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async reloadDocument() {
|
||||||
|
const response = await this.vault.fetchCooldown(this.target)
|
||||||
|
// TODO: Add a check that the request was successful
|
||||||
|
this.document = await response.text()
|
||||||
|
}
|
||||||
|
|
||||||
onConnect() {
|
onConnect() {
|
||||||
this.containerSlotted = document.createElement("div")
|
super.onConnect()
|
||||||
this.containerSlotted.slot = "display-container"
|
this.recalculateVault()
|
||||||
this.loadButton = document.createElement("button")
|
this.recreateLoadingElement()
|
||||||
this.loadButton.innerText = "Load"
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
this.loadButton.addEventListener("click", this.load.bind(this))
|
this.reloadDocument().then(this.recreateContentsElement)
|
||||||
this.containerSlotted.appendChild(this.loadButton)
|
|
||||||
this.appendChild(this.containerSlotted)
|
|
||||||
}
|
|
||||||
|
|
||||||
data
|
|
||||||
|
|
||||||
async fetchData() {
|
|
||||||
const vref = this.getAttribute("vref")
|
|
||||||
const wref = this.getAttribute("wref")
|
|
||||||
const url = new URL(wref, vref)
|
|
||||||
|
|
||||||
const response = await fetch(url, {})
|
|
||||||
|
|
||||||
if(!response.ok) throw new FetchError(response, "Fetch response is not ok")
|
|
||||||
|
|
||||||
this.data = await response.text()
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
|
||||||
this.loadButton.disabled = true
|
|
||||||
|
|
||||||
await this.fetchData()
|
|
||||||
|
|
||||||
this.containerSlotted.remove()
|
|
||||||
this.containerSlotted = null
|
|
||||||
|
|
||||||
const fileExtension = fileDetails(this.getAttribute("wref")).extension
|
|
||||||
|
|
||||||
this.containerSlotted = document.createElement({
|
|
||||||
"md": customElements.getName(MarkdownElement),
|
|
||||||
"canvas": customElements.getName(CanvasElement),
|
|
||||||
}[fileExtension])
|
|
||||||
this.containerSlotted.slot = "display-container"
|
|
||||||
this.containerSlotted.setAttribute("contents", this.data)
|
|
||||||
this.appendChild(this.containerSlotted)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
export {NodeFileElement, NodeGroupElement, NodeTextElement} from "src/elements/canvas/node/base.mjs"
|
export {CanvasElement, NodeFileElement, NodeGroupElement, NodeTextElement, EdgeElement} from "./canvas/index.mjs"
|
||||||
export {MarkdownElement, HashtagElement, WikilinkElement, FrontMatterElement} from "./markdown.mjs"
|
export {MarkdownElement, HashtagElement, WikilinkElement, FrontMatterElement} from "./markdown/index.mjs"
|
||||||
export {CanvasElement} from "./canvas/canvas.mjs"
|
|
||||||
export {DisplayElement} from "./display.mjs"
|
export {DisplayElement} from "./display.mjs"
|
||||||
export {EdgeElement} from "src/elements/canvas/edge/base.mjs"
|
export {VaultElement} from "./vault.mjs"
|
||||||
|
|
73
src/elements/markdown/frontmatter.mjs
Normal file
73
src/elements/markdown/frontmatter.mjs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { CustomElement } from "../base.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element rendering Obsidian front matter.
|
||||||
|
*/
|
||||||
|
export class FrontMatterElement extends CustomElement {
|
||||||
|
static get template() {
|
||||||
|
return document.getElementById("template-frontmatter")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The programming language used to define this front matter, obtained from the `lang` attribute.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
get language() {
|
||||||
|
return this.getAttribute("lang")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text contained in this front matter, obtained from the `data` attribute.
|
||||||
|
*/
|
||||||
|
get data() {
|
||||||
|
return this.getAttribute("data")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element marking the front matter as preformatted text.
|
||||||
|
* Can be recreated with {@link recreatePreCodeElement}.
|
||||||
|
* @type {HTMLPreElement}
|
||||||
|
*/
|
||||||
|
preElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the slot where {@link preElement} should be placed in.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static PRE_ELEMENT_SLOT = "frontmatter-contents"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element displaying the code of the front matter.
|
||||||
|
* Can be recreated with {@link recreatePreCodeElement}.
|
||||||
|
* @type {HTMLElement}
|
||||||
|
*/
|
||||||
|
codeElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate {@link preElement} and {@link codeElement} with the current value of {@link language} and {@link data}.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
recreatePreCodeElement() {
|
||||||
|
if(this.preElement) {
|
||||||
|
this.preElement.remove()
|
||||||
|
this.preElement = null
|
||||||
|
this.codeElement = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.codeElement = document.createElement("code")
|
||||||
|
this.codeElement.setAttribute("lang", this.language)
|
||||||
|
this.codeElement.innerText = this.data
|
||||||
|
|
||||||
|
this.preElement = document.createElement("pre")
|
||||||
|
this.preElement.slot = this.constructor.PRE_ELEMENT_SLOT
|
||||||
|
|
||||||
|
this.preElement.appendChild(this.codeElement)
|
||||||
|
this.appendChild(this.preElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnect() {
|
||||||
|
super.onConnect()
|
||||||
|
this.recreatePreCodeElement()
|
||||||
|
}
|
||||||
|
}
|
52
src/elements/markdown/hashtag.mjs
Normal file
52
src/elements/markdown/hashtag.mjs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { CustomElement } from "../base.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element rendering an Obsidian Hashtag.
|
||||||
|
*/
|
||||||
|
export class HashtagElement extends CustomElement {
|
||||||
|
static get template() {
|
||||||
|
return document.getElementById("template-hashtag")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the hashtag, with no leading hash, obtained from the `tag` attribute.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
get tag() {
|
||||||
|
return this.getAttribute("tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element displaying the hashtag.
|
||||||
|
* Can be recreated with {@link recreateTagElement}.
|
||||||
|
* @type {HTMLSpanElement}
|
||||||
|
*/
|
||||||
|
tagElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the slot where {@link tagElement} should be placed in.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static TAG_ELEMENT_SLOT = "hashtag-tag"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate {@link tagElement} with the current value of {@link tag}.
|
||||||
|
*/
|
||||||
|
recreateTagElement() {
|
||||||
|
if(this.tagElement) {
|
||||||
|
this.tagElement.remove()
|
||||||
|
this.tagElement = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tagElement = document.createElement("span")
|
||||||
|
this.tagElement.slot = this.constructor.TAG_ELEMENT_SLOT
|
||||||
|
this.tagElement.innerText = `#${this.tag}`
|
||||||
|
this.appendChild(this.tagElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnect() {
|
||||||
|
super.onConnect()
|
||||||
|
this.recreateTagElement()
|
||||||
|
}
|
||||||
|
}
|
4
src/elements/markdown/index.mjs
Normal file
4
src/elements/markdown/index.mjs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export {MarkdownElement} from "./renderer.mjs"
|
||||||
|
export {FrontMatterElement} from "./frontmatter.mjs"
|
||||||
|
export {HashtagElement} from "./hashtag.mjs"
|
||||||
|
export {WikilinkElement} from "./wikilink.mjs"
|
|
@ -1,12 +1,20 @@
|
||||||
import { Marked } from "https://unpkg.com/marked@9.1.2/lib/marked.esm.js";
|
import { Marked } from "https://unpkg.com/marked@9.1.2/lib/marked.esm.js";
|
||||||
import { CustomElement } from "./base.mjs";
|
import { CustomElement } from "../base.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element rendering the Markdown contents of an Obsidian page.
|
* Element rendering the Markdown contents of an Obsidian page.
|
||||||
*/
|
*/
|
||||||
export class MarkdownElement extends CustomElement {
|
export class MarkdownElement extends CustomElement {
|
||||||
static marked = new Marked({
|
static get template() {
|
||||||
|
return document.getElementById("template-markdown")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Marked} Markdown renderer.
|
||||||
|
* @type {Marked}
|
||||||
|
*/
|
||||||
|
static MARKED = new Marked({
|
||||||
extensions: [
|
extensions: [
|
||||||
{
|
{
|
||||||
name: "frontmatter",
|
name: "frontmatter",
|
||||||
|
@ -26,7 +34,8 @@ export class MarkdownElement extends CustomElement {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer(token) {
|
renderer(token) {
|
||||||
return `<x-frontmatter><code slot="frontmatter-contents" lang="${token.lang}">${token.data}</code></x-frontmatter>`;
|
// TODO: Doesn't this break if token.data contains quotes?
|
||||||
|
return `<x-frontmatter lang="${token.lang}" data="${token.data}"></x-frontmatter>`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -41,13 +50,13 @@ export class MarkdownElement extends CustomElement {
|
||||||
return {
|
return {
|
||||||
type: "wikilink",
|
type: "wikilink",
|
||||||
raw: match[0],
|
raw: match[0],
|
||||||
wref: match[1],
|
target: match[1],
|
||||||
text: match[2],
|
text: match[2],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer(token) {
|
renderer(token) {
|
||||||
return `<x-wikilink wref="${token.wref}"><span slot="wikilink-text">${token.text ?? token.wref}</span></x-wikilink>`
|
return `<x-wikilink target="${token.target}" text="${token.text}"></x-wikilink>`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -67,61 +76,50 @@ export class MarkdownElement extends CustomElement {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer(token) {
|
renderer(token) {
|
||||||
return `<x-hashtag><span slot="hashtag-text">#${token.tag}</span></x-hashtag>`
|
return `<x-hashtag tag="${token.tag}"></x-hashtag>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
contentsElement
|
/**
|
||||||
|
* Markdown source of the document to render, obtained from the `document` attribute.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
get markdownDocument() {
|
||||||
|
return this.getAttribute("document")
|
||||||
|
}
|
||||||
|
|
||||||
static getTemplate() {
|
/**
|
||||||
return document.getElementById("template-markdown")
|
* Element containing the rendered Markdown source.
|
||||||
|
* Can be recreated with {@link recreateDocumentElement}.
|
||||||
|
* @type {HTMLDivElement}
|
||||||
|
*/
|
||||||
|
documentElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the slot where {@link documentElement} should be placed in.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static DOCUMENT_ELEMENT_SLOT = "markdown-document"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate {@link documentElement} using the current value of {@link markdownDocument}.
|
||||||
|
*/
|
||||||
|
recreateDocumentElement() {
|
||||||
|
if(this.documentElement) {
|
||||||
|
this.documentElement.remove()
|
||||||
|
this.documentElement = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.documentElement = document.createElement("div")
|
||||||
|
this.documentElement.slot = this.constructor.DOCUMENT_ELEMENT_SLOT
|
||||||
|
this.documentElement.innerHTML = this.constructor.MARKED.parse(this.markdownDocument)
|
||||||
|
this.appendChild(this.documentElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnect() {
|
onConnect() {
|
||||||
const markdown = this.getAttribute("contents")
|
super.onConnect()
|
||||||
|
this.recreateDocumentElement()
|
||||||
this.contentsElement = document.createElement("div")
|
|
||||||
this.contentsElement.setAttribute("slot", "markdown-contents")
|
|
||||||
this.contentsElement.innerHTML = MarkdownElement.marked.parse(markdown)
|
|
||||||
|
|
||||||
this.appendChild(this.contentsElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Element rendering Obsidian front matter.
|
|
||||||
*/
|
|
||||||
export class FrontMatterElement extends CustomElement {
|
|
||||||
static getTemplate() {
|
|
||||||
return document.getElementById("template-frontmatter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Element rendering an Obsidian Hashtag.
|
|
||||||
*/
|
|
||||||
export class HashtagElement extends CustomElement {
|
|
||||||
static getTemplate() {
|
|
||||||
return document.getElementById("template-hashtag")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Element rendering an Obsidian Wikilink.
|
|
||||||
*/
|
|
||||||
export class WikilinkElement extends CustomElement {
|
|
||||||
static getTemplate() {
|
|
||||||
return document.getElementById("template-wikilink")
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnect() {
|
|
||||||
const instanceElement = this.instance.querySelector(".wikilink")
|
|
||||||
|
|
||||||
const destinationURL = new URL(window.location)
|
|
||||||
destinationURL.hash = this.getAttribute("wref")
|
|
||||||
|
|
||||||
instanceElement.href = destinationURL
|
|
||||||
}
|
}
|
||||||
}
|
}
|
62
src/elements/markdown/wikilink.mjs
Normal file
62
src/elements/markdown/wikilink.mjs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { CustomElement } from "../base.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element rendering an Obsidian Wikilink.
|
||||||
|
*/
|
||||||
|
export class WikilinkElement extends CustomElement {
|
||||||
|
static get template() {
|
||||||
|
return document.getElementById("template-hashtag")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path or name of the file this node points to.
|
||||||
|
* @returns {string} The value in question.
|
||||||
|
*/
|
||||||
|
get target() {
|
||||||
|
return this.getAttribute("target")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text that should be displayed in this wikilink.
|
||||||
|
* @returns {string} The text in question.
|
||||||
|
*/
|
||||||
|
get text() {
|
||||||
|
return this.getAttribute("text") ?? this.target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element displaying the wikilink.
|
||||||
|
* Can be recreated with {@link recreateTagElement}.
|
||||||
|
* @type {HTMLAnchorElement}
|
||||||
|
*/
|
||||||
|
anchorElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the slot where {@link anchorElement} should be placed in.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static ANCHOR_ELEMENT_SLOT = "wikilink-anchor"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate {@link anchorElement} with the current value of {@link target} and {@link text}.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
recreateTagElement() {
|
||||||
|
if(this.anchorElement) {
|
||||||
|
this.anchorElement.remove()
|
||||||
|
this.anchorElement = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.anchorElement = document.createElement("a")
|
||||||
|
this.anchorElement.slot = this.constructor.ANCHOR_ELEMENT_SLOT
|
||||||
|
this.anchorElement.href = "#" // TODO: Add href behaviour to the anchor.
|
||||||
|
this.anchorElement.innerText = this.text
|
||||||
|
this.appendChild(this.anchorElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnect() {
|
||||||
|
super.onConnect()
|
||||||
|
this.recreateTagElement()
|
||||||
|
}
|
||||||
|
}
|
86
src/elements/vault.mjs
Normal file
86
src/elements/vault.mjs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import { CustomElement } from "./base.mjs";
|
||||||
|
import { sleep } from "../utils/sleep.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element storing information about a Vault for its children.
|
||||||
|
* The first direct children must have a `[slot="vault-child"]` attribute.
|
||||||
|
*/
|
||||||
|
export class VaultElement extends CustomElement {
|
||||||
|
static get template() {
|
||||||
|
return document.getElementById("template-vault")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base URL where the Vault is available at.
|
||||||
|
*/
|
||||||
|
get base() {
|
||||||
|
return this.getAttribute("base")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link fetch} the file at the given path ignoring cooldowns.
|
||||||
|
* @param path {string} The path where the file is located.
|
||||||
|
* @returns {Promise<Response>} The resulting HTTP response.
|
||||||
|
*/
|
||||||
|
async fetchImmediately(path) {
|
||||||
|
const url = new URL(path, this.base)
|
||||||
|
return await fetch(url, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cooldown between two {@link fetchCooldown} requests in milliseconds, as obtained from the `cooldown` parameter.
|
||||||
|
*/
|
||||||
|
get cooldownMs() {
|
||||||
|
return Number(this.getAttribute("cooldown"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue containing the `resolve` functions necessary to make the calls to {@link fetchCooldown} proceed beyond the waiting phase.
|
||||||
|
* To be called by the preceding {@link fetchCooldown} if possible.
|
||||||
|
* @type {((v: unknown) => void)[]}
|
||||||
|
*/
|
||||||
|
#fetchQueue = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<void>} A Promise that will wait for this caller's turn in the {@link #fetchQueue}.
|
||||||
|
*/
|
||||||
|
fetchQueueTurn() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.#fetchQueue.push(resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A promise that will advance the fetch queue after {@link cooldownMs}.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async #scheduleNextFetchQueueTurn() {
|
||||||
|
await sleep(this.cooldownMs)
|
||||||
|
const resolve = this.#fetchQueue.shift()
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link fetch} the file at the given path, awaiting for cooldowns to expire.
|
||||||
|
* @param path {string} The path where the file is located.
|
||||||
|
* @returns {Promise<Response>} The resulting HTTP response.
|
||||||
|
*/
|
||||||
|
async fetchCooldown(path) {
|
||||||
|
// Sit waiting in queue
|
||||||
|
if(this.#fetchQueue.length > 0) {
|
||||||
|
await this.fetchQueueTurn()
|
||||||
|
}
|
||||||
|
// Perform the request
|
||||||
|
const result = await this.fetchImmediately(path)
|
||||||
|
// Start the next item in queue
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
|
this.#scheduleNextFetchQueueTurn()
|
||||||
|
// Return the request's result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnect() {
|
||||||
|
super.onConnect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import { CanvasElement, HashtagElement, MarkdownElement, NodeFileElement, WikilinkElement, DisplayElement, EdgeElement, NodeGroupElement, NodeTextElement, FrontMatterElement } from "./elements/index.mjs";
|
import { CanvasElement, HashtagElement, NodeFileElement, WikilinkElement, DisplayElement, EdgeElement, NodeGroupElement, NodeTextElement, FrontMatterElement, MarkdownElement } from "./elements/index.mjs";
|
||||||
import { configFromWindow } from "./config.mjs";
|
|
||||||
|
|
||||||
customElements.define("x-node-file", NodeFileElement)
|
customElements.define("x-node-file", NodeFileElement)
|
||||||
customElements.define("x-node-text", NodeTextElement)
|
customElements.define("x-node-text", NodeTextElement)
|
||||||
|
@ -11,10 +10,3 @@ customElements.define("x-hashtag", HashtagElement)
|
||||||
customElements.define("x-canvas", CanvasElement)
|
customElements.define("x-canvas", CanvasElement)
|
||||||
customElements.define("x-display", DisplayElement)
|
customElements.define("x-display", DisplayElement)
|
||||||
customElements.define("x-edge", EdgeElement)
|
customElements.define("x-edge", EdgeElement)
|
||||||
|
|
||||||
const config = configFromWindow()
|
|
||||||
const displayElement = document.createElement("x-display")
|
|
||||||
displayElement.setAttribute("vref", config.vref)
|
|
||||||
displayElement.setAttribute("wref", config.wref)
|
|
||||||
displayElement.setAttribute("root", "")
|
|
||||||
document.body.appendChild(displayElement)
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
export function filePath(file) {
|
/**
|
||||||
|
* Compute a file path, resolving `.` and `..`.
|
||||||
|
* @param path The file path.
|
||||||
|
* @returns {string[]} Array of directories from the root of the path to the target location.
|
||||||
|
*/
|
||||||
|
export function filePath(path) {
|
||||||
const stack = []
|
const stack = []
|
||||||
|
|
||||||
for(const part of file.split("/")) {
|
for(const part of path.split("/")) {
|
||||||
if(part === ".") {
|
if(part === ".") {
|
||||||
// noinspection UnnecessaryContinueJS
|
// noinspection UnnecessaryContinueJS
|
||||||
continue
|
continue
|
||||||
|
|
7
src/utils/sleep.mjs
Normal file
7
src/utils/sleep.mjs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Sleep.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function sleep(timeMs) {
|
||||||
|
return await new Promise(resolve => setTimeout(resolve, timeMs))
|
||||||
|
}
|
Loading…
Reference in a new issue