This commit is contained in:
vd
2026-10-12 20:38:04 +02:00
parent 18172b4004
commit 4a01f0d7a9
132 changed files with 3809 additions and 4830 deletions
+87 -117
View File
@@ -12,7 +12,6 @@
return this._value;
}
set value(newValue) {
if (this._value == newValue) return;
this._value = newValue;
this.callSubscriptions();
}
@@ -24,6 +23,9 @@
this._bindings.add(fn);
fn(this._value);
}
subscribeSilent(fn) {
this._bindings.add(fn);
}
// stringify
toString() {
return JSON.stringify(this._value);
@@ -32,6 +34,7 @@
var ListState = class extends State {
additionHandlers = /* @__PURE__ */ new Set();
removalHandlers = /* @__PURE__ */ new Map();
genericRemovalHandlers = /* @__PURE__ */ new Set();
// init
constructor(initialItems) {
super(new Set(initialItems));
@@ -39,6 +42,7 @@
// list
add(...items) {
items.forEach((item) => {
if (this.value.has(item)) return;
this.value.add(item);
this.additionHandlers.forEach((handler) => handler(item));
});
@@ -47,8 +51,9 @@
remove(...items) {
items.forEach((item) => {
this.value.delete(item);
this.genericRemovalHandlers.forEach((handler) => handler(item));
if (!this.removalHandlers.has(item)) return;
this.removalHandlers.get(item)(item);
this.removalHandlers.get(item).forEach((handler) => handler(item));
this.removalHandlers.delete(item);
});
this.callSubscriptions();
@@ -62,11 +67,16 @@
[...this.value.values()].forEach(handler);
}
handleRemoval(item, handler) {
this.removalHandlers.set(item, handler);
if (!this.removalHandlers.has(item))
this.removalHandlers.set(item, /* @__PURE__ */ new Set());
this.removalHandlers.get(item).add(handler);
}
handleRemovals(handler) {
this.genericRemovalHandlers.add(handler);
}
// stringification
toString() {
const array = [...this.value];
const array = [...this.value.values()];
const json = JSON.stringify(array);
return json;
}
@@ -74,7 +84,7 @@
function createProxyState(statesToSubscibe, fn) {
const proxyState = new State(fn());
statesToSubscibe.forEach(
(state) => state.subscribe(() => proxyState.value = fn())
(state) => state.subscribeSilent(() => proxyState.value = fn())
);
return proxyState;
}
@@ -103,7 +113,7 @@
case "enter": {
element.addEventListener("keydown", (e) => {
if (e.key != "Enter") return;
value();
value(e);
});
break;
}
@@ -113,29 +123,19 @@
}
break;
}
case "keystroke": {
element.addEventListener("keydown", (e) => {
if (e.metaKey == false && e.ctrlKey == false) return;
if (e.key != directiveValue) return;
value(e);
});
break;
}
case "subscribe": {
if (directiveValue == "children") {
element.style.scrollBehavior = "smooth";
try {
const [listState, toElement] = value;
listState.handleAddition((newItem) => {
const child = toElement(newItem);
listState.handleRemoval(
newItem,
() => child.remove()
);
element.append(child);
element.scrollTop = element.scrollHeight;
});
} catch {
throw `error: cannot process subscribe:children directive because ListItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`;
}
} else {
const state = value;
state.subscribe(
(newValue) => element[directiveValue] = newValue
);
}
const state = value;
state.subscribe(
(newValue) => element[directiveValue] = newValue
);
break;
}
case "bind": {
@@ -167,6 +167,41 @@
);
break;
}
case "children": {
switch (directiveValue) {
case "set": {
const state = value;
state.subscribe((newValue) => {
element.innerHTML = "";
element.append(...[newValue].flat());
});
break;
}
case "append":
case "prepend": {
try {
const [listState, toElement] = value;
listState.handleAddition((newItem) => {
const child = toElement(newItem);
listState.handleRemoval(
newItem,
() => child.remove()
);
if (directiveValue == "append") {
element.append(child);
} else if (directiveValue == "prepend") {
element.prepend(child);
}
});
} catch (error) {
console.error(error);
throw `error: cannot process subscribe:children directive.
Usage: "children:append={[list, converter]}"; you can find a more detailed example in the documentation.`;
}
}
}
break;
}
default:
element.setAttribute(attributename, value);
}
@@ -213,6 +248,7 @@
// UTILITY METHODS
send(messageObject) {
if (this.ws == void 0) return false;
if (this.ws.readyState != 1) return false;
const messageString = JSON.stringify(messageObject);
this.ws.send(messageString);
return true;
@@ -235,6 +271,7 @@
} else if (data.deletedMailbox) {
return this.mailboxDeleteHandler(data.deletedMailbox);
} else {
this.send({ uuid: data.uuid, confirmingMessageReceived: true });
this.messageHandler(data);
}
});
@@ -288,7 +325,7 @@
};
// static/src/translations.tsx
var staticTextEnglish = {
var translations = {
mailbox: "Mailbox",
requestMailbox: "Request",
deleteMailbox: "Delete",
@@ -299,72 +336,23 @@
reconnecting: "Reconnecting...",
info: "Info",
message: "Message",
messages: "Messages",
message_placeholder: "Hello, world!",
message_placeholder_generic: "Type a message...",
noMessagesReceived: "No messages received",
messageLastReceived: "Last received message",
messagesReceived: "Received Messages",
send: "Send",
serverInfo: "Server Info",
settings: "Settings",
subscribeToChannel: "Subscribe to channel",
subscribe: "Subscribe",
unsubscribe: "Unubscribe"
unsubscribe: "Unubscribe",
privacyPolicy: "Privacy Policy",
privacyPolicyContent: [
"Data retention: This server may store messages sent for up to seven days if the recipient has mailboxes enabled in order to receive messages sent to them while they are offline. Mailboxes are deleted after seven days of inactivity, or upon user request. Messages are removed from mailboxes once received. Data stored about messages consists of the channel which the message is sent on, and the message body (content).",
"Processing: This server does not implement any kind of authentication or other means to identify, track, or verify user identity. The administration of this server has no means to track user activity or access data in any way not accessible to other non-administrative users. However, IP addresses are processed in order to uphold connections between client devices and this server (Legal basis: Art. 6(1)(b) GDPR \u2013 provision of the requested communication service)",
"Security: Data sent to this server is broadcasted on channels. Anyone may receive any message if they know the corresponding channel. It is strongly recommended to encrypt messages before sending them to this server (end-to-end encryption).",
"Rights: Pursuant to Art. 11 GDPR, because the administration does not retain records identifying the authors of individual transmissions, individual rights of access or erasure regarding specific messages cannot be fulfilled. Users have the right to lodge a complaint with a competent data protection authority (Art. 77 GDPR)."
],
imprint: "Imprint"
};
var allTranslations = {
en: staticTextEnglish,
es: {
mailbox: "Buz\xF3n",
requestMailbox: "Solicitar",
deleteMailbox: "Eliminar",
mailboxConnected: "Buz\xF3n activado",
channel: "Canal",
channel_placeholder: "mi-canal",
disconnected: "Sin coneci\xF3n",
reconnecting: "Conectando...",
info: "Info",
message: "Mensaje",
messages: "Mensajes",
message_placeholder: "Hola!",
message_placeholder_generic: "Nuevo mensaje",
noMessagesReceived: "Sin mensajes",
messageLastReceived: "\xDAltimo mensaje",
messagesReceived: "Todos los Mensajes",
send: "Enviar",
serverInfo: "Informaci\xF3n",
settings: "Configuraci\xF3n",
subscribeToChannel: "Suscribirse a un canal",
subscribe: "Suscribirse",
unsubscribe: "Cancelar"
},
de: {
mailbox: "Briefkasten",
requestMailbox: "Beantragen",
deleteMailbox: "L\xF6schen",
mailboxConnected: "Briefkasten aktiv",
channel: "Kanal",
channel_placeholder: "mein-kanal",
disconnected: "Verbindung getrennt",
reconnecting: "Verbindung wird hergestellt...",
info: "Daten",
message: "Nachricht",
messages: "Nachrichten",
message_placeholder: "Hallo!",
message_placeholder_generic: "Neue Nachricht",
noMessagesReceived: "Keine Nachrichten",
messageLastReceived: "Letzte Nachricht",
messagesReceived: "Empfangene Nachrichten",
send: "Senden",
serverInfo: "Serverdaten",
settings: "Einstellungen",
subscribeToChannel: "Kanal beitreten",
subscribe: "Beitreten",
unsubscribe: "Verlassen"
}
};
var language = navigator.language.substring(0, 2);
var translations = allTranslations[language];
// static/src/model.tsx
var Message = class {
@@ -532,41 +520,23 @@
))));
}
// static/src/messageScreen.tsx
var convertMessageToElement = (message) => {
return /* @__PURE__ */ createElement("div", { class: "tile", style: "flex: 0" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, message.channel), /* @__PURE__ */ createElement("b", null, message.body)));
};
function MessageScreen() {
return /* @__PURE__ */ createElement("article", { id: "message-screen" }, /* @__PURE__ */ createElement("header", null, translations.messagesReceived), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement(
"div",
{
class: "flex-column gap",
"subscribe:children": [messages, convertMessageToElement]
}
)), /* @__PURE__ */ createElement("footer", null, /* @__PURE__ */ createElement(
"input",
{
style: "max-width: unset",
placeholder: translations.message_placeholder_generic,
"bind:value": newMessageBody,
"on:enter": sendMessage
}
), /* @__PURE__ */ createElement(
"button",
{
class: "primary",
"on:click": sendMessage,
"toggle:disabled": isMessageEmpty
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
)));
// static/src/privacyScreen.tsx
var specificPolicy = new ListState([]);
fetch("/privacy").then(async (response) => {
(await response.text()).split("\n").forEach((x) => specificPolicy.add(x));
});
function paragraph(x) {
return /* @__PURE__ */ createElement("p", null, x);
}
function PrivacyScreen() {
return /* @__PURE__ */ createElement("article", { id: "privacy-screen" }, /* @__PURE__ */ createElement("header", null, translations.privacyPolicy), /* @__PURE__ */ createElement("div", null, ...translations.privacyPolicyContent.map(paragraph), /* @__PURE__ */ createElement("div", { "children:append": [specificPolicy, paragraph] })));
}
// static/src/index.tsx
document.body.prepend(
/* @__PURE__ */ createElement("menu", null, /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#info-screen", active: true }, /* @__PURE__ */ createElement("span", { class: "icon" }, "info"), translations.info), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#main-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "settings"), translations.settings), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#message-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "chat"), translations.messages))
/* @__PURE__ */ createElement("menu", null, /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#info-screen", active: true }, /* @__PURE__ */ createElement("span", { class: "icon" }, "info"), translations.info), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#main-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "settings"), translations.settings), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#privacy-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "privacy_tip"), translations.privacyPolicy), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "/imprint" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "attribution"), translations.imprint))
);
document.querySelector("main").append(InfoScreen(), MainScreen(), MessageScreen());
document.querySelector("main").append(InfoScreen(), MainScreen(), PrivacyScreen());
document.body.append(
/* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isDisconnected }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement(
"div",