Patch file explorer

This commit is contained in:
Andrea Alberti 2024-07-28 17:14:25 +02:00
parent 40b72d919f
commit a9dfdb8066
5 changed files with 263 additions and 103 deletions

View file

@ -43,8 +43,9 @@ import { patchOpenFile, unpatchOpenFile, addKeyListeners, removeKeyListeners } f
import { patchFilemanager, unpatchFilemanager } from 'patchFileManager';
import { EditorSelection } from '@codemirror/state';
import { patchImportFunctions, unpatchImportFunctions } from "patchImportFunctions";
import { patchImportFunctions, unpatchImportFunctions } from "patchImportFunctions";
import { patchFileExplorer, unpatchFileExplorer } from "patchFileExplorer";
import { monkeyPatchConsole, unpatchConsole } from "patchConsole";
// Default plugin settings
@ -150,6 +151,7 @@ export default class ImportAttachments extends Plugin {
folderElements.forEach((folder: Element) => {
if (folder.parentNode && folder.parentNode instanceof HTMLElement) {
// console.log(folder);
if (this.settings.hideAttachmentFolders) {
folder.parentNode.classList.add('import-plugin-hidden');
}
@ -236,7 +238,7 @@ export default class ImportAttachments extends Plugin {
this.addSettingTab(new ImportAttachmentsSettingTab(this.app, this));
// Set up the mutation observer for hiding folders
this.setupObserver();
// this.setupObserver();
// Monkey patches of the openFile function
if (Platform.isDesktopApp) {
@ -832,12 +834,31 @@ export default class ImportAttachments extends Plugin {
}
async openAttachmentsFolder() {
/*
// Monkey-path file explorer to hide attachment folders
patchFileExplorer(this);
return;
// Get the file explorer plugin
const fileExplorer = this.app.internalPlugins.getPluginById('file-explorer');
// Check if the plugin is loaded and enabled
if (fileExplorer) {
const leaves = this.app.workspace.getLeavesOfType('file-explorer');
for (const leaf of leaves) {
console.log(leaf);
const viewInstance = leaf.view;
if (viewInstance) {
break;
}
}
console.log(fileExplorer);
const viewFactory = fileExplorer.views['file-explorer'];
// Create a new instance of the view to identify its class
@ -847,41 +868,30 @@ export default class ImportAttachments extends Plugin {
// Identify the class used in createItemDom
const viewClass = viewInstance.constructor;
// Find the JJ class by exploring the prototype
// console.log('viewClass:', viewClass.prototype.createItemDom);
// Log the createItemDom method to inspect it
console.log('viewClass createFolderDom:', viewClass.prototype.createFolderDom);
// Use a dummy instance to explore properties (assuming createItemDom creates JJ instances)
const dummyItem = viewClass.prototype.createItemDom.call(viewInstance, {}); // Pass an empty object as an argument
// Log the dummy item to inspect it
// console.log('dummyItem:', dummyItem);
// Get the JJ class from the dummy item
const JJClass = dummyItem.constructor;
// Log the JJ class to ensure it's identified correctly
console.log('JJClass:', JJClass.prototype.updateTitle);
// Patch the JJ class prototype
if (JJClass.prototype.updateTitle) {
const originalUpdateTitle = JJClass.prototype.updateTitle;
JJClass.prototype.updateTitle = function(...args: any) {
console.log('Custom behavior before updateTitle');
console.log(args);
const result = originalUpdateTitle.apply(this, args);
console.log('Custom behavior after updateTitle');
// Patch the createItemDom method in viewClass prototype
if (viewClass.prototype.createFolderDom) {
const originalCreateFolderDom = viewClass.prototype.createFolderDom;
viewClass.prototype.createFolderDom = function(...args) {
console.log('Custom behavior before createFolderDom');
const result = originalCreateFolderDom.apply(this, args);
console.log(result);
console.log('Custom behavior after createFolderDom');
return result;
};
console.log('PATCHED');
console.log('createFolderDom PATCHED');
} else {
console.error('updateTitle method not found on JJ prototype');
console.error('createFolderDom method not found on viewClass prototype');
}
} else {
console.error('File Explorer plugin not found or not enabled');
}
return;
*/
const md_active_file = this.app.workspace.getActiveFile();
@ -906,10 +916,6 @@ export default class ImportAttachments extends Plugin {
const absAttachmentsFolderPath = Utils.joinPaths(this.vaultPath,attachmentsFolderPath);
if(!absAttachmentsFolderPath) return;
// TODO: Ask whether to create an Attachment folder
// Open the folder in the system's default file explorer
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { shell } = require('electron');

152
src/patchFileExplorer.ts Normal file
View file

@ -0,0 +1,152 @@
// patchImportFunctions.ts
/* eslint-disable @typescript-eslint/no-inferrable-types */
import { TFolder,WorkspaceLeaf, View, FileExplorerItem } from 'obsidian';
import ImportAttachments from 'main';
// Define a type for the view class with the required methods
interface FileExplorerView extends View {
createFolderDom(folder: TFolder): FileExplorerItem;
}
// Save a reference to the original method for the monkey patch
let originalCreateFolderDom: ((folder: TFolder) => FileExplorerItem) | null = null;
let viewClass: { new(leaf: WorkspaceLeaf): FileExplorerView } | null = null;
function unpatchFileExplorer() {
if (originalCreateFolderDom && viewClass) {
viewClass.prototype.createFolderDom = originalCreateFolderDom;
originalCreateFolderDom = null;
}
}
function matchesPatternWithHolder(plugin: ImportAttachments, filePath: string): boolean {
// Check if filePath starts with startsWidth or contains /startsWidth
const startsWithMatch = filePath.startsWith(plugin.folderPathStartsWith) || filePath.includes(`/${plugin.folderPathStartsWith}`);
// Check if filePath ends with endsWidth
const endsWithMatch = filePath.endsWith(plugin.folderPathEndsWith);
// Return true only if both conditions are met
return startsWithMatch && endsWithMatch;
}
function matchesPatternWithoutHolder(plugin: ImportAttachments, filePath: string): boolean {
const folderName = plugin.settings.folderPath;
return filePath.endsWith(`/${folderName}`) || filePath === folderName;
}
function patchFileExplorer(plugin: ImportAttachments) {
if (originalCreateFolderDom) { return; }
const leaves = plugin.app.workspace.getLeavesOfType('file-explorer');
for (const leaf of leaves) {
const viewInstance = leaf.view as FileExplorerView;
originalCreateFolderDom = viewInstance.constructor.prototype.createFolderDom;
viewClass = viewInstance.constructor as { new(leaf: WorkspaceLeaf): FileExplorerView };
break;
}
if (!viewClass) {
console.error("file-explorer plugin could not be patched.");
return;
}
viewClass.prototype.createFolderDom = function(this: FileExplorerView, folder: TFolder): unknown {
if(!originalCreateFolderDom) {
console.error('Something went wrong in patching file-explorer plugin.')
return;
}
const result = originalCreateFolderDom.apply(this, [folder]);
console.log(result);
if(result) {
const folderName = result.file.name;
let hidden = false;
if (plugin.settings.folderPath.includes("${notename}") && matchesPatternWithHolder(plugin,folderName)) {
console.log("FOUND",folderName);
hidden = true;
} else if (matchesPatternWithoutHolder(plugin,folderName)) {
console.log("FOUND",folderName);
hidden = true;
}
if(hidden) result.el.toggleClass("import-plugin-hidden",true);
console.log(result.constructor);
console.log('Custom behavior after createFolderDom');
}
return result;
};
console.log("PATCHED");
}
export { patchFileExplorer, unpatchFileExplorer };
/*
function matchesPatternWithHolder(filePath: string): boolean {
// Check if filePath starts with startsWidth or contains /startsWidth
const startsWithMatch = filePath.startsWith(plugin.folderPathStartsWith) || filePath.includes(`/${plugin.folderPathStartsWith}`);
// Check if filePath ends with endsWidth
const endsWithMatch = filePath.endsWith(plugin.folderPathEndsWith);
// Return true only if both conditions are met
return startsWithMatch && endsWithMatch;
}
function matchesPatternWithoutHolder(filePath: string): boolean {
const folderName = plugin.settings.folderPath;
return filePath.endsWith(`/${folderName}`) || filePath === folderName;
}
Vault.prototype.onChange = function (this: Vault, eventType: string, filePath: string, oldPath?: string, stat?: FileStats) {
if (!originalOnChange) {
throw new Error("Could not execute the original onChange function.");
}
// const fileExplorerPlugin = plugin.app.internalPlugins.getPluginById('file-explorer');
// if(filePath.endsWith('.xyz')) {
// // console.log("XYZ:",eventType);
// // console.log(originalOnChange);
// originalOnChange.call(this, eventType, filePath, oldPath, stat);
// return;
// }
// if(filePath.endsWith('.md')) {
// // console.log("MD:",eventType);
// return;
// }
if (eventType === 'folder-created') {
const placeholder = "${notename}";
if (plugin.settings.folderPath.includes(placeholder) && matchesPatternWithHolder(filePath)) {
// console.log("1",filePath)
// console.log(TFolder);
// Handle folder creation event manually
const newFolder = new TFolder(this, filePath);
this.fileMap[filePath] = newFolder;
// debugger
this.addChild(newFolder);
this.trigger("create", this.fileMap[filePath]);
return;
} else if (matchesPatternWithoutHolder(filePath)) {
console.log("2",filePath)
}
}
originalOnChange.call(this, eventType, filePath, oldPath, stat);
};
// const fileExplorer = plugin.app.internalPlugins.getPluginById('file-explorer');
// const xyz = fileExplorer.views['file-explorer']
// console.log(xyz);
*/

View file

@ -8,7 +8,6 @@ import ImportAttachments from 'main';
// Save a reference to the original method for the monkey patch
let originalGetAvailablePathForAttachments: ((fileName: string, extension: string, currentFile: TFile | null) => Promise<string>) | null = null;
let originalSaveAttachment: ((fileName: string, fileExtension: string, fileData: ArrayBuffer) => Promise<TFile>) | null = null;
let originalOnChange: ((eventType: string, filePath: string, oldPath?: string, stat?: FileStats) => void) | null = null;
let data: ArrayBuffer | null = null;
function unpatchImportFunctions() {
@ -21,11 +20,6 @@ function unpatchImportFunctions() {
App.prototype.saveAttachment = originalSaveAttachment;
originalSaveAttachment = null;
}
if(originalOnChange) {
Vault.prototype.onChange = originalOnChange;
originalOnChange = null;
}
}
function patchImportFunctions(plugin: ImportAttachments) {
@ -79,12 +73,13 @@ function patchImportFunctions(plugin: ImportAttachments) {
return newAttachmentFile;
}
return;
/*
if (!originalOnChange) {
originalOnChange = Vault.prototype.onChange;
}
*/
/*
function matchesPatternWithHolder(filePath: string): boolean {
// Check if filePath starts with startsWidth or contains /startsWidth
const startsWithMatch = filePath.startsWith(plugin.folderPathStartsWith) || filePath.includes(`/${plugin.folderPathStartsWith}`);
@ -108,44 +103,46 @@ function patchImportFunctions(plugin: ImportAttachments) {
// const fileExplorerPlugin = plugin.app.internalPlugins.getPluginById('file-explorer');
if(filePath.endsWith('.xyz')) {
// console.log("XYZ:",eventType);
// console.log(originalOnChange);
originalOnChange.call(this, eventType, filePath, oldPath, stat);
return;
}
if(filePath.endsWith('.md')) {
// console.log("MD:",eventType);
return;
}
// if (eventType === 'folder-created') {
// const placeholder = "${notename}";
// if (plugin.settings.folderPath.includes(placeholder) && matchesPatternWithHolder(filePath)) {
// // console.log("1",filePath)
// // console.log(TFolder);
// // Handle folder creation event manually
// const newFolder = new TFolder(this, filePath);
// this.fileMap[filePath] = newFolder;
// // debugger
// this.addChild(newFolder);
// this.trigger("create", this.fileMap[filePath]);
// return;
// } else if (matchesPatternWithoutHolder(filePath)) {
// console.log("2",filePath)
// }
// if(filePath.endsWith('.xyz')) {
// // console.log("XYZ:",eventType);
// // console.log(originalOnChange);
// originalOnChange.call(this, eventType, filePath, oldPath, stat);
// return;
// }
// if(filePath.endsWith('.md')) {
// // console.log("MD:",eventType);
// return;
// }
if (eventType === 'folder-created') {
const placeholder = "${notename}";
if (plugin.settings.folderPath.includes(placeholder) && matchesPatternWithHolder(filePath)) {
// console.log("1",filePath)
// console.log(TFolder);
// Handle folder creation event manually
const newFolder = new TFolder(this, filePath);
this.fileMap[filePath] = newFolder;
// debugger
this.addChild(newFolder);
this.trigger("create", this.fileMap[filePath]);
return;
} else if (matchesPatternWithoutHolder(filePath)) {
console.log("2",filePath)
}
}
originalOnChange.call(this, eventType, filePath, oldPath, stat);
};
// const fileExplorer = plugin.app.internalPlugins.getPluginById('file-explorer');
// const xyz = fileExplorer.views['file-explorer']
// console.log(xyz);
*/
}
export { patchImportFunctions, unpatchImportFunctions };

View file

@ -4,31 +4,44 @@ import 'obsidian';
import { EditorView } from '@codemirror/view';
declare module 'obsidian' {
interface App {
openWithDefaultApp(filepath: string): Promise<void>;
saveAttachment(fileName: string, fileExtension: string, fileData: ArrayBuffer): Promise<TFile>;
}
interface App {
openWithDefaultApp(filepath: string): Promise<void>;
saveAttachment(fileName: string, fileExtension: string, fileData: ArrayBuffer): Promise<TFile>;
}
interface Vault {
getConfig(configName: string): unknown;
getAvailablePathForAttachments(fileName: string, extension: string, currentFile: TFile | null): Promise<string>;
onChange(eventType: string, filePath: string, oldPath?: string, stat?: FileStats): void;
}
interface Vault {
getConfig(configName: string): unknown;
getAvailablePathForAttachments(fileName: string, extension: string, currentFile: TFile | null): Promise<string>;
onChange(eventType: string, filePath: string, oldPath?: string, stat?: FileStats): void;
}
interface MenuItem {
dom: HTMLElement;
callback: () => void;
}
interface MenuItem {
dom: HTMLElement;
callback: () => void;
}
interface Menu {
items: MenuItem[];
}
interface Menu {
items: MenuItem[];
}
interface FileManager {
promptForDeletion(file: TAbstractFile): Promise<void>;
}
interface FileManager {
promptForDeletion(file: TAbstractFile): Promise<void>;
}
interface Editor {
cm: EditorView;
}
interface FileExplorerItem {
el: HTMLDivElement;
selfEl: HTMLDivElement;
innerEl: HTMLDivElement;
coverEl: HTMLDivElement;
childrenEl: HTMLDivElement;
collapseEl: HTMLDivElement;
collapsed: boolean;
collapsible: boolean;
file: TFile;
}
interface Editor {
cm: EditorView;
}
}

View file

@ -1,15 +1,7 @@
.import-plugin-hidden {
height: 0;
overflow: hidden;
.nav-files-container:not(.import-plugin-hidden) .import-plugin-hidden {
display: none;
}
/*
.nav-files-container:not(.show-unsupported) .is-unsupported {
text-color: red;
display: block !important;
}
*/
.import-plugin table {
width: 100%;
border-collapse: collapse;