Add addressbook import to frontend

This commit is contained in:
Lennart
2025-08-23 19:50:34 +02:00
parent 1e90ff3d6c
commit 9050484932
7 changed files with 195 additions and 7 deletions

View File

@@ -0,0 +1,92 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Ref, createRef, ref } from 'lit/directives/ref.js';
@customElement("import-addressbook-form")
export class ImportAddressbookForm extends LitElement {
constructor() {
super()
}
protected override createRenderRoot() {
return this
}
@property()
user: string = ''
@property()
principal: string
@property()
addressbook_id: string = self.crypto.randomUUID()
dialog: Ref<HTMLDialogElement> = createRef()
form: Ref<HTMLFormElement> = createRef()
file: File;
override render() {
return html`
<button @click=${() => this.dialog.value.showModal()}>Import addressbook</button>
<dialog ${ref(this.dialog)}>
<h3>Import addressbook</h3>
<form @submit=${this.submit} ${ref(this.form)}>
<label>
principal (for group addressbook)
<select name="principal" value=${this.user} @change=${e => this.principal = e.target.value}>
<option value=${this.user}>${this.user}</option>
${window.rusticalUser.memberships.map(membership => html`
<option value=${membership}>${membership}</option>
`)}
</select>
</label>
<br>
<label>
id
<input type="text" name="id" value=${this.addressbook_id} @change=${e => this.addressbook_id = e.target.value} />
</label>
<br>
<label>
file
<input type="file" accept="text/vcard" name="file" @change=${e => this.file = e.target.files[0]} />
</label>
<button type="submit">Import</button>
<button type="submit" @click=${event => { event.preventDefault(); this.dialog.value.close(); this.form.value.reset() }} class="cancel">Cancel</button>
</form>
</dialog>
`
}
async submit(e: SubmitEvent) {
e.preventDefault()
this.principal ||= this.user
if (!this.principal) {
alert("Empty principal")
return
}
if (!this.addressbook_id) {
alert("Empty id")
return
}
let response = await fetch(`/carddav/principal/${this.principal}/${this.addressbook_id}`, {
method: 'IMPORT',
headers: {
'Content-Type': 'text/vcard'
},
body: this.file,
})
if (response.status >= 400) {
alert(`Error ${response.status}: ${await response.text()}`)
return null
}
window.location.reload()
return null
}
}
declare global {
interface HTMLElementTagNameMap {
'import-addressbook-form': ImportAddressbookForm
}
}

View File

@@ -1,7 +1,6 @@
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { Ref, createRef, ref } from 'lit/directives/ref.js'; import { Ref, createRef, ref } from 'lit/directives/ref.js';
import { escapeXml } from ".";
@customElement("import-calendar-form") @customElement("import-calendar-form")
export class ImportCalendarForm extends LitElement { export class ImportCalendarForm extends LitElement {
@@ -19,8 +18,6 @@ export class ImportCalendarForm extends LitElement {
principal: string principal: string
@property() @property()
cal_id: string = self.crypto.randomUUID() cal_id: string = self.crypto.randomUUID()
@property()
timezone_id: string = ''
dialog: Ref<HTMLDialogElement> = createRef() dialog: Ref<HTMLDialogElement> = createRef()
form: Ref<HTMLFormElement> = createRef() form: Ref<HTMLFormElement> = createRef()

View File

@@ -19,6 +19,7 @@ export default defineConfig({
"lib/import-calendar-form.ts", "lib/import-calendar-form.ts",
"lib/create-addressbook-form.ts", "lib/create-addressbook-form.ts",
"lib/edit-addressbook-form.ts", "lib/edit-addressbook-form.ts",
"lib/import-addressbook-form.ts",
"lib/delete-button.ts", "lib/delete-button.ts",
], ],
output: { output: {

View File

@@ -0,0 +1,100 @@
import { i, x } from "./lit-z6_uA4GX.mjs";
import { n as n$1, t } from "./property-D0NJdseG.mjs";
import { e, n } from "./ref-CPp9J0V5.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 ImportAddressbookForm = class extends i {
constructor() {
super();
this.user = "";
this.addressbook_id = self.crypto.randomUUID();
this.dialog = e();
this.form = e();
}
createRenderRoot() {
return this;
}
render() {
return x`
<button @click=${() => this.dialog.value.showModal()}>Import addressbook</button>
<dialog ${n(this.dialog)}>
<h3>Import addressbook</h3>
<form @submit=${this.submit} ${n(this.form)}>
<label>
principal (for group addressbook)
<select name="principal" value=${this.user} @change=${(e2) => this.principal = e2.target.value}>
<option value=${this.user}>${this.user}</option>
${window.rusticalUser.memberships.map((membership) => x`
<option value=${membership}>${membership}</option>
`)}
</select>
</label>
<br>
<label>
id
<input type="text" name="id" value=${this.addressbook_id} @change=${(e2) => this.addressbook_id = e2.target.value} />
</label>
<br>
<label>
file
<input type="file" accept="text/vcard" name="file" @change=${(e2) => this.file = e2.target.files[0]} />
</label>
<button type="submit">Import</button>
<button type="submit" @click=${(event) => {
event.preventDefault();
this.dialog.value.close();
this.form.value.reset();
}} class="cancel">Cancel</button>
</form>
</dialog>
`;
}
async submit(e2) {
e2.preventDefault();
this.principal || (this.principal = this.user);
if (!this.principal) {
alert("Empty principal");
return;
}
if (!this.addressbook_id) {
alert("Empty id");
return;
}
let response = await fetch(`/carddav/principal/${this.principal}/${this.addressbook_id}`, {
method: "IMPORT",
headers: {
"Content-Type": "text/vcard"
},
body: this.file
});
if (response.status >= 400) {
alert(`Error ${response.status}: ${await response.text()}`);
return null;
}
window.location.reload();
return null;
}
};
__decorateClass([
n$1()
], ImportAddressbookForm.prototype, "user", 2);
__decorateClass([
n$1()
], ImportAddressbookForm.prototype, "principal", 2);
__decorateClass([
n$1()
], ImportAddressbookForm.prototype, "addressbook_id", 2);
ImportAddressbookForm = __decorateClass([
t("import-addressbook-form")
], ImportAddressbookForm);
export {
ImportAddressbookForm
};

View File

@@ -16,7 +16,6 @@ let ImportCalendarForm = class extends i {
super(); super();
this.user = ""; this.user = "";
this.cal_id = self.crypto.randomUUID(); this.cal_id = self.crypto.randomUUID();
this.timezone_id = "";
this.dialog = e(); this.dialog = e();
this.form = e(); this.form = e();
} }
@@ -93,9 +92,6 @@ __decorateClass([
__decorateClass([ __decorateClass([
n$1() n$1()
], ImportCalendarForm.prototype, "cal_id", 2); ], ImportCalendarForm.prototype, "cal_id", 2);
__decorateClass([
n$1()
], ImportCalendarForm.prototype, "timezone_id", 2);
ImportCalendarForm = __decorateClass([ ImportCalendarForm = __decorateClass([
t("import-calendar-form") t("import-calendar-form")
], ImportCalendarForm); ], ImportCalendarForm);

View File

@@ -65,4 +65,5 @@
{% endif %} {% endif %}
<create-addressbook-form user="{{ user.id }}"></create-addressbook-form> <create-addressbook-form user="{{ user.id }}"></create-addressbook-form>
<import-addressbook-form user="{{ user.id }}"></import-addressbook-form>

View File

@@ -10,6 +10,7 @@ window.rusticalUser = JSON.parse(document.querySelector('#data-rustical-user').i
<script type="module" src="/frontend/assets/js/import-calendar-form.mjs" async></script> <script type="module" src="/frontend/assets/js/import-calendar-form.mjs" async></script>
<script type="module" src="/frontend/assets/js/create-addressbook-form.mjs" async></script> <script type="module" src="/frontend/assets/js/create-addressbook-form.mjs" async></script>
<script type="module" src="/frontend/assets/js/edit-addressbook-form.mjs" async></script> <script type="module" src="/frontend/assets/js/edit-addressbook-form.mjs" async></script>
<script type="module" src="/frontend/assets/js/import-addressbook-form.mjs" async></script>
<script type="module" src="/frontend/assets/js/delete-button.mjs" async></script> <script type="module" src="/frontend/assets/js/delete-button.mjs" async></script>
{% endblock %} {% endblock %}
{% block header_center %} {% block header_center %}