mirror of
https://github.com/broekema41/obsidian-vcf-contacts.git
synced 2026-07-22 05:42:58 +00:00
fixes for all review items to get a comunity registration listing
This commit is contained in:
parent
350b39373a
commit
7879cce3d0
13 changed files with 53 additions and 45 deletions
4
LICENSE
4
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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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/"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import * as yaml from "js-yaml";
|
||||
import { stringifyYaml } from "obsidian";
|
||||
|
||||
export function mdRender(record: Record<string, any>, hashtags: string): string {
|
||||
const { NOTE, ...recordWithoutNote } = record;
|
||||
|
|
@ -9,7 +9,7 @@ export function mdRender(record: Record<string, any>, hashtags: string): string
|
|||
const tempTags= recordWithoutNote.CATEGORIES.split(',')
|
||||
additionalTags = `#${tempTags.join(' #')}`
|
||||
}
|
||||
const yamlString = yaml.dump(recordWithoutNote, { lineWidth: -1 });
|
||||
const yamlString = stringifyYaml(recordWithoutNote);
|
||||
|
||||
return `---
|
||||
${yamlString}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@ 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);
|
||||
// Normalize line endings to \n first (handles \r, \r\n)
|
||||
const normalized = vCardData.replace(/\r\n?/g, '\n');
|
||||
|
||||
const lines = normalized.split('\n');
|
||||
const unfoldedLines: string[] = [];
|
||||
let currentLine = "";
|
||||
|
||||
for (const line of lines) {
|
||||
if (/^\s/.test(line)) {
|
||||
// Continuation of the previous line (line folding)
|
||||
currentLine += line.trimStart();
|
||||
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;
|
||||
|
|
@ -20,7 +23,6 @@ function unfoldVCardLines(vCardData: string): string[] {
|
|||
|
||||
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<string, any> = {};
|
||||
|
||||
for (const line of unfoldedLines) {
|
||||
const parsedLine = parseVCardLine(line);
|
||||
const indexedParsedLine = indexIfKeysExist(vCardObject, parsedLine)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const CopyableItem: React.FC<CopyableItemProps> = ({ 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 });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue