mirror of
https://github.com/jalad25/contact-note.git
synced 2026-07-22 06:53:06 +00:00
Docs updated. New preview added.
This commit is contained in:
parent
342f532ffb
commit
466eddca93
13 changed files with 147 additions and 154 deletions
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -42,6 +42,7 @@ Closes #
|
|||
- [ ] `npm run lint` passes.
|
||||
- [ ] `npm run build` passes with no TypeScript errors.
|
||||
- [ ] I have tested these changes in a local Obsidian vault.
|
||||
- [ ] I have tested in both the sidebar contacts view and a contacts base view (`.base` file), if the change touches shared rendering or contact data.
|
||||
- [ ] I have tested the affected UI in both light and dark mode (if any styles or DOM structure were touched).
|
||||
- [ ] I have tested these changes on both the Obsidian desktop application and the Obsidian mobile app (if relevant).
|
||||
- [ ] I have updated documentation (README, CONTRIBUTING, or DEVELOPMENT) where applicable.
|
||||
|
|
|
|||
1
.github/PULL_REQUEST_TEMPLATE/refactor.md
vendored
1
.github/PULL_REQUEST_TEMPLATE/refactor.md
vendored
|
|
@ -33,5 +33,6 @@
|
|||
- [ ] `npm run lint` passes.
|
||||
- [ ] `npm run build` passes with no TypeScript errors.
|
||||
- [ ] I have tested the affected areas in a local vault to confirm no regression.
|
||||
- [ ] I have tested in both the sidebar contacts view and a contacts base view (`.base` file), if the change touches shared rendering or contact data.
|
||||
- [ ] I have tested the affected UI in both light and dark mode (if any styles or DOM structure were touched).
|
||||
- [ ] I have tested the affected behavior on both the Obsidian desktop application and the Obsidian mobile app (if relevant).
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Before opening a new bug report:
|
|||
- Make sure you are running the latest version of the plugin and the minimum required version of Obsidian.
|
||||
- Try to reproduce the bug in a clean vault with other plugins disabled or removed.
|
||||
- Try to reproduce the bug in **both light and dark mode**, since some issues are theme-specific.
|
||||
- Try to reproduce the bug in the **Obsidian desktop application and the Obsidian mobile app**, since some issues are device-specific.
|
||||
|
||||
## Suggesting Enhancements
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ For usage questions, configuration help, or sharing how you use the plugin, plea
|
|||
4. **Add a schema migration** if your change alters the on-disk shape of `ContactNoteConfiguration` (renamed/removed fields, restructured values). See [Configuration Schema Migrations](DEVELOPMENT.md#configuration-schema-migrations).
|
||||
5. **Lint your code** by running `npm run lint`.
|
||||
6. **Build the project** with `npm run build` to ensure there are no TypeScript errors.
|
||||
7. **Test your changes** in a local Obsidian vault. If your change touches the shared card renderer or contact data model, verify both the **Contacts view** (sidebar) and a **Contacts base view** (`.base` file).
|
||||
7. **Test your changes** in a local Obsidian vault. If your change touches the shared card renderer or contact data model, verify both the **ContactsView** (sidebar panel) and **ContactsBasesView** (`.base` file).
|
||||
8. **Update documentation** (README or DEVELOPMENT.md) if your changes affect user-facing behavior or development workflow.
|
||||
|
||||
### Creating a Pull Request
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ This document outlines how to set up a local development environment and the int
|
|||
|
||||
- **[Node.js](https://nodejs.org/)**: v22 or later recommended
|
||||
- **[Git](https://git-scm.com/)**: latest version
|
||||
- **[Obsidian](https://obsidian.md/)**: 1.11.4 for the API's `Bases` and a local vault for testing. Older versions will not load the plugin.
|
||||
- **[Obsidian](https://obsidian.md/)**: 1.12.7 for the API's `Bases` and a local vault for testing. Older versions will not load the plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
@ -73,26 +73,30 @@ The files required for a release are:
|
|||
```
|
||||
contact-note/
|
||||
├── src/
|
||||
│ ├── main.ts # Plugin entry point, configuration, file events, view registration
|
||||
│ ├── Contact.ts # Contact data model
|
||||
│ ├── ContactNoteCard.ts # Shared contact card builder (used by reading view, contacts view, and bases view)
|
||||
│ ├── ContactNoteSettingTab.ts # Settings tab UI; owns ContactNoteSettings and DEFAULT_SETTINGS
|
||||
│ ├── SchemaMigration.ts # data.json migrations
|
||||
│ ├── main.ts # Plugin entry point, configuration, file events, view registration, ribbon, commands
|
||||
│ ├── Contact.ts # Runtime contact model (parsed frontmatter)
|
||||
│ ├── ContactNote.ts # On-disk contact note schema: BUILTIN_FIELDS and the frontmatter template builder
|
||||
│ ├── ContactCard.ts # Shared contact card builder (used by reading view, panel view, and bases view)
|
||||
│ ├── ContactsBase.ts # `.base` file builder and append-view mutator; owns DEFAULT_PROPERTY_ORDER
|
||||
│ ├── ContactNoteSettingTab.ts # Settings tab UI; owns ContactNoteSettings and DEFAULT_SETTINGS
|
||||
│ ├── ConfigurationSchemaMigration.ts # data.json migrations
|
||||
│ ├── views/
|
||||
│ │ ├── ContactsView.ts # Contacts view (ItemView); owns ContactsViewOptions and DEFAULT_VIEW_OPTIONS
|
||||
│ │ └── ContactsBasesView.ts # Bases-integrated contacts base view (BasesView)
|
||||
│ │ ├── ContactsView.ts # Contacts view in a sidebar panel (ItemView); owns ContactsViewOptions and DEFAULT_VIEW_OPTIONS
|
||||
│ │ └── ContactsBasesView.ts # Contacts view in a base (BasesView)
|
||||
│ ├── modals/
|
||||
│ │ ├── NewContactModal.ts # "New contact" dialog
|
||||
│ │ └── EditViewFilterModal.ts # Contacts view filter editor
|
||||
│ │ ├── NewContactNoteModal.ts # "New contact" dialog
|
||||
│ │ ├── NewContactsBaseModal.ts # "New base with contacts view" dialog
|
||||
│ │ ├── AppendContactsBaseViewModal.ts # "Add contacts view to base" dialog
|
||||
│ │ └── EditViewFilterModal.ts # Panel view filter editor
|
||||
│ └── suggesters/
|
||||
│ └── FolderSuggest.ts # Folder-path autocompletion for settings inputs
|
||||
├── styles.css # Plugin styles
|
||||
├── manifest.json # Obsidian plugin manifest
|
||||
├── versions.json # Plugin/Obsidian version map
|
||||
├── package.json # npm manifest and scripts
|
||||
├── tsconfig.json # TypeScript config
|
||||
├── esbuild.config.mjs # esbuild config
|
||||
└── eslint.config.mjs # ESLint config
|
||||
│ └── FolderSuggest.ts # Folder-path autocompletion for settings inputs
|
||||
├── styles.css # Plugin styles
|
||||
├── manifest.json # Obsidian plugin manifest
|
||||
├── versions.json # Plugin/Obsidian version map
|
||||
├── package.json # npm manifest and scripts
|
||||
├── tsconfig.json # TypeScript config
|
||||
├── esbuild.config.mjs # esbuild config
|
||||
└── eslint.config.mjs # ESLint config
|
||||
```
|
||||
|
||||
## Configuration Shape
|
||||
|
|
@ -102,31 +106,42 @@ User configuration is stored in `data.json` and loaded into `plugin.configuratio
|
|||
```ts
|
||||
type ContactNoteConfiguration =
|
||||
{ schemaVersion: number }
|
||||
& ContactNoteSettings // from ContactNoteSettingTab.ts — fields edited in the settings tab
|
||||
& ContactsViewOptions; // from views/ContactsView.ts — per-view options edited from the ⋮ menu
|
||||
& ContactNoteSettings // from ContactNoteSettingTab.ts, fields edited in the settings tab
|
||||
& ContactsViewOptions; // from views/ContactsView.ts, per-view options edited from the ⋮ menu
|
||||
```
|
||||
|
||||
`DEFAULT_CONFIGURATION` is the merged default of the two sub-defaults plus `schemaVersion`. `loadSettings()` runs the saved object through `migrate(...)` and then strips any keys not in `DEFAULT_CONFIGURATION`, so removed fields self-clean from `data.json` on the next save.
|
||||
|
||||
## Contacts Base View and Sidebar View
|
||||
## Contacts Views in a (Sidebar) Panel or Base
|
||||
|
||||
The plugin registers two views under the same view type `CONTACT_CARDS_LIST_VIEW_TYPE`:
|
||||
The plugin registers two views under the same view type `CONTACT_NOTE_LIST_VIEW_TYPE`:
|
||||
|
||||
- `ContactsView` via `registerView`
|
||||
- `ContactsBasesView` via `registerBasesView`
|
||||
- `ContactsView` (the contacts view in a sidebar panel) via `registerView`
|
||||
- `ContactsBasesView` (the contacts view in a base) via `registerBasesView`
|
||||
|
||||
A few things worth knowing before changing the bases view:
|
||||
|
||||
- **Per-base options.** The `static getViewOptions(config)` method returns the toggle group rendered in Bases' options panel. Toggle values are read at render time via `this.config.get(...)` in `onDataUpdated`.
|
||||
- **Property reads.** Scalar and list frontmatter fields are read through Bases' query API (`entry.getValue("note.<field>")`). The `socials` field is read directly from `metadataCache` because Bases' `ObjectValue` has no public key-enumeration API.
|
||||
- **Injected New button.** Bases' native New button creates a file at the vault root using only the visible columns' frontmatter, which is the wrong location and shape for a contact. It cannot be intercepted, so the native button is hidden via CSS scoped to the plugin's bases view, and `injectNewButton()` adds a replacement that opens `NewContactModal`.
|
||||
- **Default property order.** On first render of a fresh view, `ContactsBasesView` seeds the property order with `firstName`, `lastName`, `displayName` so the column picker is populated. Once the user customises the order, the seed is not reapplied.
|
||||
- **Injected New button.** Bases' native New button creates a file at the vault root using only the visible columns' frontmatter, which is the wrong location and shape for a contact. It cannot be intercepted, so the native button is hidden via CSS scoped to the plugin's bases view, and `injectNewButton()` adds a replacement that opens `NewContactNoteModal`.
|
||||
- **Default property order.** On first render of a fresh view, `ContactsBasesView` seeds the property order with `DEFAULT_PROPERTY_ORDER` from [`ContactsBase.ts`](src/ContactsBase.ts) (`firstName`, `middleName`, `lastName`, `displayName`) so the column picker is populated. Once the user customises the order, the seed is not reapplied.
|
||||
|
||||
The **Create new base with Contacts base view** command (and the matching `book-plus` ribbon icon) generates a `.base` YAML file pre-configured with the contact filter, ordering, and default toggle values. The file location and base-view name are driven by the `baseFolderPath` and `defaultBaseViewName` settings.
|
||||
## Base File Generation and Mutation
|
||||
|
||||
[`ContactsBase.ts`](src/ContactsBase.ts) is the single source for `.base` YAML emitted or modified by the plugin:
|
||||
|
||||
- `buildContactsBaseFile(...)` produces the YAML written by **Create new base with contacts view** (driven by `NewContactsBaseModal`). The `isContact` formula sits at the top level; the `filters.and: [- formula.isContact]` block is written *inside the view* so multiple views in a base can opt in or out independently.
|
||||
- `appendContactsViewToBase(...)` powers **Add contacts view to base** (driven by `AppendContactsBaseViewModal`). It appends a new view block to an existing `.base`, inserts an `isContact` formula if the file doesn't already have one, and reports a `formulaMismatch` when the existing formula doesn't match the current identification settings so the modal can surface a notice.
|
||||
|
||||
Both flows take `useFolder`, `folderPath`, `tag`, and the user-entered `viewName` from the modal. The base file folder is taken from the `baseFolderPath` setting; the base file name and view name are entered per-creation in the modal (there is no "default base file/view name" setting).
|
||||
|
||||
## Plugin Event Bus
|
||||
|
||||
[`main.ts`](src/main.ts) creates a plugin-scoped `Events` instance (`plugin.events`) and `saveConfiguration()` fires `configuration-changed` after every write. `ContactsView` subscribes to that event in `onOpen` and re-initialises its in-memory contacts on each fire, which is how the panel view picks up changes to **Identify contacts by folder / folder path / tag** without requiring the user to reopen it. New listeners that need to respond to settings changes should subscribe to the same event rather than polling configuration.
|
||||
|
||||
## Configuration Schema Migrations
|
||||
|
||||
Plugin configurations are versioned through [`SchemaMigration.ts`](src/SchemaMigration.ts). The `schemaVersion` field on `ContactNoteConfiguration` records the version of the data on disk, and `CURRENT_SCHEMA_VERSION` (defined in [`main.ts`](src/main.ts)) records the version the running code expects.
|
||||
Plugin configurations are versioned through [`ConfigurationSchemaMigration.ts`](src/ConfigurationSchemaMigration.ts). The `schemaVersion` field on `ContactNoteConfiguration` records the version of the data on disk, and `CURRENT_SCHEMA_VERSION` (defined in [`main.ts`](src/main.ts)) records the version the running code expects.
|
||||
|
||||
On every plugin load, `loadSettings()` runs the user's saved data through `migrate(...)`, which steps the data forward one version at a time using the entries in the `MIGRATIONS` array. If anything was migrated, the upgraded settings are written back to disk so the user only pays the migration cost once.
|
||||
|
||||
|
|
@ -137,11 +152,11 @@ On every plugin load, `loadSettings()` runs the user's saved data through `migra
|
|||
When a configuration change would break existing user data (renamed field, restructured value, removed field with a non-default replacement, etc.):
|
||||
|
||||
1. **Bump `CURRENT_SCHEMA_VERSION`** in [`main.ts`](src/main.ts) by one.
|
||||
2. **Add a step function** named `migrate_N_to_N+1(raw)` under the *Migration Step Functions* region of [`SchemaMigration.ts`](src/SchemaMigration.ts). It receives the previous-version shape as `any` and returns `Partial<ContactNoteConfiguration> & { schemaVersion: N+1 }`.
|
||||
2. **Add a step function** named `migrate_N_to_N+1(raw)` under the *Migration Step Functions* region of [`ConfigurationSchemaMigration.ts`](src/ConfigurationSchemaMigration.ts). It receives the previous-version shape as `any` and returns `Partial<ContactNoteConfiguration> & { schemaVersion: N+1 }`.
|
||||
3. **Register it** by adding `{ from: N, to: N+1, apply: migrate_N_to_N+1 }` to the `MIGRATIONS` array.
|
||||
4. **Update the relevant type and default** to reflect the new shape:
|
||||
- For settings-tab fields, edit `ContactNoteSettings` and `DEFAULT_SETTINGS` in [`ContactNoteSettingTab.ts`](src/ContactNoteSettingTab.ts).
|
||||
- For per-view options surfaced through the `⋮` menu, edit `ContactsViewOptions` and `DEFAULT_VIEW_OPTIONS` in [`views/ContactsView.ts`](src/views/ContactsView.ts).
|
||||
- For sidebar panel view options, edit `ContactsViewOptions` and `DEFAULT_VIEW_OPTIONS` in [`views/ContactsView.ts`](src/views/ContactsView.ts).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Never edit a migration step after it has shipped. Users who already ran the old version of the step would silently desync from those who ran the new version. If a step needs correcting, write a *new* step that fixes the bad data forward.
|
||||
|
|
@ -153,7 +168,7 @@ Currently, the project relies on manual testing within an Obsidian vault. When m
|
|||
- The plugin loads without errors (check the developer console with `Ctrl+Shift+I` (Windows) or `Command+Option+I` (macOS)).
|
||||
- Existing contact notes still render correctly.
|
||||
- Configuration persist across reloads.
|
||||
- The Contacts view updates correctly when notes are added, modified, or deleted.
|
||||
- The Contacts view in a panel or base updates correctly when notes are added, modified, or deleted.
|
||||
- A Contacts base view (`.base` file using the plugin's view) renders entries, responds to its `Display` toggles, and the injected **New** button creates a contact in the configured contacts folder or with the configured tag in the vault's root.
|
||||
- Both folder-based and tag-based contact identification work.
|
||||
- Auto-rename collision handling: creating a second contact with the same `First [Middle] Last` produces `First [Middle] Last 1.md` and surfaces a notice.
|
||||
|
|
@ -206,5 +221,6 @@ If the workflows are unavailable, the release can be performed manually:
|
|||
|
||||
- [Obsidian Plugin Developer Docs](https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin)
|
||||
- [Obsidian API reference](https://github.com/obsidianmd/obsidian-api)
|
||||
- [Obsidian Help Documents](https://obsidian.md/help/)
|
||||
- [Obsidian Sample Plugin](https://github.com/obsidianmd/obsidian-sample-plugin)
|
||||
- [eslint-plugin-obsidianmd](https://github.com/obsidianmd/eslint-plugin)
|
||||
|
|
|
|||
141
README.md
141
README.md
|
|
@ -8,18 +8,26 @@
|
|||
<img src="https://img.shields.io/github/downloads/Jalad25/contact-note/total" alt="Assets downloaded">
|
||||
</p>
|
||||
|
||||
> [!WARNING]
|
||||
> **Breaking change in 2.0.0:** The `email` and `phone` frontmatter
|
||||
> properties were renamed to `emails` and `phoneNumbers`. Existing
|
||||
> contact notes using the old keys will continue to load but their
|
||||
> emails and phone numbers will not appear on contact cards until
|
||||
> renamed. Update each note's frontmatter (or run a vault-wide
|
||||
> find-and-replace) to migrate.
|
||||
|
||||
# Contact Note
|
||||
|
||||
An [Obsidian](https://obsidian.md/) plugin that turns frontmatter in notes designated as contacts into visual contact cards. Browse them with a built-in searchable, filterable view or surface them through an Obsidian Bases view that adds sorting, grouping, and extensive filtering.
|
||||
An [Obsidian](https://obsidian.md/) plugin that turns frontmatter in notes designated as contacts into visual contact cards. Browse them with a built-in searchable, filterable view or surface them through an Obsidian Bases view that adds sorting, grouping, and extensive filtering and search.
|
||||
|
||||

|
||||
<video src="assets/Preview.mp4" controls></video>
|
||||
|
||||
## Features
|
||||
|
||||
- **Contact Card in Note** — Renders a contact card in reading mode for any note identified as a contact.
|
||||
- **Contacts View** — A dedicated sidebar view that lists all contact notes with search, alphabet filter, display options, and a filter limiting the view to entries that match one or more frontmatter conditions.
|
||||
- **Contacts in Bases** — Render the same contact cards inside an Obsidian `.base` file, with grouping, sorting, searching, and filtering driven by Bases.
|
||||
- **Contact Note Template** — Create new contacts from the contacts view or a contacts base with an auto-generated frontmatter template.
|
||||
- **Contacts View in a dedicated panel** — A dedicated sidebar view that lists all contact notes with search, alphabet filter, display options, and a filter limiting the view to entries that match one or more frontmatter conditions.
|
||||
- **Contacts View in an Obsidian Base** — Render the same contact cards inside an Obsidian `.base` file, with grouping, sorting, searching, and filtering driven by Bases.
|
||||
- **Contact Note Template** — Create new contacts from the contacts panel or a base view with an auto-generated frontmatter template.
|
||||
- **Contact File Naming Enforcement** — Enforces file naming in `First [Middle] Last` format automatically. Duplicate names are disambiguated with a numeric suffix.
|
||||
- **Multiple Values for Certain Frontmatter Properties** — Supports multiple emails, phone numbers, and social media profiles per contact.
|
||||
- **Contact Note Identification** — Contacts identified by folder or tag.
|
||||
|
|
@ -69,7 +77,7 @@ A note is treated as a contact note in one of two ways, configured in settings:
|
|||
|
||||
To manually create a new contact, create a new note with at least the `firstName` and `lastName` frontmatter properties in either the path of contact notes or with the contact tag specified in the plugin settings. For a list of all frontmatter properties recognized by the plugin, see [Contact Note Frontmatter Reference](#contact-note-frontmatter-reference) below.
|
||||
|
||||
To create a new contact using the plugin's template, select the **user-plus** button in the contacts view header or the **New** button in a contacts base to open the new contact dialog. Enter a first and last name and select **Create**.
|
||||
To create a new contact using the plugin's template, select the **user-plus** button in the panel header or the **New** button in the base view to open the new contact dialog. Enter a first and last name and select **Create**.
|
||||
|
||||
A new note will be created with a pre-populated frontmatter template and opened automatically.
|
||||
|
||||
|
|
@ -78,14 +86,14 @@ A new note will be created with a pre-populated frontmatter template and opened
|
|||
>
|
||||
> If a file with the target name already exists, the new contact is renamed to `First [Middle] Last 1` (incrementing the suffix until the name is unique) and a notice is shown so you know the disambiguation happened.
|
||||
|
||||
### Browsing Contact Notes
|
||||
### Browsing Contacts
|
||||
|
||||
There are two ways to browse contact notes in the vault:
|
||||
There are two ways to browse contacts in the vault:
|
||||
|
||||
- **[Contacts view](#contacts-view)** — A dedicated sidebar leaf that lists every contact note with search, alphabet filter, and a configurable view filter.
|
||||
- **[Contacts base view](#contacts-base-view)** — A custom view type for Obsidian Bases that renders the same contact cards inside a `.base` file and adds Bases' searching, grouping, sorting, and filtering on top.
|
||||
- **[Sidebar Panel](#contacts-view-in-a-panel)** — A dedicated sidebar panel that lists every contact's card with search, alphabet filter, and a configurable view filter.
|
||||
- **[Bases View](#contacts-view-in-a-base)** — A custom view type for Obsidian Bases that lists every contact's card inside a `.base` file and adds Bases' searching, grouping, sorting, and filtering on top.
|
||||
|
||||
Both views render the same contact cards and offer the same display options (`Condensed`, `Show contact details`, `Last name first`).
|
||||
Both surfaces render the same contact cards and offer the same display options (`Condensed`, `Show contact details`, `Last name first`).
|
||||
|
||||
## Contact Note Frontmatter Reference
|
||||
|
||||
|
|
@ -99,10 +107,10 @@ All fields are optional except `firstName` and `lastName`.
|
|||
| `displayName` | string | Overrides the resolved display name everywhere if set. |
|
||||
| `title` | string | Job title or role. |
|
||||
| `company` | string | Company or organization name. |
|
||||
| `email` | string or list | One or more email addresses. |
|
||||
| `phone` | string or list | One or more phone numbers. |
|
||||
| `emails` | string or list | One or more email addresses. |
|
||||
| `phoneNumbers` | string or list | One or more phone numbers. |
|
||||
| `photo` | string | Vault path to a photo file (e.g. `Attachments/jane.jpg`). |
|
||||
| `aliases` | list | Obsidian aliases for the note. Pre-populated with `firstName` on creation. Not used directly by plugin. |
|
||||
| `aliases` | list | Obsidian aliases for the note. Pre-populated with `firstName` on creation when using the new contact dialog. Not used directly by plugin. |
|
||||
| `socials` | list | List of social media handles. See [Socials](#socials) below. |
|
||||
|
||||
**Example:**
|
||||
|
|
@ -115,9 +123,9 @@ lastName: La Forge
|
|||
displayName: Papa
|
||||
company: Geordi Bytes LLC.
|
||||
title: Professional Cutie
|
||||
email:
|
||||
emails:
|
||||
- geordi@notarealemail.com
|
||||
phone:
|
||||
phoneNumbers:
|
||||
- 123-456-7890
|
||||
photo: Attachments/Geordi.jpg
|
||||
aliases:
|
||||
|
|
@ -177,11 +185,9 @@ The display name is resolved in the following order:
|
|||
|
||||
If the `photo` path is not set, the contact card displays a default person icon in its place.
|
||||
|
||||
## Contacts View
|
||||
## Contacts View in a Panel
|
||||
|
||||

|
||||
|
||||
Open the contacts view from the **book-user** ribbon icon or the **Open contacts view** command.
|
||||
Open the contacts view in a panel by selecting the **book-user** ribbon icon or running the **Open contacts view in panel** command.
|
||||
|
||||
### Header Buttons
|
||||
|
||||
|
|
@ -191,6 +197,10 @@ Open the contacts view from the **book-user** ribbon icon or the **Open contacts
|
|||
| **Search contacts** | Toggle the search bar (see [Search](#search)). |
|
||||
| **View options** (`⋮`) | Open the view options menu. |
|
||||
|
||||
### Search
|
||||
|
||||
Select the **search** icon in the header to show the search bar. The search filters by first name, last name, middle name, and display name.
|
||||
|
||||
### View Options Menu
|
||||
|
||||
The `⋮` menu contains options that affect the current view only and are persisted across sessions:
|
||||
|
|
@ -202,17 +212,9 @@ The `⋮` menu contains options that affect the current view only and are persis
|
|||
| Last name first | Overrides the resolved display name and displays names as `Last, First Middle`. Does not affect the contact card within a note. |
|
||||
| Edit view filter… | Open the [View Filter](#view-filter) editor. |
|
||||
|
||||
### Search
|
||||
|
||||
Select the **search** icon in the header to show the search bar. The search filters by first name, last name, middle name, and display name.
|
||||
|
||||
### Alphabet Filter
|
||||
|
||||
Select any letter in the alphabet bar to filter contacts whose last name starts with that letter. Select the same letter again to clear the filter.
|
||||
|
||||
### View Filter
|
||||
|
||||
Add one or more filter conditions to limit which contacts appear in the contacts view. All conditions must match for a contact to be shown (AND logic). Open the editor from **Edit view filter…** in the view's `⋮` menu.
|
||||
Add one or more filter conditions to limit which contacts appear in the panel view. All conditions must match for a contact to be shown (AND logic). Open the editor from **Edit view filter…** in the view's `⋮` menu.
|
||||
|
||||
Each condition targets a frontmatter property by key and supports the following operators:
|
||||
|
||||
|
|
@ -226,19 +228,33 @@ Each condition targets a frontmatter property by key and supports the following
|
|||
|
||||
Filters apply to any frontmatter property, including custom fields not used by the plugin.
|
||||
|
||||
## Contacts Base View
|
||||
### Alphabet Filter
|
||||
|
||||
The Contacts base view is a plugin Bases view that can be used within an Obsidian `.base` file to render the same contact cards as the Contacts view, while letting you use Bases' filtering, searching, grouping, and sorting.
|
||||
Select any letter in the alphabet bar to filter contacts whose last name starts with that letter. Select the same letter again to clear the filter.
|
||||
|
||||
### Creating a Contacts Base View
|
||||
## Contacts View in a Base
|
||||
|
||||
Select the **book-plus** ribbon icon, or run the **Create new base with Contacts base view** command, to generate a new `.base` file pre-configured to display contact notes through the plugin's Bases view. The base view includes a filter that limits entries to your configured contact folder or tag, sorts by last name, and is opened automatically once created.
|
||||
There are two commands for adding a contacts view to a base:
|
||||
|
||||
The file name and the in-base view name come from the **Default base file and view name** setting; the file is placed in the folder set by **New base folder path**. See [Settings Reference](#settings-reference) below.
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| **Create new base with contacts view** | Creates a new `.base` file pre-configured with the `isContact` formula along with a filter using the formula to filter the contacts view to your configured contact folder or tag, sorts by last name, pre-selects properties of `firstName`, `lastName`, `middleName`, and `displayName` for searching, and seeds the default settings for the Display toggles. The file is placed in the folder set by **New base folder path** (see [Settings Reference](#settings-reference)) and is opened automatically. |
|
||||
| **Add contacts view to base** | Available only when the active file is a `.base` file. Appends a new contacts view to the existing base and adds the `isContact` formula if it is not already present. |
|
||||
|
||||
The New button opens the same New contact dialog as the Contacts view, ensuring new files land in the correct folder with the correct frontmatter shape.
|
||||
Both commands prompt for a view name (and a base file name in the case of **Create new base with contacts view**). If the chosen file name already exists in the target folder, a numeric suffix is appended and a notice is shown.
|
||||
|
||||
The base view exposes three toggles in the Bases options panel under **Display**:
|
||||
When **Add contacts view to base** is run on a base whose existing `isContact` formula does not match your current contact identification settings, the existing formula is left untouched and a notice is shown so you can update it by hand.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Creating a Contacts view in a `.base` file without using the listed commands will result in a view without the pre-configured sorting, formulas, and filtering.
|
||||
|
||||
### `+ New` Button
|
||||
|
||||
The New button opens the same New contact dialog as the panel view, ensuring new files land in the correct folder with the correct frontmatter shape.
|
||||
|
||||
### Display Options
|
||||
|
||||
The contacts view exposes three toggles in the Bases options panel under **Display**:
|
||||
|
||||
| Option | Description |
|
||||
|---|---|
|
||||
|
|
@ -246,21 +262,25 @@ The base view exposes three toggles in the Bases options panel under **Display**
|
|||
| Show contact details | Expand each card to include emails, phone numbers, and socials. Hidden while **Condensed** is enabled. |
|
||||
| Last name first | Overrides the resolved display name and displays names as `Last, First Middle`. Does not affect the contact card within a note. |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Though the Contacts view looks simliar to the Obsidian Bases built-in Cards View, the Contacts View will not change what it displays in the card based off the Properties selected (or `order` set in the raw `.base` file). The card will only change its display based on the display options set.
|
||||
|
||||
### Default New Base Configuration
|
||||
|
||||
The **Create new base with Contacts base view** command writes a `.base` file with the following structure:
|
||||
The **Create new base with contacts view** command writes a `.base` file with the following structure:
|
||||
|
||||
```yaml
|
||||
formulas:
|
||||
isContact: 'file.inFolder("Contacts")' # or file.hasTag("contact"), depending on your settings at the time the base was created
|
||||
filters:
|
||||
and:
|
||||
- formula.isContact
|
||||
isContact: "file.inFolder(\"...\")" #Or file.hasTag(\"...\") depending on settings at time of creation
|
||||
views:
|
||||
- type: contact-note-list
|
||||
name: Contacts
|
||||
order:
|
||||
filters:
|
||||
and:
|
||||
- formula.isContact
|
||||
order: #Properties in the view
|
||||
- note.firstName
|
||||
- note.middleName
|
||||
- note.lastName
|
||||
- note.displayName
|
||||
sort:
|
||||
|
|
@ -271,16 +291,18 @@ views:
|
|||
showDetails: false
|
||||
```
|
||||
|
||||
If your settings identify contacts by tag instead of folder, the formula is written as `file.hasTag("...")` (using your configured tag).
|
||||
|
||||
Each piece serves a purpose:
|
||||
|
||||
| Section | What it does |
|
||||
|---|---|
|
||||
| `formulas.isContact` | Captures your **Contact File Identification** setting at the moment the base was created — `file.inFolder("…")` if you were on folder mode, `file.hasTag("…")` if you were on tag mode. The expression is written into the file as a literal string and **does not** track future changes to the setting. If you switch identification mode or rename your contacts folder/tag, edit the formula in the base by hand or run **Create new base with Contacts base view** again to generate a fresh file. |
|
||||
| `filters.and` | Applies `isContact` as a filter to all views in the base. |
|
||||
| `formulas.isContact` | Captures your **Contact File Identification** setting at the moment the base was created. `file.inFolder("…")` if you were in folder mode, `file.hasTag("…")` if you were in tag mode. The expression is written into the file as a literal string and **does not** track future changes to the settings. If you switch identification mode or rename your contacts folder/tag, edit the formula in the base by hand or run **Create new base with contacts view** again to generate a fresh file. |
|
||||
| `views[].type: contact-note-list` | Tells Bases to render this view through Contact Note's renderer. Without this, Bases falls back to its built-in table or card view and the plugin's contact cards are not used. |
|
||||
| `views[].name` | The label shown in the base's view tabs. |
|
||||
| `views[].order` | The properties shown in the Bases property picker. The plugin ignores this for rendering, but Bases uses it for grouping and search. |
|
||||
| `views[].sort` | The default sort order for the base view. |
|
||||
| `views[].filters.and` | Applies `isContact` as a filter to this view. |
|
||||
| `views[].order` | The properties shown in the Bases property picker. The plugin ignores this for rendering, but Bases uses it for search. |
|
||||
| `views[].sort` | The default sort order for the view. |
|
||||
| `views[].condensed` / `lastNameFirst` / `showDetails` | The starting values for the **Display** toggles described above. |
|
||||
|
||||
### Editing a Base Freely
|
||||
|
|
@ -290,24 +312,24 @@ A `.base` file is plain YAML, and the plugin treats whatever Bases passes it as
|
|||
A few things to know before you edit:
|
||||
|
||||
- **`type: contact-note-list` is required.** A view without this exact value renders through Bases' built-in views, not Contact Note's. The plugin's three Display toggles, the New button, and the contact card layout only exist on views with this type.
|
||||
- **`formulas.isContact` is a snapshot, not a live link.** It's written into the file once when the base is created and is never touched again by the plugin. You can edit it freely to broaden or narrow the filter, but if you later change the **Identify contacts by folder** setting (or rename the folder/tag) the formula will *not* update on its own. You'll need to edit it by hand or recreate the base.
|
||||
- **Removing `formulas.isContact` or `filters.and` removes the contact-note guarantee.** If the filter no longer pins the base to your contacts folder or tag, non-contact notes can leak into the view, and the cards will render with whatever frontmatter happens to be present (often empty placeholders).
|
||||
- **`sort` can be replaced freely.** Sort by company, by `displayName`, by any frontmatter property — Bases applies the sort before handing entries to the view.
|
||||
- **Removing `order` (properties) is harmless visually but changes how Bases' searches.** The plugin renders cards regardless of the `order` list, but Bases' built-in search bar only matches against properties that are listed there. Keep `note.firstName`, `note.middleName`, `note.lastName`, and `note.displayName` in `order` to keep search working the same way it does in the Contacts view.
|
||||
- **You can have multiple views in one base.** Add another entry under `views:` (for example a Bases table view) to switch between contact cards and a tabular layout in the same file. Each view stores its own sort, group, properties, and filter. However, creating a new Contacts base view within an existing base without the plugin's commands will not apply the default `sort`, `order` (properties), `formulas` (if in base not created by plugin), or `filters` (if in base not created by plugin).
|
||||
- **`formulas.isContact` is a snapshot, not a live link.** It's written into the file once when the base is created (or when **Add contacts view to base** is run on a base that doesn't already have it) and is never touched again by the plugin. You can edit it freely to broaden or narrow the filter, but if you later change the **Identify contacts by folder** setting (or rename the folder/tag) the formula will *not* update on its own. You'll need to edit it by hand or recreate the base.
|
||||
- **Removing `formulas.isContact` or the view's `filters.and` removes the contact-note guarantee.** If the filter no longer pins the view to your contacts folder or tag, non-contact notes can leak into the view, and the cards will render with whatever frontmatter happens to be present (often empty placeholders).
|
||||
- **`sort` can be replaced freely.** Sort by company, by `displayName`, by any frontmatter property. Bases applies the sort before handing entries to the view.
|
||||
- **Removing `order` (properties) is harmless visually but changes how Bases searches.** The plugin renders cards regardless of the `order` list, but Bases' built-in search bar only matches against properties that are listed there. Keep `note.firstName`, `note.middleName`, `note.lastName`, and `note.displayName` in `order` to keep search working the same way it does in the panel view.
|
||||
- **You can have multiple views in one base.** Add another entry under `views:` (for example a Bases table view) to switch between contact cards and a tabular layout in the same file. Each view stores its own sort, group, properties, and filter. Adding a contacts view by hand (rather than via **Add contacts view to base**) will not apply the default `sort`, `order`, or per-view `filters`.
|
||||
- **Search and the contact card body don't always agree.** Bases' search bar matches against the properties in `order`, not the rendered card. To add more properties to search off of, select them in the properties dropdown or add them to the `order` list.
|
||||
|
||||
### Creating a Base Without the Plugin
|
||||
### Creating a base without the plugin
|
||||
|
||||
You can also create a `.base` file the normal Obsidian way (right-click or long-press on mobile in the file tree → **New base**, or use the built-in command). When you do, none of the plugin's defaults are present. The new base ships with Bases' generic table view, no filter, no sort, and no display toggles. Specifically you will be missing:
|
||||
|
||||
- The `isContact` formula and the filter that limits entries to your configured contact folder or tag. Every note in the vault is shown until you add one.
|
||||
- The `isContact` formula and the per-view filter that limits entries to your configured contact folder or tag. Every note in the vault is shown until you add one.
|
||||
- The default sort by last name (entries are listed in Bases' default order, usually file name).
|
||||
- The `order` list that wires Bases' search to first/middle/last/display name fields.
|
||||
- The `condensed: true` / `lastNameFirst: true` / `showDetails: false` defaults for the contact card view.
|
||||
- The plugin's contact card view itself. Until you change `type:` to `contact-note-list`, the file renders with Bases' generic table or card view.
|
||||
|
||||
To convert a hand-built base view into a contacts base view, set the view type to `contact-note-list`, add `formulas.isContact` and `filters.and` as shown above, and copy whatever subset of `order` / `sort` / display toggles you want. The **Create new base with Contacts base view** command exists precisely so you don't have to do this by hand.
|
||||
The simplest way to add a contacts view to an existing base is to run **Add contacts view to base** while the base is the active file. It appends a fully configured contacts view and adds the `isContact` formula if it's missing.
|
||||
|
||||
## Settings Reference
|
||||
|
||||
|
|
@ -316,24 +338,25 @@ To convert a hand-built base view into a contacts base view, set the view type t
|
|||
| Setting | Description | Default |
|
||||
|---|---|---|
|
||||
| Identify contacts by folder | When enabled, notes inside the specified folder are treated as contacts. When disabled, notes with the specified tag are used instead. | Enabled |
|
||||
| Contacts folder path | Path to the contacts folder, relative to the vault root. The match is recursive: every note in this folder and in any of its subfolders is treated as a contact. | `Contacts` |
|
||||
| Contacts folder path | Path to the contacts folder, relative to the vault root. The match is recursive: every note in this folder and in any of its subfolders is treated as a contact. Cannot be set to the vault root. | `Contacts` |
|
||||
| Contact tag | Tag used to identify contact notes (without the `#`). Only shown when folder mode is disabled. | `contact` |
|
||||
|
||||
### Contacts View
|
||||
### Contacts View in a Panel
|
||||
|
||||
| Setting | Description | Default |
|
||||
|---|---|---|
|
||||
| View name | Name shown at the top of the contacts view. | `Contacts` |
|
||||
| View name | Name shown at the top of the panel view. | `Contacts` |
|
||||
|
||||
> Display options (`Condensed`, `Show contact details`, `Last name first`) and the view filter editor are managed through the view's `⋮` menu. See [View Options Menu](#view-options-menu).
|
||||
|
||||
### Contacts Base View
|
||||
### Contacts View in a Base
|
||||
|
||||
| Setting | Description | Default |
|
||||
|---|---|---|
|
||||
| Default base file and view name | Name written into new contacts bases created by the plugin. Used both as the file name and as the view's display name inside the base. | `Contacts` |
|
||||
| New base folder path | Folder where new contacts bases are created, relative to the vault root. Leave empty to place them in the vault root. | *(empty)* |
|
||||
|
||||
> The base file name and the in-base view name are entered in the dialog opened by **Create new base with contacts view** or **Add contacts view to base**, not in settings.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions of all kinds are welcome!
|
||||
|
|
|
|||
BIN
assets/Preview.mp4
Normal file
BIN
assets/Preview.mp4
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Contact Note",
|
||||
"version": "1.1.2",
|
||||
"minAppVersion": "1.12.7",
|
||||
"description": "Turn frontmatter in notes designated as contacts into visual contact cards. Browse them with a built-in searchable, filterable view or surface them through an Obsidian Bases view that adds sorting, grouping, and extensive filtering.",
|
||||
"description": "Turn frontmatter in notes designated as contacts into visual contact cards. Browse them with a built-in searchable, filterable view or surface them through an Obsidian Bases view that adds sorting, grouping, and extensive filtering and searching.",
|
||||
"author": "Jalad",
|
||||
"authorUrl": "https://jalad.ncc-1701.enterprises",
|
||||
"isDesktopOnly": false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "contact-note",
|
||||
"version": "1.1.2",
|
||||
"description": "Turn frontmatter in notes designated as contacts into visual contact cards. Browse them with a built-in searchable, filterable view or surface them through an Obsidian Bases view that adds sorting, grouping, and extensive filtering.",
|
||||
"description": "Turn frontmatter in notes designated as contacts into visual contact cards. Browse them with a built-in searchable, filterable view or surface them through an Obsidian Bases view that adds sorting, grouping, and extensive filtering and searching.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ export function buildContactCard(
|
|||
|
||||
// Emails
|
||||
if (contact.emails.length > 0) {
|
||||
const emailsEl = detailsEl.createDiv({ cls: `${pluginId}-emails` });
|
||||
const emailsEl = detailsEl.createDiv({ cls: `${pluginId}-card-emails` });
|
||||
for (const email of contact.emails) {
|
||||
const row = emailsEl.createDiv({ cls: `${pluginId}-card-detail-row` });
|
||||
const emailIcon = row.createSpan({ cls: `${pluginId}-card-detail-icon` });
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export class ContactNoteSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
/* Contacts View Settings */
|
||||
new Setting(containerEl).setName("Contacts view in panel").setHeading();
|
||||
new Setting(containerEl).setName("Contacts view in a panel").setHeading();
|
||||
|
||||
// viewName
|
||||
new Setting(containerEl)
|
||||
|
|
|
|||
69
styles.css
69
styles.css
|
|
@ -18,18 +18,17 @@
|
|||
|
||||
/* Validation Error */
|
||||
.contact-note-card-error {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--color-red);
|
||||
border-radius: var(--radius-m, 8px);
|
||||
background-color: var(--background-modifier-error);
|
||||
color: var(--text-normal);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.contact-note-card-error p {
|
||||
margin: 6px 0 0;
|
||||
font-size: var(--font-ui-small);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* Photo */
|
||||
|
|
@ -61,7 +60,6 @@
|
|||
.contact-note-card-name {
|
||||
font-size: 1.7rem;
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-normal);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +74,6 @@
|
|||
.contact-note-card-company {
|
||||
font-size: 1.1rem;
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--text-normal);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +85,6 @@
|
|||
gap: 6px 32px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.contact-note-card-detail-row {
|
||||
|
|
@ -111,7 +107,6 @@
|
|||
|
||||
.contact-note-card-detail-value {
|
||||
font-size: var(--font-ui-small);
|
||||
color: var(--text-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
@ -119,9 +114,10 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Emails and Phones */
|
||||
/* Socials, Emails, and Phone Numbers */
|
||||
.contact-note-card-emails,
|
||||
.contact-note-card-phones {
|
||||
.contact-note-card-phones,
|
||||
.contact-note-card-socials {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
|
@ -132,14 +128,8 @@
|
|||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* Contact Note */
|
||||
.contact-note .contact-note-card {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
/* Bases and View */
|
||||
|
||||
/* Contact Card Normal */
|
||||
.contact-note-view .contact-note-card:hover,
|
||||
.contact-note-bases-view .contact-note-card:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
|
|
@ -147,6 +137,7 @@
|
|||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
/* Contact Card Normal */
|
||||
.contact-note-view .contact-note-card,
|
||||
.contact-note-bases-view .contact-note-card {
|
||||
gap: 12px;
|
||||
|
|
@ -216,11 +207,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.contact-note-view-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.contact-note-view-header-btns {
|
||||
|
|
@ -230,30 +216,11 @@
|
|||
}
|
||||
|
||||
.contact-note-view-header-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s, 4px);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-note-view-header-btn:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.contact-note-view-header-btn.is-active {
|
||||
background: var(--interactive-accent);
|
||||
border-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.contact-note-view-header-btn svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
|
@ -269,33 +236,12 @@
|
|||
}
|
||||
|
||||
.contact-note-view-alpha-btn {
|
||||
height: 24px;
|
||||
padding: 0 4px;
|
||||
font-size: var(--font-ui-smaller);
|
||||
border-radius: var(--radius-s, 4px);
|
||||
}
|
||||
|
||||
.contact-note-view-alpha-btn.is-active {
|
||||
background: var(--interactive-accent) !important;
|
||||
color: var(--text-on-accent) !important;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
.contact-note-view-search {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s, 4px);
|
||||
background: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
font-size: var(--font-ui-small);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.contact-note-view-search:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
|
@ -338,6 +284,11 @@
|
|||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.contact-note-bases-view-new-btn svg {
|
||||
color: var(--text-muted);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.contact-note-bases-view-new-btn .text-button-label {
|
||||
padding-inline-start: 5px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue