diff --git a/frontend/src/routes/contact/Card.svelte b/frontend/src/routes/contact/Card.svelte
new file mode 100644
index 0000000..c62e7fb
--- /dev/null
+++ b/frontend/src/routes/contact/Card.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
{contact.vcard}
+
\ No newline at end of file
diff --git a/frontend/src/routes/contact/FN.svelte b/frontend/src/routes/contact/FN.svelte
new file mode 100644
index 0000000..dd3b5b1
--- /dev/null
+++ b/frontend/src/routes/contact/FN.svelte
@@ -0,0 +1,9 @@
+
+
+{name}
diff --git a/frontend/src/routes/contact/Index.svelte b/frontend/src/routes/contact/Index.svelte
index 44512d6..21092a3 100644
--- a/frontend/src/routes/contact/Index.svelte
+++ b/frontend/src/routes/contact/Index.svelte
@@ -1,58 +1,15 @@
-{t('contacts')}
\ No newline at end of file
+{t('contacts')}
+{#each contacts as contact}
+
+{/each}
\ No newline at end of file
diff --git a/frontend/src/routes/contact/Name.svelte b/frontend/src/routes/contact/Name.svelte
new file mode 100644
index 0000000..b6655e4
--- /dev/null
+++ b/frontend/src/routes/contact/Name.svelte
@@ -0,0 +1,23 @@
+
+
+{#if n.prefix}
+{n.prefix}
+{/if}
+{#if n.given}
+{n.given}
+{/if}
+{#if n.family}
+{n.family}
+{/if}
+{#if n.additional}
+{n.additional}
+{/if}
+{#if n.suffix}
+{n.suffix}
+{/if}
diff --git a/frontend/src/routes/contact/Org.svelte b/frontend/src/routes/contact/Org.svelte
new file mode 100644
index 0000000..0ae5f93
--- /dev/null
+++ b/frontend/src/routes/contact/Org.svelte
@@ -0,0 +1,9 @@
+
+
+{o}
diff --git a/frontend/src/vcard.js b/frontend/src/vcard.js
new file mode 100644
index 0000000..ec20322
--- /dev/null
+++ b/frontend/src/vcard.js
@@ -0,0 +1,82 @@
+export function parse(data){
+ var code = data.vcard;
+ const lines = code.split("\n");
+ let unprocessed = null;
+ var o = {};
+ for (var line of lines){
+ if (unprocessed = null) unprocessed = line;
+ if (line.startsWith(' ')) { // extend line
+ unprocessed += line.substring(1);
+ } else {
+ // process complete line
+ const parts = line.split(':');
+ var prefix = parts[0];
+ var val = parts.slice(1).join(':');
+ var key = prefix.split(';')[0].toUpperCase();
+ switch (key) {
+ case "ADR": o[key] = parseAdr(prefix,val); break;
+ default:
+ if (key) o[prefix]=val;
+ }
+ unprocessed = line;
+ }
+ }
+ console.log(o);
+}
+
+function parseAdr(key,val){
+ var parts = val.split(';');
+ var adr = {
+ postbox : parts[0],
+ ext : parts[1],
+ street : parts[2],
+ locality : parts[3],
+ region : parts[4],
+ code : parts[5],
+ country : parts[6]
+ }
+ parts = key.split(';');
+ for (let part of parts){
+ let inner = part.split('=');
+ if (inner.length<2) continue;
+ const k = inner[0];
+ const v = inner.slice(1).join('=');
+ adr[k] = v;
+ }
+ return adr;
+}
+
+export function fn(vcard){
+ const match = vcard.match(/^FN:(.+)$/m);
+ return match ? match[1].trim() : '';
+}
+
+export function org(vcard){
+ const match = vcard.match(/^ORG:(.+)$/m);
+ return match ? match[1].trim() : '';
+}
+
+export function name(vcard){
+ const match = vcard.match(/^N:(.+)$/m);
+ let name = {
+ family: null,
+ given: null,
+ additional: null,
+ prefix: null,
+ suffix: null
+ }
+ if (match){
+ const parts = match[1].trim().split(';');
+ name.family = parts[0];
+ name.given = parts[1];
+ name.additional = parts[2];
+ name.prefix = parts[3];
+ name.suffix = parts[4];
+ }
+ return name;
+}
+
+
+export function byName(a,b){
+ return fn(a.vcard).localeCompare(fn(b.vcard));
+}
\ No newline at end of file