No description
  • TypeScript 56.9%
  • Vue 41.4%
  • JavaScript 1.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
SitiSchu 85aa3a0bb8
All checks were successful
Build and package / build (push) Successful in 30s
Add create contact support and new contact button
2026-10-23 11:04:20 +02:00
.forgejo Add packaging workflow and project documentation 2026-10-22 16:56:43 +02:00
config Add local development stack and CardDAV proxy config 2026-10-22 16:56:43 +02:00
docs/screenshots Add profile picture editor screenshot 2026-10-22 22:45:50 +02:00
src Add create contact support and new contact button 2026-10-23 11:04:20 +02:00
tests/unit Add create contact support and new contact button 2026-10-23 11:04:20 +02:00
.gitignore Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
.prettierignore Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
.prettierrc.json Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
.renovaterc.json Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
docker-compose.yml Add local development stack and CardDAV proxy config 2026-10-22 16:56:43 +02:00
eslint.config.js Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
LICENSE Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
package.json Bump version to 1.3.0 2026-10-23 10:06:54 +02:00
pnpm-lock.yaml Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
README.md Add create contact support and new contact button 2026-10-23 11:04:20 +02:00
tsconfig.json Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00
vite.config.ts Set up contacts app tooling and metadata 2026-10-22 16:56:25 +02:00

Note: This project is fully vibe coded. Use at your own risk.

Contacts for OpenCloud

A web app for OpenCloud that browses address books and contacts from the OpenCloud CardDAV endpoint (/carddav/). It is based on web-app-skeleton.

Table of contents

Screenshots

Contacts

Contacts view with address books, categories, and contact details

Editing a contact

Editing a contact with photo upload, name, and organization fields

Profile picture editor

Square crop editor for a contact profile picture

Contacts provider in the OpenCloud global search

Build and install

pnpm build

Copy the contents of dist/ into $OC_DATA_DIR/web/assets/apps/contacts and restart OpenCloud. The generated manifest.json is used for app discovery.

Prebuilt packages

.forgejo/workflows/build.yml runs the tests, type checks, lint, and build on every push and pull request, then packages the result as opencloud-contacts-<version>.zip.

Every push to master replaces a rolling Latest development build prerelease (latest tag) with the new zip and SHA256SUMS. Pushing a v* tag publishes a separate versioned release instead. Both are downloadable from the repository's Releases page.

The archive contains a top-level contacts/ directory, so it unpacks directly into the apps folder:

unzip opencloud-contacts-<version>.zip -d "$OC_DATA_DIR/web/assets/apps"

Then restart OpenCloud (for example systemctl restart opencloud, or restart the OpenCloud container). The workflow expects a Forgejo runner with the docker label.

Features

  • CardDAV discovery through current-user-principal and addressbook-home-set
  • Address book sidebar with descriptions and contact counts
  • Contact list grouped alphabetically, with generated avatars and optional photos
  • Search across names, companies, email addresses, phone numbers, addresses, and notes
  • Global OpenCloud search provider with a topbar preview and full result list; results deep-link directly to the contact
  • Category sidebar with per-category counts and single-category filtering
  • Contact details for email, phone, postal addresses, websites, organization, title, role, birthday, anniversary, categories, notes, and group membership
  • Create contacts in the selected address book, and edit their names, organization, contact methods, addresses, websites, dates, categories, and notes
  • Upload, crop, replace, or remove contact photos; choose a square with pan and zoom, then the image is compressed into a 512px JPEG before being embedded in the vCard
  • Pill-based category editor with autocomplete for existing categories; type and press comma to add a new one
  • Email and phone categories based on vCard types: Private (HOME), Office (WORK), Mobile (CELL), Fax, Pager, and Other, with existing custom types preserved
  • Conditional PUT updates (If-Match) that reject concurrent changes, and guarded creates (If-None-Match: *) that never overwrite an existing contact
  • vCard fields the editor does not expose are preserved, including groups and custom X- properties
  • Apple-style custom labels (X-ABLabel) and preferred values (PREF)
  • vCard 3.0 and 4.0 parsing, including folded lines, quoted-printable values, base64 photos, data URI photos, and groups (KIND:group)
  • Partial-failure handling: one inaccessible address book does not hide the others
  • Creating and deleting contacts is not implemented yet

The app uses the access token from the OpenCloud session (useAuthStore) and sends it as a Bearer token. Authentication is terminated by the OpenCloud proxy, which forwards the authenticated user to Radicale (or another CardDAV backend).

Architecture

src/
  carddav/
    client.ts      CardDAV transport, discovery, address book queries, updates
    xml.ts         PROPFIND/REPORT builders and multistatus parsing
    vcard.ts       vCard parser and contact mapping
    generate.ts    Editable-field mapping and vCard rewriting
  composables/
    useContacts.ts Address book/contact state, loading, selection
  search/
    provider.ts    Global search provider (preview and full list)
    contactCache.ts Cached address books and contacts for search
    types.ts       Search result item and type guard
  components/      Sidebar, contact list, contact details, avatar, search views
  utils/contact.ts Search, sorting, grouping, labels, link/date formatting
  utils/image.ts   Image decoding, square cropping, and JPEG compression
  utils/link.ts    Contact deep link query helpers
  utils/search.ts  KQL search term extraction
  views/           Contacts page
  types.ts         Domain model shared by every layer

The layers are independent:

  • carddav/ knows HTTP, DAV XML, and vCard. It does not import Vue or UI code.
  • useContacts owns asynchronous state and exposes data plus actions.
  • Components receive data through props and emit intent. They do not call the network.
  • types.ts is the shared contract. Add a new field there, populate it in vcard.ts, then render it in a component.

Contact keeps raw (the original vCard), and parseVCard returns every property, including unknown X- properties. updateContactVCard rewrites only the editable properties and preserves the rest, so photos, groups, custom fields, and future features survive an edit. Adding create or delete follows the same pattern: add the conditional DAV call to CardDavClient and reuse the serializer, without replacing the parser.

Development

Requirements:

  • Node.js 22 or newer
  • pnpm (use the version from the packageManager field)
  • Docker and Docker Compose

Install dependencies and start the watcher:

pnpm install
pnpm build:w

Start the development stack in another terminal:

docker compose up

Open https://host.docker.internal:9200 and sign in with admin / admin. The built app is mounted from ./dist into /web/apps/contacts and is discovered automatically. The included Radicale service creates a Personal Address Book on first access.

pnpm build:w needs to be running before the app can be loaded. A browser reload is required after a rebuild.

The stack publishes port 9200, so stop any other OpenCloud dev stack before docker compose up. To deploy into an existing instance instead, copy dist/ to $OC_DATA_DIR/web/assets/apps/contacts and restart OpenCloud.

CardDAV backend

The development setup routes CardDAV through OpenCloud. For an existing installation, add the following routes to the OpenCloud proxy configuration and point them at your CardDAV server:

additional_policies:
  - name: default
    routes:
      - endpoint: /carddav/
        backend: http://radicale:5232
        remote_user_header: X-Remote-User
        skip_x_access_token: true
        additional_headers:
          - X-Script-Name: /carddav
      - endpoint: /.well-known/carddav
        backend: http://radicale:5232
        remote_user_header: X-Remote-User
        skip_x_access_token: true
        additional_headers:
          - X-Script-Name: /carddav

The app talks to /carddav/ on the same origin as OpenCloud, so no additional CSP or CORS configuration is required.

Testing

pnpm test:unit
pnpm check:types
pnpm lint

The unit tests cover CardDAV XML parsing, vCard parsing (folding, escapes, typed values, custom labels, photos, quoted-printable, vCard 4 groups), vCard rewriting and field preservation and photo add/replace/remove, the authenticated CardDAV client (discovery, query, update, failure isolation, error mapping), the global search provider (KQL extraction, result mapping, source filtering, caching, de-duplication, partial failures), and contact search/sorting, category counting/filtering, and label helpers.