mirror of
https://github.com/broekema41/obsidian-vcf-contacts.git
synced 2026-07-22 05:42:58 +00:00
feature() saving work on the models and the parser
This commit is contained in:
parent
619a286f80
commit
8a164c6ae7
5 changed files with 135 additions and 58 deletions
17
package-lock.json
generated
17
package-lock.json
generated
|
|
@ -1,14 +1,15 @@
|
|||
{
|
||||
"name": "obsidian-contacts",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-contacts",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-yaml": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
|
@ -460,9 +461,7 @@
|
|||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/array-union": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -1560,8 +1559,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
|
|
@ -2529,9 +2526,7 @@
|
|||
"argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"array-union": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -3275,8 +3270,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-yaml": "^4.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
116
src/file/file.ts
116
src/file/file.ts
|
|
@ -1,6 +1,8 @@
|
|||
import { normalizePath, Notice, TFile, TFolder, Vault, Workspace } from "obsidian";
|
||||
import {App, Modal, normalizePath, Notice, TFile, TFolder, Vault, Workspace} from "obsidian";
|
||||
import { join } from "path";
|
||||
import { Template } from "src/settings/settings";
|
||||
import { ContactNameModal } from "../modals/contactNameModal";
|
||||
import {FileExistsModal} from "../modals/fileExistsModal";
|
||||
|
||||
const customFormat =
|
||||
`/---contact---/
|
||||
|
|
@ -45,45 +47,51 @@ export function findContactFiles(contactsFolder: TFolder) {
|
|||
return contactFiles;
|
||||
}
|
||||
|
||||
export function createContactFile(folderPath: string, template: Template, hashtag: string, vault: Vault, workspace: Workspace) {
|
||||
const folder = vault.getAbstractFileByPath(folderPath)
|
||||
if (!folder) {
|
||||
new Notice(`Can not find path: '${folderPath}'. Please update "Contacts" plugin settings`);
|
||||
return;
|
||||
}
|
||||
export function createContactFile(app: App, folderPath: string, template: Template, hashtag: string, vault: Vault, workspace: Workspace) {
|
||||
const folder = vault.getAbstractFileByPath(folderPath)
|
||||
if (!folder) {
|
||||
new Notice(`Can not find path: '${folderPath}'. Please update "Contacts" plugin settings`);
|
||||
return;
|
||||
}
|
||||
|
||||
vault.create(normalizePath(join(folderPath, `Contact ${findNextFileNumber(folderPath, vault)}.md`)), getNewFileContent(template, hashtag))
|
||||
.then(createdFile => openFile(createdFile, workspace));
|
||||
new ContactNameModal(app, async (givenName, familyName) => {
|
||||
const filePath = normalizePath(join(folderPath, `${givenName} ${familyName}.md`));
|
||||
const fileExists = await vault.adapter.exists(filePath);
|
||||
if (fileExists) {
|
||||
new FileExistsModal(filePath, (action: "replace" | "skip") => {
|
||||
if(action === "skip") {
|
||||
new Notice("File creation skipped.");
|
||||
}
|
||||
|
||||
if(action === "replace") {
|
||||
vault.adapter.write(filePath, getNewFileContent(template, hashtag))
|
||||
.then(() => {
|
||||
const file = vault.getAbstractFileByPath(filePath);
|
||||
if (file instanceof TFile) {
|
||||
openFile(file, workspace);
|
||||
}
|
||||
new Notice(`File overwritten.`);
|
||||
});
|
||||
}
|
||||
}).open();
|
||||
} else {
|
||||
vault.create(filePath, getNewFileContent(template, hashtag))
|
||||
.then(createdFile => openFile(createdFile, workspace));
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
|
||||
function findNextFileNumber(folderPath: string, vault: Vault) {
|
||||
const folder = vault.getAbstractFileByPath(
|
||||
normalizePath(folderPath)
|
||||
) as TFolder;
|
||||
// export function createContactFileWithData() {
|
||||
// const folder = vault.getAbstractFileByPath(folderPath)
|
||||
// if (!folder) {
|
||||
// new Notice(`Can not find path: '${folderPath}'. Please update "Contacts" plugin settings`);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// vault.create(normalizePath(join(folderPath, `Contact ${findNextFileNumber(folderPath, vault)}.md`)), getNewFileContent(template, hashtag))
|
||||
// .then(createdFile => openFile(createdFile, workspace));
|
||||
// }
|
||||
|
||||
let nextNumber = 0;
|
||||
Vault.recurseChildren(folder, (contactNote) => {
|
||||
if (!(contactNote instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
const name = contactNote.basename;
|
||||
const regex = /Contact(?<number>\s\d+)*/g;
|
||||
for (const match of name.matchAll(regex)) {
|
||||
if (!match.groups || !match.groups.number) {
|
||||
if (nextNumber === 0) {
|
||||
nextNumber = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const currentNumberString = match.groups.number.trim();
|
||||
if (currentNumberString != undefined && currentNumberString !== "") {
|
||||
const currentNumber = parseInt(currentNumberString);
|
||||
nextNumber = Math.max(nextNumber, (currentNumber + 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
return nextNumber === 0 ? "" : nextNumber.toString();
|
||||
}
|
||||
|
||||
function getNewFileContent(template: Template, hashtag: string): string {
|
||||
let hashtagSuffix = '';
|
||||
|
|
@ -98,4 +106,38 @@ function getNewFileContent(template: Template, hashtag: string): string {
|
|||
default:
|
||||
return customFormat + hashtagSuffix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function openFilePicker(type: string): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = type;
|
||||
input.style.display = "none";
|
||||
|
||||
input.addEventListener("change", () => {
|
||||
if (input?.files && input.files.length > 0) {
|
||||
const file = input.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function (event) {
|
||||
const rawData = event?.target?.result || '';
|
||||
if (typeof rawData === "string") {
|
||||
resolve(rawData);
|
||||
} else {
|
||||
resolve(new TextDecoder("utf-8").decode(rawData));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
reader.readAsText(file, "UTF-8");
|
||||
} else {
|
||||
resolve('');
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
document.body.removeChild(input);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ type HeaderProps = {
|
|||
onSortChange: React.Dispatch<React.SetStateAction<Sort>>;
|
||||
sort: Sort;
|
||||
onCreateContact: () => void;
|
||||
importVCF: () => void;
|
||||
exportVCF: () => void;
|
||||
};
|
||||
|
||||
export const HeaderView = (props: HeaderProps) => {
|
||||
|
|
@ -22,11 +24,33 @@ export const HeaderView = (props: HeaderProps) => {
|
|||
<div
|
||||
id="create-btn"
|
||||
className="clickable-icon nav-action-button"
|
||||
data-icon="contact"
|
||||
aria-label="Create New Contact"
|
||||
ref={(element) => (buttons.current[1] = element)}
|
||||
onClick={props.onCreateContact}
|
||||
>
|
||||
Create
|
||||
</div>
|
||||
/>
|
||||
|
||||
<div
|
||||
id="import-vcf-btn"
|
||||
data-icon="file-down"
|
||||
className={
|
||||
"clickable-icon nav-action-button "
|
||||
}
|
||||
aria-label="import VCF"
|
||||
ref={(element) => (buttons.current[2] = element)}
|
||||
onClick={ props.importVCF }
|
||||
/>
|
||||
<div
|
||||
id="import-vcf-btn"
|
||||
data-icon="file-up"
|
||||
className={
|
||||
"clickable-icon nav-action-button "
|
||||
}
|
||||
aria-label="export VCF"
|
||||
ref={(element) => (buttons.current[3] = element)}
|
||||
onClick={ props.exportVCF }
|
||||
/>
|
||||
|
||||
<div className="vl"></div>
|
||||
<div
|
||||
id="sort-by-name-btn"
|
||||
|
|
@ -36,7 +60,7 @@ export const HeaderView = (props: HeaderProps) => {
|
|||
(props.sort === Sort.NAME && "is-active")
|
||||
}
|
||||
aria-label="Sort By Name"
|
||||
ref={(element) => (buttons.current[1] = element)}
|
||||
ref={(element) => (buttons.current[4] = element)}
|
||||
onClick={() => props.onSortChange(Sort.NAME)}
|
||||
/>
|
||||
<div
|
||||
|
|
@ -47,7 +71,7 @@ export const HeaderView = (props: HeaderProps) => {
|
|||
(props.sort === Sort.LAST_CONTACT && "is-active")
|
||||
}
|
||||
aria-label="Sort By Last Contact"
|
||||
ref={(element) => (buttons.current[2] = element)}
|
||||
ref={(element) => (buttons.current[5] = element)}
|
||||
onClick={() => props.onSortChange(Sort.LAST_CONTACT)}
|
||||
/>
|
||||
<div
|
||||
|
|
@ -58,7 +82,7 @@ export const HeaderView = (props: HeaderProps) => {
|
|||
(props.sort === Sort.BIRTHDAY && "is-active")
|
||||
}
|
||||
aria-label="Sort By Birthday"
|
||||
ref={(element) => (buttons.current[3] = element)}
|
||||
ref={(element) => (buttons.current[6] = element)}
|
||||
onClick={() => props.onSortChange(Sort.BIRTHDAY)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
import { normalizePath, TAbstractFile, TFile, TFolder } from "obsidian";
|
||||
import * as React from "react";
|
||||
import { useApp } from "src/context/hooks";
|
||||
import { createContactFile, findContactFiles } from "src/file/file";
|
||||
import { createContactFile, findContactFiles, openFilePicker } from "src/file/file";
|
||||
import ContactsPlugin from "src/main";
|
||||
import { Contact } from "src/parse/contact";
|
||||
import { parseContactFiles } from "src/parse/parse";
|
||||
import { Sort } from "src/util/constants";
|
||||
import { ContactsListView } from "./ContactsListView";
|
||||
import { HeaderView } from "./HeaderView";
|
||||
import { parseToSingles, parseVcard } from "src/parse/vcard/vcardParse";
|
||||
|
||||
type RootProps = {
|
||||
plugin: ContactsPlugin;
|
||||
};
|
||||
|
||||
export const SidebarRootView = (props: RootProps) => {
|
||||
const { vault, metadataCache, workspace } = useApp();
|
||||
const app = useApp();
|
||||
const { vault, metadataCache, workspace } = app;
|
||||
const [contacts, setContacts] = React.useState<Contact[]>([]);
|
||||
const [sort, setSort] = React.useState<Sort>(Sort.LAST_CONTACT);
|
||||
const folder = props.plugin.settings.contactsFolder;
|
||||
|
|
@ -67,8 +69,23 @@ export const SidebarRootView = (props: RootProps) => {
|
|||
<div>
|
||||
<HeaderView
|
||||
onSortChange={setSort}
|
||||
importVCF={() => {
|
||||
openFilePicker('.vcf').then((fileContent: string) => {
|
||||
if (fileContent === '') {
|
||||
return;
|
||||
} else {
|
||||
const singles: string[] = parseToSingles(fileContent);
|
||||
for (const single of singles) {
|
||||
const vcardData = parseVcard(single);
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
exportVCF={() => {}}
|
||||
onCreateContact={() =>
|
||||
createContactFile(
|
||||
app,
|
||||
folder,
|
||||
props.plugin.settings.template,
|
||||
props.plugin.settings.defaultHashtag,
|
||||
|
|
|
|||
Loading…
Reference in a new issue