Browse Source

working on address card editor

module/contact
Stephan Richter 4 weeks ago
parent
commit
c888845191
  1. 9
      contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java
  2. 3
      frontend/src/Components/LineEditor.svelte
  3. 59
      frontend/src/routes/contact/Address.svelte
  4. 14
      frontend/src/routes/contact/Card.svelte
  5. 34
      frontend/src/routes/contact/Email.svelte
  6. 19
      frontend/src/routes/contact/ExtraField.svelte
  7. 10
      frontend/src/routes/contact/FN.svelte
  8. 1
      frontend/src/routes/contact/Index.svelte
  9. 16
      frontend/src/routes/contact/Org.svelte
  10. 2
      frontend/src/vcard.js
  11. 10
      translations/src/main/resources/de.json
  12. 11
      translations/src/main/resources/en.json
  13. 4
      web/src/main/resources/web/css/default-color.css
  14. 6
      web/src/main/resources/web/css/default.css

9
contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java

@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.notFound; @@ -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{ @@ -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;
}
}

3
frontend/src/Components/LineEditor.svelte

@ -8,6 +8,7 @@ @@ -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 @@ @@ -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}

59
frontend/src/routes/contact/Address.svelte

@ -1,31 +1,46 @@ @@ -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>

14
frontend/src/routes/contact/Card.svelte

@ -15,12 +15,12 @@ @@ -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 @@ @@ -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 @@ @@ -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>

34
frontend/src/routes/contact/Email.svelte

@ -1,9 +1,37 @@ @@ -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}

19
frontend/src/routes/contact/ExtraField.svelte

@ -1,14 +1,25 @@ @@ -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}

10
frontend/src/routes/contact/FN.svelte

@ -1,18 +1,18 @@ @@ -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}

1
frontend/src/routes/contact/Index.svelte

@ -18,7 +18,6 @@ @@ -18,7 +18,6 @@
yikes();
var data = await res.json();
contacts = Object.values(data).sort(byName);
console.log(contacts);
} else {
error(res);
}

16
frontend/src/routes/contact/Org.svelte

@ -1,12 +1,18 @@ @@ -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}

2
frontend/src/vcard.js

@ -28,7 +28,7 @@ export function email(vcard){ @@ -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
}

10
translations/src/main/resources/de.json

@ -38,6 +38,9 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -189,6 +195,7 @@
"pos": "Pos",
"position": "Position",
"positions": "Positionen",
"post_code": "Postleitzahl",
"postpone": "aufschieben",
"price": "Preis",
"priority": "Priorität",
@ -197,6 +204,7 @@ @@ -197,6 +204,7 @@
"projects": "Projekte",
"record": "Eintrag",
"region": "Bundesland",
"repeat_new_password": "Wiederholung",
"results": "Ergebnisse",
@ -241,6 +249,7 @@ @@ -241,6 +249,7 @@
"501": "Nicht implementiert"
},
"stock": "Inventar",
"street": "Straße",
"subject": "Betreff",
"subtask": "Unteraufgabe",
"subtasks": "Unteraufgaben",
@ -254,6 +263,7 @@ @@ -254,6 +263,7 @@
"task_list": "Aufgabenliste",
"tasks": "Aufgaben",
"tax_id": "Steuernummer",
"TAX-NUMBER": "Steuernummer",
"tax_rate": "Steuersatz",
"template": "Vorlage",
"theme": "Design",

11
translations/src/main/resources/en.json

@ -38,6 +38,9 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -188,6 +195,7 @@
"pos": "pos",
"position": "position",
"positions": "positions",
"post_code": "post cdoe",
"postpone": "postpone",
"price": "price",
"priority": "priority",
@ -196,6 +204,7 @@ @@ -196,6 +204,7 @@
"projects": "projects",
"record": "record",
"region": "region",
"repeat_new_password": "repeat new password",
"results": "results",
@ -240,6 +249,7 @@ @@ -240,6 +249,7 @@
"501": "Not implemented"
},
"stock": "stock",
"street": "street",
"subject": "subject",
"subtask": "subtask",
"subtasks": "subtasks",
@ -253,6 +263,7 @@ @@ -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",

4
web/src/main/resources/web/css/default-color.css

@ -239,4 +239,8 @@ textarea{ @@ -239,4 +239,8 @@ textarea{
.kanban .state_20 .box.p100,
.kanban .state_40 .box.p100{
border: 5px solid #733440;
}
.vcard span.inactive{
color: #222200;
}

6
web/src/main/resources/web/css/default.css

@ -402,8 +402,12 @@ fieldset.vcard{ @@ -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{

Loading…
Cancel
Save