From 0bc8fd56b25f4d4c6c088f73133f2bd0db07cdbc Mon Sep 17 00:00:00 2001 From: Andrea Alberti Date: Fri, 10 May 2024 20:49:07 +0200 Subject: [PATCH] Extended plugin with revealing files. --- .gitignore | 2 +- dist/{ => apple-silicon}/manifest.json | 0 dist/{ => apple-silicon}/styles.css | 0 esbuild.config.mjs | 20 +- src/main.js | 1357 +++++++++++++++++++++ src/{types.d.ts => obsidian-augment.d.ts} | 3 +- src/patchOpenFile.ts | 16 +- src/types.ts | 4 + src/{ => types}/global.d.ts | 0 src/types/obsidian-augment.d.ts | 9 + tsconfig.json | 3 +- 11 files changed, 1399 insertions(+), 15 deletions(-) rename dist/{ => apple-silicon}/manifest.json (100%) rename dist/{ => apple-silicon}/styles.css (100%) create mode 100644 src/main.js rename src/{types.d.ts => obsidian-augment.d.ts} (64%) rename src/{ => types}/global.d.ts (100%) create mode 100644 src/types/obsidian-augment.d.ts diff --git a/.gitignore b/.gitignore index e09a007..4f64c65 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ node_modules # Don't include the compiled main.js file in the repo. # They should be uploaded to GitHub releases instead. -main.js +dist # Exclude sourcemaps *.map diff --git a/dist/manifest.json b/dist/apple-silicon/manifest.json similarity index 100% rename from dist/manifest.json rename to dist/apple-silicon/manifest.json diff --git a/dist/styles.css b/dist/apple-silicon/styles.css similarity index 100% rename from dist/styles.css rename to dist/apple-silicon/styles.css diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 6d97626..57e3168 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -1,6 +1,7 @@ import esbuild from "esbuild"; import process from "process"; import os from "os"; +import fs from "fs/promises"; import builtins from "builtin-modules"; import copy from 'esbuild-plugin-copy'; @@ -30,9 +31,26 @@ const getOutDir = () => { } }; +// Ensure the output directory exists +const ensureOutDirExists = async (outdir) => { + try { + await fs.mkdir(outdir, { recursive: true }); + console.log(`Output directory created: ${outdir}`); + } catch (err) { + console.error(`Error creating output directory: ${err.message}`); + process.exit(1); + } +}; + // Determine whether to build for production or development const prod = (process.argv[2] === "production"); +// Get the output directory +const outdir = getOutDir(); + +// Create the output directory if it doesn't exist +await ensureOutDirExists(outdir); + const context = await esbuild.context({ banner: { js: banner, @@ -60,7 +78,7 @@ const context = await esbuild.context({ logLevel: "info", sourcemap: prod ? false : "inline", treeShaking: true, - outdir: getOutDir(), + outdir, plugins: [ copy({ assets: { diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..b65d8f4 --- /dev/null +++ b/src/main.js @@ -0,0 +1,1357 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ + +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/main.ts +var main_exports = {}; +__export(main_exports, { + default: () => ImportAttachments +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian2 = require("obsidian"); + +// src/ImportAttachmentsModal.ts +var import_obsidian = require("obsidian"); + +// src/types.ts +var ImportActionType = /* @__PURE__ */ ((ImportActionType2) => { + ImportActionType2["MOVE"] = "MOVE"; + ImportActionType2["COPY"] = "COPY"; + ImportActionType2["LINK"] = "LINK"; + ImportActionType2["ASK_USER"] = "ASK_USER"; + return ImportActionType2; +})(ImportActionType || {}); +var YesNoTypes = /* @__PURE__ */ ((YesNoTypes2) => { + YesNoTypes2["YES"] = "YES"; + YesNoTypes2["NO"] = "NO"; + YesNoTypes2["ASK_USER"] = "ASK_USER"; + return YesNoTypes2; +})(YesNoTypes || {}); +var MultipleFilesImportTypes = /* @__PURE__ */ ((MultipleFilesImportTypes2) => { + MultipleFilesImportTypes2["BULLETED"] = "BULLETED"; + MultipleFilesImportTypes2["NUMBERED"] = "NUMBERED"; + MultipleFilesImportTypes2["INLINE"] = "INLINE"; + return MultipleFilesImportTypes2; +})(MultipleFilesImportTypes || {}); +var RelativeLocation = /* @__PURE__ */ ((RelativeLocation2) => { + RelativeLocation2["SAME"] = "SAME"; + RelativeLocation2["VAULT"] = "VAULT"; + return RelativeLocation2; +})(RelativeLocation || {}); +var LinkFormat = /* @__PURE__ */ ((LinkFormat2) => { + LinkFormat2["RELATIVE"] = "RELATIVE"; + LinkFormat2["ABSOLUTE"] = "ABSOLUTE"; + return LinkFormat2; +})(LinkFormat || {}); + +// src/utils.ts +var import_fs = require("fs"); +var path = __toESM(require("path")); +var crypto2 = __toESM(require("crypto")); + +// node_modules/uuid/dist/esm-browser/rng.js +var getRandomValues; +var rnds8 = new Uint8Array(16); +function rng() { + if (!getRandomValues) { + getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); + if (!getRandomValues) { + throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); + } + } + return getRandomValues(rnds8); +} + +// node_modules/uuid/dist/esm-browser/stringify.js +var byteToHex = []; +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 256).toString(16).slice(1)); +} +function unsafeStringify(arr, offset = 0) { + return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]; +} + +// node_modules/uuid/dist/esm-browser/native.js +var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); +var native_default = { + randomUUID +}; + +// node_modules/uuid/dist/esm-browser/v4.js +function v4(options, buf, offset) { + if (native_default.randomUUID && !buf && !options) { + return native_default.randomUUID(); + } + options = options || {}; + const rnds = options.random || (options.rng || rng)(); + rnds[6] = rnds[6] & 15 | 64; + rnds[8] = rnds[8] & 63 | 128; + if (buf) { + offset = offset || 0; + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + return buf; + } + return unsafeStringify(rnds); +} +var v4_default = v4; + +// src/utils.ts +function getFilename(filepath) { + return path.parse(filepath).name; +} +async function arePathsSameFile(file1, file2) { + try { + const realpath1 = await import_fs.promises.realpath(file1); + const realpath2 = await import_fs.promises.realpath(file2); + return path.relative(realpath1, realpath2) == ""; + } catch (error) { + console.error("Error resolving paths:", error); + return false; + } +} +async function hashFile(filePath) { + const hash = crypto2.createHash("md5"); + let fileHandle = null; + try { + fileHandle = await import_fs.promises.open(filePath, "r"); + const stream = fileHandle.createReadStream(); + for await (const chunk of stream) { + hash.update(chunk); + } + return hash.digest("hex"); + } finally { + if (fileHandle) { + await fileHandle.close(); + } + } +} +function formatDateTime(dateFormat) { + try { + const dateTime = window.moment().format(dateFormat); + return dateTime; + } catch (error) { + if (error instanceof Error) { + console.error("Error formatting date:", error.message); + } else { + console.error("Error formatting date:", error); + } + return "DATE_ERROR"; + } +} +async function createAttachmentName(namePattern, dateFormat, originalFilePath) { + const originalFilePath_parsed = path.parse(originalFilePath); + const fileToImportName = originalFilePath_parsed.name; + let attachmentName = namePattern.replace(/\$\{original\}/g, fileToImportName).replace(/\$\{uuid\}/g, v4_default()).replace(/\$\{date\}/g, formatDateTime(dateFormat)); + if (namePattern.includes("${md5}")) { + let hash = ""; + try { + hash = await hashFile(originalFilePath); + } catch (err) { + console.error("Error hashing the file:", err); + } + attachmentName = attachmentName.replace(/\$\{md5\}/g, hash); + } + attachmentName += originalFilePath_parsed.ext; + return attachmentName; +} +async function findNewFilename(destFilePath) { + const destFilePath_parse = path.parse(destFilePath); + let counter = 1; + let fileExists; + let newFilename = null; + do { + newFilename = path.join(destFilePath_parse.dir, `${destFilePath_parse.name} (${counter})${destFilePath_parse.ext}`); + fileExists = await checkFileExists(newFilename); + counter += 1; + } while (fileExists); + return newFilename; +} +async function isFileInVault(vaultPath, filePath) { + try { + const realFilePath = await import_fs.promises.realpath(filePath); + const realVaultFolderPath = await import_fs.promises.realpath(vaultPath); + const normalizedFilePath = path.normalize(realFilePath); + const normalizedVaultFolderPath = path.normalize(realVaultFolderPath); + if (normalizedFilePath.startsWith(`${normalizedVaultFolderPath}${path.sep}`)) { + return normalizedFilePath; + } else { + return false; + } + } catch (error) { + console.error("Error resolving paths:", error); + return false; + } +} +async function checkFileExists(filePath) { + try { + const stats = await import_fs.promises.stat(filePath); + return stats.isFile(); + } catch (error) { + if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") { + return false; + } + throw error; + } +} +async function checkDirectoryExists(dirPath) { + try { + const stats = await import_fs.promises.stat(dirPath); + return stats.isDirectory(); + } catch (error) { + if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") { + return false; + } + throw error; + } +} +async function ensureDirectoryExists(path4) { + const doExist = await checkDirectoryExists(path4); + if (!doExist) { + await import_fs.promises.mkdir(path4, { recursive: true }); + } + return true; +} + +// src/ImportAttachmentsModal.ts +var path2 = __toESM(require("path")); +var ImportActionTypeModal = class extends import_obsidian.Modal { + // Private variable to store the checkbox state + constructor(app, plugin, lastActionFilesOnImport, lastEmbedOnImport) { + super(app); + this.plugin = plugin; + this.lastActionFilesOnImport = lastActionFilesOnImport; + this.lastEmbedOnImport = lastEmbedOnImport; + this.resolveChoice = () => { + }; + this.rememberChoice = false; + this.promise = new Promise((resolve) => { + this.resolveChoice = resolve; + }); + this.selectedAction = lastActionFilesOnImport; + this.selectedEmbedOption = lastEmbedOnImport; + } + createToggle(table, questionText, optionA, optionB, initialOption, callback, withSeparator = false) { + const tr = table.createEl("tr"); + if (withSeparator) { + tr.addClass("sep"); + } + tr.createEl("td", { text: questionText, cls: "action-question" }); + tr.createEl("td", { text: optionA, cls: "toggle-option-A" }); + const td = tr.createEl("td"); + const switchLabel = td.createEl("label", { cls: "switch" }); + const input = switchLabel.createEl("input", { type: "checkbox" }); + if (initialOption == 0 /* A */) { + input.checked = false; + } else { + input.checked = true; + } + switchLabel.createEl("span", { cls: "slider" }); + tr.createEl("td", { text: optionB, cls: "toggle-option-B" }); + input.addEventListener("change", () => { + if (callback) { + callback(input.checked ? 1 /* B */ : 0 /* A */); + } + }); + } + onOpen() { + let initialOption; + const { contentEl } = this; + const container = contentEl.createDiv({ cls: "import-attach-plugin" }); + container.createEl("h2", { text: "Import Files" }); + container.createEl("p", { text: "Configure the import options and then press either enter or the import button." }); + const table = container.createEl("table"); + switch (this.lastActionFilesOnImport) { + case "MOVE" /* MOVE */: + initialOption = 0 /* A */; + break; + case "COPY" /* COPY */: + default: + initialOption = 1 /* B */; + break; + } + this.createToggle(table, "Do you want to move or copy the files to the vault?", "Move", "Copy", initialOption, (selectedOption) => { + if (selectedOption == 0 /* A */) { + this.selectedAction = "MOVE" /* MOVE */; + } else { + this.selectedAction = "COPY" /* COPY */; + } + }, true); + switch (this.lastEmbedOnImport) { + case "YES" /* YES */: + initialOption = 0 /* A */; + break; + case "NO" /* NO */: + default: + initialOption = 1 /* B */; + break; + } + this.createToggle(table, "Do you want to embed or link the files to the vault?", "Embed", "Link", initialOption, (selectedOption) => { + if (selectedOption == 0 /* A */) { + this.selectedEmbedOption = "YES" /* YES */; + } else { + this.selectedEmbedOption = "NO" /* NO */; + } + }, true); + this.createToggle(table, "Save this answer in the settings for the future?", "Yes", "No", 1 /* B */, (selectedOption) => { + if (selectedOption == 0 /* A */) { + this.rememberChoice = true; + } else { + this.rememberChoice = false; + } + }, true); + const importButtonContainer = container.createDiv({ cls: "import-buttons" }); + const importButton = importButtonContainer.createEl("button", { + text: "Import", + cls: "mod-cta" + }); + importButton.addEventListener("click", () => { + this.import(); + }); + setTimeout(() => { + importButton.focus(); + }, 0); + } + async import() { + this.resolveChoice({ + action: this.selectedAction, + embed: this.selectedEmbedOption, + rememberChoice: this.rememberChoice + }); + this.close(); + } + onClose() { + this.contentEl.empty(); + this.resolveChoice(null); + } +}; +var OverwriteChoiceModal = class extends import_obsidian.Modal { + constructor(app, plugin, originalFilePath, destFilePath) { + super(app); + this.plugin = plugin; + this.originalFilePath = originalFilePath; + this.destFilePath = destFilePath; + this.resolveChoice = () => { + }; + this.promise = new Promise((resolve) => { + this.resolveChoice = resolve; + }); + this.filename = getFilename(destFilePath); + } + onOpen() { + const { contentEl } = this; + const container = contentEl.createDiv({ cls: "import-attach-plugin" }); + container.createEl("h2", { text: "Import Files" }); + const paragraph = container.createEl("p"); + paragraph.append('You are trying to copy the file "'); + const origFileLink = paragraph.createEl("a", { + text: this.originalFilePath, + href: "#" + }); + origFileLink.addEventListener("click", (e) => { + e.preventDefault(); + window.require("electron").remote.shell.showItemInFolder(this.originalFilePath); + }); + paragraph.append('" into the vault, where a "'); + const vaultFileLink = paragraph.createEl("a", { + text: "file", + href: "#" + }); + vaultFileLink.addEventListener("click", (e) => { + e.preventDefault(); + window.require("electron").remote.shell.showItemInFolder(this.destFilePath); + }); + paragraph.append('" with the same name is already present.'); + container.createEl("p", { text: "How do you want to proceed?" }); + const buttonContainer = container.createDiv({ cls: "import-buttons" }); + const keepButton = buttonContainer.createEl("button", { + text: "Keep both", + cls: "mod-cta" + }); + keepButton.addEventListener("click", () => { + this.resolveChoice(1 /* KEEPBOTH */); + this.close(); + }); + const overwriteButton = buttonContainer.createEl("button", { + text: "Overwrite", + cls: "mod-warning" + }); + overwriteButton.addEventListener("click", () => { + this.resolveChoice(0 /* OVERWRITE */); + this.close(); + }); + const skipButton = buttonContainer.createEl("button", { + text: "Skip", + cls: "mod-cancel" + }); + skipButton.addEventListener("click", () => { + this.resolveChoice(2 /* SKIP */); + this.close(); + }); + setTimeout(() => { + keepButton.focus(); + }, 0); + } + onClose() { + this.contentEl.empty(); + this.resolveChoice(null); + } +}; +var DeleteAttachmentFolderModal = class extends import_obsidian.Modal { + // To resolve the promise. Initialize with a no-op function + constructor(app, plugin, attachmentFolderPath) { + super(app); + this.plugin = plugin; + this.attachmentFolderPath = attachmentFolderPath; + this.resolveChoice = () => { + }; + this.promise = new Promise((resolve) => { + this.resolveChoice = resolve; + }); + } + onOpen() { + const attachmentFolderPath_parsed = path2.parse(this.attachmentFolderPath); + const { contentEl } = this; + const container = contentEl.createDiv({ cls: "import-attach-plugin" }); + container.createEl("h2", { text: "Import Files" }); + const paragraph = container.createEl("p"); + paragraph.append('Do you want to move the attachment folder "'); + const fileLink = paragraph.createEl("a", { + text: attachmentFolderPath_parsed.name, + href: "#" + }); + fileLink.addEventListener("click", (e) => { + e.preventDefault(); + window.require("electron").remote.shell.openPath(this.attachmentFolderPath); + }); + paragraph.append('" to the system trash?'); + const buttonContainer = container.createDiv({ cls: "import-buttons" }); + const deleteButton = buttonContainer.createEl("button", { + text: "Delete", + cls: "mod-warning" + }); + deleteButton.addEventListener("click", () => { + this.resolveChoice(true); + this.close(); + }); + const cancelButton = buttonContainer.createEl("button", { + text: "Skip", + cls: "mod-cancel" + }); + cancelButton.addEventListener("click", () => { + this.resolveChoice(false); + this.close(); + }); + setTimeout(() => { + cancelButton.focus(); + }, 0); + } + onClose() { + this.contentEl.empty(); + this.resolveChoice(false); + } +}; +var ImportFromVaultChoiceModal = class extends import_obsidian.Modal { + // To resolve the promise. Initialize with a no-op function + constructor(app, plugin, vaultPath, originalFilePath, importAction) { + super(app); + this.plugin = plugin; + this.vaultPath = vaultPath; + this.originalFilePath = originalFilePath; + this.importAction = importAction; + this.resolveChoice = () => { + }; + this.promise = new Promise((resolve) => { + this.resolveChoice = resolve; + }); + } + onOpen() { + const { contentEl } = this; + const container = contentEl.createDiv({ cls: "import-attach-plugin" }); + container.createEl("h2", { text: "Import Files" }); + const paragraph = container.createEl("p"); + paragraph.append('The file you are trying to import "'); + const fileLink = paragraph.createEl("a", { + text: path2.relative(this.vaultPath, this.originalFilePath), + href: "#" + }); + fileLink.addEventListener("click", (e) => { + e.preventDefault(); + window.require("electron").remote.shell.showItemInFolder(this.originalFilePath); + }); + paragraph.append('" is already stored in the vault.'); + if (this.importAction == "MOVE" /* MOVE */) { + container.createEl("p", { text: "You intended to move the file. However, moving a file that is already in the vault to a new destination in the same vault is not supported; only copying and linking operations are allowed." }); + } + container.createEl("p", { text: "Do you want to make a copy or refer to the original file in the vault through a relative path?" }); + const buttonContainer = container.createDiv({ cls: "import-buttons" }); + const linkButton = buttonContainer.createEl("button", { + text: "Relative path", + cls: "mod-cta" + }); + linkButton.addEventListener("click", () => { + this.resolveChoice(1 /* LINK */); + this.close(); + }); + const copyButton = buttonContainer.createEl("button", { + text: "Copy", + cls: "mod-warning" + }); + copyButton.addEventListener("click", () => { + this.resolveChoice(0 /* COPY */); + this.close(); + }); + const skipButton = buttonContainer.createEl("button", { + text: "Skip", + cls: "mod-cancel" + }); + skipButton.addEventListener("click", () => { + this.resolveChoice(2 /* SKIP */); + this.close(); + }); + setTimeout(() => { + linkButton.focus(); + }, 0); + } + onClose() { + this.contentEl.empty(); + this.resolveChoice(null); + } +}; + +// src/main.ts +var import_fs2 = require("fs"); +var path3 = __toESM(require("path")); +var DEFAULT_SETTINGS = { + actionDroppedFilesOnImport: "ASK_USER" /* ASK_USER */, + // Default to asking the user + actionPastedFilesOnImport: "ASK_USER" /* ASK_USER */, + // Default to asking the user + lastActionPastedFilesOnImport: "COPY" /* COPY */, + // Default to copying files + lastActionDroppedFilesOnImport: "COPY" /* COPY */, + // Default to copying files + embedFilesOnImport: "ASK_USER" /* ASK_USER */, + // Default to linking files + lastEmbedFilesOnImport: "NO" /* NO */, + // Default to linking + multipleFilesImportType: "BULLETED" /* BULLETED */, + // Default to bulleted list when importing multiple files + relativeLocation: "VAULT" /* VAULT */, + // Default to vault + folderPath: "00 Meta/Attachments", + // Default to a folder in the vault + linkFormat: "ABSOLUTE" /* ABSOLUTE */, + attachmentName: "${original}", + // Default to the original name of the attachment + dateFormat: "YYYY_MM_DDTHH_mm_ss", + customDisplayText: true, + // Default to true + autoRenameAttachmentFolder: true, + // Default to true + autoDeleteAttachmentFolder: true, + // Default to true + confirmDeleteAttachmentFolder: true, + // Default to true + hideAttachmentFolders: true + // Default to true +}; +var ImportAttachments = class extends import_obsidian2.Plugin { + constructor(app, manifest) { + super(app, manifest); + this.settings = { ...DEFAULT_SETTINGS }; + this.renameCallbackEnabled = true; + this.deleteCallbackEnabled = true; + this.observer = null; + this.hideFolderNames = []; + const adapter = this.app.vault.adapter; + if (!(adapter instanceof import_obsidian2.FileSystemAdapter)) { + throw new Error("The vault folder could not be determined."); + } + this.vaultPath = adapter.getBasePath(); + } + setupObserver() { + this.configureHideFolderNames(); + const callback = (mutationsList, observer) => { + mutationsList.forEach((record) => { + var _a, _b; + if ((_b = (_a = record.target) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.classList.contains("nav-folder")) { + this.hideAttachmentFolders(); + } + }); + }; + const targetNode = document.body; + this.observer = new MutationObserver(callback); + const config = { + childList: true, + subtree: true + }; + this.observer.observe(targetNode, config); + } + async hideAttachmentFolders(recheckPreviouslyHiddenFolders) { + if (recheckPreviouslyHiddenFolders) { + document.querySelectorAll(".import-attach-hidden").forEach((folder) => { + if (folder.parentElement) { + folder.parentElement.style.height = ""; + } + if (folder.parentElement) { + folder.parentElement.style.overflow = ""; + } + folder.removeClass(".import-attach-hidden"); + }); + } + this.hideFolderNames.forEach((folderPattern) => { + if (folderPattern === "") + return; + const folderElements = document.querySelectorAll(folderPattern); + folderElements.forEach((folder) => { + if (!folder || !folder.parentElement) { + return; + } + folder.addClass("import-attach-hidden"); + folder.parentElement.style.height = this.settings.hideAttachmentFolders ? "0" : ""; + folder.parentElement.style.overflow = this.settings.hideAttachmentFolders ? "hidden" : ""; + }); + }); + } + splitAroundOriginal(input, placeholder) { + const firstIndex = input.indexOf(placeholder); + if (firstIndex === -1) { + return [input, ""]; + } + const lastIndex = input.lastIndexOf(placeholder); + const endOfPlaceholderIndex = lastIndex + placeholder.length; + const beforeFirst = input.substring(0, firstIndex); + const afterLast = input.substring(endOfPlaceholderIndex); + return [beforeFirst, afterLast]; + } + configureHideFolderNames() { + const placeholder = "${notename}"; + if (this.settings.folderPath.includes(placeholder)) { + const [startsWith, endsWith] = this.splitAroundOriginal(this.settings.folderPath, placeholder); + if (endsWith != "") { + this.hideFolderNames = [ + `[data-path$="${endsWith}"]` + ]; + } else if (startsWith != "") { + this.hideFolderNames = [ + `.nav-folder-title[data-path^="${startsWith}"], .nav-folder-title[data-path*="/${startsWith}"]` + ]; + } + } else { + this.hideFolderNames = [ + `[data-path$="/${this.settings.folderPath}"], [data-path="${this.settings.folderPath}"]` + ]; + } + } + async onload() { + await this.loadSettings(); + this.addSettingTab(new ImportAttachmentsSettingTab(this.app, this)); + this.setupObserver(); + this.addCommand({ + id: "move-file-to-vault-link", + name: "Move File to Vault as Link", + callback: () => this.chooseFileToImport({ + embed: false, + action: "MOVE" /* MOVE */ + }) + }); + this.addCommand({ + id: "move-file-to-vault-embed", + name: "Move File to Vault as Embedded", + callback: () => this.chooseFileToImport({ + embed: true, + action: "MOVE" /* MOVE */ + }) + }); + this.addCommand({ + id: "copy-file-to-vault-link", + name: "Copy File to Vault as Link", + callback: () => this.chooseFileToImport({ + embed: false, + action: "COPY" /* COPY */ + }) + }); + this.addCommand({ + id: "copy-file-to-vault-embed", + name: "Copy File to Vault as Embedded", + callback: () => this.chooseFileToImport({ + embed: true, + action: "COPY" /* COPY */ + }) + }); + this.addCommand({ + id: "open-attachments-folder", + name: "Open Attachments Folder", + callback: () => this.openAttachmentsFolder() + }); + this.registerEvent( + // check obsidian.d.ts for other types of events + this.app.workspace.on("editor-drop", async (evt, editor, view) => { + var _a; + if (evt.defaultPrevented) + return; + if (!(view instanceof import_obsidian2.MarkdownView)) { + console.error("No view provided"); + return; + } + const altKeyPressed = evt.altKey; + if (altKeyPressed) { + return; + } else { + evt.preventDefault(); + } + const doForceAsking = evt.shiftKey; + const files = (_a = evt == null ? void 0 : evt.dataTransfer) == null ? void 0 : _a.files; + if (files && files.length > 0) { + await this.handleFiles(files, editor, view, doForceAsking, 1 /* DRAG_AND_DROP */); + } else { + console.error("No files dropped"); + } + }) + ); + this.registerEvent( + // check obsidian.d.ts for other types of events + this.app.workspace.on("editor-paste", async (evt, editor, view) => { + if (evt.defaultPrevented) + return; + if (!(view instanceof import_obsidian2.MarkdownView)) { + console.error("No view provided"); + return; + } + const clipboardData = evt.clipboardData; + if (clipboardData) { + const files = clipboardData.files; + if (files && files.length > 0) { + evt.preventDefault(); + const doToggleEmbedPreference = false; + await this.handleFiles(files, editor, view, doToggleEmbedPreference, 0 /* PASTE */); + } else { + } + } + }) + ); + this.registerEvent( + this.app.vault.on("rename", async (newFile, oldPath) => { + if (!this.settings.autoRenameAttachmentFolder) { + return; + } + if (this.renameCallbackEnabled) { + const oldPath_parsed = path3.parse(oldPath); + if (oldPath_parsed.ext != ".md") { + return; + } + const oldAttachmentFolderPath = this.getAttachmentFolder(oldPath_parsed); + if (!oldAttachmentFolderPath) { + return; + } + if (await checkDirectoryExists(oldAttachmentFolderPath.attachmentsFolderPath)) { + const newAttachmentFolderPath = this.getAttachmentFolder(path3.parse(newFile.path)); + if (!newAttachmentFolderPath) { + return; + } + const oldPath2 = path3.relative(this.vaultPath, oldAttachmentFolderPath.attachmentsFolderPath); + const newPath = path3.relative(this.vaultPath, newAttachmentFolderPath.attachmentsFolderPath); + try { + this.renameCallbackEnabled = false; + await this.renameFile(oldPath2, newPath); + } catch (error) { + const msg = "Failed to rename the attachment folder"; + console.error(msg); + console.error("Original attachment folder:", oldPath2); + console.error("New attachment folder:", newPath); + console.error("Error msg:", error); + new import_obsidian2.Notice(msg + "."); + } finally { + this.renameCallbackEnabled = true; + } + } + } + }) + ); + this.registerEvent( + this.app.vault.on("delete", async (file) => { + if (!this.settings.autoDeleteAttachmentFolder) { + return; + } + if (!this.settings.folderPath.includes("${notename}")) { + return; + } + if (this.deleteCallbackEnabled) { + const file_parsed = path3.parse(file.path); + if (file_parsed.ext != ".md") { + return; + } + const attachmentFolderPath = this.getAttachmentFolder(file_parsed); + if (!attachmentFolderPath) { + return; + } + if (await checkDirectoryExists(attachmentFolderPath.attachmentsFolderPath)) { + const modal = new DeleteAttachmentFolderModal(this.app, this, attachmentFolderPath.attachmentsFolderPath); + modal.open(); + const choice = await modal.promise; + if (!choice) + return; + const filePath = path3.relative(this.vaultPath, attachmentFolderPath.attachmentsFolderPath); + try { + this.deleteCallbackEnabled = false; + await this.trashFile(filePath); + } catch (error) { + const msg = "Failed to remove the attachment folder"; + console.error(msg + ":", filePath); + console.error("Error msg:", error); + new import_obsidian2.Notice(msg + "."); + } finally { + this.deleteCallbackEnabled = true; + } + } + } + }) + ); + console.log("Loaded plugin Import Attachments+"); + } + onunload() { + if (this.observer) { + this.observer.disconnect(); + } + } + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + async saveSettings() { + await this.saveData(this.settings); + } + async renameFile(oldFilePath, newFilePath) { + try { + const file = this.app.vault.getAbstractFileByPath(oldFilePath); + if (file instanceof import_obsidian2.TAbstractFile) { + await this.app.fileManager.renameFile(file, newFilePath); + new import_obsidian2.Notice("Attachment folder renamed successfully."); + } else { + new import_obsidian2.Notice("Attachment folder could not be found at the given location."); + } + } catch (error) { + const msg = "Failed to rename file"; + console.error(msg + ":", error); + new import_obsidian2.Notice(msg + "."); + } + } + async trashFile(filePath) { + try { + const file = this.app.vault.getAbstractFileByPath(filePath); + if (file instanceof import_obsidian2.TAbstractFile) { + await await this.app.vault.adapter.trashSystem(filePath); + new import_obsidian2.Notice("Attachment folder moved to system trash successfully."); + } else { + new import_obsidian2.Notice("Attachment folder could not be found at the given location."); + } + } catch (error) { + const msg = "Failed to rename file"; + console.error(msg + ":", error); + new import_obsidian2.Notice(msg + "."); + } + } + async handleFiles(files, editor, view, doForceAsking, importType) { + const attachmentsFolder = this.getAttachmentFolder(); + if (!attachmentsFolder) { + return; + } + const { attachmentsFolderPath, currentNoteFolderPath } = attachmentsFolder; + let actionFilesOnImport = "COPY" /* COPY */; + let lastActionFilesOnImport = "COPY" /* COPY */; + switch (importType) { + case 1 /* DRAG_AND_DROP */: + actionFilesOnImport = this.settings.actionDroppedFilesOnImport; + lastActionFilesOnImport = this.settings.lastActionDroppedFilesOnImport; + break; + case 0 /* PASTE */: + actionFilesOnImport = this.settings.actionPastedFilesOnImport; + lastActionFilesOnImport = this.settings.lastActionPastedFilesOnImport; + break; + } + let embedOption = this.settings.embedFilesOnImport; + const lastEmbedOption = this.settings.lastEmbedFilesOnImport; + if (doForceAsking || actionFilesOnImport == "ASK_USER" /* ASK_USER */ || embedOption == "ASK_USER" /* ASK_USER */) { + const modal = new ImportActionTypeModal(this.app, this, lastActionFilesOnImport, lastEmbedOption); + modal.open(); + const choice = await modal.promise; + if (choice == null) + return; + actionFilesOnImport = choice.action; + switch (importType) { + case 1 /* DRAG_AND_DROP */: + if (choice.rememberChoice) { + this.settings.actionDroppedFilesOnImport = actionFilesOnImport; + } + this.settings.lastActionDroppedFilesOnImport = actionFilesOnImport; + break; + case 0 /* PASTE */: + if (choice.rememberChoice) { + this.settings.actionPastedFilesOnImport = actionFilesOnImport; + } + this.settings.lastActionPastedFilesOnImport = actionFilesOnImport; + break; + } + embedOption = choice.embed; + if (choice.rememberChoice) { + this.settings.embedFilesOnImport = embedOption; + } + this.settings.lastEmbedFilesOnImport = embedOption; + await this.saveSettings(); + } + const doEmbed = embedOption == "YES" /* YES */; + const importSettings = { + embed: doEmbed, + action: actionFilesOnImport + }; + this.moveFileToAttachmentsFolder(files, attachmentsFolderPath, currentNoteFolderPath, editor, view, importSettings); + } + getAttachmentFolder(noteFilePath = null) { + try { + if (!noteFilePath) { + noteFilePath = (() => { + const activeFile = this.app.workspace.getActiveFile(); + if (activeFile == null) { + throw new Error("The active note could not be determined."); + } + return path3.parse(activeFile.path); + })(); + } + if (!noteFilePath || noteFilePath.ext !== ".md") { + throw new Error("No Markdown file was found."); + } + if (!this.vaultPath) + return null; + const noteFolderPath = path3.join(this.vaultPath, noteFilePath.dir); + const notename = noteFilePath.name; + let referencePath = ""; + switch (this.settings.relativeLocation) { + case "VAULT" /* VAULT */: + referencePath = this.vaultPath; + break; + case "SAME" /* SAME */: + referencePath = noteFolderPath; + break; + } + const relativePath = this.settings.folderPath.replace(/\$\{notename\}/g, notename); + const attachmentsFolderPath = path3.join(referencePath, relativePath); + return { + attachmentsFolderPath, + currentNoteFolderPath: noteFolderPath + }; + } catch (error) { + if (error instanceof Error) { + console.error(error.message); + new import_obsidian2.Notice(error.message); + } else { + console.error("An unknown error occurred:", error); + new import_obsidian2.Notice("An unknown error occurred"); + } + return null; + } + } + async chooseFileToImport(importSettings) { + const markdownView = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView); + const editor = markdownView == null ? void 0 : markdownView.editor; + if (!editor) { + const msg = "No active markdown editor found."; + console.error(msg); + new import_obsidian2.Notice(msg); + return; + } + const attachmentsFolder = this.getAttachmentFolder(); + if (!attachmentsFolder) { + return; + } + const { attachmentsFolderPath, currentNoteFolderPath: referencePath } = attachmentsFolder; + const input = document.createElement("input"); + input.type = "file"; + input.multiple = true; + input.onchange = async (e) => { + const target = e.target; + const files = target.files; + if (files && files.length > 0) { + await this.moveFileToAttachmentsFolder(files, attachmentsFolderPath, referencePath, editor, markdownView, importSettings); + } else { + const msg = "No files selected or file access error."; + console.error(msg); + new import_obsidian2.Notice(msg); + } + }; + input.click(); + } + async moveFileToAttachmentsFolder(filesToImport, attachmentsFolderPath, currentNoteFolderPath, editor, view, importSettings) { + await ensureDirectoryExists(attachmentsFolderPath); + const cursor = editor.getCursor(); + if (filesToImport.length > 1 && this.settings.multipleFilesImportType != "INLINE" /* INLINE */) { + if (cursor.ch !== 0) { + editor.replaceRange("\n", cursor); + editor.setCursor({ line: cursor.line + 1, ch: 0 }); + } + } + const multipleFiles = filesToImport.length > 1; + const tasks = Array.from(filesToImport).map(async (fileToImport) => { + const originalFilePath = fileToImport.path; + let destFilePath = path3.join( + attachmentsFolderPath, + await createAttachmentName(this.settings.attachmentName, this.settings.dateFormat, originalFilePath) + ); + const existingFile = await checkFileExists(destFilePath); + if (existingFile && await arePathsSameFile(originalFilePath, destFilePath)) { + return destFilePath; + } + if (!this.vaultPath) + return null; + const inVault = await isFileInVault(this.vaultPath, originalFilePath); + if (inVault) { + const modal = new ImportFromVaultChoiceModal(this.app, this, this.vaultPath, inVault, importSettings.action); + modal.open(); + const choice = await modal.promise; + if (choice == null) { + return null; + } + switch (choice) { + case 2 /* SKIP */: + return null; + break; + case 1 /* LINK */: + importSettings.action = "LINK" /* LINK */; + break; + case 0 /* COPY */: + importSettings.action = "COPY" /* COPY */; + break; + } + } + if (existingFile && importSettings.action != "LINK" /* LINK */) { + const modal = new OverwriteChoiceModal(this.app, this, originalFilePath, destFilePath); + modal.open(); + const choice = await modal.promise; + if (choice == null) { + return null; + } + switch (choice) { + case 0 /* OVERWRITE */: + break; + case 1 /* KEEPBOTH */: + destFilePath = await findNewFilename(destFilePath); + if (destFilePath == null) { + return null; + } + break; + case 2 /* SKIP */: + return null; + break; + } + } + try { + switch (importSettings.action) { + case "MOVE" /* MOVE */: + await import_fs2.promises.rename(originalFilePath, destFilePath); + return destFilePath; + case "COPY" /* COPY */: + await import_fs2.promises.copyFile(originalFilePath, destFilePath); + return destFilePath; + case "LINK" /* LINK */: + default: + return originalFilePath; + } + } catch (error) { + const msg = "Failed to process the file"; + new import_obsidian2.Notice(msg + "."); + console.error(msg + ":", originalFilePath, error); + return null; + } + }); + const results = await Promise.all(tasks); + const counter = 0; + results.forEach((importedFilePath, index) => { + if (importedFilePath) { + this.insertLinkToEditor(currentNoteFolderPath, importedFilePath, editor, view, importSettings, multipleFiles ? index + 1 : 0); + } + }); + if (counter > 0) { + let operation = ""; + switch (importSettings.action) { + case "MOVE" /* MOVE */: + operation = "Moved"; + break; + case "COPY" /* COPY */: + operation = "Copied"; + break; + } + new import_obsidian2.Notice(`${operation} successfully ${counter} files to the attachments folder.`); + } + } + async openAttachmentsFolder() { + const attachmentsFolder = this.getAttachmentFolder(); + if (!attachmentsFolder) { + return; + } + const { attachmentsFolderPath } = attachmentsFolder; + if (!await checkDirectoryExists(attachmentsFolderPath)) { + const msg = "This note does not have an attachment folder."; + console.error(msg + ":", attachmentsFolderPath); + new import_obsidian2.Notice(msg + "."); + } + const { shell } = require("electron"); + shell.openPath(attachmentsFolder.attachmentsFolderPath); + } + insertLinkToEditor(currentNoteFolderPath, importedFilePath, editor, view, importSettings, counter) { + const filename = getFilename(importedFilePath); + let relativePath; + switch (this.settings.linkFormat) { + case "RELATIVE" /* RELATIVE */: + relativePath = path3.relative(currentNoteFolderPath, importedFilePath); + break; + case "ABSOLUTE" /* ABSOLUTE */: + default: + relativePath = path3.relative(this.vaultPath, importedFilePath); + break; + } + let prefix = ""; + let postfix = ""; + let customDisplay = ""; + if (counter > 0) { + switch (this.settings.multipleFilesImportType) { + case "BULLETED" /* BULLETED */: + prefix = "- "; + postfix = "\n"; + break; + case "NUMBERED" /* NUMBERED */: + prefix = `${counter}. `; + postfix = "\n"; + break; + case "INLINE" /* INLINE */: + if (counter > 1) { + prefix = "\n\n"; + } + break; + } + } + if (this.settings.customDisplayText) { + customDisplay = "|" + filename; + } + if (importSettings.embed) { + prefix = prefix + "!"; + } + const linkText = prefix + "[[" + relativePath + customDisplay + "]]" + postfix; + const cursor = editor.getCursor(); + editor.replaceRange(linkText, cursor); + if (counter == 0) { + if (this.settings.customDisplayText) { + const startCursorPos = { + line: cursor.line, + ch: cursor.ch + relativePath.length + prefix.length + 3 + }; + const endCursorPos = { + line: cursor.line, + ch: startCursorPos.ch + filename.length + }; + editor.setSelection(startCursorPos, endCursorPos); + } else { + const newCursorPos = { + line: cursor.line, + ch: cursor.ch + linkText.length + }; + editor.setCursor(newCursorPos); + } + } else { + const newCursorPos = { + line: cursor.line, + ch: cursor.ch + linkText.length + }; + editor.setCursor(newCursorPos); + } + } +}; +var ImportAttachmentsSettingTab = class extends import_obsidian2.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + display() { + const { containerEl } = this; + containerEl.empty(); + containerEl.createEl("h2", { text: "Settings for Import Attachments+ Plugin" }); + containerEl.createEl("h3", { text: "Import options" }); + new import_obsidian2.Setting(containerEl).setName("Whether to move or copy files that are drag-and-dropped?").setDesc("Choose whether files that are dragged and dropped into the editor should be moved or copied. Alternatively, the user is asked each time. By holding the SHIFT key pressed, you will be shown the import panel, however you configured this option.").addDropdown((dropdown) => { + dropdown.addOption("ASK_USER" /* ASK_USER */, "Ask each time"); + dropdown.addOption("MOVE" /* MOVE */, "Move"); + dropdown.addOption("COPY" /* COPY */, "Copy"); + dropdown.setValue(this.plugin.settings.actionDroppedFilesOnImport).onChange(async (value) => { + if (value in ImportActionType) { + this.plugin.settings.actionDroppedFilesOnImport = value; + if (value != "ASK_USER" /* ASK_USER */) { + this.plugin.settings.lastActionDroppedFilesOnImport = value; + } + await this.plugin.saveSettings(); + } else { + console.error("Invalid import action type:", value); + } + }); + }); + new import_obsidian2.Setting(containerEl).setName("Whether to move or copy files that are copy-and-pasted?").setDesc("Choose whether files that are copy and pasted into the editor should be moved or copied. Alternatively, the user is asked each time. By holding the SHIFT key pressed, you will be shown the import panel, however you configured this option.").addDropdown((dropdown) => { + dropdown.addOption("ASK_USER" /* ASK_USER */, "Ask each time"); + dropdown.addOption("MOVE" /* MOVE */, "Move"); + dropdown.addOption("COPY" /* COPY */, "Copy"); + dropdown.setValue(this.plugin.settings.actionPastedFilesOnImport).onChange(async (value) => { + if (value in ImportActionType) { + this.plugin.settings.actionPastedFilesOnImport = value; + if (value != "ASK_USER" /* ASK_USER */) { + this.plugin.settings.lastActionPastedFilesOnImport = value; + } + await this.plugin.saveSettings(); + } else { + console.error("Invalid import action type:", value); + } + }); + }); + new import_obsidian2.Setting(containerEl).setName("Embed imported documents:").setDesc("If this option is enabled, the files are imported as an embedded document; if it is deactivated, they are imported as a linked document. By holding the SHIFT key pressed, you will be shown the import panel, however you configured this option.").addDropdown((dropdown) => { + dropdown.addOption("ASK_USER" /* ASK_USER */, "Ask each time"); + dropdown.addOption("YES" /* YES */, "Yes"); + dropdown.addOption("NO" /* NO */, "No"); + dropdown.setValue(this.plugin.settings.embedFilesOnImport).onChange(async (value) => { + if (Object.values(YesNoTypes).includes(value)) { + this.plugin.settings.embedFilesOnImport = value; + if (value != "ASK_USER" /* ASK_USER */) { + this.plugin.settings.lastEmbedFilesOnImport = value; + } + await this.plugin.saveSettings(); + } else { + console.error("Invalid option selection:", value); + } + }); + }); + new import_obsidian2.Setting(containerEl).setName("Import multiple files as:").setDesc("Choose how to import multiple files: as a bulleted list, as a numbered list, or inline without using lists.").addDropdown((dropdown) => { + dropdown.addOption("BULLETED" /* BULLETED */, "Bulleted list"); + dropdown.addOption("NUMBERED" /* NUMBERED */, "Numbered list"); + dropdown.addOption("INLINE" /* INLINE */, "Inline"); + dropdown.setValue(this.plugin.settings.multipleFilesImportType).onChange(async (value) => { + if (Object.values(MultipleFilesImportTypes).includes(value)) { + this.plugin.settings.multipleFilesImportType = value; + await this.plugin.saveSettings(); + } else { + console.error("Invalid option selection:", value); + } + }); + }); + new import_obsidian2.Setting(containerEl).setName("Insert display text for links based on filename:").setDesc("If this option is enabled, the basename of the imported document is used as in place of the custom display text.").addToggle((toggle) => toggle.setValue(this.plugin.settings.customDisplayText).onChange(async (value) => { + this.plugin.settings.customDisplayText = value; + await this.plugin.saveSettings(); + })); + containerEl.createEl("h3", { text: "Attachment folder configuration" }); + new import_obsidian2.Setting(containerEl).setName("Default location for new attachments:").setDesc("The reference folder for importing new attachments.").addDropdown((dropdown) => { + dropdown.addOption("VAULT" /* VAULT */, "Vault folder"); + dropdown.addOption("SAME" /* SAME */, "Same folder as current file"); + dropdown.setValue(this.plugin.settings.relativeLocation).onChange(async (value) => { + if (Object.values(RelativeLocation).includes(value)) { + this.plugin.settings.relativeLocation = value; + await this.plugin.saveSettings(); + } else { + console.error("Invalid option selection:", value); + } + }); + }); + new import_obsidian2.Setting(containerEl).setName("Attachment folder where to import new attachments, relative to the default location:").setDesc("Where newly created notes are placed. Use ${notename} as a placeholder for the name of the note.").addText((text) => { + text.setPlaceholder("Enter folder path"); + text.setValue(this.plugin.settings.folderPath); + text.onChange(async (value) => { + this.plugin.settings.folderPath = value; + await this.plugin.saveSettings(); + this.plugin.configureHideFolderNames(); + await this.plugin.hideAttachmentFolders(true); + }); + }); + new import_obsidian2.Setting(containerEl).setName("Attachment link format:").setDesc("What types of links to use for the imported attachments.").addDropdown((dropdown) => { + dropdown.addOption("RELATIVE" /* RELATIVE */, "With respect to the note's path (relative path)"); + dropdown.addOption("ABSOLUTE" /* ABSOLUTE */, "With respect to the vault's path (absolute path)"); + dropdown.setValue(this.plugin.settings.linkFormat).onChange(async (value) => { + if (Object.values(LinkFormat).includes(value)) { + this.plugin.settings.linkFormat = value; + await this.plugin.saveSettings(); + } else { + console.error("Invalid option selection:", value); + } + }); + }); + new import_obsidian2.Setting(containerEl).setName("Name of the imported attachments:").setDesc(createFragment((frag) => { + frag.appendText("Choose how to name the imported attachments, using the following variables as a placeholder:"); + frag.createEl("ul").createEl("li", { text: "${original} for the name of the original file" }).createEl("li", { text: "${date} for the current date" }).createEl("li", { text: "${uuid} for a 128-bit Universally Unique Identifier" }).createEl("li", { text: "${md5} for a MD5 hash of the imported file" }); + })).addText((text) => { + text.setPlaceholder("Enter attachment name"); + text.setValue(this.plugin.settings.attachmentName); + text.onChange(async (value) => { + if (value.trim() == "") { + value = "${original}"; + } + this.plugin.settings.attachmentName = value; + await this.plugin.saveSettings(); + }); + }); + new import_obsidian2.Setting(containerEl).setName("Date format:").setDesc(createFragment((frag) => { + frag.appendText("Choose the date format, based on "); + frag.createEl("a", { + href: "https://momentjscom.readthedocs.io/en/latest/moment/04-displaying/01-format", + text: "momentjs" + }); + frag.appendText(" syntax."); + })).addText((text) => { + text.setPlaceholder("Enter date format"); + text.setValue(this.plugin.settings.dateFormat); + text.onChange(async (value) => { + this.plugin.settings.dateFormat = value; + await this.plugin.saveSettings(); + }); + }); + containerEl.createEl("h3", { text: "Attachment management" }); + new import_obsidian2.Setting(containerEl).setName("Rename the attachment folder automatically and update all links correspondingly:").setDesc("If this option is enabled, when you rename/move an note, if the renamed note has an attachment folder connected to it, its attachment folder is renamed/moved to a new name/location corresponding to the new name of the note.").addToggle((toggle) => toggle.setValue(this.plugin.settings.autoRenameAttachmentFolder).onChange(async (value) => { + this.plugin.settings.autoRenameAttachmentFolder = value; + await this.plugin.saveSettings(); + })); + new import_obsidian2.Setting(containerEl).setName("Delete the attachment folder automatically when the corresponding note is deleted:").setDesc("If this option is enabled, when you delete a note, if the deleted note has an attachment folder connected to it, its attachment folder will be deleted as well. Note: automatic deletion only works when the name of the attachment folder contains ${notename}.").addToggle((toggle) => toggle.setValue(this.plugin.settings.autoDeleteAttachmentFolder).onChange(async (value) => { + this.plugin.settings.autoDeleteAttachmentFolder = value; + await this.plugin.saveSettings(); + })); + new import_obsidian2.Setting(containerEl).setName("Ask confirmation before deleting the attachment folder:").setDesc("If enabled, the user is asked each time whether to delete the attachment folder.").addToggle((toggle) => toggle.setValue(this.plugin.settings.confirmDeleteAttachmentFolder).onChange(async (value) => { + this.plugin.settings.confirmDeleteAttachmentFolder = value; + await this.plugin.saveSettings(); + })); + containerEl.createEl("h3", { text: "Display of attachment folders" }); + new import_obsidian2.Setting(containerEl).setName("Hide attachment folders:").setDesc("If this option is enabled, the attachment folders will not be shown.").addToggle((toggle) => toggle.setValue(this.plugin.settings.hideAttachmentFolders).onChange(async (value) => { + this.plugin.settings.hideAttachmentFolders = value; + await this.plugin.saveSettings(); + await this.plugin.hideAttachmentFolders(true); + })); + } +}; diff --git a/src/types.d.ts b/src/obsidian-augment.d.ts similarity index 64% rename from src/types.d.ts rename to src/obsidian-augment.d.ts index beb44c8..125adf1 100644 --- a/src/types.d.ts +++ b/src/obsidian-augment.d.ts @@ -1,6 +1,5 @@ -// types.d.ts +// obsidian-augment.d.ts -// Extend the App interface to include openWithDefaultApp // declare module 'obsidian' { // interface App { // openWithDefaultApp(filePath: string): Promise; diff --git a/src/patchOpenFile.ts b/src/patchOpenFile.ts index 8760d24..69097d0 100644 --- a/src/patchOpenFile.ts +++ b/src/patchOpenFile.ts @@ -74,17 +74,12 @@ function patchOpenFile(plugin: ImportAttachments) { // Monkey patch the openFile method WorkspaceLeaf.prototype.openFile = async function patchedOpenFile(this: WorkspaceLeaf, file: TFile, openState?: OpenViewState): Promise { - + // console.log(`Meta key is pressed: ${metaKeyPressed}`); // console.log(`Alt key is pressed: ${altKeyPressed}`); const newEmptyLeave = this.getViewState()?.type == 'empty'; - if (newEmptyLeave) { - // close prepared empty tab - this.detach(); - } - if(metaKeyPressed){ if(altKeyPressed){ window.require('electron').remote.shell.showItemInFolder(path.join(plugin.vaultPath,file.path)); @@ -92,17 +87,18 @@ function patchOpenFile(plugin: ImportAttachments) { else { plugin.app.openWithDefaultApp(file.path); } - return; } else { if(originalOpenFile) { return originalOpenFile.call(this, file, openState); - } else { - return; } - } + if (newEmptyLeave) { + // close prepared empty tab + this.detach(); + } + return; } } diff --git a/src/types.ts b/src/types.ts index d1c4d57..bc94676 100644 --- a/src/types.ts +++ b/src/types.ts @@ -78,6 +78,10 @@ export enum OverwriteChoiceOptions { SKIP, } +export interface App { + openWithDefaultApp(filepath: string): Promise; +} + // Define a type for what resolveChoice will accept export type OverwriteChoiceResult = OverwriteChoiceOptions | null; diff --git a/src/global.d.ts b/src/types/global.d.ts similarity index 100% rename from src/global.d.ts rename to src/types/global.d.ts diff --git a/src/types/obsidian-augment.d.ts b/src/types/obsidian-augment.d.ts new file mode 100644 index 0000000..43f56d7 --- /dev/null +++ b/src/types/obsidian-augment.d.ts @@ -0,0 +1,9 @@ +// obsidian-augment.d.ts + +import 'obsidian' + +declare module 'obsidian' { + interface App { + openWithDefaultApp(filepath: string): Promise; + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 79f900a..681cd3a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ ], //"jsx": "react-jsx", // Required by React }, - "typeRoots": ["./src/global"], + + "typeRoots": ["./src/types","./node_modules/@types"], "include": [ "./src/**/*.ts" ]