Compare commits

...

11 commits

Author SHA1 Message Date
fenris a08bd73e9d [mod] Titel der Website 2025-10-22 16:10:39 +02:00
fenris 56658aa7f6 [mod] passwort-exhibit-knopf 2025-10-09 02:38:16 +02:00
fenris 2434d46ca6 [mod] conf-example 2025-10-06 18:07:04 +02:00
fenris 4b6ed73bd3 [mod] namespace: espe -> zackeneule [mod] page:invitation_handle [mod] tools:deploy:default target path 2025-10-06 18:06:50 +02:00
fenris 2c4cba0548 [upd] plankton 2025-10-06 18:03:46 +02:00
fenris 3cb0fe64f2 [mod] URL-Templates nicht länger ans Backend senden 2025-10-05 23:26:40 +02:00
fenris 0e04d8d88b [mod] readme 2025-09-22 21:30:19 +02:00
fenris 8ad256c7eb [mod] readme 2025-09-22 18:34:36 +02:00
Christian Fraß 46ae15b62d [mod] loc 2025-08-25 22:33:25 +00:00
Christian Fraß 2cb6cc55cb [mod] invitation_handle 2025-08-25 14:08:35 +00:00
Christian Fraß 23504111ec Einladungs-System (#2)
## Aufgaben

- [\#193](https://vikunja.ramsch.sx/tasks/193)

## Zugehörige MRs

- https://forgejo.linke.sx/espe/datamodel/pulls/3
- https://forgejo.linke.sx/espe/backend/pulls/6

Reviewed-on: https://forgejo.linke.sx/espe/frontend-zackeneule/pulls/2
Co-authored-by: Christian Fraß <christian.frass@dielinke-glauchau.de>
Co-committed-by: Christian Fraß <christian.frass@dielinke-glauchau.de>
2025-08-25 14:23:44 +02:00
57 changed files with 11459 additions and 2129 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,10 @@
"backend": {
"scheme": "http",
"host": "localhost",
"port": 7979,
"port": 4916,
"path_base": ""
},
"settings": {
"title": "Lokaler Espe-Test"
}
}

183
misc/model.ts Normal file
View file

@ -0,0 +1,183 @@
type type_group = {
name : boolean;
};
type type_property = {
name : string;
type : (
"boolean"
|
"int"
|
"string"
);
};
type type_member = {
name : string;
groups : Array<type_group>;
email_address : (null | string);
password_image : (null | string);
active : boolean;
};
type type_model = {
groups : Array<{id : int; data : type_group;}>;
properties : Array<{id : int; data : type_property;}>;
members : Array<{id : int; data : type_member;}>;
};
const model_linke_sx : type_model = {
"groups": [
{
"id": 1,
"data": {
"name": "lv-sachsen"
}
},
{
"id": 2,
"data": {
"name": "kv-zwickau"
}
},
{
"id": 3,
"data": {
"name": "ov-glauchau"
}
},
{
"id": 4,
"data": {
"name": "lag-netzpolitik_und_gaming"
}
},
{
"id": 5,
"data": {
"name": "kv-leipzig"
}
},
{
"id": 6,
"data": {
"name": "kv-dresden"
}
},
],
"properties": [
{
"id": 1,
"data": {
"name": "real_name",
"type": "string"
}
},
{
"id": 2,
"data": {
"name": "allow_email_sending",
"type": "boolean"
}
},
],
"members": [
{
"id": 1,
"data": {
"name": "m1973476",
"groups": [1, 2, 3, 4],
"email_address": "christian.frass@dielinke-glauchau.de",
"password_image": "____",
"active": true,
"properties": [
{
"id": 1,
"value": "Christian Fraß"
},
{
"id": 2,
"value": true
},
]
}
},
{
"id": 2,
"data": {
"name": "m2487647",
"groups": [1, 2, 4, 6],
"email_address": "paul.furkert@dielinke-dresden.de",
"password_image": "____",
"active": true,
"properties": [
{
"id": 1,
"value": "Paul Furkert"
},
{
"id": 2,
"value": true
},
]
}
},
],
};
const model_ramsch_sx : type_model = {
"groups": [
{
"id": 1,
"data": {
"name": "kulturknall"
}
},
{
"id": 2,
"data": {
"name": "lixer"
}
},
{
"id": 3,
"data": {
"name": "dill"
}
},
],
"properties": [
],
"members": [
{
"id": 1,
"data": {
"name": "fenris",
"groups": [1, 2, 3],
"email_address": "roydfalk@folksprak.org",
"password_image": "____",
"active": true,
"properties": [
]
}
},
{
"id": 2,
"data": {
"name": "magda",
"groups": [2],
"email_address": "magda@zschocher.com",
"password_image": "____",
"active": true,
"properties": [
]
}
},
]
};

View file

@ -1,8 +1,8 @@
# Espe | Frontend
# Espe | Frontend: Zackeneule
## Beschreibung
- Web-Anwendung für [Espe](https://gitlab.die-linke.cloud/espe/documentation)
- Web-Anwendung für [Espe](/misc/espe-meta)
## Erstellung

View file

@ -4,52 +4,49 @@
},
"tree": {
"common.not_used": "nicht verwendet",
"common.changeable": "änderbar",
"common.initial_value": "Vorausfüllung",
"common.timezone_shift": "Zeitzonen-Verschiebung",
"common.date": "Datum",
"common.time": "Uhzeit",
"common.loading": "laden …",
"common.checking": "prüfen …",
"domain.group.name.label": "Name",
"domain.group.label.label": "Beschriftung",
"domain.member.member": "Mitglied",
"domain.member.name.label": "Anmeldename",
"domain.member.name.help": "der Name für die Anmeldung bei den Netz-Diensten.\n\nkann später NICHT mehr geändert werden",
"domain.member.label.label": "Anzeigename",
"domain.member.label.help": "wie du angezeigt wirst\n\nkann später geändert werden",
"domain.member.groups.label": "Gruppen",
"domain.member.groups.help": "die Gruppen, denen du zugeordnet bist",
"domain.member.registered.label": "registriert",
"domain.member.enabled.label": "aktiviert",
"domain.member.email_address.label": "E-Mail-Adresse",
"domain.member.email_address.help": "für Benachrichtigungen von Diensten",
"domain.member.password_set.label": "Passwort gesetzt",
"domain.invitation.key.label": "Schlüssel",
"domain.invitation.expiry.label": "Ablaufzeitpunkt",
"domain.invitation.url.label": "URL",
"widget.password_policy.title": "Passwort-Richtlinien",
"widget.password_policy.minimum_length": "mindestens {{minimum_length}} Zeichen",
"widget.password_policy.maximum_length": "höchstens {{maximum_length}} Zeichen",
"widget.password_policy.must_contain_letter": "muss einen Buchstaben beinhalten",
"widget.password_policy.must_contain_number": "muss eine Zahl beinhalten",
"widget.password_policy.must_contain_special_character": "muss ein Sonderzeichen beinhalten",
"page.login.title": "Anmelden",
"page.login.name": "Name",
"page.login.password": "Passwort",
"page.login.submit": "Anmelden",
"page.create.title": "Mitglied anlegen",
"page.list.title": "Mitglieder-Liste",
"page.view.title": "Mitglied",
"page.view.form.field.membership_number.label": "Mitgliedsnummer",
"page.view.form.field.name_real_value.label": "Echter Name",
"page.view.form.field.name_real_index.label": "Namens-Index",
"page.view.form.field.groups.label": "Gruppen",
"page.view.form.field.registered.label": "registriert",
"page.view.form.field.enabled.label": "aktiviert",
"page.view.form.field.email_address_private.label": "private E-Mail-Adresse",
"page.view.form.field.email_address_veiled.label": "pseudonymisierte E-Mail-Adresse",
"page.view.form.field.email_address_nominal.label": "namentliche E-Mail-Adresse",
"page.view.form.field.email_redirect_to_private_address.label": "eingehende E-Mails zu privater Adresse umleiten",
"page.view.form.field.email_allow_sending.label": "Versenden von E-Mails erlauben",
"page.view.form.field.name_login.label": "Anmeldename",
"page.view.form.field.password_set.label": "Passwort gesetzt",
"page.view.form.action.save": "Änderungen speichern",
"page.view.form.action.summon": "Zur Registrierung auffordern",
"page.view.misc.summoned": "Benachrichtigung verschickt",
"page.view.misc.test_info": "Im Produktiv-Szenario würde an dieser Stelle eine E-Mail an die hinterlegte private E-Mail-Adresse des Mitglieds versendet werden mit einem Willkommens-Gruß und dem Aufruf folgenden Link zu öffnen:\n\n{{url}}",
"page.register.title": "Registrieren",
"page.register.form.field.email_address.label": "Partei-E-Mail-Adresse einrichten",
"page.register.form.field.email_address.help": "Für Partei-Angelegenheiten möchten wir dir anbieten gesonderte E-Mail-Adressen zu verwenden.\n\nDeine namentliche E-Mail-Adresse würde lauten »{{email_address_nominal}}« und die pseudonymisierte »{{email_address_veiled}}«\n\nDie Partei-E-Mail-Adressen können zum Empfangen von E-Mails verwendet werden. Falls es nötig werden sollte, dass du auch E-Mails mit über die Partei-Adresse verschicken kannst, wende dich bitte an den/die Mitgliederbeauftragte:n!",
"page.register.form.field.email_address.option.none": "keine",
"page.register.form.field.email_address.option.only_veiled": "nur pseudonymisiert",
"page.register.form.field.email_address.option.both": "pseudonymisiert und namentlich",
"page.register.form.field.email_redirect.label": "eingehende E-Mails an private Adresse leiten",
"page.register.form.field.email_redirect.help": "",
"page.register.form.field.password_value.label": "Passwort für Netz-Dienste",
"page.register.form.field.password_value.help": "das Passwort für die Anmeldung bei den Netz-Diensten.\n\nDu solltest dir merken oder geeignet abspeichern, was du hier einträgst.\n\nSolltest du dieses Passwort mal vergessen oder verlieren, hast du die Möglichkeit ein neues zu setzen.",
"page.register.form.field.password_confirmation.label": "Passwort wiederholen",
"page.register.form.field.password_confirmation.help": "",
"page.register.form.submit": "Abschicken",
"page.register.flaw.already_registered": "bereits registriert",
"page.register.flaw.password_mismatch": "die Passwörter stimmen nicht überein",
"page.register.flaw.password_too_short": "das Passwort muss mindestens {{minimum_length}} Zeichen haben",
"page.register.flaw.password_too_long": "das Passwort darf höchstens {{maximum_length}} Zeichen haben",
"page.register.flaw.password_lacks_letter": "das Passwort muss einen Buchstaben beinhalten",
"page.register.flaw.password_lacks_number": "das Passwort muss eine Zahl beinhalten",
"page.register.flaw.password_lacks_special_character": "das Passwort muss ein Sonderzeichen beinhalten",
"page.register.flaw.unhandled_error": "da ist etwas schief gelaufen :/",
"page.register.success": "Danke!",
"page.logout.title": "Abmelden",
"page.group_list.title": "Gruppen",
"page.group_create.title": "Gruppe anlegen",
"page.group_create.form.submit": "Abschicken",
"page.group_view.title": "Gruppe bearbeiten",
"page.member_create.title": "Mitglied anlegen",
"page.member_list.title": "Mitglieder",
"page.member_view.title": "Mitglied",
"page.member_view.form.action.save": "Änderungen speichern",
"page.password_change_init.title": "Passwort ändern",
"page.password_change_init.info": "Falls dein Mitglieds-Konto zugeordnet werden kann, wird eine E-Mail mit weiteren Anweisungen an deine private Adresse geschickt.",
"page.password_change_init.identifier": "Anmelde-Name oder private E-Mail-Adresse",
@ -69,6 +66,23 @@
"page.password_change_exec.flaw.password_lacks_number": "das Passwort muss ein Zahl beinhalten",
"page.password_change_exec.flaw.password_lacks_special_character": "das Passwort muss ein Sonderzeichen beinhalten",
"page.password_change_exec.flaw.unhandled_error": "da ist etwas schief gelaufen :/",
"page.password_change_exec.status.success": "erledigt"
"page.password_change_exec.status.success": "erledigt",
"page.invitation_list.title": "Einladungen",
"page.invitation_create.title": "Einladung anlegen",
"page.invitation_create.form.field.send_immediatly.label": "Link sofort versenden",
"page.invitation_create.form.action.submit": "anlegen",
"page.invitation_view.form.action.delete": "löschen",
"page.invitation_view.title": "Einladung",
"page.invitation_handle.title": "Einladung",
"page.invitation_handle.message.invalid": "ungültig",
"page.invitation_handle.message.successful": "erfolgreich; eine E-Mail mit Zugangsdaten wird an die angegebene Adresse geschickt",
"page.invitation_handle.form.action.submit": "annehmen",
"page.invitation_handle.form.field.password_value.label": "Passwort",
"page.invitation_handle.form.field.password_value.help": "das Passwort für die Anmeldung bei den Netz-Diensten.\n\ndu solltest dir merken oder geeignet abspeichern, was du hier einträgst.\n\nsolltest du dieses Passwort mal vergessen oder verlieren, hast du die Möglichkeit ein neues zu setzen.",
"page.invitation_handle.form.field.password_confirmation.label": "Passwort wiederholen",
"page.invitation_handle.flaw.already_registered": "bereits registriert",
"page.invitation_handle.flaw.password_invalid": "Passwort ungültig",
"page.invitation_handle.flaw.password_mismatch": "die Passwörter stimmen nicht überein",
"page.invitation_handle.flaw.unhandled_error": "da ist etwas schief gelaufen :/"
}
}

View file

@ -4,71 +4,85 @@
},
"tree": {
"common.not_used": "not used",
"page.login.title": "Login",
"common.changeable": "changeable",
"common.initial_value": "initial value",
"common.timezone_shift": "timezone shift",
"common.date": "date",
"common.time": "time",
"common.loading": "loading …",
"common.checking": "checking …",
"domain.group.name.label": "name",
"domain.group.label.label": "label",
"domain.member.member": "member",
"domain.member.name.label": "login name",
"domain.member.name.help": "the name for logging in to the online services\n\ncan NOT be changed later on",
"domain.member.label.label": "display name",
"domain.member.label.help": "how you are displayed\n\ncan be changed later on",
"domain.member.groups.label": "groups",
"domain.member.groups.help": "groups, which you belong to",
"domain.member.registered.label": "registered",
"domain.member.enabled.label": "enabled",
"domain.member.email_address.label": "e-mail address",
"domain.member.email_address.help": "for notifications from services",
"domain.member.password_set.label": "password set",
"domain.invitation.key.label": "key",
"domain.invitation.expiry.label": "expiration",
"domain.invitation.url.url": "URL",
"widget.password_policy.title": "password policy",
"widget.password_policy.minimum_length": "at least {{minimum_length}} characters",
"widget.password_policy.maximum_length": "at most {{maximum_length}} characters",
"widget.password_policy.must_contain_letter": "must contain a letter",
"widget.password_policy.must_contain_number": "must contain a number",
"widget.password_policy.must_contain_special_character": "must contain a special character",
"page.login.title": "login",
"page.login.name": "name",
"page.login.password": "password",
"page.login.submit": "login",
"page.create.title": "Create member",
"page.list.title": "Member list",
"page.view.title": "Member",
"page.view.form.field.membership_number.label": "membership number",
"page.view.form.field.name_real_value.label": "real name",
"page.view.form.field.name_real_index.label": "name index",
"page.view.form.field.groups.label": "groups",
"page.view.form.field.registered.label": "registered",
"page.view.form.field.enabled.label": "enabled",
"page.view.form.field.email_address_private.label": "private e-mail address",
"page.view.form.field.email_address_veiled.label": "veiled e-mail address",
"page.view.form.field.email_address_nominal.label": "nominal e-mail address",
"page.view.form.field.email_redirect_to_private_address.label": "redirect incoming e-mails to private address",
"page.view.form.field.email_allow_sending.label": "allow sending e-mails",
"page.view.form.field.name_login.label": "login name",
"page.view.form.field.password_set.label": "password set",
"page.view.form.action.save": "save changes",
"page.view.form.action.summon": "urge for registration",
"page.view.misc.summoned": "notification sent",
"page.view.misc.test_info": "in a productive environment the system would now send an e-mail to the member's private address with a welcome note and a call to open the following link:\n\n{{url}}",
"page.register.title": "Register",
"page.register.form.field.email_address.label": "Set up party e-mail address",
"page.register.form.field.email_address.help": "We offer you to use a special e-mail address for any party concerns.\n\nYour namely e-mail address would be »{{email_address_nominal}}« and the veiled one »{{email_address_veiled}}«\n\nThe party e-mail address may be used for the reception of e-mails. In case it becomes necessary for you to submit e-mails via a party address, please get in contact with your membership authority!",
"page.register.form.field.email_address.option.none": "none",
"page.register.form.field.email_address.option.only_veiled": "only veiled",
"page.register.form.field.email_address.option.both": "both, veiled and namely",
"page.register.form.field.email_redirect.label": "redirect incoming e-mails to private address",
"page.register.form.field.email_redirect.help": "",
"page.register.form.field.password_value.label": "password for online services",
"page.register.form.field.password_value.help": "the password for logging in to the online services\n\nYou should remember, denote or save properly, what you enter here.\n\nIn case you forget or lose the password some day, you can reset it.",
"page.register.form.field.password_confirmation.label": "confirm password",
"page.register.form.field.password_confirmation.help": "",
"page.register.form.submit": "submit",
"page.register.flaw.already_registered": "already registered",
"page.register.flaw.password_mismatch": "passwords do not match",
"page.register.flaw.password_too_short": "the password must have at least {{minimum_length}} characters",
"page.register.flaw.password_too_long": "the password must not have more than {{maximum_length}} characters",
"page.register.flaw.password_lacks_letter": "the password must contain a letter",
"page.register.flaw.password_lacks_number": "the password must contain a number",
"page.register.flaw.password_lacks_special_character": "the password must contain a special character",
"page.register.flaw.unhandled_error": "something went wrong :/",
"page.register.success": "Thanks!",
"page.password_change_init.title": "Change Password",
"page.password_change_init.info": "In case your member account can be found, an e-mail with further instructions will be sent to your private e-mail address.",
"page.password_change_init.identifier": "Login name or private e-mail address",
"page.password_change_init.submit": "Procede",
"page.logout.title": "logout",
"page.group_list.title": "groups",
"page.group_create.title": "create group",
"page.group_create.form.submit": "submit",
"page.group_view.title": "edit group",
"page.member_create.title": "Create member",
"page.member_list.title": "members",
"page.member_view.title": "member",
"page.member_view.form.action.save": "save changes",
"page.password_change_init.title": "change Password",
"page.password_change_init.info": "in case your member account can be found, an e-mail with further instructions will be sent to your private e-mail address.",
"page.password_change_init.identifier": "login name or private e-mail address",
"page.password_change_init.submit": "procede",
"page.password_change_init.status.wait": "just a second …",
"page.password_change_init.status.success": "Done. Take a look into your e-mail inbox",
"page.password_change_init.status.fail": "Something went wrong :/",
"page.password_change_exec.title": "Change Password",
"page.password_change_init.status.fail": "something went wrong :/",
"page.password_change_exec.title": "change password",
"page.password_change_exec.form.field.password_value.label": "password",
"page.password_change_exec.form.field.password_confirmation.label": "confirm password",
"page.password_change_exec.form.submit": "Change",
"page.password_change_exec.form.submit": "change",
"page.password_change_exec.flaw.token_invalid": "permission expired",
"page.password_change_exec.flaw.password_mismatch": "die Passwörter stimmen nicht überein",
"page.password_change_exec.flaw.password_too_short": "das Passwort muss mindestens {{minimum_length}} Zeichen haben",
"page.password_change_exec.flaw.password_too_long": "das Passwort darf höchstens {{maximum_length}} Zeichen haben",
"page.password_change_exec.flaw.password_lacks_letter": "das Passwort muss einen Buchstaben beinhalten",
"page.password_change_exec.flaw.password_lacks_number": "das Passwort muss ein Zahl beinhalten",
"page.password_change_exec.flaw.password_lacks_special_character": "das Passwort muss ein Sonderzeichen beinhalten",
"page.password_change_exec.flaw.unhandled_error": "da ist etwas schief gelaufen :/",
"page.password_change_exec.status.success": "done"
"page.password_change_exec.flaw.password_mismatch": "the passwords do not match",
"page.password_change_exec.flaw.password_too_short": "the password must have at least {{minimum_length}} characters",
"page.password_change_exec.flaw.password_too_long": "the password must have at most {{maximum_length}} characters",
"page.password_change_exec.flaw.password_lacks_letter": "the password must contain a letter",
"page.password_change_exec.flaw.password_lacks_number": "the password must contain a number",
"page.password_change_exec.flaw.password_lacks_special_character": "the password must contain a special character",
"page.password_change_exec.flaw.unhandled_error": "something went wrong :/",
"page.password_change_exec.status.success": "done",
"page.invitation_list.title": "invitations",
"page.invitation_create.title": "create invitation",
"page.invitation_create.form.field.send_immediatly.label": "send link immediatly",
"page.invitation_create.form.action.submit": "create",
"page.invitation_view.form.action.delete": "delete",
"page.invitation_view.title": "invitation",
"page.invitation_handle.title": "invitation",
"page.invitation_handle.message.invalid": "invalid",
"page.invitation_handle.message.successful": "successful; an e-mail, containing the credentials, is sent to the provided address",
"page.invitation_handle.form.action.submit": "accept",
"page.invitation_handle.form.field.password_value.label": "password",
"page.invitation_handle.form.field.password_value.help": "the password for logging in to the online services\n\nyou should remember, denote or save properly, what you enter here.\n\nin case you forget or lose the password some day, you can reset it.",
"page.invitation_handle.form.field.password_confirmation.label": "confirm password",
"page.invitation_handle.flaw.already_registered": "already registered",
"page.invitation_handle.flaw.password_invalid": "invalid password",
"page.invitation_handle.flaw.password_mismatch": "passwords do not match",
"page.invitation_handle.flaw.unhandled_error": "something went wrong :/"
}
}

View file

@ -14,43 +14,30 @@ You should have received a copy of the GNU General Public License along with thi
*/
.view-title
{
display: none;
}
/*
.view-form > .plankton_form
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.view-form > .plankton_form > .plankton_form_input
{
flex: 1;
}
.view-form > .plankton_form > .plankton_form_actions
namespace _zackeneule.helpers
{
flex: 1;
}
/**
* @todo outsource
*/
.plankton_form_actions > *
export function template_request(
id : string
) : DocumentFragment
{
display: block;
margin: 8px;
let dom_template = document.querySelector("template#" + id);
// return template["content"].cloneNode(true);
return (document.importNode(dom_template["content"], true) as DocumentFragment)
}
section.view .plankton_input_list_element > *
/**
* @todo outsource
*/
export function null_when_empty(
str : (null | string)
) : (null | string)
{
display: inline-block;
return (((str === null) || (str === "")) ? null : str);
}
section.view .plankton_input_list_element_input
{
}

103
source/logic.ts Normal file
View file

@ -0,0 +1,103 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _zackeneule.logic
{
/**
*/
let _cache : lib_plankton.cache.type_subject<any>;
/**
*/
export async function init(
) : Promise<void>
{
_cache = lib_plankton.cache.make<int>(
{
// "chest": lib_plankton.storage.memory.implementation_chest<int>({}),
}
);
await lib_plankton.cache.init(
_cache
);
}
/**
*/
export function group_data(
) : Promise<
{
pool : Map<
int,
{
name : string;
label : string;
}
>;
order : Array<int>;
}
>
{
return lib_plankton.cache.get<
{
pool : Map<
int,
{
name : string;
label : string;
}
>;
order : Array<int>;
}
>(
_cache,
"group_data",
120,
async () => {
const array : Array<
{
id : int;
preview : {
name : string;
label : string;
};
}
> = await _zackeneule.backend.group_list();
const pool : Map<int, {name : string; label : string;}> = new Map<int, {name : string; label : string;}>();
const order : Array<int> = new Array<int>();
for (const entry of array)
{
pool.set(
entry.id,
{
"name": entry.preview.name,
"label": entry.preview.label,
}
);
order.push(entry.id);
}
return {
"pool": pool,
"order": order,
};
}
);
}
}

View file

@ -25,7 +25,7 @@ async function update_nav(
options = Object.assign(
{
"mode": (
(await _espe.backend.logged_in())
(await _zackeneule.backend.logged_in())
? "logged_in"
: "logged_out"
),
@ -56,22 +56,27 @@ function setup_nav(
> = [
{
"location": {"name": "login", "parameters": {}},
"label": "Anmelden",
"label": lib_plankton.translate.get("page.login.title"),
"classes": ["logged_out"],
},
{
"location": {"name": "list", "parameters": {}},
"label": "Liste",
"location": {"name": "group_list", "parameters": {}},
"label": lib_plankton.translate.get("page.group_list.title"),
"classes": ["logged_in"],
},
{
"location": {"name": "create", "parameters": {}},
"label": "Anlegen",
"location": {"name": "member_list", "parameters": {}},
"label": lib_plankton.translate.get("page.member_list.title"),
"classes": ["logged_in"],
},
{
"location": {"name": "invitation_list", "parameters": {}},
"label": lib_plankton.translate.get("page.invitation_list.title"),
"classes": ["logged_in"],
},
{
"location": {"name": "logout", "parameters": {}},
"label": "Abmelden",
"label": lib_plankton.translate.get("page.logout.title"),
"classes": ["logged_in"],
},
];
@ -99,9 +104,9 @@ function setup_nav(
async function main(
) : Promise<void>
{
await _espe.conf.load();
await _zackeneule.conf.load();
await _espe.backend.init();
await _zackeneule.backend.init();
await lib_plankton.translate.initialize(
{
@ -115,6 +120,8 @@ async function main(
}
);
await _zackeneule.logic.init();
await lib_plankton.zoo_page.init(
document.querySelector("main"),
{
@ -123,8 +130,16 @@ async function main(
);
// set title
document.querySelector("header > h1").textContent = _espe.conf.get().settings.title;
document.querySelector("title").textContent = _espe.conf.get().settings.title;
{
const title : string = lib_plankton.string.coin(
"Espe | {{conf_title}}",
{
"conf_title": _zackeneule.conf.get().settings.title,
}
);
document.querySelector("header > h1").textContent = title;
document.querySelector("title").textContent = title;
}
setup_nav();

View file

@ -1,107 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"create",
(parameters, target_element) => {
target_element.appendChild(template_request("create"));
target_element.querySelector(".create-title").textContent = lib_plankton.translate.get("page.create.title");
const form = new lib_plankton.zoo_form.class_form<
{
membership_number : string;
name_real_value : string;
email_address_private : (null | string);
groups : Array<string>;
},
{
membership_number : string;
name_real_value : string;
email_address_private : string;
groups : Array<string>;
}
>(
value => ({
"membership_number": value.membership_number,
"name_real_value": value.name_real_value,
"email_address_private": (value.email_address_private ?? ""),
"groups": value.groups,
}),
representation => ({
"membership_number": representation.membership_number,
"name_real_value": representation.name_real_value,
"email_address_private": representation.email_address_private,
"groups": representation.groups,
}),
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "name_real_value",
"input": new lib_plankton.zoo_input.class_input_text(),
/**
* @todo translate
*/
"label": "Echter Name",
},
{
"name": "membership_number",
"input": new lib_plankton.zoo_input.class_input_text(),
/**
* @todo translate
*/
"label": "Mitgliedsnummer",
},
{
"name": "email_address_private",
"input": new lib_plankton.zoo_input.class_input_text(),
/**
* @todo translate
*/
"label": "Private E-Mail-Adresse",
},
{
"name": "groups",
"input": new lib_plankton.zoo_input.class_input_list(
() => new lib_plankton.zoo_input.class_input_text()
),
"label": lib_plankton.translate.get("page.view.form.field.groups.label"),
},
]
),
[
{
"label": "Senden",
"procedure": async (get_value, get_representation) => {
const value = await get_value();
const id : int = await _espe.backend.member_project(
value,
lib_plankton.zoo_page.encode(
{
"name": "view",
"parameters": {
"id": "{{id}}",
}
}
)
);
lib_plankton.zoo_page.set({"name": "view", "parameters": {"id": id}});
},
}
]
);
form.setup(target_element.querySelector(".create-form") as HTMLElement);
}
);

View file

@ -0,0 +1,63 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"group_create",
(parameters, target_element) => {
target_element.appendChild(_zackeneule.helpers.template_request("group_create"));
target_element.querySelector(".group_create-title").textContent = lib_plankton.translate.get("page.group_create.title");
const form = new lib_plankton.zoo_form.class_form<
{
name : string;
label : string;
},
{
name : string;
label : string;
}
>(
value => value,
representation => representation,
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("domain.group.name.label"),
},
{
"name": "label",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("domain.group.label.label"),
},
]
),
[
{
"label": lib_plankton.translate.get("page.group_create.form.submit"),
"procedure": async (get_value, get_representation) => {
const value = await get_value();
const id : int = await _zackeneule.backend.group_add(value);
// lib_plankton.zoo_page.set({"name": "group_view", "parameters": {"id": id}});
lib_plankton.zoo_page.set({"name": "group_list", "parameters": {}});
},
}
]
);
form.setup(target_element.querySelector(".group_create-form") as HTMLElement);
}
);

View file

@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>.
-->
<template id="create">
<h2 class="create-title"></h2>
<div class="create-form"></div>
<template id="group_create">
<h2 class="group_create-title"></h2>
<div class="group_create-form"></div>
</template>

View file

@ -14,46 +14,51 @@ You should have received a copy of the GNU General Public License along with thi
*/
lib_plankton.zoo_page.register(
"list",
"group_list",
(parameters, target_element) => {
type type_item = {
id : int;
preview : {
membership_number : string;
name_real_value : string;
name_real_index : int;
name : string;
label : string;
};
};
const term : (null | string) = (parameters["term"] ?? "");
target_element.appendChild(template_request("list"));
target_element.appendChild(_zackeneule.helpers.template_request("group_list"));
target_element.querySelector(".list-title").textContent = lib_plankton.translate.get("page.list.title");
target_element.querySelector(".group_list-title").textContent = lib_plankton.translate.get("page.group_list.title");
// create link
{
const element : HTMLElement = target_element.querySelector(".group_list-create");
element.setAttribute(
"href",
lib_plankton.zoo_page.encode(
{
"name": "group_create",
"parameters": {
}
}
)
);
element.textContent = lib_plankton.translate.get("page.group_create.title");
}
const search : lib_plankton.zoo_search.type_search<type_item> = lib_plankton.zoo_search.make<type_item>(
(term) => _espe.backend.member_list(term),
(term) => _zackeneule.backend.group_list(),
{
"encode_item": (item) => lib_plankton.string.coin(
"{{membership_number}} | {{name}}{{addition}}",
"{{label}}",
{
"membership_number": item.preview.membership_number,
"name": item.preview.name_real_value,
"addition": (
(
(item.preview.name_real_index === null)
||
(item.preview.name_real_index <= 1)
)
? ""
: (" (" + item.preview.name_real_index.toFixed(0) + ")")
),
"label": item.preview.label,
}
),
"hooks_begin": [
(term) => {
lib_plankton.zoo_page.set(
{
"name": "list",
"name": "group_list",
"parameters": {
"term": term,
}
@ -65,7 +70,7 @@ lib_plankton.zoo_page.register(
(item) => {
lib_plankton.zoo_page.set(
{
"name": "view",
"name": "group_view",
"parameters": {
"id": item.id.toFixed(0),
}
@ -77,7 +82,7 @@ lib_plankton.zoo_page.register(
);
lib_plankton.zoo_search.render(
search,
target_element.querySelector(".list-search"),
target_element.querySelector(".group_list-search"),
{
"state": {
"term": term,

View file

@ -0,0 +1,22 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="group_list">
<h2 class="group_list-title"></h2>
<div class="group_list-links">
<a class="group_list-create"></a>
</div>
<div class="group_list-search"></div>
</template>

View file

@ -1,4 +1,4 @@
<!--
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
@ -11,9 +11,9 @@ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Gen
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
*/
<template id="list">
<h2 class="list-title"></h2>
<div class="list-search"></div>
</template>
.group_list-links
{
margin-bottom: 16px;
}

View file

@ -0,0 +1,69 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"group_view",
async (parameters, target_element) => {
const id : int = parseInt(parameters["id"]);
let dom_fragment : DocumentFragment = _zackeneule.helpers.template_request("group_view");
dom_fragment.querySelector(".group_view-title").textContent = lib_plankton.translate.get("page.group_view.title");
const group_object = await _zackeneule.backend.group_get(id);
const form = new lib_plankton.zoo_form.class_form<
{
name : string;
label : string;
},
{
name : string;
label : string;
}
>(
value => value,
representation => representation,
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("domain.group.name.label"),
},
{
"name": "label",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("domain.group.label.label"),
},
]
),
[
{
"label": lib_plankton.translate.get("page.group_create.form.submit"),
"procedure": async (get_value, get_representation) => {
const value = await get_value();
await _zackeneule.backend.group_modify(id, value);
lib_plankton.zoo_page.set({"name": "group_list", "parameters": {}});
},
}
]
);
await form.setup(dom_fragment.querySelector(".group_view-form") as HTMLElement);
await form.input_write(group_object);
target_element.appendChild(dom_fragment);
},
);

View file

@ -13,10 +13,10 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>.
-->
<template id="view">
<section class="view">
<h2 class="view-title"></h2>
<div class="view-form">
<template id="group_view">
<section class="group_view">
<h2 class="group_view-title"></h2>
<div class="group_view-form">
</div>
</section>
</template>

View file

@ -13,3 +13,9 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>.
*/
.group_view-title
{
display: none;
}

View file

@ -0,0 +1,269 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"invitation_create",
async (parameters, target_element) => {
target_element.appendChild(_zackeneule.helpers.template_request("invitation_create"));
target_element.querySelector(".invitation_create-title").textContent = lib_plankton.translate.get("page.invitation_create.title");
const indent = str => (/*"... " + */str);
const group_data = await _zackeneule.logic.group_data();
/**
* @todo unify with form of "invitation_view"
*/
const form = new lib_plankton.zoo_form.class_form<
{
data : {
name_changeable : boolean;
name_value : (null | string);
label_changeable : boolean;
label_value : (null | string);
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<int>;
expiry : (null | int);
};
send_immediatly : boolean;
},
{
name : {
changeable : boolean;
value : string;
};
label : {
changeable : boolean;
value : string;
};
email_address : {
changeable : boolean;
value : string;
};
groups : {
changeable : boolean;
value : Array<int>;
};
expiry : (null | lib_plankton.pit.type_datetime);
send_immediatly : boolean;
}
>(
value => ({
"name": {
"changeable": value.data.name_changeable,
"value": value.data.name_value,
},
"label": {
"changeable": value.data.label_changeable,
"value": value.data.label_value,
},
"email_address": {
"changeable": value.data.email_address_changeable,
"value": (value.data.email_address_value ?? ""),
},
"groups": {
"changeable": value.data.groups_changeable,
"value": value.data.groups_value,
},
"expiry": (
(value.data.expiry === null)
?
null
:
lib_plankton.pit.to_datetime(lib_plankton.pit.from_unix_timestamp(value.data.expiry))
),
"send_immediatly": value.send_immediatly,
}),
representation => ({
"data": {
"name_changeable": representation.name.changeable,
"name_value": _zackeneule.helpers.null_when_empty(representation.name.value),
"label_changeable": representation.label.changeable,
"label_value": _zackeneule.helpers.null_when_empty(representation.label.value),
"email_address_changeable": representation.email_address.changeable,
"email_address_value": _zackeneule.helpers.null_when_empty(representation.email_address.value),
"groups_changeable": representation.groups.changeable,
"groups_value": representation.groups.value,
"expiry": (
(representation.expiry === null)
?
null
:
lib_plankton.pit.to_unix_timestamp(lib_plankton.pit.from_datetime(representation.expiry))
),
},
"send_immediatly": representation.send_immediatly,
}),
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.name.label"),
},
{
"name": "label",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.label.label"),
},
{
"name": "email_address",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.email_address.label"),
},
{
"name": "groups",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_wrapped<Set<string>, Array<int>>(
new lib_plankton.zoo_input.class_input_set(
group_data.order.map(
group_id => group_id.toFixed(0)
),
group_id_encoded => group_data.pool.get(parseInt(group_id_encoded)).label
),
(value_inner) => {
const array : Array<int> = [];
for (const group_id_encoded of value_inner)
{
array.push(parseInt(group_id_encoded));
}
return array;
},
(value_outer) => new Set<string>(value_outer.map(group_id => group_id.toFixed(0)))
),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.groups.label"),
},
{
"name": "expiry",
"input": new lib_plankton.zoo_input.class_input_soft(
new lib_plankton.zoo_input.class_input_datetime_central_europe(
{
// "label_timezone_shift": indent(lib_plankton.translate.get("common.timezone_shift")),
"label_date": indent(lib_plankton.translate.get("common.date")),
"label_time": indent(lib_plankton.translate.get("common.time")),
}
)
),
"label": lib_plankton.translate.get("domain.invitation.expiry.label"),
},
{
"name": "send_immediatly",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": indent(lib_plankton.translate.get("page.invitation_create.form.field.send_immediatly.label")),
},
]
),
[
{
"label": lib_plankton.translate.get("page.invitation_create.form.action.submit"),
"procedure": async (get_value, get_representation) => {
const value = await get_value();
const result : {id : int; key : string;} = await _zackeneule.backend.invitation_create(
value.data,
value.send_immediatly
);
lib_plankton.zoo_page.set(
false
?
{
"name": "invitation_view",
"parameters": {"id": result.id.toFixed(0)}
}
:
{
"name": "invitation_list",
"parameters": {}
}
);
},
}
]
);
await form.setup(target_element.querySelector(".invitation_create-form") as HTMLElement);
form.input_write(
{
"data": {
"name_changeable": false,
"name_value": "",
"label_changeable": true,
"label_value": "",
"email_address_changeable": true,
"email_address_value": null,
"groups_changeable": false,
"groups_value": [],
/**
* @todo conf
*/
"expiry": lib_plankton.pit.shift_week(lib_plankton.pit.now(), 2),
},
"send_immediatly": true,
}
);
}
);

View file

@ -0,0 +1,19 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="invitation_create">
<h2 class="invitation_create-title"></h2>
<div class="invitation_create-form"></div>
</template>

View file

@ -0,0 +1,31 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
.invitation_create-title
{
text-transform: capitalize;
}
.invitation_create-form .plankton_input_group_field .plankton_input_group
{
padding-left: 32px;
}
.invitation_create-form .plankton_input_group_field .plankton_input_group > *
{
display: inline-block;
margin-right: 16px;
vertical-align: top;
}

View file

@ -0,0 +1,395 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"invitation_handle",
async (parameters, target_element) => {
async function set_state(
state : ("load" | "fill" | "wait" | "error" | "success"),
messages : Array<string> = []
) : Promise<void>
{
target_element.querySelector(".invitation_handle").setAttribute("rel", state);
target_element.querySelector(".invitation_handle-message").textContent = "";
const class_ = (() => {
switch (state)
{
case "load": return lib_plankton.zoo_widget.class_panel_info;
case "wait": return lib_plankton.zoo_widget.class_panel_info;
case "error": return lib_plankton.zoo_widget.class_panel_error;
case "success": return lib_plankton.zoo_widget.class_panel_success;
default: return lib_plankton.zoo_widget.class_panel;
}
}) ();
const widget : lib_plankton.zoo_widget.interface_widget = (
(messages.length <= 0)
?
new lib_plankton.zoo_widget.class_empty()
:
new class_(
(
(messages.length === 1)
?
new lib_plankton.zoo_widget.class_string(messages[0])
:
new lib_plankton.zoo_widget.class_list(
messages
.map(x => new lib_plankton.zoo_widget.class_string(x))
)
),
{
"collapseable": false,
"collapsed_initial_value": false,
}
)
);
await widget.load(target_element.querySelector(".invitation_handle-message"));
}
// parameters
const key : string = parameters["key"];
update_nav({"mode": null});
target_element.appendChild(_zackeneule.helpers.template_request("invitation_handle"));
await set_state(
"load",
[
lib_plankton.translate.get("common.loading"),
]
);
target_element.querySelector(".invitation_handle-title").textContent = lib_plankton.translate.get("page.invitation_handle.title");
let data : (
null
|
{
name_changeable : boolean;
name_value : (null | string);
label_changeable : boolean;
label_value : (null | string);
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<int>;
}
);
try
{
data = await _zackeneule.backend.invitation_examine(key);
}
catch (error)
{
data = null;
}
if (data === null)
{
await set_state(
"error",
[
lib_plankton.translate.get("page.invitation_handle.message.invalid"),
]
);
}
else
{
const password_policy : lib_plankton.password.type_policy = await _zackeneule.backend.password_policy();
const group_data = await _zackeneule.logic.group_data();
const form = new lib_plankton.zoo_form.class_form<
{
name : string;
label : string;
email_address : (null | string);
groups : Array<int>;
password_value : string;
password_confirmation : string;
},
{
name : string;
label : string;
email_address : string;
groups : Array<int>;
password_value : string;
password_confirmation : string;
}
>(
value => ({
"name": value.name,
"label": value.label,
"email_address": (value.email_address ?? ""),
"groups": value.groups,
"password_value": value.password_value,
"password_confirmation": value.password_confirmation,
}),
representation => ({
"name": representation.name,
"label": representation.label,
"email_address": representation.email_address,
"groups": representation.groups,
"password_value": representation.password_value,
"password_confirmation": representation.password_confirmation,
}),
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text(
{
"read_only": (! data.name_changeable),
"pattern": "^[a-z0-9_\.]+$",
}
),
"label": lib_plankton.translate.get("domain.member.name.label"),
"help": new lib_plankton.zoo_widget.class_panel_info(
new lib_plankton.zoo_widget.class_list(
lib_plankton.translate.get("domain.member.name.help")
.split("\n\n")
.map(x => new lib_plankton.zoo_widget.class_string(x))
),
{
"collapseable": true,
"collapsed_initial_value": true,
}
),
},
{
"name": "label",
"input": new lib_plankton.zoo_input.class_input_text(
{
"read_only": (! data.label_changeable),
}
),
"label": lib_plankton.translate.get("domain.member.label.label"),
"help": new lib_plankton.zoo_widget.class_panel_info(
new lib_plankton.zoo_widget.class_list(
lib_plankton.translate.get("domain.member.label.help")
.split("\n\n")
.map(x => new lib_plankton.zoo_widget.class_string(x))
),
{
"collapseable": true,
"collapsed_initial_value": true,
}
),
},
{
"name": "email_address",
"input": new lib_plankton.zoo_input.class_input_text(
{
"read_only": (! data.email_address_changeable),
}
),
"label": lib_plankton.translate.get("domain.member.email_address.label"),
"help": new lib_plankton.zoo_widget.class_panel_info(
new lib_plankton.zoo_widget.class_list(
lib_plankton.translate.get("domain.member.email_address.help")
.split("\n\n")
.map(x => new lib_plankton.zoo_widget.class_string(x))
),
{
"collapseable": true,
"collapsed_initial_value": true,
}
),
},
{
"name": "groups",
"input": new lib_plankton.zoo_input.class_input_wrapped<Set<string>, Array<int>>(
new lib_plankton.zoo_input.class_input_set(
group_data.order.map(
group_id => group_id.toFixed(0)
),
group_id_encoded => group_data.pool.get(parseInt(group_id_encoded)).label,
{
"read_only": (! data.groups_changeable),
}
),
(value_inner) => {
const array : Array<int> = [];
for (const group_id_encoded of value_inner)
{
array.push(parseInt(group_id_encoded));
}
return array;
},
(value_outer) => new Set<string>(value_outer.map(group_id => group_id.toFixed(0)))
),
"label": lib_plankton.translate.get("domain.member.groups.label"),
"help": new lib_plankton.zoo_widget.class_panel_info(
new lib_plankton.zoo_widget.class_list(
(
lib_plankton.translate.get("domain.member.groups.help")
.split("\n\n")
.map(x => new lib_plankton.zoo_widget.class_string(x))
)
),
{
"collapseable": true,
"collapsed_initial_value": true,
}
),
},
{
"name": "password_value",
"input": new lib_plankton.zoo_input.class_input_password(
{
"exhibit_initial_value": true,
}
),
"label": lib_plankton.translate.get("page.invitation_handle.form.field.password_value.label"),
"help": new lib_plankton.zoo_widget.class_panel_info(
new lib_plankton.zoo_widget.class_list(
(
lib_plankton.translate.get("page.invitation_handle.form.field.password_value.help")
.split("\n\n")
.map<lib_plankton.zoo_widget.interface_widget>(x => new lib_plankton.zoo_widget.class_string(x))
)
.concat(
[
new _zackeneule.widget.password_policy.class_password_policy(
password_policy,
{
"title": "Richtlinien",
}
),
]
)
),
{
"collapseable": true,
"collapsed_initial_value": true,
}
),
},
{
"name": "password_confirmation",
"input": new lib_plankton.zoo_input.class_input_password(
),
"label": lib_plankton.translate.get("page.invitation_handle.form.field.password_confirmation.label"),
/*
"help": new lib_plankton.zoo_widget.class_panel_info(
new lib_plankton.zoo_widget.class_string(
lib_plankton.translate.get("page.invitation_handle.form.field.password_confirmation.help")
),
{
"collapseable": true,
"collapsed_initial_value": true,
}
),
*/
},
]
),
[
{
"label": lib_plankton.translate.get("page.invitation_handle.form.action.submit"),
"procedure": async (get_value, get_representation) => {
const value = await get_value();
let flaws : Array<{incident : string; details : Record<string, any>;}>;
await set_state(
"wait",
[
lib_plankton.translate.get("common.checking"),
]
);
if (value.password_value !== value.password_confirmation)
{
flaws = [
{"incident": "password_mismatch", "details": {}},
];
}
else
{
try
{
const flaws_ = await _zackeneule.backend.invitation_accept(
key,
{
"label": value.label,
"name": value.name,
"email_address": value.email_address,
"groups": value.groups,
"password": value.password_value,
}
);
flaws = (
(
flaws_
.filter(
flaw => (! flaw.incident.startsWith("password_"))
)
)
.concat(
(flaws_.find(flaw => flaw.incident.startsWith("password_")) !== undefined)
?
[{"incident": "password_invalid", "details": {}}]
:
[]
)
);
}
catch (error)
{
flaws = [
{"incident": "unhandled_error", "details": {}},
];
}
}
if (flaws.length > 0)
{
await set_state(
"error",
flaws.map(
flaw => lib_plankton.string.coin(
lib_plankton.translate.get("page.invitation_handle.flaw." + flaw.incident),
flaw.details
)
)
);
}
else
{
await set_state(
"success",
[
lib_plankton.translate.get("page.invitation_handle.message.successful"),
]
);
}
},
}
]
);
await form.setup(target_element.querySelector(".invitation_handle-form") as HTMLElement);
form.input_write(
{
"name": data.name_value,
"label": data.label_value,
"email_address": data.email_address_value,
"groups": data.groups_value,
"password_value": lib_plankton.password.generate(password_policy),
"password_confirmation": "",
}
);
set_state(
"fill",
[
]
);
}
}
);

View file

@ -0,0 +1,25 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="invitation_handle">
<section class="invitation_handle">
<h2 class="invitation_handle-title"></h2>
<div class="invitation_handle-info">
</div>
<div class="invitation_handle-message">
</div>
<div class="invitation_handle-form"></div>
</section>
</template>

View file

@ -0,0 +1,38 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
.invitation_handle-message
{
margin-bottom: 16px;
}
.invitation_handle:not([rel]) .invitation_handle-message {display: none;}
.invitation_handle:not([rel]) .invitation_handle-form {display: none;}
.invitation_handle[rel="load"] .invitation_handle-message {}
.invitation_handle[rel="load"] .invitation_handle-form {display: none;}
.invitation_handle[rel="fill"] .invitation_handle-message {}
.invitation_handle[rel="fill"] .invitation_handle-form {}
.invitation_handle[rel="wait"] .invitation_handle-message {}
.invitation_handle[rel="wait"] .invitation_handle-form {display: none;}
.invitation_handle[rel="error"] .invitation_handle-message {}
.invitation_handle[rel="error"] .invitation_handle-form {}
.invitation_handle[rel="success"] .invitation_handle-message {}
.invitation_handle[rel="success"] .invitation_handle-form {display: none;}

View file

@ -0,0 +1,109 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"invitation_list",
async (parameters, target_element) => {
// types
type type_item = {
id : int;
preview : {
key : string;
expiry : (null | int);
name_value : string;
label_value : string;
};
};
// parameters
const term : (null | string) = (parameters["term"] ?? "");
// exec
target_element.appendChild(_zackeneule.helpers.template_request("invitation_list"));
target_element.querySelector(".invitation_list-title").textContent = lib_plankton.translate.get("page.invitation_list.title");
// exec : create link
{
const element : HTMLElement = target_element.querySelector(".invitation_list-create");
element.setAttribute(
"href",
lib_plankton.zoo_page.encode(
{
"name": "invitation_create",
"parameters": {
}
}
)
);
element.textContent = lib_plankton.translate.get("page.invitation_create.title");
}
const search : lib_plankton.zoo_search.type_search<type_item> = lib_plankton.zoo_search.make<type_item>(
(term) => _zackeneule.backend.invitation_list(),
{
"encode_item": (item) => (
item.preview.label_value
??
item.preview.name_value
??
lib_plankton.string.coin("[{{id}}]", {"id": item.id.toFixed(0)})
),
"hooks_begin": [
(term) => {
lib_plankton.zoo_page.set(
{
"name": "invitation_list",
"parameters": {
"term": term,
}
}
);
}
],
"hooks_select": [
(item) => {
lib_plankton.zoo_page.set(
{
"name": "invitation_view",
"parameters": {
"id": item.id.toFixed(0),
}
}
);
}
]
}
);
lib_plankton.zoo_search.render(
search,
target_element.querySelector(".invitation_list-search"),
{
"state": {
"term": "",
}
}
);
/*
const data = await _zackeneule.backend.invitation_list();
(target_element.querySelector(".invitation_list-data") as HTMLElement).textContent = JSON.stringify(
data,
undefined,
" "
);
*/
}
);

View file

@ -0,0 +1,22 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="invitation_list">
<h2 class="invitation_list-title"></h2>
<div class="invitation_list-links">
<a class="invitation_list-create"></a>
</div>
<div class="invitation_list-search"></div>
</template>

View file

@ -0,0 +1,19 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
.invitation_list-links
{
margin-bottom: 16px;
}

View file

@ -0,0 +1,310 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"invitation_view",
async (parameters, target_element) => {
// functions
const get_url = (item) => {
const url : URL = new URL(window.location.toString());
url.hash = lib_plankton.string.coin(
"#invitation_handle,key={{key}}",
{
"key": item.key,
}
);
return url.toString();
};
// parameters
const id : int = parseInt(parameters["id"]);
target_element.appendChild(_zackeneule.helpers.template_request("invitation_view"));
target_element.querySelector(".invitation_view-title").textContent = lib_plankton.translate.get("page.invitation_view.title");
const indent = str => (/*"... " + */str);
/**
* @todo cache
*/
const groups_as_array : Array<
{
id : int;
preview : {
name : string;
label : string;
};
}
> = await _zackeneule.backend.group_list();
const groups_as_map : Map<
int,
{
name : string;
label : string;
}
> = new Map<
int,
{
name : string;
label : string;
}
>();
for (const group_thingy of groups_as_array)
{
groups_as_map.set(
group_thingy.id,
{
"name": group_thingy.preview.name,
"label": group_thingy.preview.label,
}
);
}
/**
* @todo unify with form of "invitation_create"
*/
const form = new lib_plankton.zoo_form.class_form<
{
key : string;
expiry : (null | int);
name_changeable : boolean;
name_value : string;
label_changeable : boolean;
label_value : string;
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<int>;
},
{
key : string;
expiry : (null | lib_plankton.pit.type_datetime);
name : {
changeable : boolean;
value : string;
};
label : {
changeable : boolean;
value : string;
};
email_address : {
changeable : boolean;
value : string;
};
groups : {
changeable : boolean;
value : Array<int>;
};
url : string;
}
>(
value => ({
"expiry": (
(value.expiry === null)
?
null
:
lib_plankton.pit.to_datetime(lib_plankton.pit.from_unix_timestamp(value.expiry))
),
"key": value.key,
"name": {
"changeable": value.name_changeable,
"value": value.name_value,
},
"label": {
"changeable": value.label_changeable,
"value": value.label_value,
},
"email_address": {
"changeable": value.email_address_changeable,
"value": (value.email_address_value ?? ""),
},
"groups": {
"changeable": value.groups_changeable,
"value": value.groups_value,
},
"url": get_url(value),
}),
representation => ({
"expiry": (
(representation.expiry === null)
?
null
:
lib_plankton.pit.to_unix_timestamp(lib_plankton.pit.from_datetime(representation.expiry))
),
"key": representation.key,
"name_changeable": representation.name.changeable,
"name_value": representation.name.value,
"label_changeable": representation.label.changeable,
"label_value": representation.label.value,
"email_address_changeable": representation.email_address.changeable,
"email_address_value": _zackeneule.helpers.null_when_empty(representation.email_address.value),
"groups_changeable": representation.groups.changeable,
"groups_value": representation.groups.value,
"url": "",
}),
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "url",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": false}),
"label": lib_plankton.translate.get("domain.invitation.url.label"),
},
{
"name": "key",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("domain.invitation.key.label"),
},
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.name.label"),
},
{
"name": "label",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.label.label"),
},
{
"name": "email_address",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.email_address.label"),
},
{
"name": "groups",
"input": new lib_plankton.zoo_input.class_input_group(
[
{
"name": "value",
"input": new lib_plankton.zoo_input.class_input_wrapped<Set<string>, Array<int>>(
new lib_plankton.zoo_input.class_input_set(
groups_as_array.map(
group_thingy => group_thingy.id.toFixed(0)
),
group_id_encoded => groups_as_map.get(parseInt(group_id_encoded)).label,
{"read_only": true}
),
(value_inner) => {
const array : Array<int> = [];
for (const group_id_encoded of value_inner)
{
array.push(parseInt(group_id_encoded));
}
return array;
},
(value_outer) => new Set<string>(value_outer.map(group_id => group_id.toFixed(0)))
),
"label": indent(lib_plankton.translate.get("common.initial_value")),
},
{
"name": "changeable",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": indent(lib_plankton.translate.get("common.changeable")),
},
]
),
"label": lib_plankton.translate.get("domain.member.groups.label"),
},
{
"name": "expiry",
"input": new lib_plankton.zoo_input.class_input_soft(
new lib_plankton.zoo_input.class_input_datetime_central_europe(
{
// "read_only": true,
// "label_timezone_shift": indent(lib_plankton.translate.get("common.timezone_shift")),
"label_date": indent(lib_plankton.translate.get("common.date")),
"label_time": indent(lib_plankton.translate.get("common.time")),
}
)
),
"label": lib_plankton.translate.get("domain.invitation.expiry.label"),
},
]
),
[
{
"label": lib_plankton.translate.get("page.invitation_view.form.action.delete"),
"procedure": async () => {
await _zackeneule.backend.invitation_delete(id);
lib_plankton.zoo_page.set(
{
"name": "invitation_list",
"parameters": {
}
}
);
},
},
]
);
await form.setup(target_element.querySelector(".invitation_view-form") as HTMLElement);
const data : {
key: string;
expiry : (null | int);
name_changeable : boolean;
name_value : string;
label_changeable : boolean;
label_value : string;
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<int>;
} = await _zackeneule.backend.invitation_read(id);
form.input_write(data);
}
);

View file

@ -0,0 +1,19 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="invitation_view">
<h2 class="invitation_view-title"></h2>
<div class="invitation_view-form"></div>
</template>

View file

@ -0,0 +1,39 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
.invitation_view-form .plankton_input_group_field .plankton_input_group
{
padding-left: 32px;
}
.invitation_view-form .plankton_input_group_field .plankton_input_group > *
{
display: inline-block;
margin-right: 16px;
vertical-align: top;
}
.invitation_view:not([rel]) .invitation_view-message {display: none;}
.invitation_view:not([rel]) .invitation_view-form {display: none;}
.invitation_view[rel="fill"] .invitation_view-message {}
.invitation_view[rel="fill"] .invitation_view-form {}
.invitation_view[rel="wait"] .invitation_view-message {}
.invitation_view[rel="wait"] .invitation_view-form {display: none;}
.invitation_view[rel="done"] .invitation_view-message {}
.invitation_view[rel="done"] .invitation_view-form {display: none;}

View file

@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with thi
lib_plankton.zoo_page.register(
"login",
(parameters, target_element) => {
target_element.appendChild(template_request("login"));
target_element.appendChild(_zackeneule.helpers.template_request("login"));
target_element.querySelector(".login-title").textContent = lib_plankton.translate.get("page.login.title");
@ -52,10 +52,10 @@ lib_plankton.zoo_page.register(
"procedure": async (get_value, get_representation) => {
const value = await get_value();
(
_espe.backend.login(value.name, value.password)
_zackeneule.backend.login(value.name, value.password)
.then(
() => {
lib_plankton.zoo_page.set({"name": "list", "parameters": {}});
lib_plankton.zoo_page.set({"name": "member_list", "parameters": {}});
update_nav({"mode": "logged_in"});
}
)

View file

@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with thi
lib_plankton.zoo_page.register(
"logout",
async (parameters, target_element) => {
await _espe.backend.logout();
await _zackeneule.backend.logout();
lib_plankton.zoo_page.set({"name": "index", "parameters": {}});
update_nav({"mode": "logged_out"});
}

View file

@ -0,0 +1,95 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"member_list",
(parameters, target_element) => {
type type_item = {
id : int;
preview : {
label : string;
name : string;
};
};
const term : (null | string) = (parameters["term"] ?? "");
target_element.appendChild(_zackeneule.helpers.template_request("member_list"));
target_element.querySelector(".member_list-title").textContent = lib_plankton.translate.get("page.member_list.title");
// create link
/*
{
const element : HTMLElement = target_element.querySelector(".member_list-create");
element.setAttribute(
"href",
lib_plankton.zoo_page.encode(
{
"name": "member_create",
"parameters": {
}
}
)
);
element.textContent = lib_plankton.translate.get("page.member_create.title");
}
*/
const search : lib_plankton.zoo_search.type_search<type_item> = lib_plankton.zoo_search.make<type_item>(
(term) => _zackeneule.backend.member_list(term),
{
"encode_item": (item) => lib_plankton.string.coin(
"{{label}}",
{
"label": item.preview.label,
}
),
"hooks_begin": [
(term) => {
lib_plankton.zoo_page.set(
{
"name": "member_list",
"parameters": {
"term": term,
}
}
);
}
],
"hooks_select": [
(item) => {
lib_plankton.zoo_page.set(
{
"name": "member_view",
"parameters": {
"id": item.id.toFixed(0),
}
}
);
}
]
}
);
lib_plankton.zoo_search.render(
search,
target_element.querySelector(".member_list-search"),
{
"state": {
"term": term,
}
}
);
}
);

View file

@ -0,0 +1,22 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="member_list">
<h2 class="member_list-title"></h2>
<div class="member_list-links">
<a class="member_list-create"></a>
</div>
<div class="member_list-search"></div>
</template>

View file

@ -0,0 +1,19 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
.member_list-links
{
margin-bottom: 16px;
}

View file

@ -0,0 +1,147 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"member_view",
async (parameters, target_element) => {
const id : int = parseInt(parameters["id"]);
let dom_fragment : DocumentFragment = _zackeneule.helpers.template_request("member_view");
dom_fragment.querySelector(".member_view-title").textContent = lib_plankton.translate.get("page.member_view.title");
const group_data = await _zackeneule.logic.group_data();
const member_data = await _zackeneule.backend.member_get(id);
const form = new lib_plankton.zoo_form.class_form<
{
name : string;
label : string;
groups : Array<int>;
enabled : boolean;
email_address : (null | string);
password_set : boolean;
},
{
name : string;
label : string;
groups : Array<int>;
enabled : boolean;
email_address : (null | string);
password_set : boolean;
}
>(
value => ({
"name": value.name,
"label": value.label,
"groups": value.groups,
"enabled": value.enabled,
"email_address": value.email_address,
"password_set": value.password_set,
}),
representation => ({
"name": representation.name,
"label": representation.label,
"groups": representation.groups,
"enabled": representation.enabled,
"email_address": representation.email_address,
"password_set": representation.password_set,
}),
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("domain.member.name.label"),
},
{
"name": "label",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("domain.member.label.label"),
},
{
"name": "enabled",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": lib_plankton.translate.get("domain.member.enabled.label"),
},
{
"name": "password_set",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": lib_plankton.translate.get("domain.member.password_set.label"),
},
{
"name": "email_address",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("domain.member.email_address.label"),
},
{
"name": "groups",
"input": new lib_plankton.zoo_input.class_input_wrapped<Set<string>, Array<int>>(
new lib_plankton.zoo_input.class_input_set(
group_data.order.map(
group_id => group_id.toFixed(0)
),
group_id_encoded => group_data.pool.get(parseInt(group_id_encoded)).label
),
(value_inner) => {
const array : Array<int> = [];
for (const group_id_encoded of value_inner)
{
array.push(parseInt(group_id_encoded));
}
return array;
},
(value_outer) => new Set<string>(value_outer.map(group_id => group_id.toFixed(0)))
),
"label": lib_plankton.translate.get("domain.member.groups.label"),
},
]
),
(
[
{
"label": lib_plankton.translate.get("page.member_view.form.action.save"),
"procedure": async (get_value, get_representation) => {
const value = await get_value();
await _zackeneule.backend.member_modify(
id,
{
"label": value.label,
"email_address": value.email_address,
"groups": value.groups,
"enabled": value.enabled,
}
);
},
},
]
)
);
await form.setup(dom_fragment.querySelector(".member_view-form") as HTMLElement);
await form.input_write(
{
"name": member_data.name,
"label": member_data.label,
"groups": member_data.groups,
"enabled": member_data.enabled,
"email_address": member_data.email_address,
"password_set": member_data.password_set,
}
);
target_element.appendChild(dom_fragment);
},
);

View file

@ -0,0 +1,22 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="member_view">
<section class="member_view">
<h2 class="member_view-title"></h2>
<div class="member_view-form">
</div>
</section>
</template>

View file

@ -14,14 +14,28 @@ You should have received a copy of the GNU General Public License along with thi
*/
/**
* @todo outsource
*/
function template_request(
id : string
) : DocumentFragment
.member_view-title
{
let dom_template = document.querySelector("template#" + id);
// return template["content"].cloneNode(true);
return (document.importNode(dom_template["content"], true) as DocumentFragment)
display: none;
}
/*
.member_view-form > .plankton_form
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.member_view-form > .plankton_form > .plankton_form_input
{
flex: 1;
}
.member_view-form > .plankton_form > .plankton_form_actions
{
flex: 1;
}
*/

View file

@ -38,7 +38,7 @@ lib_plankton.zoo_page.register(
const token : string = parameters.token;
update_nav({"mode": null});
target_element.appendChild(template_request("password_change_exec"));
target_element.appendChild(_zackeneule.helpers.template_request("password_change_exec"));
target_element.querySelector(".password_change_exec-title").textContent = lib_plankton.translate.get("page.password_change_exec.title");
set_state(
"load",
@ -90,7 +90,7 @@ lib_plankton.zoo_page.register(
}
else {
try {
flaws = await _espe.backend.member_password_change_execute(
flaws = await _zackeneule.backend.member_password_change_execute(
id,
token,
value.password_value

View file

@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with thi
lib_plankton.zoo_page.register(
"password_change_init",
(parameters, target_element) => {
target_element.appendChild(template_request("password_change_init"));
target_element.appendChild(_zackeneule.helpers.template_request("password_change_init"));
target_element.querySelector(".password_change_init-title").textContent = lib_plankton.translate.get("page.password_change_init.title");
target_element.querySelector(".password_change_init-info").textContent = lib_plankton.translate.get("page.password_change_init.info");
@ -49,17 +49,8 @@ lib_plankton.zoo_page.register(
target_element.querySelector(".password_change_init-stat").textContent = lib_plankton.translate.get("page.password_change_init.status.wait");
const value = await get_value();
(
_espe.backend.member_password_change_initialize(
value.identifier,
lib_plankton.zoo_page.encode(
{
"name": "password_change_exec",
"parameters": {
"id": "{{id}}",
"token": "{{token}}",
}
}
)
_zackeneule.backend.member_password_change_initialize(
value.identifier
)
.then(
() => {

View file

@ -1,231 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"register",
async (parameters, target_element) => {
function set_state(
state : ("load" | "fill" | "wait" | "done"),
messages : Array<string> = []
) : void
{
target_element.querySelector(".register").setAttribute("rel", state);
target_element.querySelector(".register-message").textContent = "";
let dom_list = document.createElement("ul");
messages.forEach(
message => {
let dom_message = document.createElement("li");
dom_message.textContent = message;
dom_list.appendChild(dom_message);
}
);
target_element.querySelector(".register-message").appendChild(dom_list);
}
const id : int = parseInt(parameters["id"]);
const verification : string = parameters["verification"];
// const verification : string = (new URLSearchParams(location.search)).get("verification");
update_nav({"mode": null});
target_element.appendChild(template_request("register"));
set_state(
"load",
[
]
);
let member_data : (
null
|
{
name_real_value : string;
name_real_index : int;
name_login : string;
email_address_veiled : string;
email_address_nominal : string;
}
);
try {
member_data = await _espe.backend.member_info(id, verification);
}
catch (error) {
member_data = null;
}
if (member_data === null) {
set_state(
"fill",
[
lib_plankton.translate.get("page.register.flaw.already_registered"),
]
);
}
else {
// title
{
target_element.querySelector(".register-title").textContent = lib_plankton.translate.get("page.register.title");
}
// info
{
}
// form
{
let input : lib_plankton.zoo_input.interface_input<
{
email_address : ("none" | "only_veiled" | "both");
email_redirect : boolean;
password_value : string;
password_confirmation : string;
}
>;
// input
{
input = new lib_plankton.zoo_input.class_input_group(
[
{
"name": "email_address",
"input": new lib_plankton.zoo_input.class_input_enumeration(
[
{
"value": "none",
"label": lib_plankton.translate.get("page.register.form.field.email_address.option.none"),
},
{
"value": "only_veiled",
"label": lib_plankton.translate.get("page.register.form.field.email_address.option.only_veiled"),
},
{
"value": "both",
"label": lib_plankton.translate.get("page.register.form.field.email_address.option.both"),
},
]
),
"label": lib_plankton.translate.get("page.register.form.field.email_address.label"),
"help": lib_plankton.translate.get(
"page.register.form.field.email_address.help",
{
"email_address_veiled": member_data.email_address_veiled,
"email_address_nominal": member_data.email_address_nominal,
}
),
},
{
"name": "email_redirect",
"input": new lib_plankton.zoo_input.class_input_checkbox(
),
"label": lib_plankton.translate.get("page.register.form.field.email_redirect.label"),
},
{
"name": "password_value",
"input": new lib_plankton.zoo_input.class_input_password(
),
"label": lib_plankton.translate.get("page.register.form.field.password_value.label"),
"help": lib_plankton.translate.get("page.register.form.field.password_value.help"),
},
{
"name": "password_confirmation",
"input": new lib_plankton.zoo_input.class_input_password(
),
"label": lib_plankton.translate.get("page.register.form.field.password_confirmation.label"),
},
]
);
await input.setup(target_element.querySelector(".register-form-input") as HTMLElement);
await input.write(
{
"email_address": _espe.conf.get().settings.registration_defaults.email_address,
"email_redirect": _espe.conf.get().settings.registration_defaults.email_redirect,
"password_value": "",
"password_confirmation": "",
}
);
}
// actions
{
target_element.querySelector(".register-form-action-send").textContent = lib_plankton.translate.get("page.register.form.submit");
target_element.querySelector(".register-form-action-send").addEventListener(
"click",
async (event) => {
const value : {
email_address : ("none" | "only_veiled" | "both");
email_redirect : boolean;
password_value : string;
password_confirmation : string;
} = await input.read();
let flaws : Array<{incident : string; details : Record<string, any>;}>;
set_state(
"wait",
[
]
);
if (value.password_value !== value.password_confirmation) {
flaws = [
{"incident": "password_mismatch", "details": {}},
];
}
else {
try {
flaws = await _espe.backend.member_register(
id,
verification,
{
"email_use_veiled_address": (value.email_address !== "none"),
"email_use_nominal_address": (value.email_address === "both"),
"email_redirect_to_private_address": value.email_redirect,
"password": value.password_value,
},
lib_plankton.zoo_page.encode(
{
"name": "view",
"parameters": {
"id": "{{id}}",
}
}
)
);
}
catch (error) {
flaws = [
{"incident": "unhandled_error", "details": {}},
];
}
}
if (flaws.length > 0) {
set_state(
"fill",
flaws.map(
flaw => lib_plankton.string.coin(
lib_plankton.translate.get("page.register.flaw." + flaw.incident),
flaw.details
)
)
);
}
else {
set_state(
"done",
[
lib_plankton.translate.get("page.register.success")
]
);
}
}
);
}
}
}
},
);

View file

@ -1,31 +0,0 @@
<!--
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
-->
<template id="register">
<section class="register">
<h2 class="register-title"></h2>
<div class="register-info">
</div>
<div class="register-message">
</div>
<div class="register-form">
<div class="register-form-input">
</div>
<div class="register-form-actions">
<button class="register-form-action-send"></button>
</div>
</div>
</section>
</template>

View file

@ -1,27 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
.register:not([rel]) .register-message {display: none;}
.register:not([rel]) .register-form {display: none;}
.register[rel="fill"] .register-message {}
.register[rel="fill"] .register-form {}
.register[rel="wait"] .register-message {}
.register[rel="wait"] .register-form {display: none;}
.register[rel="done"] .register-message {}
.register[rel="done"] .register-form {display: none;}

View file

@ -1,210 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
lib_plankton.zoo_page.register(
"view",
async (parameters, target_element) => {
const id : int = parseInt(parameters["id"]);
let dom_fragment : DocumentFragment = template_request("view");
dom_fragment.querySelector(".view-title").textContent = lib_plankton.translate.get("page.view.title");
const member_data = await _espe.backend.member_get(id);
const form = new lib_plankton.zoo_form.class_form<
{
membership_number : string;
name_real_value : string;
name_real_index : int;
groups : Array<string>;
registered : boolean;
enabled : boolean;
email_address_private : (null | string);
email_address_veiled : (null | string);
email_address_nominal : (null | string);
email_redirect_to_private_address : boolean;
email_allow_sending : boolean;
name_login : string;
password_set : boolean;
},
{
membership_number : string;
name_real_value : string;
name_real_index : int;
groups : Array<string>;
registered : boolean;
enabled : boolean;
email_address_private : (null | string);
email_address_veiled : (null | string);
email_address_nominal : (null | string);
email_redirect_to_private_address : boolean;
email_allow_sending : boolean;
name_login : string;
password_set : boolean;
}
>(
value => value,
representation => representation,
new lib_plankton.zoo_input.class_input_group(
[
{
"name": "name_real_value",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.name_real_value.label"),
},
{
"name": "name_real_index",
"input": new lib_plankton.zoo_input.class_input_number({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.name_real_index.label"),
},
{
"name": "membership_number",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.membership_number.label"),
},
{
"name": "groups",
"input": new lib_plankton.zoo_input.class_input_list(
() => new lib_plankton.zoo_input.class_input_text()
),
"label": lib_plankton.translate.get("page.view.form.field.groups.label"),
},
{
"name": "enabled",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": lib_plankton.translate.get("page.view.form.field.enabled.label"),
},
{
"name": "registered",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.registered.label"),
},
{
"name": "name_login",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.name_login.label"),
},
{
"name": "password_set",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.password_set.label"),
},
{
"name": "email_address_private",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("page.view.form.field.email_address_private.label"),
},
{
"name": "email_address_veiled",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.email_address_veiled.label"),
},
{
"name": "email_address_nominal",
"input": new lib_plankton.zoo_input.class_input_text({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.email_address_nominal.label"),
},
{
"name": "email_redirect_to_private_address",
"input": new lib_plankton.zoo_input.class_input_checkbox({"read_only": true}),
"label": lib_plankton.translate.get("page.view.form.field.email_redirect_to_private_address.label"),
},
{
"name": "email_allow_sending",
"input": new lib_plankton.zoo_input.class_input_checkbox(),
"label": lib_plankton.translate.get("page.view.form.field.email_allow_sending.label"),
},
]
),
(
[
{
"label": lib_plankton.translate.get("page.view.form.action.save"),
"procedure": async (get_value, get_representation) => {
const value = await get_value();
await _espe.backend.member_modify(
id,
{
"email_address_private": value.email_address_private,
"groups": value.groups,
"enabled": value.enabled,
"registered": value.registered,
}
);
},
},
]
.concat(
member_data.registered
? []
: [
{
"label": lib_plankton.translate.get("page.view.form.action.summon"),
"procedure": async (get_value, get_representation) => {
const url : string = await _espe.backend.member_summon(
id,
lib_plankton.zoo_page.encode(
{
"name": "register",
"parameters": {
"id": id,
"verification": "{{verification}}",
}
}
)
);
if (_espe.conf.get().settings.test_mode) {
alert(lib_plankton.translate.get("page.view.misc.test_info", {"url": url}));
}
else {
alert(lib_plankton.translate.get("page.view.misc.summoned"));
}
},
},
]
)
)
);
await form.setup(dom_fragment.querySelector(".view-form") as HTMLElement);
await form.input_write(
{
"membership_number": member_data.membership_number,
"name_real_value": member_data.name_real_value,
"name_real_index": member_data.name_real_index,
"groups": member_data.groups,
"registered": member_data.registered,
"enabled": member_data.enabled,
"email_address_private": member_data.email_address_private,
"email_address_veiled": (
member_data.email_use_veiled_address
? member_data.email_address_veiled
: ("(" + lib_plankton.translate.get("common.not_used") + ")")
),
"email_address_nominal": (
member_data.email_use_nominal_address
? member_data.email_address_nominal
: ("(" + lib_plankton.translate.get("common.not_used") + ")")
),
"email_redirect_to_private_address": member_data.email_redirect_to_private_address,
"email_allow_sending": member_data.email_allow_sending,
"name_login": member_data.name_login,
"password_set": member_data.password_set,
}
);
target_element.appendChild(dom_fragment);
},
);

View file

@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>.
*/
namespace _espe.backend
namespace _zackeneule.backend
{
/**
@ -85,10 +85,10 @@ namespace _espe.backend
lib_plankton.string.coin(
("{{scheme}}://{{host}}:{{port}}{{path_base}}{{path_action}}"),
{
"scheme": _espe.conf.get().backend.scheme,
"host": _espe.conf.get().backend.host,
"port": _espe.conf.get().backend.port.toFixed(0),
"path_base": _espe.conf.get().backend.path_base,
"scheme": _zackeneule.conf.get().backend.scheme,
"host": _zackeneule.conf.get().backend.host,
"port": _zackeneule.conf.get().backend.port.toFixed(0),
"path_base": _zackeneule.conf.get().backend.path_base,
"path_action": path,
}
),
@ -166,22 +166,87 @@ namespace _espe.backend
/**
*/
export async function email(
receivers : Array<string>,
subject : string,
content : string
) : Promise<void>
export async function password_policy(
) : Promise<
{
minimum_length : (null | int);
maximum_length : (null | int);
must_contain_letter : boolean;
must_contain_number : boolean;
must_contain_special_character : boolean;
}
>
{
return (
abstract_call(
"GET",
"/password_policy"
)
);
}
/**
*/
export async function group_list(
) : Promise<
Array<
{
id : int;
preview : {
name : string;
label : string;
};
}
>
>
{
return (
abstract_call(
"GET",
"/group/list"
)
);
}
/**
*/
export async function group_get(
group_id : int
) : Promise<
{
name : string;
label : string;
}
>
{
return (
abstract_call(
"GET",
lib_plankton.string.coin("/group/read/{{id}}", {"id": group_id.toFixed(0)})
)
);
}
/**
*/
export async function group_add(
group_object : {
name : string;
label : string;
}
) : Promise<
int
>
{
return (
abstract_call(
"POST",
"/email",
"/group/add",
{
"data": {
"receivers": receivers,
"subject": subject,
"content": content,
},
"data": group_object,
}
)
);
@ -190,40 +255,21 @@ namespace _espe.backend
/**
*/
export async function verification_get(
data : any
) : Promise<string>
export async function group_modify(
group_id : int,
group_object_data : {
label : string;
}
) : Promise<
void
>
{
return (
abstract_call(
"POST",
"/verification/get",
"PATCH",
lib_plankton.string.coin("/group/modify/{{id}}", {"id": group_id.toFixed(0)}),
{
"data": {
"data": data,
},
}
)
);
}
/**
*/
export async function verification_check(
data : any,
verification : string
) : Promise<boolean>
{
return (
abstract_call(
"POST",
"/verification/check",
{
"data": {
"data": data,
"verification": verification,
}
"data": group_object_data
}
)
);
@ -239,9 +285,8 @@ namespace _espe.backend
{
id : int;
preview : {
membership_number : string;
name_real_value : string;
name_real_index : int;
name : string;
label : string;
};
}
>
@ -262,128 +307,18 @@ namespace _espe.backend
id : int
) : Promise<
{
membership_number : string;
name_real_value : string;
name_real_index : int;
email_address_private : (null | string);
groups : Array<string>;
registered : boolean;
name : string;
label : string;
email_address : (null | string);
groups : Array<int>;
enabled : boolean;
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
email_allow_sending : boolean;
password_set : boolean;
email_address_veiled : string;
email_address_nominal : string;
name_login : string;
}
>
{
return abstract_call(
"GET",
("/member/read/" + id.toFixed(0))
);
}
/**
*/
export async function member_project(
data : {
membership_number : string;
name_real_value : string;
email_address_private : (null | string);
groups : Array<string>;
},
notification_target_url_template : string
) : Promise<int>
{
return abstract_call(
"POST",
"/member/project",
{
"data": {
"membership_number": data.membership_number,
"name_real_value": data.name_real_value,
"email_address_private": data.email_address_private,
"groups": data.groups,
"notification_target_url_template": notification_target_url_template,
}
}
);
}
/**
*/
export async function member_info(
id : int,
key : string
) : Promise<
{
name_real_value : string;
name_real_index : int;
name_login : string;
email_address_veiled : string;
email_address_nominal : string;
}
>
{
return abstract_call(
"GET",
lib_plankton.string.coin(
"/member/info/{{id}}?key={{key}}",
{
"id": id.toFixed(0),
"key": key,
}
)
);
}
/**
*/
export async function member_register(
id : int,
verification : string,
data : {
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
password : (null | string);
},
notification_target_url_template : string
) : Promise<
Array<
{
incident : string;
details : Record<string, any>;
}
>
>
{
return (
abstract_call(
"POST",
lib_plankton.string.coin(
"/member/register/{{id}}?key={{key}}",
{
"id": id.toFixed(0),
"key": verification,
}
),
{
"data": Object.assign(
data,
{"notification_target_url_template": notification_target_url_template}
),
"custom_response_handlers": {
409: (output_data_raw) => output_data_raw,
},
}
)
lib_plankton.string.coin("/member/read/{{id}}", {"id": id.toFixed(0)})
);
}
@ -393,16 +328,16 @@ namespace _espe.backend
export async function member_modify(
id : int,
data : {
email_address_private : (null | string);
groups : Array<string>;
registered : boolean;
label : string;
email_address : (null | string);
groups : Array<int>;
enabled : boolean;
}
) : Promise<void>
{
return abstract_call(
"PATCH",
("/member/modify/" + id.toFixed(0)),
lib_plankton.string.coin("/member/modify/{{id}}", {"id": id.toFixed(0)}),
{
"data": data
}
@ -410,32 +345,10 @@ namespace _espe.backend
}
/**
*/
export async function member_summon(
id : int,
url_template : string
) : Promise<string>
{
return (
abstract_call(
"POST",
("/member/summon/" + id.toFixed(0)),
{
"data": {
"url_template": url_template,
},
}
)
);
}
/**
*/
export async function member_password_change_initialize(
identifier : string,
url_template : string
identifier : string
) : Promise<string>
{
return (
@ -444,8 +357,7 @@ namespace _espe.backend
"/member/password_change/initialize",
{
"data": {
"identifier": identifier,
"url_template": url_template,
"identifier": identifier
},
}
)
@ -471,7 +383,7 @@ namespace _espe.backend
return (
abstract_call(
"PATCH",
("/member/password_change/execute/" + id.toFixed(0)),
lib_plankton.string.coin("/member/password_change/execute/{{id}}", {"id": id.toFixed(0)}),
{
"data": {
"token": token,
@ -485,4 +397,169 @@ namespace _espe.backend
);
}
/**
*/
export async function invitation_list(
) : Promise<
Array<
{
id : int;
preview : {
key : string;
expiry : (null | int);
name_value : string;
label_value : string;
};
}
>
>
{
return abstract_call(
"GET",
lib_plankton.string.coin(
"/invitation/list",
{
}
)
);
}
/**
*/
export async function invitation_read(
id : int
) : Promise<
{
key : string;
expiry : (null | int);
name_changeable : boolean;
name_value : string;
label_changeable : boolean;
label_value : string;
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<int>;
}
>
{
return abstract_call(
"GET",
lib_plankton.string.coin("/invitation/read/{{id}}", {"id": id.toFixed(0)})
);
}
/**
*/
export async function invitation_create(
data : {
name_changeable : boolean;
name_value : string;
label_changeable : boolean;
label_value : string;
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<int>;
expiry : (null | int);
},
send_immediatly : boolean,
) : Promise<
{
id : int;
key : string;
}
>
{
return abstract_call(
"POST",
"/invitation/create",
{
"data": {
"data": data,
"send_immediatly": send_immediatly,
}
}
);
}
/**
*/
export async function invitation_delete(
id : int
) : Promise<void>
{
return abstract_call(
"DELETE",
lib_plankton.string.coin("/invitation/delete/{{id}}", {"id": id.toFixed(0)}),
);
}
/**
*/
export async function invitation_examine(
key : string
) : Promise<
{
name_changeable : boolean;
name_value : string;
label_changeable : boolean;
label_value : string;
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<int>;
}
>
{
return abstract_call(
"GET",
lib_plankton.string.coin("/invitation/examine?key={{key}}", {"key": key})
);
}
/**
*/
export async function invitation_accept(
key : string,
data : {
name : string;
label : string;
groups : Array<int>;
email_address : (null | string);
password : string;
}
) : Promise<
Array<
{
incident : string;
details : Record<string, any>;
}
>
>
{
return abstract_call(
"POST",
"/invitation/accept",
{
"data": {
"key": key,
"data": {
"name": data.name,
"label": data.label,
"groups": data.groups,
"email_address": data.email_address,
"password": data.password,
},
}
}
);
}
}

View file

@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>.
*/
namespace _espe.conf
namespace _zackeneule.conf
{
/**

View file

@ -30,7 +30,6 @@ html
color: hsl(var(--hue), 0%, 100%);
}
body
{
max-width: 960px;
@ -158,6 +157,78 @@ nav > ul > li:hover::after
}
*/
.widget-panel
{
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.widget-panel-rule
{
flex-basis: 2%;
min-height: 8px;
cursor: pointer;
}
.widget-panel-content
{
flex-basis: 98%;
}
.widget-panel:not(.widget-panel-collapsed)
{
padding: 8px;
border-radius: 4px;
}
.widget-panel:not(.widget-panel-collapsed) > .widget-panel-rule
{
border-left: 4px solid hsl(var(--hue), 0%, 75%);
}
.widget-panel.widget-panel-collapsed > .widget-panel-rule::after
{
content: "(?)";
}
.widget-panel.widget-panel-collapsed > .widget-panel-content
{
display: none;
}
.widget-list
{
margin: 0;
padding: 0;
}
.widget-panel_info:not(.widget-panel-collapsed)
{
background-color: hsl(240, 25%, 25%);
color: hsl(240, 50%, 75%);
}
.widget-panel_warning:not(.widget-panel-collapsed)
{
background-color: hsl(60, 25%, 25%);
color: hsl(60, 50%, 75%);
}
.widget-panel_error:not(.widget-panel-collapsed)
{
background-color: hsl(0, 25%, 25%);
color: hsl(0, 50%, 75%);
}
.widget-panel_success:not(.widget-panel-collapsed)
{
background-color: hsl(120, 25%, 25%);
color: hsl(120, 50%, 75%);
}
.plankton_form_field
{
margin-bottom: 16px;
@ -171,14 +242,67 @@ nav > ul > li:hover::after
font-weight: bold;
}
.plankton_form_actions > *
{
display: block;
margin: 8px;
text-transform: capitalize;
}
.plankton_search_item
{
cursor: pointer;
}
.plankton_input_password_input
{
display: inline-block;
}
.plankton_input_password_exhibit
{
margin: 8px;
padding: 8px;
background-color: hsla(var(--hue), 0%, 0%, 50%);
cursor: pointer;
display: inline-block;
font-weight: bold;
border-radius: 2px;
}
.plankton_input_password_exhibit::before
{
"content": "[";
}
.plankton_input_password_exhibit::after
{
"content": "[";
}
.plankton_input_group_field_help_content
{
display: block;
font-size: 0.75em;
margin-top: 4px;
margin-bottom: 4px;
margin-left: 8px;
}
/*
.plankton_input_group_field.plankton_input_group_field_help_active > .plankton_input_group_field_help_content
{
}
.plankton_input_group_field:not(.plankton_input_group_field_help_active) > .plankton_input_group_field_help_content
{
display: none;
}
*/
.plankton_input_group_field
{
margin-bottom: 16px;
margin-bottom: 32px;
}
.plankton_input_group_field_label
@ -210,7 +334,6 @@ nav > ul > li:hover::after
display: block;
}
.plankton_input_enumeration > *
{
display: block;
@ -236,3 +359,19 @@ nav > ul > li:hover::after
{
margin-bottom: 8px;
}
.plankton_input_list_element > *
{
display: inline-block;
}
.plankton_input_soft_inactive
{
display: none!important;
}
.plankton_input_set_option
{
display: block;
}

View file

@ -0,0 +1,152 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _zackeneule.widget.password_policy
{
/**
*/
export class class_password_policy implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private data : lib_plankton.password.type_policy;
/**
*/
private title : (null | string);
/**
*/
public constructor(
data : lib_plankton.password.type_policy,
{
"title": title = lib_plankton.translate.get("widget.password_policy.title"),
}
:
{
title ?: (null | string);
}
=
{
}
)
{
this.data = data;
this.title = title;
}
/**
*/
public load(
target_element : Element
) : Promise<void>
{
const entries : Array<string> = (
[]
// minimum_length
.concat(
(this.data.minimum_length === null)
?
[]
:
lib_plankton.translate.get(
"widget.password_policy.minimum_length",
{
"minimum_length": this.data.minimum_length.toFixed(0),
}
)
)
// maximum_length
.concat(
(this.data.minimum_length === null)
?
[]
:
lib_plankton.translate.get(
"widget.password_policy.maximum_length",
{
"maximum_length": this.data.maximum_length.toFixed(0),
}
)
)
// must_contain_letter
.concat(
(! this.data.must_contain_letter)
?
[]
:
lib_plankton.translate.get(
"widget.password_policy.must_contain_letter",
{
}
)
)
// must_contain_number
.concat(
(! this.data.must_contain_number)
?
[]
:
lib_plankton.translate.get(
"widget.password_policy.must_contain_number",
{
}
)
)
// must_contain_special_character
.concat(
(! this.data.must_contain_special_character)
?
[]
:
lib_plankton.translate.get(
"widget.password_policy.must_contain_special_character",
{
}
)
)
);
target_element.appendChild(_zackeneule.helpers.template_request("widget-password_policy"));
target_element.querySelector(".widget-password_policy").classList.toggle(
"widget-password_policy-without_title",
(this.title === null)
);
target_element.querySelector(".widget-password_policy-title").textContent = (this.title ?? "");
const element_ul : HTMLElement = target_element.querySelector(".widget-password_policy-entries");
entries.forEach(
entry => {
const element_li : HTMLElement = document.createElement("li");
element_li.textContent = entry;
element_ul.appendChild(element_li);
}
);
return Promise.resolve<void>(undefined);
}
}
}

View file

@ -0,0 +1,6 @@
<template id="widget-password_policy">
<div class="widget-password_policy">
<span class="widget-password_policy-title"></span>
<ul class="widget-password_policy-entries"></ul>
</div>
</template>

View file

@ -0,0 +1,13 @@
.widget-password_policy-title
{
display: block;
/*
font-weight: bold;
*/
}
.widget-password_policy-without_title .widget-password_policy-title
{
display: none;
}

View file

@ -1,7 +1,21 @@
#!/usr/bin/env python3
# Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Frontend
# Copyright (C) 2024 Christian Fraß
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
import sys as _sys
import os as _os
import shutil as _shutil
import argparse as _argparse
@ -16,6 +30,14 @@ def main():
metavar = "<output-directory>",
help = "output directory",
)
argument_parser.add_argument(
"-c",
"--conf-path",
type = str,
default = "",
metavar = "<conf-path>",
help = "path to conf file to be put",
)
args = argument_parser.parse_args()
## exec
@ -28,6 +50,11 @@ def main():
" ".join(targets),
)
)
if (args.conf_path != ""):
_shutil.copyfile(
args.conf_path,
_os.path.join(args.output_directory, "conf.json")
)
_sys.stdout.write("%s\n" % args.output_directory)

View file

@ -19,7 +19,7 @@ def main():
"--target-directory",
type = str,
dest = "target_directory",
default = "/opt/espe",
default = "/opt/zackeneule",
metavar = "<target-directory>",
help = "directory on the target system, where the files shall be put; default: /opt/espe",
)

View file

@ -35,55 +35,73 @@ _default: ${dir_build}/logic.js ${dir_build}/style.css ${dir_build}/index.html d
${dir_temp}/logic-unlinked.js: \
${dir_lib}/plankton/plankton.d.ts \
${dir_source}/logic/helpers.ts \
${dir_source}/logic/backend.ts \
${dir_source}/logic/conf.ts \
${dir_source}/helpers.ts \
${dir_source}/resources/backend.ts \
${dir_source}/resources/conf.ts \
${dir_source}/logic.ts \
${dir_source}/widgets/password_policy/logic.ts \
${dir_source}/pages/index/logic.ts \
${dir_source}/pages/login/logic.ts \
${dir_source}/pages/logout/logic.ts \
${dir_source}/pages/create/logic.ts \
${dir_source}/pages/list/logic.ts \
${dir_source}/pages/view/logic.ts \
${dir_source}/pages/register/logic.ts \
${dir_source}/pages/group_list/logic.ts \
${dir_source}/pages/group_view/logic.ts \
${dir_source}/pages/group_create/logic.ts \
${dir_source}/pages/member_list/logic.ts \
${dir_source}/pages/member_view/logic.ts \
${dir_source}/pages/password_change_init/logic.ts \
${dir_source}/pages/password_change_exec/logic.ts \
${dir_source}/logic/main.ts
${dir_source}/pages/invitation_list/logic.ts \
${dir_source}/pages/invitation_view/logic.ts \
${dir_source}/pages/invitation_create/logic.ts \
${dir_source}/pages/invitation_handle/logic.ts \
${dir_source}/main.ts
@ ${cmd_log} "logic | compile …"
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_tsc} --lib es2020,dom $^ --outFile $@
@ ${cmd_tsc} --lib es2020,dom --target es2015 $^ --outFile $@
${dir_build}/logic.js: ${dir_lib}/plankton/plankton.js ${dir_temp}/logic-unlinked.js
@ ${cmd_log} "logic | link …"
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_cat} $^ > $@
@ ${cmd_chmod} +x $@
${dir_build}/style.css: \
${dir_source}/style/style.css \
${dir_source}/style/main.css \
${dir_source}/widgets/password_policy/style.css \
${dir_source}/pages/index/style.css \
${dir_source}/pages/login/style.css \
${dir_source}/pages/logout/style.css \
${dir_source}/pages/create/style.css \
${dir_source}/pages/list/style.css \
${dir_source}/pages/view/style.css \
${dir_source}/pages/register/style.css \
${dir_source}/pages/group_list/style.css \
${dir_source}/pages/group_view/style.css \
${dir_source}/pages/group_create/style.css \
${dir_source}/pages/member_list/style.css \
${dir_source}/pages/member_view/style.css \
${dir_source}/pages/password_change_init/style.css \
${dir_source}/pages/password_change_exec/style.css
${dir_source}/pages/password_change_exec/style.css \
${dir_source}/pages/invitation_list/style.css \
${dir_source}/pages/invitation_view/style.css \
${dir_source}/pages/invitation_create/style.css \
${dir_source}/pages/invitation_handle/style.css
@ ${cmd_log} "style …"
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_cat} $^ > $@
${dir_build}/index.html: \
${dir_source}/structure/index.html.tpl \
${dir_source}/index.html.tpl \
${dir_source}/widgets/password_policy/structure.html \
${dir_source}/pages/index/structure.html \
${dir_source}/pages/login/structure.html \
${dir_source}/pages/logout/structure.html \
${dir_source}/pages/create/structure.html \
${dir_source}/pages/list/structure.html \
${dir_source}/pages/view/structure.html \
${dir_source}/pages/register/structure.html \
${dir_source}/pages/group_list/structure.html \
${dir_source}/pages/group_view/structure.html \
${dir_source}/pages/group_create/structure.html \
${dir_source}/pages/member_list/structure.html \
${dir_source}/pages/member_view/structure.html \
${dir_source}/pages/password_change_init/structure.html \
${dir_source}/pages/password_change_exec/structure.html
${dir_source}/pages/password_change_exec/structure.html \
${dir_source}/pages/invitation_list/structure.html \
${dir_source}/pages/invitation_view/structure.html \
${dir_source}/pages/invitation_create/structure.html \
${dir_source}/pages/invitation_handle/structure.html
@ ${cmd_log} "structure …"
@ ${cmd_mkdir} $(dir $@)
@ tools/make-index $^ > $@

View file

@ -2,7 +2,7 @@
## consts
dir=lib/plankton
dir=$(pwd)/lib/plankton
modules=""
modules="${modules} base"
@ -10,8 +10,11 @@ modules="${modules} file"
modules="${modules} json"
modules="${modules} base64"
modules="${modules} string"
modules="${modules} password"
modules="${modules} translate"
modules="${modules} storage"
modules="${modules} cache"
modules="${modules} zoo-widget"
modules="${modules} zoo-input"
modules="${modules} zoo-form"
modules="${modules} zoo-search"
@ -21,7 +24,17 @@ modules="${modules} zoo-page"
## exec
if false
then
mkdir -p ${dir}
cd ${dir}
ptk bundle web ${modules}
cd - > /dev/null
else
mkdir -p ${dir}
mkdir /tmp/sandbox -p
cd /tmp/sandbox
ptk fetch web ${modules}
schwamm --include=/tmp/sandbox/plankton.swm.json --output=dump:logic-decl > ${dir}/plankton.d.ts
schwamm --include=/tmp/sandbox/plankton.swm.json --output=dump:logic-impl > ${dir}/plankton.js
fi