general refactoring

This commit is contained in:
2025-12-23 13:52:38 +00:00
parent ce493956a8
commit 2daa3c3e6b
6 changed files with 125 additions and 1 deletions

View File

@@ -33,3 +33,6 @@ UPTIME_KUMA_DOMAIN=
############# Stalwart #############
STALWART_DOMAIN=
############# Textarea #############
TEXTAREA_DOMAIN=

View File

@@ -131,4 +131,12 @@
reverse_proxy http://stalwart:8080
}
############## textarea ##############
{$TEXTAREA_DOMAIN} {
root * /volume/textarea
file_server {
browse off
}
}
import Caddyfile.private

View File

@@ -24,6 +24,7 @@ services:
- ./data/data:/data
- ./data/config:/config
- ./data/log:/var/log/caddy
- ../volume:/volume
- ../private_volume:/private_volume
env_file:
- ./.env

View File

@@ -50,6 +50,8 @@
feeds:
- url: https://selfh.st/rss/
title: selfh.st
- url: https://antonz.org/index.xml
title: antonz.org
- size: small
widgets:

View File

@@ -6,7 +6,7 @@ networks:
services:
stalwart:
image: stalwartlabs/stalwart:v0.14
image: stalwartlabs/stalwart:v0.15
container_name: stalwart
restart: unless-stopped
labels:

110
volume/textarea/index.html Normal file
View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Textarea</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
color-scheme: light dark;
background-color: #fff;
color: #161616;
@media (prefers-color-scheme: dark) {
background-color: #000;
color: #fff;
}
}
article {
outline: none;
margin: 0 auto;
padding: 18px max(18px, calc(50vw - 400px));
width: 100%;
min-height: 100vh;
font: 18px / 1.5 system-ui;
tab-size: 4;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
white-space: pre-wrap;
text-wrap-style: pretty;
}
</style>
<article contenteditable="plaintext-only" spellcheck></article>
<script>
const article = document.querySelector('article')
article.addEventListener('input', debounce(500, save))
addEventListener('DOMContentLoaded', load)
addEventListener('hashchange', load)
async function load() {
try {
if (location.hash === '') {
await set(localStorage.getItem('hash') ?? '')
if (article.textContent) history.replaceState({}, '', await get())
return
}
await set(location.hash)
} catch (e) {
article.textContent = ''
article.removeAttribute('style')
}
updateTitle()
}
async function save() {
const hash = await get()
if (location.hash !== hash) history.replaceState({}, '', hash)
try { localStorage.setItem('hash', hash) } catch (e) {}
updateTitle()
}
async function set(hash) {
const [content, style] = (await decompress(hash.slice(1))).split('\x00')
article.textContent = content
if (style) article.setAttribute('style', style)
}
async function get() {
const style = article.getAttribute('style')
const content = article.textContent + (style !== null ? '\x00' + style : '')
return '#' + await compress(content)
}
function updateTitle() {
const match = article.textContent.match(/^\n*#(.+)\n/)
document.title = match?.[1] ?? 'Textarea'
}
async function compress(string) {
const byteArray = new TextEncoder().encode(string)
const stream = new CompressionStream('deflate-raw')
const writer = stream.writable.getWriter()
writer.write(byteArray)
writer.close()
const buffer = await new Response(stream.readable).arrayBuffer()
return new Uint8Array(buffer).toBase64().replace(/\+/g, "-").replace(/\//g, "_")
}
async function decompress(b64) {
const byteArray = Uint8Array.fromBase64(b64.replace(/-/g, "+").replace(/_/g, "/"))
const stream = new DecompressionStream('deflate-raw')
const writer = stream.writable.getWriter()
writer.write(byteArray)
writer.close()
const buffer = await new Response(stream.readable).arrayBuffer()
return new TextDecoder().decode(buffer)
}
function debounce(ms, fn) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => fn(...args), ms)
}
}
</script>