From 6d54b721daebacc49bb6ad254f0775305caa1a02 Mon Sep 17 00:00:00 2001 From: qwqcode Date: Tue, 17 Sep 2024 16:59:28 +0800 Subject: [PATCH] docs(landing): add multi-language and dark-mode for landing --- .prettierignore | 2 +- README.md | 4 +- docs/docs/guide/intro.md | 4 +- docs/landing/index.html | 11 +- docs/landing/package.json | 7 +- .../public/images/demo-video-1-thumbnail.png | Bin 47505 -> 26131 bytes docs/landing/scripts/update-readme.ts | 21 +- docs/landing/src/App.scss | 49 ++- docs/landing/src/App.tsx | 19 +- docs/landing/src/Features.ts | 220 ------------ docs/landing/src/components/FeatureDesc.scss | 2 +- docs/landing/src/components/FeatureDesc.tsx | 6 +- docs/landing/src/components/FeatureTitle.tsx | 5 +- .../src/components/Features/FeatureBase.tsx | 8 +- .../src/components/Features/FullFeature.scss | 24 +- .../src/components/Features/FullFeature.tsx | 11 +- .../components/Features/FullFeatureIcon.tsx | 90 +++-- .../components/Features/FullFeatureList.tsx | 13 +- .../src/components/Features/FuncFeature.scss | 10 +- .../src/components/Features/FuncFeature.tsx | 205 +++++++++-- .../src/components/Features/QuickFeature.scss | 25 +- .../src/components/Features/QuickFeature.tsx | 139 ++++++-- .../src/components/Features/SafeFeature.tsx | 50 ++- .../components/Features/SlightFeature.scss | 14 +- .../src/components/Features/SlightFeature.tsx | 44 ++- docs/landing/src/components/Footer.scss | 6 +- docs/landing/src/components/Footer.tsx | 8 +- docs/landing/src/components/Header.scss | 53 ++- docs/landing/src/components/Header.tsx | 96 +++++- .../landing/src/components/LearnMoreLink.scss | 4 +- docs/landing/src/components/LearnMoreLink.tsx | 12 +- docs/landing/src/components/Reveal.scss | 2 +- docs/landing/src/components/Reveal.tsx | 34 +- docs/landing/src/components/Slogan.scss | 16 +- docs/landing/src/components/Slogan.tsx | 57 +++- docs/landing/src/darkmode.scss | 9 + docs/landing/src/features.ts | 235 +++++++++++++ docs/landing/src/hooks/darkmode.ts | 61 ++++ docs/landing/src/hooks/visible.ts | 13 +- docs/landing/src/i18n/en.ts | 132 +++++++ docs/landing/src/i18n/index.ts | 38 +++ docs/landing/src/i18n/zh-CN.ts | 129 +++++++ docs/landing/src/i18next.d.ts | 11 + docs/landing/src/main.tsx | 3 + docs/landing/src/responsive.scss | 53 ++- docs/landing/vite.config.ts | 16 +- pnpm-lock.yaml | 321 ++++++++++++++---- 47 files changed, 1720 insertions(+), 572 deletions(-) delete mode 100644 docs/landing/src/Features.ts create mode 100644 docs/landing/src/darkmode.scss create mode 100644 docs/landing/src/features.ts create mode 100644 docs/landing/src/hooks/darkmode.ts create mode 100644 docs/landing/src/i18n/en.ts create mode 100644 docs/landing/src/i18n/index.ts create mode 100644 docs/landing/src/i18n/zh-CN.ts create mode 100644 docs/landing/src/i18next.d.ts diff --git a/.prettierignore b/.prettierignore index 636fcef05..4994f9895 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,7 +12,7 @@ pnpm-lock.yaml ui/artalk/src/api/v2.ts docs/swagger/swagger.json docs/swagger/swagger.yaml -docs/** +docs/docs/** ui/artalk-sidebar/src/lib/md5.js ui/artalk/src/lib/detect.ts **/dist/** diff --git a/README.md b/README.md index 81af709fc..442894a77 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,10 @@ Artalk 是一款简单易用但功能丰富的评论系统,你可以开箱即 * [图片懒加载](https://artalk.js.org/guide/frontend/img-lazy-load.html): 延迟加载图片,优化体验 * [Latex](https://artalk.js.org/guide/frontend/latex.html): Latex 公式解析集成 * [夜间模式](https://artalk.js.org/guide/frontend/config.html#darkmode): 夜间模式切换 -* [扩展插件](https://artalk.js.org/develop/): 创造更多可能性 +* [扩展插件](https://artalk.js.org/develop/plugin.html): 创造更多可能性 * [多语言](https://artalk.js.org/guide/frontend/i18n.html): 多国语言切换 * [命令行](https://artalk.js.org/guide/backend/config.html): 命令行操作管理能力 -* [API 文档](https://artalk.js.org/develop/): 提供 OpenAPI 格式文档 +* [API 文档](https://artalk.js.org/http-api.html): 提供 OpenAPI 格式文档 * [程序升级](https://artalk.js.org/guide/backend/update.html): 版本检测,一键升级 diff --git a/docs/docs/guide/intro.md b/docs/docs/guide/intro.md index 469e6d76f..e3256bcc7 100644 --- a/docs/docs/guide/intro.md +++ b/docs/docs/guide/intro.md @@ -45,10 +45,10 @@ Artalk 的功能包括但不限于: * [图片懒加载](https://artalk.js.org/guide/frontend/img-lazy-load.html): 延迟加载图片,优化体验 * [Latex](https://artalk.js.org/guide/frontend/latex.html): Latex 公式解析集成 * [夜间模式](https://artalk.js.org/guide/frontend/config.html#darkmode): 夜间模式切换 -* [扩展插件](https://artalk.js.org/develop/): 创造更多可能性 +* [扩展插件](https://artalk.js.org/develop/plugin.html): 创造更多可能性 * [多语言](https://artalk.js.org/guide/frontend/i18n.html): 多国语言切换 * [命令行](https://artalk.js.org/guide/backend/config.html): 命令行操作管理能力 -* [API 文档](https://artalk.js.org/develop/): 提供 OpenAPI 格式文档 +* [API 文档](https://artalk.js.org/http-api.html): 提供 OpenAPI 格式文档 * [程序升级](https://artalk.js.org/guide/backend/update.html): 版本检测,一键升级 diff --git a/docs/landing/index.html b/docs/landing/index.html index b521c4432..a23f8ee4f 100644 --- a/docs/landing/index.html +++ b/docs/landing/index.html @@ -1,5 +1,5 @@ - - + + @@ -7,8 +7,11 @@ name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi" /> - Artalk | 自托管评论系统 - + Artalk - A Self-hosted Comment System +
diff --git a/docs/landing/package.json b/docs/landing/package.json index 801b477df..22c5ee2b5 100644 --- a/docs/landing/package.json +++ b/docs/landing/package.json @@ -22,8 +22,13 @@ "react-icons": "5.3.0" }, "devDependencies": { + "@headlessui/react": "^2.1.8", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react-swc": "^3.7.0" + "@vitejs/plugin-react-swc": "^3.7.0", + "i18next": "^23.15.1", + "i18next-browser-languagedetector": "^8.0.0", + "react-i18next": "^15.0.2", + "usehooks-ts": "^3.1.0" } } diff --git a/docs/landing/public/images/demo-video-1-thumbnail.png b/docs/landing/public/images/demo-video-1-thumbnail.png index afd6f6364ae181ab015669bd25c69c8386e78137..62ca4642eef04a9f4801f9840c28e71ee9f736c5 100644 GIT binary patch literal 26131 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 literal 47505 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

OT^k#lYf`Ohh0)AJsmh+L>|Q|1e+kuZ7KWZ(NJi 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 diff --git a/docs/landing/scripts/update-readme.ts b/docs/landing/scripts/update-readme.ts index e01b379a9..00a7e18d3 100644 --- a/docs/landing/scripts/update-readme.ts +++ b/docs/landing/scripts/update-readme.ts @@ -1,19 +1,24 @@ import path from 'node:path' import { writeFileSync, readFileSync } from 'node:fs' -import { Features } from '../src/Features' +import { getFeatures } from '../src/features' +import { initI18nSSR } from '../src/i18n' + +const t = await initI18nSSR('zh-CN') const __dirname = new URL('.', import.meta.url).pathname -const features = Features.map(o => `* [${o.name}](${o.link}): ${o.desc}`).join('\n') +const features = getFeatures(t) + .map((o) => `* [${o.name}](${o.link}): ${o.desc}`) + .join('\n') -const pathList = [ - '../../../README.md', - '../../docs/guide/intro.md', -] +const pathList = ['../../../README.md', '../../docs/guide/intro.md'] -pathList.forEach(p => { +pathList.forEach((p) => { p = path.resolve(__dirname, p) - const newReadme = readFileSync(p, 'utf-8').replace(/[\s\S]*/, `\n${features}\n`) + const newReadme = readFileSync(p, 'utf-8').replace( + /[\s\S]*/, + `\n${features}\n`, + ) writeFileSync(p, newReadme) console.log(`Updated ${p}`) diff --git a/docs/landing/src/App.scss b/docs/landing/src/App.scss index 26c57745d..90e11dc39 100644 --- a/docs/landing/src/App.scss +++ b/docs/landing/src/App.scss @@ -2,21 +2,54 @@ box-sizing: border-box; } +:root { + --color-primary: #007bff; + --color-font: #333; + --color-font-secondary: #67757f; + --color-bg: #fff; + --color-bg-transparent: rgba(255, 255, 255, 0.9); + --color-bg-grey: #f6f8fa; + --color-border: #e1e4ec; +} + body { - font: 16px "SF Pro Display", "SF Pro Icons", "Helvetica Neue", "Helvetica", "Arial", sans-serif; + font: + 16px 'SF Pro Display', + 'SF Pro Icons', + 'Helvetica Neue', + 'Helvetica', + 'Arial', + sans-serif; overflow-x: hidden; + background-color: var(--color-bg); + color: var(--color-font); } .container { width: 100%; margin: 0 auto; - @media (min-width: 640px) { max-width: 640px; } - @media (min-width: 720px) { max-width: 720px; } - @media (min-width: 768px) { max-width: 768px; } - @media (min-width: 1024px) { max-width: 1024px; } - @media (min-width: 1280px) { max-width: 1280px; } - @media (min-width: 1536px) { max-width: 1536px; } + @media (min-width: 640px) { + max-width: 590px; + } + @media (min-width: 720px) { + max-width: 670px; + } + @media (min-width: 768px) { + max-width: 718px; + } + @media (min-width: 1024px) { + max-width: 974px; + } + @media (min-width: 1280px) { + max-width: 1230px; + } + @media (min-width: 1536px) { + max-width: 1446px; + } + @media (min-width: 1920px) { + max-width: 80%; + } } a { @@ -24,7 +57,7 @@ a { } .anim-fade-in { - animation: fadeIn .8s ease-in-out; + animation: fadeIn 0.8s ease-in-out; } @keyframes fadeIn { diff --git a/docs/landing/src/App.tsx b/docs/landing/src/App.tsx index fd25d0755..ec72d6048 100644 --- a/docs/landing/src/App.tsx +++ b/docs/landing/src/App.tsx @@ -1,6 +1,10 @@ +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' import 'normalize.css' import './App.scss' +import './darkmode.scss' +import { useDarkMode } from './hooks/darkmode' import { Header } from './components/Header' import { Slogan } from './components/Slogan' import { Footer } from './components/Footer' @@ -11,9 +15,22 @@ import { FuncFeature } from './components/Features/FuncFeature' import { SafeFeature } from './components/Features/SafeFeature' function App() { + const { t, i18n } = useTranslation() + const { isDarkMode, toggle: toggleDarkMode } = useDarkMode() + + useEffect(() => { + document.title = t('home_title') + document.documentElement.lang = i18n.language + }, [t, i18n.language]) + + useEffect(() => { + if (isDarkMode) document.documentElement.classList.add('app-dark-mode') + else document.documentElement.classList.remove('app-dark-mode') + }, [isDarkMode]) + return ( <> -

+
diff --git a/docs/landing/src/Features.ts b/docs/landing/src/Features.ts deleted file mode 100644 index 519bfdbb5..000000000 --- a/docs/landing/src/Features.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { TbLayoutSidebarRightExpandFilled, TbMailFilled, TbEyeFilled, TbTransformFilled, TbLocationFilled, TbCardsFilled, TbPhotoSearch, TbMath, TbPlug, TbLanguage, TbTerminal, TbApi, TbSocial } from 'react-icons/tb' -import { BiSolidNotification, BiSolidBadgeCheck } from 'react-icons/bi' -import { RiLoader4Fill, RiPushpinLine, RiRobot2Fill, RiUpload2Fill } from 'react-icons/ri' -import { BsFillShieldLockFill } from 'react-icons/bs' -import { PiMoonFill, PiSmileyWinkBold } from 'react-icons/pi' -import { GrUpgrade } from 'react-icons/gr' -import { LuListTree, LuNewspaper } from 'react-icons/lu' -import { FaArrowTrendUp } from 'react-icons/fa6' -import { FaMarkdown, FaRegSave, FaSortAmountUpAlt } from 'react-icons/fa' -import { HiOutlineDocumentSearch } from 'react-icons/hi' -import { IoSearch, IoSend } from 'react-icons/io5' -import { IoMdLocate } from 'react-icons/io' - -export interface FeatureItem { - icon: React.FC - name: string - desc: string - link: string -} - -export const Features: FeatureItem[] = [ - { - icon: TbLayoutSidebarRightExpandFilled, - name: '侧边栏', - desc: '快速管理、直观浏览', - link: 'https://artalk.js.org/guide/frontend/sidebar.html' - }, - { - icon: TbSocial, - name: '社交登录', - desc: '通过社交账号快速登录', - link: 'https://artalk.js.org/guide/frontend/auth.html' - }, - { - icon: TbMailFilled, - name: '邮件通知', - desc: '多种发送方式、邮件模板', - link: 'https://artalk.js.org/guide/backend/email.html' - }, - { - icon: IoSend, - name: '多元推送', - desc: '多种推送方式、通知模版', - link: 'https://artalk.js.org/guide/backend/admin_notify.html' - }, - { - icon: BiSolidNotification, - name: '站内通知', - desc: '红点标记、提及列表', - link: 'https://artalk.js.org/guide/frontend/sidebar.html' - }, - { - icon: RiRobot2Fill, - name: '验证码', - desc: '多种验证类型、频率限制', - link: 'https://artalk.js.org/guide/backend/captcha.html' - }, - { - icon: BsFillShieldLockFill, - name: '评论审核', - desc: '内容检测、垃圾拦截', - link: 'https://artalk.js.org/guide/backend/moderator.html' - }, - { - icon: RiUpload2Fill, - name: '图片上传', - desc: '自定义上传、支持图床', - link: 'https://artalk.js.org/guide/backend/img-upload.html' - }, - { - icon: FaMarkdown, - name: 'Markdown', - desc: '支持 Markdown 语法', - link: 'https://artalk.js.org/guide/intro.html' - }, - { - icon: PiSmileyWinkBold, - name: '表情包', - desc: '兼容 OwO,快速集成', - link: 'https://artalk.js.org/guide/frontend/emoticons.html' - }, - { - icon: TbCardsFilled, - name: '多站点', - desc: '站点隔离、集中管理', - link: 'https://artalk.js.org/guide/backend/multi-site.html' - }, - { - icon: BiSolidBadgeCheck, - name: '管理员', - desc: '密码验证、徽章标识', - link: 'https://artalk.js.org/guide/backend/multi-site.html' - }, - { - icon: LuNewspaper, - name: '页面管理', - desc: '快速查看、标题一键跳转', - link: 'https://artalk.js.org/guide/frontend/sidebar.html', - }, - { - icon: TbEyeFilled, - name: '浏览量统计', - desc: '轻松统计网页浏览量', - link: 'https://artalk.js.org/guide/frontend/pv.html' - }, - { - icon: LuListTree, - name: '层级结构', - desc: '嵌套分页列表、滚动加载', - link: 'https://artalk.js.org/guide/frontend/config.html#nestmax', - }, - { - icon: FaArrowTrendUp, - name: '评论投票', - desc: '赞同或反对评论', - link: 'https://artalk.js.org/guide/frontend/config.html#vote', - }, - { - icon: FaSortAmountUpAlt, - name: '评论排序', - desc: '多种排序方式,自由选择', - link: 'https://artalk.js.org/guide/frontend/config.html#listsort', - }, - { - icon: IoSearch, - name: '评论搜索', - desc: '快速搜索评论内容', - link: 'https://artalk.js.org/guide/frontend/sidebar.html', - }, - { - icon: RiPushpinLine, - name: '评论置顶', - desc: '重要消息置顶显示', - link: 'https://artalk.js.org/guide/frontend/sidebar.html', - }, - { - icon: HiOutlineDocumentSearch, - name: '仅看作者', - desc: '仅显示作者的评论', - link: 'https://artalk.js.org/guide/frontend/config.html', - }, - { - icon: IoMdLocate, - name: '评论跳转', - desc: '快速跳转到引用的评论', - link: 'https://artalk.js.org/guide/intro.html', - }, - { - icon: FaRegSave, - name: '自动保存', - desc: '输入内容防丢功能', - link: 'https://artalk.js.org/guide/frontend/config.html', - }, - { - icon: TbLocationFilled, - name: 'IP 属地', - desc: '用户 IP 属地展示', - link: 'https://artalk.js.org/guide/frontend/ip-region.html' - }, - { - icon: TbTransformFilled, - name: '数据迁移', - desc: '自由迁移、快速备份', - link: 'https://artalk.js.org/guide/transfer.html' - }, - { - icon: TbPhotoSearch, - name: '图片灯箱', - desc: '图片灯箱快速集成', - link: 'https://artalk.js.org/guide/frontend/lightbox.html' - }, - { - icon: RiLoader4Fill, - name: '图片懒加载', - desc: '延迟加载图片,优化体验', - link: 'https://artalk.js.org/guide/frontend/img-lazy-load.html' - }, - { - icon: TbMath, - name: 'Latex', - desc: 'Latex 公式解析集成', - link: 'https://artalk.js.org/guide/frontend/latex.html' - }, - { - icon: PiMoonFill, - name: '夜间模式', - desc: '夜间模式切换', - link: 'https://artalk.js.org/guide/frontend/config.html#darkmode' - }, - { - icon: TbPlug, - name: '扩展插件', - desc: '创造更多可能性', - link: 'https://artalk.js.org/develop/plugin.html' - }, - { - icon: TbLanguage, - name: '多语言', - desc: '多国语言切换', - link: 'https://artalk.js.org/guide/frontend/i18n.html' - }, - { - icon: TbTerminal, - name: '命令行', - desc: '命令行操作管理能力', - link: 'https://artalk.js.org/guide/backend/config.html' - }, - { - icon: TbApi, - name: 'API 文档', - desc: '提供 OpenAPI 格式文档', - link: 'https://artalk.js.org/http-api.html' - }, - { - icon: GrUpgrade, - name: '程序升级', - desc: '版本检测,一键升级', - link: 'https://artalk.js.org/guide/backend/update.html' - } -] diff --git a/docs/landing/src/components/FeatureDesc.scss b/docs/landing/src/components/FeatureDesc.scss index a480930d2..b813b784e 100644 --- a/docs/landing/src/components/FeatureDesc.scss +++ b/docs/landing/src/components/FeatureDesc.scss @@ -1,6 +1,6 @@ .feature-desc { font-size: 24px; line-height: 2; - color: #67757F; + color: var(--color-font-secondary); margin: 60px 0; } diff --git a/docs/landing/src/components/FeatureDesc.tsx b/docs/landing/src/components/FeatureDesc.tsx index 74829e935..4f9727228 100644 --- a/docs/landing/src/components/FeatureDesc.tsx +++ b/docs/landing/src/components/FeatureDesc.tsx @@ -3,9 +3,5 @@ import './FeatureDesc.scss' export const FeatureDesc: React.FC<{ children: React.ReactNode }> = ({ children }) => { - return ( -
- {children} -
- ) + return
{children}
} diff --git a/docs/landing/src/components/FeatureTitle.tsx b/docs/landing/src/components/FeatureTitle.tsx index e2d539575..157e2705c 100644 --- a/docs/landing/src/components/FeatureTitle.tsx +++ b/docs/landing/src/components/FeatureTitle.tsx @@ -9,8 +9,9 @@ interface FeatureTitleProps { export const FeatureTitle: React.FC = (props) => { return ( -
- {props.text.slice(0, 1)}{props.text.slice(1, props.text.length)} +
+ {props.text.slice(0, 1)} + {props.text.slice(1, props.text.length)}
) diff --git a/docs/landing/src/components/Features/FeatureBase.tsx b/docs/landing/src/components/Features/FeatureBase.tsx index d4857c65f..04bbbddf1 100644 --- a/docs/landing/src/components/Features/FeatureBase.tsx +++ b/docs/landing/src/components/Features/FeatureBase.tsx @@ -9,11 +9,15 @@ interface FeatureBaseProps extends React.ComponentProps<'div'> { export const FeatureBase: React.FC = (props) => { const { className, onVisibleChange, ...otherProps } = props - const refElem = useRef(null) const isSlightFeatureVisible = useIsElementVisible(refElem) - const classNames = ['feature', 'container', isSlightFeatureVisible ? 'visible' : '', ...(className?.split(' ') || [])] + const classNames = [ + 'feature', + 'container', + isSlightFeatureVisible ? 'visible' : '', + ...(className?.split(' ') || []), + ] useEffect(() => { onVisibleChange && onVisibleChange(isSlightFeatureVisible) diff --git a/docs/landing/src/components/Features/FullFeature.scss b/docs/landing/src/components/Features/FullFeature.scss index 7ee2cfd94..63c4b5996 100644 --- a/docs/landing/src/components/Features/FullFeature.scss +++ b/docs/landing/src/components/Features/FullFeature.scss @@ -5,7 +5,6 @@ .row { display: flex; margin-bottom: 30px; - place-items: center; justify-items: stretch; &:nth-child(even) { @@ -17,15 +16,15 @@ } .item { - color: #000; + color: var(--color-font); margin: 0 15px; flex: 1; font-size: 25px; border-radius: 20px; - border: 3px solid #E1E4EC; - background: #fff; + border: 3px solid var(--color-border); + background: var(--color-bg); padding: 40px 50px; - transition: transform 200ms cubic-bezier(0.750, 0.250, 0.250, 0.750); + transition: transform 200ms cubic-bezier(0.75, 0.25, 0.25, 0.75); &:hover { transform: scale(1.05); @@ -37,7 +36,7 @@ margin-bottom: 20px; svg { - margin-right: .5em; + margin-right: 0.5em; } .text { @@ -47,7 +46,7 @@ .body { .desc { - color: #67757F; + color: var(--color-font-secondary); display: flex; place-items: center; min-height: 3em; @@ -63,21 +62,22 @@ .folder-icon.animate { @keyframes fileUpAnim { - 50% { transform: translateY(-10%) } + 50% { + transform: translateY(-10%); + } } .file-1 { - animation: fileUpAnim 1s cubic-bezier(0.860, 0.000, 0.070, 1.000) forwards; + animation: fileUpAnim 1s cubic-bezier(0.86, 0, 0.07, 1) forwards; } - .file-2 { - animation: fileUpAnim 1s cubic-bezier(0.860, 0.000, 0.070, 1.000) forwards; + animation: fileUpAnim 1s cubic-bezier(0.86, 0, 0.07, 1) forwards; animation-delay: 500ms; } .file-3 { - animation: fileUpAnim 1s cubic-bezier(0.860, 0.000, 0.070, 1.000) forwards; + animation: fileUpAnim 1s cubic-bezier(0.86, 0, 0.07, 1) forwards; animation-delay: 1s; } } diff --git a/docs/landing/src/components/Features/FullFeature.tsx b/docs/landing/src/components/Features/FullFeature.tsx index c1b0160a9..17b29f51a 100644 --- a/docs/landing/src/components/Features/FullFeature.tsx +++ b/docs/landing/src/components/Features/FullFeature.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import './FullFeature.scss' import { FeatureTitle } from '../FeatureTitle' import { FeatureDesc } from '../FeatureDesc' @@ -8,17 +9,17 @@ import { FolderIcon } from './FullFeatureIcon' import { FullFeatureList } from './FullFeatureList' export const FullFeature: React.FC = () => { + const { t } = useTranslation() + return ( - - + +
- - Artalk 提供丰富的内置功能,我们尽力在简洁的同时保持功能的全面,为您带来开箱即用的体验。 - + {t('feature_full_desc_line_1')}
diff --git a/docs/landing/src/components/Features/FullFeatureIcon.tsx b/docs/landing/src/components/Features/FullFeatureIcon.tsx index a9afc25d7..bddb82890 100644 --- a/docs/landing/src/components/Features/FullFeatureIcon.tsx +++ b/docs/landing/src/components/Features/FullFeatureIcon.tsx @@ -7,8 +7,8 @@ export const FolderIcon: React.FC = () => { const visible = useIsElementVisible(refEl) useEffect(() => { - let animateTimer: number|null = null - let cleanUpTimer: number|null = null + let animateTimer: number | null = null + let cleanUpTimer: number | null = null const triggerAnimate = () => { setAnimate(true) cleanUpTimer && window.clearTimeout(cleanUpTimer) @@ -32,28 +32,70 @@ export const FolderIcon: React.FC = () => { return (
- - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + +
) diff --git a/docs/landing/src/components/Features/FullFeatureList.tsx b/docs/landing/src/components/Features/FullFeatureList.tsx index 2fc10e399..91ba15842 100644 --- a/docs/landing/src/components/Features/FullFeatureList.tsx +++ b/docs/landing/src/components/Features/FullFeatureList.tsx @@ -1,20 +1,23 @@ import React from 'react' -import { Features, FeatureItem } from '../../Features' +import { useTranslation } from 'react-i18next' +import { getFeatures, FeatureItem } from '../../features.ts' export const FullFeatureList: React.FC = () => { + const { t } = useTranslation() + // let func list item group by every two items - const FuncListGrouped = Features.reduce((result, current, index) => { + const FuncListGrouped = getFeatures(t).reduce((result, current, index) => { if (index % 3 === 0) result.push([current]) else result[result.length - 1].push(current) return result }, []) return ( -
+
{FuncListGrouped.map((row, i) => ( -
+
{row.map((item, j) => ( - +
{item.name} diff --git a/docs/landing/src/components/Features/FuncFeature.scss b/docs/landing/src/components/Features/FuncFeature.scss index f88fb67f3..9a3da604a 100644 --- a/docs/landing/src/components/Features/FuncFeature.scss +++ b/docs/landing/src/components/Features/FuncFeature.scss @@ -13,7 +13,7 @@ } .wave { - animation: .5s linear WaveAnim infinite; + animation: 0.5s linear WaveAnim infinite; } } @@ -30,13 +30,13 @@ display: flex; place-items: center; justify-content: space-between; - color: #000; + color: var(--color-font); font-size: 27px; margin-bottom: 25px; &:hover { svg { - animation: .5s cubic-bezier(0.25, 1, 0.5, 1) funcGrpNameArrowAnim; + animation: 0.5s cubic-bezier(0.25, 1, 0.5, 1) funcGrpNameArrowAnim; opacity: 1; } } @@ -51,7 +51,7 @@ svg { margin: 0 20px; opacity: 0; - color: #67757F; + color: #67757f; } } @@ -63,7 +63,7 @@ .item { font-size: 20px; - background: #F8FAFC; + background: var(--color-bg-grey); border-radius: 20px; padding: 20px 0; text-align: center; diff --git a/docs/landing/src/components/Features/FuncFeature.tsx b/docs/landing/src/components/Features/FuncFeature.tsx index 0ae2ef037..c7d989a45 100644 --- a/docs/landing/src/components/Features/FuncFeature.tsx +++ b/docs/landing/src/components/Features/FuncFeature.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import './FuncFeature.scss' import { FaArrowRight } from 'react-icons/fa' import { FeatureTitle } from '../FeatureTitle' @@ -6,48 +7,179 @@ import { FeatureDesc } from '../FeatureDesc' import { Reveal } from '../Reveal' import { FeatureBase } from './FeatureBase' -const FuncGrps: {name: string, items: string[], link?: string}[] = [ - { name: '社交登录', items: ['Github', 'GitLab', 'Twitter', 'Facebook', 'Mastodon', 'Google', 'Microsoft', 'Apple', 'Discord', 'Slack', 'Tiktok', 'Steam'], link: 'https://artalk.js.org/guide/frontend/auth.html' }, - { name: '邮箱发送', items: ['SMTP', '阿里云邮件', 'sendmail'], link: 'https://artalk.js.org/guide/backend/email.html' }, - { name: '验证码', items: ['Turnstile', 'reCAPTCHA', 'hCaptcha', '极验'], link: 'https://artalk.js.org/guide/backend/captcha.html' }, - { name: '消息推送', items: ['Telegram', '飞书', '钉钉', 'Bark', 'WebHook', 'Slack', 'LINE'], link: 'https://artalk.js.org/guide/backend/admin_notify.html' }, - { name: '评论审核', items: ['Akismet', '腾讯云', '阿里云', '离线词库'], link: 'https://artalk.js.org/guide/backend/moderator.html' }, - { name: '表情包', items: ['标准格式', 'OwO 格式'], link: 'https://artalk.js.org/guide/frontend/emoticons.html' }, - { name: '图片上传', items: ['本地保存', 'UPGIT', '自定义函数'], link: 'https://artalk.js.org/guide/backend/img-upload.html' }, - { name: '富文本', items: ['Markdown', 'Latex'], link: 'https://artalk.js.org/guide/frontend/latex.html' }, - { name: '代码高亮', items: ['hanabi', 'highlight.js'] }, - { name: '用户头像', items: ['Gravatar', '自定义函数'], link: 'https://artalk.js.org/guide/frontend/config.html' }, - { name: '图片灯箱', items: ['LightGallery', 'FancyBox', 'lightbox2'], link: 'https://artalk.js.org/guide/frontend/lightbox.html' }, - { name: '数据库', items: ['SQLite', 'MySQL', 'PostgreSQL', 'SQL Server'], link: 'https://artalk.js.org/guide/backend/config.html' }, - { name: '高速缓存', items: ['内建缓存', 'Redis', 'Memcache'], link: 'https://artalk.js.org/guide/backend/config.html' }, - { name: '程序部署', items: ['二进制文件', 'Docker 镜像'], link: 'https://artalk.js.org/guide/deploy.html' }, - { name: '操作系统', items: ['Linux', 'Windows', 'macOS', 'FreeBSD'] }, - { name: '平台架构', items: ['x86', 'ARM'] }, - { name: '评论导入', items: ['Typecho', 'WordPress', 'Valine', 'Waline', 'Disqus', 'Commento', 'Twikoo', 'Artrans'], link: 'https://artalk.js.org/guide/transfer.html' }, -] - export const FuncFeature: React.FC = () => { + const { t } = useTranslation() + + const FuncGrps: { name: string; items: string[]; link?: string }[] = [ + { + name: t('func_social_login'), + items: [ + 'Github', + 'GitLab', + 'Twitter', + 'Facebook', + 'Mastodon', + 'Google', + 'Microsoft', + 'Apple', + 'Discord', + 'Slack', + 'Tiktok', + 'Steam', + ], + link: 'https://artalk.js.org/guide/frontend/auth.html', + }, + { + name: t('func_email'), + items: ['SMTP', t('func_email_aliyun'), 'sendmail'], + link: 'https://artalk.js.org/guide/backend/email.html', + }, + { + name: t('func_captcha'), + items: ['Turnstile', 'reCAPTCHA', 'hCaptcha', t('func_captcha_geetest')], + link: 'https://artalk.js.org/guide/backend/captcha.html', + }, + { + name: t('func_message_pusher'), + items: [ + 'Telegram', + t('func_message_pusher_lark'), + t('func_message_pusher_dingtalk'), + 'Bark', + 'WebHook', + 'Slack', + 'LINE', + ], + link: 'https://artalk.js.org/guide/backend/admin_notify.html', + }, + { + name: t('func_moderator'), + items: [ + 'Akismet', + t('func_moderator_tencent'), + t('func_moderator_aliyun'), + t('func_moderator_offline'), + ], + link: 'https://artalk.js.org/guide/backend/moderator.html', + }, + { + name: t('func_emoji'), + items: [t('func_emoji_standard_format'), t('func_emoji_owo_format')], + link: 'https://artalk.js.org/guide/frontend/emoticons.html', + }, + { + name: t('func_img_upload'), + items: [t('func_img_upload_local'), 'UPGIT', t('func_img_upload_function')], + link: 'https://artalk.js.org/guide/backend/img-upload.html', + }, + { + name: t('func_rich_text'), + items: ['Markdown', 'Latex'], + link: 'https://artalk.js.org/guide/frontend/latex.html', + }, + { name: t('func_code_highlight'), items: ['hanabi', 'highlight.js'] }, + { + name: t('func_avatar'), + items: ['Gravatar', t('func_avatar_function')], + link: 'https://artalk.js.org/guide/frontend/config.html', + }, + { + name: t('func_img_lightbox'), + items: ['LightGallery', 'FancyBox', 'lightbox2'], + link: 'https://artalk.js.org/guide/frontend/lightbox.html', + }, + { + name: t('func_database'), + items: ['SQLite', 'MySQL', 'PostgreSQL', 'SQL Server'], + link: 'https://artalk.js.org/guide/backend/config.html', + }, + { + name: t('func_cache'), + items: [t('func_cache_internal'), 'Redis', 'Memcache'], + link: 'https://artalk.js.org/guide/backend/config.html', + }, + { + name: t('func_deploy'), + items: [t('func_deploy_bin'), t('func_deploy_docker')], + link: 'https://artalk.js.org/guide/deploy.html', + }, + { name: t('func_os'), items: ['Linux', 'Windows', 'macOS', 'FreeBSD'] }, + { name: t('func_platform'), items: ['x86', 'ARM'] }, + { + name: t('func_transfer_import'), + items: [ + 'Typecho', + 'WordPress', + 'Valine', + 'Waline', + 'Disqus', + 'Commento', + 'Twikoo', + 'Artrans', + ], + link: 'https://artalk.js.org/guide/transfer.html', + }, + ] + return ( - - + +
- - Artalk 提供丰富第三方接入能力。 - + {t('feature_func_desc_line_1')}
@@ -55,13 +187,18 @@ export const FuncFeature: React.FC = () => { {FuncGrps.map((grp, i) => (
{grp.link ? ( - {grp.name} + + {grp.name} + + ) : ( -
{grp.name}
+
{grp.name}
)}
{grp.items.map((item, j) => ( -
{item}
+
+ {item} +
))}
diff --git a/docs/landing/src/components/Features/QuickFeature.scss b/docs/landing/src/components/Features/QuickFeature.scss index f8abb6cf2..bffb1797d 100644 --- a/docs/landing/src/components/Features/QuickFeature.scss +++ b/docs/landing/src/components/Features/QuickFeature.scss @@ -1,6 +1,6 @@ .feature.quick { .deploy-links { - margin: 80px 30px; + margin: 80px 0; display: flex; flex-direction: column; @@ -11,7 +11,7 @@ flex-wrap: wrap; place-items: flex-end; font-size: 35px; - color: #000; + color: var(--color-font); &:not(:last-child) { margin-bottom: 1.8em; @@ -31,7 +31,7 @@ .bold { font-size: 1.5em; - margin-right: .2em; + margin-right: 0.2em; } span { @@ -39,6 +39,7 @@ } .arrow-icon { + margin-left: auto; position: relative; display: flex; flex-direction: row; @@ -52,11 +53,11 @@ top: 0; position: absolute; display: block; - background: #55ACEE; + background: #55acee; height: 2em; - width: .6em; - margin-right: .3em; - transition: width 500ms cubic-bezier(0.075, 0.820, 0.165, 1.000); + width: 0.6em; + margin-right: 0.3em; + transition: width 500ms cubic-bezier(0.075, 0.82, 0.165, 1); } svg { @@ -77,7 +78,7 @@ .airplane { opacity: 0; - transition: opacity .4s; + transition: opacity 0.4s; } &.visible { @@ -89,7 +90,7 @@ .airplane { opacity: 1; - animation: airplaneAnim 2s cubic-bezier(0.075, 0.820, 0.165, 1.000); + animation: airplaneAnim 2s cubic-bezier(0.075, 0.82, 0.165, 1); } } @@ -117,8 +118,8 @@ align-items: center; flex-direction: row; margin-bottom: 1.5em; - background: #f6f8fa; - color: #67757F; + background: var(--color-bg-grey); + color: #67757f; .content { padding: 0 30px; @@ -130,7 +131,7 @@ place-items: center; padding: 0 20px; height: 100%; - background: #00ACD7; + background: #00acd7; color: #fff; cursor: pointer; diff --git a/docs/landing/src/components/Features/QuickFeature.tsx b/docs/landing/src/components/Features/QuickFeature.tsx index 1dec3a101..8ebf5c19c 100644 --- a/docs/landing/src/components/Features/QuickFeature.tsx +++ b/docs/landing/src/components/Features/QuickFeature.tsx @@ -1,5 +1,6 @@ import React from 'react' import './QuickFeature.scss' +import { useTranslation } from 'react-i18next' import { FiArrowUpRight } from 'react-icons/fi' import { FeatureTitle } from '../FeatureTitle' import { LearnMoreLink } from '../LearnMoreLink' @@ -8,10 +9,12 @@ import { Reveal } from '../Reveal' import { FeatureBase } from './FeatureBase' export const QuickFeature: React.FC = () => { + const { t } = useTranslation() + const ArrowIcon = ( -
+
- +
) @@ -39,29 +42,60 @@ export const QuickFeature: React.FC = () => { } return ( - - + + -
+ {/* Golang Description */} -
+
- Artalk 采用 Golang 编写,快速跨平台启动你的评论服务器,
我们提供了 Docker 镜像,满足更多部署需求。
+ {t('feature_swift_desc_line_1')} +
+ {t('feature_swift_desc_line_2')} +
- -
docker pull artalk/artalk-go
-
{isCopied ? 'Copied!' : 'Copy'}
+
+
docker pull artalk/artalk-go
+
+ {isCopied ? t('copied') : t('copy')} +
- +
- - - - - - - - - - - - - + + + + + + + + + + + + +
diff --git a/docs/landing/src/components/Features/SafeFeature.tsx b/docs/landing/src/components/Features/SafeFeature.tsx index 4a12079d0..60e5ef86a 100644 --- a/docs/landing/src/components/Features/SafeFeature.tsx +++ b/docs/landing/src/components/Features/SafeFeature.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import './SafeFeature.scss' import { FeatureTitle } from '../FeatureTitle' import { FeatureDesc } from '../FeatureDesc' @@ -6,22 +7,53 @@ import { Reveal } from '../Reveal' import { FeatureBase } from './FeatureBase' export const SafeFeature: React.FC = () => { + const { t } = useTranslation() + return ( - - + + -
+
- 开源自由软件,自托管评论数据,透明可控。
内置数据迁移工具,轻松转移您的评论内容。

- 我们始终将安全放在首位,并持续提供更新。 + + {t('feature_safe_desc_line_1')} +
+ {t('feature_safe_desc_line_2')} +
+
+
+ {t('feature_safe_desc_line_3')}
diff --git a/docs/landing/src/components/Features/SlightFeature.scss b/docs/landing/src/components/Features/SlightFeature.scss index aa514b594..4b865219c 100644 --- a/docs/landing/src/components/Features/SlightFeature.scss +++ b/docs/landing/src/components/Features/SlightFeature.scss @@ -9,7 +9,7 @@ position: relative; height: 24px; width: 100%; - background: #D9D9D9; + background: var(--color-bg-grey); border-radius: 80px; overflow: hidden; @@ -17,8 +17,8 @@ position: absolute; height: 100%; width: 0%; - background: #77B255; - transition: all 800ms cubic-bezier(0.230, 1.000, 0.320, 1.000); + background: #77b255; + transition: all 800ms cubic-bezier(0.23, 1, 0.32, 1); } } @@ -31,12 +31,12 @@ font-size: 23px; .size { - color: #77B255; + color: #77b255; font-weight: bold; } .percent { - color: #67757F; + color: #67757f; } } } @@ -53,7 +53,7 @@ } } .wind { - animation: windAnim .5s cubic-bezier(0.230, 1.000, 0.320, 1.000); + animation: windAnim 0.5s cubic-bezier(0.23, 1, 0.32, 1); } @keyframes leafAnim { @@ -63,7 +63,7 @@ } .leaf { - animation: leafAnim 1s cubic-bezier(0.250, 0.460, 0.450, 0.940); + animation: leafAnim 1s cubic-bezier(0.25, 0.46, 0.45, 0.94); } } } diff --git a/docs/landing/src/components/Features/SlightFeature.tsx b/docs/landing/src/components/Features/SlightFeature.tsx index 53d725104..5848ff841 100644 --- a/docs/landing/src/components/Features/SlightFeature.tsx +++ b/docs/landing/src/components/Features/SlightFeature.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' import { FeatureTitle } from '../FeatureTitle' import { Reveal } from '../Reveal' import { FeatureDesc } from '../FeatureDesc' @@ -8,13 +9,14 @@ import './SlightFeature.scss' export const SlightFeature: React.FC = () => { const [percent, setPercent] = useState(0) const [visible, setVisible] = useState(false) + const { t } = useTranslation() const onVisibleChange = (visible: boolean) => { setVisible(visible) if (visible) { const fn = setInterval(() => { setPercent((prev) => { - const next = Math.round(prev+0.5) + const next = Math.round(prev + 0.5) if (next <= 100) { return next } else { @@ -29,17 +31,14 @@ export const SlightFeature: React.FC = () => { } return ( - - + +
-
= 100 || visible ? 100 : 0}%`}} /> +
= 100 || visible ? 100 : 0}%` }} />
40KB
@@ -49,18 +48,37 @@ export const SlightFeature: React.FC = () => { - 原生 JS 前端程序,核心功能无依赖框架,
小巧体积、快速加载,迅速响应每刻交互。 + {t('feature_slight_desc_line_1')} +
+ {t('feature_slight_desc_line_2')}
diff --git a/docs/landing/src/components/Footer.scss b/docs/landing/src/components/Footer.scss index cb2f8de6d..9dd8d195b 100644 --- a/docs/landing/src/components/Footer.scss +++ b/docs/landing/src/components/Footer.scss @@ -2,10 +2,10 @@ margin: 80px auto; padding-top: 50px; text-align: center; - border-top: 1px solid #f4f4f4; + border-top: 1px solid var(--color-border); .brand { - color: #0071E3; + color: var(--color-primary); font-size: 25px; font-weight: 700; } @@ -15,7 +15,7 @@ font-size: 20px; .red { - color: #DD2E44; + color: #dd2e44; } } } diff --git a/docs/landing/src/components/Footer.tsx b/docs/landing/src/components/Footer.tsx index ae71a7951..2f5e8063e 100644 --- a/docs/landing/src/components/Footer.tsx +++ b/docs/landing/src/components/Footer.tsx @@ -3,9 +3,11 @@ import './Footer.scss' export const Footer: React.FC = () => { return ( -
-
Artalk
-
The Artalk. Made with ♥️.
+
+
Artalk
+
+ The Artalk. Made with ♥️. +
) } diff --git a/docs/landing/src/components/Header.scss b/docs/landing/src/components/Header.scss index aeb2ff530..69cd0e787 100644 --- a/docs/landing/src/components/Header.scss +++ b/docs/landing/src/components/Header.scss @@ -3,7 +3,7 @@ position: sticky; top: 0; left: 0; - transition: background .2s ease; + transition: background 0.2s ease; .container { height: 70px; @@ -14,12 +14,12 @@ } &.fixed { - border-bottom: 1px solid #f4f4f4; - background: rgba(255, 255, 255, 0.9); + border-bottom: 1px solid var(--color-border); + background: var(--color-bg-transparent); } .brand { - color: #0071E3; + color: var(--color-primary); font-size: 25px; font-weight: 700; } @@ -31,15 +31,58 @@ justify-content: space-between; .link-item { - color: #000; + display: flex; + flex-direction: row; + align-items: center; + color: var(--color-font); font-weight: bold; cursor: pointer; + .language-toggle { + margin-left: -4px; + display: flex; + flex-direction: row; + align-items: center; + } + + button { + all: unset; + } + &:not(:last-child) { margin-right: 40px; } } } + + .menu-toggle-mobile { + display: none; + } } +.header-language-list { + z-index: 1000; + background: var(--color-bg); + display: flex; + flex-direction: column; + border-radius: 15px; + overflow: hidden; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + margin-top: 15px; + padding: 7px; + .language-item { + border-radius: 10px; + color: var(--color-font); + padding: 10px 20px; + cursor: pointer; + + &:not(:last-child) { + margin-bottom: 5px; + } + + &:hover { + background: var(--color-bg-grey); + } + } +} diff --git a/docs/landing/src/components/Header.tsx b/docs/landing/src/components/Header.tsx index 30e4c853a..4cb992925 100644 --- a/docs/landing/src/components/Header.tsx +++ b/docs/landing/src/components/Header.tsx @@ -1,9 +1,57 @@ import React, { useEffect, useState } from 'react' -import './Header.scss' +import { useTranslation } from 'react-i18next' +import { Popover, PopoverButton, PopoverPanel, useClose } from '@headlessui/react' +import { MdArrowDropDown, MdClose, MdDarkMode, MdMenu, MdSunny } from 'react-icons/md' import { AiFillGithub } from 'react-icons/ai' +import { IoLanguage } from 'react-icons/io5' +import './Header.scss' + +const LanguageList: React.FC = () => { + const close = useClose() + + const { i18n } = useTranslation() + + const languages = [ + { + name: 'English', + code: 'en', + }, + { + name: '简体中文', + code: 'zh', + }, + ] -export const Header: React.FC = () => { + const changeLanguage = (lang: string) => { + i18n.changeLanguage(lang) + close() + } + + return ( + <> + {languages.map((lang) => ( + { + changeLanguage(lang.code) + }} + > + {lang.name} + + ))} + + ) +} + +export interface HeaderProps { + darkModeHandler: { isDarkMode: boolean; toggle: () => void } +} + +export const Header: React.FC = ({ darkModeHandler }) => { + const { t } = useTranslation() const [fixed, setFixed] = useState(false) + const [mobileShow, setMobileShow] = useState(false) useEffect(() => { const scrollHandler = () => { @@ -21,18 +69,44 @@ export const Header: React.FC = () => { return (
-
-
- Artalk -
+ ) diff --git a/docs/landing/src/components/LearnMoreLink.scss b/docs/landing/src/components/LearnMoreLink.scss index c14931efa..3ddf87ea2 100644 --- a/docs/landing/src/components/LearnMoreLink.scss +++ b/docs/landing/src/components/LearnMoreLink.scss @@ -4,12 +4,12 @@ font-size: 24px; .prompt { - color: #000; + color: var(--color-font); } .link-btn { cursor: pointer; - color: #0071E3; + color: var(--color-primary); .icon { margin-left: 5px; diff --git a/docs/landing/src/components/LearnMoreLink.tsx b/docs/landing/src/components/LearnMoreLink.tsx index 48d173243..ffc8aab7e 100644 --- a/docs/landing/src/components/LearnMoreLink.tsx +++ b/docs/landing/src/components/LearnMoreLink.tsx @@ -1,5 +1,6 @@ import React from 'react' import { FaArrowRight } from 'react-icons/fa' +import { useTranslation } from 'react-i18next' import './LearnMoreLink.scss' interface LearnMoreLinkProps { @@ -8,10 +9,17 @@ interface LearnMoreLinkProps { } export const LearnMoreLink: React.FC = (props) => { + const { t } = useTranslation() + return (
- {props.prompt} - 了解更多 + {props.prompt}   + + {t('learn_more')}{' '} + + + +
) } diff --git a/docs/landing/src/components/Reveal.scss b/docs/landing/src/components/Reveal.scss index 9f105a7ee..01138d25a 100644 --- a/docs/landing/src/components/Reveal.scss +++ b/docs/landing/src/components/Reveal.scss @@ -4,7 +4,7 @@ @keyframes fadeInUp { 0% { opacity: 0; - transform: translate3d(0,100%,0); + transform: translate3d(0, 100%, 0); } 100% { diff --git a/docs/landing/src/components/Reveal.tsx b/docs/landing/src/components/Reveal.tsx index fb07f273a..e1e6b665e 100644 --- a/docs/landing/src/components/Reveal.tsx +++ b/docs/landing/src/components/Reveal.tsx @@ -9,12 +9,15 @@ interface RevelProps { } export const Reveal: React.FC = (props) => { - props = {...{ - // default config - threshold: 0.5, - duration: 1000, - delay: 0 - }, ...props} + props = { + ...{ + // default config + threshold: 0.5, + duration: 1000, + delay: 0, + }, + ...props, + } const [isVisible, setIsVisible] = useState(false) const elementRef = useRef(null) @@ -27,14 +30,17 @@ export const Reveal: React.FC = (props) => { return } - const observer = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting) { - setIsVisible(true) - observerRefValue && observer.unobserve(observerRefValue) - } - }, { - threshold: props.threshold - }) + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + setIsVisible(true) + observerRefValue && observer.unobserve(observerRefValue) + } + }, + { + threshold: props.threshold, + }, + ) observerRefValue && observer.observe(observerRefValue) diff --git a/docs/landing/src/components/Slogan.scss b/docs/landing/src/components/Slogan.scss index 0d5a38b5b..1a4ff6d0f 100644 --- a/docs/landing/src/components/Slogan.scss +++ b/docs/landing/src/components/Slogan.scss @@ -21,6 +21,7 @@ width: 35vw; .demo-video { + border-radius: 4px; width: 100%; height: auto; object-fit: contain; @@ -36,7 +37,7 @@ .highlight { overflow: hidden; position: relative; - color: rgba(0, 113, 227, 0.9); + color: var(--color-primary); white-space: nowrap; @keyframes wipe { @@ -45,14 +46,14 @@ } 100% { - stroke: #BDCFFF + stroke: #bdcfff; } } .line-wrap { z-index: -1; position: absolute; - opacity: .7; + opacity: 0.7; bottom: -8.5px; left: 0; @@ -81,23 +82,24 @@ justify-content: center; place-items: center; cursor: pointer; - color: #000; + color: var(--color-font); &:not(:last-child) { margin-right: 15px; } &.blue { - background: #0071E3; + background: var(--color-primary); color: #fff; &:hover { - background: #0080ff; + opacity: 0.9; + background: var(--color-primary); } } &:hover { - background: #f4f4f4; + background: var(--color-bg-grey); } svg { diff --git a/docs/landing/src/components/Slogan.tsx b/docs/landing/src/components/Slogan.tsx index f52b8ae90..a08b842a1 100644 --- a/docs/landing/src/components/Slogan.tsx +++ b/docs/landing/src/components/Slogan.tsx @@ -1,12 +1,15 @@ import './Slogan.scss' +import { useTranslation } from 'react-i18next' import { useEffect, useRef, useState } from 'react' import { FaArrowRight } from 'react-icons/fa' import { Reveal } from './Reveal' export const Slogan: React.FC = () => { + const { t } = useTranslation() type VideoState = 'ready' | 'loading' | 'playing' | 'paused' const [videoState, setVideoState] = useState('ready') - const videoLink = 'https://github.com/user-attachments/assets/d8d1597a-7e1f-45f3-963e-371c0528bf91' + const videoLink = + 'https://github.com/user-attachments/assets/d8d1597a-7e1f-45f3-963e-371c0528bf91' const videoMimeType = 'video/webm' // Must set for Safari const videoElRef = useRef(null) @@ -45,14 +48,27 @@ export const Slogan: React.FC = () => { }, [videoElRef, videoState]) return ( -
+
- + Self-hosted - - + +
@@ -60,20 +76,35 @@ export const Slogan: React.FC = () => { Comment System - -
+
{videoState !== 'playing' && ( - + )} -
-
-
+
) } diff --git a/docs/landing/src/darkmode.scss b/docs/landing/src/darkmode.scss new file mode 100644 index 000000000..023a9f127 --- /dev/null +++ b/docs/landing/src/darkmode.scss @@ -0,0 +1,9 @@ +.app-dark-mode { + --color-primary: #58a6ff; + --color-font: rgba(255, 255, 255, 0.8); + --color-font-secondary: #c9d1d9; + --color-bg: #22272e; + --color-bg-transparent: rgba(34, 39, 46, 0.9); + --color-bg-grey: #2d333b; + --color-border: #444c56; +} diff --git a/docs/landing/src/features.ts b/docs/landing/src/features.ts new file mode 100644 index 000000000..c476ae3a7 --- /dev/null +++ b/docs/landing/src/features.ts @@ -0,0 +1,235 @@ +import { + TbLayoutSidebarRightExpandFilled, + TbMailFilled, + TbEyeFilled, + TbTransformFilled, + TbLocationFilled, + TbCardsFilled, + TbPhotoSearch, + TbMath, + TbPlug, + TbLanguage, + TbTerminal, + TbApi, + TbSocial, +} from 'react-icons/tb' +import { BiSolidNotification, BiSolidBadgeCheck } from 'react-icons/bi' +import { RiLoader4Fill, RiPushpinLine, RiRobot2Fill, RiUpload2Fill } from 'react-icons/ri' +import { BsFillShieldLockFill } from 'react-icons/bs' +import { PiMoonFill, PiSmileyWinkBold } from 'react-icons/pi' +import { GrUpgrade } from 'react-icons/gr' +import { LuListTree, LuNewspaper } from 'react-icons/lu' +import { FaArrowTrendUp } from 'react-icons/fa6' +import { FaMarkdown, FaRegSave, FaSortAmountUpAlt } from 'react-icons/fa' +import { HiOutlineDocumentSearch } from 'react-icons/hi' +import { IoSearch, IoSend } from 'react-icons/io5' +import { IoMdLocate } from 'react-icons/io' +import { TFunction } from 'i18next' + +export interface FeatureItem { + icon: React.FC + name: string + desc: string + link: string +} + +export const getFeatures = (t: TFunction): FeatureItem[] => [ + { + icon: TbLayoutSidebarRightExpandFilled, + name: t('feature_sidebar_name'), + desc: t('feature_sidebar_desc'), + link: 'https://artalk.js.org/guide/frontend/sidebar.html', + }, + { + icon: TbSocial, + name: t('feature_social_login_name'), + desc: t('feature_social_login_desc'), + link: 'https://artalk.js.org/guide/frontend/auth.html', + }, + { + icon: TbMailFilled, + name: t('feature_email_notification_name'), + desc: t('feature_email_notification_desc'), + link: 'https://artalk.js.org/guide/backend/email.html', + }, + { + icon: IoSend, + name: t('feature_diverse_push_name'), + desc: t('feature_diverse_push_desc'), + link: 'https://artalk.js.org/guide/backend/admin_notify.html', + }, + { + icon: BiSolidNotification, + name: t('feature_site_notification_name'), + desc: t('feature_site_notification_desc'), + link: 'https://artalk.js.org/guide/frontend/sidebar.html', + }, + { + icon: RiRobot2Fill, + name: t('feature_captcha_name'), + desc: t('feature_captcha_desc'), + link: 'https://artalk.js.org/guide/backend/captcha.html', + }, + { + icon: BsFillShieldLockFill, + name: t('feature_comment_moderation_name'), + desc: t('feature_comment_moderation_desc'), + link: 'https://artalk.js.org/guide/backend/moderator.html', + }, + { + icon: RiUpload2Fill, + name: t('feature_image_upload_name'), + desc: t('feature_image_upload_desc'), + link: 'https://artalk.js.org/guide/backend/img-upload.html', + }, + { + icon: FaMarkdown, + name: t('feature_markdown_name'), + desc: t('feature_markdown_desc'), + link: 'https://artalk.js.org/guide/intro.html', + }, + { + icon: PiSmileyWinkBold, + name: t('feature_emoji_pack_name'), + desc: t('feature_emoji_pack_desc'), + link: 'https://artalk.js.org/guide/frontend/emoticons.html', + }, + { + icon: TbCardsFilled, + name: t('feature_multi_site_name'), + desc: t('feature_multi_site_desc'), + link: 'https://artalk.js.org/guide/backend/multi-site.html', + }, + { + icon: BiSolidBadgeCheck, + name: t('feature_admin_name'), + desc: t('feature_admin_desc'), + link: 'https://artalk.js.org/guide/backend/multi-site.html', + }, + { + icon: LuNewspaper, + name: t('feature_page_management_name'), + desc: t('feature_page_management_desc'), + link: 'https://artalk.js.org/guide/frontend/sidebar.html', + }, + { + icon: TbEyeFilled, + name: t('feature_page_view_statistics_name'), + desc: t('feature_page_view_statistics_desc'), + link: 'https://artalk.js.org/guide/frontend/pv.html', + }, + { + icon: LuListTree, + name: t('feature_hierarchical_structure_name'), + desc: t('feature_hierarchical_structure_desc'), + link: 'https://artalk.js.org/guide/frontend/config.html#nestmax', + }, + { + icon: FaArrowTrendUp, + name: t('feature_comment_voting_name'), + desc: t('feature_comment_voting_desc'), + link: 'https://artalk.js.org/guide/frontend/config.html#vote', + }, + { + icon: FaSortAmountUpAlt, + name: t('feature_comment_sorting_name'), + desc: t('feature_comment_sorting_desc'), + link: 'https://artalk.js.org/guide/frontend/config.html#listsort', + }, + { + icon: IoSearch, + name: t('feature_comment_search_name'), + desc: t('feature_comment_search_desc'), + link: 'https://artalk.js.org/guide/frontend/sidebar.html', + }, + { + icon: RiPushpinLine, + name: t('feature_comment_pinning_name'), + desc: t('feature_comment_pinning_desc'), + link: 'https://artalk.js.org/guide/frontend/sidebar.html', + }, + { + icon: HiOutlineDocumentSearch, + name: t('feature_view_author_only_name'), + desc: t('feature_view_author_only_desc'), + link: 'https://artalk.js.org/guide/frontend/config.html', + }, + { + icon: IoMdLocate, + name: t('feature_comment_jump_name'), + desc: t('feature_comment_jump_desc'), + link: 'https://artalk.js.org/guide/intro.html', + }, + { + icon: FaRegSave, + name: t('feature_auto_save_name'), + desc: t('feature_auto_save_desc'), + link: 'https://artalk.js.org/guide/frontend/config.html', + }, + { + icon: TbLocationFilled, + name: t('feature_ip_region_name'), + desc: t('feature_ip_region_desc'), + link: 'https://artalk.js.org/guide/frontend/ip-region.html', + }, + { + icon: TbTransformFilled, + name: t('feature_data_migration_name'), + desc: t('feature_data_migration_desc'), + link: 'https://artalk.js.org/guide/transfer.html', + }, + { + icon: TbPhotoSearch, + name: t('feature_image_lightbox_name'), + desc: t('feature_image_lightbox_desc'), + link: 'https://artalk.js.org/guide/frontend/lightbox.html', + }, + { + icon: RiLoader4Fill, + name: t('feature_image_lazy_load_name'), + desc: t('feature_image_lazy_load_desc'), + link: 'https://artalk.js.org/guide/frontend/img-lazy-load.html', + }, + { + icon: TbMath, + name: t('feature_latex_name'), + desc: t('feature_latex_desc'), + link: 'https://artalk.js.org/guide/frontend/latex.html', + }, + { + icon: PiMoonFill, + name: t('feature_night_mode_name'), + desc: t('feature_night_mode_desc'), + link: 'https://artalk.js.org/guide/frontend/config.html#darkmode', + }, + { + icon: TbPlug, + name: t('feature_extension_plugin_name'), + desc: t('feature_extension_plugin_desc'), + link: 'https://artalk.js.org/develop/plugin.html', + }, + { + icon: TbLanguage, + name: t('feature_multi_language_name'), + desc: t('feature_multi_language_desc'), + link: 'https://artalk.js.org/guide/frontend/i18n.html', + }, + { + icon: TbTerminal, + name: t('feature_command_line_name'), + desc: t('feature_command_line_desc'), + link: 'https://artalk.js.org/guide/backend/config.html', + }, + { + icon: TbApi, + name: t('feature_api_documentation_name'), + desc: t('feature_api_documentation_desc'), + link: 'https://artalk.js.org/http-api.html', + }, + { + icon: GrUpgrade, + name: t('feature_program_upgrade_name'), + desc: t('feature_program_upgrade_desc'), + link: 'https://artalk.js.org/guide/backend/update.html', + }, +] diff --git a/docs/landing/src/hooks/darkmode.ts b/docs/landing/src/hooks/darkmode.ts new file mode 100644 index 000000000..ef18164ba --- /dev/null +++ b/docs/landing/src/hooks/darkmode.ts @@ -0,0 +1,61 @@ +import { + useIsMounted, + useIsomorphicLayoutEffect, + useLocalStorage, + useMediaQuery, +} from 'usehooks-ts' + +const COLOR_SCHEME_QUERY = '(prefers-color-scheme: dark)' +const LOCAL_STORAGE_KEY = 'usehooks-ts-dark-mode' + +type DarkModeOptions = { + defaultValue?: boolean + localStorageKey?: string + initializeWithValue?: boolean +} + +type DarkModeReturn = { + isDarkMode: boolean + toggle: () => void + enable: () => void + disable: () => void + set: (value: boolean) => void +} + +export function useDarkMode(options: DarkModeOptions = {}): DarkModeReturn { + const { defaultValue, localStorageKey = LOCAL_STORAGE_KEY, initializeWithValue = true } = options + + const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY, { + initializeWithValue, + defaultValue, + }) + const [isDarkMode, setDarkMode] = useLocalStorage( + localStorageKey, + defaultValue ?? isDarkOS ?? false, + { initializeWithValue }, + ) + + // Update darkMode if os prefers changes + const allowDarkOSChange = useIsMounted() + useIsomorphicLayoutEffect(() => { + if (allowDarkOSChange() && isDarkOS !== isDarkMode) { + setDarkMode(isDarkOS) + } + }, [isDarkOS]) + + return { + isDarkMode, + toggle: () => { + setDarkMode((prev) => !prev) + }, + enable: () => { + setDarkMode(true) + }, + disable: () => { + setDarkMode(false) + }, + set: (value) => { + setDarkMode(value) + }, + } +} diff --git a/docs/landing/src/hooks/visible.ts b/docs/landing/src/hooks/visible.ts index b424add15..6394d0997 100644 --- a/docs/landing/src/hooks/visible.ts +++ b/docs/landing/src/hooks/visible.ts @@ -4,11 +4,14 @@ export const useIsElementVisible = (target: React.RefObject) => const [isVisible, setIsVisible] = useState(false) useEffect(() => { - const observer = new IntersectionObserver((entries) => { - setIsVisible(entries[0].isIntersecting) - }, { - threshold: 0.4 - }) + const observer = new IntersectionObserver( + (entries) => { + setIsVisible(entries[0].isIntersecting) + }, + { + threshold: 0.4, + }, + ) target.current && observer.observe(target.current) diff --git a/docs/landing/src/i18n/en.ts b/docs/landing/src/i18n/en.ts new file mode 100644 index 000000000..fd015cd6a --- /dev/null +++ b/docs/landing/src/i18n/en.ts @@ -0,0 +1,132 @@ +export const en = { + home_title: 'Artalk - A Self-hosted Comment System', + get_artalk: 'Get Artalk', + docs: 'Docs', + changelog: 'Changelog', + feature_slight_title: 'SLIGHT', + feature_slight_desc_line_1: 'A native JS client, framework-agnostic,', + feature_slight_desc_line_2: + 'compact in size, fast to load, and instantly responsive to every interaction.', + feature_swift_title: 'SWIFT', + feature_swift_desc_line_1: + 'Artalk is written in Golang, enabling fast, cross-platform deployment.', + feature_swift_desc_line_2: 'We provide Docker images to meet various deployment needs.', + feature_swift_self_compile: 'Planning to compile from source?', + deploy_bin: 'BINARY', + deploy_bin_sub: 'DEPLOYMENT', + deploy_docker: 'DOCKER', + deploy_docker_sub: 'DEPLOYMENT', + learn_more: 'Learn More', + copy: 'Copy', + copied: 'Copied!', + feature_full_title: 'ROBUST', + feature_full_desc_line_1: + 'Artalk offers a wealth of built-in features, striving to maintain comprehensive functionality while keeping it simple, providing an out-of-the-box experience.', + feature_func_title: 'EXTENSIBLE', + feature_func_desc_line_1: 'Artalk provides extensive third-party integration capabilities.', + feature_safe_title: 'SECURE', + feature_safe_desc_line_1: + 'Artalk is open-source free software, self-hosted for comment data, transparent and controllable.', + feature_safe_desc_line_2: + 'Artalk includes a data migration tool to easily transfer your comment content.', + feature_safe_desc_line_3: + 'At Artalk, we always prioritize security and continuously provide updates.', + func_social_login: 'Social Login', + func_email: 'Email Sending', + func_captcha: 'Captcha', + func_message_pusher: 'Message Push', + func_moderator: 'Content Moderation', + func_emoji: 'Emoji Pack', + func_img_upload: 'Image Upload', + func_rich_text: 'Rich Text', + func_code_highlight: 'Code Highlighting', + func_avatar: 'User Avatar', + func_img_lightbox: 'Image Lightbox', + func_database: 'Database', + func_cache: 'High-Speed Cache', + func_deploy: 'Program Deployment', + func_os: 'Operating System', + func_platform: 'Platform Architecture', + func_transfer_import: 'Comment Import', + func_email_aliyun: 'Aliyun Email', + func_captcha_geetest: 'Geetest', + func_message_pusher_lark: 'Lark', + func_message_pusher_dingtalk: 'DingTalk', + func_moderator_aliyun: 'Aliyun', + func_moderator_tencent: 'Tencent Cloud', + func_moderator_offline: 'Offline Dictionary', + func_emoji_standard_format: 'Standard Format', + func_emoji_owo_format: 'OwO Format', + func_img_upload_local: 'Local Storage', + func_img_upload_function: 'Custom Function', + func_avatar_function: 'Custom Function', + func_cache_internal: 'Internal Cache', + func_deploy_bin: 'Binary File', + func_deploy_docker: 'Docker Image', + feature_sidebar_name: 'Sidebar', + feature_sidebar_desc: 'Quick management, intuitive browsing', + feature_social_login_name: 'Social Login', + feature_social_login_desc: 'Fast login via social accounts', + feature_email_notification_name: 'Email Notification', + feature_email_notification_desc: 'Various sending methods, email templates', + feature_diverse_push_name: 'Diverse Push', + feature_diverse_push_desc: 'Multiple push methods, notification templates', + feature_site_notification_name: 'Site Notification', + feature_site_notification_desc: 'Red dot marks, mention list', + feature_captcha_name: 'Captcha', + feature_captcha_desc: 'Various verification types, frequency limits', + feature_comment_moderation_name: 'Comment Moderation', + feature_comment_moderation_desc: 'Content detection, spam interception', + feature_image_upload_name: 'Image Upload', + feature_image_upload_desc: 'Custom upload, supports image hosting', + feature_markdown_name: 'Markdown', + feature_markdown_desc: 'Supports Markdown syntax', + feature_emoji_pack_name: 'Emoji Pack', + feature_emoji_pack_desc: 'Compatible with OwO, quick integration', + feature_multi_site_name: 'Multi-Site', + feature_multi_site_desc: 'Site isolation, centralized management', + feature_admin_name: 'Admin', + feature_admin_desc: 'Password verification, badge identification', + feature_page_management_name: 'Page Management', + feature_page_management_desc: 'Quick view, one-click title navigation', + feature_page_view_statistics_name: 'Page View Statistics', + feature_page_view_statistics_desc: 'Easily track page views', + feature_hierarchical_structure_name: 'Hierarchical Structure', + feature_hierarchical_structure_desc: 'Nested paginated list, infinite scroll', + feature_comment_voting_name: 'Comment Voting', + feature_comment_voting_desc: 'Upvote or downvote comments', + feature_comment_sorting_name: 'Comment Sorting', + feature_comment_sorting_desc: 'Various sorting options, freely selectable', + feature_comment_search_name: 'Comment Search', + feature_comment_search_desc: 'Quick comment content search', + feature_comment_pinning_name: 'Comment Pinning', + feature_comment_pinning_desc: 'Pin important messages', + feature_view_author_only_name: 'View Author Only', + feature_view_author_only_desc: "Show only the author's comments", + feature_comment_jump_name: 'Comment Jump', + feature_comment_jump_desc: 'Quickly jump to quoted comment', + feature_auto_save_name: 'Auto Save', + feature_auto_save_desc: 'Content loss prevention', + feature_ip_region_name: 'IP Region', + feature_ip_region_desc: "Display user's IP region", + feature_data_migration_name: 'Data Migration', + feature_data_migration_desc: 'Free migration, quick backup', + feature_image_lightbox_name: 'Image Lightbox', + feature_image_lightbox_desc: 'Quick integration of image lightbox', + feature_image_lazy_load_name: 'Image Lazy Load', + feature_image_lazy_load_desc: 'Lazy load images, optimize experience', + feature_latex_name: 'Latex', + feature_latex_desc: 'Integrate Latex formula parsing', + feature_night_mode_name: 'Night Mode', + feature_night_mode_desc: 'Switch to night mode', + feature_extension_plugin_name: 'Extension Plugin', + feature_extension_plugin_desc: 'Create more possibilities', + feature_multi_language_name: 'Multi-Language', + feature_multi_language_desc: 'Switch between multiple languages', + feature_command_line_name: 'Command Line', + feature_command_line_desc: 'Command line operation management', + feature_api_documentation_name: 'API Documentation', + feature_api_documentation_desc: 'Provides OpenAPI format documentation', + feature_program_upgrade_name: 'Program Upgrade', + feature_program_upgrade_desc: 'Version check, one-click upgrade', +} diff --git a/docs/landing/src/i18n/index.ts b/docs/landing/src/i18n/index.ts new file mode 100644 index 000000000..c8ead2d57 --- /dev/null +++ b/docs/landing/src/i18n/index.ts @@ -0,0 +1,38 @@ +import i18n, { InitOptions } from 'i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import { initReactI18next } from 'react-i18next' +import { en } from './en' +import { zhCN } from './zh-CN' + +export type MessageSchema = typeof en +export const DefaultNameSpace = 'translation' + +const i18nConfig: InitOptions = { + defaultNS: DefaultNameSpace, + resources: { + en: { translation: en }, + zh: { translation: zhCN }, + 'zh-CN': { translation: zhCN }, + 'zh-TW': { translation: zhCN }, + 'zh-HK': { translation: zhCN }, + }, + fallbackLng: 'en', + + interpolation: { + escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape + }, +} + +export function initI18n() { + i18n.use(initReactI18next).use(LanguageDetector).init(i18nConfig) +} + +export function initI18nSSR(locale: string) { + return i18n.use(initReactI18next).init({ + ...i18nConfig, + lng: locale, + interpolation: { + escapeValue: true, + }, + }) +} diff --git a/docs/landing/src/i18n/zh-CN.ts b/docs/landing/src/i18n/zh-CN.ts new file mode 100644 index 000000000..4eee2132e --- /dev/null +++ b/docs/landing/src/i18n/zh-CN.ts @@ -0,0 +1,129 @@ +import { MessageSchema } from '.' + +export const zhCN: MessageSchema = { + home_title: 'Artalk | 自托管评论系统', + get_artalk: '获取 Artalk', + docs: '文档', + changelog: '更新日志', + feature_slight_title: '轻量', + feature_slight_desc_line_1: '原生 JS 前端程序,核心功能无依赖框架,', + feature_slight_desc_line_2: '小巧体积、快速加载,迅速响应每刻交互。', + feature_swift_title: '快捷', + feature_swift_desc_line_1: 'Artalk 采用 Golang 编写,快速跨平台启动你的评论服务器,', + feature_swift_desc_line_2: '我们提供了 Docker 镜像,满足更多部署需求。', + feature_swift_self_compile: '打算使用代码自编译?', + deploy_bin: '二进制', + deploy_bin_sub: '文件部署', + deploy_docker: 'Docker', + deploy_docker_sub: '镜像部署', + learn_more: '了解更多', + copy: '复制', + copied: '已复制!', + feature_full_title: '全面', + feature_full_desc_line_1: + 'Artalk 提供丰富的内置功能,我们尽力在简洁的同时保持功能的全面,为您带来开箱即用的体验。', + feature_func_title: '可选集成', + feature_func_desc_line_1: 'Artalk 提供丰富第三方接入能力。', + feature_safe_title: '安全', + feature_safe_desc_line_1: 'Artalk 开源自由软件,自托管评论数据,透明可控。', + feature_safe_desc_line_2: 'Artalk 内置数据迁移工具,轻松转移您的评论内容。', + feature_safe_desc_line_3: 'Artalk 我们始终将安全放在首位,并持续提供更新。', + func_social_login: '社交登录', + func_email: '邮箱发送', + func_captcha: '验证码', + func_message_pusher: '消息推送', + func_moderator: '内容审核', + func_emoji: '表情包', + func_img_upload: '图片上传', + func_rich_text: '富文本', + func_code_highlight: '代码高亮', + func_avatar: '用户头像', + func_img_lightbox: '图片灯箱', + func_database: '数据库', + func_cache: '高速缓存', + func_deploy: '程序部署', + func_os: '操作系统', + func_platform: '平台架构', + func_transfer_import: '评论导入', + func_email_aliyun: '阿里云邮件', + func_captcha_geetest: '极验', + func_message_pusher_lark: '飞书', + func_message_pusher_dingtalk: '钉钉', + func_moderator_aliyun: '腾讯云', + func_moderator_tencent: '腾讯云', + func_moderator_offline: '离线词库', + func_emoji_standard_format: '标准格式', + func_emoji_owo_format: 'OwO 格式', + func_img_upload_local: '本地保存', + func_img_upload_function: '自定义函数', + func_avatar_function: '自定义函数', + func_cache_internal: '内建缓存', + func_deploy_bin: '二进制文件', + func_deploy_docker: 'Docker 镜像', + feature_sidebar_name: '侧边栏', + feature_sidebar_desc: '快速管理、直观浏览', + feature_social_login_name: '社交登录', + feature_social_login_desc: '通过社交账号快速登录', + feature_email_notification_name: '邮件通知', + feature_email_notification_desc: '多种发送方式、邮件模板', + feature_diverse_push_name: '多元推送', + feature_diverse_push_desc: '多种推送方式、通知模版', + feature_site_notification_name: '站内通知', + feature_site_notification_desc: '红点标记、提及列表', + feature_captcha_name: '验证码', + feature_captcha_desc: '多种验证类型、频率限制', + feature_comment_moderation_name: '评论审核', + feature_comment_moderation_desc: '内容检测、垃圾拦截', + feature_image_upload_name: '图片上传', + feature_image_upload_desc: '自定义上传、支持图床', + feature_markdown_name: 'Markdown', + feature_markdown_desc: '支持 Markdown 语法', + feature_emoji_pack_name: '表情包', + feature_emoji_pack_desc: '兼容 OwO,快速集成', + feature_multi_site_name: '多站点', + feature_multi_site_desc: '站点隔离、集中管理', + feature_admin_name: '管理员', + feature_admin_desc: '密码验证、徽章标识', + feature_page_management_name: '页面管理', + feature_page_management_desc: '快速查看、标题一键跳转', + feature_page_view_statistics_name: '浏览量统计', + feature_page_view_statistics_desc: '轻松统计网页浏览量', + feature_hierarchical_structure_name: '层级结构', + feature_hierarchical_structure_desc: '嵌套分页列表、滚动加载', + feature_comment_voting_name: '评论投票', + feature_comment_voting_desc: '赞同或反对评论', + feature_comment_sorting_name: '评论排序', + feature_comment_sorting_desc: '多种排序方式,自由选择', + feature_comment_search_name: '评论搜索', + feature_comment_search_desc: '快速搜索评论内容', + feature_comment_pinning_name: '评论置顶', + feature_comment_pinning_desc: '重要消息置顶显示', + feature_view_author_only_name: '仅看作者', + feature_view_author_only_desc: '仅显示作者的评论', + feature_comment_jump_name: '评论跳转', + feature_comment_jump_desc: '快速跳转到引用的评论', + feature_auto_save_name: '自动保存', + feature_auto_save_desc: '输入内容防丢功能', + feature_ip_region_name: 'IP 属地', + feature_ip_region_desc: '用户 IP 属地展示', + feature_data_migration_name: '数据迁移', + feature_data_migration_desc: '自由迁移、快速备份', + feature_image_lightbox_name: '图片灯箱', + feature_image_lightbox_desc: '图片灯箱快速集成', + feature_image_lazy_load_name: '图片懒加载', + feature_image_lazy_load_desc: '延迟加载图片,优化体验', + feature_latex_name: 'Latex', + feature_latex_desc: 'Latex 公式解析集成', + feature_night_mode_name: '夜间模式', + feature_night_mode_desc: '夜间模式切换', + feature_extension_plugin_name: '扩展插件', + feature_extension_plugin_desc: '创造更多可能性', + feature_multi_language_name: '多语言', + feature_multi_language_desc: '多国语言切换', + feature_command_line_name: '命令行', + feature_command_line_desc: '命令行操作管理能力', + feature_api_documentation_name: 'API 文档', + feature_api_documentation_desc: '提供 OpenAPI 格式文档', + feature_program_upgrade_name: '程序升级', + feature_program_upgrade_desc: '版本检测,一键升级', +} diff --git a/docs/landing/src/i18next.d.ts b/docs/landing/src/i18next.d.ts new file mode 100644 index 000000000..76681bd47 --- /dev/null +++ b/docs/landing/src/i18next.d.ts @@ -0,0 +1,11 @@ +import 'i18next' +import type { MessageSchema } from './i18n' + +declare module 'i18next' { + interface CustomTypeOptions { + defaultNS: 'translation' + resources: { + translation: MessageSchema + } + } +} diff --git a/docs/landing/src/main.tsx b/docs/landing/src/main.tsx index dc4d0f187..b64b586cb 100644 --- a/docs/landing/src/main.tsx +++ b/docs/landing/src/main.tsx @@ -1,8 +1,11 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' +import { initI18n } from './i18n' import './responsive.scss' +initI18n() + ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/docs/landing/src/responsive.scss b/docs/landing/src/responsive.scss index 0e3642905..e548ca5db 100644 --- a/docs/landing/src/responsive.scss +++ b/docs/landing/src/responsive.scss @@ -1,8 +1,14 @@ -@import "include-media"; +@import 'include-media'; -$breakpoints: (sm: 640px, md: 768px, lg: 1024px, xl: 1280px, xxl: 1536px); +$breakpoints: ( + sm: 640px, + md: 768px, + lg: 1024px, + xl: 1280px, + xxl: 1536px, +); -@include media(">lg", "lg', ' { - const extType = assetInfo.name.split(".")[1]; + const extType = assetInfo.name.split('.')[1] if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) { - return `landing/img/[name]-[hash][extname]`; + return `landing/img/[name]-[hash][extname]` } - return `landing/[name]-[hash][extname]`; + return `landing/[name]-[hash][extname]` }, - chunkFileNames: "landing/[name]-[hash].js", - entryFileNames: "landing/[name]-[hash].js", + chunkFileNames: 'landing/[name]-[hash].js', + entryFileNames: 'landing/[name]-[hash].js', }, }, }, -}); +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78ed42b21..a89fc280c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,6 +154,9 @@ importers: specifier: 5.3.0 version: 5.3.0(react@18.3.1) devDependencies: + '@headlessui/react': + specifier: ^2.1.8 + version: 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react': specifier: ^18.3.5 version: 18.3.5 @@ -162,7 +165,19 @@ importers: version: 18.3.0 '@vitejs/plugin-react-swc': specifier: ^3.7.0 - version: 3.7.0(vite@5.4.5(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0)) + version: 3.7.0(@swc/helpers@0.5.13)(vite@5.4.5(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0)) + i18next: + specifier: ^23.15.1 + version: 23.15.1 + i18next-browser-languagedetector: + specifier: ^8.0.0 + version: 8.0.0 + react-i18next: + specifier: ^15.0.2 + version: 15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + usehooks-ts: + specifier: ^3.1.0 + version: 3.1.0(react@18.3.1) docs/swagger: devDependencies: @@ -334,14 +349,14 @@ importers: version: 5.6.2 vite: specifier: '*' - version: 5.4.2(@types/node@22.5.2)(sass@1.77.8)(terser@5.31.6) + version: 5.4.2(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0) devDependencies: esbuild-plugin-raw: specifier: ^0.1.8 version: 0.1.8(esbuild@0.23.1) tsup: specifier: ^8.2.4 - version: 8.2.4(@microsoft/api-extractor@7.47.9(@types/node@22.5.5))(@swc/core@1.7.26)(postcss@8.4.47)(tsx@4.19.1)(typescript@5.6.2)(yaml@2.5.1) + version: 8.2.4(@microsoft/api-extractor@7.47.9(@types/node@22.5.5))(@swc/core@1.7.26(@swc/helpers@0.5.13))(postcss@8.4.47)(tsx@4.19.1)(typescript@5.6.2)(yaml@2.5.1) ui/plugin-lightbox: dependencies: @@ -960,6 +975,34 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.11': + resolution: {integrity: sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.26.24': + resolution: {integrity: sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + + '@headlessui/react@2.1.8': + resolution: {integrity: sha512-uajqVkAcVG/wHwG9Fh5PFMcFpf2VxM4vNRNKxRjuK009kePVur8LkuuygHfIE+2uZ7z7GnlTtYsyUe6glPpTLg==} + engines: {node: '>=10'} + peerDependencies: + react: ^18 + react-dom: ^18 + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -1057,6 +1100,37 @@ packages: engines: {node: '>=18'} hasBin: true + '@react-aria/focus@3.18.2': + resolution: {integrity: sha512-Jc/IY+StjA3uqN73o6txKQ527RFU7gnG5crEl5Xy3V+gbYp2O5L3ezAo/E0Ipi2cyMbG6T5Iit1IDs7hcGu8aw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + + '@react-aria/interactions@3.22.2': + resolution: {integrity: sha512-xE/77fRVSlqHp2sfkrMeNLrqf2amF/RyuAS6T5oDJemRSgYM3UoxTbWjucPhfnoW7r32pFPHHgz4lbdX8xqD/g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + + '@react-aria/ssr@3.9.5': + resolution: {integrity: sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + + '@react-aria/utils@3.25.2': + resolution: {integrity: sha512-GdIvG8GBJJZygB4L2QJP1Gabyn2mjFsha73I2wSe+o4DYeGWoJiMZRM06PyTIxLH4S7Sn7eVDtsSBfkc2VY/NA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + + '@react-stately/utils@3.10.3': + resolution: {integrity: sha512-moClv7MlVSHpbYtQIkm0Cx+on8Pgt1XqtPx6fy9rQFb2DNc9u1G3AUVnqA17buOkH1vLxAtX4MedlxMWyRCYYA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + + '@react-types/shared@3.24.1': + resolution: {integrity: sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + '@redocly/ajv@8.11.2': resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} @@ -1375,9 +1449,21 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.13': + resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + '@swc/types@0.1.12': resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==} + '@tanstack/react-virtual@3.10.8': + resolution: {integrity: sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@tanstack/virtual-core@3.10.8': + resolution: {integrity: sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==} + '@tsconfig/node18@18.2.4': resolution: {integrity: sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==} @@ -1426,9 +1512,6 @@ packages: '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - '@types/node@22.5.2': - resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==} - '@types/node@22.5.5': resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} @@ -2714,6 +2797,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} @@ -2739,6 +2825,12 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + i18next-browser-languagedetector@8.0.0: + resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==} + + i18next@23.15.1: + resolution: {integrity: sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -3065,6 +3157,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.escape@4.0.1: resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==} @@ -3553,10 +3648,6 @@ packages: resolution: {integrity: sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.45: - resolution: {integrity: sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.4.47: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} @@ -3614,6 +3705,19 @@ packages: peerDependencies: react: ^18.3.1 + react-i18next@15.0.2: + resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + react-icons@5.3.0: resolution: {integrity: sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==} peerDependencies: @@ -3748,11 +3852,6 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.77.8: - resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} - engines: {node: '>=14.0.0'} - hasBin: true - sass@1.78.0: resolution: {integrity: sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==} engines: {node: '>=14.0.0'} @@ -3872,10 +3971,6 @@ packages: peerDependencies: solid-js: ^1.3 - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4037,11 +4132,6 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - terser@5.31.6: - resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} - engines: {node: '>=10'} - hasBin: true - terser@5.32.0: resolution: {integrity: sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==} engines: {node: '>=10'} @@ -4323,6 +4413,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 + usehooks-ts@3.1.0: + resolution: {integrity: sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==} + engines: {node: '>=16.15.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4514,6 +4610,10 @@ packages: jsdom: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + vscode-jsonrpc@6.0.0: resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} engines: {node: '>=8.0.0 || >=10.0.0'} @@ -5270,6 +5370,40 @@ snapshots: '@exodus/schemasafe@1.3.0': {} + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.11': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/react@0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/utils': 0.2.8 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.8': {} + + '@headlessui/react@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react': 0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/focus': 3.18.2(react@18.3.1) + '@react-aria/interactions': 3.22.2(react@18.3.1) + '@tanstack/react-virtual': 3.10.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.3.0': {} @@ -5403,6 +5537,46 @@ snapshots: dependencies: playwright: 1.47.1 + '@react-aria/focus@3.18.2(react@18.3.1)': + dependencies: + '@react-aria/interactions': 3.22.2(react@18.3.1) + '@react-aria/utils': 3.25.2(react@18.3.1) + '@react-types/shared': 3.24.1(react@18.3.1) + '@swc/helpers': 0.5.13 + clsx: 2.1.1 + react: 18.3.1 + + '@react-aria/interactions@3.22.2(react@18.3.1)': + dependencies: + '@react-aria/ssr': 3.9.5(react@18.3.1) + '@react-aria/utils': 3.25.2(react@18.3.1) + '@react-types/shared': 3.24.1(react@18.3.1) + '@swc/helpers': 0.5.13 + react: 18.3.1 + + '@react-aria/ssr@3.9.5(react@18.3.1)': + dependencies: + '@swc/helpers': 0.5.13 + react: 18.3.1 + + '@react-aria/utils@3.25.2(react@18.3.1)': + dependencies: + '@react-aria/ssr': 3.9.5(react@18.3.1) + '@react-stately/utils': 3.10.3(react@18.3.1) + '@react-types/shared': 3.24.1(react@18.3.1) + '@swc/helpers': 0.5.13 + clsx: 2.1.1 + react: 18.3.1 + + '@react-stately/utils@3.10.3(react@18.3.1)': + dependencies: + '@swc/helpers': 0.5.13 + react: 18.3.1 + + '@react-types/shared@3.24.1(react@18.3.1)': + dependencies: + react: 18.3.1 + '@redocly/ajv@8.11.2': dependencies: fast-deep-equal: 3.1.3 @@ -5688,7 +5862,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.7.26': optional: true - '@swc/core@1.7.26': + '@swc/core@1.7.26(@swc/helpers@0.5.13)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.12 @@ -5703,13 +5877,26 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.7.26 '@swc/core-win32-ia32-msvc': 1.7.26 '@swc/core-win32-x64-msvc': 1.7.26 + '@swc/helpers': 0.5.13 '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.13': + dependencies: + tslib: 2.7.0 + '@swc/types@0.1.12': dependencies: '@swc/counter': 0.1.3 + '@tanstack/react-virtual@3.10.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.10.8 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/virtual-core@3.10.8': {} + '@tsconfig/node18@18.2.4': {} '@types/argparse@1.0.38': {} @@ -5763,11 +5950,6 @@ snapshots: '@types/mdurl@2.0.0': {} - '@types/node@22.5.2': - dependencies: - undici-types: 6.19.8 - optional: true - '@types/node@22.5.5': dependencies: undici-types: 6.19.8 @@ -5872,9 +6054,9 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react-swc@3.7.0(vite@5.4.5(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0))': + '@vitejs/plugin-react-swc@3.7.0(@swc/helpers@0.5.13)(vite@5.4.5(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0))': dependencies: - '@swc/core': 1.7.26 + '@swc/core': 1.7.26(@swc/helpers@0.5.13) vite: 5.4.5(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0) transitivePeerDependencies: - '@swc/helpers' @@ -7403,6 +7585,10 @@ snapshots: html-escaper@2.0.2: {} + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + html-tags@3.3.1: optional: true @@ -7433,6 +7619,14 @@ snapshots: human-signals@2.1.0: {} + i18next-browser-languagedetector@8.0.0: + dependencies: + '@babel/runtime': 7.25.6 + + i18next@23.15.1: + dependencies: + '@babel/runtime': 7.25.6 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -7753,6 +7947,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.debounce@4.0.8: {} + lodash.escape@4.0.1: {} lodash.flattendeep@4.4.0: {} @@ -8191,9 +8387,9 @@ snapshots: postcss-resolve-nested-selector@0.1.6: optional: true - postcss-safe-parser@7.0.0(postcss@8.4.45): + postcss-safe-parser@7.0.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 optional: true postcss-selector-parser@6.1.2: @@ -8215,13 +8411,6 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 - postcss@8.4.45: - dependencies: - nanoid: 3.3.7 - picocolors: 1.1.0 - source-map-js: 1.2.1 - optional: true - postcss@8.4.47: dependencies: nanoid: 3.3.7 @@ -8273,6 +8462,15 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-i18next@15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.25.6 + html-parse-stringify: 3.0.1 + i18next: 23.15.1 + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-icons@5.3.0(react@18.3.1): dependencies: react: 18.3.1 @@ -8467,13 +8665,6 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.77.8: - dependencies: - chokidar: 3.6.0 - immutable: 4.3.7 - source-map-js: 1.2.0 - optional: true - sass@1.78.0: dependencies: chokidar: 3.6.0 @@ -8616,9 +8807,6 @@ snapshots: transitivePeerDependencies: - supports-color - source-map-js@1.2.0: - optional: true - source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -8770,9 +8958,9 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.0 - postcss: 8.4.45 + postcss: 8.4.47 postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 7.0.0(postcss@8.4.45) + postcss-safe-parser: 7.0.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 @@ -8857,14 +9045,6 @@ snapshots: tapable@2.2.1: {} - terser@5.31.6: - dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true - terser@5.32.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -8949,7 +9129,7 @@ snapshots: tslib@2.7.0: {} - tsup@8.2.4(@microsoft/api-extractor@7.47.9(@types/node@22.5.5))(@swc/core@1.7.26)(postcss@8.4.47)(tsx@4.19.1)(typescript@5.6.2)(yaml@2.5.1): + tsup@8.2.4(@microsoft/api-extractor@7.47.9(@types/node@22.5.5))(@swc/core@1.7.26(@swc/helpers@0.5.13))(postcss@8.4.47)(tsx@4.19.1)(typescript@5.6.2)(yaml@2.5.1): dependencies: bundle-require: 5.0.0(esbuild@0.23.1) cac: 6.7.14 @@ -8969,7 +9149,7 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: '@microsoft/api-extractor': 7.47.9(@types/node@22.5.5) - '@swc/core': 1.7.26 + '@swc/core': 1.7.26(@swc/helpers@0.5.13) postcss: 8.4.47 typescript: 5.6.2 transitivePeerDependencies: @@ -9193,6 +9373,11 @@ snapshots: dependencies: react: 18.3.1 + usehooks-ts@3.1.0(react@18.3.1): + dependencies: + lodash.debounce: 4.0.8 + react: 18.3.1 + util-deprecate@1.0.2: {} validate-html-nesting@1.2.2: {} @@ -9295,16 +9480,16 @@ snapshots: - supports-color - typescript - vite@5.4.2(@types/node@22.5.2)(sass@1.77.8)(terser@5.31.6): + vite@5.4.2(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0): dependencies: esbuild: 0.21.5 postcss: 8.4.44 rollup: 4.21.2 optionalDependencies: - '@types/node': 22.5.2 + '@types/node': 22.5.5 fsevents: 2.3.3 - sass: 1.77.8 - terser: 5.31.6 + sass: 1.78.0 + terser: 5.32.0 vite@5.4.5(@types/node@22.5.5)(sass@1.78.0)(terser@5.32.0): dependencies: @@ -9404,6 +9589,8 @@ snapshots: - supports-color - terser + void-elements@3.1.0: {} + vscode-jsonrpc@6.0.0: {} vscode-languageclient@7.0.0: