v0.3.1: 添加编辑器搜索/替换功能,修复ESLint和lint-staged错误

- 新增 SearchReplace 组件:Ctrl+F 搜索、Ctrl+H 替换、实时高亮、大小写切换、导航、全部替换
- 修复 tabStore.test.ts 未使用变量的 ESLint 错误
- 修复 lint-staged tsc 命令不兼容 Milkdown .d.ts 的问题
- 版本号 0.3.0 → 0.3.1 (package.json + AboutDialog)
- tsc 零错误、78/78 测试通过、构建成功
This commit is contained in:
thzxx
2026-06-04 10:22:15 +08:00
parent b1803c6467
commit b34cbb5de3
7 changed files with 642 additions and 46 deletions
+241 -37
View File
@@ -4,7 +4,8 @@
box-sizing: border-box;
}
html, body {
html,
body {
height: 100%;
font-family: var(--font-ui);
font-size: 14px;
@@ -33,7 +34,8 @@ html, body {
z-index: 10;
}
.toolbar-left, .toolbar-right {
.toolbar-left,
.toolbar-right {
display: flex;
align-items: center;
gap: 2px;
@@ -65,7 +67,9 @@ html, body {
color: var(--primary);
}
.toolbar-btn svg { flex-shrink: 0; }
.toolbar-btn svg {
flex-shrink: 0;
}
.toolbar-divider {
width: 1px;
@@ -129,7 +133,9 @@ html, body {
overflow-x: auto;
}
.editor-toolbar::-webkit-scrollbar { height: 0; }
.editor-toolbar::-webkit-scrollbar {
height: 0;
}
.toolbar-btn-sm {
display: flex;
@@ -203,9 +209,15 @@ html, body {
color: var(--text);
}
.milkdown-wrapper .milkdown .editor h1 { font-size: 1.6em; }
.milkdown-wrapper .milkdown .editor h2 { font-size: 1.4em; }
.milkdown-wrapper .milkdown .editor h3 { font-size: 1.2em; }
.milkdown-wrapper .milkdown .editor h1 {
font-size: 1.6em;
}
.milkdown-wrapper .milkdown .editor h2 {
font-size: 1.4em;
}
.milkdown-wrapper .milkdown .editor h3 {
font-size: 1.2em;
}
.milkdown-wrapper .milkdown .editor blockquote {
border-left: 3px solid var(--primary);
@@ -420,7 +432,9 @@ html, body {
.tab-item:hover .tab-close,
.tab-item.active .tab-close,
.tab-item.modified .tab-close { opacity: 1; }
.tab-item.modified .tab-close {
opacity: 1;
}
.tab-close:hover {
background: var(--border);
@@ -541,13 +555,16 @@ html, body {
flex-shrink: 0;
}
.status-left, .status-right {
.status-left,
.status-right {
display: flex;
align-items: center;
gap: 8px;
}
.status-divider { color: var(--border); }
.status-divider {
color: var(--border);
}
/* Drop Overlay */
#drop-overlay {
@@ -603,7 +620,9 @@ html, body {
animation: fadeIn 0.3s ease;
}
.welcome-icon { margin-bottom: 24px; }
.welcome-icon {
margin-bottom: 24px;
}
.welcome-content h1 {
font-size: 28px;
@@ -655,7 +674,9 @@ html, body {
border: 1px solid var(--border);
}
.welcome-btn.secondary:hover { background: var(--bg-tertiary); }
.welcome-btn.secondary:hover {
background: var(--bg-tertiary);
}
/* 最近打开文件 */
.welcome-recent {
@@ -698,7 +719,9 @@ html, body {
border-bottom: 1px solid var(--border-light);
}
.recent-item:last-child { border-bottom: none; }
.recent-item:last-child {
border-bottom: none;
}
.recent-item:hover {
background: var(--bg-tertiary);
@@ -737,7 +760,9 @@ html, body {
margin-bottom: 6px;
}
.welcome-tips p:last-child { margin-bottom: 0; }
.welcome-tips p:last-child {
margin-bottom: 0;
}
/* Sidebar */
#sidebar {
@@ -754,7 +779,9 @@ html, body {
position: relative;
}
#sidebar.collapsed { display: none; }
#sidebar.collapsed {
display: none;
}
#sidebar-header {
display: flex;
@@ -811,7 +838,9 @@ html, body {
border-radius: 0;
}
.tree-item:hover { background: var(--sidebar-hover); }
.tree-item:hover {
background: var(--sidebar-hover);
}
.tree-item.active {
background: var(--sidebar-active);
@@ -819,7 +848,9 @@ html, body {
font-weight: 500;
}
.tree-indent { flex-shrink: 0; }
.tree-indent {
flex-shrink: 0;
}
.tree-arrow {
display: flex;
@@ -832,7 +863,9 @@ html, body {
transition: transform 0.15s ease;
}
.tree-arrow.expanded { transform: rotate(90deg); }
.tree-arrow.expanded {
transform: rotate(90deg);
}
.tree-icon {
display: flex;
@@ -844,7 +877,10 @@ html, body {
margin-right: 4px;
}
.tree-icon svg { width: 14px; height: 14px; }
.tree-icon svg {
width: 14px;
height: 14px;
}
.tree-name {
overflow: hidden;
@@ -858,7 +894,8 @@ html, body {
padding-bottom: 4px;
}
.sidebar-section-header, .independent-files-header {
.sidebar-section-header,
.independent-files-header {
font-size: 11px;
font-weight: 600;
color: var(--text-tertiary);
@@ -889,8 +926,12 @@ html, body {
}
/* View Modes */
#app.mode-preview #editor-panel { display: none; }
#app.mode-editor #preview-panel { display: none; }
#app.mode-preview #editor-panel {
display: none;
}
#app.mode-editor #preview-panel {
display: none;
}
/* Toast */
#toast-notification {
@@ -907,7 +948,9 @@ html, body {
box-shadow: var(--shadow-lg);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
transition:
opacity 0.3s ease,
transform 0.3s ease;
z-index: 9999;
max-width: 480px;
text-align: center;
@@ -920,15 +963,31 @@ html, body {
}
/* Scrollbar */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* About Dialog */
@@ -1094,9 +1153,15 @@ html, body {
margin: 0;
}
.confirm-header.confirm-danger h3 { color: #d93025; }
.confirm-header.confirm-warning h3 { color: #e37400; }
:root.dark .confirm-header.confirm-warning h3 { color: #fdd663; }
.confirm-header.confirm-danger h3 {
color: #d93025;
}
.confirm-header.confirm-warning h3 {
color: #e37400;
}
:root.dark .confirm-header.confirm-warning h3 {
color: #fdd663;
}
.confirm-body {
padding: 12px 24px 20px;
@@ -1212,8 +1277,12 @@ html, body {
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* Status bar loading indicator */
@@ -1365,9 +1434,9 @@ html, body {
/* Focus visible 为所有交互元素提供清晰的焦点指示 */
button:focus-visible,
[role="treeitem"]:focus-visible,
[role="tab"]:focus-visible,
[role="menuitem"]:focus-visible {
[role='treeitem']:focus-visible,
[role='tab']:focus-visible,
[role='menuitem']:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
@@ -1396,3 +1465,138 @@ button:focus-visible,
white-space: nowrap;
border: 0;
}
/* ===== Search & Replace Panel ===== */
.search-replace-panel {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 12px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
animation: slideDown 0.15s ease-out;
}
@keyframes slideDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.search-row {
display: flex;
align-items: center;
gap: 4px;
}
.search-input-group {
flex: 1;
display: flex;
align-items: center;
position: relative;
min-width: 0;
}
.search-input {
flex: 1;
height: 28px;
padding: 0 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
font-size: 12px;
font-family: var(--font-ui);
outline: none;
transition: border-color 0.15s;
}
.search-input:focus {
border-color: var(--primary);
}
.search-input::placeholder {
color: var(--text-tertiary);
}
.search-count {
position: absolute;
right: 8px;
font-size: 11px;
color: var(--text-tertiary);
pointer-events: none;
white-space: nowrap;
}
.search-btn {
display: flex;
align-items: center;
justify-content: center;
min-width: 28px;
height: 28px;
padding: 0 6px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 12px;
font-family: var(--font-ui);
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.search-btn:hover {
background: var(--bg-tertiary);
color: var(--text);
}
.search-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.search-btn.active {
background: var(--primary-light);
color: var(--primary);
}
.search-close:hover {
background: #e74c3c20;
color: #e74c3c;
}
.replace-btn {
font-size: 11px;
padding: 0 8px;
}
/* Search match highlights */
mark.search-match-highlight {
background: rgba(255, 213, 0, 0.4);
color: inherit;
border-radius: 2px;
padding: 0;
}
mark.search-match-active {
background: rgba(255, 150, 0, 0.6);
outline: 1px solid rgba(255, 150, 0, 0.8);
border-radius: 2px;
}
:root.dark mark.search-match-highlight {
background: rgba(255, 213, 0, 0.25);
}
:root.dark mark.search-match-active {
background: rgba(255, 180, 0, 0.4);
outline-color: rgba(255, 180, 0, 0.6);
}