Compare commits
2 Commits
6478f54689
...
9ba719e8d3
Author | SHA1 | Date | |
---|---|---|---|
|
9ba719e8d3 | ||
|
fe8aa24ca8 |
187
Main.tscn
187
Main.tscn
@ -6,3 +6,190 @@
|
|||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
script = ExtResource( 1 )
|
script = ExtResource( 1 )
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
anchor_left = 0.0380859
|
||||||
|
anchor_top = 0.087
|
||||||
|
anchor_right = 0.961914
|
||||||
|
anchor_bottom = 0.936667
|
||||||
|
margin_top = -0.200005
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": true
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
|
margin_right = 1182.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 146.0
|
||||||
|
margin_top = 9.0
|
||||||
|
margin_right = 231.0
|
||||||
|
margin_bottom = 23.0
|
||||||
|
text = "Server folder:"
|
||||||
|
|
||||||
|
[node name="ServerPathLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 235.0
|
||||||
|
margin_top = 9.0
|
||||||
|
margin_right = 491.0
|
||||||
|
margin_bottom = 23.0
|
||||||
|
rect_min_size = Vector2( 256, 0 )
|
||||||
|
text = "/home/username/long/path/to/server"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="OpenServerFolderButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 495.0
|
||||||
|
margin_right = 554.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
rect_min_size = Vector2( 0, 32 )
|
||||||
|
text = "Open..."
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 558.0
|
||||||
|
margin_top = 9.0
|
||||||
|
margin_right = 632.0
|
||||||
|
margin_bottom = 23.0
|
||||||
|
text = "Server port:"
|
||||||
|
|
||||||
|
[node name="SpinBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 636.0
|
||||||
|
margin_right = 710.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
min_value = 81.0
|
||||||
|
max_value = 8000.0
|
||||||
|
value = 3001.0
|
||||||
|
|
||||||
|
[node name="StartServerButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 714.0
|
||||||
|
margin_right = 799.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
rect_min_size = Vector2( 0, 32 )
|
||||||
|
text = "Start server"
|
||||||
|
|
||||||
|
[node name="ServerStatusLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 803.0
|
||||||
|
margin_top = 9.0
|
||||||
|
margin_right = 914.0
|
||||||
|
margin_bottom = 23.0
|
||||||
|
text = "Server is running!"
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 918.0
|
||||||
|
margin_right = 1036.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
rect_min_size = Vector2( 0, 32 )
|
||||||
|
size_flags_horizontal = 12
|
||||||
|
text = "Open in browser"
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer"]
|
||||||
|
margin_top = 36.0
|
||||||
|
margin_right = 34.0
|
||||||
|
margin_bottom = 50.0
|
||||||
|
size_flags_horizontal = 0
|
||||||
|
text = "Files:"
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
|
||||||
|
margin_top = 54.0
|
||||||
|
margin_right = 1182.0
|
||||||
|
margin_bottom = 611.0
|
||||||
|
size_flags_vertical = 3
|
||||||
|
split_offset = -263
|
||||||
|
|
||||||
|
[node name="Tree" type="Tree" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
margin_right = 322.0
|
||||||
|
margin_bottom = 557.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
margin_left = 334.0
|
||||||
|
margin_right = 1182.0
|
||||||
|
margin_bottom = 557.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 557.0
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer"]
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 557.0
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 24.0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
margin_top = 5.0
|
||||||
|
margin_right = 32.0
|
||||||
|
margin_bottom = 19.0
|
||||||
|
text = "Title:"
|
||||||
|
|
||||||
|
[node name="DocTitleLineEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
margin_left = 36.0
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 24.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
placeholder_text = "(Optional)"
|
||||||
|
|
||||||
|
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
margin_top = 28.0
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 52.0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer2"]
|
||||||
|
margin_top = 5.0
|
||||||
|
margin_right = 34.0
|
||||||
|
margin_bottom = 19.0
|
||||||
|
text = "Date:"
|
||||||
|
|
||||||
|
[node name="DocDateLineEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer2"]
|
||||||
|
margin_left = 38.0
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 24.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
placeholder_text = "(Optional)"
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
margin_top = 56.0
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 557.0
|
||||||
|
size_flags_vertical = 3
|
||||||
|
split_offset = 328
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer"]
|
||||||
|
margin_right = 378.0
|
||||||
|
margin_bottom = 501.0
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
margin_right = 378.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
text = "Content"
|
||||||
|
|
||||||
|
[node name="DocumentInputTextEdit" type="TextEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 378.0
|
||||||
|
margin_bottom = 501.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
show_line_numbers = true
|
||||||
|
wrap_enabled = true
|
||||||
|
|
||||||
|
[node name="VBoxContainer2" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer"]
|
||||||
|
margin_left = 390.0
|
||||||
|
margin_right = 848.0
|
||||||
|
margin_bottom = 501.0
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer2"]
|
||||||
|
margin_right = 458.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
text = "Content Preview (Not accurate!)"
|
||||||
|
|
||||||
|
[node name="ContentPreviewRichTextLabel" type="RichTextLabel" parent="VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer/HSplitContainer/VBoxContainer2"]
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 458.0
|
||||||
|
margin_bottom = 501.0
|
||||||
|
size_flags_vertical = 3
|
||||||
|
@ -24,6 +24,11 @@ config/name="Ticle Frontend"
|
|||||||
run/main_scene="res://Main.tscn"
|
run/main_scene="res://Main.tscn"
|
||||||
config/icon="res://icon.png"
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/size/width=1280
|
||||||
|
window/size/height=720
|
||||||
|
|
||||||
[editor_plugins]
|
[editor_plugins]
|
||||||
|
|
||||||
enabled=PoolStringArray( "res://addons/http_server/plugin.cfg" )
|
enabled=PoolStringArray( "res://addons/http_server/plugin.cfg" )
|
||||||
|
@ -1,405 +1,248 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Parcel Sandbox</title>
|
<title>Tickle</title>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<style></style>
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Lobster&family=Nunito+Sans:ital,wght@0,400;0,700;1,400&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--accent: hotpink;
|
||||||
|
--background: #fdfdfd;
|
||||||
|
font-family: "Nunito Sans", sans-serif;
|
||||||
|
}
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.trigger {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.left-nav > * {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.left-nav > .menu {
|
||||||
|
padding: 1em;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
.left-nav > .menu a {
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--accent);
|
||||||
|
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
}
|
||||||
|
.left-nav > .menu a:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--background);
|
||||||
|
}
|
||||||
|
.left-nav > .trigger:checked ~ .menu {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition-duration: 0.1s;
|
||||||
|
}
|
||||||
|
.burger {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 2em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
|
||||||
|
transition: background 0.5s ease;
|
||||||
|
}
|
||||||
|
.left-nav > .trigger:checked ~ .burger {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-nav > .trigger:checked ~ .burger,
|
||||||
|
#Loading {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
background: rgba(17, 17, 17, 0.2);
|
||||||
|
}
|
||||||
|
#Loading {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 8rem;
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease-in, height 0s linear 0.3s;
|
||||||
|
height: 0;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.is-loading #Loading {
|
||||||
|
opacity: 1;
|
||||||
|
height: 100%;
|
||||||
|
transition: opacity 1s ease-in, height 0 linear;
|
||||||
|
}
|
||||||
|
#Loading::after {
|
||||||
|
content: "❤";
|
||||||
|
animation: beat 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
|
}
|
||||||
|
@keyframes beat {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
5% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
39% {
|
||||||
|
transform: scale(0.85);
|
||||||
|
}
|
||||||
|
45% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#Body {
|
||||||
|
padding: 3em;
|
||||||
|
max-width: 52em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
#Body h1,
|
||||||
|
#Body h2,
|
||||||
|
#Body h3,
|
||||||
|
#Body h4 {
|
||||||
|
color: var(--accent);
|
||||||
|
font-family: "Lobster", serif;
|
||||||
|
}
|
||||||
|
#Body a {
|
||||||
|
color: var(--background);
|
||||||
|
background-color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.1em 0.4em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<template id="my-link">
|
<nav class="left-nav">
|
||||||
<style>
|
<input id="main-nav" type="checkbox" class="trigger" />
|
||||||
:host {
|
<label for="main-nav" class="burger">≡</label>
|
||||||
--background-regular: hsla(196, 61%, 58%, 0.75);
|
<div id="Menu" class="menu"></div>
|
||||||
--background-active: red;
|
</nav>
|
||||||
text-decoration: none;
|
<header id="Menu"></header>
|
||||||
color: #18272f;
|
<main id="Body"></main>
|
||||||
font-weight: 700;
|
<div id="Loading"></div>
|
||||||
cursor: pointer;
|
<script type="module">
|
||||||
position: relative;
|
/**
|
||||||
display: flex;
|
* markdown parser. Remove if you don't use markdown
|
||||||
}
|
*/
|
||||||
|
// @ts-ignore
|
||||||
:host span {
|
import { micromark } from "https://esm.sh/micromark@3?bundle";
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host::before {
|
|
||||||
content: "";
|
|
||||||
background-color: var(--background-regular);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 3px;
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
z-index: -1;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(:hover)::before {
|
|
||||||
bottom: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([active])::before {
|
|
||||||
background-color: var(--background-active);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<span><slot /></span>
|
|
||||||
</template>
|
|
||||||
<template id="my-menu">
|
|
||||||
<style>
|
|
||||||
:host ul,
|
|
||||||
:host li {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
:host nav {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<nav>
|
|
||||||
<slot />
|
|
||||||
</nav>
|
|
||||||
</template>
|
|
||||||
<my-menu id="menu">
|
|
||||||
<my-link main href="d">Home</my-link>
|
|
||||||
<h2>Articles</h2>
|
|
||||||
</my-menu>
|
|
||||||
<div id="App"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
//@ts-check
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
*
|
|
||||||
* UTILITIES
|
|
||||||
*
|
|
||||||
* A few common methods to use in the project
|
|
||||||
*
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
const Signal = () => {
|
|
||||||
const listeners = new Set();
|
|
||||||
|
|
||||||
return {
|
|
||||||
remove: listeners.delete.bind(listeners),
|
|
||||||
add(/** @type {(arg:any)=>void} */ listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
return listeners.delete.bind(listeners, listener);
|
|
||||||
},
|
|
||||||
emit(/** @type {any} */ data) {
|
|
||||||
listeners.forEach((l) => l(data));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getText = (/** @type {string} */ file) =>
|
|
||||||
fetch(`./${file}`)
|
|
||||||
.then((response) => response.text())
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(`could not find file "${file}"`);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
const parseMarkdown = (/** @type {string} */ text) =>
|
|
||||||
text
|
|
||||||
// lists
|
|
||||||
.replace(
|
|
||||||
/^\s*\n((?:\*\s.+\s*\n)+)([^\*])/gm,
|
|
||||||
(_, bullets, next) =>
|
|
||||||
`<ul>${bullets.replace(
|
|
||||||
/^\*\s(.+)/gm,
|
|
||||||
"<li>$1</li>"
|
|
||||||
)}\n</ul>\n\n${next}`
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/^\s*\n((?:\d\..+\s*\n)+)([^\*])/gm,
|
|
||||||
(_, bullets, next) =>
|
|
||||||
`<ol>${bullets.replace(
|
|
||||||
/^\d\.\s(.+)/gm,
|
|
||||||
"<li>$1</li>"
|
|
||||||
)}\n</ol>\n\n${next}`
|
|
||||||
)
|
|
||||||
// blockquotes
|
|
||||||
.replace(/^\>(.+)/gm, "<blockquote>$1</blockquote>")
|
|
||||||
// headers
|
|
||||||
.replace(/(#+)(.+)/g, (_, { length: l }, t) => `<h${l}>${t}</h${l}>`)
|
|
||||||
.replace(/^(.+)\n\=+/gm, "<h1>$1</h1>")
|
|
||||||
.replace(/^(.+)\n\-+/gm, "<h2>$1</h2>")
|
|
||||||
//images
|
|
||||||
.replace(/\!\[([^\]]+)\]\(([^\)]+)\)/g, '<img src="$2" alt="$1" />')
|
|
||||||
//links
|
|
||||||
.replace(
|
|
||||||
/[\[]{1}([^\]]+)[\]]{1}[\(]{1}([^\)\"]+)(\"(.+)\")?[\)]{1}/g,
|
|
||||||
'<a href="$2" title="$4">$1</a>'
|
|
||||||
)
|
|
||||||
//font styles
|
|
||||||
.replace(/[\*\_]{2}([^\*\_]+)[\*\_]{2}/g, "<strong>$1</strong>")
|
|
||||||
.replace(/[\*\_]{1}([^\*\_]+)[\*\_]{1}/g, "<em>$1</em>")
|
|
||||||
.replace(/[\~]{2}([^\~]+)[\~]{2}/g, "<del>$1</del>")
|
|
||||||
//pre
|
|
||||||
.replace(/^\s*\n\`\`\`(([^\s]+))?/gm, '<pre class="$2">')
|
|
||||||
.replace(/^\`\`\`\s*\n/gm, "</pre>\n\n")
|
|
||||||
//code
|
|
||||||
.replace(/[\`]{1}([^\`]+)[\`]{1}/g, "<code>$1</code>")
|
|
||||||
//p
|
|
||||||
.replace(/^\s*(\n)?(.+)/gm, (m) => {
|
|
||||||
return /\<(\/)?(h\d|ul|ol|li|blockquote|pre|img)/.test(m)
|
|
||||||
? m
|
|
||||||
: "<p>" + m + "</p>";
|
|
||||||
})
|
|
||||||
//strip p from pre
|
|
||||||
.replace(/(\<pre.+\>)\s*\n\<p\>(.+)\<\/p\>/gm, "$1$2")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const getMarkdown = (/** @type {string} */ file) =>
|
|
||||||
getText(file).then(parseMarkdown);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* useful to check for transitions while developing styles, if the loading screen disappears too fast
|
||||||
*
|
*
|
||||||
* @param {string} tag
|
|
||||||
* @param {Record<string, any>} props
|
|
||||||
* @param {string|Node[]} children
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
const el = (tag = "div", props = {}, children = []) => {
|
const wait = (val) => new Promise((ok) => setTimeout(ok, 1, val));
|
||||||
const node = document.createElement(tag);
|
|
||||||
Object.keys(props).forEach((key) => {
|
/**
|
||||||
node.setAttribute(key, props[key]);
|
* The elements we will need
|
||||||
});
|
*/
|
||||||
if (typeof children == "string") {
|
const [Menu, Body, Loading] = ["Menu", "Body", "Loading"].map((id) =>
|
||||||
children = [document.createTextNode(children)];
|
document.getElementById(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cache the original title to append it to the page title
|
||||||
|
*/
|
||||||
|
const mainTitle = document.title;
|
||||||
|
|
||||||
|
const startLoading = () => document.body.classList.add("is-loading");
|
||||||
|
const stopLoading = () => document.body.classList.remove("is-loading");
|
||||||
|
|
||||||
|
const getCurrentPage = () =>
|
||||||
|
window.location.hash[1] === "/" ? window.location.hash.slice(2) : "";
|
||||||
|
|
||||||
|
const onHashChange = (evt) => {
|
||||||
|
const path = getCurrentPage();
|
||||||
|
if (!path) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
children.forEach((child) => node.appendChild(child));
|
startLoading();
|
||||||
return node;
|
return fetch(`./${path}`)
|
||||||
};
|
.then((response) => response.text())
|
||||||
|
.then(wait)
|
||||||
const makeTitelize = (alwaysLowCaps = [], alwaysUpperCaps = []) => {
|
.then((text) => {
|
||||||
const specials = [...alwaysLowCaps, ...alwaysUpperCaps].reduce(
|
const [, title] = text.match(/^(#\s\w+)/) ||
|
||||||
(result, word) =>
|
text.match(/(.*?)\n===+/m) || [, path];
|
||||||
result.set(new RegExp("\\b" + word + "\\b", "gi"), word),
|
document.title = `${title} | ${mainTitle}`;
|
||||||
/** @type {Map<RegExp, string>}*/ (new Map())
|
Body.innerHTML = micromark(text);
|
||||||
);
|
stopLoading();
|
||||||
const titelize = (/** @type {string} */ text) => {
|
|
||||||
text = text
|
|
||||||
.replace(/_-\//g, " ")
|
|
||||||
.replace(/\.\w+$/, "")
|
|
||||||
.replace(/\s+/, " ")
|
|
||||||
.split(" ")
|
|
||||||
.map((word) =>
|
|
||||||
word.length > 1
|
|
||||||
? word[0].toUpperCase() + word.slice(1).toLowerCase()
|
|
||||||
: word
|
|
||||||
)
|
|
||||||
.join(" ");
|
|
||||||
for (const [key, value] of specials) {
|
|
||||||
text = text.replace(key, value);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
return titelize;
|
|
||||||
};
|
|
||||||
|
|
||||||
const titelize = makeTitelize(["the", "a"], ["TV", "ID", "AI"]);
|
|
||||||
|
|
||||||
const Router = (() => {
|
|
||||||
const onRouteChange = Signal();
|
|
||||||
let route = "";
|
|
||||||
|
|
||||||
const set = (/** @type {string} */ newRoute) => {
|
|
||||||
if (newRoute === route) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
window.location.hash = newRoute;
|
|
||||||
route = newRoute;
|
|
||||||
onRouteChange.emit(route);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const get = () => window.location.hash.slice(1).replace(/\//gi, "/");
|
|
||||||
|
|
||||||
const is = (href) => href === get();
|
|
||||||
|
|
||||||
window.addEventListener("popstate", () => set(get()));
|
|
||||||
|
|
||||||
return { set, get, is, onRouteChange };
|
|
||||||
})();
|
|
||||||
|
|
||||||
const getTemplateClone = (/** @type {string} */ id) => {
|
|
||||||
const templateModel = /** @type {HTMLTemplateElement} */ (
|
|
||||||
document.getElementById(id)
|
|
||||||
);
|
|
||||||
const template = /** @type {HTMLElement} */ (
|
|
||||||
templateModel.content.cloneNode(true)
|
|
||||||
);
|
|
||||||
return template;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* WEB COMPONENTS
|
|
||||||
*
|
|
||||||
* Sources:
|
|
||||||
* https://web.dev/custom-elements-best-practices/
|
|
||||||
* https://googlechromelabs.github.io/howto-components/
|
|
||||||
*
|
|
||||||
* A set of neat components to use in the page
|
|
||||||
*
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
class CustomElement extends HTMLElement {
|
|
||||||
/** @type {ShadowRoot} */
|
|
||||||
shadow = this.attachShadow({ mode: "closed" });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A user may set a property on an instance of an element, before its prototype has been connected to this class.
|
|
||||||
* Will check for any instance properties and run them through the proper class setters.
|
|
||||||
* @param {string} prop
|
|
||||||
*/
|
|
||||||
_syncProperty(prop) {
|
|
||||||
if (this.hasOwnProperty(prop)) {
|
|
||||||
let value = this[prop];
|
|
||||||
delete this[prop];
|
|
||||||
this[prop] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyLink extends CustomElement {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.shadow.append(getTemplateClone("my-link"));
|
|
||||||
this.shadow.addEventListener("click", this._onClick.bind(this));
|
|
||||||
Router.onRouteChange.add(this.updateActive.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observedAttributes() {
|
|
||||||
return ["href", "active", "main"];
|
|
||||||
}
|
|
||||||
|
|
||||||
_onClick() {
|
|
||||||
if (this.href) {
|
|
||||||
Router.set(this.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeChangedCallback(property, oldValue, newValue) {
|
|
||||||
if (oldValue === newValue) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this[property] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateActive() {
|
|
||||||
if (Router.is(this.href)) {
|
|
||||||
this.setAttribute("active", "");
|
|
||||||
} else {
|
|
||||||
this.removeAttribute("active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set href(/** @type {string}*/ value) {
|
|
||||||
this.setAttribute("href", value);
|
|
||||||
this.updateActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
get href() {
|
|
||||||
return this.getAttribute("href");
|
|
||||||
}
|
|
||||||
|
|
||||||
set main(/** @type {boolean}*/ value) {
|
|
||||||
if (value) {
|
|
||||||
this.setAttribute("main", "");
|
|
||||||
} else {
|
|
||||||
this.removeAttribute("main");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get main() {
|
|
||||||
return this.hasAttribute("main");
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
["active", "main"].forEach((prop) => this._syncProperty(prop));
|
|
||||||
this.updateActive();
|
|
||||||
if (this.getAttribute("main")) {
|
|
||||||
console.log("sdfsdff");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("my-link", MyLink);
|
|
||||||
|
|
||||||
class MyMenu extends CustomElement {
|
|
||||||
_handled = new Set();
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.shadow.append(getTemplateClone("my-menu"));
|
|
||||||
const slot = this.shadow.querySelector("slot");
|
|
||||||
slot.addEventListener("slotchange", (event) => {
|
|
||||||
for (const child of slot.assignedElements()) {
|
|
||||||
if (this._handled.has(child) || !(child instanceof MyLink)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this._handled.add(child);
|
|
||||||
// TODO: pre-fetch
|
|
||||||
//console.log("new child: ", child);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
|
||||||
customElements.define("my-menu", MyMenu);
|
|
||||||
|
|
||||||
/**********************************************************************
|
window.addEventListener("hashchange", onHashChange);
|
||||||
* MARKDOWN PARSING
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
const load = (/** @type {string} */ file) =>
|
/**
|
||||||
getMarkdown(file).then((md) => {
|
* Loads the article list, parses it, creates the menu items
|
||||||
app.innerHTML = md;
|
*/
|
||||||
});
|
const start = () => {
|
||||||
|
startLoading();
|
||||||
|
fetch("./files.txt")
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((lines) => {
|
||||||
|
Menu.innerHTML = lines
|
||||||
|
.split(`\n`)
|
||||||
|
.map((line, index) => {
|
||||||
|
const today = new Date().toISOString().split("T")[0];
|
||||||
|
const result = line.match(
|
||||||
|
/(?<name>.+)\.(?<ext>\w{2,3})(?:\s(?<date>[\d-]+)?(?<title>.+))?/
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
|
console.log(`could not parse line ${index}: [${line}]`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
groups: { name, ext, date = today, title = name },
|
||||||
|
} = result;
|
||||||
|
const href = `/${name}.${ext}`;
|
||||||
|
if (!getCurrentPage()) {
|
||||||
|
window.location.hash = `#${href}`;
|
||||||
|
}
|
||||||
|
return { name, href, date, title };
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort(({ date: a }, { date: b }) => a - b)
|
||||||
|
.map(
|
||||||
|
({ href, title }) => `<a data-link href="#${href}">${title}</a>`
|
||||||
|
)
|
||||||
|
.join(`\n`);
|
||||||
|
|
||||||
/**********************************************************************
|
onHashChange();
|
||||||
* BOOSTRAPPING
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
getText("files.txt").then((lines) => {
|
|
||||||
lines
|
|
||||||
.split(`\n`)
|
|
||||||
.map((line) => {
|
|
||||||
const [file, maybeDate, ...rest] = line.split(/\s/);
|
|
||||||
const href = file.trim();
|
|
||||||
let date = maybeDate ? new Date(maybeDate) : new Date();
|
|
||||||
if (isNaN(date.getTime())) {
|
|
||||||
date = new Date();
|
|
||||||
rest.unshift(maybeDate);
|
|
||||||
}
|
|
||||||
const textContent = rest.length
|
|
||||||
? rest.join(" ").trim()
|
|
||||||
: titelize(file);
|
|
||||||
return { href, date, textContent };
|
|
||||||
})
|
|
||||||
.sort(({ date: a }, { date: b }) => a.getTime() - b.getTime())
|
|
||||||
.forEach(({ href, date, textContent }) => {
|
|
||||||
const link = /** @type {MyLink} */ el(
|
|
||||||
"my-link",
|
|
||||||
{ href },
|
|
||||||
textContent
|
|
||||||
);
|
|
||||||
document.getElementById("menu").appendChild(link);
|
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
const app = document.getElementById("App");
|
start();
|
||||||
|
|
||||||
const BLOCKQUOTE = Symbol("blockquote");
|
|
||||||
const PARAGRAPH = Symbol("paragraph");
|
|
||||||
const LIST = Symbol("list");
|
|
||||||
|
|
||||||
Router.onRouteChange.add((route) => load(route));
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user