init
This commit is contained in:
commit
9c3bfe3aa3
25 changed files with 683 additions and 0 deletions
13
Makefile
Normal file
13
Makefile
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
SRC_DIR := ./dist
|
||||||
|
DEST_DIR := ../web
|
||||||
|
|
||||||
|
all: install build copy
|
||||||
|
|
||||||
|
install:
|
||||||
|
@npm install
|
||||||
|
|
||||||
|
build:
|
||||||
|
@npm run build
|
||||||
|
|
||||||
|
copy:
|
||||||
|
@rsync -av --exclude='index.html' $(SRC_DIR)/ $(DEST_DIR)
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
vjfw - something something..
|
48
index.test.html
Normal file
48
index.test.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Zutto.fi</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="description" content="Index portal for services under zutto.fi"/>
|
||||||
|
<meta name="keywords" content="Index,Services,Zutto"/>
|
||||||
|
<meta name="author" content="@zutto:zutto.fi"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="src/css/main.css" raw core />
|
||||||
|
<link rel="stylesheet" type="text/css" href="src/css/shimmer.css" raw />
|
||||||
|
<link rel="stylesheet" type="text/css" href="src/css/tripledot.css" raw />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<h3>Hello World</h3>
|
||||||
|
</div>
|
||||||
|
<div id="mainContent">
|
||||||
|
<a href="https://fedi.zutto.fi">https://fedi.zutto.fi (Directory)</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="https://wiki.zutto.fi">https://wiki.zutto.fi</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="https://git.zutto.fi">https://git.zutto.fi</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<a href="https://ip.zutto.fi">https://ip.zutto.fi</a>
|
||||||
|
- <div content="myip" lazy class="" template>(Your IP address is %s)</div>
|
||||||
|
</span>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/> <!-- . -->
|
||||||
|
<div id="footer" class="bottom">
|
||||||
|
Matrix chat: <a href="https://matrix.to/#/#fedi.zutto.fi:zutto.fi">https://matrix.to/#/#zutto.fi:zutto.fi</a><br/>
|
||||||
|
Direct contact: <a href="https://matrix.to/#/@zutto:zutto.fi">https://matrix.to/#/@zutto:zutto.fi</a><br/>
|
||||||
|
<div content="time"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- load private services-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- import core scripts -->
|
||||||
|
<script type="module" src="src/js/app.js" async raw></script>
|
||||||
|
</html>
|
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "vjfw",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "vjfw - Not a VanillaJS Framework",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --mode=production",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"mini-css-extract-plugin": "^2.9.0",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"webpack": "^5.91.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
}
|
||||||
|
}
|
25
src/css/main.css
Normal file
25
src/css/main.css
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* core overrides */
|
||||||
|
a { white-space: nowrap; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
div[content] {
|
||||||
|
display: inline;
|
||||||
|
}
|
22
src/css/shimmer.css
Normal file
22
src/css/shimmer.css
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -468px 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 468px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shimmer {
|
||||||
|
animation-duration: .5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: shimmer;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
background: #f6f7f8;
|
||||||
|
background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);
|
||||||
|
background-size: 800px 104px;
|
||||||
|
height: 96px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
13
src/css/tripledot.css
Normal file
13
src/css/tripledot.css
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@keyframes dot-blink {
|
||||||
|
0% { content: ''; }
|
||||||
|
33% { content: '.'; }
|
||||||
|
66% { content: '..'; }
|
||||||
|
100% { content: '...'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
animation: dot-blink 0.80s infinite steps(1, end);
|
||||||
|
}
|
||||||
|
|
24
src/html.js
Normal file
24
src/html.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
class html {
|
||||||
|
constructor() {
|
||||||
|
this.html = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async loadingStart(element) {}
|
||||||
|
|
||||||
|
async loadingEnd(element) {}
|
||||||
|
|
||||||
|
async render(element, content) {
|
||||||
|
var template = '%s';
|
||||||
|
if (element.hasAttribute('html-template'))
|
||||||
|
template = element.getAttribute('html-template');
|
||||||
|
else if (element.hasAttribute("template")) {
|
||||||
|
template = element.innerHTML;
|
||||||
|
element.setAttribute('html-template', template);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = template.replace('%s', content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
9
src/js/app.js
Normal file
9
src/js/app.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { MyIP } from './plugins/myip.js';
|
||||||
|
import { Time } from './plugins/time.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var time = new Time();
|
||||||
|
var myip = new MyIP();
|
||||||
|
})();
|
22
src/js/components/loader.js
Normal file
22
src/js/components/loader.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { html } from '../html.js';
|
||||||
|
export class loader extends html {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadingStart(element) {
|
||||||
|
element.classList.add('shimmer');
|
||||||
|
return this.render(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadingEnd(element) {
|
||||||
|
element.classList.remove('shimmer');
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(element) {
|
||||||
|
return super.render(element, `
|
||||||
|
<span id="loader" class="loading"></span>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
29
src/js/components/vhtml.js
Normal file
29
src/js/components/vhtml.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { html } from "../html.js";
|
||||||
|
import { loader } from "./loader.js";
|
||||||
|
export class vhtml extends html {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.loader = undefined;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadingStart(element){
|
||||||
|
if (this.loader !== undefined) this.loader.loadingEnd(element);
|
||||||
|
this.loader = new loader();
|
||||||
|
this.loader.loadingStart(element);
|
||||||
|
return super.loadingStart(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async loadingEnd(element){
|
||||||
|
if (this.loader !== undefined)
|
||||||
|
this.loader.loadingEnd(element);
|
||||||
|
|
||||||
|
return super.loadingEnd(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(element, content){
|
||||||
|
return super.render(element, content);
|
||||||
|
}
|
||||||
|
}
|
40
src/js/fetch.js
Normal file
40
src/js/fetch.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
export class fetcher {
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// get html content from a url
|
||||||
|
// opts - object with options
|
||||||
|
// opts.url - url to fetch
|
||||||
|
// opts.method - http method
|
||||||
|
// opts.headers - http headers
|
||||||
|
// opts.body - http body
|
||||||
|
// opts.mode - cors, no-cors, same-origin
|
||||||
|
// opts.cache - default, no-store, reload, no-cache, force-cache, only-if-cached
|
||||||
|
// opts.credentials - omit, same-origin, include
|
||||||
|
// opts.redirect - follow, error, manual
|
||||||
|
// opts.referrer - no-referrer, client
|
||||||
|
async go(opts) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(opts.url, {
|
||||||
|
method: opts.method || 'GET',
|
||||||
|
headers: opts.headers || {},
|
||||||
|
body: opts.body || null,
|
||||||
|
mode: opts.mode || 'cors',
|
||||||
|
cache: opts.cache || 'default',
|
||||||
|
credentials: opts.credentials || 'same-origin',
|
||||||
|
redirect: opts.redirect || 'follow',
|
||||||
|
referrer: opts.referrer || 'client'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
|
return html;
|
||||||
|
} catch(error) {
|
||||||
|
console.error("error at fetch.get:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
24
src/js/html.js
Normal file
24
src/js/html.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export class html {
|
||||||
|
constructor() {
|
||||||
|
this.html = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async loadingStart(element) {}
|
||||||
|
|
||||||
|
async loadingEnd(element) {
|
||||||
|
console.log("super stop", element);
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(element, content) {
|
||||||
|
var template = '%s';
|
||||||
|
if (element.hasAttribute('html-template'))
|
||||||
|
template = element.getAttribute('html-template');
|
||||||
|
else if (element.hasAttribute("template")) {
|
||||||
|
template = element.innerHTML;
|
||||||
|
element.setAttribute('html-template', template);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = template.replace('%s', content);
|
||||||
|
}
|
||||||
|
}
|
61
src/js/plugins/myip.js
Normal file
61
src/js/plugins/myip.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { vj } from "../vjfw.js";
|
||||||
|
import { vhtml } from "../components/vhtml.js";
|
||||||
|
import { fetcher } from "../fetch.js";
|
||||||
|
|
||||||
|
export class MyIP extends vhtml {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._ip = "";
|
||||||
|
this._source = "https://ip.zutto.fi/"
|
||||||
|
|
||||||
|
vj.watcher.watch('[content="myip"]', (element) => {
|
||||||
|
this.loadingStart(element);
|
||||||
|
this.ip().then((data) => {
|
||||||
|
try {
|
||||||
|
this._ip = data.trim();
|
||||||
|
vj.ps.publish("ip", {"ip": this._ip});
|
||||||
|
this.render(element);
|
||||||
|
this.loadingEnd(element);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error here", e)
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async ip() {
|
||||||
|
try {
|
||||||
|
const fetch = new fetcher();
|
||||||
|
console.log("ok");
|
||||||
|
return fetch.go({"url": `${this._source}/ip` });
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async loadingStart(element) {
|
||||||
|
vj.ps.publish("ip.load.start", {"element": element});
|
||||||
|
return super.loadingStart(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadingEnd(element) {
|
||||||
|
vj.ps.publish("ip.load.end", {"element": element});
|
||||||
|
return super.loadingEnd(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(element) {
|
||||||
|
try {
|
||||||
|
const rendered = await super.render(element, this._ip);
|
||||||
|
vj.ps.publish("ip.rendered", { "element": element, "rendered": rendered });
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
20
src/js/plugins/time.js
Normal file
20
src/js/plugins/time.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { html } from '../html.js';
|
||||||
|
import { vj } from '../vjfw.js';
|
||||||
|
|
||||||
|
export class Time extends html {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
vj.watcher.watch('[content="time"]', (element) => {
|
||||||
|
this.render(element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async render(element) {
|
||||||
|
// update every second
|
||||||
|
setInterval(() => {
|
||||||
|
super.render(element, new Date().toLocaleTimeString('en-GB', { hour12: false }).toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
42
src/js/ps.js
Normal file
42
src/js/ps.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// pubsub library
|
||||||
|
export class ps {
|
||||||
|
constructor() {
|
||||||
|
this.subscribers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribe to event
|
||||||
|
// event: string
|
||||||
|
// callback: function
|
||||||
|
// return: void
|
||||||
|
subscribe(event, callback) {
|
||||||
|
if (!this.subscribers[event]) {
|
||||||
|
this.subscribers[event] = [];
|
||||||
|
}
|
||||||
|
this.subscribers[event].push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish event
|
||||||
|
// event: string
|
||||||
|
// data: any
|
||||||
|
// return: void
|
||||||
|
// note: data is passed to all subscribers
|
||||||
|
publish(event, data) {
|
||||||
|
if (this.subscribers[event]) {
|
||||||
|
this.subscribers[event].forEach(callback => {
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// unsubscribe from event
|
||||||
|
// event: string
|
||||||
|
// callback: function
|
||||||
|
// return: void
|
||||||
|
unsubscribe(event, callback) {
|
||||||
|
if (this.subscribers[event]) {
|
||||||
|
this.subscribers[event] = this.subscribers[event].filter(cb => cb !== callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/js/vjfw.js
Normal file
16
src/js/vjfw.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { ps } from './ps.js';
|
||||||
|
import { watcher } from './watcher.js';
|
||||||
|
|
||||||
|
|
||||||
|
export class vjfw {
|
||||||
|
constructor() {
|
||||||
|
this.version = '0.0.1';
|
||||||
|
this.compiled = false;
|
||||||
|
this.watcher = new watcher();
|
||||||
|
this.ps = new ps();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const vj = new vjfw();
|
57
src/js/watcher.js
Normal file
57
src/js/watcher.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
export class watcher {
|
||||||
|
constructor() {
|
||||||
|
this.observers = new Map();
|
||||||
|
this.initObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
initObserver() {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach(mutation => {
|
||||||
|
mutation.addedNodes.forEach(node => {
|
||||||
|
if (node.nodeType === 1) {
|
||||||
|
this.checkNode(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNode(node) {
|
||||||
|
this.observers.forEach((callback, selector) => {
|
||||||
|
if (node.matches(selector)) {
|
||||||
|
if (node.hasAttribute('lazyload')) {
|
||||||
|
this.observeLazyLoad(node, callback);
|
||||||
|
} else {
|
||||||
|
callback(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
observeLazyLoad(element, callback) {
|
||||||
|
const intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
callback(entry.target);
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
intersectionObserver.observe(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(selector, callback) {
|
||||||
|
document.querySelectorAll(selector).forEach(element => {
|
||||||
|
if (element.hasAttribute('lazyload')) {
|
||||||
|
this.observeLazyLoad(element, callback);
|
||||||
|
} else {
|
||||||
|
callback(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.observers.set(selector, callback);
|
||||||
|
}
|
||||||
|
}
|
25
web/css/main.css
Normal file
25
web/css/main.css
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* core overrides */
|
||||||
|
a { white-space: nowrap; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
div[content] {
|
||||||
|
display: inline;
|
||||||
|
}
|
22
web/css/shimmer.css
Normal file
22
web/css/shimmer.css
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -468px 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 468px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shimmer {
|
||||||
|
animation-duration: .5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: shimmer;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
background: #f6f7f8;
|
||||||
|
background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);
|
||||||
|
background-size: 800px 104px;
|
||||||
|
height: 96px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
13
web/css/tripledot.css
Normal file
13
web/css/tripledot.css
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@keyframes dot-blink {
|
||||||
|
0% { content: ''; }
|
||||||
|
33% { content: '.'; }
|
||||||
|
66% { content: '..'; }
|
||||||
|
100% { content: '...'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
animation: dot-blink 0.80s infinite steps(1, end);
|
||||||
|
}
|
||||||
|
|
48
web/index.html
Normal file
48
web/index.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Zutto.fi</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="description" content="Index portal for services under zutto.fi"/>
|
||||||
|
<meta name="keywords" content="Index,Services,Zutto"/>
|
||||||
|
<meta name="author" content="@zutto:zutto.fi"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/main.css" raw core />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/shimmer.css" raw />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/tripledot.css" raw />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<h3>Hello World</h3>
|
||||||
|
</div>
|
||||||
|
<div id="mainContent">
|
||||||
|
<a href="https://fedi.zutto.fi">https://fedi.zutto.fi (Directory)</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="https://wiki.zutto.fi">https://wiki.zutto.fi</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="https://git.zutto.fi">https://git.zutto.fi</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<a href="https://ip.zutto.fi">https://ip.zutto.fi</a>
|
||||||
|
- <div content="myip" lazy class="" template>(Your IP address is %s)</div>
|
||||||
|
</span>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/> <!-- . -->
|
||||||
|
<div id="footer" class="bottom">
|
||||||
|
Matrix chat: <a href="https://matrix.to/#/#fedi.zutto.fi:zutto.fi">https://matrix.to/#/#zutto.fi:zutto.fi</a><br/>
|
||||||
|
Direct contact: <a href="https://matrix.to/#/@zutto:zutto.fi">https://matrix.to/#/@zutto:zutto.fi</a><br/>
|
||||||
|
<div content="time"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- load private services-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- import core scripts -->
|
||||||
|
<script type="module" src="main.js" async raw></script>
|
||||||
|
</html>
|
1
web/main.js
Normal file
1
web/main.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(()=>{"use strict";class e{constructor(){this.subscribers={}}subscribe(e,r){this.subscribers[e]||(this.subscribers[e]=[]),this.subscribers[e].push(r)}publish(e,r){this.subscribers[e]&&this.subscribers[e].forEach((e=>{e(r)}))}unsubscribe(e,r){this.subscribers[e]&&(this.subscribers[e]=this.subscribers[e].filter((e=>e!==r)))}}class r{constructor(){this.observers=new Map,this.initObserver()}initObserver(){new MutationObserver((e=>{e.forEach((e=>{e.addedNodes.forEach((e=>{1===e.nodeType&&this.checkNode(e)}))}))})).observe(document.body,{childList:!0,subtree:!0})}checkNode(e){this.observers.forEach(((r,t)=>{e.matches(t)&&(e.hasAttribute("lazyload")?this.observeLazyLoad(e,r):r(e))}))}observeLazyLoad(e,r){new IntersectionObserver(((e,t)=>{e.forEach((e=>{e.isIntersecting&&(r(e.target),t.unobserve(e.target))}))})).observe(e)}watch(e,r){document.querySelectorAll(e).forEach((e=>{e.hasAttribute("lazyload")?this.observeLazyLoad(e,r):r(e)})),this.observers.set(e,r)}}const t=new class{constructor(){return this.version="0.0.1",this.compiled=!1,this.watcher=new r,this.ps=new e,this}};class s{constructor(){this.html=""}async loadingStart(e){}async loadingEnd(e){console.log("super stop",e)}async render(e,r){var t="%s";e.hasAttribute("html-template")?t=e.getAttribute("html-template"):e.hasAttribute("template")&&(t=e.innerHTML,e.setAttribute("html-template",t)),e.innerHTML=t.replace("%s",r)}}class n extends s{constructor(){return super(),this}async loadingStart(e){return e.classList.add("shimmer"),this.render(e)}async loadingEnd(e){e.classList.remove("shimmer")}async render(e){return super.render(e,'\n <span id="loader" class="loading"></span>\n ')}}class i extends s{constructor(){return super(),this.loader=void 0,this}async loadingStart(e){return void 0!==this.loader&&this.loader.loadingEnd(e),this.loader=new n,this.loader.loadingStart(e),super.loadingStart(e)}async loadingEnd(e){return void 0!==this.loader&&this.loader.loadingEnd(e),super.loadingEnd(e)}async render(e,r){return super.render(e,r)}}class o{constructor(){}async go(e){try{const r=await fetch(e.url,{method:e.method||"GET",headers:e.headers||{},body:e.body||null,mode:e.mode||"cors",cache:e.cache||"default",credentials:e.credentials||"same-origin",redirect:e.redirect||"follow",referrer:e.referrer||"client"});if(!r.ok)throw new Error("Network response was not ok");return await r.text()}catch(e){console.error("error at fetch.get:",e)}}}new class extends s{constructor(){super(),t.watcher.watch('[content="time"]',(e=>{this.render(e)}))}async render(e){setInterval((()=>{super.render(e,(new Date).toLocaleTimeString("en-GB",{hour12:!1}).toString())}))}},new class extends i{constructor(){super(),this._ip="",this._source="https://ip.zutto.fi/",t.watcher.watch('[content="myip"]',(e=>{this.loadingStart(e),this.ip().then((r=>{try{this._ip=r.trim(),t.ps.publish("ip",{ip:this._ip}),this.render(e),this.loadingEnd(e)}catch(e){throw console.log("error here",e),e}}))}))}async ip(){try{const e=new o;return console.log("ok"),e.go({url:`${this._source}/ip`})}catch(e){throw e}}async loadingStart(e){return t.ps.publish("ip.load.start",{element:e}),super.loadingStart(e)}async loadingEnd(e){return t.ps.publish("ip.load.end",{element:e}),super.loadingEnd(e)}async render(e){try{const r=await super.render(e,this._ip);t.ps.publish("ip.rendered",{element:e,rendered:r})}catch(e){throw e}}}})();
|
63
web/styles.css
Normal file
63
web/styles.css
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* core overrides */
|
||||||
|
a { white-space: nowrap; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
div[content] {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -468px 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 468px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shimmer {
|
||||||
|
animation-duration: .5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: shimmer;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
background: #f6f7f8;
|
||||||
|
background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);
|
||||||
|
background-size: 800px 104px;
|
||||||
|
height: 96px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes dot-blink {
|
||||||
|
0% { content: ''; }
|
||||||
|
33% { content: '.'; }
|
||||||
|
66% { content: '..'; }
|
||||||
|
100% { content: '...'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
animation: dot-blink 0.80s infinite steps(1, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
25
webpack.config.js
Normal file
25
webpack.config.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
const path = require('path');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/js/app.js',
|
||||||
|
output: {
|
||||||
|
filename: 'main.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: 'styles.css',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
mode: 'development', // or 'production'
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue