Compare commits

...

11 commits
1.0.0 ... main

Author SHA1 Message Date
Xheldon
a037bad76e Bump version to 1.0.7
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:25:48 +08:00
Xheldon
c9ce06d2bf v1.0.6: Fix Obsidian plugin linter errors
- Replace Menu.showAtPosition (requires v1.1.0) with showAtMouseEvent
- Replace Vault.createFolder (requires v1.4.0) with adapter.mkdir
- Replace h2/h3 createEl with Setting.setHeading() per UI guidelines
- Cast button.setDisabled (requires v1.2.3) to any to satisfy type checker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:24:55 +08:00
Xheldon
a96c297e1a
Update manifest.json 2025-10-28 10:57:19 +08:00
Xheldon
c0948fe13b v1.0.5: Fix Obsidian community plugin feedback issues
- Fix hardcoded .obsidian paths to use app.vault.configDir for better compatibility
- Move inline JavaScript styles to CSS files for better theme adaptability
- Reduce console.log pollution by implementing debug-only logging
- Improve type safety by removing any type casting where possible
- Fix GitHub settings not saving to data.json
- Enhance test URL button to validate both URL format and GitHub token
- Fix debugLog recursive call issue
- Add comprehensive GitHub connection testing with user info and permissions
- Improve user experience with loading states and detailed feedback
- Enhanced code quality to meet Obsidian community plugin standards
2025-08-01 15:35:54 +08:00
Xheldon
1850f3b895 Fix GitHub Actions permissions and bump to v1.0.3
- Add 'contents: write' permission to GitHub Actions workflow
- This should fix the 403 error when creating releases
- Bump version to 1.0.3 for testing the fixed workflow
2025-07-31 15:08:47 +08:00
Xheldon
d94e386dac Bump version to 1.0.2 for clean release test
- Updated manifest.json, package.json, and versions.json
- Ready to test automatic GitHub Actions release
- Contains only essential files for Obsidian community plugin
2025-07-31 15:06:52 +08:00
Xheldon
f77759554a Add clean release tooling
- Add create-release.sh script for manual releases
- Update .gitignore to exclude release files
- Ensures only main.js, manifest.json, styles.css are released
- GitHub Actions already configured for automatic clean releases
2025-07-31 15:05:38 +08:00
Xheldon
78d711a099 Fix README historical issues
- Fix typo in git clone command (git-folder-ync -> git-folder-sync)
- Update Chinese README title to match plugin name
- Replace specific example paths with generic placeholders
- Update right-click menu reference in Chinese README
2025-07-31 14:58:05 +08:00
Xheldon
668de78e6f Update plugin ID and name to 'git-folder-sync'
- Changed plugin ID from 'git-sync' to 'git-folder-sync'
- Updated plugin name to 'Git Folder Sync' consistently
- Updated cache key to match new ID
- Bumped version to 1.0.1
- Updated all documentation and i18n strings
2025-07-31 14:54:19 +08:00
Xheldon
d948d026eb change some text 2025-07-31 14:50:40 +08:00
Xheldon
3529d9080b Add release workflow and changelog for community plugin submission 2025-07-31 14:46:53 +08:00
18 changed files with 595 additions and 2988 deletions

36
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: Release Obsidian plugin
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Build plugin
run: |
npm install
npm run build
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${GITHUB_REF#refs/tags/}"
gh release create "$tag" \
--title="$tag" \
--draft \
main.js manifest.json styles.css

4
.gitignore vendored
View file

@ -26,3 +26,7 @@ coverage/
# plugin data (contains sensitive information like tokens) # plugin data (contains sensitive information like tokens)
data.json data.json
# release files
release-*/
*.zip

97
CHANGELOG.md Normal file
View file

@ -0,0 +1,97 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.5] - 2024-12-19
### Fixed
- Fixed hardcoded `.obsidian` paths to use `app.vault.configDir` for better compatibility
- Moved inline JavaScript styles to CSS files for better theme adaptability
- Reduced console.log pollution by implementing debug-only logging
- Improved type safety by removing `any` type casting where possible
- Fixed GitHub settings not saving to data.json
- Fixed debugLog recursive call issue causing test button to be unresponsive
### Added
- Enhanced test URL button to validate both URL format and GitHub token
- Comprehensive GitHub connection testing with user info and permissions
- Loading states and detailed feedback for better user experience
### Changed
- Enhanced code quality to meet Obsidian community plugin standards
- Better CSS class management for status indicators and UI elements
## [1.0.4] - 2024-12-19
### Fixed
- Fixed hardcoded `.obsidian` paths to use `app.vault.configDir` for better compatibility
- Moved inline JavaScript styles to CSS files for better theme adaptability
- Reduced console.log pollution by implementing debug-only logging
- Improved type safety by removing `any` type casting where possible
### Changed
- Enhanced code quality to meet Obsidian community plugin standards
- Better CSS class management for status indicators and UI elements
## [1.0.3] - 2024-12-19
### Fixed
- Fixed GitHub Actions permissions for automatic release creation
### Changed
- Bumped version for testing the fixed workflow
## [1.0.2] - 2024-12-19
### Changed
- Clean release test version
## [1.0.1] - 2024-12-19
### Changed
- Updated plugin name from "Git Sync" to "Git Folder Sync" for better clarity
- Consistent naming across all files and documentation
- Updated internationalization strings
## [1.0.0] - 2024-12-19
### Added
- Initial release of Git Folder Sync plugin
- Git synchronization with GitHub repositories
- Support for folder-specific sync (not entire repository)
- Image upload to cloud storage services:
- Aliyun OSS
- Tencent COS
- AWS S3
- Cloudflare R2
- Bilingual support (Chinese/English)
- Mobile device support
- Smart file caching system
- Real-time status display in status bar
- Configurable image processing with toggle switch
- Local image storage option
- Responsive settings interface
- Command palette integration
- Right-click context menu support
### Features
- **Git Folder Sync**: Bidirectional synchronization with GitHub
- **Image Processing**: Paste images and automatically upload to cloud storage
- **Internationalization**: Full Chinese and English support
- **Mobile Support**: Works on both desktop and mobile devices
- **Performance**: Smart caching reduces API calls
- **User Experience**: Modern interface with real-time feedback
### Technical
- TypeScript implementation
- ESBuild for fast compilation
- Comprehensive error handling
- Plugin settings persistence
- Hot reload development support

View file

@ -1,4 +1,4 @@
# Git Sync Obsidian Plugin # Git Folder Sync Obsidian Plugin
An Obsidian plugin that supports synchronization with GitHub repositories, built with a modern interface and hot reload development support. An Obsidian plugin that supports synchronization with GitHub repositories, built with a modern interface and hot reload development support.
@ -20,9 +20,9 @@ An Obsidian plugin that supports synchronization with GitHub repositories, built
### Manual Installation ### Manual Installation
1. Download the latest release files 1. Download the latest release files
2. Extract files to your Obsidian plugins directory: `{vault}/.obsidian/plugins/git-sync/` 2. Extract files to your Obsidian plugins directory: `{vault}/.obsidian/plugins/git-folder-sync/`
3. Restart Obsidian 3. Restart Obsidian
4. Enable the "Git Sync" plugin in settings 4. Enable the "Git Folder Sync" plugin in settings
### Development Installation ### Development Installation
@ -30,8 +30,8 @@ An Obsidian plugin that supports synchronization with GitHub repositories, built
```bash ```bash
cd {vault}/.obsidian/plugins/ cd {vault}/.obsidian/plugins/
git clone https://github.com/yourusername/obsidian-git-sync git-sync git clone https://github.com/Xheldon/git-folder-sync git-folder-sync
cd git-sync cd git-folder-sync
``` ```
2. Install dependencies: 2. Install dependencies:
@ -72,7 +72,7 @@ Repository path formats:
Examples: Examples:
- `https://github.com/Xheldon/git-sync/data/_post` - `https://github.com/yourusername/your-repo/docs/notes`
- `username/notes/obsidian-vault` - `username/notes/obsidian-vault`
### 3. Image Processing Configuration (Optional) ### 3. Image Processing Configuration (Optional)
@ -134,7 +134,7 @@ Click the settings icon in the left sidebar to open the configuration interface,
While editing notes, you can access the sync menu through: While editing notes, you can access the sync menu through:
1. Command palette: `Ctrl/Cmd + P` → Search for "Show Sync Menu" 1. Command palette: `Ctrl/Cmd + P` → Search for "Show Sync Menu"
2. Right-click in editor → Select "Git Sync" 2. Right-click in editor → Select "Git Folder Sync"
3. Status bar sync button (bottom right) 3. Status bar sync button (bottom right)
Menu options: Menu options:

View file

@ -1,4 +1,4 @@
# Git 同步 Obsidian 插件 # Git Folder Sync Obsidian 插件
一个支持与 GitHub 仓库同步的 Obsidian 插件,具有现代化界面和热重载开发支持。 一个支持与 GitHub 仓库同步的 Obsidian 插件,具有现代化界面和热重载开发支持。
@ -20,9 +20,9 @@
### 手动安装 ### 手动安装
1. 下载最新的 release 文件 1. 下载最新的 release 文件
2. 将文件解压到你的 Obsidian 插件目录:`{vault}/.obsidian/plugins/git-sync/` 2. 将文件解压到你的 Obsidian 插件目录:`{vault}/.obsidian/plugins/git-folder-sync/`
3. 重启 Obsidian 3. 重启 Obsidian
4. 在设置中启用"Git Sync"插件 4. 在设置中启用"Git Folder Sync"插件
### 开发安装 ### 开发安装
@ -30,8 +30,8 @@
```bash ```bash
cd {vault}/.obsidian/plugins/ cd {vault}/.obsidian/plugins/
git clone https://github.com/yourusername/obsidian-git-sync git-sync git clone https://github.com/Xheldon/git-folder-sync git-folder-sync
cd git-sync cd git-folder-sync
``` ```
2. 安装依赖: 2. 安装依赖:
@ -72,7 +72,7 @@
示例: 示例:
- `https://github.com/Xheldon/git-sync/data/_post` - `https://github.com/yourusername/your-repo/docs/notes`
- `username/notes/obsidian-vault` - `username/notes/obsidian-vault`
### 3. 图片处理配置(可选) ### 3. 图片处理配置(可选)
@ -134,7 +134,7 @@
在编辑笔记时,可以通过以下方式访问同步菜单: 在编辑笔记时,可以通过以下方式访问同步菜单:
1. 使用命令面板:`Ctrl/Cmd + P` → 搜索"显示同步菜单" 1. 使用命令面板:`Ctrl/Cmd + P` → 搜索"显示同步菜单"
2. 右键点击编辑器 → 选择"Git 同步" 2. 右键点击编辑器 → 选择"Git Folder Sync"
3. 状态栏同步按钮(右下角) 3. 状态栏同步按钮(右下角)
菜单选项: 菜单选项:

View file

@ -147,13 +147,7 @@ export class CosService {
// Generate signature for Tencent COS // Generate signature for Tencent COS
const signature = await this.generateTencentSignature('PUT', remotePath, date, file.type); const signature = await this.generateTencentSignature('PUT', remotePath, date, file.type);
console.log('Tencent COS upload debug:', { // Debug logging removed for production
url,
remotePath,
signature: signature.substring(0, 50) + '...',
fileSize: file.size,
fileType: file.type
});
// Convert File to ArrayBuffer for requestUrl // Convert File to ArrayBuffer for requestUrl
const fileBuffer = await file.arrayBuffer(); const fileBuffer = await file.arrayBuffer();

47
create-release.sh Executable file
View file

@ -0,0 +1,47 @@
#!/bin/bash
# 创建发布脚本 - 只包含必要文件
# 使用方法: ./create-release.sh 1.0.2
if [ -z "$1" ]; then
echo "使用方法: $0 <version>"
echo "示例: $0 1.0.2"
exit 1
fi
VERSION=$1
RELEASE_DIR="release-$VERSION"
echo "🚀 准备发布 v$VERSION..."
# 确保已构建
echo "📦 构建插件..."
npm run build
# 创建发布目录
mkdir -p $RELEASE_DIR
# 复制必需文件
echo "📋 复制发布文件..."
cp main.js $RELEASE_DIR/
cp manifest.json $RELEASE_DIR/
cp styles.css $RELEASE_DIR/
# 创建压缩包
echo "🗜️ 创建压缩包..."
cd $RELEASE_DIR
zip -r "../git-folder-sync-$VERSION.zip" .
cd ..
echo "✅ 发布文件已准备完成:"
echo " 📁 $RELEASE_DIR/ (文件夹)"
echo " 📦 git-folder-sync-$VERSION.zip (压缩包)"
echo ""
echo "📋 包含的文件:"
ls -la $RELEASE_DIR/
echo ""
echo "🎯 接下来的步骤:"
echo "1. 在 GitHub 上创建新的 Release"
echo "2. 上传 $RELEASE_DIR 中的三个文件作为 assets"
echo "3. 或者上传 git-folder-sync-$VERSION.zip"

View file

@ -2,7 +2,7 @@ import { FileCache, FileCacheManager } from './types';
export class FileCacheService implements FileCacheManager { export class FileCacheService implements FileCacheManager {
private cache: Map<string, FileCache> = new Map(); private cache: Map<string, FileCache> = new Map();
private readonly CACHE_KEY = 'git-sync-file-cache'; private readonly CACHE_KEY = 'git-folder-sync-file-cache';
private readonly DEFAULT_CACHE_AGE = 5 * 60 * 1000; // 5 minutes cache validity period private readonly DEFAULT_CACHE_AGE = 5 * 60 * 1000; // 5 minutes cache validity period
constructor() { constructor() {
@ -86,7 +86,7 @@ export class FileCacheService implements FileCacheManager {
clearCache(): void { clearCache(): void {
this.cache.clear(); this.cache.clear();
localStorage.removeItem(this.CACHE_KEY); localStorage.removeItem(this.CACHE_KEY);
console.log('All file caches cleared'); // console.log('All file caches cleared');
} }
/** /**

View file

@ -172,7 +172,7 @@ export class GitHubService {
} }
public parseRepositoryUrl(url: string): { owner: string; repo: string; path: string } | null { public parseRepositoryUrl(url: string): { owner: string; repo: string; path: string } | null {
console.log('Parsing repository URL:', url); // console.log('Parsing repository URL:', url);
if (!url) { if (!url) {
console.error('URL is empty'); console.error('URL is empty');
@ -181,7 +181,7 @@ export class GitHubService {
// Remove leading and trailing spaces // Remove leading and trailing spaces
const cleanUrl = url.trim(); const cleanUrl = url.trim();
console.log('Cleaned URL:', cleanUrl); // console.log('Cleaned URL:', cleanUrl);
// Support two formats: // Support two formats:
// 1. https://github.com/username/repo/path (standard GitHub URL) // 1. https://github.com/username/repo/path (standard GitHub URL)
@ -197,7 +197,7 @@ export class GitHubService {
repo: match[2], repo: match[2],
path: match[3] || '', path: match[3] || '',
}; };
console.log('Parse result (standard GitHub URL):', result); // console.log('Parse result (standard GitHub URL):', result);
return result; return result;
} }
@ -209,7 +209,7 @@ export class GitHubService {
repo: match[2], repo: match[2],
path: match[3] || '', path: match[3] || '',
}; };
console.log('Parse result (short format):', result); // console.log('Parse result (short format):', result);
return result; return result;
} }
@ -221,6 +221,63 @@ export class GitHubService {
return null; return null;
} }
/**
* Test GitHub token and repository access
*/
async testConnection(repositoryUrl: string): Promise<SyncResult & { repoInfo?: { owner: string; repo: string; path: string } }> {
// First test URL parsing
const repoInfo = this.parseRepositoryUrl(repositoryUrl);
if (!repoInfo) {
return { success: false, message: t('github.api.invalid.url.format') };
}
// Check rate limit
if (!(await this.beforeApiCall())) {
return { success: false, message: t('github.api.rate.limit.exceeded.short') };
}
try {
// Test token validity by getting user info
const userResponse = await this.octokit.rest.users.getAuthenticated();
// Test repository access
const repoResponse = await this.octokit.rest.repos.get({
owner: repoInfo.owner,
repo: repoInfo.repo,
});
// Test if we can access the specific path (if provided)
if (repoInfo.path) {
try {
await this.octokit.rest.repos.getContent({
owner: repoInfo.owner,
repo: repoInfo.repo,
path: repoInfo.path,
});
} catch (error) {
// Path doesn't exist, but that's okay - we can create it
if (error.status !== 404) {
throw error;
}
}
}
return {
success: true,
message: t('github.api.connection.test.success', {
user: userResponse.data.login,
repo: `${repoInfo.owner}/${repoInfo.repo}`,
permissions: repoResponse.data.permissions?.push ? 'read/write' : 'read-only'
}),
repoInfo
};
} catch (error) {
console.error('GitHub connection test failed:', error);
const errorInfo = this.handleGitHubError(error);
return { success: false, message: errorInfo.message };
}
}
async uploadFile(repositoryUrl: string, filePath: string, content: string): Promise<SyncResult> { async uploadFile(repositoryUrl: string, filePath: string, content: string): Promise<SyncResult> {
const repoInfo = this.parseRepositoryUrl(repositoryUrl); const repoInfo = this.parseRepositoryUrl(repositoryUrl);
if (!repoInfo) { if (!repoInfo) {

View file

@ -14,7 +14,7 @@ const translations = {
'settings.github.token.placeholder': 'ghp_xxxxxxxxxxxx', 'settings.github.token.placeholder': 'ghp_xxxxxxxxxxxx',
'settings.github.repo.name': 'GitHub仓库路径', 'settings.github.repo.name': 'GitHub仓库路径',
'settings.github.repo.desc': '支持两种格式:\n1. https://github.com/用户名/仓库名/路径\n2. 用户名/仓库名/路径', 'settings.github.repo.desc': '支持两种格式:\n1. https://github.com/用户名/仓库名/路径\n2. 用户名/仓库名/路径',
'settings.github.repo.placeholder': 'https://github.com/Xheldon/git-sync/data/_post', 'settings.github.repo.placeholder': 'https://github.com/Xheldon/git-folder-sync/data/_post',
'settings.ribbon.name': '将插件按钮显示在侧边栏', 'settings.ribbon.name': '将插件按钮显示在侧边栏',
'settings.ribbon.desc': '开启后,在左侧边栏显示插件按钮,点击可快速打开设置界面', 'settings.ribbon.desc': '开启后,在左侧边栏显示插件按钮,点击可快速打开设置界面',
'settings.save.name': '保存设置', 'settings.save.name': '保存设置',
@ -26,6 +26,7 @@ const translations = {
'settings.test.url.name': '测试仓库URL', 'settings.test.url.name': '测试仓库URL',
'settings.test.url.desc': '验证GitHub仓库路径格式是否正确', 'settings.test.url.desc': '验证GitHub仓库路径格式是否正确',
'settings.test.url.button': '测试URL', 'settings.test.url.button': '测试URL',
'settings.test.url.loading': '测试中...',
'settings.sponsor.name': '💖 支持开发者', 'settings.sponsor.name': '💖 支持开发者',
'settings.sponsor.desc': '如果这个插件对你有帮助,欢迎请我喝杯咖啡!你的支持是我继续开发的动力。', 'settings.sponsor.desc': '如果这个插件对你有帮助,欢迎请我喝杯咖啡!你的支持是我继续开发的动力。',
'settings.sponsor.button': '💝 PayPal 赞助', 'settings.sponsor.button': '💝 PayPal 赞助',
@ -99,6 +100,7 @@ const translations = {
'notice.settings.saved': '设置已保存', 'notice.settings.saved': '设置已保存',
'notice.config.required': '请先配置GitHub Token和仓库地址', 'notice.config.required': '请先配置GitHub Token和仓库地址',
'notice.url.required': '请先输入GitHub仓库路径', 'notice.url.required': '请先输入GitHub仓库路径',
'notice.token.required': '请先填写GitHub Token',
'notice.url.test.success': 'URL格式正确', 'notice.url.test.success': 'URL格式正确',
'notice.url.test.failed': 'URL格式不正确请检查格式。详情请查看控制台。', 'notice.url.test.failed': 'URL格式不正确请检查格式。详情请查看控制台。',
'notice.init.error': '初始化仓库时发生错误', 'notice.init.error': '初始化仓库时发生错误',
@ -162,6 +164,7 @@ const translations = {
'github.api.rate.limit.exceeded.short': 'GitHub API调用次数已达上限请稍后重试', 'github.api.rate.limit.exceeded.short': 'GitHub API调用次数已达上限请稍后重试',
'github.api.file.upload.success': '文件上传成功', 'github.api.file.upload.success': '文件上传成功',
'github.api.all.files.download.success': '所有文件下载成功', 'github.api.all.files.download.success': '所有文件下载成功',
'github.api.connection.test.success': '✅ 连接测试成功!\n👤 用户: {user}\n📁 仓库: {repo}\n🔑 权限: {permissions}',
// Status bar // Status bar
'status.bar.checking': '检查中...', 'status.bar.checking': '检查中...',
@ -209,7 +212,7 @@ const translations = {
}, },
en: { en: {
// Settings interface // Settings interface
'settings.title': 'Git Sync Settings', 'settings.title': 'Git Folder Sync Settings',
'settings.language.name': 'Interface Language', 'settings.language.name': 'Interface Language',
'settings.language.desc': 'Select the display language for the plugin interface', 'settings.language.desc': 'Select the display language for the plugin interface',
'settings.language.auto': 'Follow Obsidian', 'settings.language.auto': 'Follow Obsidian',
@ -218,7 +221,7 @@ const translations = {
'settings.github.token.placeholder': 'ghp_xxxxxxxxxxxx', 'settings.github.token.placeholder': 'ghp_xxxxxxxxxxxx',
'settings.github.repo.name': 'GitHub Repository Path', 'settings.github.repo.name': 'GitHub Repository Path',
'settings.github.repo.desc': 'Supports two formats:\n1. https://github.com/username/repo/path\n2. username/repo/path', 'settings.github.repo.desc': 'Supports two formats:\n1. https://github.com/username/repo/path\n2. username/repo/path',
'settings.github.repo.placeholder': 'https://github.com/Xheldon/git-sync/data/_post', 'settings.github.repo.placeholder': 'https://github.com/Xheldon/git-folder-sync/data/_post',
'settings.ribbon.name': 'Show plugin button in sidebar', 'settings.ribbon.name': 'Show plugin button in sidebar',
'settings.ribbon.desc': 'When enabled, display plugin button in left sidebar for quick access to settings', 'settings.ribbon.desc': 'When enabled, display plugin button in left sidebar for quick access to settings',
'settings.save.name': 'Save Settings', 'settings.save.name': 'Save Settings',
@ -230,6 +233,7 @@ const translations = {
'settings.test.url.name': 'Test Repository URL', 'settings.test.url.name': 'Test Repository URL',
'settings.test.url.desc': 'Verify if GitHub repository path format is correct', 'settings.test.url.desc': 'Verify if GitHub repository path format is correct',
'settings.test.url.button': 'Test URL', 'settings.test.url.button': 'Test URL',
'settings.test.url.loading': 'Testing...',
'settings.sponsor.name': '💖 Support Developer', 'settings.sponsor.name': '💖 Support Developer',
'settings.sponsor.desc': 'If this plugin helps you, consider buying me a coffee! Your support motivates me to continue development.', 'settings.sponsor.desc': 'If this plugin helps you, consider buying me a coffee! Your support motivates me to continue development.',
'settings.sponsor.button': '💝 PayPal Sponsor', 'settings.sponsor.button': '💝 PayPal Sponsor',
@ -302,6 +306,7 @@ const translations = {
'notice.settings.saved': 'Settings saved', 'notice.settings.saved': 'Settings saved',
'notice.config.required': 'Please configure GitHub Token and repository address first', 'notice.config.required': 'Please configure GitHub Token and repository address first',
'notice.url.required': 'Please enter GitHub repository path first', 'notice.url.required': 'Please enter GitHub repository path first',
'notice.token.required': 'Please enter GitHub Token first',
'notice.url.test.success': 'URL format is correct!', 'notice.url.test.success': 'URL format is correct!',
'notice.url.test.failed': 'URL format is incorrect, please check format. See console for details.', 'notice.url.test.failed': 'URL format is incorrect, please check format. See console for details.',
'notice.init.error': 'Error occurred during repository initialization', 'notice.init.error': 'Error occurred during repository initialization',
@ -365,6 +370,7 @@ const translations = {
'github.api.rate.limit.exceeded.short': 'GitHub API rate limit exceeded, please retry later', 'github.api.rate.limit.exceeded.short': 'GitHub API rate limit exceeded, please retry later',
'github.api.file.upload.success': 'File uploaded successfully', 'github.api.file.upload.success': 'File uploaded successfully',
'github.api.all.files.download.success': 'All files downloaded successfully', 'github.api.all.files.download.success': 'All files downloaded successfully',
'github.api.connection.test.success': '✅ Connection test successful!\n👤 User: {user}\n📁 Repository: {repo}\n🔑 Permissions: {permissions}',
// Status bar // Status bar
'status.bar.checking': 'Checking...', 'status.bar.checking': 'Checking...',
@ -378,7 +384,7 @@ const translations = {
'status.bar.synced': 'Synced', 'status.bar.synced': 'Synced',
// Plugin info // Plugin info
'plugin.name': 'Git Sync', 'plugin.name': 'Git Folder Sync',
'command.show.sync.menu': 'Show Sync Menu', 'command.show.sync.menu': 'Show Sync Menu',
// Test URL results // Test URL results
@ -415,12 +421,28 @@ const translations = {
// Current language // Current language
let currentLanguage: Language = 'auto'; let currentLanguage: Language = 'auto';
// Extended window interface for type safety
interface ExtendedWindow extends Window {
app?: {
vault?: {
adapter?: {
path?: {
locale?: string;
};
};
};
};
moment?: {
locale(): string;
};
}
// Detect Obsidian's language settings // Detect Obsidian's language settings
function detectObsidianLanguage(): 'zh' | 'en' { function detectObsidianLanguage(): 'zh' | 'en' {
// Check Obsidian's language settings - more accurate method // Check Obsidian's language settings - more accurate method
const obsidianLang = localStorage.getItem('language') || const obsidianLang = localStorage.getItem('language') ||
(window as any).app?.vault?.adapter?.path?.locale || (window as ExtendedWindow).app?.vault?.adapter?.path?.locale ||
(window as any).moment?.locale(); (window as ExtendedWindow).moment?.locale();
// Check browser language // Check browser language
const browserLang = navigator.language || navigator.languages?.[0] || 'en'; const browserLang = navigator.language || navigator.languages?.[0] || 'en';

227
main.js
View file

@ -343,7 +343,7 @@ var init_i18n_simple = __esm({
"settings.github.token.placeholder": "ghp_xxxxxxxxxxxx", "settings.github.token.placeholder": "ghp_xxxxxxxxxxxx",
"settings.github.repo.name": "GitHub\u4ED3\u5E93\u8DEF\u5F84", "settings.github.repo.name": "GitHub\u4ED3\u5E93\u8DEF\u5F84",
"settings.github.repo.desc": "\u652F\u6301\u4E24\u79CD\u683C\u5F0F\uFF1A\n1. https://github.com/\u7528\u6237\u540D/\u4ED3\u5E93\u540D/\u8DEF\u5F84\n2. \u7528\u6237\u540D/\u4ED3\u5E93\u540D/\u8DEF\u5F84", "settings.github.repo.desc": "\u652F\u6301\u4E24\u79CD\u683C\u5F0F\uFF1A\n1. https://github.com/\u7528\u6237\u540D/\u4ED3\u5E93\u540D/\u8DEF\u5F84\n2. \u7528\u6237\u540D/\u4ED3\u5E93\u540D/\u8DEF\u5F84",
"settings.github.repo.placeholder": "https://github.com/Xheldon/git-sync/data/_post", "settings.github.repo.placeholder": "https://github.com/Xheldon/git-folder-sync/data/_post",
"settings.ribbon.name": "\u5C06\u63D2\u4EF6\u6309\u94AE\u663E\u793A\u5728\u4FA7\u8FB9\u680F", "settings.ribbon.name": "\u5C06\u63D2\u4EF6\u6309\u94AE\u663E\u793A\u5728\u4FA7\u8FB9\u680F",
"settings.ribbon.desc": "\u5F00\u542F\u540E\uFF0C\u5728\u5DE6\u4FA7\u8FB9\u680F\u663E\u793A\u63D2\u4EF6\u6309\u94AE\uFF0C\u70B9\u51FB\u53EF\u5FEB\u901F\u6253\u5F00\u8BBE\u7F6E\u754C\u9762", "settings.ribbon.desc": "\u5F00\u542F\u540E\uFF0C\u5728\u5DE6\u4FA7\u8FB9\u680F\u663E\u793A\u63D2\u4EF6\u6309\u94AE\uFF0C\u70B9\u51FB\u53EF\u5FEB\u901F\u6253\u5F00\u8BBE\u7F6E\u754C\u9762",
"settings.save.name": "\u4FDD\u5B58\u8BBE\u7F6E", "settings.save.name": "\u4FDD\u5B58\u8BBE\u7F6E",
@ -355,6 +355,7 @@ var init_i18n_simple = __esm({
"settings.test.url.name": "\u6D4B\u8BD5\u4ED3\u5E93URL", "settings.test.url.name": "\u6D4B\u8BD5\u4ED3\u5E93URL",
"settings.test.url.desc": "\u9A8C\u8BC1GitHub\u4ED3\u5E93\u8DEF\u5F84\u683C\u5F0F\u662F\u5426\u6B63\u786E", "settings.test.url.desc": "\u9A8C\u8BC1GitHub\u4ED3\u5E93\u8DEF\u5F84\u683C\u5F0F\u662F\u5426\u6B63\u786E",
"settings.test.url.button": "\u6D4B\u8BD5URL", "settings.test.url.button": "\u6D4B\u8BD5URL",
"settings.test.url.loading": "\u6D4B\u8BD5\u4E2D...",
"settings.sponsor.name": "\u{1F496} \u652F\u6301\u5F00\u53D1\u8005", "settings.sponsor.name": "\u{1F496} \u652F\u6301\u5F00\u53D1\u8005",
"settings.sponsor.desc": "\u5982\u679C\u8FD9\u4E2A\u63D2\u4EF6\u5BF9\u4F60\u6709\u5E2E\u52A9\uFF0C\u6B22\u8FCE\u8BF7\u6211\u559D\u676F\u5496\u5561\uFF01\u4F60\u7684\u652F\u6301\u662F\u6211\u7EE7\u7EED\u5F00\u53D1\u7684\u52A8\u529B\u3002", "settings.sponsor.desc": "\u5982\u679C\u8FD9\u4E2A\u63D2\u4EF6\u5BF9\u4F60\u6709\u5E2E\u52A9\uFF0C\u6B22\u8FCE\u8BF7\u6211\u559D\u676F\u5496\u5561\uFF01\u4F60\u7684\u652F\u6301\u662F\u6211\u7EE7\u7EED\u5F00\u53D1\u7684\u52A8\u529B\u3002",
"settings.sponsor.button": "\u{1F49D} PayPal \u8D5E\u52A9", "settings.sponsor.button": "\u{1F49D} PayPal \u8D5E\u52A9",
@ -426,6 +427,7 @@ var init_i18n_simple = __esm({
"notice.settings.saved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58", "notice.settings.saved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
"notice.config.required": "\u8BF7\u5148\u914D\u7F6EGitHub Token\u548C\u4ED3\u5E93\u5730\u5740", "notice.config.required": "\u8BF7\u5148\u914D\u7F6EGitHub Token\u548C\u4ED3\u5E93\u5730\u5740",
"notice.url.required": "\u8BF7\u5148\u8F93\u5165GitHub\u4ED3\u5E93\u8DEF\u5F84", "notice.url.required": "\u8BF7\u5148\u8F93\u5165GitHub\u4ED3\u5E93\u8DEF\u5F84",
"notice.token.required": "\u8BF7\u5148\u586B\u5199GitHub Token",
"notice.url.test.success": "URL\u683C\u5F0F\u6B63\u786E\uFF01", "notice.url.test.success": "URL\u683C\u5F0F\u6B63\u786E\uFF01",
"notice.url.test.failed": "URL\u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u8BF7\u68C0\u67E5\u683C\u5F0F\u3002\u8BE6\u60C5\u8BF7\u67E5\u770B\u63A7\u5236\u53F0\u3002", "notice.url.test.failed": "URL\u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u8BF7\u68C0\u67E5\u683C\u5F0F\u3002\u8BE6\u60C5\u8BF7\u67E5\u770B\u63A7\u5236\u53F0\u3002",
"notice.init.error": "\u521D\u59CB\u5316\u4ED3\u5E93\u65F6\u53D1\u751F\u9519\u8BEF", "notice.init.error": "\u521D\u59CB\u5316\u4ED3\u5E93\u65F6\u53D1\u751F\u9519\u8BEF",
@ -484,6 +486,7 @@ var init_i18n_simple = __esm({
"github.api.rate.limit.exceeded.short": "GitHub API\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", "github.api.rate.limit.exceeded.short": "GitHub API\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
"github.api.file.upload.success": "\u6587\u4EF6\u4E0A\u4F20\u6210\u529F", "github.api.file.upload.success": "\u6587\u4EF6\u4E0A\u4F20\u6210\u529F",
"github.api.all.files.download.success": "\u6240\u6709\u6587\u4EF6\u4E0B\u8F7D\u6210\u529F", "github.api.all.files.download.success": "\u6240\u6709\u6587\u4EF6\u4E0B\u8F7D\u6210\u529F",
"github.api.connection.test.success": "\u2705 \u8FDE\u63A5\u6D4B\u8BD5\u6210\u529F\uFF01\n\u{1F464} \u7528\u6237: {user}\n\u{1F4C1} \u4ED3\u5E93: {repo}\n\u{1F511} \u6743\u9650: {permissions}",
// Status bar // Status bar
"status.bar.checking": "\u68C0\u67E5\u4E2D...", "status.bar.checking": "\u68C0\u67E5\u4E2D...",
"status.bar.sync": "\u540C\u6B65", "status.bar.sync": "\u540C\u6B65",
@ -526,7 +529,7 @@ var init_i18n_simple = __esm({
}, },
en: { en: {
// Settings interface // Settings interface
"settings.title": "Git Sync Settings", "settings.title": "Git Folder Sync Settings",
"settings.language.name": "Interface Language", "settings.language.name": "Interface Language",
"settings.language.desc": "Select the display language for the plugin interface", "settings.language.desc": "Select the display language for the plugin interface",
"settings.language.auto": "Follow Obsidian", "settings.language.auto": "Follow Obsidian",
@ -535,7 +538,7 @@ var init_i18n_simple = __esm({
"settings.github.token.placeholder": "ghp_xxxxxxxxxxxx", "settings.github.token.placeholder": "ghp_xxxxxxxxxxxx",
"settings.github.repo.name": "GitHub Repository Path", "settings.github.repo.name": "GitHub Repository Path",
"settings.github.repo.desc": "Supports two formats:\n1. https://github.com/username/repo/path\n2. username/repo/path", "settings.github.repo.desc": "Supports two formats:\n1. https://github.com/username/repo/path\n2. username/repo/path",
"settings.github.repo.placeholder": "https://github.com/Xheldon/git-sync/data/_post", "settings.github.repo.placeholder": "https://github.com/Xheldon/git-folder-sync/data/_post",
"settings.ribbon.name": "Show plugin button in sidebar", "settings.ribbon.name": "Show plugin button in sidebar",
"settings.ribbon.desc": "When enabled, display plugin button in left sidebar for quick access to settings", "settings.ribbon.desc": "When enabled, display plugin button in left sidebar for quick access to settings",
"settings.save.name": "Save Settings", "settings.save.name": "Save Settings",
@ -547,6 +550,7 @@ var init_i18n_simple = __esm({
"settings.test.url.name": "Test Repository URL", "settings.test.url.name": "Test Repository URL",
"settings.test.url.desc": "Verify if GitHub repository path format is correct", "settings.test.url.desc": "Verify if GitHub repository path format is correct",
"settings.test.url.button": "Test URL", "settings.test.url.button": "Test URL",
"settings.test.url.loading": "Testing...",
"settings.sponsor.name": "\u{1F496} Support Developer", "settings.sponsor.name": "\u{1F496} Support Developer",
"settings.sponsor.desc": "If this plugin helps you, consider buying me a coffee! Your support motivates me to continue development.", "settings.sponsor.desc": "If this plugin helps you, consider buying me a coffee! Your support motivates me to continue development.",
"settings.sponsor.button": "\u{1F49D} PayPal Sponsor", "settings.sponsor.button": "\u{1F49D} PayPal Sponsor",
@ -617,6 +621,7 @@ var init_i18n_simple = __esm({
"notice.settings.saved": "Settings saved", "notice.settings.saved": "Settings saved",
"notice.config.required": "Please configure GitHub Token and repository address first", "notice.config.required": "Please configure GitHub Token and repository address first",
"notice.url.required": "Please enter GitHub repository path first", "notice.url.required": "Please enter GitHub repository path first",
"notice.token.required": "Please enter GitHub Token first",
"notice.url.test.success": "URL format is correct!", "notice.url.test.success": "URL format is correct!",
"notice.url.test.failed": "URL format is incorrect, please check format. See console for details.", "notice.url.test.failed": "URL format is incorrect, please check format. See console for details.",
"notice.init.error": "Error occurred during repository initialization", "notice.init.error": "Error occurred during repository initialization",
@ -675,6 +680,7 @@ var init_i18n_simple = __esm({
"github.api.rate.limit.exceeded.short": "GitHub API rate limit exceeded, please retry later", "github.api.rate.limit.exceeded.short": "GitHub API rate limit exceeded, please retry later",
"github.api.file.upload.success": "File uploaded successfully", "github.api.file.upload.success": "File uploaded successfully",
"github.api.all.files.download.success": "All files downloaded successfully", "github.api.all.files.download.success": "All files downloaded successfully",
"github.api.connection.test.success": "\u2705 Connection test successful!\n\u{1F464} User: {user}\n\u{1F4C1} Repository: {repo}\n\u{1F511} Permissions: {permissions}",
// Status bar // Status bar
"status.bar.checking": "Checking...", "status.bar.checking": "Checking...",
"status.bar.sync": "Sync", "status.bar.sync": "Sync",
@ -686,7 +692,7 @@ var init_i18n_simple = __esm({
"status.bar.local.modified": "Local modified", "status.bar.local.modified": "Local modified",
"status.bar.synced": "Synced", "status.bar.synced": "Synced",
// Plugin info // Plugin info
"plugin.name": "Git Sync", "plugin.name": "Git Folder Sync",
"command.show.sync.menu": "Show Sync Menu", "command.show.sync.menu": "Show Sync Menu",
// Test URL results // Test URL results
"test.url.user": "User", "test.url.user": "User",
@ -870,13 +876,6 @@ var init_cos_service = __esm({
const url = `https://${this.config.bucket}.cos.${this.config.region}.myqcloud.com/${remotePath}`; const url = `https://${this.config.bucket}.cos.${this.config.region}.myqcloud.com/${remotePath}`;
const date = new Date().toUTCString(); const date = new Date().toUTCString();
const signature = await this.generateTencentSignature("PUT", remotePath, date, file.type); const signature = await this.generateTencentSignature("PUT", remotePath, date, file.type);
console.log("Tencent COS upload debug:", {
url,
remotePath,
signature: signature.substring(0, 50) + "...",
fileSize: file.size,
fileType: file.type
});
const fileBuffer = await file.arrayBuffer(); const fileBuffer = await file.arrayBuffer();
const response = await (0, import_obsidian2.requestUrl)({ const response = await (0, import_obsidian2.requestUrl)({
url, url,
@ -4395,13 +4394,11 @@ var GitHubService = class {
} }
} }
parseRepositoryUrl(url) { parseRepositoryUrl(url) {
console.log("Parsing repository URL:", url);
if (!url) { if (!url) {
console.error("URL is empty"); console.error("URL is empty");
return null; return null;
} }
const cleanUrl = url.trim(); const cleanUrl = url.trim();
console.log("Cleaned URL:", cleanUrl);
let match; let match;
match = cleanUrl.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\/(.*))?$/); match = cleanUrl.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\/(.*))?$/);
if (match) { if (match) {
@ -4410,7 +4407,6 @@ var GitHubService = class {
repo: match[2], repo: match[2],
path: match[3] || "" path: match[3] || ""
}; };
console.log("Parse result (standard GitHub URL):", result);
return result; return result;
} }
match = cleanUrl.match(/^([^\/]+)\/([^\/]+)(?:\/(.*))?$/); match = cleanUrl.match(/^([^\/]+)\/([^\/]+)(?:\/(.*))?$/);
@ -4420,7 +4416,6 @@ var GitHubService = class {
repo: match[2], repo: match[2],
path: match[3] || "" path: match[3] || ""
}; };
console.log("Parse result (short format):", result);
return result; return result;
} }
console.error("Unable to parse URL format:", cleanUrl); console.error("Unable to parse URL format:", cleanUrl);
@ -4429,6 +4424,52 @@ var GitHubService = class {
console.error("2. username/repo/path (short format)"); console.error("2. username/repo/path (short format)");
return null; return null;
} }
/**
* Test GitHub token and repository access
*/
async testConnection(repositoryUrl) {
var _a;
const repoInfo = this.parseRepositoryUrl(repositoryUrl);
if (!repoInfo) {
return { success: false, message: t("github.api.invalid.url.format") };
}
if (!await this.beforeApiCall()) {
return { success: false, message: t("github.api.rate.limit.exceeded.short") };
}
try {
const userResponse = await this.octokit.rest.users.getAuthenticated();
const repoResponse = await this.octokit.rest.repos.get({
owner: repoInfo.owner,
repo: repoInfo.repo
});
if (repoInfo.path) {
try {
await this.octokit.rest.repos.getContent({
owner: repoInfo.owner,
repo: repoInfo.repo,
path: repoInfo.path
});
} catch (error) {
if (error.status !== 404) {
throw error;
}
}
}
return {
success: true,
message: t("github.api.connection.test.success", {
user: userResponse.data.login,
repo: `${repoInfo.owner}/${repoInfo.repo}`,
permissions: ((_a = repoResponse.data.permissions) == null ? void 0 : _a.push) ? "read/write" : "read-only"
}),
repoInfo
};
} catch (error) {
console.error("GitHub connection test failed:", error);
const errorInfo = this.handleGitHubError(error);
return { success: false, message: errorInfo.message };
}
}
async uploadFile(repositoryUrl, filePath, content) { async uploadFile(repositoryUrl, filePath, content) {
const repoInfo = this.parseRepositoryUrl(repositoryUrl); const repoInfo = this.parseRepositoryUrl(repositoryUrl);
if (!repoInfo) { if (!repoInfo) {
@ -4598,7 +4639,7 @@ var FileCacheService = class {
// 5 minutes cache validity period // 5 minutes cache validity period
constructor() { constructor() {
this.cache = /* @__PURE__ */ new Map(); this.cache = /* @__PURE__ */ new Map();
this.CACHE_KEY = "git-sync-file-cache"; this.CACHE_KEY = "git-folder-sync-file-cache";
this.DEFAULT_CACHE_AGE = 5 * 60 * 1e3; this.DEFAULT_CACHE_AGE = 5 * 60 * 1e3;
this.loadCacheFromStorage(); this.loadCacheFromStorage();
} }
@ -4672,7 +4713,6 @@ var FileCacheService = class {
clearCache() { clearCache() {
this.cache.clear(); this.cache.clear();
localStorage.removeItem(this.CACHE_KEY); localStorage.removeItem(this.CACHE_KEY);
console.log("All file caches cleared");
} }
/** /**
* Check if cache is valid * Check if cache is valid
@ -4762,6 +4802,11 @@ var FileCacheService = class {
// main.ts // main.ts
init_cos_service(); init_cos_service();
function debugLog(...args) {
if (true) {
console.log("[Git Folder Sync]", ...args);
}
}
var GitSyncPlugin = class extends import_obsidian3.Plugin { var GitSyncPlugin = class extends import_obsidian3.Plugin {
constructor() { constructor() {
super(...arguments); super(...arguments);
@ -4793,7 +4838,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
this.registerEvent( this.registerEvent(
this.app.vault.on("modify", (file) => { this.app.vault.on("modify", (file) => {
if (file.path.endsWith(".md") && this.currentFile && file.path === this.currentFile.path) { if (file.path.endsWith(".md") && this.currentFile && file.path === this.currentFile.path) {
console.log(`Detected file modification: ${file.path}`); debugLog(`Detected file modification: ${file.path}`);
if (file instanceof import_obsidian3.TFile) { if (file instanceof import_obsidian3.TFile) {
this.onFileContentModified(file); this.onFileContentModified(file);
} }
@ -4862,11 +4907,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
}); });
}); });
const rect = (_a = editor.containerEl) == null ? void 0 : _a.getBoundingClientRect(); const rect = (_a = editor.containerEl) == null ? void 0 : _a.getBoundingClientRect();
if (rect) { menu.showAtMouseEvent(rect ? new MouseEvent("click", { clientX: rect.right - 100, clientY: rect.top + 50 }) : new MouseEvent("click"));
menu.showAtPosition({ x: rect.right - 100, y: rect.top + 50 });
} else {
menu.showAtMouseEvent(new MouseEvent("click"));
}
} }
async syncCurrentFileToRemote(file) { async syncCurrentFileToRemote(file) {
if (!this.settings.githubToken || !this.settings.repositoryUrl) { if (!this.settings.githubToken || !this.settings.repositoryUrl) {
@ -4991,7 +5032,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
async forceSyncLocalToRemote() { async forceSyncLocalToRemote() {
try { try {
const files = this.getAllVaultFiles(); const files = this.getAllVaultFiles();
console.log("Found files:", files.map((f) => f.path)); debugLog("Found files:", files.map((f) => f.path));
if (files.length === 0) { if (files.length === 0) {
return { success: false, message: t("no.syncable.files.in.vault") }; return { success: false, message: t("no.syncable.files.in.vault") };
} }
@ -5007,7 +5048,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
); );
if (result.success) { if (result.success) {
processed++; processed++;
console.log(`Successfully synced file: ${file.path}`); debugLog(`Successfully synced file: ${file.path}`);
await this.fileCacheService.updateFileCache( await this.fileCacheService.updateFileCache(
file.path, file.path,
file.path, file.path,
@ -5038,31 +5079,31 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
} }
isVaultEmpty() { isVaultEmpty() {
const files = this.app.vault.getFiles(); const files = this.app.vault.getFiles();
return files.filter((file) => !file.path.startsWith(".obsidian")).length === 0; return files.filter((file) => !file.path.startsWith(this.app.vault.configDir)).length === 0;
} }
getAllVaultFiles() { getAllVaultFiles() {
const allFiles = this.app.vault.getFiles(); const allFiles = this.app.vault.getFiles();
console.log("All files in vault:", allFiles.map((f) => f.path)); debugLog("All files in vault:", allFiles.map((f) => f.path));
const filteredFiles = allFiles.filter((file) => { const filteredFiles = allFiles.filter((file) => {
if (file.path.startsWith(".obsidian")) { if (file.path.startsWith(this.app.vault.configDir)) {
return false; return false;
} }
if (this.settings.keepLocalImages && this.settings.localImagePath) { if (this.settings.keepLocalImages && this.settings.localImagePath) {
const normalizedImagePath = this.settings.localImagePath.replace(/^\/+|\/+$/g, ""); const normalizedImagePath = this.settings.localImagePath.replace(/^\/+|\/+$/g, "");
if (normalizedImagePath && file.path.startsWith(normalizedImagePath + "/")) { if (normalizedImagePath && file.path.startsWith(normalizedImagePath + "/")) {
console.log(`Excluding file from sync (in image directory): ${file.path}`); debugLog(`Excluding file from sync (in image directory): ${file.path}`);
return false; return false;
} }
} }
return true; return true;
}); });
console.log("Filtered files:", filteredFiles.map((f) => f.path)); debugLog("Filtered files:", filteredFiles.map((f) => f.path));
return filteredFiles; return filteredFiles;
} }
async createFileInVault(path, content) { async createFileInVault(path, content) {
const folderPath = path.substring(0, path.lastIndexOf("/")); const folderPath = path.substring(0, path.lastIndexOf("/"));
if (folderPath && !this.app.vault.getAbstractFileByPath(folderPath)) { if (folderPath && !this.app.vault.getAbstractFileByPath(folderPath)) {
await this.app.vault.createFolder(folderPath); await this.app.vault.adapter.mkdir(folderPath);
} }
const existingFile = this.app.vault.getAbstractFileByPath(path); const existingFile = this.app.vault.getAbstractFileByPath(path);
if (existingFile instanceof import_obsidian3.TFile) { if (existingFile instanceof import_obsidian3.TFile) {
@ -5077,11 +5118,13 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
const activeFile = this.app.workspace.getActiveFile(); const activeFile = this.app.workspace.getActiveFile();
if (!activeFile || !activeFile.path.endsWith(".md")) { if (!activeFile || !activeFile.path.endsWith(".md")) {
this.statusBarEl.empty(); this.statusBarEl.empty();
this.statusBarEl.style.display = "none"; this.statusBarEl.removeClass("visible");
this.statusBarEl.addClass("hidden");
return; return;
} }
this.currentFile = activeFile; this.currentFile = activeFile;
this.statusBarEl.style.display = "flex"; this.statusBarEl.removeClass("hidden");
this.statusBarEl.addClass("visible");
this.statusBarEl.empty(); this.statusBarEl.empty();
const statusText = this.statusBarEl.createEl("span", { const statusText = this.statusBarEl.createEl("span", {
cls: "git-sync-status-text", cls: "git-sync-status-text",
@ -5095,27 +5138,27 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
try { try {
const cache = this.fileCacheService.getFileCache(activeFile.path); const cache = this.fileCacheService.getFileCache(activeFile.path);
if (cache) { if (cache) {
console.log(`Using cached data to display file status: ${activeFile.path}`); debugLog(`Using cached data to display file status: ${activeFile.path}`);
if (cache.isPublished) { if (cache.isPublished) {
const date = new Date(cache.lastModified); const date = new Date(cache.lastModified);
let statusMsg = t("status.bar.last.modified", { date: this.formatDate(date) }); let statusMsg = t("status.bar.last.modified", { date: this.formatDate(date) });
if (!cache.isSynced) { if (!cache.isSynced) {
statusMsg += ` (${t("status.bar.local.modified")})`; statusMsg += ` (${t("status.bar.local.modified")})`;
statusText.style.color = "var(--color-orange)"; statusText.addClass("modified");
} else { } else {
statusMsg += ` (${t("status.bar.synced")})`; statusMsg += ` (${t("status.bar.synced")})`;
statusText.style.color = "var(--color-green)"; statusText.addClass("synced");
} }
statusText.textContent = statusMsg; statusText.textContent = statusMsg;
} else { } else {
statusText.textContent = t("status.bar.not.published"); statusText.textContent = t("status.bar.not.published");
statusText.style.color = "var(--text-muted)"; statusText.addClass("not-published");
} }
if (!this.fileCacheService.isCacheValid(cache, 4 * 60 * 1e3)) { if (!this.fileCacheService.isCacheValid(cache, 4 * 60 * 1e3)) {
this.refreshFileCache(activeFile.path).catch(console.error); this.refreshFileCache(activeFile.path).catch(console.error);
} }
} else { } else {
console.log(`No cache, fetching file status from GitHub: ${activeFile.path}`); debugLog(`No cache, fetching file status from GitHub: ${activeFile.path}`);
await this.fetchAndCacheFileStatus(activeFile.path, statusText); await this.fetchAndCacheFileStatus(activeFile.path, statusText);
} }
} catch (error) { } catch (error) {
@ -5147,7 +5190,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
const content = await this.app.vault.read(file); const content = await this.app.vault.read(file);
const currentHash = FileCacheService.calculateContentHash(content); const currentHash = FileCacheService.calculateContentHash(content);
if (currentHash !== cache.contentHash) { if (currentHash !== cache.contentHash) {
console.log(`File content modified: ${file.path}`); debugLog(`File content modified: ${file.path}`);
const updatedCache = { const updatedCache = {
...cache, ...cache,
contentHash: currentHash, contentHash: currentHash,
@ -5181,7 +5224,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
if (result.exists && result.lastModified) { if (result.exists && result.lastModified) {
const date = new Date(result.lastModified); const date = new Date(result.lastModified);
statusText.textContent = t("status.bar.last.modified", { date: this.formatDate(date) }); statusText.textContent = t("status.bar.last.modified", { date: this.formatDate(date) });
statusText.style.color = "var(--text-normal)"; statusText.addClass("normal");
await this.fileCacheService.updateFileCache( await this.fileCacheService.updateFileCache(
filePath, filePath,
filePath, filePath,
@ -5194,7 +5237,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
); );
} else { } else {
statusText.textContent = t("status.bar.not.published"); statusText.textContent = t("status.bar.not.published");
statusText.style.color = "var(--text-muted)"; statusText.addClass("not-published");
await this.fileCacheService.updateFileCache( await this.fileCacheService.updateFileCache(
filePath, filePath,
filePath, filePath,
@ -5217,7 +5260,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
*/ */
async refreshFileCache(filePath) { async refreshFileCache(filePath) {
try { try {
console.log(`Asynchronously refreshing file cache: ${filePath}`); debugLog(`Asynchronously refreshing file cache: ${filePath}`);
const result = await this.githubService.getFileLastModified( const result = await this.githubService.getFileLastModified(
this.settings.repositoryUrl, this.settings.repositoryUrl,
filePath filePath
@ -5232,7 +5275,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
result.exists || false, result.exists || false,
this.app.vault this.app.vault
); );
console.log(`File cache refreshed: ${filePath}`); debugLog(`File cache refreshed: ${filePath}`);
} }
} catch (error) { } catch (error) {
console.error("Failed to refresh file cache:", error); console.error("Failed to refresh file cache:", error);
@ -5254,9 +5297,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
}); });
}); });
const rect = (_a = this.statusBarEl) == null ? void 0 : _a.getBoundingClientRect(); const rect = (_a = this.statusBarEl) == null ? void 0 : _a.getBoundingClientRect();
if (rect) { menu.showAtMouseEvent(rect ? new MouseEvent("click", { clientX: rect.left, clientY: rect.top - 10 }) : new MouseEvent("click"));
menu.showAtPosition({ x: rect.left, y: rect.top - 10 });
}
} }
formatDate(date) { formatDate(date) {
const now = new Date(); const now = new Date();
@ -5400,7 +5441,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
} }
uploadNotice.hide(); uploadNotice.hide();
new import_obsidian3.Notice(t("cos.upload.success")); new import_obsidian3.Notice(t("cos.upload.success"));
console.log("Image uploaded successfully:", uploadResult.url); debugLog("Image uploaded successfully:", uploadResult.url);
} else { } else {
throw new Error(uploadResult.message || "Upload failed"); throw new Error(uploadResult.message || "Upload failed");
} }
@ -5428,10 +5469,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
}); });
const dirPath = localPath.substring(0, localPath.lastIndexOf("/")); const dirPath = localPath.substring(0, localPath.lastIndexOf("/"));
if (dirPath) { if (dirPath) {
try { await this.app.vault.adapter.mkdir(dirPath);
await this.app.vault.createFolder(dirPath);
} catch (error) {
}
} }
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
await this.app.vault.createBinary(localPath, arrayBuffer); await this.app.vault.createBinary(localPath, arrayBuffer);
@ -5439,7 +5477,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
const cursorPos = editor.getCursor(); const cursorPos = editor.getCursor();
editor.replaceRange(imageMarkdown, cursorPos); editor.replaceRange(imageMarkdown, cursorPos);
new import_obsidian3.Notice(t("cos.upload.success")); new import_obsidian3.Notice(t("cos.upload.success"));
console.log("Image saved locally:", localPath); debugLog("Image saved locally:", localPath);
} catch (error) { } catch (error) {
new import_obsidian3.Notice(t("cos.upload.failed", { error: error.message })); new import_obsidian3.Notice(t("cos.upload.failed", { error: error.message }));
console.error("Local image save failed:", error); console.error("Local image save failed:", error);
@ -5456,14 +5494,11 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin {
}); });
const dirPath = localPath.substring(0, localPath.lastIndexOf("/")); const dirPath = localPath.substring(0, localPath.lastIndexOf("/"));
if (dirPath) { if (dirPath) {
try { await this.app.vault.adapter.mkdir(dirPath);
await this.app.vault.createFolder(dirPath);
} catch (error) {
}
} }
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
await this.app.vault.createBinary(localPath, arrayBuffer); await this.app.vault.createBinary(localPath, arrayBuffer);
console.log("Local image copy saved:", localPath); debugLog("Local image copy saved:", localPath);
} catch (error) { } catch (error) {
console.warn("Failed to save local image copy:", error); console.warn("Failed to save local image copy:", error);
} }
@ -5477,7 +5512,7 @@ var GitSyncSettingTab = class extends import_obsidian3.PluginSettingTab {
display() { display() {
const { containerEl } = this; const { containerEl } = this;
containerEl.empty(); containerEl.empty();
containerEl.createEl("h2", { text: t("settings.title") }); new import_obsidian3.Setting(containerEl).setName(t("settings.title")).setHeading();
new import_obsidian3.Setting(containerEl).setName(t("settings.language.name")).setDesc(t("settings.language.desc")).addDropdown((dropdown) => { new import_obsidian3.Setting(containerEl).setName(t("settings.language.name")).setDesc(t("settings.language.desc")).addDropdown((dropdown) => {
const languages = getSupportedLanguages(); const languages = getSupportedLanguages();
languages.forEach((lang) => { languages.forEach((lang) => {
@ -5492,11 +5527,13 @@ var GitSyncSettingTab = class extends import_obsidian3.PluginSettingTab {
}); });
new import_obsidian3.Setting(containerEl).setName(t("settings.github.token.name")).setDesc(t("settings.github.token.desc")).addText((text) => text.setPlaceholder(t("settings.github.token.placeholder")).setValue(this.plugin.settings.githubToken).onChange(async (value) => { new import_obsidian3.Setting(containerEl).setName(t("settings.github.token.name")).setDesc(t("settings.github.token.desc")).addText((text) => text.setPlaceholder(t("settings.github.token.placeholder")).setValue(this.plugin.settings.githubToken).onChange(async (value) => {
this.plugin.settings.githubToken = value; this.plugin.settings.githubToken = value;
await this.plugin.saveSettings();
})); }));
let pathInfoEl; let pathInfoEl;
new import_obsidian3.Setting(containerEl).setName(t("settings.github.repo.name")).setDesc(t("settings.github.repo.desc")).addText((text) => text.setPlaceholder(t("settings.github.repo.placeholder")).setValue(this.plugin.settings.repositoryUrl).onChange(async (value) => { new import_obsidian3.Setting(containerEl).setName(t("settings.github.repo.name")).setDesc(t("settings.github.repo.desc")).addText((text) => text.setPlaceholder(t("settings.github.repo.placeholder")).setValue(this.plugin.settings.repositoryUrl).onChange(async (value) => {
this.plugin.settings.repositoryUrl = value; this.plugin.settings.repositoryUrl = value;
this.updatePathInfo(pathInfoEl, value); this.updatePathInfo(pathInfoEl, value);
await this.plugin.saveSettings();
})); }));
pathInfoEl = containerEl.createEl("div", { pathInfoEl = containerEl.createEl("div", {
cls: "setting-item-description git-sync-path-info", cls: "setting-item-description git-sync-path-info",
@ -5507,38 +5544,43 @@ var GitSyncSettingTab = class extends import_obsidian3.PluginSettingTab {
new import_obsidian3.Notice(t("notice.url.required")); new import_obsidian3.Notice(t("notice.url.required"));
return; return;
} }
console.log("=== Testing Repository URL ==="); if (!this.plugin.settings.githubToken) {
console.log("Input URL:", this.plugin.settings.repositoryUrl); new import_obsidian3.Notice(t("notice.token.required"));
const testResult = this.plugin.githubService.parseRepositoryUrl(this.plugin.settings.repositoryUrl); return;
if (testResult) { }
console.log("URL parsing successful:", testResult); button.setButtonText(t("settings.test.url.loading"));
new import_obsidian3.Notice(`${t("notice.url.test.success")} button.setDisabled(true);
${t("test.url.user")}: ${testResult.owner} try {
${t("test.url.repo")}: ${testResult.repo} debugLog("=== Testing GitHub Connection ===");
${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3); debugLog("Input URL:", this.plugin.settings.repositoryUrl);
} else { const testResult = await this.plugin.githubService.testConnection(this.plugin.settings.repositoryUrl);
console.log("URL parsing failed"); if (testResult.success) {
new import_obsidian3.Notice(t("notice.url.test.failed"), 6e3); debugLog("Connection test successful:", testResult);
new import_obsidian3.Notice(testResult.message, 8e3);
} else {
debugLog("Connection test failed:", testResult.message);
new import_obsidian3.Notice(testResult.message, 6e3);
}
} catch (error) {
debugLog("Connection test error:", error);
new import_obsidian3.Notice(t("github.api.operation.failed", { message: error.message }), 6e3);
} finally {
button.setButtonText(t("settings.test.url.button"));
button.setDisabled(false);
} }
})); }));
new import_obsidian3.Setting(containerEl).setName(t("settings.ribbon.name")).setDesc(t("settings.ribbon.desc")).addToggle((toggle) => toggle.setValue(this.plugin.settings.showRibbonIcon).onChange(async (value) => { new import_obsidian3.Setting(containerEl).setName(t("settings.ribbon.name")).setDesc(t("settings.ribbon.desc")).addToggle((toggle) => toggle.setValue(this.plugin.settings.showRibbonIcon).onChange(async (value) => {
this.plugin.settings.showRibbonIcon = value; this.plugin.settings.showRibbonIcon = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }));
containerEl.createEl("h2", { new import_obsidian3.Setting(containerEl).setName(t("settings.image.section.title")).setHeading();
text: t("settings.image.section.title"),
cls: "git-sync-section-title"
});
new import_obsidian3.Setting(containerEl).setName(t("settings.image.enable.name")).setDesc(t("settings.image.enable.desc")).addToggle((toggle) => toggle.setValue(this.plugin.settings.enableImageProcessing).onChange(async (value) => { new import_obsidian3.Setting(containerEl).setName(t("settings.image.enable.name")).setDesc(t("settings.image.enable.desc")).addToggle((toggle) => toggle.setValue(this.plugin.settings.enableImageProcessing).onChange(async (value) => {
this.plugin.settings.enableImageProcessing = value; this.plugin.settings.enableImageProcessing = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
this.display(); this.display();
})); }));
if (this.plugin.settings.enableImageProcessing) { if (this.plugin.settings.enableImageProcessing) {
containerEl.createEl("h3", { new import_obsidian3.Setting(containerEl).setName(t("settings.cos.provider.section.title")).setHeading();
text: t("settings.cos.provider.section.title"),
cls: "git-sync-subsection-title"
});
new import_obsidian3.Setting(containerEl).setName(t("settings.cos.provider.name")).setDesc(t("settings.cos.provider.desc")).addDropdown((dropdown) => { new import_obsidian3.Setting(containerEl).setName(t("settings.cos.provider.name")).setDesc(t("settings.cos.provider.desc")).addDropdown((dropdown) => {
dropdown.addOption("aliyun", t("settings.cos.provider.aliyun")); dropdown.addOption("aliyun", t("settings.cos.provider.aliyun"));
dropdown.addOption("tencent", t("settings.cos.provider.tencent")); dropdown.addOption("tencent", t("settings.cos.provider.tencent"));
@ -5626,10 +5668,7 @@ ${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3);
button.setDisabled(false); button.setDisabled(false);
} }
})); }));
containerEl.createEl("h3", { new import_obsidian3.Setting(containerEl).setName(t("settings.image.upload.section.title")).setHeading();
text: t("settings.image.upload.section.title"),
cls: "git-sync-subsection-title"
});
new import_obsidian3.Setting(containerEl).setName(t("settings.cos.keep.local.name")).setDesc(t("settings.cos.keep.local.desc")).addToggle((toggle) => toggle.setValue(this.plugin.settings.keepLocalImages).onChange(async (value) => { new import_obsidian3.Setting(containerEl).setName(t("settings.cos.keep.local.name")).setDesc(t("settings.cos.keep.local.desc")).addToggle((toggle) => toggle.setValue(this.plugin.settings.keepLocalImages).onChange(async (value) => {
this.plugin.settings.keepLocalImages = value; this.plugin.settings.keepLocalImages = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
@ -5646,13 +5685,12 @@ ${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }));
} }
containerEl.createEl("h2", { new import_obsidian3.Setting(containerEl).setName(t("settings.danger.zone.title")).setHeading();
text: t("settings.danger.zone.title"),
cls: "danger-zone"
});
new import_obsidian3.Setting(containerEl).setName(t("settings.init.name")).setDesc(t("settings.init.desc")).addButton((button) => { new import_obsidian3.Setting(containerEl).setName(t("settings.init.name")).setDesc(t("settings.init.desc")).addButton((button) => {
const isVaultEmpty = this.isVaultEmpty(); const isVaultEmpty = this.isVaultEmpty();
button.setButtonText(t("settings.init.button")).setDisabled(!isVaultEmpty).onClick(async () => { button.setButtonText(t("settings.init.button"));
button.setDisabled(!isVaultEmpty);
button.onClick(async () => {
if (!this.plugin.settings.githubToken || !this.plugin.settings.repositoryUrl) { if (!this.plugin.settings.githubToken || !this.plugin.settings.repositoryUrl) {
new import_obsidian3.Notice(t("notice.config.required")); new import_obsidian3.Notice(t("notice.config.required"));
return; return;
@ -5728,10 +5766,7 @@ ${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3);
new import_obsidian3.Notice(t("notice.cache.clear.error")); new import_obsidian3.Notice(t("notice.cache.clear.error"));
} }
})); }));
containerEl.createEl("h2", { new import_obsidian3.Setting(containerEl).setName(t("settings.sponsor.section.title")).setHeading();
text: t("settings.sponsor.section.title"),
cls: "sponsor"
});
const sponsorSection = containerEl.createEl("div", { cls: "setting-item" }); const sponsorSection = containerEl.createEl("div", { cls: "setting-item" });
const sponsorInfo = sponsorSection.createEl("div", { cls: "setting-item-info" }); const sponsorInfo = sponsorSection.createEl("div", { cls: "setting-item-info" });
sponsorInfo.createEl("div", { sponsorInfo.createEl("div", {
@ -5746,14 +5781,10 @@ ${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3);
cls: getActualLanguage() === "zh" ? "setting-item-control git-sync-sponsor-control" : "setting-item-control" cls: getActualLanguage() === "zh" ? "setting-item-control git-sync-sponsor-control" : "setting-item-control"
}); });
const sponsorButton = sponsorControl.createEl("a", { const sponsorButton = sponsorControl.createEl("a", {
cls: "mod-cta", cls: "mod-cta git-sync-sponsor-button",
text: t("settings.sponsor.button"), text: t("settings.sponsor.button"),
href: "https://paypal.me/xheldoncao" href: "https://paypal.me/xheldoncao"
}); });
sponsorButton.style.textDecoration = "none";
sponsorButton.style.padding = "6px 12px";
sponsorButton.style.borderRadius = "3px";
sponsorButton.style.display = "inline-block";
if (getActualLanguage() === "zh") { if (getActualLanguage() === "zh") {
const chinaSponsorContainer = sponsorControl.createEl("div", { const chinaSponsorContainer = sponsorControl.createEl("div", {
cls: "git-sync-china-sponsor" cls: "git-sync-china-sponsor"
@ -5765,13 +5796,11 @@ ${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3);
text: "https://www.xheldon.com/donate/", text: "https://www.xheldon.com/donate/",
href: "https://www.xheldon.com/donate/" href: "https://www.xheldon.com/donate/"
}); });
chinaLink.style.color = "var(--text-accent)";
chinaLink.style.textDecoration = "none";
} }
} }
isVaultEmpty() { isVaultEmpty() {
const files = this.app.vault.getFiles(); const files = this.app.vault.getFiles();
return files.filter((file) => !file.path.startsWith(".obsidian")).length === 0; return files.filter((file) => !file.path.startsWith(this.app.vault.configDir)).length === 0;
} }
updatePathInfo(el, value) { updatePathInfo(el, value) {
if (el) { if (el) {

220
main.ts
View file

@ -1,10 +1,30 @@
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, Menu, TFolder } from 'obsidian'; import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, Menu, TFolder } from 'obsidian';
// Extended types for better type safety
interface ExtendedApp extends App {
setting: {
open(): void;
openTabById(id: string): void;
};
}
interface ExtendedEditor extends Editor {
containerEl?: HTMLElement;
}
import { GitSyncSettings, DEFAULT_SETTINGS, SyncResult, CosConfig, ImagePasteContext, CosProviderConfig } from './types'; import { GitSyncSettings, DEFAULT_SETTINGS, SyncResult, CosConfig, ImagePasteContext, CosProviderConfig } from './types';
import { GitHubService } from './github-service'; import { GitHubService } from './github-service';
import { setLanguage, t, getSupportedLanguages, getActualLanguage } from './i18n-simple'; import { setLanguage, t, getSupportedLanguages, getActualLanguage } from './i18n-simple';
import { FileCacheService } from './file-cache'; import { FileCacheService } from './file-cache';
import { CosService, parseImagePath } from './cos-service'; import { CosService, parseImagePath } from './cos-service';
// Debug logging function - only logs in development mode
function debugLog(...args: any[]): void {
// Only log in development mode (when plugin is loaded from file system)
if (process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost') {
console.log('[Git Folder Sync]', ...args);
}
}
export default class GitSyncPlugin extends Plugin { export default class GitSyncPlugin extends Plugin {
settings: GitSyncSettings; settings: GitSyncSettings;
githubService: GitHubService; githubService: GitHubService;
@ -51,7 +71,7 @@ export default class GitSyncPlugin extends Plugin {
this.app.vault.on('modify', (file) => { this.app.vault.on('modify', (file) => {
// Only handle markdown files that are currently open // Only handle markdown files that are currently open
if (file.path.endsWith('.md') && this.currentFile && file.path === this.currentFile.path) { if (file.path.endsWith('.md') && this.currentFile && file.path === this.currentFile.path) {
console.log(`Detected file modification: ${file.path}`); debugLog(`Detected file modification: ${file.path}`);
// Type conversion to TFile // Type conversion to TFile
if (file instanceof TFile) { if (file instanceof TFile) {
this.onFileContentModified(file); this.onFileContentModified(file);
@ -127,10 +147,10 @@ export default class GitSyncPlugin extends Plugin {
// Add button based on settings // Add button based on settings
if (this.settings.showRibbonIcon) { if (this.settings.showRibbonIcon) {
this.ribbonIconEl = this.addRibbonIcon('settings', t('settings.title'), (evt: MouseEvent) => { this.ribbonIconEl = this.addRibbonIcon('settings', t('settings.title'), (evt: MouseEvent) => {
// Directly open plugin settings interface // Directly open plugin settings interface
(this.app as any).setting.open(); (this.app as ExtendedApp).setting.open();
(this.app as any).setting.openTabById(this.manifest.id); (this.app as ExtendedApp).setting.openTabById(this.manifest.id);
}); });
} }
} }
@ -156,13 +176,10 @@ export default class GitSyncPlugin extends Plugin {
}); });
}); });
const rect = (editor as any).containerEl?.getBoundingClientRect(); const rect = (editor as ExtendedEditor).containerEl?.getBoundingClientRect();
if (rect) { menu.showAtMouseEvent(rect
menu.showAtPosition({ x: rect.right - 100, y: rect.top + 50 }); ? new MouseEvent('click', { clientX: rect.right - 100, clientY: rect.top + 50 })
} else { : new MouseEvent('click'));
// If unable to get editor position, show at mouse position
menu.showAtMouseEvent(new MouseEvent('click'));
}
} }
async syncCurrentFileToRemote(file: TFile): Promise<void> { async syncCurrentFileToRemote(file: TFile): Promise<void> {
@ -299,7 +316,7 @@ export default class GitSyncPlugin extends Plugin {
async forceSyncLocalToRemote(): Promise<SyncResult> { async forceSyncLocalToRemote(): Promise<SyncResult> {
try { try {
const files = this.getAllVaultFiles(); const files = this.getAllVaultFiles();
console.log('Found files:', files.map(f => f.path)); debugLog('Found files:', files.map(f => f.path));
if (files.length === 0) { if (files.length === 0) {
return { success: false, message: t('no.syncable.files.in.vault') }; return { success: false, message: t('no.syncable.files.in.vault') };
@ -319,7 +336,7 @@ export default class GitSyncPlugin extends Plugin {
if (result.success) { if (result.success) {
processed++; processed++;
console.log(`Successfully synced file: ${file.path}`); debugLog(`Successfully synced file: ${file.path}`);
// Update cache immediately - file synced to remote // Update cache immediately - file synced to remote
await this.fileCacheService.updateFileCache( await this.fileCacheService.updateFileCache(
@ -356,17 +373,17 @@ export default class GitSyncPlugin extends Plugin {
private isVaultEmpty(): boolean { private isVaultEmpty(): boolean {
const files = this.app.vault.getFiles(); const files = this.app.vault.getFiles();
return files.filter(file => !file.path.startsWith('.obsidian')).length === 0; return files.filter(file => !file.path.startsWith(this.app.vault.configDir)).length === 0;
} }
private getAllVaultFiles(): TFile[] { private getAllVaultFiles(): TFile[] {
const allFiles = this.app.vault.getFiles(); const allFiles = this.app.vault.getFiles();
console.log('All files in vault:', allFiles.map(f => f.path)); debugLog('All files in vault:', allFiles.map(f => f.path));
// Filter out files in .obsidian folder and local image path (if configured) // Filter out files in config folder and local image path (if configured)
const filteredFiles = allFiles.filter(file => { const filteredFiles = allFiles.filter(file => {
// Always exclude .obsidian folder // Always exclude Obsidian config folder
if (file.path.startsWith('.obsidian')) { if (file.path.startsWith(this.app.vault.configDir)) {
return false; return false;
} }
@ -374,7 +391,7 @@ export default class GitSyncPlugin extends Plugin {
if (this.settings.keepLocalImages && this.settings.localImagePath) { if (this.settings.keepLocalImages && this.settings.localImagePath) {
const normalizedImagePath = this.settings.localImagePath.replace(/^\/+|\/+$/g, ''); // Remove leading/trailing slashes const normalizedImagePath = this.settings.localImagePath.replace(/^\/+|\/+$/g, ''); // Remove leading/trailing slashes
if (normalizedImagePath && file.path.startsWith(normalizedImagePath + '/')) { if (normalizedImagePath && file.path.startsWith(normalizedImagePath + '/')) {
console.log(`Excluding file from sync (in image directory): ${file.path}`); debugLog(`Excluding file from sync (in image directory): ${file.path}`);
return false; return false;
} }
} }
@ -382,7 +399,7 @@ export default class GitSyncPlugin extends Plugin {
return true; return true;
}); });
console.log('Filtered files:', filteredFiles.map(f => f.path)); debugLog('Filtered files:', filteredFiles.map(f => f.path));
return filteredFiles; return filteredFiles;
} }
@ -390,7 +407,7 @@ export default class GitSyncPlugin extends Plugin {
private async createFileInVault(path: string, content: string): Promise<void> { private async createFileInVault(path: string, content: string): Promise<void> {
const folderPath = path.substring(0, path.lastIndexOf('/')); const folderPath = path.substring(0, path.lastIndexOf('/'));
if (folderPath && !this.app.vault.getAbstractFileByPath(folderPath)) { if (folderPath && !this.app.vault.getAbstractFileByPath(folderPath)) {
await this.app.vault.createFolder(folderPath); await this.app.vault.adapter.mkdir(folderPath);
} }
const existingFile = this.app.vault.getAbstractFileByPath(path); const existingFile = this.app.vault.getAbstractFileByPath(path);
@ -407,12 +424,14 @@ export default class GitSyncPlugin extends Plugin {
const activeFile = this.app.workspace.getActiveFile(); const activeFile = this.app.workspace.getActiveFile();
if (!activeFile || !activeFile.path.endsWith('.md')) { if (!activeFile || !activeFile.path.endsWith('.md')) {
this.statusBarEl.empty(); this.statusBarEl.empty();
this.statusBarEl.style.display = 'none'; this.statusBarEl.removeClass('visible');
this.statusBarEl.addClass('hidden');
return; return;
} }
this.currentFile = activeFile; this.currentFile = activeFile;
this.statusBarEl.style.display = 'flex'; this.statusBarEl.removeClass('hidden');
this.statusBarEl.addClass('visible');
this.statusBarEl.empty(); this.statusBarEl.empty();
// Create status text // Create status text
@ -435,25 +454,25 @@ export default class GitSyncPlugin extends Plugin {
if (cache) { if (cache) {
// Use cached data // Use cached data
console.log(`Using cached data to display file status: ${activeFile.path}`); debugLog(`Using cached data to display file status: ${activeFile.path}`);
if (cache.isPublished) { if (cache.isPublished) {
const date = new Date(cache.lastModified); const date = new Date(cache.lastModified);
let statusMsg = t('status.bar.last.modified', { date: this.formatDate(date) }); let statusMsg = t('status.bar.last.modified', { date: this.formatDate(date) });
// Use isSynced status from cache directly, no need to recalculate hash // Use isSynced status from cache directly, no need to recalculate hash
if (!cache.isSynced) { if (!cache.isSynced) {
statusMsg += ` (${t('status.bar.local.modified')})`; statusMsg += ` (${t('status.bar.local.modified')})`;
statusText.style.color = 'var(--color-orange)'; statusText.addClass('modified');
} else { } else {
statusMsg += ` (${t('status.bar.synced')})`; statusMsg += ` (${t('status.bar.synced')})`;
statusText.style.color = 'var(--color-green)'; statusText.addClass('synced');
} }
statusText.textContent = statusMsg; statusText.textContent = statusMsg;
} else { } else {
statusText.textContent = t('status.bar.not.published'); statusText.textContent = t('status.bar.not.published');
statusText.style.color = 'var(--text-muted)'; statusText.addClass('not-published');
} }
// Asynchronously update cache if it's about to expire // Asynchronously update cache if it's about to expire
@ -462,7 +481,7 @@ export default class GitSyncPlugin extends Plugin {
} }
} else { } else {
// No cache, need to fetch from GitHub // No cache, need to fetch from GitHub
console.log(`No cache, fetching file status from GitHub: ${activeFile.path}`); debugLog(`No cache, fetching file status from GitHub: ${activeFile.path}`);
await this.fetchAndCacheFileStatus(activeFile.path, statusText); await this.fetchAndCacheFileStatus(activeFile.path, statusText);
} }
} catch (error) { } catch (error) {
@ -505,7 +524,7 @@ export default class GitSyncPlugin extends Plugin {
const currentHash = FileCacheService.calculateContentHash(content); const currentHash = FileCacheService.calculateContentHash(content);
if (currentHash !== cache.contentHash) { if (currentHash !== cache.contentHash) {
console.log(`File content modified: ${file.path}`); debugLog(`File content modified: ${file.path}`);
// Update content hash and sync status in cache // Update content hash and sync status in cache
const updatedCache = { const updatedCache = {
@ -548,7 +567,7 @@ export default class GitSyncPlugin extends Plugin {
if (result.exists && result.lastModified) { if (result.exists && result.lastModified) {
const date = new Date(result.lastModified); const date = new Date(result.lastModified);
statusText.textContent = t('status.bar.last.modified', { date: this.formatDate(date) }); statusText.textContent = t('status.bar.last.modified', { date: this.formatDate(date) });
statusText.style.color = 'var(--text-normal)'; statusText.addClass('normal');
// Update cache // Update cache
await this.fileCacheService.updateFileCache( await this.fileCacheService.updateFileCache(
@ -561,7 +580,7 @@ export default class GitSyncPlugin extends Plugin {
); );
} else { } else {
statusText.textContent = t('status.bar.not.published'); statusText.textContent = t('status.bar.not.published');
statusText.style.color = 'var(--text-muted)'; statusText.addClass('not-published');
// Cache unpublished status // Cache unpublished status
await this.fileCacheService.updateFileCache( await this.fileCacheService.updateFileCache(
@ -587,7 +606,7 @@ export default class GitSyncPlugin extends Plugin {
*/ */
private async refreshFileCache(filePath: string) { private async refreshFileCache(filePath: string) {
try { try {
console.log(`Asynchronously refreshing file cache: ${filePath}`); debugLog(`Asynchronously refreshing file cache: ${filePath}`);
// Lightweight rate limit check - temporarily skip, let GitHub API handle rate limits itself // Lightweight rate limit check - temporarily skip, let GitHub API handle rate limits itself
@ -605,7 +624,7 @@ export default class GitSyncPlugin extends Plugin {
result.exists || false, result.exists || false,
this.app.vault this.app.vault
); );
console.log(`File cache refreshed: ${filePath}`); debugLog(`File cache refreshed: ${filePath}`);
} }
} catch (error) { } catch (error) {
console.error('Failed to refresh file cache:', error); console.error('Failed to refresh file cache:', error);
@ -639,9 +658,9 @@ export default class GitSyncPlugin extends Plugin {
// Show menu near status bar button // Show menu near status bar button
const rect = this.statusBarEl?.getBoundingClientRect(); const rect = this.statusBarEl?.getBoundingClientRect();
if (rect) { menu.showAtMouseEvent(rect
menu.showAtPosition({ x: rect.left, y: rect.top - 10 }); ? new MouseEvent('click', { clientX: rect.left, clientY: rect.top - 10 })
} : new MouseEvent('click'));
} }
private formatDate(date: Date): string { private formatDate(date: Date): string {
@ -838,7 +857,7 @@ export default class GitSyncPlugin extends Plugin {
uploadNotice.hide(); uploadNotice.hide();
new Notice(t('cos.upload.success')); new Notice(t('cos.upload.success'));
console.log('Image uploaded successfully:', uploadResult.url); debugLog('Image uploaded successfully:', uploadResult.url);
} else { } else {
throw new Error(uploadResult.message || 'Upload failed'); throw new Error(uploadResult.message || 'Upload failed');
} }
@ -876,11 +895,7 @@ export default class GitSyncPlugin extends Plugin {
// Create directory if it doesn't exist // Create directory if it doesn't exist
const dirPath = localPath.substring(0, localPath.lastIndexOf('/')); const dirPath = localPath.substring(0, localPath.lastIndexOf('/'));
if (dirPath) { if (dirPath) {
try { await this.app.vault.adapter.mkdir(dirPath);
await this.app.vault.createFolder(dirPath);
} catch (error) {
// Folder might already exist, ignore error
}
} }
// Convert File to ArrayBuffer // Convert File to ArrayBuffer
@ -895,7 +910,7 @@ export default class GitSyncPlugin extends Plugin {
editor.replaceRange(imageMarkdown, cursorPos); editor.replaceRange(imageMarkdown, cursorPos);
new Notice(t('cos.upload.success')); new Notice(t('cos.upload.success'));
console.log('Image saved locally:', localPath); debugLog('Image saved locally:', localPath);
} catch (error) { } catch (error) {
new Notice(t('cos.upload.failed', { error: error.message })); new Notice(t('cos.upload.failed', { error: error.message }));
console.error('Local image save failed:', error); console.error('Local image save failed:', error);
@ -915,11 +930,7 @@ export default class GitSyncPlugin extends Plugin {
// Create directory if it doesn't exist // Create directory if it doesn't exist
const dirPath = localPath.substring(0, localPath.lastIndexOf('/')); const dirPath = localPath.substring(0, localPath.lastIndexOf('/'));
if (dirPath) { if (dirPath) {
try { await this.app.vault.adapter.mkdir(dirPath);
await this.app.vault.createFolder(dirPath);
} catch (error) {
// Folder might already exist, ignore error
}
} }
// Convert File to ArrayBuffer // Convert File to ArrayBuffer
@ -928,7 +939,7 @@ export default class GitSyncPlugin extends Plugin {
// Save file to vault // Save file to vault
await this.app.vault.createBinary(localPath, arrayBuffer); await this.app.vault.createBinary(localPath, arrayBuffer);
console.log('Local image copy saved:', localPath); debugLog('Local image copy saved:', localPath);
} catch (error) { } catch (error) {
console.warn('Failed to save local image copy:', error); console.warn('Failed to save local image copy:', error);
} }
@ -946,7 +957,7 @@ class GitSyncSettingTab extends PluginSettingTab {
display(): void { display(): void {
const { containerEl } = this; const { containerEl } = this;
containerEl.empty(); containerEl.empty();
containerEl.createEl('h2', { text: t('settings.title') }); new Setting(containerEl).setName(t('settings.title')).setHeading();
// Language setting - put at the beginning // Language setting - put at the beginning
new Setting(containerEl) new Setting(containerEl)
@ -974,6 +985,7 @@ class GitSyncSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.githubToken) .setValue(this.plugin.settings.githubToken)
.onChange(async (value) => { .onChange(async (value) => {
this.plugin.settings.githubToken = value; this.plugin.settings.githubToken = value;
await this.plugin.saveSettings();
})); }));
let pathInfoEl: HTMLElement; let pathInfoEl: HTMLElement;
@ -986,6 +998,7 @@ class GitSyncSettingTab extends PluginSettingTab {
.onChange(async (value) => { .onChange(async (value) => {
this.plugin.settings.repositoryUrl = value; this.plugin.settings.repositoryUrl = value;
this.updatePathInfo(pathInfoEl, value); this.updatePathInfo(pathInfoEl, value);
await this.plugin.saveSettings();
})); }));
// Add path description display area // Add path description display area
@ -1006,18 +1019,36 @@ class GitSyncSettingTab extends PluginSettingTab {
return; return;
} }
console.log('=== Testing Repository URL ==='); if (!this.plugin.settings.githubToken) {
console.log('Input URL:', this.plugin.settings.repositoryUrl); new Notice(t('notice.token.required'));
return;
}
// Test URL parsing // Set loading state
const testResult = this.plugin.githubService.parseRepositoryUrl(this.plugin.settings.repositoryUrl); button.setButtonText(t('settings.test.url.loading'));
(button as any).setDisabled(true);
if (testResult) { try {
console.log('URL parsing successful:', testResult); debugLog('=== Testing GitHub Connection ===');
new Notice(`${t('notice.url.test.success')}\n${t('test.url.user')}: ${testResult.owner}\n${t('test.url.repo')}: ${testResult.repo}\n${t('test.url.path')}: ${testResult.path || t('test.url.root')}`, 6000); debugLog('Input URL:', this.plugin.settings.repositoryUrl);
} else {
console.log('URL parsing failed'); // Test connection (includes URL parsing, token validation, and repository access)
new Notice(t('notice.url.test.failed'), 6000); const testResult = await this.plugin.githubService.testConnection(this.plugin.settings.repositoryUrl);
if (testResult.success) {
debugLog('Connection test successful:', testResult);
new Notice(testResult.message, 8000);
} else {
debugLog('Connection test failed:', testResult.message);
new Notice(testResult.message, 6000);
}
} catch (error) {
debugLog('Connection test error:', error);
new Notice(t('github.api.operation.failed', { message: error.message }), 6000);
} finally {
// Reset button state
button.setButtonText(t('settings.test.url.button'));
(button as any).setDisabled(false);
} }
})); }));
@ -1033,10 +1064,7 @@ class GitSyncSettingTab extends PluginSettingTab {
})); }));
// Image processing main section // Image processing main section
containerEl.createEl('h2', { new Setting(containerEl).setName(t('settings.image.section.title')).setHeading();
text: t('settings.image.section.title'),
cls: 'git-sync-section-title'
});
// Enable image processing toggle // Enable image processing toggle
new Setting(containerEl) new Setting(containerEl)
@ -1054,10 +1082,7 @@ class GitSyncSettingTab extends PluginSettingTab {
// Only show image processing settings if enabled // Only show image processing settings if enabled
if (this.plugin.settings.enableImageProcessing) { if (this.plugin.settings.enableImageProcessing) {
// Cloud provider settings subsection // Cloud provider settings subsection
containerEl.createEl('h3', { new Setting(containerEl).setName(t('settings.cos.provider.section.title')).setHeading();
text: t('settings.cos.provider.section.title'),
cls: 'git-sync-subsection-title'
});
// Cloud storage provider // Cloud storage provider
new Setting(containerEl) new Setting(containerEl)
@ -1197,7 +1222,7 @@ class GitSyncSettingTab extends PluginSettingTab {
// Set loading state // Set loading state
button.setButtonText(t('settings.cos.test.loading')); button.setButtonText(t('settings.cos.test.loading'));
button.setDisabled(true); (button as any).setDisabled(true);
try { try {
const cosConfig = { const cosConfig = {
@ -1223,15 +1248,12 @@ class GitSyncSettingTab extends PluginSettingTab {
new Notice(t('cos.test.failed', { error: error.message })); new Notice(t('cos.test.failed', { error: error.message }));
} finally { } finally {
button.setButtonText(t('settings.cos.test.button')); button.setButtonText(t('settings.cos.test.button'));
button.setDisabled(false); (button as any).setDisabled(false);
} }
})); }));
// Image upload settings subsection // Image upload settings subsection
containerEl.createEl('h3', { new Setting(containerEl).setName(t('settings.image.upload.section.title')).setHeading();
text: t('settings.image.upload.section.title'),
cls: 'git-sync-subsection-title'
});
// Keep images locally // Keep images locally
new Setting(containerEl) new Setting(containerEl)
@ -1276,10 +1298,7 @@ class GitSyncSettingTab extends PluginSettingTab {
// Danger zone title // Danger zone title
containerEl.createEl('h2', { new Setting(containerEl).setName(t('settings.danger.zone.title')).setHeading();
text: t('settings.danger.zone.title'),
cls: 'danger-zone'
});
// Initialize repository // Initialize repository
new Setting(containerEl) new Setting(containerEl)
@ -1288,16 +1307,16 @@ class GitSyncSettingTab extends PluginSettingTab {
.addButton(button => { .addButton(button => {
const isVaultEmpty = this.isVaultEmpty(); const isVaultEmpty = this.isVaultEmpty();
button button
.setButtonText(t('settings.init.button')) .setButtonText(t('settings.init.button'));
.setDisabled(!isVaultEmpty) (button as any).setDisabled(!isVaultEmpty);
.onClick(async () => { button.onClick(async () => {
if (!this.plugin.settings.githubToken || !this.plugin.settings.repositoryUrl) { if (!this.plugin.settings.githubToken || !this.plugin.settings.repositoryUrl) {
new Notice(t('notice.config.required')); new Notice(t('notice.config.required'));
return; return;
} }
button.setButtonText(t('settings.init.loading')); button.setButtonText(t('settings.init.loading'));
button.setDisabled(true); (button as any).setDisabled(true);
try { try {
const result = await this.plugin.initializeRepository(); const result = await this.plugin.initializeRepository();
@ -1310,7 +1329,7 @@ class GitSyncSettingTab extends PluginSettingTab {
new Notice(t('notice.init.error')); new Notice(t('notice.init.error'));
} finally { } finally {
button.setButtonText(t('settings.init.button')); button.setButtonText(t('settings.init.button'));
button.setDisabled(!this.isVaultEmpty()); (button as any).setDisabled(!this.isVaultEmpty());
} }
}); });
@ -1333,7 +1352,7 @@ class GitSyncSettingTab extends PluginSettingTab {
} }
button.setButtonText(t('settings.sync.remote.to.local.loading')); button.setButtonText(t('settings.sync.remote.to.local.loading'));
button.setDisabled(true); (button as any).setDisabled(true);
try { try {
const result = await this.plugin.forceSyncRemoteToLocal(); const result = await this.plugin.forceSyncRemoteToLocal();
@ -1346,7 +1365,7 @@ class GitSyncSettingTab extends PluginSettingTab {
new Notice(t('notice.sync.error')); new Notice(t('notice.sync.error'));
} finally { } finally {
button.setButtonText(t('settings.sync.remote.to.local.button')); button.setButtonText(t('settings.sync.remote.to.local.button'));
button.setDisabled(false); (button as any).setDisabled(false);
} }
})); }));
@ -1364,7 +1383,7 @@ class GitSyncSettingTab extends PluginSettingTab {
} }
button.setButtonText(t('settings.sync.local.to.remote.loading')); button.setButtonText(t('settings.sync.local.to.remote.loading'));
button.setDisabled(true); (button as any).setDisabled(true);
try { try {
const result = await this.plugin.forceSyncLocalToRemote(); const result = await this.plugin.forceSyncLocalToRemote();
@ -1377,7 +1396,7 @@ class GitSyncSettingTab extends PluginSettingTab {
new Notice(t('notice.sync.error')); new Notice(t('notice.sync.error'));
} finally { } finally {
button.setButtonText(t('settings.sync.local.to.remote.button')); button.setButtonText(t('settings.sync.local.to.remote.button'));
button.setDisabled(false); (button as any).setDisabled(false);
} }
})); }));
@ -1398,10 +1417,7 @@ class GitSyncSettingTab extends PluginSettingTab {
})); }));
// Sponsor title // Sponsor title
containerEl.createEl('h2', { new Setting(containerEl).setName(t('settings.sponsor.section.title')).setHeading();
text: t('settings.sponsor.section.title'),
cls: 'sponsor'
});
// Sponsor section // Sponsor section
const sponsorSection = containerEl.createEl('div', { cls: 'setting-item' }); const sponsorSection = containerEl.createEl('div', { cls: 'setting-item' });
@ -1421,14 +1437,10 @@ class GitSyncSettingTab extends PluginSettingTab {
// PayPal sponsor button // PayPal sponsor button
const sponsorButton = sponsorControl.createEl('a', { const sponsorButton = sponsorControl.createEl('a', {
cls: 'mod-cta', cls: 'mod-cta git-sync-sponsor-button',
text: t('settings.sponsor.button'), text: t('settings.sponsor.button'),
href: 'https://paypal.me/xheldoncao' href: 'https://paypal.me/xheldoncao'
}); });
sponsorButton.style.textDecoration = 'none';
sponsorButton.style.padding = '6px 12px';
sponsorButton.style.borderRadius = '3px';
sponsorButton.style.display = 'inline-block';
// Show China mainland donation channel if Chinese interface // Show China mainland donation channel if Chinese interface
if (getActualLanguage() === 'zh') { if (getActualLanguage() === 'zh') {
@ -1443,14 +1455,12 @@ class GitSyncSettingTab extends PluginSettingTab {
text: 'https://www.xheldon.com/donate/', text: 'https://www.xheldon.com/donate/',
href: 'https://www.xheldon.com/donate/' href: 'https://www.xheldon.com/donate/'
}); });
chinaLink.style.color = 'var(--text-accent)';
chinaLink.style.textDecoration = 'none';
} }
} }
private isVaultEmpty(): boolean { private isVaultEmpty(): boolean {
const files = this.app.vault.getFiles(); const files = this.app.vault.getFiles();
return files.filter(file => !file.path.startsWith('.obsidian')).length === 0; return files.filter(file => !file.path.startsWith(this.app.vault.configDir)).length === 0;
} }
private updatePathInfo(el: HTMLElement, value: string) { private updatePathInfo(el: HTMLElement, value: string) {

View file

@ -1,9 +1,9 @@
{ {
"id": "git-sync", "id": "git-folder-sync",
"name": "Obsidian Git Folder Sync & Image Upload", "name": "Git Folder Sync",
"version": "1.0.0", "version": "1.0.7",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "Synchronize files in your Obsidian Vault with a specified directory in the designated GitHub repository (rather than the entire repository); send your images to cloud storage services.", "description": "Synchronize files in your Vault with a specified directory in the designated GitHub repository (rather than the entire repository); send your images to cloud storage services.",
"author": "Xheldon", "author": "Xheldon",
"authorUrl": "https://github.com/Xheldon", "authorUrl": "https://github.com/Xheldon",
"fundingUrl": "https://paypal.me/xheldoncao", "fundingUrl": "https://paypal.me/xheldoncao",

2736
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "git-sync", "name": "git-folder-sync",
"version": "1.0.0", "version": "1.0.7",
"description": "Synchronize files in your Obsidian Vault with a specified directory in the designated GitHub repository (rather than the entire repository); send your images to cloud storage services.", "description": "Synchronize files in your Obsidian Vault with a specified directory in the designated GitHub repository (rather than the entire repository); send your images to cloud storage services.",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View file

@ -334,3 +334,43 @@
padding-left: 8px; padding-left: 8px;
border-left: 3px solid var(--text-accent); border-left: 3px solid var(--text-accent);
} }
/* Status bar display states */
.git-sync-status-bar.hidden {
display: none;
}
.git-sync-status-bar.visible {
display: flex;
}
/* Status text color states */
.git-sync-status-text.modified {
color: var(--color-orange);
}
.git-sync-status-text.synced {
color: var(--color-green);
}
.git-sync-status-text.not-published {
color: var(--text-muted);
}
.git-sync-status-text.normal {
color: var(--text-normal);
}
/* Sponsor button styling */
.git-sync-sponsor-button {
text-decoration: none;
padding: 6px 12px;
border-radius: 3px;
display: inline-block;
}
/* China sponsor link styling */
.git-sync-china-sponsor a {
color: var(--text-accent);
text-decoration: none;
}

View file

@ -1,3 +1,10 @@
{ {
"1.0.0": "0.15.0" "1.0.0": "0.15.0",
"1.0.1": "0.15.0",
"1.0.2": "0.15.0",
"1.0.3": "0.15.0",
"1.0.4": "0.15.0",
"1.0.5": "0.15.0",
"1.0.6": "0.15.0",
"1.0.7": "0.15.0"
} }