diff --git a/crates/frontend/js-components/lib/create-birthday-calendar-form.ts b/crates/frontend/js-components/lib/create-birthday-calendar-form.ts new file mode 100644 index 0000000..a99ec9a --- /dev/null +++ b/crates/frontend/js-components/lib/create-birthday-calendar-form.ts @@ -0,0 +1,102 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { Ref, createRef, ref } from 'lit/directives/ref.js'; +import { escapeXml } from "."; +import { getTimezones } from "./timezones.ts"; + +@customElement("create-birthday-calendar-form") +export class CreateCalendarForm extends LitElement { + protected override createRenderRoot() { + return this + } + + @property() + principal: string = '' + @property() + addr_id: string = '' + @property() + displayname: string = '' + @property() + description: string = '' + @property() + color: string = '' + + dialog: Ref = createRef() + form: Ref = createRef() + @property() + timezones: Array = [] + + override render() { + return html` + + +

Create calendar

+
+ +
+ +
+ +
+ + +
+
+ ` + } + + async submit(e: SubmitEvent) { + e.preventDefault() + if (!this.addr_id) { + alert("Empty id") + return + } + if (!this.displayname) { + alert("Empty displayname") + return + } + + let response = await fetch(`/caldav/principal/${this.principal}/_birthdays_${this.addr_id}`, { + method: 'MKCOL', + headers: { + 'Content-Type': 'application/xml' + }, + body: ` + + + + ${escapeXml(this.displayname)} + ${this.description ? `${escapeXml(this.description)}` : ''} + ${this.color ? `${escapeXml(this.color)}` : ''} + + + + + + + ` + }) + + if (response.status >= 400) { + alert(`Error ${response.status}: ${await response.text()}`) + return null + } + window.location.reload() + return null + } +} + +declare global { + interface HTMLElementTagNameMap { + 'create-calendar-form': CreateCalendarForm + } +} diff --git a/crates/frontend/js-components/vite.config.ts b/crates/frontend/js-components/vite.config.ts index 7480569..9397a06 100644 --- a/crates/frontend/js-components/vite.config.ts +++ b/crates/frontend/js-components/vite.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ rollupOptions: { input: [ + "lib/create-birthday-calendar-form.ts", "lib/create-calendar-form.ts", "lib/edit-calendar-form.ts", "lib/import-calendar-form.ts", diff --git a/crates/frontend/public/assets/js/create-birthday-calendar-form.mjs b/crates/frontend/public/assets/js/create-birthday-calendar-form.mjs new file mode 100644 index 0000000..7978fc8 --- /dev/null +++ b/crates/frontend/public/assets/js/create-birthday-calendar-form.mjs @@ -0,0 +1,122 @@ +import { i, x } from "./lit-DkXrt_Iv.mjs"; +import { n as n$1, t } from "./property-B8WoKf1Y.mjs"; +import { e, n } from "./ref-BwbQvJBB.mjs"; +import { e as escapeXml } from "./index-_IB1wMbZ.mjs"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __decorateClass = (decorators, target, key, kind) => { + var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; + for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--) + if (decorator = decorators[i2]) + result = (kind ? decorator(target, key, result) : decorator(result)) || result; + if (kind && result) __defProp(target, key, result); + return result; +}; +let CreateCalendarForm = class extends i { + constructor() { + super(...arguments); + this.principal = ""; + this.addr_id = ""; + this.displayname = ""; + this.description = ""; + this.color = ""; + this.dialog = e(); + this.form = e(); + this.timezones = []; + } + createRenderRoot() { + return this; + } + render() { + return x` + + +

Create calendar

+
+ +
+ +
+ +
+ + +
+
+ `; + } + async submit(e2) { + e2.preventDefault(); + if (!this.addr_id) { + alert("Empty id"); + return; + } + if (!this.displayname) { + alert("Empty displayname"); + return; + } + let response = await fetch(`/caldav/principal/${this.principal}/_birthdays_${this.addr_id}`, { + method: "MKCOL", + headers: { + "Content-Type": "application/xml" + }, + body: ` + + + + ${escapeXml(this.displayname)} + ${this.description ? `${escapeXml(this.description)}` : ""} + ${this.color ? `${escapeXml(this.color)}` : ""} + + + + + + + ` + }); + if (response.status >= 400) { + alert(`Error ${response.status}: ${await response.text()}`); + return null; + } + window.location.reload(); + return null; + } +}; +__decorateClass([ + n$1() +], CreateCalendarForm.prototype, "principal", 2); +__decorateClass([ + n$1() +], CreateCalendarForm.prototype, "addr_id", 2); +__decorateClass([ + n$1() +], CreateCalendarForm.prototype, "displayname", 2); +__decorateClass([ + n$1() +], CreateCalendarForm.prototype, "description", 2); +__decorateClass([ + n$1() +], CreateCalendarForm.prototype, "color", 2); +__decorateClass([ + n$1() +], CreateCalendarForm.prototype, "timezones", 2); +CreateCalendarForm = __decorateClass([ + t("create-birthday-calendar-form") +], CreateCalendarForm); +export { + CreateCalendarForm +}; diff --git a/crates/frontend/public/templates/components/sections/addressbooks_section.html b/crates/frontend/public/templates/components/sections/addressbooks_section.html index e699b0b..cf2291c 100644 --- a/crates/frontend/public/templates/components/sections/addressbooks_section.html +++ b/crates/frontend/public/templates/components/sections/addressbooks_section.html @@ -1,6 +1,6 @@

{{user.id }}'s Addressbooks

    - {% for (meta, addressbook) in addressbooks %} + {% for (meta, birthday_cal, addressbook) in addressbooks %}
  • @@ -24,9 +24,17 @@ > + {% if !birthday_cal.is_some() %} + + {% endif %}
  • @@ -37,7 +45,7 @@ {%if !deleted_addressbooks.is_empty() %}

    Deleted Addressbooks

      - {% for (meta, addressbook) in deleted_addressbooks %} + {% for (meta, birthday_cal, addressbook) in deleted_addressbooks %}
    • diff --git a/crates/frontend/public/templates/pages/user.html b/crates/frontend/public/templates/pages/user.html index e4c2f88..ce658c2 100644 --- a/crates/frontend/public/templates/pages/user.html +++ b/crates/frontend/public/templates/pages/user.html @@ -5,6 +5,7 @@ + diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index cee1d06..84f6751 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -12,7 +12,7 @@ use http::{Method, StatusCode}; use routes::{addressbooks::route_addressbooks, calendars::route_calendars}; use rustical_oidc::{OidcConfig, OidcServiceConfig, route_get_oidc_callback, route_post_oidc}; use rustical_store::{ - AddressbookStore, CalendarStore, + AddressbookStore, CalendarStore, PrefixedCalendarStore, auth::{AuthenticationProvider, middleware::AuthenticationLayer}, }; use std::sync::Arc; @@ -39,7 +39,11 @@ use crate::routes::{ #[cfg(not(feature = "dev"))] use assets::{Assets, EmbedService}; -pub fn frontend_router( +pub fn frontend_router< + AP: AuthenticationProvider, + CS: CalendarStore, + AS: AddressbookStore + PrefixedCalendarStore, +>( prefix: &'static str, auth_provider: Arc, cal_store: Arc, diff --git a/crates/frontend/src/routes/addressbooks.rs b/crates/frontend/src/routes/addressbooks.rs index f0707fc..4b446ef 100644 --- a/crates/frontend/src/routes/addressbooks.rs +++ b/crates/frontend/src/routes/addressbooks.rs @@ -1,10 +1,12 @@ -use std::sync::Arc; - use askama::Template; use askama_web::WebTemplate; use axum::{Extension, extract::Path, response::IntoResponse}; use http::StatusCode; -use rustical_store::{Addressbook, AddressbookStore, CollectionMetadata, auth::Principal}; +use rustical_store::{ + Addressbook, AddressbookStore, Calendar, CollectionMetadata, PrefixedCalendarStore, + auth::Principal, +}; +use std::sync::Arc; use crate::pages::user::{Section, UserPage}; @@ -18,11 +20,11 @@ impl Section for AddressbooksSection { #[template(path = "components/sections/addressbooks_section.html")] pub struct AddressbooksSection { pub user: Principal, - pub addressbooks: Vec<(CollectionMetadata, Addressbook)>, - pub deleted_addressbooks: Vec<(CollectionMetadata, Addressbook)>, + pub addressbooks: Vec<(CollectionMetadata, Option, Addressbook)>, + pub deleted_addressbooks: Vec<(CollectionMetadata, Option, Addressbook)>, } -pub async fn route_addressbooks( +pub async fn route_addressbooks( Path(user_id): Path, Extension(addr_store): Extension>, user: Principal, @@ -43,22 +45,42 @@ pub async fn route_addressbooks( let mut addressbook_infos = vec![]; for addressbook in addressbooks { + let birthday_id = format!("{}{}", AS::PREFIX, &addressbook.id); + let birthday_cal = match addr_store + .get_calendar(&addressbook.principal, &birthday_id, true) + .await + { + Ok(cal) => Some(cal), + Err(rustical_store::Error::NotFound) => None, + err => Some(err.unwrap()), + }; addressbook_infos.push(( addr_store .addressbook_metadata(&addressbook.principal, &addressbook.id) .await .unwrap(), + birthday_cal, addressbook, )); } let mut deleted_addressbook_infos = vec![]; for addressbook in deleted_addressbooks { + let birthday_id = format!("{}{}", AS::PREFIX, &addressbook.id); + let birthday_cal = match addr_store + .get_calendar(&addressbook.principal, &birthday_id, true) + .await + { + Ok(cal) => Some(cal), + Err(rustical_store::Error::NotFound) => None, + err => Some(err.unwrap()), + }; deleted_addressbook_infos.push(( addr_store .addressbook_metadata(&addressbook.principal, &addressbook.id) .await .unwrap(), + birthday_cal, addressbook, )); }