[WIP] Start working on website

This commit is contained in:
Talia 2023-12-11 16:53:32 +01:00
commit 53e19602f2
3 changed files with 653 additions and 0 deletions

463
index.html Normal file
View file

@ -0,0 +1,463 @@
<html theme=dark>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script type="module" src="https://cdn.jsdelivr.net/gh/darkwiiplayer/components@master/TypeWriter.js"></script>
<style>
@import
/* url('https://cdn.jsdelivr.net/gh/darkwiiplayer/css@main/all.css') */
url('https://darkwiiplayer.github.io/css/all.css') layer(framework);
@import
/* url('https://cdn.jsdelivr.net/gh/darkwiiplayer/css@main/schemes/talia.css') */
url('https://darkwiiplayer.github.io/css/schemes/talia.css') layer(theme);
@import url('styles.css') layer(site);
.jsdelivr-badge {
filter: saturate(.4) hue-rotate(250deg);
border-radius: .2em;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css">
<script>
const scrollHook = () => {
document.body.setAttribute("scroll", window.scrollY)
}
window.addEventListener("scroll", scrollHook)
document.addEventListener("readystatechange", scrollHook)
const observer = new IntersectionObserver(events => {}, {
rootMargin: '-1px 0px 0px 0px',
threshold: [1]
})
</script>
<script type="module">
import hljs from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/highlight.min.js';
import lang_html from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/languages/xml.min.js"
hljs.registerLanguage("html", lang_html)
document.querySelectorAll('code[lang="js"]').forEach(code => {
code.innerHTML = hljs.highlight("javascript", code.innerText).value
})
</script>
<script type="module">
import "./js/NyooomShowcase.js"
</script>
<a name=top></a>
<header role="navigation" class="fixed">
<nav class="bar">
<ul>
<li><a href="#top">Top</a></li>
<li><a href="#getting-started">Getting Started</a></li>
<li><a href="https://github.com/darkwiiplayer/nyooom">GitHub</a></li>
<li><a href="https://www.npmjs.com/package/nyooom">npm</a></li>
</ul>
</nav>
<img alt="" class="jsdelivr-badge" src="https://data.jsdelivr.com/v1/package/npm/nyooom/badge">
</header>
<page-hero role="banner" cover=60>
<!-- <img src="https://picsum.photos/1920/1080"> -->
<hgroup>
<h1 style="font-size: 6vw">Nyooom</span></h1>
<code lang="js">import { html } from "nyooom.js"</code>
<p>
A new way of building
<type-writer loop>
<span>HTML</span>
<template>SVG</template>
<template>templates</template>
<template>components</template>
<template>applications</template>
</type-writer>
in vanilla JavaScript
</p>
</hgroup>
<flex-row gap=1>
<!--<a class="button" href="#elevator-pitch">Elevator Pitch</a>-->
<a class="button" href="#getting-started">Get Started</a>
</flex-row>
</page-hero>
<vertical-spacer style="height: 6rem"></vertical-spacer>
<main>
<section id="elevator-pitch" class="content-width">
<h2>Elevator Pitch</h2>
<p class="big">
With nyooom <em>Nyooom</em> you can elegantly express nested DOM structures in <em>plain JavaScript</em>.
</p>
<p class="big">
At its core, it is a library to <em>generate HTML elements</em>.
</p>
<p class="big">
Together with its utility modules like <code>Observable</code>, it turns into a powerful <em>front end micro framework</em>.
</p>
<nyooom-showcase>
<code><div contenteditable="false">return html.p(
"Text Node",
html.br(),
html.b("HTML Node"),
{style:{color:"salmon"}},
)</div></code>
</nyooom-showcase>
<vertical-spacer></vertical-spacer>
<p>
Nyooom aims to be <em>small</em> enough that you can use it even if you only need it in a single function,
<em>easy</em> enough that you can figure out what's going on even if you've never used it before and
<em>powerful</em> enough that you won't <i>need</i> another framework, without preventing you from using one if you prefer.
</p>
<p>
If that sounds good, it's time to back up those claims!
<p>
</p>
Continue reading to get an overview of how nyooom works and what it can do.<br>
Or jump down to the <a href="#nyooom-explained">explainer</a> to get a more detailed explanation of what nyooom is good at and why.
</p>
</section>
<section id="getting-started" class="content-width">
<h2>Getting Started</h2>
<section>
<p class="important">
Trying out nyooom is super easy!<br>
Just import the <code>html</code> export into your script and start generating DOM nodes.
</p>
<pre><code lang=js>import {html} from "https://cdn.jsdelivr.net/npm/nyooom@1.3.1/nyooom.min.js"</code></pre>
</section>
<section>
<p>
Indexing this object with any value will return a node factory function.<br>
The type of node is decided by the property name: calling <code>html.div()</code> will create a <code>&lt;div&gt;</code> node.
</p>
<nyooom-showcase preview="false">
<code>
<div contenteditable="false">return html.div()</div>
</code>
</nyooom-showcase>
<p>
If you quickly want to render your HTML to a website to see the result,
the component helper found in <code>sckooma/observable.js</code> makes this a lot easier.<br>
Just pass it a function that returns some DOM nodes, and it'll register a custom element for you.
</p>
<pre><code lang=js>import {component} from "https://cdn.jsdelivr.net/npm/nyooom@1.3.1/state.min.js"
const myComponent = () =&gt; html.div("Rendered Component")
component(myComponent) //Registers it as &lt;my-component&gt;</code></pre>
<p>
Of course you can also just call <code lang=js>document.body.append(html.span("My Text"))</code> in your script.
</p>
</section>
</section>
<section class="content-width">
<h2>Basic DOM generation</h2>
<section>
<p>
Content can be added to a node by simply passing it as arguments to the function.
String arguments get inserted as text nodes, and DOM nodes are simply appended to the child list.
<br>
This way, your JS code can start looking almost like a proper templating language,
albeit with some extra quotes and braces here and there.
</p>
<nyooom-showcase preview="false">
<code>
<div contenteditable="false">return html.span(
"Nyooom ",
html.u(
"is ",
html.b("cool")
)
)</div>
</code>
</nyooom-showcase>
</section>
<section>
<p>
Adding attributes is just as easy: Just pass an object into the function.
If the value is an array, it'll get joined with spaces.
</p>
<nyooom-showcase preview="false">
<code>
<div contenteditable="false">return html.span({
id: "warning",
class: ["danger", "bold"],
})</div>
</code>
</nyooom-showcase>
</section>
<section>
<p>
If the value is a function, it'll be added as an event listener instead.
</p>
<nyooom-showcase code="false">
<code>
<div contenteditable="false">return html.button("Click Me!", {
click: event => {
alert("Button clicked :3")
}
})</div>
</code>
</nyooom-showcase>
<section>
<p>
Setting inline styles is just as easy:
</p>
<nyooom-showcase>
<code>
<div contenteditable="false">return html.div(
{ style: {
color: "salmon"
}},
html.span("Big Salmon",
{ style: {
fontSize: "1.4em"
}}
)
)</div>
</code>
</nyooom-showcase>
<p>
<code>camelCase</code> names will be converted to <code>kebab-case</code> and values
will be converted to strings.
</p>
<p>
And the same goes for the <code>dataset</code> property.
</p>
<nyooom-showcase preview=false>
<code>
<div contenteditable="false">return html.span(
{ dataset: {
from: "Fire",
to: "Heat",
snakeCase: "converted"
} },
"Heat from Fire"
)</div>
</code>
</nyooom-showcase>
<p>
And setting <code>shadowRoot</code> to a DOM node or an array of such will attach a shadow DOM to the newly created node.
</p>
<nyooom-showcase code=false>
<code>
<div contenteditable="false">return html.div(
{ shadowRoot:
html.p("Shadow DOM text")
},
"Light DOM text"
)</div>
</code>
</nyooom-showcase>
</section>
</section>
</section>
<section class="content-width">
<h2>It's all just JavaScript</h2>
<p>When generating HTML with nyooom, you never stop writing vanilla JavaScript that runs directly in your browser.</p>
<p>
What this means is that there's no new syntax for things you already know how to do.
Functions like <code>filter</code> or <code>map</code> can be applied directly to your data,
nodes can be assigned to variables right as you create them,
common structures can be extracted into function and even passed around as arguments,
and the syntax is just the boring old javascript that most tools already understand.
</p>
<nyooom-showcase code="false">
<code>
<div contenteditable="false">return html.ul(
["u", "b", "i"].map(
type =&gt; html.li(html[type](
`&lt;${type}&gt; element`
))
)
)</div>
</code>
</nyooom-showcase>
<section class="content-width">
<h2>State changes in Nyooom</h2>
<pre><code lang=js>import {ObservableObject} from 'nyooom/observable.js'</code></pre>
<h3>nyooom.js</h3>
<p>
Nyooom considers object with a truthy <code>observable</code> property to be Observables.
These objects are assumed to have a <code>value</code> property representing their current
value, and to emit a <code>"change"</code> event when their value changes.
</p>
<p>
Passing an observable to a generator function where an attribute would be expected
will bind the attribute to the observable.
The attribute value will be set to the initial value of the observable,
and future changes of the observable will update the value of the attribute.
</p>
<p>
Passing an observable where a child element would be expected
will insert a reactive child element by inserting the current value
of the observable and replacing it whenever the value gets updated.<br>
For primitives, this means they will first be converted to strings and then into a text node.
</p>
<p>
Special attributes are also supported. For example, setting an attribute to
an observable with a function value, the function will be registered as an
event listener. Changing the value of the observable to a different function
will unregister the current listener and register the new one instead.
</p>
<h3>observable.js</h3>
<p>
This module exports several classes that store state and emit events whenever the state changes.
</p>
<p>
The most generic one of these would be <code>ObservableObject</code>,
which in its <code>values</code> property exposes a Proxy to a plain JavaScript object.
Changing any value on this proxy will emit an event on the associated <code>ObservableObject</code>.
By default, changes on all Observables are enqueued and an event is dispatched in a microtask.
</p>
<nyooom-showcase code="false">
<code>
<div contenteditable="false">const counter = new State({value: 0})
counter.valueChanged = newValue =&gt;
console.log(`Value: ${newValue}`)
return html.flexColumn(
{gap: 1},
html.button(
"Click Me! ",
html.span(counter),
{ click: () =&gt; {
counter.value += 1
}}
),
html.button(
"Clear",
{ click: () =&gt; {
counter.value = 0
}}
)
)</div>
</code>
</nyooom-showcase>
<!-- TODO: Describe constructor options -->
<p>
The basic <code>State</code> object is backed by a plain JS object, so their attributes are unique and do not persist page reload.<br>
By contrast, the <code>StoredState</code> class, which extends the core <code>State</code> class, is backed by a <code>Storage</code> object like
<code>window.localStorage</code> or <code>window.sessionStorage</code>, meaning that they persist page reloads.<br>
Additionally, they detect changes both from the current as well as from other browser tabs/windows, so any updates of the state
get propagated automatically to all states backed by the same storage.
</p>
</section>
<section class="content-width">
<h2>A simple Todo list</h2>
<p>
A simple, but not completely bare-bones todo list application,
using nothing more than Nyooom and the CSS already present
on this page to save some code.
</p>
<nyooom-showcase code="false">
<code>
<div contenteditable="false">let todo, input
const task = value =>
html.flexRow (
{class: ["todo"], gap: 1},
value,
html.span("[x]", {
style: {
color: "var(--primary-6)",
cursor: "pointer"
},
click: event =&gt; {
event
.target
.closest(".todo")
.remove()
}
})
)
return todo =
html.flexColumn(
{gap: 1},
input=html.input({
type: "text",
placeholder:
"Do some stuff",
}),
html.button("Add Task",
{
click: event =&gt; {
todo.append(task(
input.value ||
input.placeholder
))
}
})
)</div>
</code>
</nyooom-showcase>
</section>
<section id="nyooom-explained" class="content-width">
<h2>Nyooom Explained</h2>
<p>
TODO: Write
</p>
</section>
</main>
<footer class="inset box">
<flex-row gap=1>
<div>
Nyooom is great!
</div>
</flex-row>
</footer>
</html>

124
js/NyoomShowcase.js Normal file
View file

@ -0,0 +1,124 @@
import {html,empty} from "https://cdn.jsdelivr.net/gh/darkwiiplayer/skooma-js@f79e7a9/skooma.js"
import {State} from "https://cdn.jsdelivr.net/gh/darkwiiplayer/skooma-js@f79e7a9/state.js"
import element from "https://darkwiiplayer.github.io/easier-elements-js/easier-elements.js"
import hljs from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/highlight.min.js';
import lang_html from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/languages/xml.min.js"
hljs.registerLanguage("html", lang_html)
const capture = event => event.stopPropagation()
const css = `
:host {
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
gap: var(--padding, 1em);
padding: var(--padding, 1em);
position: relative;
}
@media (max-width: 60em) {
:host {
display: flex;
flex-flow: column;
}
}
.error {
font-family: "Courier New", sans-serif;
grid-column: span 2;
}
.error:empty { display: none; }
.error:not(:empty)~* { display: none; }
.hidden { display: none; }
html-preview {
contain: inline-size layout style paint;
}
.edit {
left: -1.4em;
z-index: 10;
position: absolute;
display: block;
content: '🖉';
line-height: 100%;
opacity: .2;
font-size: 2em;
cursor: pointer;
}
.edit.editing {
opacity: .6;
}
`
const theme = html.link({
rel: "stylesheet",
href: "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css"
})
element(class NyooomShowcase extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: "open"})
this.addEventListener("input", _ => {
this.render()
})
}
get editable() {
return this.querySelector('[contenteditable="true"]')
}
format() {
if (!this.editable) {
const code = this.querySelector('[contenteditable]')
code.innerHTML = hljs.highlight("javascript", code.innerText).value
}
}
connectedCallback() {
this.shadowRoot.replaceChildren(
html.slot(),
html.span("🖉", {
class: "edit",
click: ({target: button}) => {
this.querySelectorAll("[contenteditable]")
.forEach(item => {
if (item.contentEditable == "true") {
item.contentEditable = "false"
button.classList.remove("editing")
this.format()
} else {
item.contentEditable = "true"
button.classList.add("editing")
item.innerText = item.innerText
}
})
}
}),
html.style(css),
...Array.from(document.styleSheets).map(sheet => sheet.ownerNode.cloneNode(true)),
theme.cloneNode(true),
this.error = html.div({class: ["error"]}),
this.output = html.code(),
this.preview = html.htmlPreview({input: capture}),
)
this.format()
this.render()
}
render() {
const code = this.querySelector("code").innerText
try {
const fn = new Function("html", "empty", "State", code)
const result = fn(html, empty, State)
this.error.replaceChildren()
this.output.innerHTML = hljs.highlight("html", result.outerHTML).value
this.preview.classList.toggle("hidden", this.getAttribute("preview") === "false")
this.output.classList.toggle("hidden", this.getAttribute("code") === "false")
this.preview.replaceChildren(result)
} catch (error) {
console.error(error.stack)
this.error.innerText = error.stack
}
}
})

66
styles.css Normal file
View file

@ -0,0 +1,66 @@
:root {
--corner-radius: 2px;
}
[contenteditable] { white-space: pre }
#elevator-pitch {
& .big {
font-size: 1.4em;
}
& em {
font-style: unset;
color: var(--secondary-5);
}
}
p.important {
font-size: 1.2em;
}
page-hero {
background-image:
radial-gradient(ellipse at 100% -40%, #fff0, #ccaaff06 70%, #fff0 70%),
radial-gradient(ellipse at -40% -20%, #fff0, #bb88ff02 60%, #fff0 60%),
radial-gradient(ellipse at 180% 180%, #fff0, #bb88ff02 60%, #fff0 60%),
linear-gradient(to top left, #a2f1, transparent),
linear-gradient(to top right, #c2d1, transparent);
&::after {
content: '';
display: block;
position: absolute;
bottom: 0;
height: .2rem;
left: 0;
right: 0;
background-image: linear-gradient(to right, #0660, #0f6, #0660);
background-image: linear-gradient(to right in hsl, #6060, #f0f, #6060);
filter: saturate(.4) opacity(.2);
}
}
skooma-showcase {
--padding: .6em;
border-radius: var(--corner-radius);
padding: var(--padding);
background-color: #fff1;
--hr-color: var(--primary-4);
tab-size: 3;
}
type-writer {
font-weight: bold;
&:defined::after {
content: "|";
opacity: 0.5;
}
}
main {
line-height: 1.2em;
p {
line-height: 1.6em;
}
}