From 6d8fdf62f14dea120ad7d41755da752e42f37cc8 Mon Sep 17 00:00:00 2001 From: landaiqing Date: Sun, 13 Jul 2025 11:58:53 +0800 Subject: [PATCH] :bulb: Update docs --- docs/index.html | 55 ++- docs/js/changelog.js | 771 ++++++++++++++++++++++++++----------------- docs/js/script.js | 531 ++++++++++++++++++++++------- go.mod | 14 +- go.sum | 44 +-- 5 files changed, 964 insertions(+), 451 deletions(-) diff --git a/docs/index.html b/docs/index.html index 8c4d663..10812fc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,9 +4,62 @@ VoidRaft - An elegant text snippet recording tool designed for developers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -200,4 +253,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/js/changelog.js b/docs/js/changelog.js index 3cb728a..033fe94 100644 --- a/docs/js/changelog.js +++ b/docs/js/changelog.js @@ -1,211 +1,283 @@ /** * VoidRaft - Changelog Script - * Fetches release information from GitHub API with Gitea fallback + * 从GitHub API获取发布信息,支持Gitea备用源 */ -document.addEventListener('DOMContentLoaded', () => { - // Repository information - const REPOS = { - github: { - owner: 'landaiqing', - name: 'voidraft', - apiUrl: 'https://api.github.com/repos/landaiqing/voidraft/releases', - releasesUrl: 'https://github.com/landaiqing/voidraft/releases' - }, - gitea: { - owner: 'landaiqing', - name: 'voidraft', - domain: 'git.landaiqing.cn', - apiUrl: 'https://git.landaiqing.cn/api/v1/repos/landaiqing/voidraft/releases', - releasesUrl: 'https://git.landaiqing.cn/landaiqing/voidraft/releases' - } - }; - - // Error messages with i18n support - const MESSAGES = { - loading: { - en: 'Loading releases...', - zh: '正在加载版本信息...' - }, - noReleases: { - en: 'No release information found', - zh: '没有找到版本发布信息' - }, - fetchError: { - en: 'Failed to load release information. Please try again later.', - zh: '无法获取版本信息,请稍后再试' - }, - githubApiError: { - en: 'GitHub API returned an error status: ', - zh: 'GitHub API返回错误状态: ' - }, - giteaApiError: { - en: 'Gitea API returned an error status: ', - zh: 'Gitea API返回错误状态: ' - }, - dataSource: { - en: 'Data source: ', - zh: '数据来源: ' - }, - downloads: { - en: 'Downloads', - zh: '下载资源' - }, - download: { - en: 'Download', - zh: '下载' - }, - preRelease: { - en: 'Pre-release', - zh: '预发布' - } - }; - - // Element references - const elements = { - loading: document.getElementById('loading'), - changelog: document.getElementById('changelog'), - error: document.getElementById('error-message') - }; - - // Initialize - init(); - - /** - * Initialize the changelog - */ - function init() { - // Try GitHub API first - fetchReleases('github') - .catch(() => fetchReleases('gitea')) - .catch(error => { - elements.loading.style.display = 'none'; - showError(MESSAGES.fetchError[getCurrentLang()]); - }); + +/** + * 仓库配置类 + */ +class RepositoryConfig { + constructor() { + this.repos = { + github: { + owner: 'landaiqing', + name: 'voidraft', + apiUrl: 'https://api.github.com/repos/landaiqing/voidraft/releases', + releasesUrl: 'https://github.com/landaiqing/voidraft/releases' + }, + gitea: { + owner: 'landaiqing', + name: 'voidraft', + domain: 'git.landaiqing.cn', + apiUrl: 'https://git.landaiqing.cn/api/v1/repos/landaiqing/voidraft/releases', + releasesUrl: 'https://git.landaiqing.cn/landaiqing/voidraft/releases' + } + }; } - + /** - * Get current language + * 获取仓库配置 + * @param {string} source - 'github' 或 'gitea' */ - function getCurrentLang() { + getRepo(source) { + return this.repos[source]; + } + + /** + * 获取所有仓库配置 + */ + getAllRepos() { + return this.repos; + } +} + +/** + * 国际化消息管理类 + */ +class I18nMessages { + constructor() { + this.messages = { + loading: { + en: 'Loading releases...', + zh: '正在加载版本信息...' + }, + noReleases: { + en: 'No release information found', + zh: '没有找到版本发布信息' + }, + fetchError: { + en: 'Failed to load release information. Please try again later.', + zh: '无法获取版本信息,请稍后再试' + }, + githubApiError: { + en: 'GitHub API returned an error status: ', + zh: 'GitHub API返回错误状态: ' + }, + giteaApiError: { + en: 'Gitea API returned an error status: ', + zh: 'Gitea API返回错误状态: ' + }, + dataSource: { + en: 'Data source: ', + zh: '数据来源: ' + }, + downloads: { + en: 'Downloads', + zh: '下载资源' + }, + download: { + en: 'Download', + zh: '下载' + }, + preRelease: { + en: 'Pre-release', + zh: '预发布' + } + }; + } + + /** + * 获取消息 + * @param {string} key - 消息键 + * @param {string} lang - 语言代码 + */ + getMessage(key, lang = 'en') { + return this.messages[key] && this.messages[key][lang] || this.messages[key]['en'] || ''; + } + + /** + * 获取当前语言 + */ + getCurrentLang() { return window.currentLang || 'en'; } - +} + +/** + * API客户端类 + */ +class APIClient { + constructor(repositoryConfig, i18nMessages) { + this.repositoryConfig = repositoryConfig; + this.i18nMessages = i18nMessages; + } + /** - * Fetch releases from specified source - * @param {string} source - 'github' or 'gitea' + * 从指定源获取发布信息 + * @param {string} source - 'github' 或 'gitea' */ - async function fetchReleases(source) { - const apiUrl = REPOS[source].apiUrl; + async fetchReleases(source) { + const repo = this.repositoryConfig.getRepo(source); const errorMessageKey = source === 'github' ? 'githubApiError' : 'giteaApiError'; - // Setup timeout for GitHub const options = { headers: { 'Accept': 'application/json' } }; if (source === 'github') { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout - options.signal = controller.signal; - options.headers['Accept'] = 'application/vnd.github.v3+json'; - - try { - const response = await fetch(apiUrl, options); - clearTimeout(timeoutId); - - if (!response.ok) { - throw new Error(`${MESSAGES[errorMessageKey][getCurrentLang()]}${response.status}`); - } - - const releases = await response.json(); - - if (!releases || releases.length === 0) { - throw new Error(MESSAGES.noReleases[getCurrentLang()]); - } - - // Display releases - elements.loading.style.display = 'none'; - displayReleases(releases, source); - elements.changelog.style.display = 'block'; - - return releases; - } catch (error) { - clearTimeout(timeoutId); - throw error; - } + return this.fetchFromGitHub(repo, options, errorMessageKey); } else { - const response = await fetch(apiUrl, options); + return this.fetchFromGitea(repo, options, errorMessageKey); + } + } + + /** + * 从GitHub获取数据 + * @param {Object} repo - 仓库配置 + * @param {Object} options - 请求选项 + * @param {string} errorMessageKey - 错误消息键 + */ + async fetchFromGitHub(repo, options, errorMessageKey) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); + + options.signal = controller.signal; + options.headers['Accept'] = 'application/vnd.github.v3+json'; + + try { + const response = await fetch(repo.apiUrl, options); + clearTimeout(timeoutId); if (!response.ok) { - throw new Error(`${MESSAGES[errorMessageKey][getCurrentLang()]}${response.status}`); + throw new Error(`${this.i18nMessages.getMessage(errorMessageKey, this.i18nMessages.getCurrentLang())}${response.status}`); } const releases = await response.json(); - // Hide loading indicator - elements.loading.style.display = 'none'; - if (!releases || releases.length === 0) { - throw new Error(MESSAGES.noReleases[getCurrentLang()]); + throw new Error(this.i18nMessages.getMessage('noReleases', this.i18nMessages.getCurrentLang())); } - // Display releases - displayReleases(releases, source); - elements.changelog.style.display = 'block'; - return releases; + } catch (error) { + clearTimeout(timeoutId); + throw error; } } - + /** - * Show error message + * 从Gitea获取数据 + * @param {Object} repo - 仓库配置 + * @param {Object} options - 请求选项 + * @param {string} errorMessageKey - 错误消息键 */ - function showError(message) { - const errorMessageElement = elements.error.querySelector('p'); - errorMessageElement.textContent = message; - elements.error.style.display = 'block'; + async fetchFromGitea(repo, options, errorMessageKey) { + const response = await fetch(repo.apiUrl, options); + + if (!response.ok) { + throw new Error(`${this.i18nMessages.getMessage(errorMessageKey, this.i18nMessages.getCurrentLang())}${response.status}`); + } + + const releases = await response.json(); + + if (!releases || releases.length === 0) { + throw new Error(this.i18nMessages.getMessage('noReleases', this.i18nMessages.getCurrentLang())); + } + + return releases; } +} +/** + * UI管理类 + */ +class UIManager { + constructor(i18nMessages) { + this.i18nMessages = i18nMessages; + this.elements = { + loading: document.getElementById('loading'), + changelog: document.getElementById('changelog'), + error: document.getElementById('error-message') + }; + } + /** - * Display releases - * @param {Array} releases - Array of release objects - * @param {string} source - 'github' or 'gitea' + * 显示加载状态 */ - function displayReleases(releases, source) { - // Clear existing content - elements.changelog.innerHTML = ''; + showLoading() { + this.elements.loading.style.display = 'block'; + this.elements.error.style.display = 'none'; + this.elements.changelog.innerHTML = ''; + } + + /** + * 隐藏加载状态 + */ + hideLoading() { + this.elements.loading.style.display = 'none'; + } + + /** + * 显示错误消息 + * @param {string} message - 错误消息 + */ + showError(message) { + const errorMessageElement = this.elements.error.querySelector('p'); + if (errorMessageElement) { + errorMessageElement.textContent = message; + } else { + this.elements.error.textContent = message; + } + this.elements.error.style.display = 'block'; + this.hideLoading(); + } + + /** + * 显示发布信息 + * @param {Array} releases - 发布信息数组 + * @param {string} source - 数据源 + */ + displayReleases(releases, source) { + this.hideLoading(); - // Add data source indicator - const sourceElement = createSourceElement(source); - elements.changelog.appendChild(sourceElement); + // 清除现有内容 + this.elements.changelog.innerHTML = ''; - // Create release elements + // 创建数据源元素 + const sourceElement = this.createSourceElement(source); + this.elements.changelog.appendChild(sourceElement); + + // 创建发布信息元素 releases.forEach(release => { - const releaseElement = createReleaseElement(release, source); - elements.changelog.appendChild(releaseElement); + const releaseElement = this.createReleaseElement(release, source); + this.elements.changelog.appendChild(releaseElement); }); + + this.elements.changelog.style.display = 'block'; } - + /** - * Create source element + * 创建数据源元素 + * @param {string} source - 数据源 */ - function createSourceElement(source) { + createSourceElement(source) { const sourceElement = document.createElement('div'); sourceElement.className = 'data-source'; - // Create source label with i18n support + // 创建带有国际化支持的源标签 const sourceLabel = document.createElement('span'); - sourceLabel.setAttribute('data-en', MESSAGES.dataSource.en); - sourceLabel.setAttribute('data-zh', MESSAGES.dataSource.zh); - sourceLabel.textContent = MESSAGES.dataSource[getCurrentLang()]; + sourceLabel.setAttribute('data-en', this.i18nMessages.getMessage('dataSource', 'en')); + sourceLabel.setAttribute('data-zh', this.i18nMessages.getMessage('dataSource', 'zh')); + sourceLabel.textContent = this.i18nMessages.getMessage('dataSource', this.i18nMessages.getCurrentLang()); - // Create link - const sourceLink = document.createElement('a'); - sourceLink.href = REPOS[source].releasesUrl; - sourceLink.textContent = source === 'github' ? 'GitHub' : 'Gitea'; - sourceLink.target = '_blank'; + // 创建链接 + const sourceLink = document.createElement('a'); + const repositoryConfig = new RepositoryConfig(); + sourceLink.href = repositoryConfig.getRepo(source).releasesUrl; + sourceLink.textContent = source === 'github' ? 'GitHub' : 'Gitea'; + sourceLink.target = '_blank'; - // Assemble elements + // 组装元素 sourceElement.appendChild(sourceLabel); sourceElement.appendChild(sourceLink); @@ -213,34 +285,34 @@ document.addEventListener('DOMContentLoaded', () => { } /** - * Create release element - * @param {Object} release - Release data - * @param {string} source - 'github' or 'gitea' + * 创建发布信息元素 + * @param {Object} release - 发布信息对象 + * @param {string} source - 数据源 */ - function createReleaseElement(release, source) { + createReleaseElement(release, source) { const releaseElement = document.createElement('div'); releaseElement.className = 'release'; - // Format release date + // 格式化发布日期 const releaseDate = new Date(release.published_at || release.created_at); - const formattedDate = formatDate(releaseDate); + const formattedDate = DateFormatter.formatDate(releaseDate); - // Create header - const headerElement = createReleaseHeader(release, formattedDate); + // 创建头部 + const headerElement = this.createReleaseHeader(release, formattedDate); releaseElement.appendChild(headerElement); - // Add release description + // 添加发布说明 if (release.body) { const descriptionElement = document.createElement('div'); descriptionElement.className = 'release-description markdown-content'; - descriptionElement.innerHTML = parseMarkdown(release.body); + descriptionElement.innerHTML = MarkdownParser.parseMarkdown(release.body); releaseElement.appendChild(descriptionElement); } - // Add download assets - const assets = getAssetsFromRelease(release, source); + // 添加下载资源 + const assets = AssetManager.getAssetsFromRelease(release, source); if (assets && assets.length > 0) { - const assetsElement = createAssetsElement(assets); + const assetsElement = this.createAssetsElement(assets); releaseElement.appendChild(assetsElement); } @@ -248,32 +320,32 @@ document.addEventListener('DOMContentLoaded', () => { } /** - * Create release header + * 创建发布信息头部 */ - function createReleaseHeader(release, formattedDate) { + createReleaseHeader(release, formattedDate) { const headerElement = document.createElement('div'); headerElement.className = 'release-header'; - // Version element + // 版本元素 const versionElement = document.createElement('div'); versionElement.className = 'release-version'; - // Version text + // 版本文本 const versionText = document.createElement('span'); versionText.textContent = release.name || release.tag_name; versionElement.appendChild(versionText); - // Pre-release badge + // 预发布标记 if (release.prerelease) { const preReleaseTag = document.createElement('span'); preReleaseTag.className = 'release-badge pre-release'; - preReleaseTag.setAttribute('data-en', MESSAGES.preRelease.en); - preReleaseTag.setAttribute('data-zh', MESSAGES.preRelease.zh); - preReleaseTag.textContent = MESSAGES.preRelease[getCurrentLang()]; + preReleaseTag.setAttribute('data-en', this.i18nMessages.getMessage('preRelease', 'en')); + preReleaseTag.setAttribute('data-zh', this.i18nMessages.getMessage('preRelease', 'zh')); + preReleaseTag.textContent = this.i18nMessages.getMessage('preRelease', this.i18nMessages.getCurrentLang()); versionElement.appendChild(preReleaseTag); } - // Date element + // 日期元素 const dateElement = document.createElement('div'); dateElement.className = 'release-date'; dateElement.textContent = formattedDate; @@ -285,16 +357,94 @@ document.addEventListener('DOMContentLoaded', () => { } /** - * Get assets from release based on source + * 创建资源文件元素 + * @param {Array} assets - 资源文件数组 */ - function getAssetsFromRelease(release, source) { + createAssetsElement(assets) { + const assetsElement = document.createElement('div'); + assetsElement.className = 'release-assets'; + + // 资源标题 + const assetsTitle = document.createElement('div'); + assetsTitle.className = 'release-assets-title'; + assetsTitle.setAttribute('data-en', this.i18nMessages.getMessage('downloads', 'en')); + assetsTitle.setAttribute('data-zh', this.i18nMessages.getMessage('downloads', 'zh')); + assetsTitle.textContent = this.i18nMessages.getMessage('downloads', this.i18nMessages.getCurrentLang()); + + // 资源列表 + const assetList = document.createElement('ul'); + assetList.className = 'asset-list'; + + // 添加每个资源 + assets.forEach(asset => { + const assetItem = this.createAssetItem(asset); + assetList.appendChild(assetItem); + }); + + assetsElement.appendChild(assetsTitle); + assetsElement.appendChild(assetList); + + return assetsElement; + } + + /** + * 创建资源文件项 + * @param {Object} asset - 资源文件对象 + */ + createAssetItem(asset) { + const assetItem = document.createElement('li'); + assetItem.className = 'asset-item'; + + // 文件图标 + const iconElement = document.createElement('i'); + iconElement.className = `asset-icon fas fa-${FileIconHelper.getFileIcon(asset.name)}`; + + // 文件名 + const nameElement = document.createElement('span'); + nameElement.className = 'asset-name'; + nameElement.textContent = asset.name; + + // 文件大小 + const sizeElement = document.createElement('span'); + sizeElement.className = 'asset-size'; + sizeElement.textContent = FileSizeFormatter.formatFileSize(asset.size); + + // 下载链接 + const downloadLink = document.createElement('a'); + downloadLink.className = 'download-btn'; + downloadLink.href = asset.browser_download_url; + downloadLink.target = '_blank'; + downloadLink.setAttribute('data-en', this.i18nMessages.getMessage('download', 'en')); + downloadLink.setAttribute('data-zh', this.i18nMessages.getMessage('download', 'zh')); + downloadLink.textContent = this.i18nMessages.getMessage('download', this.i18nMessages.getCurrentLang()); + + // 组装资源项 + assetItem.appendChild(iconElement); + assetItem.appendChild(nameElement); + assetItem.appendChild(sizeElement); + assetItem.appendChild(downloadLink); + + return assetItem; + } +} + +/** + * 资源管理器类 + */ +class AssetManager { + /** + * 从发布信息中获取资源文件 + * @param {Object} release - 发布信息对象 + * @param {string} source - 数据源 + */ + static getAssetsFromRelease(release, source) { let assets = []; if (source === 'github') { assets = release.assets || []; } else { // Gitea assets = release.assets || []; - // Check for Gitea-specific asset structure + // 检查Gitea特定的资源结构 if (!assets.length && release.attachments) { assets = release.attachments.map(attachment => ({ name: attachment.name, @@ -306,131 +456,93 @@ document.addEventListener('DOMContentLoaded', () => { return assets; } - +} + +/** + * 文件图标助手类 + */ +class FileIconHelper { /** - * Create assets element + * 根据文件扩展名获取图标 + * @param {string} filename - 文件名 */ - function createAssetsElement(assets) { - const assetsElement = document.createElement('div'); - assetsElement.className = 'release-assets'; + static getFileIcon(filename) { + const extension = filename.split('.').pop().toLowerCase(); - // Assets title - const assetsTitle = document.createElement('div'); - assetsTitle.className = 'release-assets-title'; - assetsTitle.setAttribute('data-en', MESSAGES.downloads.en); - assetsTitle.setAttribute('data-zh', MESSAGES.downloads.zh); - assetsTitle.textContent = MESSAGES.downloads[getCurrentLang()]; + const iconMap = { + 'exe': 'download', + 'msi': 'download', + 'dmg': 'download', + 'pkg': 'download', + 'deb': 'download', + 'rpm': 'download', + 'tar': 'file-archive', + 'gz': 'file-archive', + 'zip': 'file-archive', + '7z': 'file-archive', + 'rar': 'file-archive', + 'pdf': 'file-pdf', + 'txt': 'file-alt', + 'md': 'file-alt', + 'json': 'file-code', + 'xml': 'file-code', + 'yml': 'file-code', + 'yaml': 'file-code' + }; - // Asset list - const assetList = document.createElement('ul'); - assetList.className = 'asset-list'; - - // Add each asset - assets.forEach(asset => { - const assetItem = createAssetItem(asset); - assetList.appendChild(assetItem); - }); - - assetsElement.appendChild(assetsTitle); - assetsElement.appendChild(assetList); - - return assetsElement; + return iconMap[extension] || 'file'; } - +} + +/** + * 文件大小格式化器类 + */ +class FileSizeFormatter { /** - * Create asset item + * 格式化文件大小 + * @param {number} bytes - 字节数 */ - function createAssetItem(asset) { - const assetItem = document.createElement('li'); - assetItem.className = 'asset-item'; + static formatFileSize(bytes) { + if (!bytes) return ''; - // File icon - const iconElement = document.createElement('i'); - iconElement.className = `asset-icon fas fa-${getFileIcon(asset.name)}`; - - // File name - const nameElement = document.createElement('span'); - nameElement.className = 'asset-name'; - nameElement.textContent = asset.name; - - // File size - const sizeElement = document.createElement('span'); - sizeElement.className = 'asset-size'; - sizeElement.textContent = formatFileSize(asset.size); - - // Download link - const downloadLink = document.createElement('a'); - downloadLink.className = 'download-btn'; - downloadLink.href = asset.browser_download_url; - downloadLink.target = '_blank'; - downloadLink.setAttribute('data-en', MESSAGES.download.en); - downloadLink.setAttribute('data-zh', MESSAGES.download.zh); - downloadLink.textContent = MESSAGES.download[getCurrentLang()]; - - // Assemble asset item - assetItem.appendChild(iconElement); - assetItem.appendChild(nameElement); - assetItem.appendChild(sizeElement); - assetItem.appendChild(downloadLink); - - return assetItem; - } - - /** - * Get file icon based on extension - */ - function getFileIcon(filename) { - const ext = filename.split('.').pop().toLowerCase(); - - switch (ext) { - case 'zip': - case 'gz': - case 'tar': - case '7z': - return 'file-archive'; - case 'exe': - return 'file-code'; - case 'dmg': - return 'apple'; - case 'deb': - case 'rpm': - return 'linux'; - case 'json': - case 'xml': - return 'file-alt'; - default: - return 'file'; - } - } - - /** - * Format file size - */ - function formatFileSize(bytes) { - if (bytes === 0) return '0 Bytes'; - - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); - return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i]; + return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; } - +} + +/** + * 日期格式化器类 + */ +class DateFormatter { /** - * Format date + * 格式化日期 + * @param {Date} date - 日期对象 */ - function formatDate(date) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); + static formatDate(date) { + const options = { + year: 'numeric', + month: 'long', + day: 'numeric' + }; - return `${year}-${month}-${day}`; + const lang = window.currentLang || 'en'; + const locale = lang === 'zh' ? 'zh-CN' : 'en-US'; + + return date.toLocaleDateString(locale, options); } - +} + +/** + * Markdown解析器类 + */ +class MarkdownParser { /** - * Simple Markdown parser - * Note: This is a very basic implementation that handles only common Markdown syntax + * 简单的Markdown解析 + * @param {string} markdown - Markdown文本 */ - function parseMarkdown(markdown) { + static parseMarkdown(markdown) { if (!markdown) return ''; // 预处理:保留原始换行符,用特殊标记替换 @@ -529,24 +641,65 @@ document.addEventListener('DOMContentLoaded', () => { return markdown; } - - // Update translations when language changes - window.addEventListener('languageChanged', updateUI); - - // Initial UI update based on current language - updateUI(); - - /** - * Update UI elements with current language - */ - function updateUI() { - const lang = getCurrentLang(); +} + +/** + * 更新日志主应用类 + */ +class ChangelogApp { + constructor() { + this.repositoryConfig = new RepositoryConfig(); + this.i18nMessages = new I18nMessages(); + this.apiClient = new APIClient(this.repositoryConfig, this.i18nMessages); + this.uiManager = new UIManager(this.i18nMessages); - // Update all i18n elements - document.querySelectorAll('[data-en][data-zh]').forEach(el => { - if (el.hasAttribute(`data-${lang}`)) { - el.textContent = el.getAttribute(`data-${lang}`); + this.init(); + } + + /** + * 初始化应用 + */ + init() { + this.uiManager.showLoading(); + + // 首先尝试GitHub API + this.apiClient.fetchReleases('github') + .then(releases => { + this.uiManager.displayReleases(releases, 'github'); + }) + .catch(() => { + // GitHub失败时尝试Gitea + return this.apiClient.fetchReleases('gitea') + .then(releases => { + this.uiManager.displayReleases(releases, 'gitea'); + }); + }) + .catch(error => { + console.error('获取发布信息失败:', error); + this.uiManager.showError(this.i18nMessages.getMessage('fetchError', this.i18nMessages.getCurrentLang())); + }); + + // 监听语言变化事件 + document.addEventListener('languageChanged', () => this.updateUI()); + } + + /** + * 更新UI元素(当语言变化时) + */ + updateUI() { + const elementsToUpdate = document.querySelectorAll('[data-en][data-zh]'); + const currentLang = this.i18nMessages.getCurrentLang(); + + elementsToUpdate.forEach(element => { + const text = element.getAttribute(`data-${currentLang}`); + if (text) { + element.textContent = text; } }); } -}); \ No newline at end of file +} + +// 当DOM加载完成时初始化应用 +document.addEventListener('DOMContentLoaded', () => { + new ChangelogApp(); +}); \ No newline at end of file diff --git a/docs/js/script.js b/docs/js/script.js index be71aaf..e06c96e 100644 --- a/docs/js/script.js +++ b/docs/js/script.js @@ -1,136 +1,443 @@ /** * VoidRaft - Website Script */ -document.addEventListener('DOMContentLoaded', () => { - // Initialize components - initThemeToggle(); - initLanguageToggle(); - initCardEffects(); - - // Console branding - console.log('%c VoidRaft', 'color: #ff006e; font-size: 20px; font-family: "Space Mono", monospace;'); - console.log('%c An elegant text snippet recording tool designed for developers.', 'color: #073B4C; font-family: "Space Mono", monospace;'); -}); /** - * Initialize theme toggle functionality + * 主题管理类 */ -function initThemeToggle() { - const themeToggle = document.getElementById('theme-toggle'); - if (!themeToggle) return; - - // Get initial theme from local storage or system preference - const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); - const savedTheme = localStorage.getItem('theme'); - const theme = savedTheme || (prefersDarkScheme.matches ? 'dark' : 'light'); - - // Apply initial theme - setTheme(theme); - - // Add click handler - themeToggle.addEventListener('click', () => { +class ThemeManager { + constructor() { + this.themeToggle = document.getElementById('theme-toggle'); + this.currentTheme = this.getInitialTheme(); + this.init(); + } + + /** + * 获取初始主题 + */ + getInitialTheme() { + const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); + const savedTheme = localStorage.getItem('theme'); + return savedTheme || (prefersDarkScheme.matches ? 'dark' : 'light'); + } + + /** + * 初始化主题管理器 + */ + init() { + if (!this.themeToggle) return; + + this.setTheme(this.currentTheme); + this.bindEvents(); + } + + /** + * 绑定事件 + */ + bindEvents() { + this.themeToggle.addEventListener('click', () => { + this.toggleTheme(); + }); + } + + /** + * 切换主题 + */ + toggleTheme() { document.body.classList.add('theme-transition'); - const currentTheme = document.body.classList.contains('theme-dark') ? 'dark' : 'light'; - const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark'; + this.setTheme(newTheme); + this.saveTheme(newTheme); - setTheme(newTheme); - localStorage.setItem('theme', newTheme); - - // Remove transition class after animation completes setTimeout(() => document.body.classList.remove('theme-transition'), 300); - }); + } + + /** + * 设置主题 + * @param {string} theme - 'dark' 或 'light' + */ + setTheme(theme) { + this.currentTheme = theme; + const isDark = theme === 'dark'; + + document.body.classList.toggle('theme-dark', isDark); + document.body.classList.toggle('theme-light', !isDark); + + this.updateToggleIcon(isDark); + } + + /** + * 更新切换按钮图标 + * @param {boolean} isDark - 是否为暗色主题 + */ + updateToggleIcon(isDark) { + if (this.themeToggle) { + const icon = this.themeToggle.querySelector('i'); + if (icon) { + icon.className = isDark ? 'fas fa-sun' : 'fas fa-moon'; + } + } + } + + /** + * 保存主题到本地存储 + * @param {string} theme - 主题名称 + */ + saveTheme(theme) { + localStorage.setItem('theme', theme); + } } /** - * Set theme to dark or light - * @param {string} theme - 'dark' or 'light' + * 语言管理类 */ -function setTheme(theme) { - const isDark = theme === 'dark'; - - // Update body class - document.body.classList.toggle('theme-dark', isDark); - document.body.classList.toggle('theme-light', !isDark); - - // Update toggle icon - const themeToggle = document.getElementById('theme-toggle'); - if (themeToggle) { - const icon = themeToggle.querySelector('i'); - if (icon) { - icon.className = isDark ? 'fas fa-sun' : 'fas fa-moon'; +class LanguageManager { + constructor() { + this.langToggle = document.getElementById('lang-toggle'); + this.currentLang = this.getInitialLanguage(); + this.init(); + } + + /** + * 获取初始语言 + */ + getInitialLanguage() { + const urlParams = new URLSearchParams(window.location.search); + const urlLang = urlParams.get('lang'); + const savedLang = localStorage.getItem('lang'); + const browserLang = navigator.language.startsWith('zh') ? 'zh' : 'en'; + return urlLang || savedLang || browserLang; + } + + /** + * 初始化语言管理器 + */ + init() { + if (!this.langToggle) return; + + window.currentLang = this.currentLang; + this.setLanguage(this.currentLang); + this.bindEvents(); + } + + /** + * 绑定事件 + */ + bindEvents() { + this.langToggle.addEventListener('click', () => { + this.toggleLanguage(); + }); + } + + /** + * 切换语言 + */ + toggleLanguage() { + document.body.classList.add('lang-transition'); + + const newLang = this.currentLang === 'zh' ? 'en' : 'zh'; + this.setLanguage(newLang); + this.saveLanguage(newLang); + this.updateURL(newLang); + this.notifyLanguageChange(newLang); + + setTimeout(() => document.body.classList.remove('lang-transition'), 300); + } + + /** + * 设置页面语言 + * @param {string} lang - 'zh' 或 'en' + */ + setLanguage(lang) { + this.currentLang = lang; + window.currentLang = lang; + + this.updatePageElements(lang); + this.updateHTMLLang(lang); + this.updateToggleButton(lang); + } + + /** + * 更新页面元素文本 + * @param {string} lang - 语言代码 + */ + updatePageElements(lang) { + document.querySelectorAll('[data-zh][data-en]').forEach(el => { + el.textContent = el.getAttribute(`data-${lang}`); + }); + } + + /** + * 更新HTML语言属性 + * @param {string} lang - 语言代码 + */ + updateHTMLLang(lang) { + document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en'; + } + + /** + * 更新切换按钮文本 + * @param {string} lang - 语言代码 + */ + updateToggleButton(lang) { + if (this.langToggle) { + const text = lang === 'zh' ? 'EN/中' : '中/EN'; + this.langToggle.innerHTML = ` ${text}`; + } + } + + /** + * 保存语言到本地存储 + * @param {string} lang - 语言代码 + */ + saveLanguage(lang) { + localStorage.setItem('lang', lang); + } + + /** + * 更新URL参数 + * @param {string} lang - 语言代码 + */ + updateURL(lang) { + const newUrl = new URL(window.location); + if (lang === 'zh') { + newUrl.searchParams.set('lang', 'zh'); + } else { + newUrl.searchParams.delete('lang'); + } + window.history.replaceState({}, '', newUrl); + } + + /** + * 通知语言变更 + * @param {string} lang - 语言代码 + */ + notifyLanguageChange(lang) { + window.dispatchEvent(new CustomEvent('languageChanged', { detail: { lang } })); + } + + /** + * 获取当前语言 + */ + getCurrentLanguage() { + return this.currentLang; + } +} + +/** + * SEO管理类 + */ +class SEOManager { + constructor(languageManager) { + this.languageManager = languageManager; + this.metaTexts = { + en: { + description: 'VoidRaft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.', + title: 'VoidRaft - An elegant text snippet recording tool designed for developers.', + ogTitle: 'VoidRaft - An elegant text snippet recording tool designed for developers' + }, + zh: { + description: 'VoidRaft 是专为开发者打造的优雅文本片段记录工具。支持多语言代码块、语法高亮、代码格式化、自定义主题等功能。', + title: 'VoidRaft - 专为开发者打造的优雅文本片段记录工具', + ogTitle: 'VoidRaft - 专为开发者打造的优雅文本片段记录工具' + } + }; + this.init(); + } + + /** + * 初始化SEO管理器 + */ + init() { + this.bindEvents(); + this.updateMetaTags(this.languageManager.getCurrentLanguage()); + } + + /** + * 绑定事件 + */ + bindEvents() { + window.addEventListener('languageChanged', (event) => { + this.updateMetaTags(event.detail.lang); + }); + } + + /** + * 更新SEO元标签 + * @param {string} lang - 当前语言 + */ + updateMetaTags(lang) { + const texts = this.metaTexts[lang]; + + this.updateMetaDescription(texts.description); + this.updateOpenGraphTags(texts.ogTitle, texts.description); + this.updateTwitterCardTags(texts.ogTitle, texts.description); + this.updatePageTitle(texts.title); + } + + /** + * 更新meta描述 + * @param {string} description - 描述文本 + */ + updateMetaDescription(description) { + const metaDesc = document.querySelector('meta[name="description"]'); + if (metaDesc) { + metaDesc.content = description; + } + } + + /** + * 更新Open Graph标签 + * @param {string} title - 标题 + * @param {string} description - 描述 + */ + updateOpenGraphTags(title, description) { + const ogTitle = document.querySelector('meta[property="og:title"]'); + const ogDesc = document.querySelector('meta[property="og:description"]'); + + if (ogTitle) ogTitle.content = title; + if (ogDesc) ogDesc.content = description; + } + + /** + * 更新Twitter Card标签 + * @param {string} title - 标题 + * @param {string} description - 描述 + */ + updateTwitterCardTags(title, description) { + const twitterTitle = document.querySelector('meta[property="twitter:title"]'); + const twitterDesc = document.querySelector('meta[property="twitter:description"]'); + + if (twitterTitle) twitterTitle.content = title; + if (twitterDesc) twitterDesc.content = description; + } + + /** + * 更新页面标题 + * @param {string} title - 标题 + */ + updatePageTitle(title) { + document.title = title; + } +} + +/** + * UI效果管理类 + */ +class UIEffects { + constructor() { + this.init(); + } + + /** + * 初始化UI效果 + */ + init() { + this.initCardEffects(); + } + + /** + * 初始化卡片悬停效果 + */ + initCardEffects() { + const cards = document.querySelectorAll('.feature-card'); + + cards.forEach(card => { + card.addEventListener('mouseenter', () => { + this.animateCardHover(card, true); + }); + + card.addEventListener('mouseleave', () => { + this.animateCardHover(card, false); + }); + }); + } + + /** + * 卡片悬停动画 + * @param {Element} card - 卡片元素 + * @param {boolean} isHover - 是否悬停 + */ + animateCardHover(card, isHover) { + if (isHover) { + card.style.transform = 'translateY(-8px)'; + card.style.boxShadow = '7px 7px 0 var(--shadow-color)'; + } else { + card.style.transform = 'translateY(0)'; + card.style.boxShadow = '5px 5px 0 var(--shadow-color)'; } } } /** - * Initialize language toggle functionality + * VoidRaft主应用类 */ -function initLanguageToggle() { - const langToggle = document.getElementById('lang-toggle'); - if (!langToggle) return; - - // Get initial language from local storage or default to English - const savedLang = localStorage.getItem('lang'); - const lang = savedLang || 'en'; - - // Set current language and apply it - window.currentLang = lang; - setLanguage(lang); - - // Add click handler - langToggle.addEventListener('click', () => { - document.body.classList.add('lang-transition'); - - const newLang = window.currentLang === 'zh' ? 'en' : 'zh'; - setLanguage(newLang); - window.currentLang = newLang; - localStorage.setItem('lang', newLang); - - // Notify other components about language change - window.dispatchEvent(new CustomEvent('languageChanged', { detail: { lang: newLang } })); - - // Remove transition class after animation completes - setTimeout(() => document.body.classList.remove('lang-transition'), 300); - }); -} +class VoidRaftApp { + constructor() { + this.themeManager = null; + this.languageManager = null; + this.seoManager = null; + this.uiEffects = null; + this.init(); + } -/** - * Set page language - * @param {string} lang - 'zh' or 'en' - */ -function setLanguage(lang) { - // Update all elements with data-zh and data-en attributes - document.querySelectorAll('[data-zh][data-en]').forEach(el => { - el.textContent = el.getAttribute(`data-${lang}`); - }); - - // Update HTML language attribute - document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en'; - - // Update toggle button text - const langToggle = document.getElementById('lang-toggle'); - if (langToggle) { - const text = lang === 'zh' ? 'EN/中' : '中/EN'; - langToggle.innerHTML = ` ${text}`; + /** + * 初始化应用 + */ + init() { + this.initializeManagers(); + this.showConsoleBranding(); + } + + /** + * 初始化各个管理器 + */ + initializeManagers() { + this.themeManager = new ThemeManager(); + this.languageManager = new LanguageManager(); + this.seoManager = new SEOManager(this.languageManager); + this.uiEffects = new UIEffects(); + } + + /** + * 显示控制台品牌信息 + */ + showConsoleBranding() { + console.log('%c VoidRaft', 'color: #ff006e; font-size: 20px; font-family: "Space Mono", monospace;'); + console.log('%c An elegant text snippet recording tool designed for developers.', 'color: #073B4C; font-family: "Space Mono", monospace;'); + } + + /** + * 获取主题管理器 + */ + getThemeManager() { + return this.themeManager; + } + + /** + * 获取语言管理器 + */ + getLanguageManager() { + return this.languageManager; + } + + /** + * 获取SEO管理器 + */ + getSEOManager() { + return this.seoManager; + } + + /** + * 获取UI效果管理器 + */ + getUIEffects() { + return this.uiEffects; } } -/** - * Initialize card hover effects - */ -function initCardEffects() { - const cards = document.querySelectorAll('.feature-card'); - - cards.forEach(card => { - card.addEventListener('mouseenter', () => { - card.style.transform = 'translateY(-8px)'; - card.style.boxShadow = '7px 7px 0 var(--shadow-color)'; - }); - - card.addEventListener('mouseleave', () => { - card.style.transform = 'translateY(0)'; - card.style.boxShadow = '5px 5px 0 var(--shadow-color)'; - }); - }); -} \ No newline at end of file +// 当DOM加载完成时初始化应用 +document.addEventListener('DOMContentLoaded', () => { + window.voidRaftApp = new VoidRaftApp(); +}); \ No newline at end of file diff --git a/go.mod b/go.mod index adb40e7..0772b0a 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,12 @@ require ( github.com/knadh/koanf/parsers/json v1.0.0 github.com/knadh/koanf/providers/file v1.2.0 github.com/knadh/koanf/providers/structs v1.0.0 - github.com/knadh/koanf/v2 v2.2.1 + github.com/knadh/koanf/v2 v2.2.2 github.com/robertkrimen/otto v0.5.1 - github.com/wailsapp/wails/v3 v3.0.0-alpha.10 - golang.org/x/net v0.41.0 - golang.org/x/sys v0.33.0 - golang.org/x/text v0.26.0 + github.com/wailsapp/wails/v3 v3.0.0-alpha.11 + golang.org/x/net v0.42.0 + golang.org/x/sys v0.34.0 + golang.org/x/text v0.27.0 ) require ( @@ -71,8 +71,8 @@ require ( github.com/wailsapp/mimetype v1.4.1 // indirect github.com/xanzy/go-gitlab v0.115.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/time v0.12.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 713870c..8a84fd3 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,8 @@ github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmY github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4= github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w= -github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE= -github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY= +github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A= +github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -164,8 +164,8 @@ github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2 github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v3 v3.0.0-alpha.10 h1:SrxwhkBcdtaSxQ/zujJuifJN5q8hxyba5UKv5oaM/X4= -github.com/wailsapp/wails/v3 v3.0.0-alpha.10/go.mod h1:4LCCW7s9e4PuSmu7l9OTvfWIGMO8TaSiftSeR5NpBIc= +github.com/wailsapp/wails/v3 v3.0.0-alpha.11 h1:MYZk2ci8fBd3loWanLzAYgAFcmq4qTRFyNggVqHMaHY= +github.com/wailsapp/wails/v3 v3.0.0-alpha.11/go.mod h1:4LCCW7s9e4PuSmu7l9OTvfWIGMO8TaSiftSeR5NpBIc= github.com/xanzy/go-gitlab v0.115.0 h1:6DmtItNcVe+At/liXSgfE/DZNZrGfalQmBRmOcJjOn8= github.com/xanzy/go-gitlab v0.115.0/go.mod h1:5XCDtM7AM6WMKmfDdOiEpyRWUqui2iS9ILfvCZ2gJ5M= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -174,24 +174,24 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -203,21 +203,21 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=