mirror of
https://github.com/xheldon/git-folder-sync.git
synced 2026-07-22 17:40:25 +00:00
Compare commits
8 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a037bad76e | ||
|
|
c9ce06d2bf | ||
|
|
a96c297e1a | ||
|
|
c0948fe13b | ||
|
|
1850f3b895 | ||
|
|
d94e386dac | ||
|
|
f77759554a | ||
|
|
78d711a099 |
16 changed files with 485 additions and 229 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -8,6 +8,8 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -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
|
||||||
45
CHANGELOG.md
45
CHANGELOG.md
|
|
@ -5,9 +5,54 @@ 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/),
|
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).
|
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
|
## [1.0.1] - 2024-12-19
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Updated plugin name from "Git Sync" to "Git Folder Sync" for better clarity
|
- Updated plugin name from "Git Sync" to "Git Folder Sync" for better clarity
|
||||||
- Consistent naming across all files and documentation
|
- Consistent naming across all files and documentation
|
||||||
- Updated internationalization strings
|
- Updated internationalization strings
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ 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/Xheldon/git-folder-sync git-folder-sync
|
git clone https://github.com/Xheldon/git-folder-sync git-folder-sync
|
||||||
cd git-folder-ync
|
cd git-folder-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install dependencies:
|
2. Install dependencies:
|
||||||
|
|
@ -72,7 +72,7 @@ Repository path formats:
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `https://github.com/Xheldon/git-folder-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)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Git 同步 Obsidian 插件
|
# Git Folder Sync Obsidian 插件
|
||||||
|
|
||||||
一个支持与 GitHub 仓库同步的 Obsidian 插件,具有现代化界面和热重载开发支持。
|
一个支持与 GitHub 仓库同步的 Obsidian 插件,具有现代化界面和热重载开发支持。
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
- `https://github.com/Xheldon/git-folder-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. 状态栏同步按钮(右下角)
|
||||||
|
|
||||||
菜单选项:
|
菜单选项:
|
||||||
|
|
|
||||||
|
|
@ -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
47
create-release.sh
Executable 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"
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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': '检查中...',
|
||||||
|
|
@ -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...',
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
217
main.js
217
main.js
|
|
@ -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",
|
||||||
|
|
@ -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",
|
||||||
|
|
@ -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) {
|
||||||
|
|
@ -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
230
main.ts
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"id": "git-folder-sync",
|
"id": "git-folder-sync",
|
||||||
"name": "Git Folder Sync",
|
"name": "Git Folder Sync",
|
||||||
"version": "1.0.1",
|
"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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "git-folder-sync",
|
"name": "git-folder-sync",
|
||||||
"version": "1.0.1",
|
"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": {
|
||||||
|
|
|
||||||
40
styles.css
40
styles.css
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
{
|
{
|
||||||
"1.0.0": "0.15.0",
|
"1.0.0": "0.15.0",
|
||||||
"1.0.1": "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"
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue