working on address card editor
This commit is contained in:
@@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.notFound;
|
||||
import static java.lang.System.Logger.Level.ERROR;
|
||||
import static java.text.MessageFormat.format;
|
||||
|
||||
import de.srsoftware.tools.jdbc.Query;
|
||||
import de.srsoftware.umbrella.core.BaseDb;
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import de.srsoftware.umbrella.core.model.Contact;
|
||||
@@ -90,7 +91,13 @@ public class SqliteDb extends BaseDb implements ContactDb{
|
||||
|
||||
@Override
|
||||
public Contact save(Contact contact) {
|
||||
LOG.log(ERROR,"Save not implemented!");
|
||||
if (contact.id() == 0){ // new contact
|
||||
throw UmbrellaException.unprocessable("Storing of new contacts not implemented");
|
||||
} else try { // update
|
||||
Query.update(TABLE_CONTACTS).set(DATA).where(ID,equal(contact.id())).prepare(db).apply(contact.vcard()).execute();
|
||||
} catch (SQLException e) {
|
||||
throw databaseException("Failed to update vcard {0}",contact.id());
|
||||
}
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
href = '#',
|
||||
onclick = evt => { evt.preventDefault(); startEdit(); return false },
|
||||
onSet = newVal => {return true;},
|
||||
title = t('long_click_to_edit'),
|
||||
type = 'div',
|
||||
value = $bindable(null)
|
||||
} = $props();
|
||||
@@ -103,5 +104,5 @@
|
||||
{#if editable && editing}
|
||||
<input bind:value={editValue} onkeyup={typed} autofocus />
|
||||
{:else}
|
||||
<svelte:element this={type} href={href} onclick={ignore} {onmousedown} {onmouseup} {ontouchstart} {ontouchend} {oncontextmenu} class={{editable}} title={t('long_click_to_edit')} >{value}</svelte:element>
|
||||
<svelte:element this={type} href={href} onclick={ignore} {onmousedown} {onmouseup} {ontouchstart} {ontouchend} {oncontextmenu} class={{editable}} {title} >{value}</svelte:element>
|
||||
{/if}
|
||||
|
||||
@@ -1,31 +1,46 @@
|
||||
<script>
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import { addr } from '../../vcard.js';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
let { code } = $props();
|
||||
let { code, patch = (from, to) => true } = $props();
|
||||
|
||||
let address = $derived(addr(code));
|
||||
let home = $derived(code.toLowerCase().includes('type=home'));
|
||||
let work = $derived(code.toLowerCase().includes('type=work'));
|
||||
|
||||
function onSet(oldVal,newVal){
|
||||
const newCode = code.replace(oldVal,newVal);
|
||||
return patch(code,newCode);
|
||||
}
|
||||
|
||||
function toggleHome(){
|
||||
toggleType('HOME');
|
||||
}
|
||||
|
||||
function toggleType(key){
|
||||
key = key.toUpperCase();
|
||||
if (code.toUpperCase().includes(';TYPE='+key)) {
|
||||
const regex = new RegExp(';TYPE='+key, "ig");
|
||||
patch(code,code.replace(regex,''));
|
||||
} else patch(code,code.replace('ADR','ADR;TYPE='+key));
|
||||
}
|
||||
|
||||
function toggleWork(){
|
||||
toggleType('WORK');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="address">
|
||||
{#if address.box}
|
||||
<span class="post_box">{address.box}</span>
|
||||
{/if}
|
||||
{#if address.ext}
|
||||
<span class="extended">{address.ext}</span>
|
||||
{/if}
|
||||
{#if address.street}
|
||||
<span class="street">{address.street}</span>
|
||||
{/if}
|
||||
{#if address.code}
|
||||
<span class="code">{address.code}</span>
|
||||
{/if}
|
||||
{#if address.loc}
|
||||
<span class="locality">{address.loc}</span>
|
||||
{/if}
|
||||
{#if address.region}
|
||||
<span class="region">{address.region}</span>
|
||||
{/if}
|
||||
{#if address.country}
|
||||
<span class="country">{address.country}</span>
|
||||
{/if}
|
||||
<div>
|
||||
<span class="symbol {home?'':'inactive'}" onclick={toggleHome}></span>
|
||||
<span class="symbol {work?'':'inactive'}" onclick={toggleWork} ></span>
|
||||
</div>
|
||||
<LineEditor type="span" editable={true} value={address.box} onSet={newVal => onSet(address.box,newVal)} title={t('post_box')} />
|
||||
<LineEditor type="span" editable={true} value={address.ext} onSet={newVal => onSet(address.ext,newVal)} title={t('extended_address')} />
|
||||
<LineEditor type="span" editable={true} value={address.street} onSet={newVal => onSet(address.street,newVal)} title={t('street')} />
|
||||
<LineEditor type="span" editable={true} value={address.code} onSet={newVal => onSet(address.code,newVal)} title={t('post_code')} />
|
||||
<LineEditor type="span" editable={true} value={address.loc} onSet={newVal => onSet(address.loc,newVal)} title={t('locality')} />
|
||||
<LineEditor type="span" editable={true} value={address.region} onSet={newVal => onSet(address.region,newVal)} title={t('region')} />
|
||||
<LineEditor type="span" editable={true} value={address.country} onSet={newVal => onSet(address.country,newVal)} title={t('country')} />
|
||||
</div>
|
||||
@@ -15,12 +15,12 @@
|
||||
|
||||
let addresses = $derived(contact.vcard.match(/^ADR.*:.+$/gm));
|
||||
let code = $state(false);
|
||||
let fns = $derived(contact.vcard.match(/^FN.*:.+$/gm));
|
||||
let emails = $derived(contact.vcard.match(/^EMAIL.*:.+$/gm));
|
||||
let extra_fields = $derived(contact.vcard.match(/^X-.*:.+/gm));
|
||||
let fns = $derived(contact.vcard.match(/^FN.*:.+$/gm));
|
||||
let orgs = $derived(contact.vcard.match(/^ORG.*:.+$/gm));
|
||||
|
||||
async function patch(from,to){
|
||||
console.log(`patch(${contact.id}: ${from} → ${to})`);
|
||||
if (from == to) return;
|
||||
const url = api(`contact/${contact.id}`);
|
||||
const res = await fetch(url,{
|
||||
@@ -64,7 +64,9 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Org vcard={contact.vcard} />
|
||||
{#each orgs as code}
|
||||
<Org {code} {patch} />
|
||||
{/each}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -75,21 +77,21 @@
|
||||
<tr>
|
||||
<td>
|
||||
{#each addresses as code}
|
||||
<Address {code} />
|
||||
<Address {code} {patch} />
|
||||
{/each}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{#each emails as code}
|
||||
<Email {code} />
|
||||
<Email {code} {patch} />
|
||||
{/each}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{#each extra_fields as code}
|
||||
<ExtraField {code} />
|
||||
<ExtraField {code} {patch} />
|
||||
{/each}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
<script>
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import { email } from '../../vcard.js';
|
||||
|
||||
let { code } = $props();
|
||||
let { code, patch = (from, to) => true } = $props();
|
||||
|
||||
let adr = $derived(email(code));
|
||||
let home = $derived(code.toLowerCase().includes('type=home'));
|
||||
let work = $derived(code.toLowerCase().includes('type=work'));
|
||||
let value = $derived(email(code));
|
||||
|
||||
function onSet(newVal){
|
||||
const newCode = code.replace(value,newVal);
|
||||
return patch(code,newCode);
|
||||
}
|
||||
|
||||
function toggleHome(){
|
||||
toggleType('HOME');
|
||||
}
|
||||
|
||||
function toggleType(key){
|
||||
key = key.toUpperCase();
|
||||
if (code.toUpperCase().includes(';TYPE='+key)) {
|
||||
const regex = new RegExp(';TYPE='+key, "ig");
|
||||
patch(code,code.replace(regex,''));
|
||||
} else patch(code,code.replace('EMAIL','EMAIL;TYPE='+key));
|
||||
}
|
||||
|
||||
function toggleWork(){
|
||||
toggleType('WORK');
|
||||
}
|
||||
</script>
|
||||
|
||||
<span class="email">{adr}</span>
|
||||
{#if value}
|
||||
<span class="symbol {home?'':'inactive'}" onclick={toggleHome}></span>
|
||||
<span class="symbol {work?'':'inactive'}" onclick={toggleWork} ></span>
|
||||
<LineEditor type="span" editable={true} {value} {onSet} /><br/>
|
||||
{/if}
|
||||
@@ -1,14 +1,25 @@
|
||||
<script>
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import MultiLineEditor from '../../Components/MultilineEditor.svelte';
|
||||
import { extra } from '../../vcard.js';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
let { code } = $props();
|
||||
let { code, patch = (from, to) => true } = $props();
|
||||
|
||||
let field = $derived(extra(code));
|
||||
|
||||
function onSet(newVal){
|
||||
const newCode = code.replace(field.value,newVal.replaceAll('\n','\\n'));
|
||||
return patch(code,newCode);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if field}
|
||||
<span class={field.name}>
|
||||
{field.value}
|
||||
</span>
|
||||
<div class={field.name}>
|
||||
{#if field.value.includes('\\n')}
|
||||
<MultiLineEditor type="div" editable={true} value={field.value.replaceAll('\\n','\n')} {onSet} title={t(field.name)+' – '+t('long_click_to_edit')} />
|
||||
{:else}
|
||||
<LineEditor type="div" editable={true} value={field.value} {onSet} title={t(field.name)+' – '+t('long_click_to_edit')} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,18 +1,18 @@
|
||||
<script>
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import { fn } from '../../vcard.js';
|
||||
import { t } from '../../translations.svelte';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
let { code, patch = (from, to) => true } = $props();
|
||||
|
||||
let name = $derived(fn(code));
|
||||
let value = $derived(fn(code));
|
||||
|
||||
function onSet(newVal){
|
||||
const newCode = code.replace(name,newVal);
|
||||
const newCode = code.replace(value,newVal);
|
||||
return patch(code,newCode);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if name}
|
||||
<LineEditor type="span" editable={true} value={name} {onSet} />
|
||||
{#if value}
|
||||
<LineEditor type="span" editable={true} {value} {onSet} title={t('formatted_name')}/>
|
||||
{/if}
|
||||
@@ -18,7 +18,6 @@
|
||||
yikes();
|
||||
var data = await res.json();
|
||||
contacts = Object.values(data).sort(byName);
|
||||
console.log(contacts);
|
||||
} else {
|
||||
error(res);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<script>
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import { org } from '../../vcard.js';
|
||||
import { t } from '../../translations.svelte';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
let { vcard } = $props();
|
||||
let { code, patch = (from, to) => true } = $props();
|
||||
|
||||
let o = $derived(org(vcard));
|
||||
let value = $derived(org(code));
|
||||
|
||||
function onSet(newVal){
|
||||
const newCode = code.replace(value,newVal);
|
||||
return patch(code,newCode);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if o}
|
||||
<span class="organization">{o}</span>
|
||||
{#if value}
|
||||
<LineEditor type="span" editable={true} {value} {onSet} title={t('organization')}/>
|
||||
{/if}
|
||||
@@ -28,7 +28,7 @@ export function email(vcard){
|
||||
}
|
||||
|
||||
export function extra(code){
|
||||
const match = code.match(/^X-(.+):(.+)/)
|
||||
const match = code.match(/^X-([^:]+):(.+)/)
|
||||
return match ? {name:match[1],value:match[2]} : null
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
"contained_tax": "enthaltene Steuer",
|
||||
"content": "Inhalt",
|
||||
"context": "Kontext",
|
||||
"country": "Land",
|
||||
"COURT": "Amtsgericht",
|
||||
"CUSTOMER-NUMBER": "Kundennummer",
|
||||
"customer_number_prefix": "Präfix für Kundennummer",
|
||||
"create": "anlegen",
|
||||
"create_new_object": "{object} neu anlegen",
|
||||
@@ -94,6 +97,7 @@
|
||||
"footer": "Fuß-Text",
|
||||
"foreign_id": "externe Kennung",
|
||||
"forgot_pass" : "Password vergessen?",
|
||||
"formatted_name": "Anzeigename",
|
||||
"fulltext": "Volltextsuche",
|
||||
|
||||
"go": "los!",
|
||||
@@ -122,6 +126,7 @@
|
||||
"loading_data": "Daten werden geladen…",
|
||||
"loading_object": "lade {object}…",
|
||||
"local_court": "Amtsgericht",
|
||||
"locality": "Ort",
|
||||
"login" : "Anmeldung",
|
||||
"login_services": "Login-Services",
|
||||
"logout": "Abmelden",
|
||||
@@ -172,6 +177,7 @@
|
||||
|
||||
"oidc_Login" : "Anmeldung mit OIDC",
|
||||
"old_password": "altes Passwort",
|
||||
"organization": "Organisation",
|
||||
|
||||
"page": "Seite",
|
||||
"parent_task": "übergeordnete Aufgabe",
|
||||
@@ -189,6 +195,7 @@
|
||||
"pos": "Pos",
|
||||
"position": "Position",
|
||||
"positions": "Positionen",
|
||||
"post_code": "Postleitzahl",
|
||||
"postpone": "aufschieben",
|
||||
"price": "Preis",
|
||||
"priority": "Priorität",
|
||||
@@ -197,6 +204,7 @@
|
||||
"projects": "Projekte",
|
||||
|
||||
"record": "Eintrag",
|
||||
"region": "Bundesland",
|
||||
"repeat_new_password": "Wiederholung",
|
||||
"results": "Ergebnisse",
|
||||
|
||||
@@ -241,6 +249,7 @@
|
||||
"501": "Nicht implementiert"
|
||||
},
|
||||
"stock": "Inventar",
|
||||
"street": "Straße",
|
||||
"subject": "Betreff",
|
||||
"subtask": "Unteraufgabe",
|
||||
"subtasks": "Unteraufgaben",
|
||||
@@ -254,6 +263,7 @@
|
||||
"task_list": "Aufgabenliste",
|
||||
"tasks": "Aufgaben",
|
||||
"tax_id": "Steuernummer",
|
||||
"TAX-NUMBER": "Steuernummer",
|
||||
"tax_rate": "Steuersatz",
|
||||
"template": "Vorlage",
|
||||
"theme": "Design",
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
"contained_tax": "contained tax",
|
||||
"content": "content",
|
||||
"context": "context",
|
||||
"country": "country",
|
||||
"COURT": "local court",
|
||||
"CUSTOMER-NUMBER": "customer number",
|
||||
"customer_number_prefix": "prefix for customer number",
|
||||
"create": "create",
|
||||
"create_new_object": "create {object}",
|
||||
@@ -88,11 +91,13 @@
|
||||
|
||||
"failed": "failed",
|
||||
"failed_login_attempts" : "account locked until {release_time} after {attempts} failed login attempts",
|
||||
"file": "file",
|
||||
"files": "files",
|
||||
"filter": "filter",
|
||||
"footer": "footer",
|
||||
"foreign_id": "external ID",
|
||||
"forgot_pass" : "forgot password?",
|
||||
"formatted_name": "display name",
|
||||
"fulltext": "full text search",
|
||||
|
||||
"go": "go!",
|
||||
@@ -121,6 +126,7 @@
|
||||
"loading_data": "loading data…",
|
||||
"loading_object": "loading {object}…",
|
||||
"local_court": "local court",
|
||||
"locality": "locality",
|
||||
"login" : "login",
|
||||
"login_services": "login service",
|
||||
"logout": "logout",
|
||||
@@ -171,6 +177,7 @@
|
||||
|
||||
"oidc_Login" : "Login via OIDC",
|
||||
"old_password": "old password",
|
||||
"organization": "organization",
|
||||
|
||||
"page": "page",
|
||||
"parent_task": "parent task",
|
||||
@@ -188,6 +195,7 @@
|
||||
"pos": "pos",
|
||||
"position": "position",
|
||||
"positions": "positions",
|
||||
"post_code": "post cdoe",
|
||||
"postpone": "postpone",
|
||||
"price": "price",
|
||||
"priority": "priority",
|
||||
@@ -196,6 +204,7 @@
|
||||
"projects": "projects",
|
||||
|
||||
"record": "record",
|
||||
"region": "region",
|
||||
"repeat_new_password": "repeat new password",
|
||||
"results": "results",
|
||||
|
||||
@@ -240,6 +249,7 @@
|
||||
"501": "Not implemented"
|
||||
},
|
||||
"stock": "stock",
|
||||
"street": "street",
|
||||
"subject": "subject",
|
||||
"subtask": "subtask",
|
||||
"subtasks": "subtasks",
|
||||
@@ -253,6 +263,7 @@
|
||||
"task_list": "task list",
|
||||
"tasks": "tasks",
|
||||
"tax_id": "tax ID",
|
||||
"TAX-NUMBER": "tax ID",
|
||||
"tax_rate": "tax rate",
|
||||
"template": "template",
|
||||
"theme": "design",
|
||||
|
||||
@@ -239,4 +239,8 @@ textarea{
|
||||
.kanban .state_20 .box.p100,
|
||||
.kanban .state_40 .box.p100{
|
||||
border: 5px solid #733440;
|
||||
}
|
||||
|
||||
.vcard span.inactive{
|
||||
color: #222200;
|
||||
}
|
||||
@@ -402,8 +402,12 @@ fieldset.vcard{
|
||||
.vcard .name{
|
||||
font-weight: bold;
|
||||
}
|
||||
.vcard div.address{
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.vcard .address span {
|
||||
.vcard .address > span {
|
||||
display: block;
|
||||
}
|
||||
.vcard table{
|
||||
|
||||
Reference in New Issue
Block a user