commit 33b42845774845795678cfe441d5ce281b7f894d Author: Xheldon Date: Fri Jul 25 19:53:04 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2b307f --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# compiled output +main.js +*.js.map + +# dependencies +node_modules/ + +# logs +*.log +npm-debug.log* + +# editor +.vscode/ +.idea/ + +# system files +.DS_Store +Thumbs.db + +# test +coverage/ +.nyc_output/ + +# temporary files +*.tmp +*.temp \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0033200 --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# Git 同步 Obsidian 插件 + +一个支持与 GitHub 仓库同步的 Obsidian 插件,使用 React 构建用户界面,支持热重载开发。 + +## 功能特性 + +- 🔄 **双向同步**: 支持将笔记同步到 GitHub 或从 GitHub 拉取笔记 +- 🎯 **单文件操作**: 可以单独同步当前编辑的文件 +- 📁 **批量操作**: 支持整个 Vault 的批量同步 +- 🔧 **可视化配置**: 使用 React 构建的现代化配置界面 +- 🚀 **热重载开发**: 支持开发时的热重载 +- 📂 **递归文件夹**: 完整支持文件夹结构的递归同步 + +## 安装 + +### 手动安装 + +1. 下载最新的 release 文件 +2. 将文件解压到你的 Obsidian 插件目录:`{vault}/.obsidian/plugins/git-sync/` +3. 重启 Obsidian +4. 在设置中启用"Git Sync"插件 + +### 开发安装 + +1. 克隆此仓库到你的插件目录: + + ```bash + cd {vault}/.obsidian/plugins/ + git clone https://github.com/你的用户名/obsidian-git-sync git-sync + cd git-sync + ``` + +2. 安装依赖: + + ```bash + npm install + ``` + +3. 开发模式(支持热重载): + + ```bash + npm run dev + ``` + +4. 构建生产版本: + ```bash + npm run build + ``` + +## 配置 + +### 1. GitHub Personal Access Token + +首先需要创建一个 GitHub Personal Access Token: + +1. 访问 [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens) +2. 点击"Generate new token (classic)" +3. 选择以下权限: + - `repo` (完整的仓库访问权限) +4. 复制生成的 token + +### 2. 仓库路径配置 + +仓库路径格式:`@https://github.com/用户名/仓库名/路径/到/文件夹` + +示例: + +- `@https://github.com/Xheldon/git-sync/data/_post` +- `@https://github.com/username/notes/obsidian-vault` + +## 使用方法 + +### 编辑器按钮 + +点击左侧边栏的设置图标打开配置界面。 + +### 笔记同步菜单 + +在编辑笔记时,可以通过以下方式访问同步菜单: + +1. 使用命令面板:`Ctrl/Cmd + P` → 搜索"显示同步菜单" +2. 右键点击编辑器 → 选择"Git 同步" + +菜单选项: + +- **同步当前文件到远端**: 将当前文件上传到 GitHub +- **拉取远端覆盖当前文件**: 从 GitHub 下载文件覆盖本地 + +### 配置界面功能 + +#### 基本设置 + +- **GitHub Personal Token**: 输入你的访问令牌 +- **GitHub 仓库路径**: 配置目标仓库和路径 + +#### 批量操作 + +- **初始化仓库**: 当 Vault 为空时,从远端下载所有文件 +- **强制同步远端到本地**: 将远端文件同步到本地(会覆盖同名文件) +- **强制同步本地到远端**: 将本地文件同步到远端 + +## 开发 + +### 项目结构 + +``` +├── main.ts # 插件主文件 +├── types.ts # 类型定义 +├── github-service.ts # GitHub API服务 +├── settings-modal.tsx # React配置界面 +├── styles.css # 样式文件 +├── manifest.json # 插件清单 +├── package.json # 项目配置 +├── tsconfig.json # TypeScript配置 +├── esbuild.config.mjs # 构建配置 +└── version-bump.mjs # 版本管理脚本 +``` + +### 开发命令 + +```bash +# 安装依赖 +npm install + +# 开发模式(热重载) +npm run dev + +# 构建生产版本 +npm run build + +# 版本管理 +npm run version +``` + +## 技术栈 + +- **TypeScript**: 主要开发语言 +- **React**: 用户界面框架 +- **Obsidian API**: 插件核心 API +- **GitHub API**: 通过@octokit/rest 进行仓库操作 +- **esbuild**: 快速构建工具 + +## 注意事项 + +1. **权限要求**: 需要 GitHub 仓库的写入权限 +2. **文件冲突**: 强制同步会覆盖现有文件,请谨慎使用 +3. **网络要求**: 需要稳定的网络连接访问 GitHub API +4. **Token 安全**: 请妥善保管你的 GitHub Personal Access Token + +## 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 赞助 + +如果这个插件对你有帮助,欢迎请我喝杯咖啡 ☕ + +[![PayPal](https://img.shields.io/badge/PayPal-支持赞助-blue?style=for-the-badge&logo=paypal)](https://paypal.me/xheldoncao) + +你的支持是我继续开发和维护这个项目的动力! + +## 许可证 + +MIT License diff --git a/data.json b/data.json new file mode 100644 index 0000000..6fa15f5 --- /dev/null +++ b/data.json @@ -0,0 +1,7 @@ +{ + "githubToken": "", + "repositoryUrl": "", + "lastSyncTime": 0, + "showRibbonIcon": true, + "language": "auto" +} \ No newline at end of file diff --git a/esbuild.config.mjs b/esbuild.config.mjs new file mode 100644 index 0000000..74601a0 --- /dev/null +++ b/esbuild.config.mjs @@ -0,0 +1,49 @@ +import esbuild from "esbuild"; +import process from "process"; +import builtins from "builtin-modules"; + +const banner = +`/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ +`; + +const prod = (process.argv[2] === 'production'); + +const context = await esbuild.context({ + banner: { + js: banner, + }, + entryPoints: ['main.ts'], + bundle: true, + external: [ + 'obsidian', + 'electron', + '@codemirror/autocomplete', + '@codemirror/collab', + '@codemirror/commands', + '@codemirror/language', + '@codemirror/lint', + '@codemirror/search', + '@codemirror/state', + '@codemirror/view', + '@lezer/common', + '@lezer/highlight', + '@lezer/lr', + ...builtins], + format: 'cjs', + target: 'es2018', + logLevel: "info", + sourcemap: prod ? false : 'inline', + treeShaking: true, + outfile: 'main.js', + jsx: 'automatic', +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} \ No newline at end of file diff --git a/file-cache.ts b/file-cache.ts new file mode 100644 index 0000000..ab2c6b8 --- /dev/null +++ b/file-cache.ts @@ -0,0 +1,196 @@ +import { FileCache, FileCacheManager } from './types'; + +export class FileCacheService implements FileCacheManager { + private cache: Map = new Map(); + private readonly CACHE_KEY = 'git-sync-file-cache'; + private readonly DEFAULT_CACHE_AGE = 5 * 60 * 1000; // 5 minutes cache validity period + + constructor() { + this.loadCacheFromStorage(); + } + + /** + * Load cache from local storage + */ + private loadCacheFromStorage(): void { + try { + const stored = localStorage.getItem(this.CACHE_KEY); + if (stored) { + const cacheData = JSON.parse(stored); + this.cache = new Map(Object.entries(cacheData)); + console.log(`Loaded ${this.cache.size} file caches`); + } + } catch (error) { + console.error('Failed to load file cache:', error); + this.cache.clear(); + } + } + + /** + * Save cache to local storage + */ + private saveCacheToStorage(): void { + try { + const cacheObj = Object.fromEntries(this.cache); + localStorage.setItem(this.CACHE_KEY, JSON.stringify(cacheObj)); + } catch (error) { + console.error('Failed to save file cache:', error); + } + } + + /** + * Get file cache + */ + getFileCache(filePath: string): FileCache | null { + const cache = this.cache.get(filePath); + if (!cache) { + console.log(`File ${filePath} has no cache`); + return null; + } + + if (!this.isCacheValid(cache)) { + console.log(`File ${filePath} cache has expired`); + this.removeFileCache(filePath); + return null; + } + + console.log(`File ${filePath} using cache`); + return cache; + } + + /** + * Set file cache + */ + setFileCache(filePath: string, cache: FileCache): void { + this.cache.set(filePath, { + ...cache, + cacheTime: Date.now() + }); + this.saveCacheToStorage(); + console.log(`File ${filePath} cache updated`); + } + + /** + * Remove file cache + */ + removeFileCache(filePath: string): void { + if (this.cache.delete(filePath)) { + this.saveCacheToStorage(); + console.log(`File ${filePath} cache removed`); + } + } + + /** + * Clear all cache + */ + clearCache(): void { + this.cache.clear(); + localStorage.removeItem(this.CACHE_KEY); + console.log('All file caches cleared'); + } + + /** + * Check if cache is valid + */ + isCacheValid(cache: FileCache, maxAge?: number): boolean { + const age = maxAge || this.DEFAULT_CACHE_AGE; + return (Date.now() - cache.cacheTime) < age; + } + + /** + * Get all cached files + */ + getAllCachedFiles(): FileCache[] { + return Array.from(this.cache.values()); + } + + /** + * Calculate file content hash + */ + static calculateContentHash(content: string): string { + let hash = 0; + for (let i = 0; i < content.length; i++) { + const char = content.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32-bit integer + } + return hash.toString(36); + } + + /** + * Check if file has been modified locally + */ + async checkLocalFileModified(filePath: string, vault: any): Promise { + try { + const cache = this.getFileCache(filePath); + if (!cache) return true; // Consider modified when no cache exists + + const file = vault.getAbstractFileByPath(filePath); + if (!file) return true; // Consider modified when file doesn't exist + + const content = await vault.read(file); + const currentHash = FileCacheService.calculateContentHash(content); + + return currentHash !== cache.contentHash; + } catch (error) { + console.error('Failed to check file modification status:', error); + return true; // Consider modified when error occurs + } + } + + /** + * Update file cache status + */ + async updateFileCache( + filePath: string, + githubPath: string, + lastModified: string, + sha: string, + isPublished: boolean, + vault: any + ): Promise { + try { + const file = vault.getAbstractFileByPath(filePath); + if (!file) return; + + const content = await vault.read(file); + const contentHash = FileCacheService.calculateContentHash(content); + + const cache: FileCache = { + filePath, + githubPath, + lastModified, + sha, + isPublished, + isSynced: true, // Newly updated cache is considered synced + cacheTime: Date.now(), + fileSize: content.length, + contentHash + }; + + this.setFileCache(filePath, cache); + } catch (error) { + console.error('Failed to update file cache:', error); + } + } + + /** + * Get cache statistics + */ + getCacheStats(): { + totalFiles: number; + publishedFiles: number; + syncedFiles: number; + expiredCaches: number; + } { + const allCaches = this.getAllCachedFiles(); + const expiredCaches = allCaches.filter(cache => !this.isCacheValid(cache)); + + return { + totalFiles: allCaches.length, + publishedFiles: allCaches.filter(cache => cache.isPublished).length, + syncedFiles: allCaches.filter(cache => cache.isSynced).length, + expiredCaches: expiredCaches.length + }; + } +} \ No newline at end of file diff --git a/github-service.ts b/github-service.ts new file mode 100644 index 0000000..f72dc0d --- /dev/null +++ b/github-service.ts @@ -0,0 +1,418 @@ +import { Octokit } from '@octokit/rest'; +import { GitHubFile, SyncResult } from './types'; +import { Notice } from 'obsidian'; +import { t } from './i18n-simple'; + +export class GitHubService { + private octokit: Octokit; + private rateLimitInfo: { remaining: number; resetTime: number } | null = null; + + constructor(token: string) { + this.octokit = new Octokit({ + auth: token, + }); + } + + updateToken(token: string) { + this.octokit = new Octokit({ + auth: token, + }); + // Reset rate limit information + this.rateLimitInfo = null; + } + + // Check rate limit status + async checkRateLimit(): Promise<{ canProceed: boolean; waitMinutes?: number; message?: string }> { + try { + const { data } = await this.octokit.rest.rateLimit.get(); + const core = data.resources.core; + + this.rateLimitInfo = { + remaining: core.remaining, + resetTime: core.reset + }; + + if (core.remaining === 0) { + const resetDate = new Date(core.reset * 1000); + const waitMinutes = Math.ceil((resetDate.getTime() - Date.now()) / (1000 * 60)); + + return { + canProceed: false, + waitMinutes, + message: t('github.api.rate.limit.exceeded', { minutes: waitMinutes }) + }; + } + + // Give warning if remaining requests are low + if (core.remaining < 10) { + return { + canProceed: true, + message: t('github.api.rate.limit.warning', { remaining: core.remaining }) + }; + } + + return { canProceed: true }; + } catch (error) { + // If unable to get rate limit info, allow to continue (might be network issue) + return { canProceed: true }; + } + } + + // Check rate limit before each API call + private async beforeApiCall(): Promise { + const rateCheck = await this.checkRateLimit(); + + if (!rateCheck.canProceed) { + new Notice(rateCheck.message || t('github.api.rate.limit.notice'), 8000); + return false; + } + + if (rateCheck.message && rateCheck.message.includes('⚠️')) { + new Notice(rateCheck.message, 5000); + } + + return true; + } + + private handleGitHubError(error: any): { message: string; isRetryable: boolean } { + // Check if it's a GitHub API error + if (error.status) { + switch (error.status) { + case 401: + return { + message: t('github.api.token.invalid'), + isRetryable: false + }; + + case 403: + // Check if it's rate limit + if (error.response?.headers && error.response.headers['x-ratelimit-remaining'] === '0') { + const resetTime = error.response.headers['x-ratelimit-reset']; + const resetDate = new Date(parseInt(resetTime) * 1000); + const waitMinutes = Math.ceil((resetDate.getTime() - Date.now()) / (1000 * 60)); + + return { + message: t('github.api.rate.limit.hourly', { minutes: waitMinutes }), + isRetryable: true + }; + } else { + return { + message: t('github.api.access.denied'), + isRetryable: false + }; + } + + case 404: + return { + message: t('github.api.resource.not.found'), + isRetryable: false + }; + + case 409: + return { + message: t('github.api.file.conflict.detailed'), + isRetryable: false + }; + + case 422: + return { + message: t('github.api.invalid.request'), + isRetryable: false + }; + + case 500: + case 502: + case 503: + case 504: + return { + message: t('github.api.server.error.detailed'), + isRetryable: true + }; + + default: + return { + message: t('github.api.error.with.status', { status: error.status, message: error.message || 'Unknown error' }), + isRetryable: false + }; + } + } + + // Network error + if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') { + return { + message: t('github.api.network.failed'), + isRetryable: true + }; + } + + // Timeout error + if (error.code === 'ETIMEDOUT') { + return { + message: t('github.api.timeout.detailed'), + isRetryable: true + }; + } + + // Other errors + return { + message: t('github.api.operation.failed', { message: error.message || 'Unknown error' }), + isRetryable: false + }; + } + + private showErrorNotice(errorInfo: { message: string; isRetryable: boolean }) { + const notice = new Notice(errorInfo.message, errorInfo.isRetryable ? 8000 : 5000); + + // If it's a retryable error, add retry hint + if (errorInfo.isRetryable) { + setTimeout(() => { + new Notice(t('github.api.retry.hint'), 3000); + }, 1000); + } + } + + public parseRepositoryUrl(url: string): { owner: string; repo: string; path: string } | null { + console.log('Parsing repository URL:', url); + + if (!url) { + console.error('URL is empty'); + return null; + } + + // Remove leading and trailing spaces + const cleanUrl = url.trim(); + console.log('Cleaned URL:', cleanUrl); + + // Support two formats: + // 1. https://github.com/username/repo/path (standard GitHub URL) + // 2. username/repo/path (short format) + + let match; + + // Try full GitHub URL format + match = cleanUrl.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\/(.*))?$/); + if (match) { + const result = { + owner: match[1], + repo: match[2], + path: match[3] || '', + }; + console.log('Parse result (standard GitHub URL):', result); + return result; + } + + // Try short format username/repo/path + match = cleanUrl.match(/^([^\/]+)\/([^\/]+)(?:\/(.*))?$/); + if (match) { + const result = { + owner: match[1], + repo: match[2], + path: match[3] || '', + }; + console.log('Parse result (short format):', result); + return result; + } + + console.error('Unable to parse URL format:', cleanUrl); + console.error('Supported formats:'); + console.error('1. https://github.com/username/repo/path (standard format)'); + console.error('2. username/repo/path (short format)'); + + return null; + } + + async uploadFile(repositoryUrl: string, filePath: string, content: string): Promise { + 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 { + const fullPath = repoInfo.path ? `${repoInfo.path}/${filePath}` : filePath; + + // First try to get the current SHA of the file (if it exists) + let sha: string | undefined; + try { + const { data } = await this.octokit.rest.repos.getContent({ + owner: repoInfo.owner, + repo: repoInfo.repo, + path: fullPath, + }); + + if ('sha' in data) { + sha = data.sha; + } + } catch (error) { + // File doesn't exist, this is normal + } + + // Upload or update file + await this.octokit.rest.repos.createOrUpdateFileContents({ + owner: repoInfo.owner, + repo: repoInfo.repo, + path: fullPath, + message: `Update ${filePath}`, + content: Buffer.from(content, 'utf8').toString('base64'), + sha: sha, + }); + + return { success: true, message: t('github.api.file.upload.success') }; + } catch (error) { + console.error('File upload failed:', error); + const errorInfo = this.handleGitHubError(error); + this.showErrorNotice(errorInfo); + return { success: false, message: errorInfo.message }; + } + } + + async downloadFile(repositoryUrl: string, filePath: string): Promise { + 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 { + const fullPath = repoInfo.path ? `${repoInfo.path}/${filePath}` : filePath; + + const { data } = await this.octokit.rest.repos.getContent({ + owner: repoInfo.owner, + repo: repoInfo.repo, + path: fullPath, + }); + + if ('content' in data) { + const content = Buffer.from(data.content, 'base64').toString('utf8'); + return { success: true, message: '文件下载成功', content }; + } else { + return { success: false, message: '指定路径不是文件' }; + } + } catch (error) { + console.error('File download failed:', error); + const errorInfo = this.handleGitHubError(error); + this.showErrorNotice(errorInfo); + return { success: false, message: errorInfo.message }; + } + } + + async downloadAllFiles(repositoryUrl: string): Promise }> { + 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 { + const files = await this.getAllFilesRecursively(repoInfo.owner, repoInfo.repo, repoInfo.path); + const downloadedFiles: Array<{ path: string; content: string }> = []; + + for (const file of files) { + if (file.type === 'file') { + try { + const { data } = await this.octokit.rest.repos.getContent({ + owner: repoInfo.owner, + repo: repoInfo.repo, + path: file.path, + }); + + if ('content' in data) { + const content = Buffer.from(data.content, 'base64').toString('utf8'); + // Remove repository path prefix, keep only relative path + const relativePath = repoInfo.path ? file.path.replace(`${repoInfo.path}/`, '') : file.path; + downloadedFiles.push({ path: relativePath, content }); + } + } catch (error) { + console.warn(`跳过文件 ${file.path}:`, error); + } + } + } + + return { success: true, message: t('github.api.all.files.download.success'), files: downloadedFiles }; + } catch (error) { + console.error('Download all files failed:', error); + const errorInfo = this.handleGitHubError(error); + this.showErrorNotice(errorInfo); + return { success: false, message: errorInfo.message }; + } + } + + private async getAllFilesRecursively(owner: string, repo: string, path: string): Promise { + const allFiles: GitHubFile[] = []; + + try { + const { data } = await this.octokit.rest.repos.getContent({ + owner, + repo, + path, + }); + + if (Array.isArray(data)) { + for (const item of data) { + if (item.type === 'file') { + allFiles.push(item as GitHubFile); + } else if (item.type === 'dir') { + const subFiles = await this.getAllFilesRecursively(owner, repo, item.path); + allFiles.push(...subFiles); + } + } + } + } catch (error) { + console.error(`Failed to get directory content ${path}:`, error); + } + + return allFiles; + } + + async getFileLastModified(repositoryUrl: string, filePath: string): Promise<{ success: boolean; lastModified?: string; exists: boolean }> { + const repoInfo = this.parseRepositoryUrl(repositoryUrl); + if (!repoInfo) { + return { success: false, exists: false }; + } + + // For status bar queries, we do lightweight rate limit checking + if (this.rateLimitInfo && this.rateLimitInfo.remaining === 0) { + return { success: false, exists: false }; + } + + try { + const fullPath = repoInfo.path ? `${repoInfo.path}/${filePath}` : filePath; + + // Get file's commit history, only get the latest one + const { data } = await this.octokit.rest.repos.listCommits({ + owner: repoInfo.owner, + repo: repoInfo.repo, + path: fullPath, + per_page: 1, + }); + + if (data.length > 0) { + const lastCommit = data[0]; + return { + success: true, + exists: true, + lastModified: lastCommit.commit.committer?.date || lastCommit.commit.author?.date + }; + } else { + return { success: true, exists: false }; + } + } catch (error) { + console.error('Failed to get file last modified time:', error); + // For status bar queries, we don't show error notifications, only log in console + const errorInfo = this.handleGitHubError(error); + return { success: false, exists: false }; + } + } +} \ No newline at end of file diff --git a/i18n-simple.ts b/i18n-simple.ts new file mode 100644 index 0000000..6f95f14 --- /dev/null +++ b/i18n-simple.ts @@ -0,0 +1,366 @@ +// Simplified internationalization system +export type Language = 'zh' | 'en' | 'auto'; + +// Translation texts +const translations = { + zh: { + // Settings interface + 'settings.title': 'Git同步设置', + 'settings.language.name': '界面语言', + 'settings.language.desc': '选择插件界面显示语言', + 'settings.language.auto': '跟随Obsidian', + 'settings.github.token.name': 'GitHub Personal Token', + 'settings.github.token.desc': '输入你的GitHub Personal Access Token', + 'settings.github.token.placeholder': 'ghp_xxxxxxxxxxxx', + 'settings.github.repo.name': 'GitHub仓库路径', + 'settings.github.repo.desc': '支持两种格式:\n1. https://github.com/用户名/仓库名/路径\n2. 用户名/仓库名/路径', + 'settings.github.repo.placeholder': 'https://github.com/Xheldon/git-sync/data/_post', + 'settings.ribbon.name': '将插件按钮显示在侧边栏', + 'settings.ribbon.desc': '开启后,在左侧边栏显示插件按钮,点击可快速打开设置界面', + 'settings.save.name': '保存设置', + 'settings.save.desc': '保存当前的配置设置', + 'settings.save.button': '保存设置', + 'settings.diagnose.name': '诊断Vault文件', + 'settings.diagnose.desc': '检查当前Vault中有哪些文件可以同步', + 'settings.diagnose.button': '诊断', + 'settings.test.url.name': '测试仓库URL', + 'settings.test.url.desc': '验证GitHub仓库路径格式是否正确', + 'settings.test.url.button': '测试URL', + 'settings.sponsor.name': '💖 支持开发者', + 'settings.sponsor.desc': '如果这个插件对你有帮助,欢迎请我喝杯咖啡!你的支持是我继续开发的动力。', + 'settings.sponsor.button': '💝 PayPal 赞助', + 'settings.sponsor.china.label': '🇨🇳 中国大陆捐赠渠道', + 'settings.sponsor.section.title': '赞助', + 'settings.danger.zone.title': '危险区域', + 'settings.init.name': '初始化仓库', + 'settings.init.desc': '当本Vault为空的时候可用,将远端文件同步到本地', + 'settings.init.button': '初始化仓库', + 'settings.init.loading': '初始化中...', + 'settings.sync.remote.to.local.name': '强制同步远端路径到本地', + 'settings.sync.remote.to.local.desc': '将远端的文件一股脑的同步到本地的Vault中,同名文件会被覆盖', + 'settings.sync.remote.to.local.button': '强制同步到本地', + 'settings.sync.remote.to.local.loading': '同步中...', + 'settings.sync.local.to.remote.name': '强制同步本地文件到远端', + 'settings.sync.local.to.remote.desc': '将本地的Vault中的所有文件同步到远端配置的路径中,同名文件会被覆盖', + 'settings.sync.local.to.remote.button': '强制同步到远端', + 'settings.sync.local.to.remote.loading': '同步中...', + 'settings.clear.cache.name': '清空文件缓存', + 'settings.clear.cache.desc': '清空所有文件状态缓存,下次查看时将重新从GitHub获取', + 'settings.clear.cache.button': '清空缓存', + + // Notification messages + 'notice.settings.saved': '设置已保存', + 'notice.config.required': '请先配置GitHub Token和仓库地址', + 'notice.url.required': '请先输入GitHub仓库路径', + 'notice.url.test.success': 'URL格式正确!', + 'notice.url.test.failed': 'URL格式不正确,请检查格式。详情请查看控制台。', + 'notice.init.error': '初始化仓库时发生错误', + 'notice.sync.error': '强制同步时发生错误', + 'notice.diagnose.result': 'Vault中共有 {total} 个文件,其中 {syncable} 个可同步。详情请查看控制台。', + 'notice.result.with.count': '{message},处理了 {count} 个文件', + 'notice.cache.cleared': '已清空 {count} 个文件的缓存', + 'notice.cache.clear.error': '清空缓存时发生错误', + + // Sync operations + 'sync.success': '同步成功: {filename}', + 'sync.failed': '同步失败: {message}', + 'sync.error': '同步时发生错误', + 'pull.success': '拉取成功: {filename}', + 'pull.failed': '拉取失败: {message}', + 'pull.error': '拉取时发生错误', + + // Repository operations + 'vault.not.empty': 'Vault不为空,无法初始化', + 'repo.init.success': '仓库初始化成功', + 'repo.init.failed': '仓库初始化失败: {error}', + 'force.sync.remote.to.local.success': '强制同步远端到本地成功', + 'force.sync.remote.to.local.failed': '强制同步远端到本地失败: {error}', + 'no.syncable.files.in.vault': 'Vault中没有可同步的文件', + 'force.sync.local.to.remote.success.no.failures': '强制同步本地到远端成功', + 'force.sync.local.to.remote.success': '强制同步本地到远端完成,成功: {processed},失败: {failed}', + 'force.sync.local.to.remote.failed': '强制同步本地到远端失败: {error}', + + // Actions + 'actions.sync.current.to.remote': '同步当前文件到远端', + 'actions.pull.remote.to.current': '从远端拉取到当前文件', + + // Date + 'date.today': '今天', + 'date.yesterday': '昨天', + + // GitHub API Error + 'github.api.rate.limit.exceeded': 'GitHub API调用次数已达上限。请等待 {minutes} 分钟后重试。', + 'github.api.rate.limit.warning': '⚠️ GitHub API调用次数剩余 {remaining} 次,请谨慎使用。', + 'github.api.rate.limit.notice': '速率限制超出', + 'github.api.token.invalid': 'GitHub Token无效或已过期。请检查:\n1. Token是否正确\n2. Token是否有足够的权限\n3. Token是否已过期', + 'github.api.rate.limit.hourly': 'GitHub API调用次数已达上限(每小时5000次)。\n请等待 {minutes} 分钟后重试,或使用GitHub Pro账号获得更高限额。', + 'github.api.repo.not.found': '仓库不存在或无访问权限。请检查:\n1. 仓库地址是否正确\n2. Token是否有访问该仓库的权限\n3. 仓库是否为私有仓库', + 'github.api.file.conflict': '文件冲突。远程文件已被其他人修改。\n请先同步远程更改,然后重试。', + 'github.api.validation.failed': 'GitHub API验证失败。请检查:\n1. 文件路径是否正确\n2. 文件内容是否符合要求\n3. 仓库权限设置', + 'github.api.server.error': 'GitHub服务器错误({status})。请稍后重试。', + 'github.api.network.error': '网络连接错误。请检查:\n1. 网络连接是否正常\n2. 防火墙设置\n3. 代理配置', + 'github.api.timeout.error': '请求超时。请检查网络连接或稍后重试。', + 'github.api.unknown.error': '未知错误:{message}', + 'github.api.access.denied': 'GitHub访问被拒绝。可能原因:\n1. Token权限不足\n2. 仓库为私有且Token无访问权限\n3. 仓库不存在', + 'github.api.resource.not.found': '资源未找到。请检查:\n1. 仓库路径是否正确\n2. 文件路径是否存在\n3. Token是否有访问该仓库的权限', + 'github.api.file.conflict.detailed': '文件冲突。可能原因:\n1. 文件在远程已被修改\n2. 请先从远端同步最新版本', + 'github.api.invalid.request': '请求参数无效。请检查:\n1. 文件内容是否过大(>100MB)\n2. 文件路径格式是否正确', + 'github.api.server.error.detailed': 'GitHub服务器错误,请稍后重试。如果问题持续存在,请检查GitHub服务状态。', + 'github.api.error.with.status': 'GitHub API错误 ({status}): {message}', + 'github.api.network.failed': '网络连接失败。请检查:\n1. 网络连接是否正常\n2. 是否能访问github.com\n3. 防火墙或代理设置', + 'github.api.timeout.detailed': '请求超时。请检查网络连接或稍后重试。', + 'github.api.operation.failed': '操作失败: {message}', + 'github.api.retry.hint': '💡 提示:这是临时性错误,稍后可以重试', + 'github.api.invalid.url.format': '无效的仓库URL格式', + 'github.api.rate.limit.exceeded.short': 'GitHub API调用次数已达上限,请稍后重试', + 'github.api.file.upload.success': '文件上传成功', + 'github.api.all.files.download.success': '所有文件下载成功', + + // Status bar + 'status.bar.checking': '检查中...', + 'status.bar.sync': '同步', + 'status.bar.rate.limit': 'API限制: 等待{minutes}分钟', + 'status.bar.last.modified': '上次更新: {date}', + 'status.bar.not.published': '未发布', + 'status.bar.check.failed': '检查失败', + 'status.bar.not.configured': '未配置', + 'status.bar.local.modified': '本地已修改', + 'status.bar.synced': '已同步', + + // Plugin info + 'plugin.name': 'Git同步', + 'command.show.sync.menu': '显示同步菜单', + + // Test URL results + 'test.url.user': '用户', + 'test.url.repo': '仓库', + 'test.url.path': '路径', + 'test.url.root': '根目录', + + // Path info + 'path.info.empty': '请输入GitHub仓库路径以查看同步目标', + 'path.info.sync.to': '当前 Vault 内容将同步到 {owner}/{repo} 仓库', + 'path.info.folder': ' 的 {path} 文件夹下', + 'path.info.root': ' 的根目录下', + 'path.info.error': '路径格式不正确,请使用: https://github.com/用户名/仓库名/路径 或 用户名/仓库名/路径', + }, + en: { + // Settings interface + 'settings.title': 'Git Sync Settings', + 'settings.language.name': 'Interface Language', + 'settings.language.desc': 'Select the display language for the plugin interface', + 'settings.language.auto': 'Follow Obsidian', + 'settings.github.token.name': 'GitHub Personal Token', + 'settings.github.token.desc': 'Enter your GitHub Personal Access Token', + 'settings.github.token.placeholder': 'ghp_xxxxxxxxxxxx', + 'settings.github.repo.name': 'GitHub Repository Path', + 'settings.github.repo.desc': 'Supports two formats:\n1. https://github.com/username/repo/path\n2. username/repo/path', + 'settings.github.repo.placeholder': 'https://github.com/Xheldon/git-sync/data/_post', + 'settings.ribbon.name': 'Show plugin button in sidebar', + 'settings.ribbon.desc': 'When enabled, display plugin button in left sidebar for quick access to settings', + 'settings.save.name': 'Save Settings', + 'settings.save.desc': 'Save current configuration settings', + 'settings.save.button': 'Save Settings', + 'settings.diagnose.name': 'Diagnose Vault Files', + 'settings.diagnose.desc': 'Check which files in current Vault can be synchronized', + 'settings.diagnose.button': 'Diagnose', + '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.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', + 'settings.sponsor.section.title': 'Sponsor', + 'settings.danger.zone.title': 'Danger Zone', + 'settings.init.name': 'Initialize Repository', + 'settings.init.desc': 'Available when Vault is empty, sync remote files to local', + 'settings.init.button': 'Initialize Repository', + 'settings.init.loading': 'Initializing...', + 'settings.sync.remote.to.local.name': 'Force Sync Remote to Local', + 'settings.sync.remote.to.local.desc': 'Sync all remote files to local Vault, existing files will be overwritten', + 'settings.sync.remote.to.local.button': 'Force Sync to Local', + 'settings.sync.remote.to.local.loading': 'Syncing...', + 'settings.sync.local.to.remote.name': 'Force Sync Local to Remote', + 'settings.sync.local.to.remote.desc': 'Sync all local Vault files to remote repository, existing files will be overwritten', + 'settings.sync.local.to.remote.button': 'Force Sync to Remote', + 'settings.sync.local.to.remote.loading': 'Syncing...', + 'settings.clear.cache.name': 'Clear File Cache', + 'settings.clear.cache.desc': 'Clear all file status cache, will fetch from GitHub on next view', + 'settings.clear.cache.button': 'Clear Cache', + + // Notification messages + '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.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', + 'notice.sync.error': 'Error occurred during force sync', + 'notice.diagnose.result': 'Vault has {total} files in total, {syncable} can be synced. See console for details.', + 'notice.result.with.count': '{message}, processed {count} files', + 'notice.cache.cleared': 'Cleared cache for {count} files', + 'notice.cache.clear.error': 'Error occurred while clearing cache', + + // Sync operations + 'sync.success': 'Sync success: {filename}', + 'sync.failed': 'Sync failed: {message}', + 'sync.error': 'Error occurred during sync', + 'pull.success': 'Pull success: {filename}', + 'pull.failed': 'Pull failed: {message}', + 'pull.error': 'Error occurred during pull', + + // Repository operations + 'vault.not.empty': 'Vault is not empty, cannot initialize', + 'repo.init.success': 'Repository initialized successfully', + 'repo.init.failed': 'Repository initialization failed: {error}', + 'force.sync.remote.to.local.success': 'Force sync remote to local success', + 'force.sync.remote.to.local.failed': 'Force sync remote to local failed: {error}', + 'no.syncable.files.in.vault': 'No syncable files in vault', + 'force.sync.local.to.remote.success.no.failures': 'Force sync local to remote success', + 'force.sync.local.to.remote.success': 'Force sync local to remote completed, success: {processed}, failed: {failed}', + 'force.sync.local.to.remote.failed': 'Force sync local to remote failed: {error}', + + // Actions + 'actions.sync.current.to.remote': 'Sync current file to remote', + 'actions.pull.remote.to.current': 'Pull remote to current file', + + // Date + 'date.today': 'Today', + 'date.yesterday': 'Yesterday', + + // GitHub API errors + 'github.api.rate.limit.exceeded': 'GitHub API rate limit exceeded. Please wait {minutes} minutes before retrying.', + 'github.api.rate.limit.warning': '⚠️ GitHub API calls remaining: {remaining}. Please use carefully.', + 'github.api.rate.limit.notice': 'Rate limit exceeded', + 'github.api.token.invalid': 'GitHub Token is invalid or expired. Please check:\n1. Token is correct\n2. Token has sufficient permissions\n3. Token has not expired', + 'github.api.rate.limit.hourly': 'GitHub API hourly rate limit exceeded (5000 per hour).\nPlease wait {minutes} minutes or use GitHub Pro for higher limits.', + 'github.api.repo.not.found': 'Repository not found or no access permission. Please check:\n1. Repository URL is correct\n2. Token has access to this repository\n3. Repository is not private', + 'github.api.file.conflict': 'File conflict. Remote file has been modified by others.\nPlease sync remote changes first, then retry.', + 'github.api.validation.failed': 'GitHub API validation failed. Please check:\n1. File path is correct\n2. File content meets requirements\n3. Repository permission settings', + 'github.api.server.error': 'GitHub server error ({status}). Please retry later.', + 'github.api.network.error': 'Network connection error. Please check:\n1. Network connection is normal\n2. Firewall settings\n3. Proxy configuration', + 'github.api.timeout.error': 'Request timeout. Please check network connection or retry later.', + 'github.api.unknown.error': 'Unknown error: {message}', + 'github.api.access.denied': 'GitHub access denied. Possible reasons:\n1. Insufficient token permissions\n2. Repository is private and token has no access\n3. Repository does not exist', + 'github.api.resource.not.found': 'Resource not found. Please check:\n1. Repository path is correct\n2. File path exists\n3. Token has access to this repository', + 'github.api.file.conflict.detailed': 'File conflict. Possible reasons:\n1. File has been modified remotely\n2. Please sync from remote first', + 'github.api.invalid.request': 'Invalid request parameters. Please check:\n1. File content is not too large (>100MB)\n2. File path format is correct', + 'github.api.server.error.detailed': 'GitHub server error, please retry later. If problem persists, check GitHub service status.', + 'github.api.error.with.status': 'GitHub API error ({status}): {message}', + 'github.api.network.failed': 'Network connection failed. Please check:\n1. Network connection is normal\n2. Can access github.com\n3. Firewall or proxy settings', + 'github.api.timeout.detailed': 'Request timeout. Please check network connection or retry later.', + 'github.api.operation.failed': 'Operation failed: {message}', + 'github.api.retry.hint': '💡 Tip: This is a temporary error, you can retry later', + 'github.api.invalid.url.format': 'Invalid repository URL format', + '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', + + // Status bar + 'status.bar.checking': 'Checking...', + 'status.bar.sync': 'Sync', + 'status.bar.rate.limit': 'API limit: wait {minutes} minutes', + 'status.bar.last.modified': 'Last modified: {date}', + 'status.bar.not.published': 'Not published', + 'status.bar.check.failed': 'Check failed', + 'status.bar.not.configured': 'Not configured', + 'status.bar.local.modified': 'Local modified', + 'status.bar.synced': 'Synced', + + // Plugin info + 'plugin.name': 'Git Sync', + 'command.show.sync.menu': 'Show Sync Menu', + + // Test URL results + 'test.url.user': 'User', + 'test.url.repo': 'Repo', + 'test.url.path': 'Path', + 'test.url.root': 'Root', + + // Path info + 'path.info.empty': 'Enter GitHub repository path to view sync target', + 'path.info.sync.to': 'Current Vault content will sync to {owner}/{repo} repository', + 'path.info.folder': ' under {path} folder', + 'path.info.root': ' under root directory', + 'path.info.error': 'Path format is incorrect, please use: https://github.com/username/repo/path or username/repo/path', + } +}; + +// Current language +let currentLanguage: Language = 'auto'; + +// 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(); + + // Check browser language + const browserLang = navigator.language || navigator.languages?.[0] || 'en'; + + // If it's Chinese-related language code, return Chinese + if (obsidianLang?.includes('zh') || browserLang.startsWith('zh')) { + return 'zh'; + } + + // Default return English + return 'en'; +} + +// Get the actual language used +export function getActualLanguage(): 'zh' | 'en' { + if (currentLanguage === 'auto') { + return detectObsidianLanguage(); + } + return currentLanguage as 'zh' | 'en'; +} + +// Set current language +export function setLanguage(language: Language): void { + currentLanguage = language; +} + +// Get current language +export function getCurrentLanguage(): Language { + return currentLanguage; +} + +// Translation function +export function t(key: string, params?: Record): string { + const actualLang = getActualLanguage(); + const translation = translations[actualLang][key as keyof typeof translations['zh']]; + + if (!translation) { + console.warn(`Translation missing for key: ${key}`); + return key; + } + + // If there are parameters, perform replacement + if (params) { + return Object.entries(params).reduce((text, [paramKey, paramValue]) => { + return text.replace(new RegExp(`\\{${paramKey}\\}`, 'g'), String(paramValue)); + }, translation); + } + + return translation; +} + +// Get all supported languages +export function getSupportedLanguages(): Array<{ value: Language; label: string }> { + // Detect system language for displaying content in parentheses + const detectedLang = detectObsidianLanguage(); + const detectedLangText = detectedLang === 'zh' ? '中文' : 'English'; + + // Determine the display text for "Follow" based on current plugin language + const currentLang = getActualLanguage(); + const followText = currentLang === 'zh' ? '跟随Obsidian' : 'Follow Obsidian'; + + const autoLabel = `${followText} (${detectedLangText})`; + + return [ + { value: 'auto', label: autoLabel }, + { value: 'zh', label: '中文' }, + { value: 'en', label: 'English' }, + ]; +} \ No newline at end of file diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..4fdf679 --- /dev/null +++ b/main.ts @@ -0,0 +1,953 @@ +import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, Menu, TFolder } from 'obsidian'; +import { GitSyncSettings, DEFAULT_SETTINGS, SyncResult } from './types'; +import { GitHubService } from './github-service'; +import { setLanguage, t, getSupportedLanguages, getActualLanguage } from './i18n-simple'; +import { FileCacheService } from './file-cache'; + +export default class GitSyncPlugin extends Plugin { + settings: GitSyncSettings; + githubService: GitHubService; + fileCacheService: FileCacheService; + ribbonIconEl: HTMLElement | null = null; + statusBarEl: HTMLElement | null = null; + currentFile: TFile | null = null; + private fileModifyTimeout: NodeJS.Timeout | null = null; + + async onload() { + await this.loadSettings(); + + // Initialize language settings + setLanguage(this.settings.language); + + this.githubService = new GitHubService(this.settings.githubToken); + this.fileCacheService = new FileCacheService(); + + // Add ribbon icon based on settings + this.updateRibbonIcon(); + + // Add status bar + this.statusBarEl = this.addStatusBarItem(); + this.statusBarEl.addClass('git-sync-status-bar'); + + // Listen for file switching events + this.registerEvent( + this.app.workspace.on('active-leaf-change', () => { + this.updateStatusBar(); + }) + ); + + // Listen for file open events + this.registerEvent( + this.app.workspace.on('file-open', (file) => { + this.currentFile = file; + this.updateStatusBar(); + }) + ); + + // Listen for file content modification events + this.registerEvent( + 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}`); + // Type conversion to TFile + if (file instanceof TFile) { + this.onFileContentModified(file); + } + } + }) + ); + + // Add sync button to note editing interface + this.addCommand({ + id: 'show-sync-menu', + name: t('command.show.sync.menu'), + editorCallback: (editor: Editor, view: MarkdownView) => { + this.showSyncMenu(editor, view); + } + }); + + // Add editor right-click menu + this.registerEvent( + this.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownView) => { + menu.addItem((item) => { + item + .setTitle(t('plugin.name')) + .setIcon('sync') + .onClick(async () => { + this.showSyncMenu(editor, view); + }); + }); + }) + ); + + // Add settings page + this.addSettingTab(new GitSyncSettingTab(this.app, this)); + + // Initial status bar update + this.updateStatusBar(); + } + + onunload() { + // Clean up timers + if (this.fileModifyTimeout) { + clearTimeout(this.fileModifyTimeout); + this.fileModifyTimeout = null; + } + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + this.githubService.updateToken(this.settings.githubToken); + // Update language settings + setLanguage(this.settings.language); + // Update ribbon icon display state + this.updateRibbonIcon(); + } + + private updateRibbonIcon() { + // Remove existing button + if (this.ribbonIconEl) { + this.ribbonIconEl.remove(); + this.ribbonIconEl = null; + } + + // Add button based on settings + if (this.settings.showRibbonIcon) { + 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); + }); + } + } + + private showSyncMenu(editor: Editor, view: MarkdownView) { + const menu = new Menu(); + + menu.addItem((item) => { + item + .setTitle(t('actions.sync.current.to.remote')) + .setIcon('upload') + .onClick(async () => { + await this.syncCurrentFileToRemote(view.file); + }); + }); + + menu.addItem((item) => { + item + .setTitle(t('actions.pull.remote.to.current')) + .setIcon('download') + .onClick(async () => { + await this.pullRemoteToCurrentFile(view.file); + }); + }); + + const rect = (editor as any).containerEl?.getBoundingClientRect(); + if (rect) { + menu.showAtPosition({ x: rect.right - 100, y: rect.top + 50 }); + } else { + // If unable to get editor position, show at mouse position + menu.showAtMouseEvent(new MouseEvent('click')); + } + } + + async syncCurrentFileToRemote(file: TFile): Promise { + if (!this.settings.githubToken || !this.settings.repositoryUrl) { + new Notice(t('settings.github.token.or.repo.not.configured')); + return; + } + + try { + const content = await this.app.vault.read(file); + const result = await this.githubService.uploadFile( + this.settings.repositoryUrl, + file.path, + content + ); + + if (result.success) { + new Notice(t('sync.success', { filename: file.name })); + + // Update cache - file synced to remote + await this.fileCacheService.updateFileCache( + file.path, + file.path, + new Date().toISOString(), // Use current time as last modified time + '', // SHA value can be obtained from upload result later + true, // Published + this.app.vault + ); + } else { + new Notice(t('sync.failed', { message: result.message })); + } + } catch (error) { + console.error('File sync failed:', error); + new Notice(t('sync.error')); + } + } + + async pullRemoteToCurrentFile(file: TFile): Promise { + if (!this.settings.githubToken || !this.settings.repositoryUrl) { + new Notice(t('settings.github.token.or.repo.not.configured')); + return; + } + + try { + const result = await this.githubService.downloadFile( + this.settings.repositoryUrl, + file.path + ); + + if (result.success && result.content) { + await this.app.vault.modify(file, result.content); + new Notice(t('pull.success', { filename: file.name })); + + // Update cache - file synced from remote + await this.fileCacheService.updateFileCache( + file.path, + file.path, + new Date().toISOString(), // Use current time as last modified time + '', // SHA value can be obtained from download result later + true, // Published + this.app.vault + ); + } else { + new Notice(t('pull.failed', { message: result.message })); + } + } catch (error) { + console.error('File pull failed:', error); + new Notice(t('pull.error')); + } + } + + async initializeRepository(): Promise { + if (!this.isVaultEmpty()) { + return { success: false, message: t('vault.not.empty') }; + } + + try { + const result = await this.githubService.downloadAllFiles(this.settings.repositoryUrl); + if (result.success && result.files) { + for (const file of result.files) { + await this.createFileInVault(file.path, file.content || ''); + + // Update cache - file initialized from remote + await this.fileCacheService.updateFileCache( + file.path, + file.path, + new Date().toISOString(), // Use current time as last modified time + '', // SHA value can be obtained from download result later + true, // Published + this.app.vault + ); + } + + // Refresh status bar display + setTimeout(() => this.updateStatusBar(), 2000); + + return { success: true, message: t('repo.init.success'), filesProcessed: result.files.length }; + } + return { success: false, message: result.message }; + } catch (error) { + return { success: false, message: t('repo.init.failed', { error: error.message }) }; + } + } + + async forceSyncRemoteToLocal(): Promise { + try { + const result = await this.githubService.downloadAllFiles(this.settings.repositoryUrl); + if (result.success && result.files) { + for (const file of result.files) { + await this.createFileInVault(file.path, file.content || ''); + + // Update cache - file synced from remote + await this.fileCacheService.updateFileCache( + file.path, + file.path, + new Date().toISOString(), // Use current time as last modified time + '', // SHA value can be obtained from download result later + true, // Published + this.app.vault + ); + } + + // Refresh status bar display + setTimeout(() => this.updateStatusBar(), 2000); + + return { success: true, message: t('force.sync.remote.to.local.success'), filesProcessed: result.files.length }; + } + return { success: false, message: result.message }; + } catch (error) { + return { success: false, message: t('force.sync.remote.to.local.failed', { error: error.message }) }; + } + } + + async forceSyncLocalToRemote(): Promise { + try { + const files = this.getAllVaultFiles(); + console.log('Found files:', files.map(f => f.path)); + + if (files.length === 0) { + return { success: false, message: t('no.syncable.files.in.vault') }; + } + + let processed = 0; + let failed = 0; + + for (const file of files) { + try { + const content = await this.app.vault.read(file); + const result = await this.githubService.uploadFile( + this.settings.repositoryUrl, + file.path, + content + ); + + if (result.success) { + processed++; + console.log(`Successfully synced file: ${file.path}`); + + // Update cache immediately - file synced to remote + await this.fileCacheService.updateFileCache( + file.path, + file.path, + new Date().toISOString(), // Use current time as last modified time + '', // SHA value can be obtained from upload result later + true, // Published + this.app.vault + ); + } else { + failed++; + console.error(`File sync failed: ${file.path}`, result.message); + } + } catch (error) { + failed++; + console.error(`Failed to read or sync file: ${file.path}`, error); + } + } + + const message = failed > 0 + ? t('force.sync.local.to.remote.success', { processed, failed }) + : t('force.sync.local.to.remote.success.no.failures'); + + // Refresh status bar display + setTimeout(() => this.updateStatusBar(), 2000); + + return { success: true, message, filesProcessed: processed }; + } catch (error) { + console.error('Force sync failed:', error); + return { success: false, message: t('force.sync.local.to.remote.failed', { error: error.message }) }; + } + } + + private isVaultEmpty(): boolean { + const files = this.app.vault.getFiles(); + return files.filter(file => !file.path.startsWith('.obsidian')).length === 0; + } + + private getAllVaultFiles(): TFile[] { + const allFiles = this.app.vault.getFiles(); + console.log('All files in vault:', allFiles.map(f => f.path)); + + // Filter out files in .obsidian folder + const filteredFiles = allFiles.filter(file => !file.path.startsWith('.obsidian')); + console.log('Filtered files:', filteredFiles.map(f => f.path)); + + return filteredFiles; + } + + private async createFileInVault(path: string, content: string): Promise { + const folderPath = path.substring(0, path.lastIndexOf('/')); + if (folderPath && !this.app.vault.getAbstractFileByPath(folderPath)) { + await this.app.vault.createFolder(folderPath); + } + + const existingFile = this.app.vault.getAbstractFileByPath(path); + if (existingFile instanceof TFile) { + await this.app.vault.modify(existingFile, content); + } else { + await this.app.vault.create(path, content); + } + } + + private async updateStatusBar() { + if (!this.statusBarEl) return; + + const activeFile = this.app.workspace.getActiveFile(); + if (!activeFile || !activeFile.path.endsWith('.md')) { + this.statusBarEl.empty(); + this.statusBarEl.style.display = 'none'; + return; + } + + this.currentFile = activeFile; + this.statusBarEl.style.display = 'flex'; + this.statusBarEl.empty(); + + // Create status text + const statusText = this.statusBarEl.createEl('span', { + cls: 'git-sync-status-text', + text: t('status.bar.checking') + }); + + // Create sync button + const syncButton = this.statusBarEl.createEl('button', { + cls: 'git-sync-status-button', + text: t('status.bar.sync') + }); + + // Check file remote status + if (this.settings.githubToken && this.settings.repositoryUrl) { + try { + // First check cache + const cache = this.fileCacheService.getFileCache(activeFile.path); + + if (cache) { + // Use cached data + console.log(`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)'; + } + + statusText.textContent = statusMsg; + } else { + statusText.textContent = t('status.bar.not.published'); + statusText.style.color = 'var(--text-muted)'; + } + + // Asynchronously update cache if it's about to expire + if (!this.fileCacheService.isCacheValid(cache, 4 * 60 * 1000)) { // Start async refresh after 4 minutes + this.refreshFileCache(activeFile.path).catch(console.error); + } + } else { + // No cache, need to fetch from GitHub + console.log(`No cache, fetching file status from GitHub: ${activeFile.path}`); + await this.fetchAndCacheFileStatus(activeFile.path, statusText); + } + } catch (error) { + console.error('Failed to update status bar:', error); + statusText.textContent = t('status.bar.check.failed'); + } + } else { + statusText.textContent = t('status.bar.not.configured'); + } + + // Add button click event + syncButton.addEventListener('click', (e) => { + e.stopPropagation(); + this.showSyncMenuForStatusBar(activeFile); + }); + } + + /** + * Handle file content modification events (with debounce) + */ + private async onFileContentModified(file: TFile) { + // Clear previous timer + if (this.fileModifyTimeout) { + clearTimeout(this.fileModifyTimeout); + } + + // Set debounce, execute after 2000ms + this.fileModifyTimeout = setTimeout(async () => { + try { + // Get current file cache + const cache = this.fileCacheService.getFileCache(file.path); + if (!cache) { + // No cache, update status bar directly + this.updateStatusBar(); + return; + } + + // Check if file was actually modified + const content = await this.app.vault.read(file); + const currentHash = FileCacheService.calculateContentHash(content); + + if (currentHash !== cache.contentHash) { + console.log(`File content modified: ${file.path}`); + + // Update content hash and sync status in cache + const updatedCache = { + ...cache, + contentHash: currentHash, + isSynced: false, // Mark as not synced + cacheTime: Date.now() + }; + + this.fileCacheService.setFileCache(file.path, updatedCache); + + // Update status bar display immediately + this.updateStatusBar(); + } + } catch (error) { + console.error('Failed to handle file modification event:', error); + } + }, 2000); + } + + /** + * Fetch file status from GitHub and cache it + */ + private async fetchAndCacheFileStatus(filePath: string, statusText: HTMLElement) { + try { + // Check rate limit status first + const rateLimitCheck = await this.githubService.checkRateLimit(); + + if (!rateLimitCheck.canProceed) { + statusText.textContent = t('status.bar.rate.limit', { minutes: rateLimitCheck.waitMinutes }); + return; + } + + const result = await this.githubService.getFileLastModified( + this.settings.repositoryUrl, + filePath + ); + + if (result.success) { + 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)'; + + // Update cache + await this.fileCacheService.updateFileCache( + filePath, + filePath, // GitHub path same as local path + result.lastModified, + '', // SHA value temporarily empty, can be obtained from other APIs later + true, + this.app.vault + ); + } else { + statusText.textContent = t('status.bar.not.published'); + statusText.style.color = 'var(--text-muted)'; + + // Cache unpublished status + await this.fileCacheService.updateFileCache( + filePath, + filePath, + '', + '', + false, + this.app.vault + ); + } + } else { + statusText.textContent = t('status.bar.check.failed'); + } + } catch (error) { + console.error('Failed to fetch file status:', error); + statusText.textContent = t('status.bar.check.failed'); + } + } + + /** + * Asynchronously refresh file cache + */ + private async refreshFileCache(filePath: string) { + try { + console.log(`Asynchronously refreshing file cache: ${filePath}`); + + // Lightweight rate limit check - temporarily skip, let GitHub API handle rate limits itself + + const result = await this.githubService.getFileLastModified( + this.settings.repositoryUrl, + filePath + ); + + if (result.success) { + await this.fileCacheService.updateFileCache( + filePath, + filePath, + result.lastModified || '', + '', // SHA value temporarily empty + result.exists || false, + this.app.vault + ); + console.log(`File cache refreshed: ${filePath}`); + } + } catch (error) { + console.error('Failed to refresh file cache:', error); + } + } + + private showSyncMenuForStatusBar(file: TFile) { + const menu = new Menu(); + + menu.addItem((item) => { + item + .setTitle(t('actions.sync.current.to.remote')) + .setIcon('upload') + .onClick(async () => { + await this.syncCurrentFileToRemote(file); + // Update status bar + setTimeout(() => this.updateStatusBar(), 1000); + }); + }); + + menu.addItem((item) => { + item + .setTitle(t('actions.pull.remote.to.current')) + .setIcon('download') + .onClick(async () => { + await this.pullRemoteToCurrentFile(file); + // Update status bar + setTimeout(() => this.updateStatusBar(), 1000); + }); + }); + + // Show menu near status bar button + const rect = this.statusBarEl?.getBoundingClientRect(); + if (rect) { + menu.showAtPosition({ x: rect.left, y: rect.top - 10 }); + } + } + + private formatDate(date: Date): string { + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) { + return t('date.today'); + } else if (diffDays === 1) { + return t('date.yesterday'); + } else if (diffDays < 7) { + return t('date.days.ago', { days: diffDays }); + } else { + return date.toLocaleDateString('zh-CN'); + } + } +} + +class GitSyncSettingTab extends PluginSettingTab { + plugin: GitSyncPlugin; + + constructor(app: App, plugin: GitSyncPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + containerEl.empty(); + containerEl.createEl('h2', { text: t('settings.title') }); + + // Language setting - put at the beginning + new Setting(containerEl) + .setName(t('settings.language.name')) + .setDesc(t('settings.language.desc')) + .addDropdown(dropdown => { + const languages = getSupportedLanguages(); + languages.forEach(lang => { + dropdown.addOption(lang.value, lang.label); + }); + dropdown.setValue(this.plugin.settings.language); + dropdown.onChange(async (value) => { + this.plugin.settings.language = value as 'zh' | 'en' | 'auto'; + await this.plugin.saveSettings(); + // Re-render settings interface + this.display(); + }); + }); + + new 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; + })); + + let pathInfoEl: HTMLElement; + new 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); + })); + + // Add path description display area + pathInfoEl = containerEl.createEl('div', { + cls: 'setting-item-description git-sync-path-info', + text: this.getPathInfoText(this.plugin.settings.repositoryUrl) + }); + + // Test repository URL button - put below repository path + new Setting(containerEl) + .setName(t('settings.test.url.name')) + .setDesc(t('settings.test.url.desc')) + .addButton(button => button + .setButtonText(t('settings.test.url.button')) + .onClick(async () => { + if (!this.plugin.settings.repositoryUrl) { + new Notice(t('notice.url.required')); + 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); + } + })); + + // Sidebar button display toggle + new 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; + await this.plugin.saveSettings(); + })); + + // Save settings button - put below basic settings area + const saveButtonContainer = containerEl.createEl('div', { + cls: 'git-sync-save-button-container' + }); + const saveButton = saveButtonContainer.createEl('button', { + cls: 'mod-cta', + text: t('settings.save.button') + }); + saveButton.addEventListener('click', async () => { + await this.plugin.saveSettings(); + new Notice(t('notice.settings.saved')); + }); + + // Danger zone title + containerEl.createEl('h2', { + text: t('settings.danger.zone.title'), + cls: 'danger-zone' + }); + + // Initialize repository + new Setting(containerEl) + .setName(t('settings.init.name')) + .setDesc(t('settings.init.desc')) + .addButton(button => { + const isVaultEmpty = this.isVaultEmpty(); + button + .setButtonText(t('settings.init.button')) + .setDisabled(!isVaultEmpty) + .onClick(async () => { + if (!this.plugin.settings.githubToken || !this.plugin.settings.repositoryUrl) { + new Notice(t('notice.config.required')); + return; + } + + button.setButtonText(t('settings.init.loading')); + button.setDisabled(true); + + try { + const result = await this.plugin.initializeRepository(); + if (result.success) { + new Notice(t('notice.result.with.count', { message: result.message, count: result.filesProcessed })); + } else { + new Notice(result.message); + } + } catch (error) { + new Notice(t('notice.init.error')); + } finally { + button.setButtonText(t('settings.init.button')); + button.setDisabled(!this.isVaultEmpty()); + } + }); + + if (isVaultEmpty) { + button.setCta(); + } + }); + + // Force sync remote to local + new Setting(containerEl) + .setName(t('settings.sync.remote.to.local.name')) + .setDesc(t('settings.sync.remote.to.local.desc')) + .addButton(button => button + .setButtonText(t('settings.sync.remote.to.local.button')) + .setWarning() + .onClick(async () => { + if (!this.plugin.settings.githubToken || !this.plugin.settings.repositoryUrl) { + new Notice(t('notice.config.required')); + return; + } + + button.setButtonText(t('settings.sync.remote.to.local.loading')); + button.setDisabled(true); + + try { + const result = await this.plugin.forceSyncRemoteToLocal(); + if (result.success) { + new Notice(t('notice.result.with.count', { message: result.message, count: result.filesProcessed })); + } else { + new Notice(result.message); + } + } catch (error) { + new Notice(t('notice.sync.error')); + } finally { + button.setButtonText(t('settings.sync.remote.to.local.button')); + button.setDisabled(false); + } + })); + + // Force sync local to remote + new Setting(containerEl) + .setName(t('settings.sync.local.to.remote.name')) + .setDesc(t('settings.sync.local.to.remote.desc')) + .addButton(button => button + .setButtonText(t('settings.sync.local.to.remote.button')) + .setWarning() + .onClick(async () => { + if (!this.plugin.settings.githubToken || !this.plugin.settings.repositoryUrl) { + new Notice(t('notice.config.required')); + return; + } + + button.setButtonText(t('settings.sync.local.to.remote.loading')); + button.setDisabled(true); + + try { + const result = await this.plugin.forceSyncLocalToRemote(); + if (result.success) { + new Notice(t('notice.result.with.count', { message: result.message, count: result.filesProcessed })); + } else { + new Notice(result.message); + } + } catch (error) { + new Notice(t('notice.sync.error')); + } finally { + button.setButtonText(t('settings.sync.local.to.remote.button')); + button.setDisabled(false); + } + })); + + // Clear file cache + new Setting(containerEl) + .setName(t('settings.clear.cache.name')) + .setDesc(t('settings.clear.cache.desc')) + .addButton(button => button + .setButtonText(t('settings.clear.cache.button')) + .onClick(async () => { + try { + const stats = this.plugin.fileCacheService.getCacheStats(); + this.plugin.fileCacheService.clearCache(); + new Notice(t('notice.cache.cleared', { count: stats.totalFiles })); + } catch (error) { + new Notice(t('notice.cache.clear.error')); + } + })); + + // Sponsor title + containerEl.createEl('h2', { + text: t('settings.sponsor.section.title'), + cls: 'sponsor' + }); + + // Sponsor section + const sponsorSection = containerEl.createEl('div', { cls: 'setting-item' }); + const sponsorInfo = sponsorSection.createEl('div', { cls: 'setting-item-info' }); + sponsorInfo.createEl('div', { + cls: 'setting-item-name', + text: t('settings.sponsor.name') + }); + sponsorInfo.createEl('div', { + cls: 'setting-item-description', + text: t('settings.sponsor.desc') + }); + + const sponsorControl = sponsorSection.createEl('div', { + cls: getActualLanguage() === 'zh' ? 'setting-item-control git-sync-sponsor-control' : 'setting-item-control' + }); + + // PayPal sponsor button + const sponsorButton = sponsorControl.createEl('a', { + cls: 'mod-cta', + 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') { + // Display China mainland donation channel on new line + const chinaSponsorContainer = sponsorControl.createEl('div', { + cls: 'git-sync-china-sponsor' + }); + chinaSponsorContainer.createEl('span', { + text: t('settings.sponsor.china.label') + ': ' + }); + const chinaLink = chinaSponsorContainer.createEl('a', { + 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; + } + + private updatePathInfo(el: HTMLElement, value: string) { + if (el) { + el.empty(); + el.createEl('span', { text: this.getPathInfoText(value) }); + } + } + + private getPathInfoText(value: string): string { + if (!value) { + return t('path.info.empty'); + } + + // Use GitHub service parsing method + const parseResult = this.plugin.githubService.parseRepositoryUrl(value); + + if (parseResult) { + let pathInfo = t('path.info.sync.to', { owner: parseResult.owner, repo: parseResult.repo }); + if (parseResult.path) { + pathInfo += t('path.info.folder', { path: parseResult.path }); + } else { + pathInfo += t('path.info.root'); + } + return pathInfo; + } + + return t('path.info.error'); + } +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..6156153 --- /dev/null +++ b/manifest.json @@ -0,0 +1,11 @@ +{ + "id": "git-sync", + "name": "Git Sync", + "version": "1.0.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).", + "author": "Xheldon", + "authorUrl": "https://github.com/Xheldon", + "fundingUrl": "https://paypal.me/xheldoncao", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b69cda7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2736 @@ +{ + "name": "obsidian-git-sync", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "obsidian-git-sync", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@octokit/rest": "^19.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^16.11.6", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@typescript-eslint/eslint-plugin": "5.29.0", + "@typescript-eslint/parser": "5.29.0", + "builtin-modules": "3.3.0", + "esbuild": "0.17.3", + "obsidian": "latest", + "tslib": "2.4.0", + "typescript": "4.7.4" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", + "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", + "integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", + "integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", + "integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", + "integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", + "integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", + "integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", + "integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", + "integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", + "integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", + "integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", + "integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", + "integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", + "integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", + "integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", + "integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", + "integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", + "integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", + "integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", + "integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", + "integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", + "integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", + "integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz", + "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", + "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/endpoint": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz", + "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/graphql": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz", + "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", + "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz", + "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==", + "license": "MIT", + "dependencies": { + "@octokit/tsconfig": "^1.0.2", + "@octokit/types": "^9.2.3" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=4" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "license": "MIT", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz", + "integrity": "sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^10.0.0" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz", + "integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz", + "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/request-error": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz", + "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/rest": { + "version": "19.0.13", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.13.tgz", + "integrity": "sha512-/EzVox5V9gYGdbAI+ovYj3nXQT1TtTHRT+0eZPcuC05UFSWO3mdO9UY1C0i2eLF9Un1ONJkAk+IEtYGAC+TahA==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^4.2.1", + "@octokit/plugin-paginate-rest": "^6.1.2", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^7.1.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/tsconfig": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz", + "integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==", + "license": "MIT" + }, + "node_modules/@octokit/types": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz", + "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz", + "integrity": "sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/type-utils": "5.29.0", + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz", + "integrity": "sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz", + "integrity": "sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz", + "integrity": "sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.29.0.tgz", + "integrity": "sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz", + "integrity": "sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.29.0.tgz", + "integrity": "sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz", + "integrity": "sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", + "integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.3", + "@esbuild/android-arm64": "0.17.3", + "@esbuild/android-x64": "0.17.3", + "@esbuild/darwin-arm64": "0.17.3", + "@esbuild/darwin-x64": "0.17.3", + "@esbuild/freebsd-arm64": "0.17.3", + "@esbuild/freebsd-x64": "0.17.3", + "@esbuild/linux-arm": "0.17.3", + "@esbuild/linux-arm64": "0.17.3", + "@esbuild/linux-ia32": "0.17.3", + "@esbuild/linux-loong64": "0.17.3", + "@esbuild/linux-mips64el": "0.17.3", + "@esbuild/linux-ppc64": "0.17.3", + "@esbuild/linux-riscv64": "0.17.3", + "@esbuild/linux-s390x": "0.17.3", + "@esbuild/linux-x64": "0.17.3", + "@esbuild/netbsd-x64": "0.17.3", + "@esbuild/openbsd-x64": "0.17.3", + "@esbuild/sunos-x64": "0.17.3", + "@esbuild/win32-arm64": "0.17.3", + "@esbuild/win32-ia32": "0.17.3", + "@esbuild/win32-x64": "0.17.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/obsidian": { + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.8.7.tgz", + "integrity": "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6a3b2a0 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "git-folder-sync", + "version": "1.0.0", + "description": "Synchronize files in your Obsidian Vault with a specified directory in the designated GitHub repository (rather than the entire repository).", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", + "version": "node version-bump.mjs && git add manifest.json versions.json" + }, + "keywords": [ + "obsidian", + "plugin", + "github", + "sync" + ], + "author": "Xheldon (https://github.com/Xheldon)", + "license": "MIT", + "funding": { + "type": "paypal", + "url": "https://paypal.me/xheldoncao" + }, + "devDependencies": { + "@types/node": "^16.11.6", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@typescript-eslint/eslint-plugin": "5.29.0", + "@typescript-eslint/parser": "5.29.0", + "builtin-modules": "3.3.0", + "esbuild": "0.17.3", + "obsidian": "latest", + "tslib": "2.4.0", + "typescript": "4.7.4" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "@octokit/rest": "^19.0.0" + } +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..81b196f --- /dev/null +++ b/styles.css @@ -0,0 +1,325 @@ +/* Git Sync Plugin Styles */ +.git-sync-modal { + min-width: 500px; + max-width: 800px; + width: auto; + max-height: 90vh; + overflow-y: auto; +} + +.git-sync-modal .modal-content { + padding: 20px; +} + +.git-sync-settings-container { + padding: 0; + min-width: 0; +} + +.git-sync-settings .setting-item { + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: 18px 0; + border-top: 1px solid var(--background-modifier-border); +} + +.git-sync-settings .setting-item:first-child { + border-top: none; +} + +.git-sync-settings .setting-item-info { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; + margin-right: 16px; +} + +.git-sync-settings .setting-item-name { + font-weight: var(--font-weight-medium); + color: var(--text-normal); + margin-bottom: 2px; +} + +.git-sync-settings .setting-item-description { + font-size: var(--font-ui-smaller); + color: var(--text-muted); + line-height: 1.3; +} + +.git-sync-settings .setting-item-control { + display: flex; + align-items: center; + flex-shrink: 0; +} + +/* Sponsor section special layout */ +.git-sync-sponsor-control { + flex-direction: column !important; + align-items: flex-end !important; +} + +.git-sync-settings .setting-item-input { + width: 300px; + min-width: 300px; + max-width: 400px; + padding: 6px 8px; + border: 1px solid var(--background-modifier-border); + border-radius: 3px; + background: var(--background-primary); + color: var(--text-normal); + font-size: var(--font-ui-small); +} + +/* Input field general styles */ +.git-sync-settings input[type='text'], +.git-sync-settings input[type='password'] { + width: 300px; + min-width: 300px; + max-width: 400px; +} + +/* China mainland donation channel styles */ +.git-sync-china-sponsor { + margin-top: 8px; + font-size: var(--font-ui-smaller); + color: var(--text-muted); +} + +.git-sync-settings .setting-item-input:focus { + border-color: var(--interactive-accent); + outline: none; + box-shadow: 0 0 0 2px var(--interactive-accent-hover); +} + +.git-sync-settings .setting-item-separator { + border: none; + border-top: 1px solid var(--background-modifier-border); + margin: 12px 0; +} + +.git-sync-settings button { + padding: 6px 12px; + border: 1px solid var(--interactive-normal); + border-radius: 3px; + background: var(--interactive-normal); + color: var(--text-on-accent); + cursor: pointer; + font-size: var(--font-ui-small); + font-weight: var(--font-weight-medium); + transition: all 0.1s ease; +} + +.git-sync-settings button:hover { + background: var(--interactive-hover); + border-color: var(--interactive-hover); +} + +.git-sync-settings button:active { + background: var(--interactive-active); + border-color: var(--interactive-active); +} + +.git-sync-settings button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.git-sync-settings button.mod-cta { + background: var(--interactive-accent); + border-color: var(--interactive-accent); + color: var(--text-on-accent); +} + +.git-sync-settings button.mod-cta:hover { + background: var(--interactive-accent-hover); + border-color: var(--interactive-accent-hover); +} + +.git-sync-settings button.mod-warning { + background: var(--color-orange); + border-color: var(--color-orange); + color: var(--text-on-accent); +} + +.git-sync-settings button.mod-warning:hover { + background: var(--color-red); + border-color: var(--color-red); +} + +.git-sync-settings button.mod-secondary { + background: var(--background-secondary); + border-color: var(--background-modifier-border); + color: var(--text-normal); +} + +.git-sync-settings button.mod-secondary:hover { + background: var(--background-secondary-alt); +} + +/* Sync button styles */ +.git-sync-menu-button { + display: inline-flex; + align-items: center; + padding: 4px 8px; + margin: 0 2px; + border-radius: 3px; + background: var(--interactive-normal); + color: var(--text-normal); + cursor: pointer; + font-size: var(--font-ui-smaller); + transition: all 0.1s ease; +} + +.git-sync-menu-button:hover { + background: var(--interactive-hover); +} + +.git-sync-menu-button svg { + width: 14px; + height: 14px; + margin-right: 4px; +} + +/* Responsive design */ +@media (max-width: 768px) { + .git-sync-modal { + min-width: 300px; + max-width: 90vw; + width: auto; + } + + .git-sync-settings .setting-item { + flex-direction: column; + align-items: stretch; + } + + .git-sync-settings .setting-item-info { + margin-right: 0; + margin-bottom: 12px; + } + + .git-sync-settings .setting-item-input { + width: 100%; + } +} + +/* Ensure modal content doesn't overflow */ +.git-sync-modal .modal-container { + max-height: 90vh; + overflow-y: auto; +} + +.git-sync-settings { + max-width: 100%; + overflow-x: hidden; +} + +.git-sync-settings .setting-item-input { + min-width: 200px; + max-width: 100%; + box-sizing: border-box; +} + +/* Path description styles */ +.git-sync-path-info { + background: var(--background-secondary); + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + padding: 8px 12px; + margin: 8px 0 16px 0; + font-size: var(--font-ui-smaller); + color: var(--text-accent); + font-weight: var(--font-weight-medium); +} + +.git-sync-path-info:empty { + display: none; +} + +/* Status bar styles */ +.git-sync-status-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 0 8px; + font-size: var(--font-ui-smaller); +} + +.git-sync-status-text { + color: var(--text-muted); + font-size: var(--font-ui-smaller); +} + +.git-sync-status-button { + padding: 2px 6px; + border: 1px solid var(--background-modifier-border); + border-radius: 3px; + background: var(--background-secondary); + color: var(--text-normal); + cursor: pointer; + font-size: var(--font-ui-smaller); + transition: all 0.1s ease; +} + +.git-sync-status-button:hover { + background: var(--background-secondary-alt); + border-color: var(--interactive-accent); +} + +.git-sync-status-button:active { + background: var(--interactive-accent); + color: var(--text-on-accent); +} + +.git-sync-status-button:disabled { + opacity: 0.5; + cursor: not-allowed; + background: var(--background-secondary); + border-color: var(--background-modifier-border); +} + +.git-sync-status-button:disabled:hover { + background: var(--background-secondary); + border-color: var(--background-modifier-border); +} + +/* Save button container styles */ +.git-sync-save-button-container { + display: flex; + justify-content: flex-end; + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid var(--background-modifier-border); +} + +.git-sync-save-button-container button { + padding: 8px 16px; + border: 1px solid var(--interactive-accent); + border-radius: 4px; + background: var(--interactive-accent); + color: var(--text-on-accent); + cursor: pointer; + font-size: var(--font-ui-small); + font-weight: var(--font-weight-medium); + transition: all 0.1s ease; +} + +.git-sync-save-button-container button:hover { + background: var(--interactive-accent-hover); + border-color: var(--interactive-accent-hover); +} + +.git-sync-save-button-container button:active { + background: var(--interactive-accent); + border-color: var(--interactive-accent); +} + +.danger-zone { + color: var(--color-red); +} + +.sponsor { + color: var(--color-orange); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e2c3647 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": false, + "moduleResolution": "node", + "importHelpers": true, + "declaration": true, + "outDir": "lib", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "typeRoots": ["node_modules/@types"], + "lib": ["DOM", "ES6"], + "jsx": "react-jsx" + }, + "include": ["**/*.ts", "**/*.tsx"] +} diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..3e83785 --- /dev/null +++ b/types.ts @@ -0,0 +1,58 @@ +export interface GitSyncSettings { + githubToken: string; + repositoryUrl: string; // Format: @https://github.com/user/repo/path/to/folder + lastSyncTime: number; + showRibbonIcon: boolean; // Whether to show sidebar button + language: 'zh' | 'en' | 'auto'; // Language setting, auto means follow system +} + +export const DEFAULT_SETTINGS: GitSyncSettings = { + githubToken: '', + repositoryUrl: '', + lastSyncTime: 0, + showRibbonIcon: true, + language: 'auto' // Follow Obsidian language setting +}; + +export interface GitHubFile { + name: string; + path: string; + sha: string; + size: number; + url: string; + html_url: string; + git_url: string; + download_url: string; + type: 'file' | 'dir'; + content?: string; + encoding?: string; +} + +export interface SyncResult { + success: boolean; + message: string; + filesProcessed?: number; +} + +// File cache status +export interface FileCache { + filePath: string; // File path in Obsidian + githubPath: string; // File path in GitHub + lastModified: string; // Last modified time + sha: string; // SHA value of GitHub file + isPublished: boolean; // Whether published to GitHub + isSynced: boolean; // Whether synced with GitHub + cacheTime: number; // Cache timestamp + fileSize: number; // File size + contentHash: string; // File content hash for detecting local modifications +} + +// Cache manager interface +export interface FileCacheManager { + getFileCache(filePath: string): FileCache | null; + setFileCache(filePath: string, cache: FileCache): void; + removeFileCache(filePath: string): void; + clearCache(): void; + isCacheValid(cache: FileCache, maxAge?: number): boolean; + getAllCachedFiles(): FileCache[]; +} \ No newline at end of file diff --git a/version-bump.mjs b/version-bump.mjs new file mode 100644 index 0000000..7810a16 --- /dev/null +++ b/version-bump.mjs @@ -0,0 +1,14 @@ +import { readFileSync, writeFileSync } from "fs"; + +const targetVersion = process.env.npm_package_version; + +// read manifest.json +let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const { minAppVersion } = manifest; +manifest.version = targetVersion; +writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); + +// update versions.json +let versions = JSON.parse(readFileSync("versions.json", "utf8")); +versions[targetVersion] = minAppVersion; +writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); \ No newline at end of file diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..40825b2 --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "1.0.0": "0.15.0" +} \ No newline at end of file