feat: 新增 QuantaNote 项目展示,重构项目卡片与截图浏览组件

- 新增 QuantaNote 完整项目数据(特性描述、截图、Logo)
  - 为所有项目添加 logo 和 websiteUrl 字段支持
  - 移除 stars/forks 相关展示与排序逻辑
  - ScreenshotCarousel 增加左右切换箭头和 Lightbox 全屏预览(支持键盘导航)
  - 更新项目创建文档,补充 logo 和 installGuide 配置说明
This commit is contained in:
2026-05-22 16:07:30 +08:00
parent 6b58b55c32
commit b6f15f82d8
40 changed files with 628 additions and 181 deletions
+8 -1
View File
@@ -11,7 +11,14 @@
"mcp__Claude_Preview__preview_start",
"Bash(pnpm dev *)",
"Bash(rtk read *)",
"Bash(rtk grep *)"
"Bash(rtk grep *)",
"Bash(rtk git *)",
"Bash(gh repo *)",
"Bash(gh release *)",
"Bash(gh api *)",
"Bash(echo \"exit: $?\")",
"Bash(rtk npx *)",
"Bash(rtk tsc *)"
]
}
}
+40 -3
View File
@@ -12,7 +12,8 @@
| 2 | `src/utils/iconRegistry.ts` | 仅当使用新图标时 |
| 3 | `src/data/categories.yaml` | 仅当新增分类时 |
| 4 | `src/data/statuses.yaml` | 仅当新增状态时 |
| 5 | `public/screenshots/` | 仅当有截图时 |
| 5 | `public/logos/` | 仅当有项目 Logo 时 |
| 6 | `public/screenshots/` | 仅当有截图时 |
**核心原则:** 只需创建一个 YAML 文件,项目就会自动出现在网站上。`loader.ts` 使用 `import.meta.glob('./projects/*.yaml')` 自动扫描该目录,无需手动注册。
@@ -101,6 +102,11 @@ tags:
# 如果需要新图标,见"步骤二"
icon: 'Terminal'
# 项目 Logo 图片(可选,支持 SVG/PNG/JPG
# 图片放在 public/logos/ 目录下,路径相对于 public 目录
# 若设置了 logo,卡片和详情页会优先展示 Logo 图片,否则回退到上面的 lucide 图标
# logo: '/logos/my-new-app.png'
# GitHub 仓库地址(必须)
repoUrl: 'https://github.com/shenjianZ/my-new-app'
@@ -115,8 +121,6 @@ docsUrl: 'https://github.com/shenjianZ/my-new-app#readme'
latestVersion: 'v1.0.0'
releaseDate: '2026-05-22'
license: 'MIT'
stars: 10
forks: 2
language: 'TypeScript'
lastUpdated: '2026-05-22'
@@ -189,6 +193,39 @@ architecture:
# screenshots:
# - '/screenshots/my-new-app/main.png'
# - '/screenshots/my-new-app/settings.png'
# ========== 安装指南(可选) ==========
# 每个项目生产的安装包不同,按实际包格式填写即可
# 会以紧凑的单行提示展示在"下载安装"区域内
# 不填写则使用默认的通用安装说明
installGuide:
zh:
- platform: 'Windows'
icon: '🪟'
format: '.exe'
tip: 'SmartScreen 拦截?点击"更多信息" → "仍要运行"'
- platform: 'macOS'
icon: '🍎'
format: '.dmg'
tip: '提示已损坏?终端运行: xattr -dr com.apple.quarantine /Applications/MyApp.app'
- platform: 'Linux'
icon: '🐧'
format: '.AppImage'
tip: 'chmod +x MyApp-*.AppImage && ./MyApp-*.AppImage'
en:
- platform: 'Windows'
icon: '🪟'
format: '.exe'
tip: 'SmartScreen blocked? Click "More info" → "Run anyway"'
- platform: 'macOS'
icon: '🍎'
format: '.dmg'
tip: '"Damaged" error? Run: xattr -dr com.apple.quarantine /Applications/MyApp.app'
- platform: 'Linux'
icon: '🐧'
format: '.AppImage'
tip: 'chmod +x MyApp-*.AppImage && ./MyApp-*.AppImage'
```
---
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

+6 -5
View File
@@ -1,5 +1,4 @@
import { Link } from 'react-router-dom';
import { Star } from 'lucide-react';
import { useI18n } from '../hooks/useI18n';
import { siteData } from '../data/siteData';
import { getIcon } from '../utils/iconRegistry';
@@ -20,7 +19,12 @@ export default function FeaturedCard({ project }: FeaturedCardProps) {
className="project-card project-card-link"
>
<div className="project-card-header">
<div className="project-icon">{IconComponent && <IconComponent size={22} />}</div>
<div className="project-icon">
{project.logo
? <img src={project.logo} alt={project.name} className="project-logo" />
: (IconComponent && <IconComponent size={22} />)
}
</div>
<div className="project-card-info">
<div className="project-name">
{project.displayName[lang] || project.name}
@@ -43,9 +47,6 @@ export default function FeaturedCard({ project }: FeaturedCardProps) {
</div>
<div className="project-card-meta">
<span className="badge badge-accent">
<Star size={12} /> {project.stars}
</span>
<span className="badge">{project.latestVersion}</span>
</div>
</Link>
+17 -8
View File
@@ -1,5 +1,5 @@
import { Link } from 'react-router-dom';
import { ExternalLink, Download, BookOpen, Star } from 'lucide-react';
import { ExternalLink, Download, BookOpen, Globe } from 'lucide-react';
import { useI18n } from '../hooks/useI18n';
import { siteData } from '../data/siteData';
import { getIcon } from '../utils/iconRegistry';
@@ -17,7 +17,12 @@ export default function ProjectCard({ project }: ProjectCardProps) {
return (
<div className="project-card">
<div className="project-card-header">
<div className="project-icon">{IconComponent && <IconComponent size={22} />}</div>
<div className="project-icon">
{project.logo
? <img src={project.logo} alt={project.name} className="project-logo" />
: (IconComponent && <IconComponent size={22} />)
}
</div>
<div className="project-card-info">
<div className="project-name">
<Link to={`/projects/${project.id}`}>{project.displayName[lang] || project.name}</Link>
@@ -44,12 +49,6 @@ export default function ProjectCard({ project }: ProjectCardProps) {
))}
</div>
<div className="project-card-meta">
<span className="badge badge-accent">
<Star size={12} /> {project.stars}
</span>
</div>
<div className="project-card-actions">
<a href={project.repoUrl} target="_blank" rel="noopener noreferrer" className="btn btn-sm">
<ExternalLink size={14} /> GitHub
@@ -59,6 +58,16 @@ export default function ProjectCard({ project }: ProjectCardProps) {
<Download size={14} /> {t('common.download')}
</Link>
)}
{project.websiteUrl && (
<a
href={project.websiteUrl}
target="_blank"
rel="noopener noreferrer"
className="btn btn-sm"
>
<Globe size={14} /> {t('detail.website')}
</a>
)}
{project.docsUrl && (
<a
href={project.docsUrl}
+97 -17
View File
@@ -1,4 +1,6 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { ChevronLeft, ChevronRight, X } from 'lucide-react';
interface ScreenshotCarouselProps {
screenshots?: string[];
@@ -7,6 +9,7 @@ interface ScreenshotCarouselProps {
export default function ScreenshotCarousel({ screenshots }: ScreenshotCarouselProps) {
const [active, setActive] = useState(0);
const [preview, setPreview] = useState<number | null>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const scrollTo = useCallback((i: number) => {
@@ -18,6 +21,16 @@ export default function ScreenshotCarousel({ screenshots }: ScreenshotCarouselPr
});
}, []);
const prev = useCallback(() => {
if (!screenshots) return;
scrollTo(active > 0 ? active - 1 : screenshots.length - 1);
}, [active, screenshots, scrollTo]);
const next = useCallback(() => {
if (!screenshots) return;
scrollTo(active < screenshots.length - 1 ? active + 1 : 0);
}, [active, screenshots, scrollTo]);
useEffect(() => {
const el = scrollRef.current;
if (!el) return;
@@ -36,30 +49,97 @@ export default function ScreenshotCarousel({ screenshots }: ScreenshotCarouselPr
return () => observer.disconnect();
}, []);
// 同步 lightbox 切换到轮播
useEffect(() => {
if (preview === null) return;
scrollTo(preview);
}, [preview, scrollTo]);
useEffect(() => {
if (preview === null) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') setPreview(null);
if (e.key === 'ArrowLeft') setPreview((p) => p !== null ? (p > 0 ? p - 1 : screenshots!.length - 1) : null);
if (e.key === 'ArrowRight') setPreview((p) => p !== null ? (p < screenshots!.length - 1 ? p + 1 : 0) : null);
};
document.addEventListener('keydown', onKey);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKey);
document.body.style.overflow = '';
};
}, [preview, screenshots]);
if (!screenshots || screenshots.length === 0) {
return null;
}
return (
<div className="screenshot-carousel">
<div className="screenshot-scroll" ref={scrollRef}>
{screenshots.map((src, i) => (
<div key={i} className="screenshot-slide">
<img src={src} alt={`Screenshot ${i + 1}`} className="screenshot-image" />
</div>
))}
</div>
{screenshots.length > 1 && (
<div className="screenshot-dots">
{screenshots.map((_, i) => (
<button
key={i}
className={`screenshot-dot ${i === active ? 'active' : ''}`}
onClick={() => scrollTo(i)}
/>
<>
<div className="screenshot-carousel">
{screenshots.length > 1 && (
<button className="screenshot-arrow screenshot-arrow-prev" onClick={prev} aria-label="Previous">
<ChevronLeft size={18} />
</button>
)}
<div className="screenshot-scroll" ref={scrollRef}>
{screenshots.map((src, i) => (
<div key={i} className="screenshot-slide" onClick={() => setPreview(i)}>
<img src={src} alt={`Screenshot ${i + 1}`} className="screenshot-image" />
</div>
))}
</div>
{screenshots.length > 1 && (
<button className="screenshot-arrow screenshot-arrow-next" onClick={next} aria-label="Next">
<ChevronRight size={18} />
</button>
)}
{screenshots.length > 1 && (
<div className="screenshot-dots">
{screenshots.map((_, i) => (
<button
key={i}
className={`screenshot-dot ${i === active ? 'active' : ''}`}
onClick={() => scrollTo(i)}
/>
))}
</div>
)}
</div>
{preview !== null && createPortal(
<div className="screenshot-lightbox" onClick={() => setPreview(null)}>
<button className="screenshot-lightbox-close" onClick={() => setPreview(null)} aria-label="Close">
<X size={24} />
</button>
{screenshots.length > 1 && (
<button
className="screenshot-lightbox-arrow screenshot-lightbox-prev"
onClick={(e) => { e.stopPropagation(); setPreview(p => p !== null ? (p > 0 ? p - 1 : screenshots.length - 1) : null); }}
aria-label="Previous"
>
<ChevronLeft size={28} />
</button>
)}
<img
src={screenshots[preview]}
alt={`Screenshot ${preview + 1}`}
className="screenshot-lightbox-img"
onClick={(e) => e.stopPropagation()}
/>
{screenshots.length > 1 && (
<button
className="screenshot-lightbox-arrow screenshot-lightbox-next"
onClick={(e) => { e.stopPropagation(); setPreview(p => p !== null ? (p < screenshots.length - 1 ? p + 1 : 0) : null); }}
aria-label="Next"
>
<ChevronRight size={28} />
</button>
)}
<div className="screenshot-lightbox-counter">{preview + 1} / {screenshots.length}</div>
</div>,
document.body
)}
</div>
</>
);
}
-2
View File
@@ -5,13 +5,11 @@ export default function StatsBar() {
const { t } = useI18n();
const projects = siteData.projects;
const totalStars = projects.reduce((sum, p) => sum + p.stars, 0);
const uniqueTechStacks = new Set(projects.flatMap((p) => p.techStack)).size;
const uniquePlatforms = new Set(projects.flatMap((p) => p.platforms)).size;
const stats = [
{ id: 'projects', value: projects.length, label: t('stats.projects') },
{ id: 'stars', value: totalStars, label: t('stats.stars') },
{ id: 'techStack', value: uniqueTechStacks, label: t('stats.techStack') },
{ id: 'platforms', value: uniquePlatforms, label: t('stats.platforms') },
];
+1 -4
View File
@@ -8,7 +8,6 @@ hero.cta.github: 'Visit GitHub'
hero.cta.download: 'Download Software'
hero.cta.docs: 'View Docs'
stats.projects: 'Open Source Projects'
stats.stars: 'GitHub Stars'
stats.techStack: 'Tech Stacks'
stats.platforms: 'Platforms'
featured.title: 'Featured Projects'
@@ -30,7 +29,6 @@ projects.filter.tech: 'Tech Stack'
projects.filter.platform: 'Platform'
projects.filter.status: 'Status'
projects.sort.updated: 'Recently Updated'
projects.sort.stars: 'Most Stars'
projects.sort.name: 'By Name'
projects.noResults: 'No matching projects'
projects.search: 'Search project name, description, or tags...'
@@ -49,6 +47,7 @@ detail.platforms: 'Platforms'
detail.status: 'Status'
detail.lastUpdate: 'Last Updated'
detail.repo: 'GitHub Repo'
detail.website: 'Website'
detail.docs: 'Online Docs'
detail.release: 'Download Release'
detail.installGuide: 'Installation Guide'
@@ -119,8 +118,6 @@ common.download: 'Download'
common.docs: 'Docs'
common.demo: 'Live Demo'
common.back: 'Back'
common.stars: 'Stars'
common.forks: 'Forks'
common.version: 'Version'
common.platform: 'Platform'
common.size: 'Size'
+1 -4
View File
@@ -8,7 +8,6 @@ hero.cta.github: '访问 GitHub'
hero.cta.download: '下载软件'
hero.cta.docs: '查看文档'
stats.projects: '开源项目'
stats.stars: 'GitHub Stars'
stats.techStack: '技术栈'
stats.platforms: '支持平台'
featured.title: '重点项目'
@@ -30,7 +29,6 @@ projects.filter.tech: '技术栈'
projects.filter.platform: '平台'
projects.filter.status: '状态'
projects.sort.updated: '最近更新'
projects.sort.stars: 'Star 最多'
projects.sort.name: '名称排序'
projects.noResults: '没有匹配的项目'
projects.search: '搜索项目名称、描述或标签...'
@@ -49,6 +47,7 @@ detail.platforms: '支持平台'
detail.status: '开发状态'
detail.lastUpdate: '最后更新'
detail.repo: 'GitHub 仓库'
detail.website: '官网'
detail.docs: '在线文档'
detail.release: '下载 Release'
detail.installGuide: '安装说明'
@@ -119,8 +118,6 @@ common.download: '下载'
common.docs: '文档'
common.demo: '在线演示'
common.back: '返回'
common.stars: 'Stars'
common.forks: 'Forks'
common.version: '版本'
common.platform: '平台'
common.size: '大小'
+1 -2
View File
@@ -44,13 +44,12 @@ tags:
- 'WebRTC'
- 'Self-hosted'
icon: 'Monitor'
logo: '/logos/billddesk.png'
repoUrl: 'https://github.com/shenjianZ/billddesk'
docsUrl: 'https://github.com/shenjianZ/billddesk#readme'
latestVersion: 'v0.8.0'
releaseDate: '2026-02-14'
license: 'Apache-2.0'
stars: 45
forks: 9
language: 'TypeScript'
lastUpdated: '2026-04-20'
recommended: false
+1 -2
View File
@@ -45,13 +45,12 @@ tags:
- 'Account Management'
- 'Desktop'
icon: 'KeyRound'
logo: '/logos/codex-manager.png'
repoUrl: 'https://github.com/shenjianZ/codex-manager'
docsUrl: 'https://github.com/shenjianZ/codex-manager#readme'
latestVersion: 'v0.1.0-beta'
releaseDate: '2026-04-28'
license: 'MIT'
stars: 34
forks: 2
language: 'Rust'
lastUpdated: '2026-05-05'
recommended: false
+1 -2
View File
@@ -49,13 +49,12 @@ tags:
- 'Scrcpy'
- 'Debug'
icon: 'Smartphone'
logo: '/logos/devicedeck.png'
repoUrl: 'https://github.com/shenjianZ/devicedeck'
docsUrl: 'https://github.com/shenjianZ/devicedeck#readme'
latestVersion: 'v0.3.0'
releaseDate: '2026-04-20'
license: 'MIT'
stars: 72
forks: 6
language: 'Rust'
lastUpdated: '2026-05-12'
recommended: true
+1 -2
View File
@@ -45,13 +45,12 @@ tags:
- 'Classification'
- 'API'
icon: 'Brain'
logo: '/logos/news-classifier.png'
repoUrl: 'https://github.com/shenjianZ/news-classifier'
docsUrl: 'https://github.com/shenjianZ/news-classifier#readme'
latestVersion: 'v0.1.0'
releaseDate: '2025-12-01'
license: 'MIT'
stars: 23
forks: 3
language: 'Python'
lastUpdated: '2026-03-15'
recommended: false
+171 -66
View File
@@ -4,15 +4,15 @@ displayName:
zh: 'QuantaNote'
en: 'QuantaNote'
slogan:
zh: '本地优先的跨平台桌面笔记与知识管理工具'
en: 'Local-first cross-platform desktop note & knowledge management tool'
zh: '本地优先的跨平台桌面笔记与知识管理工具 — Markdown 编辑、全文搜索、悬浮球快捷操作'
en: 'Local-first cross-platform desktop note & knowledge management — Markdown editing, full-text search, floating ball quick actions'
description:
zh: 'QuantaNote 是一个基于 Tauri 2、Rust 和 React 构建的本地优先桌面笔记软件。面向需要离线使用、Markdown 编辑、资料归档、快速搜索和长期保存笔记的用户。相比传统云笔记,它更强调本地数据控制、轻量启动和跨平台桌面体验。'
en: 'QuantaNote is a local-first desktop note-taking app built with Tauri 2, Rust, and React. Designed for users who need offline Markdown editing, knowledge archiving, fast search, and long-term note storage. Emphasizes local data control, lightweight startup, and cross-platform desktop experience.'
zh: 'QuantaNote 是一个基于 Tauri 2、Rust 和 React 构建的本地优先桌面笔记软件。支持 Markdown 编辑Vditor IR 模式)、FTS5 全文搜索(含中文子串)、标签管理、版本历史 Diff 对比、附件预览、导入导出、自动备份、悬浮球快捷操作等功能。所有数据存储在本地 SQLite,强调数据主权和轻量跨平台体验。支持云同步(开发中)。'
en: 'QuantaNote is a local-first desktop note app built with Tauri 2, Rust, and React. Features include Markdown editing (Vditor IR mode), FTS5 full-text search with Chinese substring support, tag management, version history with diff comparison, attachment preview, import/export, auto backup, and floating ball quick actions. All data is stored in local SQLite, emphasizing data ownership and lightweight cross-platform experience. Cloud sync is under development.'
type:
- 'desktop'
- 'devtool'
status: 'active'
status: 'maintained'
platforms:
- 'windows'
- 'macos'
@@ -20,50 +20,63 @@ platforms:
techStack:
- 'Tauri 2'
- 'Rust'
- 'React'
- 'React 19'
- 'TypeScript'
- 'SQLite'
- 'TailwindCSS'
- 'SQLite (rusqlite 0.35)'
- 'TailwindCSS 4'
- 'Vditor'
- 'Zustand'
- 'i18next'
features:
zh:
- 'Markdown 编辑'
- '本地 SQLite 存储'
- '全文搜索'
- '标签管理'
- '附件预览'
- '版本历史'
- '导入导出'
- '自动备份'
- '主题切换'
- '系统托盘'
- 'Markdown 编辑器(Vditor IR 模式)'
- '全文搜索(FTS5 + trigram 双引擎,支持中文子串)'
- '标签管理(CRUD + 多对多关联)'
- '命令面板(Ctrl+K 全局快速搜索)'
- '版本历史与 Diff 对比'
- '附件管理(图片/音频/视频/PDF 预览)'
- '数据导入导出JSON / ZIP 可选)'
- '定时自动备份'
- '深色/浅色主题 + 自定义强调色'
- '系统托盘 + 开机自启动'
- '悬浮球快捷操作(径向菜单)'
- '快速笔记独立窗口'
- '中英文国际化'
- '云同步(开发中)'
en:
- 'Markdown editing'
- 'Local SQLite storage'
- 'Full-text search'
- 'Tag management'
- 'Attachment preview'
- 'Version history'
- 'Import/export'
- 'Auto backup'
- 'Theme switching'
- 'System tray'
- 'Markdown editor (Vditor IR mode)'
- 'Full-text search (FTS5 + trigram, Chinese substring)'
- 'Tag management (CRUD + many-to-many)'
- 'Command palette (Ctrl+K global search)'
- 'Version history with diff comparison'
- 'Attachment management (image/audio/video/PDF preview)'
- 'Import/Export (JSON / ZIP selectable)'
- 'Scheduled auto backup'
- 'Dark/Light theme + custom accent colors'
- 'System tray + auto-start on boot'
- 'Floating ball quick actions (radial menu)'
- 'Quick note standalone window'
- 'i18n (Chinese / English)'
- 'Cloud sync (WIP)'
tags:
- 'Markdown'
- 'Notes'
- 'Knowledge Management'
- 'Desktop'
- 'Notebook'
- 'Local-first'
- 'Tauri'
- 'Vditor'
icon: 'NotebookPen'
repoUrl: 'https://github.com/shenjianZ/quantanote'
docsUrl: 'https://github.com/shenjianZ/quantanote#readme'
latestVersion: 'v0.2.0'
releaseDate: '2026-04-15'
logo: '/logos/quantanote.png'
repoUrl: 'https://github.com/shenjianZ/QuantaNote'
websiteUrl: 'https://shenjianz.github.io/QuantaNote/'
docsUrl: 'https://quantanote-docs.shenjianl.cn/'
latestVersion: 'v0.2.3'
releaseDate: '2026-05-12'
license: 'MIT'
stars: 128
forks: 12
language: 'Rust'
lastUpdated: '2026-05-10'
lastUpdated: '2026-05-12'
recommended: true
featured: true
order: 1
@@ -71,23 +84,23 @@ color: '#3B82F6'
downloads:
- platform: 'Windows'
arch: 'x64'
url: 'https://github.com/shenjianZ/quantanote/releases/download/v0.2.0/QuantaNote_0.2.0_x64-setup.exe'
size: '22.6 MB'
url: 'https://file.shenjianl.cn/softwore/QuantaNote/v0.2.3/QuantaNote-v0.2.3-windows-x64.exe'
size: '9.6 MB'
sha256: ''
- platform: 'macOS'
arch: 'Apple Silicon'
url: 'https://github.com/shenjianZ/quantanote/releases/download/v0.2.0/QuantaNote_0.2.0_aarch64.dmg'
size: '18.3 MB'
url: 'https://file.shenjianl.cn/softwore/QuantaNote/v0.2.3/QuantaNote-v0.2.3-macos-aarch64.dmg'
size: '14.3 MB'
sha256: ''
- platform: 'macOS'
arch: 'Intel'
url: 'https://github.com/shenjianZ/quantanote/releases/download/v0.2.0/QuantaNote_0.2.0_x64.dmg'
size: '19.1 MB'
url: 'https://file.shenjianl.cn/softwore/QuantaNote/v0.2.3/QuantaNote-v0.2.3-macos-x64.dmg'
size: '14.7 MB'
sha256: ''
- platform: 'Linux'
arch: 'x64'
url: 'https://github.com/shenjianZ/quantanote/releases/download/v0.2.0/QuantaNote_0.2.0_amd64.AppImage'
size: '20.2 MB'
url: 'https://file.shenjianl.cn/softwore/QuantaNote/v0.2.3/QuantaNote-v0.2.3-linux-x64.AppImage'
size: '86.1 MB'
sha256: ''
roadmap:
done:
@@ -96,44 +109,136 @@ roadmap:
- '本地存储'
- '标签系统'
- '全文搜索'
- '版本历史与 Diff'
- '附件管理'
- '导入导出'
- '自动备份'
- '主题与自定义强调色'
- '系统托盘与开机自启'
- '账号管理'
- '悬浮球快捷操作'
- '快速笔记窗口'
doing:
- '云同步'
- '多端同步'
- '账号管理'
planned:
- '插件系统'
- 'MCP 接入'
- '移动端查看'
- 'AI 辅助写作'
changelog:
- version: 'v0.2.0'
date: '2026-04-15'
- version: 'v0.2.3'
date: '2026-05-12'
changes:
zh:
- '新增账号管理模块'
- '修复 Token 刷新竞态'
- '优化同步状态显示'
- '新增附件预览支持'
- '悬浮球功能:收起时球体、展开时径向菜单,支持快速笔记和搜索'
- '悬浮球设置项:透明度调节、窗口置顶、开关控制'
- '快速笔记独立窗口,支持 Markdown 编辑'
- '完整中英文国际化支持'
en:
- 'Added account management'
- 'Fixed token refresh race condition'
- 'Improved sync status display'
- 'Added attachment preview'
- 'Floating ball: radial menu for quick notes and search'
- 'Floating ball settings: opacity, always-on-top, toggle'
- 'Quick note standalone window with Markdown editing'
- 'Full Chinese/English i18n support'
- version: 'v0.2.2'
date: '2026-05-11'
changes:
zh:
- '修复更新包公钥配置被占位符覆盖导致签名校验失败'
- '更新关于页的更新错误提示'
en:
- 'Fixed updater public key config overridden by placeholder'
- 'Improved update error messages on About page'
- version: 'v0.2.1'
date: '2026-05-11'
changes:
zh:
- '升级 rusqlite 0.31→0.35、thiserror 1→2'
- '同步状态管理改用 Result 替代 unwrap'
- '新增密码长度、标签颜色等输入验证'
- '修复前端 TopBar 非 Tauri 环境报错'
- '编辑器搜索高亮改用 DOM API'
en:
- 'Upgraded rusqlite 0.31→0.35, thiserror 1→2'
- 'Sync state management uses Result instead of unwrap'
- 'Added input validation for password length, tag colors'
- 'Fixed TopBar errors in non-Tauri environment'
- 'Editor search highlight uses DOM API'
- version: 'v0.2.0'
date: '2026-05-05'
changes:
zh:
- '新增账号管理(个人资料、修改密码、删除账号)'
- '云同步增强:Token 刷新竞态修复、同步状态指示器'
- 'Docker 多阶段构建 + docker-compose 部署'
- '文档站自动部署到 GitHub Pages'
en:
- 'Account management (profile, password change, account deletion)'
- 'Cloud sync: token refresh race fix, sync status indicator'
- 'Docker multi-stage build + docker-compose deployment'
- 'Docs auto-deploy to GitHub Pages'
- version: 'v0.1.0'
date: '2026-02-20'
date: '2026-05-02'
changes:
zh:
- '首个公开版本'
- '基础笔记 CRUD'
- 'Markdown 编辑器'
- '本地 SQLite 存储'
- '标签管理'
- 'Markdown 编辑器(Vditor IR 模式)'
- '全文搜索(FTS5 + trigram 双引擎)'
- '版本历史与 Diff 对比'
- '附件管理、导入导出、自动备份'
- '主题切换、系统托盘'
en:
- 'First public release'
- 'Basic note CRUD'
- 'Markdown editor'
- 'Local SQLite storage'
- 'Tag management'
- 'Markdown editor (Vditor IR mode)'
- 'Full-text search (FTS5 + trigram dual engine)'
- 'Version history with diff comparison'
- 'Attachments, import/export, auto backup'
- 'Theme switching, system tray'
architecture:
zh: '前端 (React + TypeScript) → Tauri Commands → Rust 核心层 → SQLite 数据库 → 本地文件存储'
en: 'Frontend (React + TypeScript) → Tauri Commands → Rust Core → SQLite Database → Local File Storage'
zh: '前端 (React 19 + Zustand + TailwindCSS 4) → Tauri 2 Commands → Rust 核心层 (rusqlite 0.35) → SQLite (WAL + FTS5) → 本地文件存储'
en: 'Frontend (React 19 + Zustand + TailwindCSS 4) → Tauri 2 Commands → Rust Core (rusqlite 0.35) → SQLite (WAL + FTS5) → Local File Storage'
screenshots:
- '/screenshots/quantanote/library.png'
- '/screenshots/quantanote/note-preview.png'
- '/screenshots/quantanote/note-edit.png'
- '/screenshots/quantanote/note-version.png'
- '/screenshots/quantanote/workspace.png'
- '/screenshots/quantanote/search-cmd.png'
- '/screenshots/quantanote/settings-appearance.png'
- '/screenshots/quantanote/settings-font.png'
- '/screenshots/quantanote/settings-data.png'
- '/screenshots/quantanote/settings-sync.png'
- '/screenshots/quantanote/settings-about.png'
- '/screenshots/quantanote/accoun-login.png'
- '/screenshots/quantanote/account-register.png'
- '/screenshots/quantanote/account-profile.png'
- '/screenshots/quantanote/account.png'
- '/screenshots/quantanote/topbar-more.png'
installGuide:
zh:
- platform: 'Windows'
icon: '🪟'
format: '.exe'
tip: 'SmartScreen 拦截?点击"更多信息" → "仍要运行"'
- platform: 'macOS'
icon: '🍎'
format: '.dmg'
tip: '提示已损坏?终端运行: xattr -dr com.apple.quarantine /Applications/QuantaNote.app'
- platform: 'Linux'
icon: '🐧'
format: '.AppImage'
tip: 'chmod +x QuantaNote-*.AppImage && ./QuantaNote-*.AppImage'
en:
- platform: 'Windows'
icon: '🪟'
format: '.exe'
tip: 'SmartScreen blocked? Click "More info" → "Run anyway"'
- platform: 'macOS'
icon: '🍎'
format: '.dmg'
tip: '"Damaged" error? Run: xattr -dr com.apple.quarantine /Applications/QuantaNote.app'
- platform: 'Linux'
icon: '🐧'
format: '.AppImage'
tip: 'chmod +x QuantaNote-*.AppImage && ./QuantaNote-*.AppImage'
+1 -2
View File
@@ -47,14 +47,13 @@ tags:
- 'NPM'
- 'MDX'
icon: 'BookOpen'
logo: '/logos/react-docs-ui.png'
repoUrl: 'https://github.com/shenjianZ/react-docs-ui'
docsUrl: 'https://github.com/shenjianZ/react-docs-ui#readme'
npmUrl: 'https://www.npmjs.com/package/react-docs-ui'
latestVersion: 'v0.5.2'
releaseDate: '2026-05-10'
license: 'MIT'
stars: 203
forks: 24
language: 'TypeScript'
lastUpdated: '2026-05-18'
recommended: true
+1 -2
View File
@@ -50,13 +50,12 @@ tags:
- 'SFTP'
- 'DevOps'
icon: 'Terminal'
logo: '/logos/ssh-terminal.png'
repoUrl: 'https://github.com/shenjianZ/ssh-terminal'
docsUrl: 'https://github.com/shenjianZ/ssh-terminal#readme'
latestVersion: 'v0.1.5'
releaseDate: '2026-03-28'
license: 'MIT'
stars: 89
forks: 8
language: 'Rust'
lastUpdated: '2026-05-08'
recommended: true
+1 -2
View File
@@ -46,13 +46,12 @@ tags:
- 'Social'
- 'Mobile'
icon: 'MapPin'
logo: '/logos/streetmoment.png'
repoUrl: 'https://github.com/shenjianZ/streetmoment'
docsUrl: 'https://github.com/shenjianZ/streetmoment#readme'
latestVersion: 'v1.0.0'
releaseDate: '2026-05-01'
license: 'MIT'
stars: 56
forks: 5
language: 'TypeScript'
lastUpdated: '2026-05-15'
recommended: false
+1 -2
View File
@@ -39,8 +39,7 @@ export function useProjectFilters() {
if (platform) result = result.filter((p) => p.platforms.includes(platform));
if (status) result = result.filter((p) => p.status === status);
if (sort === 'stars') result.sort((a, b) => b.stars - a.stars);
else if (sort === 'name') result.sort((a, b) => a.name.localeCompare(b.name));
if (sort === 'name') result.sort((a, b) => a.name.localeCompare(b.name));
else result.sort((a, b) => b.lastUpdated.localeCompare(a.lastUpdated));
return result;
+34 -45
View File
@@ -6,7 +6,7 @@ import RoadmapGrid from '../components/RoadmapGrid';
import ChangelogList from '../components/ChangelogList';
import ScreenshotCarousel from '../components/ScreenshotCarousel';
import { getIcon } from '../utils/iconRegistry';
import { ExternalLink, Download, BookOpen } from 'lucide-react';
import { ExternalLink, Download, BookOpen, Globe } from 'lucide-react';
export default function ProjectDetailPage() {
const { id } = useParams();
@@ -44,7 +44,10 @@ export default function ProjectDetailPage() {
<div className="detail-header">
<div className="detail-header-top">
<div className="detail-icon">
{IconComponent ? <IconComponent size={28} /> : <span>{p.icon}</span>}
{p.logo
? <img src={p.logo} alt={p.name} className="project-logo" />
: (IconComponent ? <IconComponent size={28} /> : <span>{p.icon}</span>)
}
</div>
<div>
<h1 className="detail-title">{p.displayName[lang] || p.name}</h1>
@@ -70,10 +73,12 @@ export default function ProjectDetailPage() {
</span>
</div>
<div className="detail-actions">
<a href={p.repoUrl} target="_blank" className="btn btn-primary">
<ExternalLink size={16} />
GitHub
</a>
{p.websiteUrl && (
<a href={p.websiteUrl} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
<Globe size={16} />
{t('detail.website')}
</a>
)}
{hasDownloads && (
<a href="#downloads" className="btn" onClick={(e) => {
e.preventDefault();
@@ -130,18 +135,21 @@ export default function ProjectDetailPage() {
<div className="detail-section" id="downloads">
<h2 className="detail-section-title">{t('detail.downloads')}</h2>
<DownloadTable downloads={p.downloads} />
{p.installGuide && (
<div className="install-guide-inline">
{p.installGuide[lang].map((item) => (
<div key={item.platform} className="install-tip-row">
<span className="install-tip-icon">{item.icon}</span>
<span className="install-tip-format">{item.format}</span>
<span className="install-tip-text">{item.tip}</span>
</div>
))}
</div>
)}
<div className="trust-note">{t('downloads.trustNote')}</div>
</div>
)}
{/* Roadmap */}
{p.roadmap && (
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.roadmap')}</h2>
<RoadmapGrid roadmap={p.roadmap} />
</div>
)}
{/* Changelog */}
{p.changelog && p.changelog.length > 0 && (
<div className="detail-section">
@@ -150,30 +158,13 @@ export default function ProjectDetailPage() {
</div>
)}
{/* Install guide */}
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.installGuide')}</h2>
<div className="install-list">
<div className="install-item">
<strong>Windows</strong>
{t('detail.install.windows')}
</div>
<div className="install-item">
<strong>macOS</strong>
{t('detail.install.macos')}
</div>
<div className="install-item">
<strong>Linux</strong>
{t('detail.install.linux')}
</div>
{p.platforms.includes('android') && (
<div className="install-item">
<strong>Android</strong>
{t('detail.install.android')}
</div>
)}
{/* Roadmap */}
{p.roadmap && (
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.roadmap')}</h2>
<RoadmapGrid roadmap={p.roadmap} />
</div>
</div>
)}
</div>
{/* Sidebar */}
@@ -191,14 +182,6 @@ export default function ProjectDetailPage() {
<span className="detail-meta-label">{t('detail.license')}</span>
<span className="detail-meta-value">{p.license}</span>
</div>
<div className="detail-meta-item">
<span className="detail-meta-label">{t('common.stars')}</span>
<span className="detail-meta-value mono">{p.stars}</span>
</div>
<div className="detail-meta-item">
<span className="detail-meta-label">{t('common.forks')}</span>
<span className="detail-meta-value mono">{p.forks}</span>
</div>
<div className="detail-meta-item">
<span className="detail-meta-label">{t('detail.lastUpdate')}</span>
<span className="detail-meta-value">{p.lastUpdated}</span>
@@ -213,6 +196,12 @@ export default function ProjectDetailPage() {
<ExternalLink size={15} />
{t('detail.repo')}
</a>
{p.websiteUrl && (
<a href={p.websiteUrl} target="_blank" rel="noopener noreferrer" className="detail-link-btn">
<Globe size={15} />
{t('detail.website')}
</a>
)}
{p.docsUrl && (
<a href={p.docsUrl} target="_blank" rel="noopener noreferrer" className="detail-link-btn">
<BookOpen size={15} />
-1
View File
@@ -45,7 +45,6 @@ export default function ProjectsPage() {
];
const sortOptions = [
{ value: 'updated', label: t('projects.sort.updated') },
{ value: 'stars', label: t('projects.sort.stars') },
{ value: 'name', label: t('projects.sort.name') },
];
+7
View File
@@ -169,6 +169,13 @@
transform: scale(1.08);
}
.project-logo {
width: 32px;
height: 32px;
object-fit: contain;
border-radius: 4px;
}
.project-card-info {
flex: 1;
min-width: 0;
+221 -7
View File
@@ -70,6 +70,11 @@
background: linear-gradient(135deg, oklch(100% 0 0 / 10%) 0%, transparent 60%);
}
.detail-icon .project-logo {
width: 48px;
height: 48px;
}
.detail-title {
font-size: 36px;
font-weight: 800;
@@ -290,12 +295,13 @@
.screenshot-scroll {
display: flex;
align-items: flex-start;
gap: 12px;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
padding-bottom: 4px;
}
.screenshot-scroll::-webkit-scrollbar {
@@ -305,13 +311,176 @@
.screenshot-slide {
flex: 0 0 85%;
max-width: 400px;
aspect-ratio: 16 / 10;
scroll-snap-align: center;
cursor: pointer;
overflow: hidden;
}
.screenshot-slide:first-child {
margin-inline-start: 4px;
}
.screenshot-image {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
border-radius: var(--radius-md);
border: 1px solid var(--border);
transition: transform 0.2s, box-shadow 0.2s;
}
.screenshot-slide:hover .screenshot-image {
transform: scale(1.02);
box-shadow: 0 4px 20px oklch(0% 0 0 / 30%);
}
/* Desktop arrows — hidden on mobile */
.screenshot-arrow {
display: none;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid var(--border);
background: oklch(15% 0.025 270 / 80%);
backdrop-filter: blur(8px);
color: var(--fg);
cursor: pointer;
align-items: center;
justify-content: center;
transition: all var(--transition);
}
.screenshot-arrow:hover {
background: oklch(74% 0.2 45 / 20%);
border-color: oklch(74% 0.2 45 / 30%);
color: var(--accent);
}
.screenshot-arrow-prev {
left: 4px;
}
.screenshot-arrow-next {
right: 4px;
}
@media (min-width: 768px) {
.screenshot-slide {
flex: 0 0 calc(33.333% - 8px);
cursor: pointer;
}
.screenshot-arrow {
display: flex;
}
}
/* Lightbox overlay */
.screenshot-lightbox {
position: fixed;
inset: 0;
z-index: 9999;
background: oklch(0% 0 0 / 85%);
backdrop-filter: blur(12px);
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
cursor: zoom-out;
animation: lightbox-in 0.2s ease;
}
@keyframes lightbox-in {
from { opacity: 0; }
to { opacity: 1; }
}
.screenshot-lightbox-img {
max-width: 90vw;
max-height: 85vh;
object-fit: contain;
border-radius: var(--radius-lg);
box-shadow: 0 8px 40px oklch(0% 0 0 / 50%);
cursor: default;
animation: lightbox-zoom 0.25s ease;
}
@keyframes lightbox-zoom {
from { transform: scale(0.92); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.screenshot-lightbox-close {
position: absolute;
top: 16px;
right: 16px;
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid oklch(100% 0 0 / 20%);
background: oklch(0% 0 0 / 50%);
color: oklch(100% 0 0 / 80%);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
z-index: 10;
}
.screenshot-lightbox-close:hover {
background: oklch(0% 0 0 / 70%);
color: oklch(100% 0 0 / 100%);
}
.screenshot-lightbox-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 48px;
height: 48px;
border-radius: 50%;
border: 1px solid oklch(100% 0 0 / 20%);
background: oklch(0% 0 0 / 40%);
color: oklch(100% 0 0 / 80%);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
z-index: 10;
}
.screenshot-lightbox-arrow:hover {
background: oklch(0% 0 0 / 60%);
color: oklch(100% 0 0 / 100%);
}
.screenshot-lightbox-prev {
left: 16px;
}
.screenshot-lightbox-next {
right: 16px;
}
.screenshot-lightbox-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: oklch(100% 0 0 / 60%);
font-size: 14px;
font-weight: 500;
letter-spacing: 0.05em;
}
.screenshot-placeholder {
aspect-ratio: 16/10;
border-radius: var(--radius-lg);
@@ -365,12 +534,6 @@
box-shadow: 0 0 8px oklch(74% 0.2 45 / 30%);
}
@media (min-width: 768px) {
.screenshot-slide {
flex: 0 0 calc(33.333% - 8px);
}
}
/* ── Install guide ───────────────────────────────────── */
.install-list {
display: flex;
@@ -405,6 +568,57 @@
letter-spacing: 0.03em;
}
/* ── Install guide inline (compact) ──────────────────── */
.install-guide-inline {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 12px;
padding: 8px 10px;
border-radius: var(--radius-md);
border: 1px dashed oklch(74% 0.2 45 / 15%);
background: oklch(74% 0.2 45 / 3%);
}
:root.light .install-guide-inline {
border-color: oklch(58% 0.22 45 / 15%);
background: oklch(58% 0.22 45 / 3%);
}
.install-tip-row {
display: flex;
align-items: baseline;
gap: 8px;
font-size: 12px;
line-height: 1.6;
padding: 3px 4px;
border-radius: var(--radius-sm);
}
.install-tip-icon {
flex-shrink: 0;
font-size: 13px;
width: 18px;
text-align: center;
}
.install-tip-format {
flex-shrink: 0;
font-family: var(--font-mono);
font-size: 11px;
font-weight: 600;
color: var(--accent);
padding: 1px 6px;
border-radius: 4px;
background: oklch(74% 0.2 45 / 10%);
}
.install-tip-text {
color: var(--muted);
min-width: 0;
word-break: break-all;
}
/* ── Project selector (for roadmap/changelog) ────────── */
.project-selector {
display: flex;
+17 -2
View File
@@ -56,6 +56,20 @@ export interface ChangelogEntry {
changes: BilingualArray;
}
export interface InstallGuideItem {
platform: string;
icon: string;
format: string;
tip: string;
}
export type InstallGuide = InstallGuideItem[];
export interface BilingualInstallGuide {
zh: InstallGuide;
en: InstallGuide;
}
export interface Project {
id: string;
name: string;
@@ -69,14 +83,14 @@ export interface Project {
features: BilingualArray;
tags: string[];
icon: string;
logo?: string;
repoUrl: string;
websiteUrl?: string;
docsUrl?: string;
npmUrl?: string;
latestVersion: string;
releaseDate: string;
license: string;
stars: number;
forks: number;
language: string;
lastUpdated: string;
recommended: boolean;
@@ -88,6 +102,7 @@ export interface Project {
changelog?: ChangelogEntry[];
architecture?: BilingualText;
screenshots?: string[];
installGuide?: BilingualInstallGuide;
}
export interface AboutData {