Deployed 115391b with MkDocs version: 1.6.1

This commit is contained in:
github-actions[bot]
2026-02-19 20:22:54 +00:00
commit 2e4894f1a4
270 changed files with 82192 additions and 0 deletions

145
assets/js/common.js Normal file
View File

@@ -0,0 +1,145 @@
const __p = typeof window !== 'undefined' && window.location && window.location.pathname ? window.location.pathname : ''
const __isZhHant = __p.includes('/zh-Hant')
const __isEn = __p.includes('/en')
// 防抖
function debounce(func, wait) {
let timeout
return function () {
const context = this
const args = arguments
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(context, args), wait)
}
}
// 组件管理系统(每个组件都是一个 Vue 实例)
const ComponentSystem = {
// 组件状态数据
components: {},
// 注册组件
register: function (componentId, componentDef) {
// 为每个组件创建自己的状态
this.components[componentId] = {
id: componentId,
instance: null,
isInitializing: false,
lastInitTime: 0,
def: componentDef,
debouncedInit: null,
app: null,
}
// 创建组件初始化函数
const initFunc = function () {
const component = ComponentSystem.components[componentId]
// 如果正在初始化或者距离上次初始化时间太短,则跳过
const now = Date.now()
if (component.isInitializing || now - component.lastInitTime < 1000) {
return
}
// 使用缓存,如果没有则查询
if (!component.instance) {
component.instance = document.getElementById(componentId)
}
// 如果找不到容器,不执行后续操作
if (!component.instance) {
return
}
// 如果组件已经初始化过且DOM没有变化则跳过
if (component.instance.hasAttribute('data-initialized')) {
return
}
// console.log(`找到组件 ${componentId} 容器,开始初始化`)
component.isInitializing = true
component.lastInitTime = now
try {
if (component.app && typeof component.app.unmount === 'function') {
try {
component.app.unmount()
} catch (err) {
console.error('组件卸载失败', err)
}
component.app = null
}
// 清空容器内容,防止重复初始化
while (component.instance.firstChild) {
component.instance.removeChild(component.instance.firstChild)
}
// 确保容器有适当的尺寸
if (!component.instance.style.width) {
component.instance.style.width = '100%'
}
// 确保 Vue 和 TDesign 已加载
if (typeof Vue !== 'undefined' && typeof TDesign !== 'undefined') {
// 创建 Vue 应用
const App = Vue.createApp(component.def)
// 注册 TDesign 组件
App.use(TDesign.default)
// 挂载应用
App.mount(component.instance)
// 保存 app 引用以便后续卸载
component.app = App
// console.log(`组件 ${componentId} 初始化成功`)
// 标记组件已初始化
component.instance.setAttribute('data-initialized', 'true')
// 立即更新主题
updateTDesignGlobalTheme()
} else if (typeof Vue !== 'undefined') {
console.error('Vue 未找到')
const errorDiv = document.createElement('div')
errorDiv.innerHTML = `<div class="admonition failure"><p class="admonition-title">组件 ${componentId} 加载失败,请检查 Vue 是否存在!</p></div>`
component.instance.appendChild(errorDiv)
} else if (typeof TDesign !== 'undefined') {
console.error('TDesign UI 未找到')
const errorDiv = document.createElement('div')
errorDiv.innerHTML = `<div class="admonition failure"><p class="admonition-title">组件 ${componentId} 加载失败,请检查 TDesign UI 是否存在!</p></div>`
component.instance.appendChild(errorDiv)
}
} catch (error) {
console.error(`组件 ${componentId} 初始化时发生错误:${error}`)
} finally {
component.isInitializing = false
}
}
// 为每个组件创建防抖初始化函数
this.components[componentId].debouncedInit = debounce(initFunc, 300)
return this.components[componentId].debouncedInit
},
// 初始化所有组件
initAll: function () {
Object.values(this.components).forEach((component) => {
if (component.debouncedInit) {
component.debouncedInit()
}
})
},
// 重新初始化组件
reinitialize: function (componentId) {
const component = this.components[componentId]
if (component) {
component.instance = document.getElementById(componentId)
if (component.instance) {
if (component.app && typeof component.app.unmount === 'function') {
try {
component.app.unmount()
} catch (err) {
console.error('组件初始化失败', err)
}
component.app = null
}
component.instance.removeAttribute('data-initialized')
setTimeout(component.debouncedInit, 300)
}
}
},
// 重新初始化所有组件
reinitializeAll: function () {
Object.keys(this.components).forEach((id) => {
this.reinitialize(id)
})
},
}

6
assets/js/component.js Normal file
View File

@@ -0,0 +1,6 @@
// 使用 mkdocs-material 与第三方 JavaScript 库集成的方法
document$.subscribe(function () {
ComponentSystem.reinitializeAll()
// Orama AI Search
loadOramaWebComponent()
})

View File

@@ -0,0 +1,909 @@
// 表格数据
const mirrorsTableData = [
{
name: __isZhHant ? '阿里雲' : __isEn ? 'Alibaba Cloud' : '阿里云',
officialName: __isZhHant ? '阿里巴巴開源鏡像站' : __isEn ? 'Alibaba Cloud Open Source Mirror' : '阿里巴巴开源镜像站',
icon: 'alibabacloud.svg',
iconStyle: { verticalAlign: '-0.2em' },
url: 'https://mirrors.aliyun.com',
domain: 'mirrors.aliyun.com',
ipv6: false,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: 'incompatible',
almalinux: true,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: true,
openkylin: true,
alpine: true,
armbian: true,
proxmox: false,
linuxmint: true,
gentoo: true,
nix_channels: false,
raspberrypi: true,
manjaro: true,
endeavouros: false,
},
{
name: __isZhHant ? '騰訊雲' : __isEn ? 'Tencent Cloud' : '腾讯云',
officialName: __isZhHant ? '騰訊軟體源' : __isEn ? 'Tencent Software Repository' : '腾讯软件源',
icon: 'tencentcloud.ico',
iconStyle: { verticalAlign: '-0.25em' },
url: 'https://mirrors.tencent.com',
domain: 'mirrors.tencent.com',
ipv6: false,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: false,
rocky: true,
almalinux: true,
epel: true,
fedora: true,
opencloudos: true,
openeuler: true,
anolis: false,
openkylin: false,
alpine: true,
armbian: false,
proxmox: false,
linuxmint: true,
gentoo: true,
nix_channels: false,
raspberrypi: false,
manjaro: false,
endeavouros: false,
},
{
name: __isZhHant ? '華為雲' : __isEn ? 'Huawei Cloud' : '华为云',
officialName: __isZhHant ? '華為開源鏡像站' : __isEn ? 'Huawei Open Source Mirror' : '华为开源镜像站',
icon: 'huaweicloud.ico',
iconStyle: { verticalAlign: '-0.15em' },
url: 'https://mirrors.huaweicloud.com',
domain: 'mirrors.huaweicloud.com',
ipv6: false,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: 'incompatible',
almalinux: true,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: false,
alpine: true,
armbian: false,
proxmox: false,
linuxmint: true,
gentoo: true,
nix_channels: false,
raspberrypi: false,
manjaro: true,
endeavouros: false,
},
{
name: __isZhHant ? '移動雲' : __isEn ? 'China Mobile Cloud' : '移动云 ',
officialName: __isZhHant ? '移動雲開源鏡像站' : __isEn ? 'China Mobile Cloud Open Source Mirror' : '移动云开源镜像站',
icon: 'cmecloud.svg',
iconStyle: { verticalAlign: '-0.2em' },
url: 'https://mirrors.cmecloud.cn',
domain: 'mirrors.cmecloud.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: false,
opensuse: true,
archlinux: false,
kali: false,
deepin: false,
rocky: true,
almalinux: false,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: true,
openkylin: false,
alpine: false,
armbian: false,
proxmox: false,
linuxmint: false,
gentoo: false,
nix_channels: false,
raspberrypi: false,
manjaro: false,
endeavouros: false,
},
{
name: __isZhHant ? '天翼雲' : __isEn ? 'China Telecom Cloud' : '天翼云',
officialName: __isZhHant ? '天翼雲開源鏡像站' : __isEn ? 'China Telecom Cloud Open Source Mirror' : '天翼云开源镜像站',
icon: 'ctyun.ico',
iconStyle: { verticalAlign: '-0.2em' },
url: 'https://mirrors.ctyun.cn',
domain: 'mirrors.ctyun.cn',
ipv6: false,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: false,
archlinux: false,
kali: false,
deepin: false,
rocky: true,
almalinux: false,
epel: true,
fedora: false,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: false,
alpine: false,
armbian: false,
proxmox: false,
linuxmint: false,
gentoo: false,
nix_channels: false,
raspberrypi: false,
manjaro: false,
endeavouros: false,
},
{
name: __isZhHant ? '網易' : __isEn ? 'NetEase' : '网易',
officialName: __isZhHant ? '網易開源鏡像站' : __isEn ? 'NetEase Open Source Mirror' : '网易开源镜像站',
icon: '163.ico',
iconStyle: { verticalAlign: '-0.15em' },
url: 'https://mirrors.163.com',
domain: 'mirrors.163.com',
ipv6: false,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: false,
deepin: true,
rocky: true,
almalinux: false,
epel: false,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: true,
alpine: false,
armbian: false,
proxmox: false,
linuxmint: true,
gentoo: true,
nix_channels: false,
raspberrypi: false,
manjaro: false,
endeavouros: false,
},
{
name: __isZhHant ? '火山引擎' : __isEn ? 'Volcengine' : '火山引擎',
officialName: __isZhHant ? '火山引擎開源軟體鏡像站' : __isEn ? 'Volcengine Open Source Software Mirror' : '火山引擎开源软件镜像站',
icon: 'volcengine.svg',
iconStyle: { verticalAlign: '-0.25em' },
url: 'https://developer.volcengine.com/mirror',
domain: 'mirrors.volces.com',
ipv6: false,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: false,
kali: true,
deepin: true,
rocky: 'incompatible',
almalinux: true,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: false,
alpine: true,
armbian: false,
proxmox: false,
linuxmint: false,
gentoo: false,
nix_channels: false,
raspberrypi: false,
manjaro: false,
endeavouros: false,
},
{
name: __isZhHant ? '清華大學' : __isEn ? 'Tsinghua University' : '清华大学',
officialName: __isZhHant ? '清華大學開源軟體鏡像站' : __isEn ? 'Tsinghua University Open Source Mirror' : '清华大学开源软件镜像站',
icon: 'tsinghua.png',
iconStyle: { verticalAlign: '-0.3em' },
url: 'https://mirrors.tuna.tsinghua.edu.cn',
domain: 'mirrors.tuna.tsinghua.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: false,
almalinux: false,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: false,
alpine: true,
armbian: true,
proxmox: true,
linuxmint: true,
gentoo: true,
nix_channels: true,
raspberrypi: true,
manjaro: true,
endeavouros: true,
},
{
name: __isZhHant ? '北京大學' : __isEn ? 'Peking University' : '北京大学',
officialName: __isZhHant ? '北京大學開源鏡像站' : __isEn ? 'Peking University Open Source Mirror' : '北京大学开源镜像站',
icon: 'pku.ico',
iconStyle: { verticalAlign: '-0.2em' },
url: 'https://mirrors.pku.edu.cn/Mirrors',
domain: 'mirrors.pku.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: false,
deepin: false,
rocky: true,
almalinux: true,
epel: true,
fedora: false,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: false,
alpine: false,
armbian: false,
proxmox: false,
linuxmint: false,
gentoo: false,
nix_channels: false,
raspberrypi: false,
manjaro: true,
endeavouros: false,
},
{
name: __isZhHant ? '浙江大學' : __isEn ? 'Zhejiang University' : '浙江大学',
officialName: __isZhHant ? '浙江大學鏡像站' : __isEn ? 'Zhejiang University Mirror Site' : '浙江大学镜像站',
icon: 'zju.ico',
iconStyle: { verticalAlign: '-0.15em' },
url: 'https://mirrors.zju.edu.cn',
domain: 'mirrors.zju.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: true,
almalinux: true,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: true,
openkylin: false,
alpine: true,
armbian: false,
proxmox: false,
linuxmint: true,
gentoo: true,
nix_channels: false,
raspberrypi: false,
manjaro: true,
endeavouros: false,
},
{
name: __isZhHant ? '南京大學' : __isEn ? 'Nanjing University' : '南京大学',
officialName: __isZhHant ? '南京大學開源鏡像站' : __isEn ? 'Nanjing University Open Source Mirror' : '南京大学开源镜像站',
icon: 'nju.ico',
iconStyle: { verticalAlign: '-0.3em' },
url: 'https://mirrors.nju.edu.cn',
domain: 'mirrors.nju.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: true,
almalinux: true,
epel: true,
fedora: true,
opencloudos: true,
openeuler: true,
anolis: true,
openkylin: true,
alpine: true,
armbian: true,
proxmox: true,
linuxmint: true,
gentoo: true,
nix_channels: true,
raspberrypi: true,
manjaro: true,
endeavouros: true,
},
{
name: __isZhHant ? '蘭州大學' : __isEn ? 'Lanzhou University' : '兰州大学',
officialName: __isZhHant ? '蘭州大學開源軟體鏡像站' : __isEn ? 'Lanzhou University Open Source Software Mirror' : '兰州大学开源软件镜像站',
icon: 'lzu.png',
iconStyle: { verticalAlign: '-0.25em' },
url: 'https://mirror.lzu.edu.cn',
domain: 'mirror.lzu.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: true,
almalinux: true,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: true,
alpine: true,
armbian: false,
proxmox: false,
linuxmint: true,
gentoo: true,
nix_channels: false,
raspberrypi: false,
manjaro: true,
endeavouros: false,
},
{
name: __isZhHant ? '上海交通大學' : __isEn ? 'Shanghai Jiao Tong University' : '上海交通大学',
officialName: __isZhHant ? '上海交通大學思源鏡像站' : __isEn ? 'Shanghai Jiao Tong University Siyuan Mirror' : '上海交通大学思源镜像站',
icon: 'sjtu.ico',
iconStyle: { verticalAlign: '-0.15em' },
url: 'https://mirror.sjtu.edu.cn',
domain: 'mirror.sjtu.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: true,
almalinux: true,
epel: 'incompatible',
fedora: 'incompatible',
opencloudos: true,
openeuler: true,
anolis: false,
openkylin: true,
alpine: true,
armbian: true,
proxmox: false,
linuxmint: true,
gentoo: true,
nix_channels: false,
raspberrypi: true,
manjaro: true,
endeavouros: true,
},
{
name: __isZhHant ? '重慶郵電大學' : __isEn ? 'Chongqing University of Posts and Telecommunications' : '重庆邮电大学',
officialName: __isZhHant ? '重慶郵電大學開源鏡像站' : __isEn ? 'Chongqing University of Posts and Telecommunications Open Source Mirror' : '重庆邮电大学开源镜像站',
icon: 'cqupt.ico',
iconStyle: {},
url: 'https://mirrors.cqupt.edu.cn',
domain: 'mirrors.cqupt.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: false,
almalinux: true,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: false,
alpine: true,
armbian: true,
proxmox: true,
linuxmint: false,
gentoo: false,
nix_channels: true,
raspberrypi: true,
manjaro: true,
endeavouros: true,
},
{
name: __isZhHant ? '中國科學技術大學' : __isEn ? 'University of Science and Technology of China' : '中国科学技术大学',
officialName: __isZhHant ? '中國科學技術大學開源軟體鏡像站' : __isEn ? 'University of Science and Technology of China Open Source Software Mirror' : '中国科学技术大学开源软件镜像站',
icon: 'ustc.png',
iconStyle: { verticalAlign: '-0.2em' },
url: 'https://mirrors.ustc.edu.cn',
domain: 'mirrors.ustc.edu.cn',
ipv6: true,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: true,
almalinux: false,
epel: true,
fedora: true,
opencloudos: false,
openeuler: true,
anolis: false,
openkylin: false,
alpine: true,
armbian: true,
proxmox: true,
linuxmint: true,
gentoo: true,
nix_channels: true,
raspberrypi: true,
manjaro: true,
endeavouros: false,
},
{
name: __isZhHant ? '中國科學院軟體研究所' : __isEn ? 'Institute of Software, Chinese Academy of Sciences (ISCAS)' : '中国科学院软件研究所',
officialName: __isZhHant ? 'ISCAS 開源鏡像站' : __isEn ? 'ISCAS Open Source Mirror' : 'ISCAS 开源镜像站',
icon: 'iscas.png',
iconStyle: { verticalAlign: '-0.25em' },
url: 'https://mirror.iscas.ac.cn',
domain: 'mirror.iscas.ac.cn',
ipv6: false,
debian: true,
ubuntu: true,
centos: true,
centos_stream: true,
opensuse: true,
archlinux: true,
kali: true,
deepin: true,
rocky: true,
almalinux: true,
epel: true,
fedora: true,
opencloudos: true,
openeuler: true,
anolis: false,
openkylin: true,
alpine: true,
armbian: true,
proxmox: true,
linuxmint: true,
gentoo: true,
nix_channels: true,
raspberrypi: true,
manjaro: true,
endeavouros: true,
},
]
// 表格列配置
const mirrorsTableColumns = [
{
colKey: 'name',
title: __isZhHant ? '鏡像站' : __isEn ? 'Mirror Site' : '镜像站',
align: 'left',
width: '180',
fixed: 'left',
},
{
colKey: 'ipv6',
title: 'IPv6',
align: 'center',
width: '70',
tooltip: 'IPv6 ' + (__isZhHant ? '(網際協定第6版)' : __isEn ? '(Internet Protocol version 6)' : '(网际协议第6版)'),
},
{
colKey: 'epel',
title: 'EPEL',
align: 'center',
width: '90',
tooltip: 'EPEL (Extra Packages for Enterprise Linux) ' + (__isZhHant ? '是由 Fedora 組織維護的一個附加軟體包倉庫,它主要適用於除 Fedora 作業系統以外的紅帽系 Linux 發行版' : __isEn ? 'is an additional package repository maintained by the Fedora organization. It is mainly applicable to Red Hat Linux distributions other than the Fedora operating system.' : '是由 Fedora 组织维护的一个附加软件包仓库,它主要适用于除 Fedora 操作系统以外的红帽系 Linux 发行版'),
},
{
colKey: 'debian',
title: 'Debian',
align: 'center',
width: '80',
},
{
colKey: 'ubuntu',
title: 'Ubuntu',
align: 'center',
width: '80',
},
{
colKey: 'centos',
title: 'CentOS',
align: 'center',
width: '80',
},
{
colKey: 'centos_stream',
title: 'CentOS Stream',
align: 'center',
width: '150',
},
{
colKey: 'opensuse',
title: 'openSUSE',
align: 'center',
width: '110',
},
{
colKey: 'archlinux',
title: 'Arch Linux',
align: 'center',
width: '120',
},
{
colKey: 'manjaro',
title: 'Manjaro',
align: 'center',
width: '100',
},
{
colKey: 'kali',
title: 'Kali Linux',
align: 'center',
width: '110',
},
{
colKey: 'armbian',
title: 'Armbian',
align: 'center',
width: '100',
},
{
colKey: 'deepin',
title: 'Deepin',
align: 'center',
width: '90',
},
{
colKey: 'raspberrypi',
title: 'Raspberry Pi OS',
align: 'center',
width: '150',
},
{
colKey: 'linuxmint',
title: 'Linux Mint',
align: 'center',
width: '120',
},
{
colKey: 'proxmox',
title: 'Proxmox VE',
align: 'center',
width: '120',
},
{
colKey: 'fedora',
title: 'Fedora',
align: 'center',
width: '90',
},
{
colKey: 'rocky',
title: 'Rocky Linux',
align: 'center',
width: '120',
},
{
colKey: 'almalinux',
title: 'AlmaLinux',
align: 'center',
width: '110',
},
{
colKey: 'opencloudos',
title: 'OpenCloudOS',
align: 'center',
width: '140',
},
{
colKey: 'openeuler',
title: 'openEuler',
align: 'center',
width: '110',
},
{
colKey: 'anolis',
title: 'Anolis OS',
align: 'center',
width: '110',
},
{
colKey: 'openkylin',
title: 'openKylin',
align: 'center',
width: '110',
},
{
colKey: 'alpine',
title: 'Alpine Linux',
align: 'center',
width: '130',
},
{
colKey: 'gentoo',
title: 'Gentoo',
align: 'center',
width: '90',
},
{
colKey: 'nix_channels',
title: 'NixOS',
align: 'center',
width: '80',
},
{
colKey: 'endeavouros',
title: 'EndeavourOS',
align: 'center',
width: '140',
},
].map((item) => {
if (item.colKey !== 'name') {
const labelSupported = __isZhHant ? '支持' : __isEn ? 'Supported' : '支持'
const labelUnsupported = __isZhHant ? '不支持' : __isEn ? 'Unsupported' : '不支持'
const labelIncompatible = __isZhHant ? '不兼容' : __isEn ? 'Incompatible' : '不兼容'
item.filter = {
label: item.title || item.colKey,
type: 'single',
list: [
{ label: labelSupported, value: 'supported' },
{ label: labelUnsupported, value: 'unsupported' },
{ label: labelIncompatible, value: 'incompatible' },
],
confirmEvents: ['onChange'],
filterMethod(value, row) {
try {
const key = item.colKey
const cell = row && Object.prototype.hasOwnProperty.call(row, key) ? row[key] : undefined
if (value === 'supported') return cell === true
if (value === 'unsupported') return cell === false
if (value === 'incompatible') return cell === 'incompatible'
return false
} catch (e) {
return false
}
},
}
item.filterMethod = function (value, row) {
try {
const key = item.colKey
const cell = row && Object.prototype.hasOwnProperty.call(row, key) ? row[key] : undefined
if (value === 'supported') return cell === true
if (value === 'unsupported') return cell === false
if (value === 'incompatible') return cell === 'incompatible'
return false
} catch (e) {
return false
}
}
}
return item
})
const mirrorsTableFilterSelectOptions = [
{ label: __isZhHant ? '全選' : __isEn ? 'Select All' : '全选', checkAll: true },
{
value: 'ipv6',
label: 'IPv6',
},
{
group: 'Debian',
children: [
{
value: 'debian',
label: 'Debian',
iconName: 'debian.svg',
},
{
value: 'ubuntu',
label: 'Ubuntu',
iconName: 'ubuntu.svg',
},
{
value: 'kali',
label: 'Kali Linux',
iconName: 'kali-linux.png',
},
{
value: 'armbian',
label: 'Armbian',
iconName: 'armbian.png',
},
{
value: 'raspberrypi',
label: 'Raspberry Pi OS',
iconName: 'raspberry-pi.png',
},
{
value: 'openkylin',
label: 'openKylin',
iconName: 'openkylin.ico',
},
{
value: 'linuxmint',
label: 'Linux Mint',
iconName: 'linux-mint.svg',
},
{
value: 'deepin',
label: 'Deepin',
iconName: 'deepin.png',
},
{
value: 'proxmox',
label: 'Proxmox VE',
iconName: 'proxmox.svg',
},
],
},
{
group: 'RedHat',
children: [
{
value: 'fedora',
label: 'Fedora',
iconName: 'fedora.ico',
},
{
value: 'centos',
label: 'CentOS',
iconName: 'centos.svg',
},
{
value: 'centos_stream',
label: 'CentOS Stream',
iconName: 'centos.svg',
},
{
value: 'rocky',
label: 'Rocky Linux',
iconName: 'rocky-linux.svg',
},
{
value: 'almalinux',
label: 'AlmaLinux',
iconName: 'almalinux.svg',
},
{
value: 'openeuler',
label: 'openEuler',
iconName: 'openeuler.ico',
},
{
value: 'opencloudos',
label: 'OpenCloudOS',
iconName: 'opencloudos.png',
},
{
value: 'anolis',
label: 'Anolis OS',
iconName: 'anolis.png',
},
{
value: 'epel',
label: 'EPEL',
},
],
},
{
group: 'Arch Linux',
children: [
{
value: 'archlinux',
label: 'Arch Linux',
iconName: 'arch-linux.ico',
},
{
value: 'manjaro',
label: 'Manjaro',
iconName: 'manjaro.svg',
},
{
value: 'endeavouros',
label: 'EndeavourOS',
iconName: 'endeavouros.png',
},
],
},
{
group: 'Other',
children: [
{
value: 'opensuse',
label: 'openSUSE',
iconName: 'opensuse.svg',
},
{
value: 'alpine',
label: 'Alpine Linux',
iconName: 'alpine.png',
},
{
value: 'gentoo',
label: 'Gentoo',
iconName: 'gentoo.svg',
},
{
value: 'nix_channels',
label: 'NixOS',
iconName: 'nixos.svg',
},
],
},
]

View File

@@ -0,0 +1,412 @@
ComponentSystem.register('mirrors-table', {
template: `
<div>
<t-config-provider :global-config="globalConfig">
<t-space v-if="!isMobile" align="center" style="margin-bottom: 8px; gap: 20px">
<blockquote>
<p>{{ startTitle[0] }} <code>Debian</code>、<code>Ubuntu</code>、<code>CentOS</code>、<code>openEuler</code> {{ startTitle[1] }}</p>
</blockquote>
<t-space style="width: 100%">
<t-popup placement="bottom" :show-arrow="false">
<template #content>
<t-checkbox-group v-model="selectedCellStatuses" style="padding: 6px" @change="onCellStatusChange">
<t-space align="start" direction="vertical" style="gap: 4px">
<t-checkbox value="supported">
<t-space align="center" style="gap: 2px">
<t-tag theme="success" variant="light" style="background-color: transparent; vertical-align: -0.35em">
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59L21 7Z"></svg>
</template>
</t-tag>
<span>{{ statusLabels.supported }}</span>
</t-space>
</t-checkbox>
<t-checkbox value="unsupported">
<t-space align="center" style="gap: 0">
<t-tag theme="danger" variant="light" style="background-color: transparent; vertical-align: -0.35em">
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" style="vertical-align: -0.2em"><path fill="currentColor" d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z"></svg>
</template>
</t-tag>
<span>{{ statusLabels.unsupported }}</span>
</t-space>
</t-checkbox>
<t-checkbox value="incompatible">
<t-space align="center" style="gap: 2px">
<t-tag theme="warning" variant="light" style="background-color: transparent; vertical-align: -0.35em">
<svg xmlns="http://www.w3.org/2000/svg" style="margin-left: 2px" width="16" height="16" viewBox="0 0 24 24" style="vertical-align: -0.15em"><path fill="#F6B604" d="M22.11 21.46L2.39 1.73L1.11 3l2.95 2.95A9.95 9.95 0 0 0 2 12c0 5.5 4.5 10 10 10c2.28 0 4.37-.77 6.05-2.06l2.79 2.79l1.27-1.27M12 20c-4.42 0-8-3.58-8-8c0-1.73.56-3.32 1.5-4.62L16.62 18.5A7.78 7.78 0 0 1 12 20M8.17 4.97L6.72 3.5C8.25 2.56 10.06 2 12 2c5.5 0 10 4.5 10 10c0 1.94-.56 3.75-1.5 5.28l-1.47-1.45c.62-1.14.97-2.44.97-3.83c0-4.42-3.58-8-8-8c-1.39 0-2.69.35-3.83.97Z"></svg>
</t-tag>
<span>{{ statusLabels.incompatible }}</span>
</t-space>
</t-checkbox>
</t-space>
</t-checkbox-group>
</template>
<t-button variant="text" shape="circle" :theme="selectedCellStatuses.length < 3 ? 'primary' : 'default'">
<svg fill="none" viewBox="0 0 24 24" width="1em" height="1em" class="t-icon t-icon-filter" style="fill: none;"><g id="filter"><path id="fill1" fill="transparent" d="M19.5 4H4.5L10.5 12.5V20H13.5V12.5L19.5 4Z" fill-rule="evenodd" clip-rule="evenodd"></path><path id="stroke1" stroke="currentColor" d="M19.5 4H4.5L10.5 12.5V20H13.5V12.5L19.5 4Z" fill-rule="evenodd" stroke-linecap="square" stroke-width="2" clip-rule="evenodd"></path></g></svg>
</t-button>
</t-popup>
<t-select
v-model="selectedRowFilters"
:options="rowFilterOptionsRendered"
:min-collapsed-num="1"
multiple
clearable
size="large"
:placeholder="rowSelectPlaceholder"
style="min-width: 160px; width: 250px"
@change="onRowFilterChange"
/>
<t-select
v-model="selectedColumnFilters"
:options="filterOptions"
:min-collapsed-num="1"
multiple
clearable
size="large"
:placeholder="selectPlaceholder"
style="min-width: 160px; width: 230px"
@change="onFilterChange"
/>
</t-space>
</t-space>
<blockquote v-if="isMobile">
<p>{{ startTitle[0] }} <code>Debian</code>、<code>Ubuntu</code>、<code>CentOS</code>、<code>openEuler</code> {{ startTitle[1] }}</p>
</blockquote>
<t-table
:columns="columns"
:data="data"
row-key="name"
size="small"
verticalAlign="bottom"
hover
@data-change="dataChange"
@filter-change="onTableFilterChange"
>
<template #IPv6="{ col }">
<t-tooltip :content="col.tooltip">
<span>IPv6</span>
</t-tooltip>
</template>
<template #EPEL="{ col }">
<t-tooltip :content="col.tooltip">
<span>EPEL</span>
</t-tooltip>
</template>
<template v-for="col in columns" :key="col.colKey" #[col.colKey]="{ row }">
<div v-if="col.colKey === 'name'">
<t-popup placement="bottom" :show-arrow="false">
<template #content>
<t-space direction="vertical" align="center" style="gap: 2px">
<span>{{ row.officialName }}</span>
<a :href="row.url" target="_blank" rel="noopener noreferrer" style="color: var(--md-typeset-a-color)">{{ row.domain }}</a>
</t-space>
</template>
<a :href="row.url" target="_blank" rel="noopener noreferrer">
<t-space align="center" style="gap: 6px">
<span style="display: flex; height: 16px; width: 16px; align-items: center; justify-content: center">
<img v-if="row.icon" :src="'/assets/images/icon/mirrors/' + row.icon" width="16" height="16">
</span>
<span style="display: flex; align-items: center; justify-content: center">{{ row.name }}</span>
</t-space>
</a>
</t-popup>
</div>
<div v-else>
<t-tag v-if="typeof row[col.colKey] === 'boolean'" :theme="row[col.colKey] ? 'success' : 'danger'" variant="light" size="small" style="background-color: transparent; height: 26px" style="z-index: 2">
<template #icon>
<div v-if="row[col.colKey] === true && !['ipv6'].includes(col.colKey) && showSupported">
<t-popup placement="bottom" :show-arrow="false">
<template #content>
<a :href="getMirrorSiteBranchUrl(row.domain, col.colKey)" target="_blank" rel="noopener noreferrer" style="color: var(--md-typeset-a-color)">{{ getMirrorSiteBranchUrl(row.domain, col.colKey) }}</a>
</template>
<a :href="getMirrorSiteBranchUrl(row.domain, col.colKey)" target="_blank" rel="noopener noreferrer" style="color: var(--td-success-color)">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59L21 7Z"></svg>
</a>
</t-popup>
</div>
<svg v-else-if="row[col.colKey] === true && showSupported" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59L21 7Z"></svg>
<svg v-else-if="row[col.colKey] === false && showUnsupported" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z"></svg>
</template>
</t-tag>
<t-tag v-else theme="warning" variant="light" size="small" style="background-color: transparent; height: 26px">
<template #icon>
<svg v-if="showIncompatible" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="#F6B604" d="M22.11 21.46L2.39 1.73L1.11 3l2.95 2.95A9.95 9.95 0 0 0 2 12c0 5.5 4.5 10 10 10c2.28 0 4.37-.77 6.05-2.06l2.79 2.79l1.27-1.27M12 20c-4.42 0-8-3.58-8-8c0-1.73.56-3.32 1.5-4.62L16.62 18.5A7.78 7.78 0 0 1 12 20M8.17 4.97L6.72 3.5C8.25 2.56 10.06 2 12 2c5.5 0 10 4.5 10 10c0 1.94-.56 3.75-1.5 5.28l-1.47-1.45c.62-1.14.97-2.44.97-3.83c0-4.42-3.58-8-8-8c-1.39 0-2.69.35-3.83.97Z"></svg>
</template>
</t-tag>
</template>
</template>
</t-table>
</t-config-provider>
</div>
`,
data() {
return {
allColumns: mirrorsTableColumns,
columns: mirrorsTableColumns,
originalData: mirrorsTableData,
data: mirrorsTableData.slice(),
rawFilterOptions: mirrorsTableFilterSelectOptions,
selectedColumnFilters: [],
selectedRowFilters: [],
selectedCellStatuses: ['supported', 'unsupported', 'incompatible'],
activeTableFilters: {},
}
},
created() {
const allKeys = this._flattenFilterKeys(this.filterOptions)
const defaultHidden = new Set(['debian', 'ubuntu', 'centos', 'openeuler'])
this.selectedColumnFilters = allKeys.filter((k) => !defaultHidden.has(k))
this.selectedRowFilters = Array.isArray(this.originalData) ? this.originalData.map((r) => r.name) : []
this._debouncedUpdateColumns = debounce(this._updateColumns.bind(this), 120)
this._debouncedUpdateRows = debounce(this._updateRows.bind(this), 120)
this._updateColumns()
this._updateRows()
},
computed: {
isMobile() {
return window.matchMedia('(max-width: 768px)').matches
},
localeFlags() {
const p = (window && window.location && window.location.pathname) || ''
return {
isZhHant: p.includes('/zh-Hant'),
isEn: p.includes('/en'),
}
},
startTitle() {
const f = this.localeFlags
return f.isZhHant ? ['下方列表中的鏡像站均同步了', '軟體倉庫,列表根據單位性質、地理位置、名稱長度排序,與實際下載速度無關。'] : f.isEn ? ['All mirror sites in the list below synchronize the', 'software repositories. The list is sorted by institution type, geographic location, and name length, and is not related to actual download speed.'] : ['下方列表中的镜像站均同步了', '软件仓库,列表根据单位性质、地理位置、名称长度进行排序,与实际速度无关。']
},
globalConfig() {
const f = this.localeFlags
return {
animation: { include: ['expand', 'fade'], exclude: [] },
table: f.isZhHant
? {
empty: '\u66AB\u7121\u6578\u64DA',
loadingText: '\u6B63\u5728\u8F09\u5165\u4E2D\uFF0C\u8ACB\u7A0D\u5F8C',
loadingMoreText: '\u9EDE\u64CA\u8F09\u5165\u66F4\u591A',
filterInputPlaceholder: '\u8ACB\u8F38\u5165\u5185\u5BB9\uFF08\u7121\u9ED8\u8A8D\u503C\uFF09',
sortAscendingOperationText: '\u9EDE\u64CA\u5347\u5E8F',
sortCancelOperationText: '\u9EDE\u64CA\u53D6\u6D88\u6392\u5E8F',
sortDescendingOperationText: '\u9EDE\u64CA\u964D\u5E8F',
clearFilterResultButtonText: '\u6E05\u7A7A\u7BE9\u9078',
columnConfigButtonText: '\u884C\u914D\u7F6E',
columnConfigTitleText: '\u8868\u683C\u884C\u914D\u7F6E',
columnConfigDescriptionText: '\u8ACB\u9078\u64C7\u9700\u8981\u5728\u8868\u683C\u4E2D\u986F\u793A\u7684\u6578\u64DA\u884C',
confirmText: '\u78BA\u8A8D',
cancelText: '\u53D6\u6D88',
resetText: '\u91CD\u7F6E',
selectAllText: '\u5168\u9078',
searchResultText: '\u641C\u5C0B"{result}"\uFF0C\u627E\u5230{count}\u9805\u7D50\u679C',
}
: f.isEn
? {
empty: 'Empty Data',
loadingText: 'loading...',
loadingMoreText: 'loading more',
filterInputPlaceholder: '',
sortAscendingOperationText: 'click to sort ascending',
sortCancelOperationText: 'click to cancel sorting',
sortDescendingOperationText: 'click to sort descending',
clearFilterResultButtonText: 'Clear',
columnConfigButtonText: 'Column Config',
columnConfigTitleText: 'Table Column Config',
columnConfigDescriptionText: 'Please select columns to show them in the table',
confirmText: 'Confirm',
cancelText: 'Cancel',
resetText: 'Reset',
selectAllText: 'Select All',
searchResultText: 'Search "{result}". Found no items. | Search "{result}". Found 1 item. | Search "{result}". Found {count} items.',
}
: undefined,
select: f.isZhHant
? {
empty: '\u66AB\u7121\u6578\u64DA',
loadingText: '\u8F09\u5165\u4E2D',
placeholder: '\u8ACB\u9078\u64C7',
}
: f.isEn
? {
empty: 'Empty Data',
loadingText: 'loading...',
placeholder: 'please select',
}
: undefined,
}
},
selectPlaceholder() {
const f = this.localeFlags
return f.isZhHant ? '选择要显示的列' : f.isEn ? 'Select columns to show' : '选择要显示的列'
},
rowSelectPlaceholder() {
const f = this.localeFlags
return f.isZhHant ? '筛选显示的镜像' : f.isEn ? 'Filter mirrors to show' : '筛选显示的镜像'
},
rowFilterOptions() {
const f = this.localeFlags
const arr = Array.isArray(this.originalData) ? this.originalData.map((r) => ({ value: r.name, label: r.name, iconName: r.icon })) : []
const head = { value: '__all__', label: f.isZhHant ? '全選' : f.isEn ? 'Select All' : '全选', checkAll: true }
return [head].concat(arr)
},
rowFilterOptionsRendered() {
return Array.isArray(this.rowFilterOptions) ? this.rowFilterOptions.map((o) => this._mapOptionForRow(o)) : []
},
statusLabels() {
const f = this.localeFlags
return {
supported: f.isZhHant ? '支援' : f.isEn ? 'Supported' : '支持',
unsupported: f.isZhHant ? '不支援' : f.isEn ? 'Unsupported' : '不支持',
incompatible: f.isZhHant ? '不相容' : f.isEn ? 'Incompatible' : '不兼容',
}
},
showSupported() {
return Array.isArray(this.selectedCellStatuses) && this.selectedCellStatuses.includes('supported')
},
showUnsupported() {
return Array.isArray(this.selectedCellStatuses) && this.selectedCellStatuses.includes('unsupported')
},
showIncompatible() {
return Array.isArray(this.selectedCellStatuses) && this.selectedCellStatuses.includes('incompatible')
},
filterOptions() {
return Array.isArray(this.rawFilterOptions) ? this.rawFilterOptions.map((o) => this._mapOptionForFilter(o)) : []
},
},
methods: {
dataChange(data) {
try {
const hasColumnFilters = this.activeTableFilters && Object.keys(this.activeTableFilters).length > 0
const hasRowFilters = Array.isArray(this.selectedRowFilters) && this.selectedRowFilters.length > 0
if (hasColumnFilters || hasRowFilters) {
return
}
this.data = data
} catch {}
},
onFilterChange() {
if (this._debouncedUpdateColumns) this._debouncedUpdateColumns()
else this._updateColumns()
},
onRowFilterChange() {
try {
if (Array.isArray(this.selectedRowFilters) && this.selectedRowFilters.includes('__all__')) {
this.selectedRowFilters = Array.isArray(this.originalData) ? this.originalData.map((r) => r.name) : []
}
} catch {}
if (this._debouncedUpdateRows) this._debouncedUpdateRows()
else this._updateRows()
},
getMirrorSiteBranchUrl(domain, branchName) {
return `https://${domain}/${branchName.replace(/_/, '-')}/`
},
_mapOptionForRow(opt) {
const prefix = '/assets/images/icon/mirrors/'
const copy = Object.assign({}, opt)
if (copy.iconName) copy.prefixIcon = prefix + copy.iconName
copy.content = function (h, ctx) {
const option = (ctx && ctx.option) || copy
const children = []
if (option.iconName) {
children.push(h('img', { src: prefix + option.iconName, width: 16, height: 16, style: 'vertical-align: middle' }))
}
children.push(h('span', { style: option.iconName ? 'margin-left: 8px' : '' }, option.label || option.value || ''))
return h('div', { style: 'display: flex; align-items: center' }, children)
}
return copy
},
_mapOptionForFilter(opt) {
const prefix = '/assets/images/icon/'
const copy = Object.assign({}, opt)
if (copy.iconName) copy.prefixIcon = prefix + copy.iconName
copy.content = function (h, ctx) {
const option = (ctx && ctx.option) || copy
const children = []
if (option.iconName) {
children.push(h('img', { src: prefix + option.iconName, width: 16, height: 16, style: 'vertical-align: middle' }))
}
children.push(h('span', { style: option.iconName ? 'margin-left: 8px' : '' }, option.label || option.value || ''))
return h('div', { style: 'display: flex; align-items: center' }, children)
}
if (Array.isArray(copy.children)) {
copy.children = copy.children.map((c) => this._mapOptionForFilter(c))
}
return copy
},
onCellStatusChange() {
if (this._debouncedUpdateRows) this._debouncedUpdateRows()
else this._updateRows()
},
onTableFilterChange(filters) {
try {
this.activeTableFilters = filters || {}
if (this._debouncedUpdateRows) this._debouncedUpdateRows()
else this._updateRows()
} catch {}
},
_updateColumns() {
try {
const keys = new Set(this.selectedColumnFilters || [])
this.columns = this.allColumns.filter((col) => col.colKey === 'name' || keys.has(col.colKey))
} catch (e) {
this.columns = this.allColumns
}
},
_updateRows() {
try {
this._computeFilteredData()
} catch {}
},
_computeFilteredData() {
try {
let rows = Array.isArray(this.originalData) ? this.originalData.slice() : []
if (Array.isArray(this.selectedRowFilters) && this.selectedRowFilters.length) {
const names = new Set(this.selectedRowFilters || [])
rows = rows.filter((r) => names.has(r.name))
}
const filters = this.activeTableFilters || {}
const filterKeys = Object.keys(filters)
if (filterKeys.length) {
rows = rows.filter((row) => {
for (let i = 0; i < filterKeys.length; i++) {
const key = filterKeys[i]
let value = filters[key]
if (Array.isArray(value)) value = value.length ? value[0] : undefined
if (!value) continue
const cell = Object.prototype.hasOwnProperty.call(row, key) ? row[key] : undefined
if (value === 'supported') {
if (cell !== true) return false
} else if (value === 'unsupported') {
if (cell !== false) return false
} else if (value === 'incompatible') {
if (cell !== 'incompatible') return false
} else {
continue
}
}
return true
})
}
this.data = rows
} catch {}
},
_flattenFilterKeys(options) {
const set = new Set()
options.forEach((opt) => {
if (opt.group && Array.isArray(opt.children)) {
opt.children.forEach((child) => {
if (child && child.value) set.add(child.value)
})
} else if (Array.isArray(opt.children)) {
opt.children.forEach((child) => {
if (child && child.value) set.add(child.value)
})
} else if (opt.value) {
set.add(opt.value)
}
})
return Array.from(set)
},
},
})

View File

@@ -0,0 +1,217 @@
const OramaI18nData = {
'zh-Hans': {
suggestions: ['如何使用', '支持哪些系统'],
askAiText: '询问 AI',
searchText: '搜索',
toSelectText: '选择',
toNavigateText: '导航',
toCloseText: '关闭',
dictionary: {
searchPlaceholder: '请输入要搜索的内容...',
chatPlaceholder: '有什么可以帮你的吗?',
noResultsFound: '未找到结果',
noResultsFoundFor: '未找到与该内容相关的结果',
suggestionsTitle: '建议',
seeAll: '查看全部',
addMore: '添加更多',
clearChat: '清空聊天',
errorMessage: '尝试搜索时发生错误。请重试。',
disclaimer: 'AI 可能会出错,请核实信息。',
startYourSearch: '开始搜索',
initErrorSearch: '无法初始化搜索服务',
initErrorChat: '无法初始化聊天服务',
chatButtonLabel: '询问 AI',
searchButtonLabel: '搜索',
},
},
'zh-Hant': {
suggestions: ['如何使用', '支援哪些系統'],
askAiText: '詢問 AI',
searchText: '搜尋',
toSelectText: '選擇',
toNavigateText: '導航',
toCloseText: '關閉',
dictionary: {
searchPlaceholder: '請輸入要搜尋的內容...',
chatPlaceholder: '有什麼可以幫你的嗎?',
noResultsFound: '未找到結果',
noResultsFoundFor: '未找到與該內容相關的結果',
suggestionsTitle: '建議',
seeAll: '看全部',
addMore: '添加更多',
clearChat: '清空聊天',
errorMessage: '嘗試搜尋時發生錯誤。請重試。',
disclaimer: 'AI 可能會出錯,請核實資訊。',
startYourSearch: '開始搜尋',
initErrorSearch: '無法初始化搜尋服務',
initErrorChat: '無法初始化聊天服務',
chatButtonLabel: '詢問 AI',
searchButtonLabel: '搜尋',
},
},
en: {
suggestions: ['How to use', 'What systems are supported'],
askAiText: 'Ask AI',
searchText: 'Search',
toSelectText: 'Select',
toNavigateText: 'Navigate',
toCloseText: 'Close',
dictionary: {
searchPlaceholder: 'Please enter the content to search...',
chatPlaceholder: 'How can I help you?',
noResultsFound: 'No results found',
noResultsFoundFor: 'No results found for',
suggestionsTitle: 'Suggestions',
seeAll: 'See all',
addMore: 'Add more',
clearChat: 'Clear chat',
errorMessage: 'An error occurred while trying to search. Please try again.',
disclaimer: 'AI can make mistakes. Please verify the information.',
startYourSearch: 'Start your search',
initErrorSearch: 'Unable to initialize search service',
initErrorChat: 'Unable to initialize chat service',
chatButtonLabel: 'Ask AI',
searchButtonLabel: 'Search',
},
},
}
// get search box config
function getOramaSearchBoxConfig() {
const currentLang = __isZhHant ? 'zh-Hant' : __isEn ? 'en' : 'zh-Hans'
return {
themeConfig: {
// colors: {
// light: {
// '--text-color-accent': 'var(--md-accent-fg-color)',
// '--background-color-tertiary': 'var(--md-accent-fg-color--transparent)',
// '--background-color-accent': 'var(--md-accent-fg-color)',
// '--border-color-accent': 'var(--md-accent-fg-color)',
// '--button-background-color-primary': 'var(--md-accent-fg-color)',
// '--button-background-color-secondary': 'hsla(240, 9%, 75%, 0.33)',
// '--button-background-color-secondary-hover': 'var(--md-accent-fg-color--transparent)',
// '--chat-button-border-color-gradientThree': 'var(--md-accent-fg-color)',
// '--chat-button-border-color-gradientFour': 'var(--md-accent-fg-color)',
// '--chat-button-background-color-gradientOne': 'var(--md-accent-fg-color)',
// },
// dark: {
// '--text-color-accent': 'var(--md-accent-fg-color)',
// '--background-color-primary': 'var(--md-default-bg-color--dark)',
// '--background-color-secondary': '#1a1b20',
// '--background-color-tertiary': 'var(--md-accent-fg-color--transparent)',
// '--background-color-accent': 'var(--md-accent-fg-color)',
// '--border-color-accent': 'var(--md-accent-fg-color)',
// '--icon-color-reverse': 'var(--md-default-bg-color--dark)',
// '--button-background-color-primary': 'var(--md-accent-fg-color)',
// '--button-background-color-secondary': 'hsla(var(--md-hue), 15%, 9%, 0.33)',
// '--button-background-color-secondary-hover': 'var(--md-accent-fg-color--transparent)',
// '--chat-button-border-color-gradientThree': 'var(--md-accent-fg-color)',
// '--chat-button-border-color-gradientFour': 'var(--md-accent-fg-color)',
// '--chat-button-background-color-gradientOne': 'var(--md-accent-fg-color)',
// },
// },
// shadow: {
// '--textarea-shadow': 'var(--md-shadow-z1)',
// },
// typography: {
// '--font-primary': 'var(--md-font-family)',
// },
},
index: {
endpoint: 'https://cloud.orama.run/v1/indexes/linuxmirrors-cn-imxypv',
api_key: 'mBJ0b68dZIk79DqCkdUSJI6yQMo4L4o3',
},
facetProperty: 'category',
sourceBaseUrl: 'https://linuxmirrors.cn',
sourcesMap: {
title: 'title',
},
resultMap: {
title: 'title',
description: 'content',
section: 'category',
},
suggestions: OramaI18nData[currentLang].suggestions,
dictionary: OramaI18nData[currentLang].dictionary,
}
}
// localization search box component
function localizationOramaSearchBox(searchBox) {
if (!searchBox) return
const currentLang = __isZhHant ? 'zh-Hant' : __isEn ? 'en' : 'zh-Hans'
const askAiText = OramaI18nData[currentLang].askAiText
const searchText = OramaI18nData[currentLang].searchText
const observer = new MutationObserver((mutations, obs) => {
const shadowRoot = searchBox.shadowRoot
if (shadowRoot) {
// Get a summary (Only PC)
// const chatButton = shadowRoot.querySelector('.chat-button')
// if (chatButton) {
// const element = chatButton.querySelector('.button-label')
// if (element && element.textContent !== askAiText) {
// element.textContent = askAiText
// }
// }
const footer = shadowRoot.querySelector('orama-footer')
if (footer) {
// Keyboard shortcuts localization (Only PC)
const toSelectText = OramaI18nData[currentLang].toSelectText
const toNavigateText = OramaI18nData[currentLang].toNavigateText
const toCloseText = OramaI18nData[currentLang].toCloseText
const shortcutDescriptions = footer.querySelectorAll('.shortcut-description')
shortcutDescriptions.forEach((element) => {
if (element.textContent === 'to select' && element.textContent !== toSelectText) {
element.textContent = toSelectText
} else if (element.textContent === 'to navigate' && element.textContent !== toNavigateText) {
element.textContent = toNavigateText
} else if (element.textContent === 'to close' && element.textContent !== toCloseText) {
element.textContent = toCloseText
}
})
// Change logo image
const logoImg = footer.querySelector('.logo-link .logo')
if (logoImg) {
let theme = 'light'
try {
const palette = __md_get('__palette')
if (palette && typeof palette.color === 'object') {
theme = palette.color.scheme === 'slate' ? 'dark' : 'light'
}
} catch {}
logoImg.src = `/assets/images/icon/orama/orama-when-${theme}.svg`
}
}
// Hide "Orama can make mistakes. Please verify the information."
// const chatFormWrapper = shadowRoot.querySelector('.chat-form-wrapper')
// if (chatFormWrapper) {
// const element = chatFormWrapper.querySelector('p.small.text-center')
// if (element) {
// element.style = 'display: none;'
// }
// }
// Navigation bar (Only Mobile)
const navigationBar = shadowRoot.querySelector('orama-navigation-bar')
if (navigationBar) {
const toggler = navigationBar.querySelector('orama-toggler')
if (toggler) {
const searchSpan = toggler.querySelector('span:nth-child(1)')
if (searchSpan && searchSpan.textContent !== searchText) {
searchSpan.textContent = searchText
searchSpan.style = 'max-height: fit-content;'
}
const askAISpan = toggler.querySelector('span:nth-child(2)')
if (askAISpan && askAISpan.textContent !== askAiText) {
askAISpan.textContent = askAiText
askAISpan.style = 'max-height: fit-content;'
}
}
}
}
})
observer.observe(searchBox, { childList: true, subtree: true, attributes: true })
setTimeout(() => observer.disconnect(), 2000)
}

View File

@@ -0,0 +1,47 @@
function loadOramaWebComponent() {
const searchBox = document.querySelector('orama-search-box')
if (searchBox) {
Object.assign(searchBox, getOramaSearchBoxConfig())
}
// custom entrance button style
const searchButton = document.querySelector('orama-search-button')
if (searchButton) {
searchButton.textContent = '搜索'
const observer = new MutationObserver((_mutations, obs) => {
if (searchButton.shadowRoot) {
const button = searchButton.shadowRoot.querySelector('button')
if (button) {
button.style = 'border-radius: 8px !important;'
button.querySelector('.button__label').style = 'height: 22px; padding: 2px 8px; font-size: 0.75rem;'
button.querySelector('span[slot="adorment-end"], .kyb-shortcut').style = 'display: none;'
// search icon style
const magnifyingGlassShadowRoot = button.querySelector('ph-magnifying-glass').shadowRoot
const styleElement = document.createElement('style')
styleElement.textContent = `
svg {
font-size: 24px;
}`
if (!magnifyingGlassShadowRoot.querySelector('style[data-custom="icon-style"]')) {
styleElement.setAttribute('data-custom', 'icon-style')
magnifyingGlassShadowRoot.appendChild(styleElement)
}
obs.disconnect()
}
}
})
observer.observe(searchButton, {
childList: true,
subtree: true,
attributes: true,
characterData: true,
})
setTimeout(() => observer.disconnect(), 2000)
}
searchButton.addEventListener('click', function () {
const searchBox = document.querySelector('orama-search-box')
// localization chat box component
localizationOramaSearchBox(searchBox)
})
}

View File

@@ -0,0 +1,30 @@
function loadOramaWebComponent() {
const searchBox = document.querySelector('orama-search-box')
if (searchBox) {
Object.assign(searchBox, getOramaSearchBoxConfig())
}
// define global function
window.openOramaSearch = function (event) {
const element = document.querySelector('orama-search-box')
if (element) {
element.open = true
localizationOramaSearchBox(element)
}
return false
}
// localization chat box component
if (searchBox) {
const openObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'open' && searchBox.hasAttribute('open')) {
localizationOramaSearchBox(searchBox)
}
})
})
openObserver.observe(searchBox, { attributes: true })
if (searchBox.hasAttribute('open')) {
localizationOramaSearchBox(searchBox)
}
}
}

View File

@@ -0,0 +1,32 @@
// 更新 TDesign 主题
function updateTDesignGlobalTheme() {
const scheme = document.querySelector('[data-md-color-scheme]')?.getAttribute('data-md-color-scheme')
const isDarkMode = scheme === 'slate' || scheme === 'dark'
if (isDarkMode) {
document.documentElement.setAttribute('theme-mode', 'dark')
} else {
document.documentElement.removeAttribute('theme-mode')
}
}
// 主题监听器
function setupThemeObserver() {
// 监听主题变化
const observer = new MutationObserver(() => {
updateTDesignGlobalTheme()
})
const element = document.querySelector('[data-md-color-scheme]')
if (element) {
observer.observe(element, {
attributes: true,
attributeFilter: ['data-md-color-scheme'],
})
} else {
// 如果元素不存在,等待页面加载完成后重试
setTimeout(setupThemeObserver, 1000)
}
}
document.addEventListener('DOMContentLoaded', function () {
setupThemeObserver()
})

View File

@@ -0,0 +1,90 @@
function useThemeTransition() {
// 更新过渡样式变量
function updateViewTransitionVariables(isDarkTheme) {
document.documentElement.style.setProperty('--view-transition-z-index-foreground', isDarkTheme ? '999' : '1')
document.documentElement.style.setProperty('--view-transition-z-index-background', isDarkTheme ? '1' : '999')
}
// 切换主题按钮点击事件
function handleThemeToggle(e) {
// 阻止默认点击事件
e.preventDefault()
e.stopPropagation()
// 获取目标输入元素
const targetId = this.getAttribute('for')
const targetInput = document.getElementById(targetId)
if (!targetInput) return
// 获取主题状态
const targetTheme = targetInput.getAttribute('data-md-color-scheme') // 目标主题system、default、slate
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'slate' : 'default' // 系统主题default、slate
const currentScheme = document.body.getAttribute('data-md-color-scheme') // 当前主题default、slate
// 当目标主题与当前主题相同时不触发动画
if (targetTheme === 'system') {
if (systemTheme === currentScheme) {
targetInput.click()
return
}
} else if (targetTheme === currentScheme) {
targetInput.click()
return
}
// 当前主题状态
const isSystemDarkTheme = systemTheme === 'slate' // 系统是否为深色主题
const isCurrentDarkTheme = currentScheme.includes('slate') // 当前是否为深色主题
const isSwitchToDarkTheme = !isCurrentDarkTheme // 是否将切换到深色主题
// 根据系统主题设置动画样式
updateViewTransitionVariables(isSystemDarkTheme)
// 判断切换方向是否与系统主题一致
// 如果系统是深色,切换到深色是"靠近系统";如果系统是浅色,切换到浅色是"靠近系统"
const isMovingTowardsSystemTheme = (isSwitchToDarkTheme && isSystemDarkTheme) || (!isSwitchToDarkTheme && !isSystemDarkTheme)
// 动画参数
const x = e.clientX
const y = e.clientY
const endRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y))
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`]
// 启动视图过渡
document
.startViewTransition(async () => {
// 切换主题
targetInput.click()
// 添加CSS类用于动画控制
document.documentElement.classList.remove(isSwitchToDarkTheme ? 'light' : 'dark')
document.documentElement.classList.add(isSwitchToDarkTheme ? 'dark' : 'light')
// 等待主题变化完成
await new Promise((resolve) => setTimeout(resolve, 100))
})
.ready.then(() => {
// 当朝向系统主题方向变化时使用使用缩小效果reversed clipPath反之放大效果clipPath
document.documentElement.animate(
{
clipPath: isMovingTowardsSystemTheme ? [...clipPath].reverse() : clipPath,
transform: 'translateZ(0)',
},
{
duration: 500,
easing: 'ease-in',
pseudoElement: isMovingTowardsSystemTheme ? '::view-transition-old(root)' : '::view-transition-new(root)',
}
)
})
}
// 不支持此特性
if (typeof document.startViewTransition !== 'function') {
return
}
// 获取主题切换按钮Dom
const themeToggles = document.querySelectorAll('form[data-md-component="palette"] .md-header__button.md-icon')
themeToggles.forEach((toggle) => {
toggle.addEventListener('click', handleThemeToggle, { capture: true })
})
// 初始化主题状态类
const currentScheme = document.body.getAttribute('data-md-color-scheme')
const isDark = currentScheme.includes('slate')
document.documentElement.classList.add(isDark ? 'dark' : 'light')
// 初始化过渡样式变量
updateViewTransitionVariables(isDark)
}
document.addEventListener('DOMContentLoaded', function () {
useThemeTransition()
})