Add customizable naming strategy for filenames as Last First. Implements #16

This commit is contained in:
Aleksejs Kovalovs 2025-12-21 04:38:41 +02:00
parent ce40b97799
commit 5a76df5856
No known key found for this signature in database
GPG key ID: EBDE72B2FD0FD95E
10 changed files with 103 additions and 14 deletions

View file

@ -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.

View file

@ -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(

View file

@ -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(

View file

@ -37,4 +37,5 @@ export const DEFAULT_SETTINGS: ContactSyncSettings = {
organizationAsLink: false,
renameFiles: false,
namingStrategy: NamingStrategy.Default,
lastFirst: false,
};

View file

@ -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',
};

View file

@ -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,
};
}

View file

@ -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'))

View file

@ -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, string | boolean>
): 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, '');
}
/**

View file

@ -10,4 +10,5 @@ export interface ContactNoteConfig {
trackSyncTime: boolean;
renameFiles: boolean;
namingStrategy: NamingStrategy;
lastFirst: boolean;
}

View file

@ -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 {