Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
本项目采用 `0.0.x` 递增版本号。正式安装包、便携包和校验清单发布在
[GitHub Releases](https://github.com/PuppetWen/CDriveShiftAI/releases)。

## 0.0.6

### 极速搜索

- 搜索结果改用后端分页与游标,滚动接近底部时继续加载;筛选、排序和动态变化都针对完整结果集执行。
- 名称搜索支持包含匹配、完整词匹配、模糊匹配和正则匹配,正则模式继续提供模板和规则辅助。
- 搜索结果表格支持拖动调整列宽,并为可迁移目录增加快捷迁移入口。
- 重新整理文件类型、搜索位置、匹配方式、排序与更多条件的界面层级,在独立窗口较窄时优先保持排序区单行。

### 新增主题

- 新增“熔橙 · 熔芯蜂巢”:橙黑工业 HUD、蜂巢背景、切面控件和能量进度效果。
- 新增“暖瓷 · 暖瓷晨光”:米白纸感、陶土色强调、柔和卡片和低干扰动态背景。
- 五套主题统一覆盖主窗口、独立极速搜索、托盘原生菜单、上下文菜单、悬浮提示、属性窗口和迁移/更新进度。
- 独立极速搜索标题栏增加紧凑主题切换器,设置与主窗口共享并持久保存。

### 设置与交互

- 设置页按更新诊断、界面外观、索引快捷操作和 AI 服务重新分组,减少过长页面与无效空白。
- 主题切换不再触发全局快捷键重新注册错误;冲突状态留在对应设置项中等待用户处理。
- 保留搜索状态、书签、筛选、列宽、窗口位置以及即时生效的设置行为。

## 0.0.5

### 实时搜索与后台资源
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ CDriveShiftAI 是一个 Windows 桌面端磁盘整理工具,用于:

当前版本完全使用自研索引管线,不调用 Everything。

当前正式版本:`0.0.5`。安装包与便携包见
当前正式版本:`0.0.6`。安装包与便携包见
[GitHub Releases](https://github.com/PuppetWen/CDriveShiftAI/releases)。

## 已实现能力
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

CDriveShiftAI 是面向 Windows 10/11 x64 的全盘文件搜索、目录归属分析与跨盘安全迁移工具。名称索引、内容索引和迁移记录都由项目自身维护,不依赖 Everything。

当前版本:`0.0.5`
当前版本:`0.0.6`

## 下载与运行

Expand Down
31 changes: 31 additions & 0 deletions docs/RELEASE-0.0.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# CDriveShiftAI 0.0.6 发布说明

本版重点升级极速搜索工作区,并新增两套完整视觉主题。搜索结果可以持续加载、动态更新、按完整结果集排序,同时保持托盘静默时的低资源占用策略。

## 极速搜索

- 后端分页与游标支持连续懒加载,不再受首批 800 条名称结果或 160 条内容结果的界面上限限制。
- 筛选与排序在后端面向完整结果集执行;新增、删除、改名和移动继续按照当前规则实时更新。
- 支持包含匹配、完整词匹配、模糊匹配和正则匹配。
- 结果表头可以拖动调整列宽,并保存用户设置。
- 文件夹结果提供“分析”和“迁移”快捷操作,文件继续支持默认打开、选择应用、属性、重命名和删除等操作。

## 两套新增主题

- **熔橙 · 熔芯蜂巢**:黑棕底色、橙色能量线、蜂巢网格、工业切面卡片和高对比进度效果。
- **暖瓷 · 暖瓷晨光**:暖米白背景、陶土色强调、柔和阴影和编辑式排版。
- 两套主题均覆盖主窗口、独立极速搜索、托盘菜单、上下文菜单、悬浮提示、属性窗口、对话框以及迁移和更新进度。
- 独立极速搜索窗口右上角可以直接切换全部五套主题,选择会同步到主窗口并持久保存。

## 设置与交互

- 设置页重新按功能拆分模块,减少纵向滚动和卡片中的无效空白。
- 主题切换不会重新注册全局快捷键;快捷键冲突只在对应设置项显示红色状态提醒。
- 搜索筛选区在普通与独立窗口中采用统一结构,并优先保持排序与属性区域单行。

## 验证

- TypeScript 与 Electron 类型检查通过。
- 37 项单元测试通过。
- 安装包与便携包完成实际构建。
- 便携包单文件启动及项目旁数据目录测试通过。
122 changes: 96 additions & 26 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ const effectColors: Record<
> = {
aurora: { background: "#183841", symbols: "#f2e7c6", nativeTheme: "dark" },
matrix: { background: "#050815", symbols: "#a8eefa", nativeTheme: "dark" },
calm: { background: "#edf4f5", symbols: "#526f7e", nativeTheme: "light" }
calm: { background: "#edf4f5", symbols: "#526f7e", nativeTheme: "light" },
ember: { background: "#0b0705", symbols: "#ffb078", nativeTheme: "dark" },
ivory: { background: "#f6f2ea", symbols: "#75534b", nativeTheme: "light" }
};

function applyNativeEffect(
Expand Down Expand Up @@ -734,17 +736,20 @@ function createTray(): void {
submenu: ([
["aurora", "方块 · 像素湖境"],
["matrix", "科技 · HUD 数据流"],
["calm", "晶境 · 玻璃流光"]
["calm", "晶境 · 玻璃流光"],
["ember", "熔橙 · 熔芯蜂巢"],
["ivory", "暖瓷 · 米白陶影"]
] as const).map(([effectMode, label]) => ({
label,
type: "radio" as const,
checked: settings.effectMode === effectMode,
icon: createTrayMenuIcon(
effectMode === "aurora"
? "theme-aurora"
: effectMode === "matrix"
? "theme-matrix"
: "theme-calm"
`theme-${effectMode}` as
| "theme-aurora"
| "theme-matrix"
| "theme-calm"
| "theme-ember"
| "theme-ivory"
),
click: () => {
void store.updateSettings({ effectMode }).then((updated) => {
Expand Down Expand Up @@ -1077,10 +1082,15 @@ function registerIpc(): void {
...previous,
...(safePatch as Partial<Omit<AppSettings, "ai">>)
};
const shortcutFailures = applyGlobalShortcuts(candidate);
if (shortcutFailures.length > 0) {
applyGlobalShortcuts(previous);
throw new Error(shortcutFailures.join(";"));
const changesKeyboardShortcuts =
Object.prototype.hasOwnProperty.call(safePatch, "globalShortcut") ||
Object.prototype.hasOwnProperty.call(safePatch, "quickSearchShortcut");
if (changesKeyboardShortcuts) {
const shortcutFailures = applyGlobalShortcuts(candidate);
if (shortcutFailures.length > 0) {
applyGlobalShortcuts(previous);
throw new Error(shortcutFailures.join(";"));
}
}
try {
const settings = await store.updateSettings(
Expand All @@ -1099,7 +1109,7 @@ function registerIpc(): void {
createTray();
return settings;
} catch (error) {
applyGlobalShortcuts(previous);
if (changesKeyboardShortcuts) applyGlobalShortcuts(previous);
throw error;
}
});
Expand Down Expand Up @@ -1519,7 +1529,7 @@ function registerIpc(): void {

ipcMain.handle(
"search:query",
async (_event, query: unknown, filters: unknown) => {
async (_event, query: unknown, filters: unknown, pageOptions?: unknown) => {
const value = assertString(query, "搜索词", 512);
const raw = (filters ?? {}) as Partial<SearchFilters>;
const allowedCategories = new Set<SearchCategory>([
Expand All @@ -1542,6 +1552,9 @@ function registerIpc(): void {
"type"
]);
const allowedSortDirections = new Set<SearchSortDirection>(["asc", "desc"]);
const regex = raw.regex === true;
const fuzzy = !regex && raw.fuzzy === true;
const wholeWord = !regex && !fuzzy && raw.wholeWord === true;
const safeFilters: SearchFilters = {
kind: ["all", "folder", "file"].includes(raw.kind ?? "") ? raw.kind! : "all",
scope: typeof raw.scope === "string" && raw.scope ? raw.scope : "*",
Expand All @@ -1568,16 +1581,30 @@ function registerIpc(): void {
modifiedAfter: typeof raw.modifiedAfter === "string" ? raw.modifiedAfter : undefined,
modifiedBefore: typeof raw.modifiedBefore === "string" ? raw.modifiedBefore : undefined,
caseSensitive: raw.caseSensitive === true,
wholeWord: raw.wholeWord === true,
wholeWord,
fuzzy,
matchPath: raw.matchPath === true,
regex: raw.regex === true,
regex,
sortBy:
raw.sortBy && allowedSortFields.has(raw.sortBy) ? raw.sortBy : "relevance",
sortDirection:
raw.sortDirection && allowedSortDirections.has(raw.sortDirection)
? raw.sortDirection
: "desc"
};
if (pageOptions && typeof pageOptions === "object") {
const rawPage = pageOptions as { cursor?: unknown; limit?: unknown };
return searchService!.searchPage(value, safeFilters, {
cursor:
typeof rawPage.cursor === "string" && rawPage.cursor.length <= 256
? rawPage.cursor
: undefined,
limit:
typeof rawPage.limit === "number"
? Math.min(10_000, Math.max(1, Math.trunc(rawPage.limit)))
: undefined
});
}
return searchService!.search(value, safeFilters);
}
);
Expand Down Expand Up @@ -1646,6 +1673,59 @@ function registerIpc(): void {
}
);

ipcMain.handle(
"search:content-query-page",
(_event, query: unknown, scope: unknown, options: unknown, pageOptions: unknown) => {
const raw =
options && typeof options === "object"
? (options as {
regex?: unknown;
caseSensitive?: unknown;
sortBy?: unknown;
sortDirection?: unknown;
minSize?: unknown;
maxSize?: unknown;
modifiedAfter?: unknown;
modifiedBefore?: unknown;
})
: {};
const rawPage =
pageOptions && typeof pageOptions === "object"
? (pageOptions as { cursor?: unknown; limit?: unknown })
: {};
return searchService!.searchContentPage(
assertString(query, "内容搜索词", 2_048),
assertString(scope, "内容搜索目录"),
{
regex: raw.regex === true,
caseSensitive: raw.caseSensitive === true,
sortBy: ["relevance", "name", "path", "size", "modified", "type"].includes(
String(raw.sortBy)
)
? (raw.sortBy as SearchSortField)
: "relevance",
sortDirection: ["asc", "desc"].includes(String(raw.sortDirection))
? (raw.sortDirection as SearchSortDirection)
: "desc",
minSize: typeof raw.minSize === "number" ? Math.max(0, raw.minSize) : undefined,
maxSize: typeof raw.maxSize === "number" ? Math.max(0, raw.maxSize) : undefined,
modifiedAfter:
typeof raw.modifiedAfter === "string" ? raw.modifiedAfter : undefined,
modifiedBefore:
typeof raw.modifiedBefore === "string" ? raw.modifiedBefore : undefined,
cursor:
typeof rawPage.cursor === "string" && rawPage.cursor.length <= 256
? rawPage.cursor
: undefined,
limit:
typeof rawPage.limit === "number"
? Math.min(2_000, Math.max(1, Math.trunc(rawPage.limit)))
: undefined
}
);
}
);

ipcMain.handle("analysis:summarize", (_event, targetPath: unknown) =>
summarizeDirectory(assertString(targetPath, "目录"))
);
Expand Down Expand Up @@ -1855,17 +1935,7 @@ if (!singleInstance) {
syncSearchBackgroundMode();
triggerVisibleUpdateCheck("application-startup", 2_500);
const shortcutFailures = applyGlobalShortcuts(store.getSettings());
if (shortcutFailures.length > 0 && mainWindow && !mainWindow.isDestroyed()) {
void dialog.showMessageBox(mainWindow, {
type: "warning",
title: "全局快捷键未完全启用",
message: shortcutFailures.join("\n"),
detail: "可在设置中修改快捷键后重新保存。",
buttons: ["知道了"],
noLink: true
});
}
if (shortcutFailures.length > 0 && startupMinimized) {
if (shortcutFailures.length > 0) {
logger.warn("shortcut.registration_incomplete", {
startupMinimized,
failures: shortcutFailures
Expand Down
34 changes: 34 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,42 @@ contextBridge.exposeInMainWorld("cDriveShiftAI", {
ipcRenderer.invoke("shell:search-context-menu", targetPath, isDirectory),
openExternal: (url: string) => ipcRenderer.invoke("shell:external", url),
search: (query: string, filters: unknown) => ipcRenderer.invoke("search:query", query, filters),
searchPage: (query: string, filters: unknown, pageOptions?: unknown) =>
ipcRenderer.invoke("search:query", query, filters, pageOptions ?? {}),
searchContent: (query: string, scope: string, options?: unknown) =>
ipcRenderer.invoke("search:content-query", query, scope, options),
searchContentPage: (
query: string,
scope: string,
options?: {
regex?: boolean;
caseSensitive?: boolean;
sortBy?: string;
sortDirection?: string;
minSize?: number;
maxSize?: number;
modifiedAfter?: string;
modifiedBefore?: string;
cursor?: string;
limit?: number;
}
) =>
ipcRenderer.invoke(
"search:content-query-page",
query,
scope,
{
regex: options?.regex,
caseSensitive: options?.caseSensitive,
sortBy: options?.sortBy,
sortDirection: options?.sortDirection,
minSize: options?.minSize,
maxSize: options?.maxSize,
modifiedAfter: options?.modifiedAfter,
modifiedBefore: options?.modifiedBefore
},
{ cursor: options?.cursor, limit: options?.limit }
),
indexContent: (scope: string) => ipcRenderer.invoke("search:content-index", scope),
contentIndexStatus: (scope: string) => ipcRenderer.invoke("search:content-status", scope),
indexerStatus: () => ipcRenderer.invoke("search:status"),
Expand Down
Loading
Loading