From 5a76df58564e7ddba474cd424e2e7dfdecc3f7ff Mon Sep 17 00:00:00 2001 From: Aleksejs Kovalovs Date: Sun, 21 Dec 2025 04:38:41 +0200 Subject: [PATCH] Add customizable naming strategy for filenames as Last First. Implements #16 --- README.md | 2 + src/__tests__/core/ContactNoteWriter.test.ts | 5 ++ .../core/ContactNoteWriterProcess.test.ts | 2 + src/config/index.ts | 1 + src/i18n/translations.ts | 11 +++ src/main.ts | 1 + src/plugin/settings.ts | 16 ++++ src/services/ContactNoteWriter.ts | 75 +++++++++++++++---- src/types/ContactNoteConfig.ts | 1 + src/types/Settings.ts | 3 + 10 files changed, 103 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c8d235d..7a1ca69 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Each contact becomes a separate note with YAML frontmatter for metadata and free - 📛 Filename prefix support (e.g. `p Ivan Ivanov.md`) +- 🏷️ Customizable naming strategy for filenames as last name first (e.g. `p Ivanov Ivan.md`) + - 🧩 Multiple naming strategies for frontmatter keys: - **Default**: Customizable prefix (e.g., `s_name`, `s_email_2`). - **VCF (vCard)**: Fully compatible with the [VCF Contacts](https://github.com/broekema41/obsidian-vcf-contacts) plugin. diff --git a/src/__tests__/core/ContactNoteWriter.test.ts b/src/__tests__/core/ContactNoteWriter.test.ts index 733bfb6..8620f70 100644 --- a/src/__tests__/core/ContactNoteWriter.test.ts +++ b/src/__tests__/core/ContactNoteWriter.test.ts @@ -125,6 +125,7 @@ describe('ContactNoteWriter', () => { trackSyncTime: true, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; await contactNoteWriter.writeNotesForContacts( @@ -174,6 +175,7 @@ describe('ContactNoteWriter', () => { trackSyncTime: true, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; await contactNoteWriter.writeNotesForContacts( @@ -222,6 +224,7 @@ describe('ContactNoteWriter', () => { trackSyncTime: true, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; await contactNoteWriter.writeNotesForContacts( @@ -264,6 +267,7 @@ describe('ContactNoteWriter', () => { trackSyncTime: true, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; await contactNoteWriter.writeNotesForContacts( @@ -304,6 +308,7 @@ describe('ContactNoteWriter', () => { trackSyncTime: true, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; await contactNoteWriter.writeNotesForContacts( diff --git a/src/__tests__/core/ContactNoteWriterProcess.test.ts b/src/__tests__/core/ContactNoteWriterProcess.test.ts index 1e630ad..083627e 100644 --- a/src/__tests__/core/ContactNoteWriterProcess.test.ts +++ b/src/__tests__/core/ContactNoteWriterProcess.test.ts @@ -115,6 +115,7 @@ describe('ContactNoteWriterUpdate', () => { trackSyncTime: true, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; await contactNoteWriter.writeNotesForContacts( @@ -160,6 +161,7 @@ describe('ContactNoteWriterUpdate', () => { trackSyncTime: true, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; await contactNoteWriter.writeNotesForContacts( diff --git a/src/config/index.ts b/src/config/index.ts index 41fdaf3..f26fcd9 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -37,4 +37,5 @@ export const DEFAULT_SETTINGS: ContactSyncSettings = { organizationAsLink: false, renameFiles: false, namingStrategy: NamingStrategy.Default, + lastFirst: false, }; diff --git a/src/i18n/translations.ts b/src/i18n/translations.ts index bf03cf6..70a43d6 100644 --- a/src/i18n/translations.ts +++ b/src/i18n/translations.ts @@ -85,6 +85,10 @@ export const ru = { File: 'Файл', 'Contact ID': 'ID контакта', 'Audit complete. Report saved to': 'Аудит завершен. Отчет сохранен в', + 'Format file names as Last First': + 'Форматировать имена файлов как Фамилия Имя', + 'If enabled, contact file names will be formatted as Last First instead of First Last': + 'Если включено, имена файлов контактов будут форматироваться как Фамилия Имя вместо Имя Фамилия', }; /** @@ -174,6 +178,10 @@ export const lv = { File: 'Fails', 'Contact ID': 'Kontakta ID', 'Audit complete. Report saved to': 'Audits pabeigts. Pārskats saglabāts', + 'Format file names as Last First': + 'Formatēt failu nosaukumus kā Uzvārds Vārds', + 'If enabled, contact file names will be formatted as Last First instead of First Last': + 'Ja iespējots, kontaktu failu nosaukumi tiks formatēti kā Uzvārds Vārds, nevis Vārds Uzvārds', }; /** @@ -262,4 +270,7 @@ export const en = { File: 'File', 'Contact ID': 'Contact ID', 'Audit complete. Report saved to': 'Audit complete. Report saved to', + 'Format file names as Last First': 'Format file names as Last First', + 'If enabled, contact file names will be formatted as Last First instead of First Last': + 'If enabled, contact file names will be formatted as Last First instead of First Last', }; diff --git a/src/main.ts b/src/main.ts index 9c7d8cc..a5b2459 100644 --- a/src/main.ts +++ b/src/main.ts @@ -212,6 +212,7 @@ export default class GoogleContactsSyncPlugin extends Plugin { trackSyncTime: this.settings.trackSyncTime, renameFiles: this.settings.renameFiles, namingStrategy: this.settings.namingStrategy, + lastFirst: this.settings.lastFirst, }; } diff --git a/src/plugin/settings.ts b/src/plugin/settings.ts index 6dbbddd..a87ffd3 100644 --- a/src/plugin/settings.ts +++ b/src/plugin/settings.ts @@ -86,6 +86,22 @@ export class ContactSyncSettingTab extends PluginSettingTab { }) ); + new Setting(containerEl) + .setName(t('Format file names as Last First')) + .setDesc( + t( + 'If enabled, contact file names will be formatted as Last First instead of First Last' + ) + ) + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.lastFirst) + .onChange(async (value) => { + this.plugin.settings.lastFirst = value; + await this.plugin.saveSettings(); + }) + ); + new Setting(containerEl) .setName(t('Naming strategy')) .setDesc(t('Strategy to generate frontmatter keys from contact data')) diff --git a/src/services/ContactNoteWriter.ts b/src/services/ContactNoteWriter.ts index 0b36df9..d607f68 100644 --- a/src/services/ContactNoteWriter.ts +++ b/src/services/ContactNoteWriter.ts @@ -113,12 +113,13 @@ export class ContactNoteWriter { return; } - let filename = this.getFilename( - contact, - id, - config.folderPath, - config.prefix - ); + const filenameContext = { + folderPath: config.folderPath, + prefix: config.prefix, + lastFirst: config.lastFirst, + }; + + let filename = this.getFilename(contact, id, filenameContext); if (!filename) { return; } @@ -190,23 +191,69 @@ export class ContactNoteWriter { * @param id - The contact ID. * @param folderPath - The folder path where the note will be stored. * @param prefix - The prefix to use for the filename. + * @param lastFirst - Whether to format the name as Last First. * @returns The generated filename, or null if the name is not available. */ private getFilename( contact: GoogleContact, id: string, + context: Record + ): string | null { + const displayNameLastFirst = this.getLastFirstName( + contact, + context.lastFirst as boolean + ); + if (displayNameLastFirst) { + return this.getNormalizedFilename( + displayNameLastFirst, + context.folderPath as string, + context.prefix as string + ); + } + + const displayName = contact.names?.[0]?.displayName; + if (displayName) { + return this.getNormalizedFilename( + displayName, + context.folderPath as string, + context.prefix as string + ); + } + + const organizationName = contact.organizations?.[0]?.name; + if (organizationName) { + return this.getNormalizedFilename( + organizationName, + context.folderPath as string, + context.prefix as string + ); + } + + if (id) { + return this.getNormalizedFilename( + id, + context.folderPath as string, + context.prefix as string + ); + } + + return null; + } + + private getNormalizedFilename( + name: string, folderPath: string, prefix: string - ): string | null { - // Try displayName first, then organization name, then fall back to ID - const name = - contact.names?.[0]?.displayName ?? contact.organizations?.[0]?.name ?? id; - if (!name) { + ): string { + const safeName = name.replace(/[\\/:*?"<>|]/g, '_'); + return normalizePath(`${folderPath}/${prefix}${safeName}.md`); + } + + private getLastFirstName(contact: GoogleContact, lastFirst: boolean) { + if (!lastFirst) { return null; } - const safeName = name.replace(/[\\/:*?"<>|]/g, '_'); - const filename = normalizePath(`${folderPath}/${prefix}${safeName}.md`); - return filename; + return contact.names?.[0]?.displayNameLastFirst?.replace(/,/g, ''); } /** diff --git a/src/types/ContactNoteConfig.ts b/src/types/ContactNoteConfig.ts index 3e4ab43..c7fe059 100644 --- a/src/types/ContactNoteConfig.ts +++ b/src/types/ContactNoteConfig.ts @@ -10,4 +10,5 @@ export interface ContactNoteConfig { trackSyncTime: boolean; renameFiles: boolean; namingStrategy: NamingStrategy; + lastFirst: boolean; } diff --git a/src/types/Settings.ts b/src/types/Settings.ts index 516995a..7beced2 100644 --- a/src/types/Settings.ts +++ b/src/types/Settings.ts @@ -52,6 +52,9 @@ export interface ContactSyncSettings { /** Naming strategy to use for contact properties */ namingStrategy: NamingStrategy; + + /** Whether to format names as Last, First */ + lastFirst: boolean; } export enum NamingStrategy {