started cleaning code for a maintainable release

This commit is contained in:
Roland Broekema 2025-04-08 17:19:55 +02:00
parent c869420040
commit 78b258b6da
9 changed files with 46 additions and 35 deletions

View file

@ -1,5 +0,0 @@
import { App } from 'obsidian';
import * as React from "react";
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
export const AppContext = React.createContext<App>(undefined!);

View file

@ -1,7 +0,0 @@
import { App } from "obsidian";
import * as React from "react";
import { AppContext } from "./context";
export const useApp = (): App => {
return React.useContext(AppContext);
};

View file

@ -0,0 +1,19 @@
import { App } from 'obsidian'
let _app: App | undefined
export function setApp(app: App) {
_app = app
}
// Safe accessor, runtime-enforced, TS knows it's not undefined
export function getApp(): App {
if (!_app) {
throw new Error('App context has not been set.')
}
return _app
}
export function clearApp() {
_app = undefined;
}

View file

@ -1,8 +1,10 @@
import {App, MetadataCache, TFile} from "obsidian";
import {TFile} from "obsidian";
import * as yaml from 'js-yaml';
import { Contact } from "./contact";
import { getApp } from "src/context/sharedAppContext";
export async function parseContactFiles(files: TFile[], metadataCache: MetadataCache) {
export async function parseContactFiles(files: TFile[]) {
const { metadataCache } = getApp();
const contactsData: Contact[] = [];
for (const file of files) {
const frontMatter = metadataCache.getFileCache(file)?.frontmatter
@ -16,8 +18,8 @@ export async function parseContactFiles(files: TFile[], metadataCache: MetadataC
return contactsData;
}
export async function updateFrontMatterValue(app: App, file: TFile, key: string, value: string) {
export async function updateFrontMatterValue(file: TFile, key: string, value: string) {
const app = getApp();
const content = await app.vault.read(file);
const match = content.match(/^---\n([\s\S]*?)\n---\n?/);

View file

@ -1,6 +1,7 @@
import {MetadataCache, Notice, TFile} from "obsidian";
import {Notice, TFile} from "obsidian";
import {parseKey} from "./vcardKey";
import {VCardStructuredFields} from "./vcardDefinitions";
import {getApp} from "src/context/sharedAppContext";
function filterNonNull<T>(array: (T | null | undefined)[]): T[] {
return array.filter((item): item is T => item !== null && item !== undefined);
@ -37,8 +38,9 @@ function renderSingleKey([key, value]:[string, string]):string {
return `${keyObj.key}${type}:${value}`;
}
function generateVCard(metadataCache: MetadataCache, file: TFile): string {
function generateVCard(file: TFile): string {
try {
const { metadataCache } = getApp();
const frontMatter = metadataCache.getFileCache(file)?.frontmatter;
if (!frontMatter) return "";
@ -71,9 +73,9 @@ function generateVCard(metadataCache: MetadataCache, file: TFile): string {
}
}
export async function vcardToString(metadataCache: MetadataCache, contactFiles: TFile[]): Promise<string> {
export async function vcardToString(contactFiles: TFile[]): Promise<string> {
return contactFiles
.map(file => generateVCard(metadataCache, file))
.map(file => generateVCard(file))
.filter(vcard => vcard !== "") // Remove empty results
.join("\n");
}

View file

@ -1,5 +1,5 @@
import * as React from "react";
import {useApp} from "src/context/hooks";
import {getApp} from "src/context/sharedAppContext";
import {fileId, openFile} from "src/file/file";
import {Contact} from "src/parse/contact";
import {setIcon, TFile} from "obsidian";
@ -15,7 +15,7 @@ type ContactProps = {
export const ContactView = (props: ContactProps) => {
const {workspace} = useApp();
const {workspace} = getApp();
const contact = props.contact;
const buttons = React.useRef<(HTMLElement | null)[]>([]);
React.useEffect(() => {

View file

@ -1,6 +1,6 @@
import {normalizePath, Notice, TAbstractFile, TFile, TFolder} from "obsidian";
import * as React from "react";
import { useApp } from "src/context/hooks";
import { getApp } from "src/context/sharedAppContext";
import {createContactFile, createFileName, findContactFiles, openFilePicker, saveVcardFilePicker} from "src/file/file";
import ContactsPlugin from "src/main";
import { Contact } from "src/parse/contact";
@ -14,13 +14,13 @@ import myScrollTo from "src/util/myScrollTo";
import { vcardToString } from "src/parse/vcard/vcardToString";
import { processAvatar } from "src/util/avatarActions";
type RootProps = {
plugin: ContactsPlugin;
};
export const SidebarRootView = (props: RootProps) => {
const app = useApp();
const { vault, metadataCache } = app;
const { vault } = getApp();
const [contacts, setContacts] = React.useState<Contact[]>([]);
const [sort, setSort] = React.useState<Sort>(Sort.NAME);
const folder = props.plugin.settings.contactsFolder;
@ -39,7 +39,7 @@ export const SidebarRootView = (props: RootProps) => {
return;
}
parseContactFiles(findContactFiles(contactsFolder), metadataCache).then((contactsData) =>{
parseContactFiles(findContactFiles(contactsFolder)).then((contactsData) =>{
setContacts(contactsData);
});
};
@ -101,7 +101,7 @@ export const SidebarRootView = (props: RootProps) => {
}}
exportAllVCF={async() => {
const allContactFiles = contacts.map((contact)=> contact.file)
const vcards = await vcardToString(metadataCache, allContactFiles);
const vcards = await vcardToString(allContactFiles);
saveVcardFilePicker(vcards)
}}
onCreateContact={async () => {
@ -117,7 +117,7 @@ export const SidebarRootView = (props: RootProps) => {
processAvatar={(contact :Contact) => {
(async () => {
try {
await processAvatar(app, contact);
await processAvatar(contact);
setTimeout(() => { parseContacts() }, 50);
} catch (err) {
new Notice(err.message);
@ -126,7 +126,7 @@ export const SidebarRootView = (props: RootProps) => {
}}
exportVCF={(contactFile: TFile) => {
(async () => {
const vcards = await vcardToString(metadataCache, [contactFile])
const vcards = await vcardToString([contactFile])
saveVcardFilePicker(vcards, contactFile)
})();
}} />

View file

@ -1,7 +1,7 @@
import { ItemView, WorkspaceLeaf } from "obsidian";
import * as React from "react";
import { createRoot } from "react-dom/client";
import { AppContext } from "src/context/context";
import {setApp, clearApp} from "src/context/sharedAppContext";
import ContactsPlugin from "src/main";
import { CONTACTS_VIEW_CONFIG } from "src/util/constants";
import { SidebarRootView } from "./components/SidebarRootView";
@ -26,15 +26,15 @@ export class ContactsView extends ItemView {
return CONTACTS_VIEW_CONFIG.icon;
}
async onOpen() {
async onOpen(){
setApp(this.app);
this.root.render(
<AppContext.Provider value={this.app}>
<SidebarRootView plugin={this.plugin} />
</AppContext.Provider>
);
}
async onClose() {
clearApp();
this.root.unmount();
}
}

View file

@ -48,7 +48,7 @@ function isHttpUrl(str: string): boolean {
}
}
export const processAvatar = async (app: App, contact: Contact) => {
export const processAvatar = async (contact: Contact) => {
try {
let rawImg :HTMLImageElement;
if (isHttpUrl(contact.data['PHOTO'])) {
@ -64,7 +64,7 @@ export const processAvatar = async (app: App, contact: Contact) => {
}
}
await updateFrontMatterValue(app, contact.file, 'PHOTO', base64EncodeImage(resizeAndCropImage(rawImg, 120)));
await updateFrontMatterValue(contact.file, 'PHOTO', base64EncodeImage(resizeAndCropImage(rawImg, 120)));
} catch (err) {
console.error(err);
throw new Error(