From 6ec7f360ca8d49e76a33bfaf14fec06c25fcc48d Mon Sep 17 00:00:00 2001 From: Mara-Li Date: Wed, 24 Jan 2024 15:42:06 +0100 Subject: [PATCH] feat: allow to "link" frontmatter to another, preventing to recopy long frontmatter (#281) --- src/GitHub/files.ts | 2 + src/GitHub/upload.ts | 2 +- src/commands/callback.ts | 10 +-- src/commands/file_menu.ts | 122 +++++++++++++++-------------- src/commands/index.ts | 4 +- src/commands/plugin_commands.ts | 12 +-- src/conversion/file_path.ts | 4 +- src/i18n/locales/en.json | 4 + src/i18n/locales/fr.json | 4 + src/settings.ts | 13 ++++ src/settings/interface.ts | 2 + src/utils/data_validation_test.ts | 125 ++++++++++++++++++++++-------- src/utils/parse_frontmatter.ts | 83 +++++++++++++++++--- 13 files changed, 271 insertions(+), 116 deletions(-) diff --git a/src/GitHub/files.ts b/src/GitHub/files.ts index 6de7fddb..05708517 100644 --- a/src/GitHub/files.ts +++ b/src/GitHub/files.ts @@ -104,6 +104,8 @@ export class FilesManagement extends Publisher { const repoFrontatter = getRepoFrontmatter( this.settings, repo, + file, + this.plugin.app, frontMatter ); const filepath = getReceiptFolder(file, this.settings, repo, this.plugin.app, repoFrontatter); diff --git a/src/GitHub/upload.ts b/src/GitHub/upload.ts index 21d764e7..34ae5a6a 100644 --- a/src/GitHub/upload.ts +++ b/src/GitHub/upload.ts @@ -179,7 +179,7 @@ export default class Publisher { this.plugin, ); const frontmatter = this.metadataCache.getFileCache(file)?.frontmatter; - const repoFrontmatter = getRepoFrontmatter(this.settings, repo.repo, frontmatter); + const repoFrontmatter = getRepoFrontmatter(this.settings, repo.repo, file, this.plugin.app, frontmatter); const isNotEmpty = await checkEmptyConfiguration(repoFrontmatter, this.plugin); repo.frontmatter = repoFrontmatter; if ( diff --git a/src/commands/callback.ts b/src/commands/callback.ts index f9e20778..ef406f15 100644 --- a/src/commands/callback.ts +++ b/src/commands/callback.ts @@ -11,7 +11,7 @@ import GithubPublisher from "../main"; import {MonoRepoProperties, MultiRepoProperties, Repository} from "../settings/interface"; import {createLink, logs} from "../utils"; import {checkRepositoryValidity, isShared} from "../utils/data_validation_test"; -import { getRepoFrontmatter } from "../utils/parse_frontmatter"; +import { frontmatterFromFile, getRepoFrontmatter } from "../utils/parse_frontmatter"; import {purgeNotesRemote, shareOneNote} from "."; import {shareEditedOnly, uploadAllEditedNotes, uploadAllNotes, uploadNewNotes} from "./plugin_commands"; @@ -32,13 +32,13 @@ export async function createLinkCallback(repo: Repository | null, plugin: Github hotkeys: [], checkCallback: (checking) => { const file = plugin.app.workspace.getActiveFile(); - const frontmatter = file ? plugin.app.metadataCache.getFileCache(file)?.frontmatter : null; + const frontmatter = frontmatterFromFile(file, plugin); if ( file && frontmatter && isShared(frontmatter, plugin.settings, file, repo) ) { if (!checking) { const multiRepo: MultiRepoProperties = { - frontmatter: getRepoFrontmatter(plugin.settings, repo, frontmatter), + frontmatter: getRepoFrontmatter(plugin.settings, repo, file, plugin.app, frontmatter), repo, }; createLink( @@ -78,7 +78,7 @@ export async function purgeNotesRemoteCallback(plugin: GithubPublisher, repo: Re //@ts-ignore callback: async () => { logs({settings: plugin.settings}, "Enabling purge command"); - const frontmatter = getRepoFrontmatter(plugin.settings, repo); + const frontmatter = getRepoFrontmatter(plugin.settings, repo, null, plugin.app); const monoRepo: MonoRepoProperties = { frontmatter: Array.isArray(frontmatter) ? frontmatter[0] : frontmatter, repo, @@ -115,7 +115,7 @@ export async function shareOneNoteCallback(repo: Repository|null, plugin: Github hotkeys: [], checkCallback: (checking) => { const file = plugin.app.workspace.getActiveFile(); - const frontmatter = file ? plugin.app.metadataCache.getFileCache(file)?.frontmatter : null; + const frontmatter = frontmatterFromFile(file, plugin); if ( file && frontmatter && isShared(frontmatter, plugin.settings, file, repo) ) { diff --git a/src/commands/file_menu.ts b/src/commands/file_menu.ts index 0d635b5e..fd54cb0c 100644 --- a/src/commands/file_menu.ts +++ b/src/commands/file_menu.ts @@ -4,7 +4,7 @@ import { Menu, MenuItem, Platform, TFile, TFolder} from "obsidian"; import GithubPublisher from "../main"; import {MonoRepoProperties, Repository} from "../settings/interface"; import {defaultRepo, getRepoSharedKey, isExcludedPath, isInDryRunFolder, isShared, multipleSharedKey} from "../utils/data_validation_test"; -import { getRepoFrontmatter } from "../utils/parse_frontmatter"; +import { getLinkedFrontmatter, getRepoFrontmatter } from "../utils/parse_frontmatter"; import {shareAllMarkedNotes, shareOneNote} from "."; import {ChooseRepoToRun} from "./suggest_other_repo_commands_modal"; @@ -18,7 +18,7 @@ import {ChooseRepoToRun} from "./suggest_other_repo_commands_modal"; export async function shareFolderRepo(plugin: GithubPublisher, folder: TFolder, branchName: string, repo: Repository | null) { const publisher = await plugin.reloadOctokit(); const statusBarItems = plugin.addStatusBarItem(); - const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, undefined); + const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, null, plugin.app, undefined); const monoProperties: MonoRepoProperties = { frontmatter: Array.isArray(repoFrontmatter) ? repoFrontmatter[0] : repoFrontmatter, repo, @@ -55,7 +55,7 @@ export function addSubMenuCommandsFolder(plugin: GithubPublisher, item: MenuItem })) .setIcon("folder-up") .onClick(async () => { - const repo = getRepoSharedKey(plugin.settings, undefined); + const repo = getRepoSharedKey(plugin.settings, plugin.app, undefined); await shareFolderRepo(plugin, folder, branchName, repo); }); }); @@ -99,65 +99,67 @@ export function addSubMenuCommandsFolder(plugin: GithubPublisher, item: MenuItem * @param {Menu} menu - The menu to add the item to */ export function addMenuFile(plugin: GithubPublisher, file: TFile, branchName: string, menu: Menu) { - const frontmatter = file instanceof TFile ? plugin.app.metadataCache.getFileCache(file)!.frontmatter : undefined; - let getSharedKey = getRepoSharedKey(plugin.settings, frontmatter); - const allKeysFromFile = multipleSharedKey(frontmatter, plugin.settings); + const frontmatter = plugin.app.metadataCache.getFileCache(file)?.frontmatter; + let getSharedKey = getRepoSharedKey(plugin.settings, plugin.app, frontmatter, file); + const allKeysFromFile = multipleSharedKey(frontmatter, plugin.settings, file, plugin.app); if ( - isShared(frontmatter, plugin.settings, file, getSharedKey) && - plugin.settings.plugin.fileMenu + !(isShared(frontmatter, plugin.settings, file, getSharedKey) && + plugin.settings.plugin.fileMenu) ) { - const repoFrontmatter = getRepoFrontmatter(plugin.settings, getSharedKey, frontmatter); - menu.addItem((item) => { - /** - * Create a submenu if multiple repo exists in the settings & platform is desktop - */ + return; + } + const repoFrontmatter = getRepoFrontmatter(plugin.settings, getSharedKey, file, plugin.app, frontmatter); - if (allKeysFromFile.length > 1 || (repoFrontmatter instanceof Array && repoFrontmatter.length > 1)) { - if (Platform.isDesktop) { - item - .setTitle("Github Publisher") - .setIcon("upload-cloud"); - } else { - //add the line to separate the commands - menu.addSeparator(); - item.setIsLabel(true); - } - subMenuCommandsFile( - plugin, - item, - file, + menu.addItem((item) => { + /** + * Create a submenu if multiple repo exists in the settings & platform is desktop + */ + + if (allKeysFromFile.length > 1 || (repoFrontmatter instanceof Array && repoFrontmatter.length > 1)) { + if (Platform.isDesktop) { + item + .setTitle("Github Publisher") + .setIcon("upload-cloud"); + } else { + //add the line to separate the commands + menu.addSeparator(); + item.setIsLabel(true); + } + subMenuCommandsFile( + plugin, + item, + file, + branchName, + getSharedKey, + menu + ); + return; + } + const fileName = plugin.getTitleFieldForCommand(file, plugin.app.metadataCache.getFileCache(file)?.frontmatter).replace(".md", ""); + + if (!frontmatter || !frontmatter[plugin.settings.plugin.shareKey]) { + const otherRepo = plugin.settings.github.otherRepo.find((repo) => repo.shareAll?.enable); + if (otherRepo) getSharedKey = otherRepo; + else if (plugin.settings.plugin.shareAll?.enable) getSharedKey = defaultRepo(plugin.settings); + } else if (frontmatter[plugin.settings.plugin.shareKey]) { + getSharedKey = defaultRepo(plugin.settings); + } + item + .setTitle(i18next.t("commands.shareViewFiles.multiple.on", { + doc: fileName, + smartKey: getSharedKey?.smartKey.toUpperCase() || i18next.t("common.default").toUpperCase() + })) + .setIcon("file-up") + .onClick(async () => { + await shareOneNote( branchName, + await plugin.reloadOctokit(), + file, getSharedKey, - menu + fileName ); - return; - } - const fileName = plugin.getTitleFieldForCommand(file, plugin.app.metadataCache.getFileCache(file)?.frontmatter).replace(".md", ""); - - if (!frontmatter || !frontmatter[plugin.settings.plugin.shareKey]) { - const otherRepo = plugin.settings.github.otherRepo.find((repo) => repo.shareAll?.enable); - if (otherRepo) getSharedKey = otherRepo; - else if (plugin.settings.plugin.shareAll?.enable) getSharedKey = defaultRepo(plugin.settings); - } else if (frontmatter[plugin.settings.plugin.shareKey]) { - getSharedKey = defaultRepo(plugin.settings); - } - item - .setTitle(i18next.t("commands.shareViewFiles.multiple.on", { - doc: fileName, - smartKey: getSharedKey?.smartKey.toUpperCase() || i18next.t("common.default").toUpperCase() - })) - .setIcon("file-up") - .onClick(async () => { - await shareOneNote( - branchName, - await plugin.reloadOctokit(), - file, - getSharedKey, - fileName - ); - }); - }); - } + }); + }); } /** @@ -172,11 +174,13 @@ export function addMenuFile(plugin: GithubPublisher, file: TFile, branchName: st * @return {Menu} - The submenu created */ export function subMenuCommandsFile(plugin: GithubPublisher, item: MenuItem, file: TFile, branchName: string, repo: Repository | null, originalMenu: Menu): Menu { - const frontmatter = plugin.app.metadataCache.getFileCache(file)?.frontmatter; + let frontmatter = plugin.app.metadataCache.getFileCache(file)?.frontmatter; + const linkedFrontmatter = getLinkedFrontmatter(frontmatter, plugin.settings, file, plugin.app); + frontmatter = linkedFrontmatter ? { ...linkedFrontmatter, ...frontmatter } : frontmatter; const fileName = plugin.getTitleFieldForCommand(file, frontmatter).replace(".md", ""); //@ts-ignore const subMenu = Platform.isDesktop ? item.setSubmenu() as Menu : originalMenu; - let repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, frontmatter); + let repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, file, plugin.app, frontmatter); repoFrontmatter = repoFrontmatter instanceof Array ? repoFrontmatter : [repoFrontmatter]; /** * default repo @@ -306,7 +310,7 @@ export async function addMenuFolder(menu: Menu, folder: TFolder, branchName: str })) .setIcon("folder-up") .onClick(async () => { - const repo = getRepoSharedKey(plugin.settings, undefined); + const repo = getRepoSharedKey(plugin.settings, plugin.app, undefined); await shareFolderRepo(plugin, folder, branchName, repo); }); }); diff --git a/src/commands/index.ts b/src/commands/index.ts index fb1ada6c..153c1232 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -178,7 +178,7 @@ export async function shareOneNote( const metadataCache = app.metadataCache; try { const frontmatter = metadataCache.getFileCache(file)?.frontmatter; - const repoFrontmatter = getRepoFrontmatter(settings, repository, frontmatter); + const repoFrontmatter = getRepoFrontmatter(settings, repository, file, PublisherManager.plugin.app, frontmatter); const isValid = await checkRepositoryValidityWithRepoFrontmatter(PublisherManager, repoFrontmatter); const multiRepo: MultiRepoProperties = { frontmatter: repoFrontmatter, @@ -243,7 +243,7 @@ export async function shareOneNote( logs({settings, e: true}, error); const notif = document.createDocumentFragment(); notif.createSpan({ cls: ["error", "obsidian-publisher", "icons", "notification"] }).innerHTML = ERROR_ICONS; - notif.createSpan({ cls: ["error", "obsidian-publisher", "notification"] }).innerHTML = i18next.t("error.errorPublish", { repo: getRepoFrontmatter(settings, repository, metadataCache.getFileCache(file)?.frontmatter) }); + notif.createSpan({ cls: ["error", "obsidian-publisher", "notification"] }).innerHTML = i18next.t("error.errorPublish", { repo: getRepoFrontmatter(settings, repository, file, app, metadataCache.getFileCache(file)?.frontmatter) }); new Notice(notif); } } diff --git a/src/commands/plugin_commands.ts b/src/commands/plugin_commands.ts index 52efafad..0fd9822f 100644 --- a/src/commands/plugin_commands.ts +++ b/src/commands/plugin_commands.ts @@ -33,7 +33,7 @@ export async function createLinkOnActiveFile(repo: Repository | null, plugin: Gi file && frontmatter && isShared(frontmatter, plugin.settings, file, repo) ) { const multiRepo: MultiRepoProperties = { - frontmatter: getRepoFrontmatter(plugin.settings, repo, frontmatter), + frontmatter: getRepoFrontmatter(plugin.settings, repo, file, plugin.app, frontmatter), repo }; await createLink( @@ -80,7 +80,7 @@ export async function shareActiveFile(plugin: GithubPublisher, repo: Repository * @return {Promise} */ export async function deleteCommands(plugin : GithubPublisher, repo: Repository | null, branchName: string): Promise { - const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo); + const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, null, plugin.app); const publisher = await plugin.reloadOctokit(); const mono: MonoRepoProperties = { frontmatter: Array.isArray(repoFrontmatter) ? repoFrontmatter[0] : repoFrontmatter, @@ -106,7 +106,7 @@ export async function uploadAllNotes(plugin: GithubPublisher, repo: Repository | const statusBarItems = plugin.addStatusBarItem(); const publisher = await plugin.reloadOctokit(); const sharedFiles = publisher.getSharedFiles(repo); - const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo); + const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, null, plugin.app); const mono: MonoRepoProperties = { frontmatter: Array.isArray(repoFrontmatter) ? repoFrontmatter[0] : repoFrontmatter, repo @@ -132,7 +132,7 @@ export async function uploadAllNotes(plugin: GithubPublisher, repo: Repository | export async function uploadNewNotes(plugin: GithubPublisher, branchName: string, repo: Repository|null): Promise { const publisher = await plugin.reloadOctokit(); - const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo); + const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, null, plugin.app); await shareNewNote( publisher, branchName, @@ -173,7 +173,7 @@ export async function repositoryValidityActiveFile(plugin:GithubPublisher, branc */ export async function uploadAllEditedNotes(plugin: GithubPublisher ,branchName: string, repo: Repository|null=null): Promise { const publisher = await plugin.reloadOctokit(); - const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo); + const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, null, plugin.app); await shareAllEditedNotes( publisher, @@ -195,7 +195,7 @@ export async function uploadAllEditedNotes(plugin: GithubPublisher ,branchName: */ export async function shareEditedOnly(branchName: string, repo: Repository|null, plugin: GithubPublisher) { const publisher = await plugin.reloadOctokit(); - const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo); + const repoFrontmatter = getRepoFrontmatter(plugin.settings, repo, null, plugin.app); await shareOnlyEdited( publisher, branchName, diff --git a/src/conversion/file_path.ts b/src/conversion/file_path.ts index 235b662c..5449a8f7 100644 --- a/src/conversion/file_path.ts +++ b/src/conversion/file_path.ts @@ -79,7 +79,7 @@ export async function createRelativePath( const shortRepo = properties.repository; const sourcePath = getReceiptFolder(sourceFile, settings, shortRepo, app, properties.frontmatter.repo); const frontmatterTarget = metadataCache.getFileCache(targetFile.linked)!.frontmatter; - const targetRepo = getRepoFrontmatter(settings, shortRepo, frontmatterTarget); + const targetRepo = getRepoFrontmatter(settings, shortRepo, targetFile.linked, app, frontmatterTarget); const isFromAnotherRepo = checkIfRepoIsInAnother(properties.frontmatter.repo, targetRepo); const shared = isInternalShared( frontmatterTarget, @@ -376,7 +376,7 @@ export function getReceiptFolder( const { vault, metadataCache } = app; if (file.extension === "md") { const frontmatter = metadataCache.getCache(file.path)?.frontmatter; - if (!repoFrontmatter) repoFrontmatter = getRepoFrontmatter(settings, otherRepo, frontmatter); + if (!repoFrontmatter) repoFrontmatter = getRepoFrontmatter(settings, otherRepo, file, app, frontmatter); repoFrontmatter = repoFrontmatter instanceof Array ? repoFrontmatter : [repoFrontmatter]; let targetRepo = repoFrontmatter.find((repo) => repo.path?.smartkey === otherRepo?.smartKey || "default"); if (!targetRepo) targetRepo = repoFrontmatter[0]; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 72309243..fcac11d1 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -482,6 +482,10 @@ "desc": "Allows you to reopen the settings on the previously used tab", "title": "Save tab" }, + "set": { + "desc": "Choose the property key you want to use to link the property of a file to another, without rewrite them each time. Work only for file linked by a wikilink in the frontmatter.", + "title": "Set of options" + }, "shareKey": { "all": { "desc": "Share all files regardless of the state of the share key of the notes", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index ad32de71..924dd22c 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -481,6 +481,10 @@ "desc": "Permet de rouvrir les paramètres sur l'onglet précédemment utilisé", "title": "Sauvegarder l'onglet" }, + "set": { + "desc": "Choisissez la clé de propriété que vous souhaitez utiliser pour lier la propriété d'un fichier à un autre, sans les réécrire à chaque fois. Ne fonctionne que pour les fichiers liés par un lien wiki dans le frontmatter.", + "title": "Set d'options" + }, "shareKey": { "all": { "desc": "Autoriser le partage de tous les fichiers et ignorer l'état de la clé de partage", diff --git a/src/settings.ts b/src/settings.ts index dc694884..1d1b91fd 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1099,6 +1099,19 @@ export class GithubPublisherSettingsTab extends PluginSettingTab { }) ); + new Setting(this.settingsPage) + .setName(i18next.t("settings.plugin.set.title")) + .setDesc(i18next.t("settings.plugin.set.desc")) + .addText((text) => + text + .setPlaceholder("Set") + .setValue(pluginSettings.setFrontmatterKey) + .onChange(async (value) => { + pluginSettings.setFrontmatterKey = value.trim(); + await this.plugin.saveSettings(); + }) + ); + this.settingsPage.createEl("h3", {text: i18next.t("settings.plugin.head.menu")}); new Setting(this.settingsPage) diff --git a/src/settings/interface.ts b/src/settings/interface.ts index adb034aa..ef30726f 100644 --- a/src/settings/interface.ts +++ b/src/settings/interface.ts @@ -152,6 +152,7 @@ export interface GitHubPublisherSettings { displayModalRepoEditing: boolean; migrated?: boolean; saveTabId?: boolean; + setFrontmatterKey: string; } } @@ -300,6 +301,7 @@ export const DEFAULT_SETTINGS: Partial = { }, noticeError: false, displayModalRepoEditing: false, + setFrontmatterKey: "Set" } }; diff --git a/src/utils/data_validation_test.ts b/src/utils/data_validation_test.ts index 7a0fa3c6..e9d3e106 100644 --- a/src/utils/data_validation_test.ts +++ b/src/utils/data_validation_test.ts @@ -1,12 +1,12 @@ import { Octokit } from "@octokit/core"; import i18next from "i18next"; -import {FrontMatterCache, normalizePath,Notice, TFile, TFolder} from "obsidian"; +import {App, FrontMatterCache, normalizePath,Notice, TFile, TFolder} from "obsidian"; import GithubPublisher from "src/main"; import {GithubBranch} from "../GitHub/branch"; import {FIND_REGEX, FrontmatterConvert, GitHubPublisherSettings, MultiProperties, RepoFrontmatter, Repository} from "../settings/interface"; import {logs, notif} from "."; -import { getRepoFrontmatter } from "./parse_frontmatter"; +import { frontmatterFromFile, getLinkedFrontmatter, getRepoFrontmatter } from "./parse_frontmatter"; /** * - Check if the file is a valid file to publish @@ -41,13 +41,22 @@ export function isInternalShared( return ["true", "1", "yes"].includes(frontmatter[shareKey].toString().toLowerCase()); } - -export function getRepoSharedKey(settings: GitHubPublisherSettings, frontmatter?: FrontMatterCache): Repository | null{ +/** + * Retrieves the shared key for a repository based on the provided settings, app, frontmatter, and file. + * + * @param settings - The GitHubPublisherSettings object containing the configuration settings. + * @param app - The App object representing the Obsidian application. + * @param frontmatter - The FrontMatterCache object representing the frontmatter of the file. + * @param file - The TFile object representing the file being processed. + * @returns The Repository object representing the repository with the shared key, or null if no repository is found. + */ +export function getRepoSharedKey(settings: GitHubPublisherSettings, app: App, frontmatter?: FrontMatterCache, file?: TFile): Repository | null{ const allOtherRepo = settings.github.otherRepo; if (settings.plugin.shareAll?.enable && !frontmatter) { return defaultRepo(settings); } else if (!frontmatter) return null; - //check all keys in the frontmatter + const linkedFrontmatter = getLinkedFrontmatter(frontmatter, settings, file, app); + frontmatter = linkedFrontmatter ? {...linkedFrontmatter, ...frontmatter} : frontmatter; for (const repo of allOtherRepo) { if (frontmatter[repo.shareKey]) { return repo; @@ -121,8 +130,14 @@ export function isExcludedPath(settings: GitHubPublisherSettings, file: TFile | /** * Allow to get all sharedKey from one file to count them + * + * @param {FrontMatterCache | undefined} frontmatter - The frontmatter of the file. + * @param {GitHubPublisherSettings} settings - The GitHub Publisher settings. + * @param {TFile | null} file - The file to get the shared keys from. + * @param {App} app - The Obsidian app instance. + * @returns {string[]} - An array of shared keys found in the file. */ -export function multipleSharedKey(frontmatter: FrontMatterCache | undefined, settings: GitHubPublisherSettings) { +export function multipleSharedKey(frontmatter: FrontMatterCache | undefined, settings: GitHubPublisherSettings, file: TFile | null, app: App): string[] { const keysInFile: string[] = []; if (settings.plugin.shareAll?.enable) keysInFile.push("share"); //add a key to count the shareAll @@ -134,6 +149,8 @@ export function multipleSharedKey(frontmatter: FrontMatterCache | undefined, set } } if (!frontmatter) return keysInFile; + const linkedRepo = getLinkedFrontmatter(frontmatter, settings, file, app); + frontmatter = linkedRepo ? {...linkedRepo, ...frontmatter} : frontmatter; const allKey = settings.github.otherRepo.map((repo) => repo.shareKey); allKey.push(settings.plugin.shareKey); @@ -142,8 +159,8 @@ export function multipleSharedKey(frontmatter: FrontMatterCache | undefined, set keysInFile.push(key); } } - - return keysInFile; + //remove duplicate + return [...new Set(keysInFile)]; } /** @@ -267,7 +284,7 @@ export async function checkEmptyConfiguration(repoFrontmatter: RepoFrontmatter | * @param {FrontmatterConvert} conditionConvert The frontmatter option to check * @return {boolean} if the text need to be converted */ -export function noTextConversion(conditionConvert: FrontmatterConvert) { +export function noTextConversion(conditionConvert: FrontmatterConvert): boolean { const convertWikilink = conditionConvert.convertWiki; const imageSettings = conditionConvert.attachment; const embedSettings = conditionConvert.embed; @@ -292,12 +309,11 @@ export async function checkRepositoryValidity( PublisherManager: GithubBranch, repository: Repository | null = null, file: TFile | null, - silent=false): Promise { + silent: boolean=false): Promise { const settings = PublisherManager.settings; - const metadataCache = PublisherManager.plugin.app.metadataCache; try { - const frontmatter = file ? metadataCache.getFileCache(file)?.frontmatter : undefined; - const repoFrontmatter = getRepoFrontmatter(settings, repository, frontmatter); + const frontmatter = frontmatterFromFile(file, PublisherManager.plugin); + const repoFrontmatter = getRepoFrontmatter(settings, repository, file, PublisherManager.plugin.app, frontmatter); const isNotEmpty = await checkEmptyConfiguration(repoFrontmatter, PublisherManager.plugin, silent); if (isNotEmpty) { await PublisherManager.checkRepository(repoFrontmatter, silent); @@ -354,6 +370,12 @@ export async function checkRepositoryValidityWithRepoFrontmatter( return false; } +/** + * Returns the default repository based on the provided settings. + * + * @param settings - The GitHubPublisherSettings object containing the configuration settings. + * @returns The Repository object representing the default repository. + */ export function defaultRepo(settings: GitHubPublisherSettings): Repository { return { smartKey: "default", @@ -379,31 +401,57 @@ export function defaultRepo(settings: GitHubPublisherSettings): Repository { }; } -export async function verifyRateLimitAPI(octokit: Octokit, settings: GitHubPublisherSettings, commands=false, numberOfFile=1): Promise { +/** + * This function is used to verify the rate limit of the GitHub API. + * It checks the remaining number of requests and the reset time of the rate limit. + * If the remaining requests are less than or equal to the specified number of files, + * it displays a notice indicating that the rate limit is limited. + * Otherwise, it displays a notice indicating that the rate limit is not limited. + * @param octokit - The Octokit instance used to make requests to the GitHub API. + * @param settings - The settings object containing the rate limit configuration. + * @param commands - Indicates whether the function is called from a command or not. Default is `false`. + * @param numberOfFile - The number of files to be processed. Default is `1`. + * @returns The remaining number of requests in the rate limit. + */ +export async function verifyRateLimitAPI( + octokit: Octokit, + settings: GitHubPublisherSettings, + commands = false, + numberOfFile = 1 +): Promise { const rateLimit = await octokit.request("GET /rate_limit"); const remaining = rateLimit.data.resources.core.remaining; const reset = rateLimit.data.resources.core.reset; const date = new Date(reset * 1000); const time = date.toLocaleTimeString(); + if (remaining <= numberOfFile) { - new Notice(i18next.t("commands.checkValidity.rateLimit.limited", {resetTime: time})); + new Notice(i18next.t("commands.checkValidity.rateLimit.limited", { resetTime: time })); return 0; } - if (!commands) { - notif({settings}, i18next.t("commands.checkValidity.rateLimit.notLimited", { - remaining: remaining, - resetTime: time - })); + + const message = i18next.t("commands.checkValidity.rateLimit.notLimited", { + remaining, + resetTime: time + }); + + if (commands) { + new Notice(message); } else { - new Notice(i18next.t("commands.checkValidity.rateLimit.notLimited", { - remaining, - resetTime: time - })); + notif({ settings }, message); } + return remaining; } -export function forcePushAttachment(file: TFile, settings: GitHubPublisherSettings) { +/** + * Determines if an attachment file needs to be force pushed based on the provided settings. + * + * @param file - The attachment file to check. + * @param settings - The GitHub Publisher settings. + * @returns True if the attachment file needs to be force pushed, false otherwise. + */ +export function forcePushAttachment(file: TFile, settings: GitHubPublisherSettings):boolean { const needToBeForPush = settings.embed.overrideAttachments.filter((path) => { const isRegex = path.path.match(FIND_REGEX); const regex = isRegex ? new RegExp(isRegex[1], isRegex[2]) : null; @@ -419,18 +467,31 @@ export function forcePushAttachment(file: TFile, settings: GitHubPublisherSettin return true; } -export function isFolderNote(properties: MultiProperties) { - const enabled = properties.settings.upload.folderNote.enable; - if (enabled) { - const model = properties.settings.upload.folderNote.rename; - const filepath = properties.filepath; - //get the file name aka split by / and get the last element +/** + * Checks if a file is a folder note based on certain conditions. + * @param properties - An object containing the properties for the file and settings. + * @returns True if the file is a folder note, false otherwise. + */ +export function isFolderNote(properties: MultiProperties): boolean { + const { settings, filepath } = properties; + const { enable, rename } = settings.upload.folderNote; + + if (enable) { const filename = filepath.split("/").pop(); - return filename === model; + return filename === rename; } + return false; } +/** + * Checks if a file or folder is located within the dry run folder specified in the GitHub Publisher settings. + * + * @param settings - The GitHub Publisher settings. + * @param repo - The repository information. If null, the default repository information from the settings will be used. + * @param file - The file or folder to check. + * @returns True if the file or folder is located within the dry run folder, false otherwise. + */ export function isInDryRunFolder(settings: GitHubPublisherSettings, repo: Repository | null, file: TFile | TFolder) { if (settings.github.dryRun.folderName.trim().length ===0) return false; const variables = { diff --git a/src/utils/parse_frontmatter.ts b/src/utils/parse_frontmatter.ts index 116c096f..d79b3909 100644 --- a/src/utils/parse_frontmatter.ts +++ b/src/utils/parse_frontmatter.ts @@ -3,10 +3,19 @@ * See docs for all the condition */ -import { FrontMatterCache, normalizePath } from "obsidian"; +import { App, FrontMatterCache, normalizePath,TFile } from "obsidian"; +import GithubPublisher from "src/main"; import { FolderSettings, FrontmatterConvert, GitHubPublisherSettings, Path, RepoFrontmatter, Repository } from "../settings/interface"; +/** + * Retrieves the frontmatter settings for a given file. + * + * @param frontmatter - The frontmatter cache for the file. + * @param settings - The GitHub Publisher settings. + * @param repo - The repository settings for the file. + * @returns The frontmatter settings for the file. + */ export function getFrontmatterSettings( frontmatter: FrontMatterCache | undefined | null, settings: GitHubPublisherSettings, @@ -105,7 +114,12 @@ export function getFrontmatterSettings( } return parseFrontmatterSettingsWithRepository(repo, frontmatter, settingsConversion); } - +/** + * Translates a boolean value or string representation of a boolean into a string value for the 'removeEmbed' setting. + * + * @param removeEmbed - The value to be translated. Can be a boolean value or a string representation of a boolean. + * @returns The translated string value for the 'removeEmbed' setting. Possible values are 'keep', 'remove', 'links', or 'bake'. + */ function translateBooleanForRemoveEmbed(removeEmbed: unknown) { if (removeEmbed === "true") { return "keep"; @@ -129,9 +143,19 @@ function translateBooleanForRemoveEmbed(removeEmbed: unknown) { export function getRepoFrontmatter( settings: GitHubPublisherSettings, repository: Repository | null, + sourceFile: TFile | null, + app: App, frontmatter?: FrontMatterCache | null, + parseSet = true, ): RepoFrontmatter[] | RepoFrontmatter { let github = repository ?? settings.github; + if (parseSet && frontmatter) { + const linkedFrontmatter = getLinkedFrontmatter(frontmatter, settings, sourceFile, app); + if (linkedFrontmatter) { + //fusion frontmatter and override the linkedFrontmatter with the frontmatter if the key is the same + frontmatter = {...linkedFrontmatter, ...frontmatter}; + } + } if (frontmatter && typeof frontmatter["shortRepo"] === "string" && frontmatter["shortRepo"] !== "default") { const smartKey = frontmatter.shortRepo.toLowerCase(); const allOtherRepo = settings.github.otherRepo; @@ -158,15 +182,12 @@ export function getRepoFrontmatter( repoFrontmatter.autoclean = false; } if (!frontmatter || (frontmatter.multipleRepo === undefined && frontmatter.repo === undefined && frontmatter.shortRepo === undefined)) { - return parsePath(settings, repository, repoFrontmatter, frontmatter) as RepoFrontmatter; + return parsePath(settings, repository, repoFrontmatter, frontmatter); } let isFrontmatterAutoClean = null; if (frontmatter.multipleRepo) { const multipleRepo = parseMultipleRepo(frontmatter, repoFrontmatter); - if (multipleRepo.length === 1) { - return parsePath(settings, repository, multipleRepo[0], frontmatter) as RepoFrontmatter; - } - return multipleRepo; + return parsePath(settings, repository, multipleRepo, frontmatter); } else if (frontmatter.repo) { if (typeof frontmatter.repo === "object") { if (frontmatter.repo.branch !== undefined) { @@ -248,7 +269,16 @@ function parseMultipleRepo( } } } - //remove duplicates + return removeDuplicateRepo(multipleRepo); +} + +/** + * Removes duplicate repositories from the given array of RepoFrontmatter objects. + * Only the {repo, owner, branch, autoclean} properties are compared. + * @param multipleRepo - An array of RepoFrontmatter objects representing multiple repositories. + * @returns An array of RepoFrontmatter objects with duplicate repositories removed. + */ +function removeDuplicateRepo(multipleRepo: RepoFrontmatter[]) { return multipleRepo.filter( (v, i, a) => a.findIndex( @@ -267,7 +297,6 @@ function parseMultipleRepo( * @param {FrontMatterCache} frontmatter - The frontmatter of the file * @param {Repository[]} allRepo - The list of all repo from the settings * @param {RepoFrontmatter} repoFrontmatter - The default repoFrontmatter (from the default settings) - * @return {RepoFrontmatter[] | RepoFrontmatter} - The repoFrontmatter from shortRepo */ function multipleShortKeyRepo(frontmatter: FrontMatterCache, allRepo: Repository[], repoFrontmatter: RepoFrontmatter, setting: GitHubPublisherSettings) { if (frontmatter.shortRepo instanceof Array) { @@ -471,3 +500,39 @@ function parseFrontmatterSettingsWithRepository( frontConvert.unshared = frontmatter?.[`${smartKey}.nonShared`] ?? frontConvert.unshared; return frontConvert; } + +export function getLinkedFrontmatter( + originalFrontmatter: FrontMatterCache | null | undefined, + settings: GitHubPublisherSettings, + sourceFile: TFile | null | undefined, + app: App, +) { + const {metadataCache, vault} = app; + const linkedKey = settings.plugin.setFrontmatterKey; + if (!linkedKey || !originalFrontmatter || !sourceFile) return originalFrontmatter; + const linkedFrontmatter = originalFrontmatter?.[linkedKey]; + if (!linkedFrontmatter) return originalFrontmatter; + let linkedFile: undefined | string; + metadataCache.getFileCache(sourceFile)?.frontmatterLinks?.forEach((link) => { + const fieldRegex = new RegExp(`${linkedKey}(\\.\\d+)?`, "g"); + if (link.key.match(fieldRegex)) { + linkedFile = link.link; + } + }); + if (!linkedFile) return originalFrontmatter; + const linkedFrontmatterFile = metadataCache.getFirstLinkpathDest(linkedFile, sourceFile.path) ?? vault.getAbstractFileByPath(linkedFile); + if (!linkedFrontmatterFile || !(linkedFrontmatterFile instanceof TFile)) return originalFrontmatter; + const linked = metadataCache.getFileCache(linkedFrontmatterFile)?.frontmatter; + if (!linked) return originalFrontmatter; + return linked; +} + +export function frontmatterFromFile(file: TFile | null, plugin: GithubPublisher) { + let frontmatter = null; + if (file) { + frontmatter = plugin.app.metadataCache.getFileCache(file)?.frontmatter; + const linkedFrontmatter = getLinkedFrontmatter(frontmatter, plugin.settings, file, plugin.app); + frontmatter = linkedFrontmatter ? { ...linkedFrontmatter, ...frontmatter } : frontmatter; + } + return frontmatter; +} \ No newline at end of file