From 56b942e7ff44b5f82f95afb2f650ce3ecebf86f1 Mon Sep 17 00:00:00 2001 From: Xheldon Date: Fri, 1 Aug 2025 15:20:29 +0800 Subject: [PATCH] =?UTF-8?q?v1.0.4:=20=E4=BF=AE=E5=A4=8D=20Obsidian=20?= =?UTF-8?q?=E7=A4=BE=E5=8C=BA=E6=8F=92=E4=BB=B6=E5=8F=8D=E9=A6=88=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复硬编码的 .obsidian 路径,使用 app.vault.configDir - 将内联 JavaScript 样式移动到 CSS 文件中 - 减少 console.log 输出,避免污染开发控制台 - 移除 any 类型转换,提高类型安全性 - 提升代码质量以符合 Obsidian 社区插件标准 --- CHANGELOG.md | 25 +++++++ cos-service.ts | 8 +-- file-cache.ts | 2 +- github-service.ts | 65 ++++++++++++++++-- i18n-simple.ts | 26 +++++++- main.js | 164 ++++++++++++++++++++++++++++++---------------- main.ts | 146 +++++++++++++++++++++++++---------------- manifest.json | 2 +- package.json | 2 +- styles.css | 40 +++++++++++ versions.json | 3 +- 11 files changed, 356 insertions(+), 127 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29b87ec..ce31fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.4] - 2024-12-19 + +### Fixed +- Fixed hardcoded `.obsidian` paths to use `app.vault.configDir` for better compatibility +- Moved inline JavaScript styles to CSS files for better theme adaptability +- Reduced console.log pollution by implementing debug-only logging +- Improved type safety by removing `any` type casting where possible + +### Changed +- Enhanced code quality to meet Obsidian community plugin standards +- Better CSS class management for status indicators and UI elements + +## [1.0.3] - 2024-12-19 + +### Fixed +- Fixed GitHub Actions permissions for automatic release creation + +### Changed +- Bumped version for testing the fixed workflow + +## [1.0.2] - 2024-12-19 + +### Changed +- Clean release test version + ## [1.0.1] - 2024-12-19 ### Changed diff --git a/cos-service.ts b/cos-service.ts index 6738084..82c417c 100644 --- a/cos-service.ts +++ b/cos-service.ts @@ -147,13 +147,7 @@ export class CosService { // Generate signature for Tencent COS 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 - }); + // Debug logging removed for production // Convert File to ArrayBuffer for requestUrl const fileBuffer = await file.arrayBuffer(); diff --git a/file-cache.ts b/file-cache.ts index edc7da1..3246cbc 100644 --- a/file-cache.ts +++ b/file-cache.ts @@ -86,7 +86,7 @@ export class FileCacheService implements FileCacheManager { clearCache(): void { this.cache.clear(); localStorage.removeItem(this.CACHE_KEY); - console.log('All file caches cleared'); + // console.log('All file caches cleared'); } /** diff --git a/github-service.ts b/github-service.ts index f72dc0d..086dbb5 100644 --- a/github-service.ts +++ b/github-service.ts @@ -172,7 +172,7 @@ export class GitHubService { } 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) { console.error('URL is empty'); @@ -181,7 +181,7 @@ export class GitHubService { // Remove leading and trailing spaces const cleanUrl = url.trim(); - console.log('Cleaned URL:', cleanUrl); + // console.log('Cleaned URL:', cleanUrl); // Support two formats: // 1. https://github.com/username/repo/path (standard GitHub URL) @@ -197,7 +197,7 @@ export class GitHubService { repo: match[2], path: match[3] || '', }; - console.log('Parse result (standard GitHub URL):', result); + // console.log('Parse result (standard GitHub URL):', result); return result; } @@ -209,7 +209,7 @@ export class GitHubService { repo: match[2], path: match[3] || '', }; - console.log('Parse result (short format):', result); + // console.log('Parse result (short format):', result); return result; } @@ -221,6 +221,63 @@ export class GitHubService { return null; } + /** + * Test GitHub token and repository access + */ + async testConnection(repositoryUrl: string): Promise { + // 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 { const repoInfo = this.parseRepositoryUrl(repositoryUrl); if (!repoInfo) { diff --git a/i18n-simple.ts b/i18n-simple.ts index 45e28ac..565c57b 100644 --- a/i18n-simple.ts +++ b/i18n-simple.ts @@ -26,6 +26,7 @@ const translations = { 'settings.test.url.name': '测试仓库URL', 'settings.test.url.desc': '验证GitHub仓库路径格式是否正确', 'settings.test.url.button': '测试URL', + 'settings.test.url.loading': '测试中...', 'settings.sponsor.name': '💖 支持开发者', 'settings.sponsor.desc': '如果这个插件对你有帮助,欢迎请我喝杯咖啡!你的支持是我继续开发的动力。', 'settings.sponsor.button': '💝 PayPal 赞助', @@ -99,6 +100,7 @@ const translations = { 'notice.settings.saved': '设置已保存', 'notice.config.required': '请先配置GitHub Token和仓库地址', 'notice.url.required': '请先输入GitHub仓库路径', + 'notice.token.required': '请先填写GitHub Token', 'notice.url.test.success': 'URL格式正确!', 'notice.url.test.failed': 'URL格式不正确,请检查格式。详情请查看控制台。', 'notice.init.error': '初始化仓库时发生错误', @@ -162,6 +164,7 @@ const translations = { 'github.api.rate.limit.exceeded.short': 'GitHub API调用次数已达上限,请稍后重试', 'github.api.file.upload.success': '文件上传成功', 'github.api.all.files.download.success': '所有文件下载成功', + 'github.api.connection.test.success': '✅ 连接测试成功!\n👤 用户: {user}\n📁 仓库: {repo}\n🔑 权限: {permissions}', // Status bar 'status.bar.checking': '检查中...', @@ -230,6 +233,7 @@ const translations = { 'settings.test.url.name': 'Test Repository URL', 'settings.test.url.desc': 'Verify if GitHub repository path format is correct', 'settings.test.url.button': 'Test URL', + 'settings.test.url.loading': 'Testing...', '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.button': '💝 PayPal Sponsor', @@ -302,6 +306,7 @@ const translations = { 'notice.settings.saved': 'Settings saved', 'notice.config.required': 'Please configure GitHub Token and repository address 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.failed': 'URL format is incorrect, please check format. See console for details.', '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.file.upload.success': 'File uploaded 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.checking': 'Checking...', @@ -415,12 +421,28 @@ const translations = { // Current language 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 function detectObsidianLanguage(): 'zh' | 'en' { // Check Obsidian's language settings - more accurate method const obsidianLang = localStorage.getItem('language') || - (window as any).app?.vault?.adapter?.path?.locale || - (window as any).moment?.locale(); + (window as ExtendedWindow).app?.vault?.adapter?.path?.locale || + (window as ExtendedWindow).moment?.locale(); // Check browser language const browserLang = navigator.language || navigator.languages?.[0] || 'en'; diff --git a/main.js b/main.js index 681de8d..323e451 100644 --- a/main.js +++ b/main.js @@ -355,6 +355,7 @@ var init_i18n_simple = __esm({ "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.button": "\u6D4B\u8BD5URL", + "settings.test.url.loading": "\u6D4B\u8BD5\u4E2D...", "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.button": "\u{1F49D} PayPal \u8D5E\u52A9", @@ -426,6 +427,7 @@ var init_i18n_simple = __esm({ "notice.settings.saved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58", "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.token.required": "\u8BF7\u5148\u586B\u5199GitHub Token", "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.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.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.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.checking": "\u68C0\u67E5\u4E2D...", "status.bar.sync": "\u540C\u6B65", @@ -547,6 +550,7 @@ var init_i18n_simple = __esm({ "settings.test.url.name": "Test Repository URL", "settings.test.url.desc": "Verify if GitHub repository path format is correct", "settings.test.url.button": "Test URL", + "settings.test.url.loading": "Testing...", "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.button": "\u{1F49D} PayPal Sponsor", @@ -617,6 +621,7 @@ var init_i18n_simple = __esm({ "notice.settings.saved": "Settings saved", "notice.config.required": "Please configure GitHub Token and repository address 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.failed": "URL format is incorrect, please check format. See console for details.", "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.file.upload.success": "File uploaded 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.checking": "Checking...", "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 date = new Date().toUTCString(); 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 response = await (0, import_obsidian2.requestUrl)({ url, @@ -4395,13 +4394,11 @@ var GitHubService = class { } } parseRepositoryUrl(url) { - console.log("Parsing repository URL:", url); if (!url) { console.error("URL is empty"); return null; } const cleanUrl = url.trim(); - console.log("Cleaned URL:", cleanUrl); let match; match = cleanUrl.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\/(.*))?$/); if (match) { @@ -4410,7 +4407,6 @@ var GitHubService = class { repo: match[2], path: match[3] || "" }; - console.log("Parse result (standard GitHub URL):", result); return result; } match = cleanUrl.match(/^([^\/]+)\/([^\/]+)(?:\/(.*))?$/); @@ -4420,7 +4416,6 @@ var GitHubService = class { repo: match[2], path: match[3] || "" }; - console.log("Parse result (short format):", result); return result; } console.error("Unable to parse URL format:", cleanUrl); @@ -4429,6 +4424,52 @@ var GitHubService = class { console.error("2. username/repo/path (short format)"); 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) { const repoInfo = this.parseRepositoryUrl(repositoryUrl); if (!repoInfo) { @@ -4672,7 +4713,6 @@ var FileCacheService = class { clearCache() { this.cache.clear(); localStorage.removeItem(this.CACHE_KEY); - console.log("All file caches cleared"); } /** * Check if cache is valid @@ -4762,6 +4802,11 @@ var FileCacheService = class { // main.ts init_cos_service(); +function debugLog(...args) { + if (true) { + console.log("[Git Folder Sync]", ...args); + } +} var GitSyncPlugin = class extends import_obsidian3.Plugin { constructor() { super(...arguments); @@ -4793,7 +4838,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { this.registerEvent( this.app.vault.on("modify", (file) => { 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) { this.onFileContentModified(file); } @@ -4991,7 +5036,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { async forceSyncLocalToRemote() { try { 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) { return { success: false, message: t("no.syncable.files.in.vault") }; } @@ -5007,7 +5052,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { ); if (result.success) { processed++; - console.log(`Successfully synced file: ${file.path}`); + debugLog(`Successfully synced file: ${file.path}`); await this.fileCacheService.updateFileCache( file.path, file.path, @@ -5038,25 +5083,25 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { } isVaultEmpty() { 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() { 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) => { - if (file.path.startsWith(".obsidian")) { + if (file.path.startsWith(this.app.vault.configDir)) { return false; } if (this.settings.keepLocalImages && this.settings.localImagePath) { const normalizedImagePath = this.settings.localImagePath.replace(/^\/+|\/+$/g, ""); 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 true; }); - console.log("Filtered files:", filteredFiles.map((f) => f.path)); + debugLog("Filtered files:", filteredFiles.map((f) => f.path)); return filteredFiles; } async createFileInVault(path, content) { @@ -5077,11 +5122,13 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { const activeFile = this.app.workspace.getActiveFile(); if (!activeFile || !activeFile.path.endsWith(".md")) { this.statusBarEl.empty(); - this.statusBarEl.style.display = "none"; + this.statusBarEl.removeClass("visible"); + this.statusBarEl.addClass("hidden"); return; } this.currentFile = activeFile; - this.statusBarEl.style.display = "flex"; + this.statusBarEl.removeClass("hidden"); + this.statusBarEl.addClass("visible"); this.statusBarEl.empty(); const statusText = this.statusBarEl.createEl("span", { cls: "git-sync-status-text", @@ -5095,27 +5142,27 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { try { const cache = this.fileCacheService.getFileCache(activeFile.path); 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) { const date = new Date(cache.lastModified); let statusMsg = t("status.bar.last.modified", { date: this.formatDate(date) }); if (!cache.isSynced) { statusMsg += ` (${t("status.bar.local.modified")})`; - statusText.style.color = "var(--color-orange)"; + statusText.addClass("modified"); } else { statusMsg += ` (${t("status.bar.synced")})`; - statusText.style.color = "var(--color-green)"; + statusText.addClass("synced"); } statusText.textContent = statusMsg; } else { 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)) { this.refreshFileCache(activeFile.path).catch(console.error); } } 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); } } catch (error) { @@ -5147,7 +5194,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { const content = await this.app.vault.read(file); const currentHash = FileCacheService.calculateContentHash(content); if (currentHash !== cache.contentHash) { - console.log(`File content modified: ${file.path}`); + debugLog(`File content modified: ${file.path}`); const updatedCache = { ...cache, contentHash: currentHash, @@ -5181,7 +5228,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { if (result.exists && result.lastModified) { const date = new Date(result.lastModified); statusText.textContent = t("status.bar.last.modified", { date: this.formatDate(date) }); - statusText.style.color = "var(--text-normal)"; + statusText.addClass("normal"); await this.fileCacheService.updateFileCache( filePath, filePath, @@ -5194,7 +5241,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { ); } else { statusText.textContent = t("status.bar.not.published"); - statusText.style.color = "var(--text-muted)"; + statusText.addClass("not-published"); await this.fileCacheService.updateFileCache( filePath, filePath, @@ -5217,7 +5264,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { */ async refreshFileCache(filePath) { try { - console.log(`Asynchronously refreshing file cache: ${filePath}`); + debugLog(`Asynchronously refreshing file cache: ${filePath}`); const result = await this.githubService.getFileLastModified( this.settings.repositoryUrl, filePath @@ -5232,7 +5279,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { result.exists || false, this.app.vault ); - console.log(`File cache refreshed: ${filePath}`); + debugLog(`File cache refreshed: ${filePath}`); } } catch (error) { console.error("Failed to refresh file cache:", error); @@ -5400,7 +5447,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { } uploadNotice.hide(); new import_obsidian3.Notice(t("cos.upload.success")); - console.log("Image uploaded successfully:", uploadResult.url); + debugLog("Image uploaded successfully:", uploadResult.url); } else { throw new Error(uploadResult.message || "Upload failed"); } @@ -5439,7 +5486,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { const cursorPos = editor.getCursor(); editor.replaceRange(imageMarkdown, cursorPos); new import_obsidian3.Notice(t("cos.upload.success")); - console.log("Image saved locally:", localPath); + debugLog("Image saved locally:", localPath); } catch (error) { new import_obsidian3.Notice(t("cos.upload.failed", { error: error.message })); console.error("Local image save failed:", error); @@ -5463,7 +5510,7 @@ var GitSyncPlugin = class extends import_obsidian3.Plugin { } const arrayBuffer = await file.arrayBuffer(); await this.app.vault.createBinary(localPath, arrayBuffer); - console.log("Local image copy saved:", localPath); + debugLog("Local image copy saved:", localPath); } catch (error) { console.warn("Failed to save local image copy:", error); } @@ -5492,11 +5539,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) => { this.plugin.settings.githubToken = value; + await this.plugin.saveSettings(); })); 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) => { this.plugin.settings.repositoryUrl = value; this.updatePathInfo(pathInfoEl, value); + await this.plugin.saveSettings(); })); pathInfoEl = containerEl.createEl("div", { cls: "setting-item-description git-sync-path-info", @@ -5507,18 +5556,29 @@ var GitSyncSettingTab = class extends import_obsidian3.PluginSettingTab { new import_obsidian3.Notice(t("notice.url.required")); return; } - console.log("=== Testing Repository URL ==="); - console.log("Input URL:", this.plugin.settings.repositoryUrl); - const testResult = this.plugin.githubService.parseRepositoryUrl(this.plugin.settings.repositoryUrl); - if (testResult) { - console.log("URL parsing successful:", testResult); - new import_obsidian3.Notice(`${t("notice.url.test.success")} -${t("test.url.user")}: ${testResult.owner} -${t("test.url.repo")}: ${testResult.repo} -${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3); - } else { - console.log("URL parsing failed"); - new import_obsidian3.Notice(t("notice.url.test.failed"), 6e3); + if (!this.plugin.settings.githubToken) { + new import_obsidian3.Notice(t("notice.token.required")); + return; + } + button.setButtonText(t("settings.test.url.loading")); + button.setDisabled(true); + try { + debugLog("=== Testing GitHub Connection ==="); + debugLog("Input URL:", this.plugin.settings.repositoryUrl); + const testResult = await this.plugin.githubService.testConnection(this.plugin.settings.repositoryUrl); + if (testResult.success) { + 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) => { @@ -5746,14 +5806,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" }); const sponsorButton = sponsorControl.createEl("a", { - cls: "mod-cta", + cls: "mod-cta git-sync-sponsor-button", text: t("settings.sponsor.button"), 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") { const chinaSponsorContainer = sponsorControl.createEl("div", { cls: "git-sync-china-sponsor" @@ -5765,13 +5821,11 @@ ${t("test.url.path")}: ${testResult.path || t("test.url.root")}`, 6e3); text: "https://www.xheldon.com/donate/", href: "https://www.xheldon.com/donate/" }); - chinaLink.style.color = "var(--text-accent)"; - chinaLink.style.textDecoration = "none"; } } isVaultEmpty() { 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) { if (el) { diff --git a/main.ts b/main.ts index e95d506..2f511af 100644 --- a/main.ts +++ b/main.ts @@ -1,10 +1,30 @@ 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 { GitHubService } from './github-service'; import { setLanguage, t, getSupportedLanguages, getActualLanguage } from './i18n-simple'; import { FileCacheService } from './file-cache'; 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 { settings: GitSyncSettings; githubService: GitHubService; @@ -51,7 +71,7 @@ export default class GitSyncPlugin extends Plugin { this.app.vault.on('modify', (file) => { // Only handle markdown files that are currently open 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 if (file instanceof TFile) { this.onFileContentModified(file); @@ -127,10 +147,10 @@ export default class GitSyncPlugin extends Plugin { // Add button based on settings 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 - (this.app as any).setting.open(); - (this.app as any).setting.openTabById(this.manifest.id); + (this.app as ExtendedApp).setting.open(); + (this.app as ExtendedApp).setting.openTabById(this.manifest.id); }); } } @@ -156,7 +176,7 @@ export default class GitSyncPlugin extends Plugin { }); }); - const rect = (editor as any).containerEl?.getBoundingClientRect(); + const rect = (editor as ExtendedEditor).containerEl?.getBoundingClientRect(); if (rect) { menu.showAtPosition({ x: rect.right - 100, y: rect.top + 50 }); } else { @@ -299,7 +319,7 @@ export default class GitSyncPlugin extends Plugin { async forceSyncLocalToRemote(): Promise { try { 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) { return { success: false, message: t('no.syncable.files.in.vault') }; @@ -319,7 +339,7 @@ export default class GitSyncPlugin extends Plugin { if (result.success) { processed++; - console.log(`Successfully synced file: ${file.path}`); + debugLog(`Successfully synced file: ${file.path}`); // Update cache immediately - file synced to remote await this.fileCacheService.updateFileCache( @@ -356,17 +376,17 @@ export default class GitSyncPlugin extends Plugin { private isVaultEmpty(): boolean { 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[] { 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 => { - // Always exclude .obsidian folder - if (file.path.startsWith('.obsidian')) { + // Always exclude Obsidian config folder + if (file.path.startsWith(this.app.vault.configDir)) { return false; } @@ -374,7 +394,7 @@ export default class GitSyncPlugin extends Plugin { if (this.settings.keepLocalImages && this.settings.localImagePath) { const normalizedImagePath = this.settings.localImagePath.replace(/^\/+|\/+$/g, ''); // Remove leading/trailing slashes 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; } } @@ -382,7 +402,7 @@ export default class GitSyncPlugin extends Plugin { return true; }); - console.log('Filtered files:', filteredFiles.map(f => f.path)); + debugLog('Filtered files:', filteredFiles.map(f => f.path)); return filteredFiles; } @@ -407,12 +427,14 @@ export default class GitSyncPlugin extends Plugin { const activeFile = this.app.workspace.getActiveFile(); if (!activeFile || !activeFile.path.endsWith('.md')) { this.statusBarEl.empty(); - this.statusBarEl.style.display = 'none'; + this.statusBarEl.removeClass('visible'); + this.statusBarEl.addClass('hidden'); return; } this.currentFile = activeFile; - this.statusBarEl.style.display = 'flex'; + this.statusBarEl.removeClass('hidden'); + this.statusBarEl.addClass('visible'); this.statusBarEl.empty(); // Create status text @@ -435,25 +457,25 @@ export default class GitSyncPlugin extends Plugin { if (cache) { // 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) { const date = new Date(cache.lastModified); let statusMsg = t('status.bar.last.modified', { date: this.formatDate(date) }); - // Use isSynced status from cache directly, no need to recalculate hash - if (!cache.isSynced) { - statusMsg += ` (${t('status.bar.local.modified')})`; - statusText.style.color = 'var(--color-orange)'; - } else { - statusMsg += ` (${t('status.bar.synced')})`; - statusText.style.color = 'var(--color-green)'; - } + // Use isSynced status from cache directly, no need to recalculate hash + if (!cache.isSynced) { + statusMsg += ` (${t('status.bar.local.modified')})`; + statusText.addClass('modified'); + } else { + statusMsg += ` (${t('status.bar.synced')})`; + statusText.addClass('synced'); + } statusText.textContent = statusMsg; } else { 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 @@ -462,7 +484,7 @@ export default class GitSyncPlugin extends Plugin { } } else { // 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); } } catch (error) { @@ -505,7 +527,7 @@ export default class GitSyncPlugin extends Plugin { const currentHash = FileCacheService.calculateContentHash(content); 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 const updatedCache = { @@ -548,7 +570,7 @@ export default class GitSyncPlugin extends Plugin { if (result.exists && result.lastModified) { const date = new Date(result.lastModified); statusText.textContent = t('status.bar.last.modified', { date: this.formatDate(date) }); - statusText.style.color = 'var(--text-normal)'; + statusText.addClass('normal'); // Update cache await this.fileCacheService.updateFileCache( @@ -561,7 +583,7 @@ export default class GitSyncPlugin extends Plugin { ); } else { statusText.textContent = t('status.bar.not.published'); - statusText.style.color = 'var(--text-muted)'; + statusText.addClass('not-published'); // Cache unpublished status await this.fileCacheService.updateFileCache( @@ -587,7 +609,7 @@ export default class GitSyncPlugin extends Plugin { */ private async refreshFileCache(filePath: string) { 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 @@ -605,7 +627,7 @@ export default class GitSyncPlugin extends Plugin { result.exists || false, this.app.vault ); - console.log(`File cache refreshed: ${filePath}`); + debugLog(`File cache refreshed: ${filePath}`); } } catch (error) { console.error('Failed to refresh file cache:', error); @@ -838,7 +860,7 @@ export default class GitSyncPlugin extends Plugin { uploadNotice.hide(); new Notice(t('cos.upload.success')); - console.log('Image uploaded successfully:', uploadResult.url); + debugLog('Image uploaded successfully:', uploadResult.url); } else { throw new Error(uploadResult.message || 'Upload failed'); } @@ -895,7 +917,7 @@ export default class GitSyncPlugin extends Plugin { editor.replaceRange(imageMarkdown, cursorPos); new Notice(t('cos.upload.success')); - console.log('Image saved locally:', localPath); + debugLog('Image saved locally:', localPath); } catch (error) { new Notice(t('cos.upload.failed', { error: error.message })); console.error('Local image save failed:', error); @@ -928,7 +950,7 @@ export default class GitSyncPlugin extends Plugin { // Save file to vault await this.app.vault.createBinary(localPath, arrayBuffer); - console.log('Local image copy saved:', localPath); + debugLog('Local image copy saved:', localPath); } catch (error) { console.warn('Failed to save local image copy:', error); } @@ -974,6 +996,7 @@ class GitSyncSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.githubToken) .onChange(async (value) => { this.plugin.settings.githubToken = value; + await this.plugin.saveSettings(); })); let pathInfoEl: HTMLElement; @@ -986,6 +1009,7 @@ class GitSyncSettingTab extends PluginSettingTab { .onChange(async (value) => { this.plugin.settings.repositoryUrl = value; this.updatePathInfo(pathInfoEl, value); + await this.plugin.saveSettings(); })); // Add path description display area @@ -1006,18 +1030,36 @@ class GitSyncSettingTab extends PluginSettingTab { return; } - console.log('=== Testing Repository URL ==='); - console.log('Input URL:', this.plugin.settings.repositoryUrl); - - // Test URL parsing - const testResult = this.plugin.githubService.parseRepositoryUrl(this.plugin.settings.repositoryUrl); - - if (testResult) { - console.log('URL parsing successful:', testResult); - 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 { - console.log('URL parsing failed'); - new Notice(t('notice.url.test.failed'), 6000); + if (!this.plugin.settings.githubToken) { + new Notice(t('notice.token.required')); + return; + } + + // Set loading state + button.setButtonText(t('settings.test.url.loading')); + button.setDisabled(true); + + try { + debugLog('=== Testing GitHub Connection ==='); + 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.setDisabled(false); } })); @@ -1421,14 +1463,10 @@ class GitSyncSettingTab extends PluginSettingTab { // PayPal sponsor button const sponsorButton = sponsorControl.createEl('a', { - cls: 'mod-cta', + cls: 'mod-cta git-sync-sponsor-button', text: t('settings.sponsor.button'), 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 if (getActualLanguage() === 'zh') { @@ -1443,14 +1481,12 @@ class GitSyncSettingTab extends PluginSettingTab { text: 'https://www.xheldon.com/donate/', href: 'https://www.xheldon.com/donate/' }); - chinaLink.style.color = 'var(--text-accent)'; - chinaLink.style.textDecoration = 'none'; } } private isVaultEmpty(): boolean { 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) { diff --git a/manifest.json b/manifest.json index 574ede4..3b84761 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "git-folder-sync", "name": "Git Folder Sync", - "version": "1.0.3", + "version": "1.0.4", "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.", "author": "Xheldon", diff --git a/package.json b/package.json index 03296bf..12218e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "git-folder-sync", - "version": "1.0.3", + "version": "1.0.4", "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", "scripts": { diff --git a/styles.css b/styles.css index 600c5e7..5736ce6 100644 --- a/styles.css +++ b/styles.css @@ -334,3 +334,43 @@ padding-left: 8px; 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; +} diff --git a/versions.json b/versions.json index a13a888..391867a 100644 --- a/versions.json +++ b/versions.json @@ -2,5 +2,6 @@ "1.0.0": "0.15.0", "1.0.1": "0.15.0", "1.0.2": "0.15.0", - "1.0.3": "0.15.0" + "1.0.3": "0.15.0", + "1.0.4": "0.15.0" } \ No newline at end of file