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

6
.gitignore vendored
View file

@ -25,4 +25,8 @@ coverage/
*.temp *.temp
# 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

@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

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) {

230
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,16 +895,12 @@ 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
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
// Save file to vault // Save file to vault
await this.app.vault.createBinary(localPath, arrayBuffer); await this.app.vault.createBinary(localPath, 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,20 +930,16 @@ 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
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
// 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 }
const testResult = this.plugin.githubService.parseRepositoryUrl(this.plugin.settings.repositoryUrl);
// Set loading state
if (testResult) { button.setButtonText(t('settings.test.url.loading'));
console.log('URL parsing successful:', testResult); (button as any).setDisabled(true);
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);
} else { try {
console.log('URL parsing failed'); debugLog('=== Testing GitHub Connection ===');
new Notice(t('notice.url.test.failed'), 6000); debugLog('Input URL:', this.plugin.settings.repositoryUrl);
// Test connection (includes URL parsing, token validation, and repository access)
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,11 +1,11 @@
{ {
"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",
"isDesktopOnly": false "isDesktopOnly": false
} }

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"
} }