From 7879cce3d083f1a7a57aa189bfede553ca8465f3 Mon Sep 17 00:00:00 2001 From: Roland Broekema Date: Thu, 17 Apr 2025 14:59:38 +0200 Subject: [PATCH] fixes for all review items to get a comunity registration listing --- LICENSE | 4 +++ README.md | 4 +++ manifest.json | 9 +++--- package.json | 4 +-- src/contacts/contactFrontmatter.ts | 7 ++--- src/contacts/contactMdTemplate.ts | 4 +-- src/contacts/vcard/vcardParse.ts | 33 ++++++++++++---------- src/settings/settings.ts | 2 -- src/ui/sidebar/components/ContactView.tsx | 4 +-- src/ui/sidebar/components/CopyableItem.tsx | 2 +- src/ui/sidebar/components/HeaderView.tsx | 12 ++++---- src/util/myScrollTo.ts | 6 ++-- versions.json | 7 +++-- 13 files changed, 53 insertions(+), 45 deletions(-) diff --git a/LICENSE b/LICENSE index 7f3b548..991a83e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2022 Vadim Beskrovnov +Copyright (c) 2025 Roland Broekema Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,3 +20,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Originally created by Vadim Beskrovnov (c) 2022. +Forked and completely rewritten by Roland Broekema (c) 2025. diff --git a/README.md b/README.md index dc0b9b3..5626ca8 100644 --- a/README.md +++ b/README.md @@ -210,3 +210,7 @@ For many of us, using Obsidian is like brewing a perfect cup of coffee—it kick ✔ **Add a dash of joy to your everyday workflow—because organization brings productivity and happy routines!** Wishing you an inspiring journey with your enhanced contact experience. ** Start using the plugin today and share your thoughts in the [community comments](https://github.com/broekema41/obsidian-vcf-contacts/discussions)!** + +🙏 Acknowledgements + +This plugin was originally inspired by and started as a fork of Vadim Beskrovnov’s Contacts plugin. You probably wouldn’t recognize it from the current codebase anymore—but still, huge thanks to Vadim for laying a solid foundation to explore, experiment with, and be inspired by. diff --git a/manifest.json b/manifest.json index c2d9ac1..f31531c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,11 @@ { "id": "vcf-contacts", "name": "VCF Contacts", - "version": "2.0.2", - "minAppVersion": "0.15.0", - "description": "Effortlessly manage, organize, and interact with your contacts inside Obsidian. Import, export, and structure vCard (VCF) files seamlessly while keeping all contact details accessible in your knowledge base. Includes click-to-call, right-click copy, structured metadata, and more!", + "version": "2.0.3", + "minAppVersion": "1.1.0", + "description": "Effortlessly manage, organize, and interact with your contacts. Import, export, and structure vCard (VCF) files seamlessly while keeping all contact details accessible in your knowledge base. Includes click-to-call, right-click copy, structured metadata, and more!", "author": "Roland Broekema", "authorUrl": "https://github.com/broekema41/", - "isDesktopOnly": true + "isDesktopOnly": true, + "forkedFrom": "https://github.com/vbeskrovnov/obsidian-contacts/" } diff --git a/package.json b/package.json index 1fdc3bd..8de696c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vcf-contacts", - "version": "2.0.2", + "version": "2.0.3", "description": "Effortlessly manage, organize, and interact with your contacts inside Obsidian. Import, export, and structure vCard (VCF) files seamlessly while keeping all contact details accessible in your knowledge base. Includes click-to-call, right-click copy, structured metadata, and more!", "main": "main.js", "scripts": { @@ -16,7 +16,6 @@ "author": "", "license": "MIT", "devDependencies": { - "@types/js-yaml": "^4.0.9", "@types/node": "^16.11.6", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", @@ -31,7 +30,6 @@ "typescript": "4.7.4" }, "dependencies": { - "js-yaml": "^4.1.0", "react": "^18.2.0", "react-dom": "^18.2.0" } diff --git a/src/contacts/contactFrontmatter.ts b/src/contacts/contactFrontmatter.ts index 05b2e70..334bf0b 100644 --- a/src/contacts/contactFrontmatter.ts +++ b/src/contacts/contactFrontmatter.ts @@ -1,5 +1,4 @@ -import * as yaml from 'js-yaml'; -import { TFile } from "obsidian"; +import { parseYaml, stringifyYaml, TFile } from "obsidian"; import { getApp } from "src/context/sharedAppContext"; export type Contact = { @@ -32,13 +31,13 @@ export async function updateFrontMatterValue(file: TFile, key: string, value: st let body = content; if (match) { - yamlObj = yaml.load(match[1]) || {}; + yamlObj = parseYaml(match[1]) || {}; body = content.slice(match[0].length); } yamlObj[key] = value; - const newFrontMatter = '---\n' + yaml.dump(yamlObj, { lineWidth: -1 }) + '---\n'; + const newFrontMatter = '---\n' + stringifyYaml(yamlObj) + '---\n'; const newContent = newFrontMatter + body; await app.vault.modify(file, newContent); diff --git a/src/contacts/contactMdTemplate.ts b/src/contacts/contactMdTemplate.ts index 37125cc..2d63585 100644 --- a/src/contacts/contactMdTemplate.ts +++ b/src/contacts/contactMdTemplate.ts @@ -1,4 +1,4 @@ -import * as yaml from "js-yaml"; +import { stringifyYaml } from "obsidian"; export function mdRender(record: Record, hashtags: string): string { const { NOTE, ...recordWithoutNote } = record; @@ -9,7 +9,7 @@ export function mdRender(record: Record, hashtags: string): string const tempTags= recordWithoutNote.CATEGORIES.split(',') additionalTags = `#${tempTags.join(' #')}` } - const yamlString = yaml.dump(recordWithoutNote, { lineWidth: -1 }); + const yamlString = stringifyYaml(recordWithoutNote); return `--- ${yamlString} diff --git a/src/contacts/vcard/vcardParse.ts b/src/contacts/vcard/vcardParse.ts index 7591e43..23d0ed2 100644 --- a/src/contacts/vcard/vcardParse.ts +++ b/src/contacts/vcard/vcardParse.ts @@ -3,24 +3,26 @@ import { ContactNameModal } from "src/ui/modals/contactNameModal"; import { convertToLatestVCFPhotoFormat } from "src/util/avatarActions"; function unfoldVCardLines(vCardData: string): string[] { - const lines = vCardData.split(/\r\n?/g); - const unfoldedLines: string[] = []; - let currentLine = ""; + // Normalize line endings to \n first (handles \r, \r\n) + const normalized = vCardData.replace(/\r\n?/g, '\n'); - for (const line of lines) { - if (/^\s/.test(line)) { - // Continuation of the previous line (line folding) - currentLine += line.trimStart(); - } else { - if (currentLine) unfoldedLines.push(currentLine); - currentLine = line; - } - } - if (currentLine) unfoldedLines.push(currentLine); + const lines = normalized.split('\n'); + const unfoldedLines: string[] = []; + let currentLine = ""; - return unfoldedLines; + for (const line of lines) { + if (/^[ \t]/.test(line)) { + // Line is a continuation (folded) + currentLine += line.slice(1); // remove the space or tab + } else { + if (currentLine) unfoldedLines.push(currentLine); + currentLine = line; + } + } + if (currentLine) unfoldedLines.push(currentLine); + + return unfoldedLines; } - /** * Extracts the base key from a vCard field name. * - If the key contains `[`, extract everything before it. @@ -241,6 +243,7 @@ export async function createEmptyVcard() { export async function parseVcard(vCardData: string) { const unfoldedLines = unfoldVCardLines(vCardData); const vCardObject: Record = {}; + for (const line of unfoldedLines) { const parsedLine = parseVCardLine(line); const indexedParsedLine = indexIfKeysExist(vCardObject, parsedLine) diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 1ba9357..66a37bc 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -30,8 +30,6 @@ export class ContactsSettingTab extends PluginSettingTab { containerEl.empty(); - containerEl.createEl('h2', { text: 'Settings for "Contacts" plugin.' }); - new Setting(containerEl) .setName('Contacts folder location') .setDesc('Files in this folder and all subfolders will be available as contacts') diff --git a/src/ui/sidebar/components/ContactView.tsx b/src/ui/sidebar/components/ContactView.tsx index 77faf2e..3567b4b 100644 --- a/src/ui/sidebar/components/ContactView.tsx +++ b/src/ui/sidebar/components/ContactView.tsx @@ -175,7 +175,7 @@ export const ContactView = (props: ContactProps) => { className={ "clickable-icon nav-action-button " } - aria-label="Process Avatar" + aria-label="Process avatar" ref={(element) => (buttons.current[0] = element)} onClick={() => props.processAvatar(contact)} > @@ -185,7 +185,7 @@ export const ContactView = (props: ContactProps) => { className={ "clickable-icon nav-action-button " } - aria-label="export VCF" + aria-label="Export vcf" ref={(element) => (buttons.current[1] = element)} onClick={() => props.exportVCF(contact.file)} > diff --git a/src/ui/sidebar/components/CopyableItem.tsx b/src/ui/sidebar/components/CopyableItem.tsx index f901957..3201bfb 100644 --- a/src/ui/sidebar/components/CopyableItem.tsx +++ b/src/ui/sidebar/components/CopyableItem.tsx @@ -19,7 +19,7 @@ export const CopyableItem: React.FC = ({ value, children }) = event.preventDefault(); const menu = new Menu(); menu.addItem((item) => - item.setTitle("Copy to Clipboard").setIcon("clipboard").onClick(handleCopy) + item.setTitle("Copy to clipboard").setIcon("clipboard").onClick(handleCopy) ); menu.showAtPosition({ x: event.pageX, y: event.pageY }); }; diff --git a/src/ui/sidebar/components/HeaderView.tsx b/src/ui/sidebar/components/HeaderView.tsx index ffa4042..76f7469 100644 --- a/src/ui/sidebar/components/HeaderView.tsx +++ b/src/ui/sidebar/components/HeaderView.tsx @@ -26,7 +26,7 @@ export const HeaderView = (props: HeaderProps) => { id="create-btn" className="clickable-icon nav-action-button" data-icon="contact" - aria-label="Create New Contact" + aria-label="Create new contact" ref={(element) => (buttons.current[1] = element)} onClick={props.onCreateContact} /> @@ -37,7 +37,7 @@ export const HeaderView = (props: HeaderProps) => { className={ "clickable-icon nav-action-button " } - aria-label="import VCF" + aria-label="Import vcf" ref={(element) => (buttons.current[2] = element)} onClick={ props.importVCF } /> @@ -47,7 +47,7 @@ export const HeaderView = (props: HeaderProps) => { className={ "clickable-icon nav-action-button " } - aria-label="export VCF" + aria-label="Export vcf" ref={(element) => (buttons.current[3] = element)} onClick={ props.exportAllVCF } /> @@ -60,7 +60,7 @@ export const HeaderView = (props: HeaderProps) => { "clickable-icon nav-action-button " + (props.sort === Sort.NAME && "is-active") } - aria-label="Sort By Name" + aria-label="Sort by name" ref={(element) => (buttons.current[4] = element)} onClick={() => props.onSortChange(Sort.NAME)} /> @@ -71,7 +71,7 @@ export const HeaderView = (props: HeaderProps) => { "clickable-icon nav-action-button " + (props.sort === Sort.BIRTHDAY && "is-active") } - aria-label="Sort By Birthday" + aria-label="Sort by birthday" ref={(element) => (buttons.current[6] = element)} onClick={() => props.onSortChange(Sort.BIRTHDAY)} /> @@ -82,7 +82,7 @@ export const HeaderView = (props: HeaderProps) => { "clickable-icon nav-action-button " + (props.sort === Sort.ORG && "is-active") } - aria-label="Sort By Organization" + aria-label="Sort by organization" ref={(element) => (buttons.current[7] = element)} onClick={() => props.onSortChange(Sort.ORG)} /> diff --git a/src/util/myScrollTo.ts b/src/util/myScrollTo.ts index 1e5d3b4..e056c76 100644 --- a/src/util/myScrollTo.ts +++ b/src/util/myScrollTo.ts @@ -1,11 +1,11 @@ import { MarkdownView, WorkspaceLeaf } from "obsidian"; import { fileId } from "src/file/file"; -let debounceTimer: NodeJS.Timeout; +let debounceTimer: number const scrollToLeaf = (leaf: WorkspaceLeaf):void => { clearTimeout(debounceTimer); // Reset debounce timer - debounceTimer = setTimeout(() => { + debounceTimer = window.setTimeout(() => { if (!(leaf?.view instanceof MarkdownView)) return; const contactElement = document.getElementById(fileId(leaf.view.file)); @@ -44,7 +44,7 @@ const scrollToTop = ():void => { const clearDebounceTimer = ():void => { - clearTimeout(debounceTimer); + window.clearTimeout(debounceTimer); } export default { diff --git a/versions.json b/versions.json index 9dab55f..1400a7e 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,6 @@ { - "2.0.0": "0.15.0", - "2.0.1": "0.15.0", - "2.0.2": "0.15.0" + "2.0.0": "1.1.0", + "2.0.1": "1.1.0", + "2.0.2": "1.1.0", + "2.0.3": "1.1.0" }