From 649d7327818f3349b475c57098015eda0a6073ed Mon Sep 17 00:00:00 2001 From: ClaudiaFang Date: Fri, 22 May 2026 03:08:15 +0000 Subject: [PATCH] feat: support hidden file sync and expand binary extension list - Use FileSystemAdapter.list() recursively to discover hidden files (e.g. .claude) - Fix ensureParentDirs to use adapter.mkdir() for hidden directory creation - Expand BINARY_EXTENSIONS with modern image, audio, video, archive, and design formats - Exclude .agents/** from ESLint to prevent project-service parse errors Co-Authored-By: Claude Sonnet 4.6 --- .claude/skills/obsidian-development/SKILL.md | 61 ------------------- .../obsidian-development/evals/evals.json | 17 ------ .../references/pr-template.md | 35 ----------- eslint.config.mts | 1 + src/logic/sync-manager.ts | 10 ++- src/main.ts | 19 +++++- src/utils/path.ts | 25 ++++++-- 7 files changed, 42 insertions(+), 126 deletions(-) delete mode 100644 .claude/skills/obsidian-development/SKILL.md delete mode 100644 .claude/skills/obsidian-development/evals/evals.json delete mode 100644 .claude/skills/obsidian-development/references/pr-template.md diff --git a/.claude/skills/obsidian-development/SKILL.md b/.claude/skills/obsidian-development/SKILL.md deleted file mode 100644 index e8148ee..0000000 --- a/.claude/skills/obsidian-development/SKILL.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -name: obsidian-development -description: Use when developing Obsidian plugins to ensure TDD with Vitest, linting, and adherence to Obsidian API conventions. Trigger this skill when the user mentions building, testing, or modifying an Obsidian plugin, or when they ask for help with Obsidian-specific APIs (commands, ribbon icons, workspace events). ---- - -# Obsidian Plugin Development - -You are a specialist in building Obsidian plugins. This skill ensures you follow the project's specific conventions for testing, linting, and API usage. - -## Core Principles - -1. **Test-Driven Development (TDD)**: Always write tests before implementation for logic and service classes. -2. **API Hygiene**: Use Obsidian's lifecycle methods correctly (`onload`, `onunload`) and register all events/intervals for automatic cleanup. -3. **Linting**: Adhere to the project's ESLint configuration (`npm run lint`). - -## Development Workflow - -### 1. Testing with Vitest - -The project uses `vitest` for unit and integration testing. Tests are located in the `tests/` directory. - -- **Command**: `npm run test` or `npm run test:ui` for the interactive dashboard. -- **Pattern**: Create a corresponding `.test.ts` file for every logic or service file. -- **Example**: - ```typescript - // tests/logic/sync-manager.test.ts - import { describe, it, expect, vi } from 'vitest'; - // ... test implementation - ``` - -### 2. Obsidian API Conventions - -- **Settings**: Define settings in `src/settings.ts`. Use `this.loadData()` and `this.saveData()` in the main plugin class. -- **Commands**: Register commands using `this.addCommand()`. Always provide an `id` and `name`. -- **UI Elements**: Use `this.addRibbonIcon()` for sidebar buttons. -- **Cleanup**: - - Use `this.registerEvent()` for workspace events. - - Use `this.registerDomEvent()` for DOM events. - - Use `this.registerInterval()` for recurring tasks. - - **Why**: These ensure that when the plugin is disabled or uninstalled, Obsidian automatically cleans up the resources to prevent memory leaks. - -### 3. Build & Deployment - -- **Dev Mode**: `npm run dev` (watches for changes and rebuilds). -- **Production Build**: `npm run build` (runs type checking and minification). -- **Versioning**: `npm run version` (bumps version in `manifest.json` and updates `versions.json`). - -## Project Structure Reference - -- `src/main.ts`: Entry point (Main Plugin Class). -- `src/settings.ts`: Settings definitions and UI tab. -- `src/services/`: External integrations (e.g., GitLab API). -- `src/logic/`: Core business logic. -- `src/ui/`: Custom modals, views, or setting components. - -## Checklist for New Features - -- [ ] Draft test cases in `tests/` before writing logic. -- [ ] Use `registerEvent` instead of raw `on` handlers where possible. -- [ ] Verify `npm run lint` passes before completion. -- [ ] Ensure all persistent data is saved via `this.saveSettings()`. diff --git a/.claude/skills/obsidian-development/evals/evals.json b/.claude/skills/obsidian-development/evals/evals.json deleted file mode 100644 index 5cfafe4..0000000 --- a/.claude/skills/obsidian-development/evals/evals.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "skill_name": "obsidian-development", - "evals": [ - { - "id": 1, - "prompt": "Add a new command to the plugin that allows users to manually check for GitLab updates for the current file. Make sure it follows the project's testing and API conventions.", - "expected_output": "The model should first create a test case for the new logic, then implement the command in src/main.ts using this.addCommand(), and ensure any events are registered correctly.", - "files": ["src/main.ts", "src/logic/sync-manager.ts"] - }, - { - "id": 2, - "prompt": "I need a way to see the current sync status in the status bar. How should I implement this?", - "expected_output": "The model should recommend using this.addStatusBarItem() and registering any update events with this.registerEvent() to ensure cleanup.", - "files": ["src/main.ts"] - } - ] -} \ No newline at end of file diff --git a/.claude/skills/obsidian-development/references/pr-template.md b/.claude/skills/obsidian-development/references/pr-template.md deleted file mode 100644 index bd53ceb..0000000 --- a/.claude/skills/obsidian-development/references/pr-template.md +++ /dev/null @@ -1,35 +0,0 @@ -# Pull Request Template - -## Template - -```markdown -# I am submitting a new Community Plugin - -- [ ] I attest that I have done my best to deliver a high-quality plugin, am proud of the code I have written, and would recommend it to others. I commit to maintaining the plugin and being responsive to bug reports. If I am no longer able to maintain it, I will make reasonable efforts to find a successor maintainer or withdraw the plugin from the directory. - -## Repo URL - - -Link to my plugin: - -## Release Checklist -- [ ] I have tested the plugin on - - [ ] Windows - - [ ] macOS - - [ ] Linux - - [ ] Android _(if applicable)_ - - [ ] iOS _(if applicable)_ -- [ ] My GitHub release contains all required files (as individual files, not just in the source.zip / source.tar.gz) - - [ ] `main.js` - - [ ] `manifest.json` - - [ ] `styles.css` _(optional)_ -- [ ] GitHub release name matches the exact version number specified in my manifest.json (_**Note:** Use the exact version number, don't include a prefix `v`_) -- [ ] The `id` in my `manifest.json` matches the `id` in the `community-plugins.json` file. -- [ ] My README.md describes the plugin's purpose and provides clear usage instructions. -- [ ] I have read the developer policies at https://docs.obsidian.md/Developer+policies, and have assessed my plugin's adherence to these policies. -- [ ] I have read the tips in https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines and have self-reviewed my plugin to avoid these common pitfalls. -- [ ] I have added a license in the LICENSE file. -- [ ] My project respects and is compatible with the original license of any code from other plugins that I'm using. - I have given proper attribution to these other projects in my `README.md`. - -``` diff --git a/eslint.config.mts b/eslint.config.mts index 9e19aaa..2676d4b 100644 --- a/eslint.config.mts +++ b/eslint.config.mts @@ -36,6 +36,7 @@ export default tseslint.config( "versions.json", "main.js", ".claude/**", + ".agents/**", "coverage/**", ]), ); diff --git a/src/logic/sync-manager.ts b/src/logic/sync-manager.ts index a2feb60..d3af1c0 100644 --- a/src/logic/sync-manager.ts +++ b/src/logic/sync-manager.ts @@ -260,12 +260,10 @@ export class SyncManager { let cur = ''; for (let i = 0; i < parts.length - 1; i++) { cur += (i > 0 ? '/' : '') + parts[i]; - if (!this.app.vault.getAbstractFileByPath(cur)) { - try { - await this.app.vault.createFolder(cur); - } catch { - // already exists or failed - } + try { + await this.app.vault.adapter.mkdir(cur); + } catch { + // already exists or failed } } } diff --git a/src/main.ts b/src/main.ts index d228b51..5346996 100644 --- a/src/main.ts +++ b/src/main.ts @@ -138,13 +138,26 @@ export default class GitLabFilesPush extends Plugin { await this.runAllFiles('pull'); } + private async listAllFilesFromAdapter(dirPath: string): Promise { + const results: string[] = []; + try { + const { files, folders } = await this.app.vault.adapter.list(dirPath || ''); + results.push(...files); + for (const folder of folders) { + const sub = await this.listAllFilesFromAdapter(folder); + results.push(...sub); + } + } catch { /* ignore inaccessible dirs */ } + return results; + } + private async runAllFiles(op: 'push' | 'pull'): Promise { - const allFiles = this.app.vault.getFiles(); - let files = this.filterFilesByVaultFolder(allFiles); + const startPath = this.settings.vaultFolder || ''; + const allPaths = await this.listAllFilesFromAdapter(startPath); await this.gitService.listFiles(this.settings.branch); await this.gitignoreManager.loadGitignores(); - files = files.filter(f => !this.gitignoreManager.isIgnored(this.getNormalizedPath(f.path))); + const files = allPaths.filter(p => !this.gitignoreManager.isIgnored(this.getNormalizedPath(p))); if (files.length === 0) { new Notice(`No files to ${op} in the configured vault folder`); diff --git a/src/utils/path.ts b/src/utils/path.ts index 2b78dc7..5bc139d 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -1,8 +1,25 @@ export const BINARY_EXTENSIONS = new Set([ - 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'pdf', 'zip', 'gz', '7z', 'rar', - 'mp3', 'mp4', 'wav', 'ogg', 'webm', 'mov', 'avi', 'wmv', 'webp', - 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'epub', 'exe', 'dll', 'so', - 'ttf', 'woff', 'woff2', 'eot', 'wasm', 'dmg', 'iso' + // Images + 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'webp', + 'tif', 'tiff', 'heic', 'heif', 'avif', 'jxl', + // Documents + 'pdf', 'epub', + 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', + 'odt', 'ods', 'odp', + // Archives + 'zip', 'gz', 'tar', 'bz2', 'xz', 'zst', '7z', 'rar', + // Audio + 'mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'opus', 'aiff', + // Video + 'mp4', 'webm', 'mov', 'avi', 'wmv', 'mkv', 'flv', 'm4v', + // Fonts + 'ttf', 'otf', 'woff', 'woff2', 'eot', + // Executables & binaries + 'exe', 'dll', 'so', 'wasm', 'dmg', 'iso', 'apk', 'deb', 'rpm', + // Databases + 'sqlite', 'db', + // Design + 'psd', 'sketch', 'fig', ]); export function isBinaryPath(path: string): boolean {