Skip to content

Commit daa434d

Browse files
committed
新增自动换行功能
1 parent b5130c6 commit daa434d

3 files changed

Lines changed: 169 additions & 72 deletions

File tree

IMGNoteClient/imgnote-client/src/renderer/src/assets/base.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
--danger-hover-bg: rgba(220, 38, 38, 0.1);
3131
--warning: #d97706;
3232

33-
--overlay-panel: rgba(255, 255, 255, 0.88);
33+
--overlay-panel: rgba(255, 255, 255, 0.95);
3434
--overlay-panel-border: rgba(0, 0, 0, 0.06);
3535
}
3636

@@ -66,7 +66,7 @@
6666
--danger-hover-bg: rgba(248, 113, 113, 0.15);
6767
--warning: #fbbf24;
6868

69-
--overlay-panel: rgba(26, 26, 30, 0.92);
69+
--overlay-panel: rgba(26, 26, 30, 0.95);
7070
--overlay-panel-border: rgba(255, 255, 255, 0.1);
7171
}
7272

IMGNoteClient/imgnote-client/src/renderer/src/components/ImageViewer.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,9 @@ onUnmounted(() => {
353353
color: var(--text-tertiary);
354354
font-size: 12px;
355355
backdrop-filter: blur(10px);
356-
transition: background 0.25s ease, border-color 0.25s ease;
356+
transition:
357+
background 0.25s ease,
358+
border-color 0.25s ease;
357359
}
358360
359361
.viewer-info {
@@ -367,7 +369,9 @@ onUnmounted(() => {
367369
letter-spacing: 0.02em;
368370
backdrop-filter: blur(10px);
369371
box-shadow: var(--shadow-card);
370-
transition: background 0.25s ease, border-color 0.25s ease;
372+
transition:
373+
background 0.25s ease,
374+
border-color 0.25s ease;
371375
}
372376
.btn {
373377
user-select: none;

IMGNoteClient/imgnote-client/src/renderer/src/views/Editor.vue

Lines changed: 161 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,39 @@
66
<span class="back-arrow">←</span>
77
</button>
88
<h1 class="editor-title">{{ title || '笔记本' }}</h1>
9-
<button
10-
type="button"
11-
class="search-btn"
12-
aria-label="搜索"
13-
title="搜索 (Ctrl+F)"
14-
@click="openSearch"
15-
>
16-
<svg
17-
class="search-icon-svg"
18-
viewBox="0 0 24 24"
19-
fill="none"
20-
stroke="currentColor"
21-
stroke-width="2"
22-
stroke-linecap="round"
9+
<CustomTooltip content="搜索 (Ctrl+F)">
10+
<button
11+
type="button"
12+
class="search-btn"
13+
aria-label="搜索"
14+
@click="openSearch"
2315
>
24-
<circle cx="11" cy="11" r="8" />
25-
<path d="m21 21-4.35-4.35" />
26-
</svg>
27-
</button>
16+
<svg
17+
class="search-icon-svg"
18+
viewBox="0 0 24 24"
19+
fill="none"
20+
stroke="currentColor"
21+
stroke-width="2"
22+
stroke-linecap="round"
23+
>
24+
<circle cx="11" cy="11" r="8" />
25+
<path d="m21 21-4.35-4.35" />
26+
</svg>
27+
</button>
28+
</CustomTooltip>
29+
<CustomTooltip :content="wrapEnabled ? '自动换行:已开启' : '自动换行:已关闭'">
30+
<button
31+
type="button"
32+
class="wrap-toggle-btn"
33+
:class="{ 'is-on': wrapEnabled }"
34+
@click="toggleWrap"
35+
>
36+
<span class="wrap-toggle-pill">
37+
<span class="wrap-toggle-dot"></span>
38+
<span class="wrap-toggle-text">{{ wrapEnabled ? '自动换行' : '单行' }}</span>
39+
</span>
40+
</button>
41+
</CustomTooltip>
2842
<button
2943
type="button"
3044
class="enc-btn"
@@ -38,25 +52,28 @@
3852
>
3953
{{ isEncrypted ? '加密设置' : '加密' }}
4054
</button>
55+
<CustomTooltip content="保存 (Ctrl+S)">
56+
<button
57+
type="button"
58+
class="btn btn-primary save-btn"
59+
:disabled="needsPassword || !dirty || saving"
60+
@click="save"
61+
>
62+
{{ saving ? '保存中…' : '保存' }}
63+
</button>
64+
</CustomTooltip>
65+
</header>
66+
<!-- 顶部栏显示模式切换:始终显示 / 自动 / 始终隐藏 -->
67+
<CustomTooltip :content="`工具栏显示:${barsModeLabel}`" class="bars-mode-tooltip">
4168
<button
4269
type="button"
43-
class="btn btn-primary save-btn"
44-
:disabled="needsPassword || !dirty || saving"
45-
@click="save"
70+
class="bars-mode-toggle"
71+
:class="`mode-${barsMode}`"
72+
@click="cycleBarsMode"
4673
>
47-
{{ saving ? '保存中…' : '保存' }}
74+
<span class="bars-mode-shape" :class="`shape-${barsMode}`"></span>
4875
</button>
49-
</header>
50-
<!-- 顶部栏显示模式切换:始终显示 / 自动 / 始终隐藏 -->
51-
<button
52-
type="button"
53-
class="bars-mode-toggle"
54-
:class="`mode-${barsMode}`"
55-
:title="`工具栏显示:${barsModeLabel}`"
56-
@click="cycleBarsMode"
57-
>
58-
<span class="bars-mode-shape" :class="`shape-${barsMode}`"></span>
59-
</button>
76+
</CustomTooltip>
6077
</div>
6178
<!-- 搜索栏:Ctrl+F 唤起 -->
6279
<Transition name="search-slide">
@@ -94,39 +111,43 @@
94111
: '无匹配'
95112
}}
96113
</span>
97-
<button
98-
type="button"
99-
class="editor-search-btn"
100-
title="上一个"
101-
:disabled="!searchQuery || searchMatches.length === 0"
102-
@click="searchPrev"
103-
>
104-
<span class="editor-search-btn-icon">▲</span>
105-
</button>
106-
<button
107-
type="button"
108-
class="editor-search-btn"
109-
title="下一个"
110-
:disabled="!searchQuery || searchMatches.length === 0"
111-
@click="searchNext"
112-
>
113-
<span class="editor-search-btn-icon">▼</span>
114-
</button>
115-
<button
116-
type="button"
117-
class="editor-search-close"
118-
title="关闭 (Esc)"
119-
@click="closeSearch"
120-
>
121-
122-
</button>
114+
<CustomTooltip content="上一个">
115+
<button
116+
type="button"
117+
class="editor-search-btn"
118+
:disabled="!searchQuery || searchMatches.length === 0"
119+
@click="searchPrev"
120+
>
121+
<span class="editor-search-btn-icon">▲</span>
122+
</button>
123+
</CustomTooltip>
124+
<CustomTooltip content="下一个">
125+
<button
126+
type="button"
127+
class="editor-search-btn"
128+
:disabled="!searchQuery || searchMatches.length === 0"
129+
@click="searchNext"
130+
>
131+
<span class="editor-search-btn-icon">▼</span>
132+
</button>
133+
</CustomTooltip>
134+
<CustomTooltip content="关闭 (Esc)">
135+
<button
136+
type="button"
137+
class="editor-search-close"
138+
@click="closeSearch"
139+
>
140+
141+
</button>
142+
</CustomTooltip>
123143
</div>
124144
</div>
125145
</div>
126146
</Transition>
127147
<div class="editor-body">
128-
<div class="editor-with-lines">
148+
<div class="editor-with-lines" :class="{ 'is-wrap': wrapEnabled }">
129149
<div
150+
v-if="!wrapEnabled"
130151
ref="linesRef"
131152
class="editor-lines"
132153
aria-hidden="true"
@@ -171,7 +192,8 @@
171192
:style="{
172193
fontSize: fontSize + 'px',
173194
lineHeight: lineHeight + 'em',
174-
whiteSpace: 'nowrap'
195+
whiteSpace: wrapEnabled ? 'pre-wrap' : 'nowrap',
196+
overflowX: wrapEnabled ? 'hidden' : 'auto'
175197
}"
176198
@input="onContentInput"
177199
@keydown="onKeydown"
@@ -187,8 +209,8 @@
187209
@blur="onEditorBlur"
188210
/>
189211
<footer class="editor-footer" :class="{ 'is-visible': barsVisible }" aria-live="polite">
190-
<div class="editor-footer-left">
191-
<span class="editor-pos">{{ currentLine }} 行 · {{ currentCol }} 列</span>
212+
<div v-if="!wrapEnabled" class="editor-footer-left">
213+
<span class="editor-pos"> {{ currentLine }} 行 · {{ currentCol }} 列 </span>
192214
</div>
193215
<template v-if="maxBytes != null">
194216
<div class="editor-footer-center">
@@ -260,6 +282,7 @@ import ConfirmDialog from '@/components/ConfirmDialog.vue'
260282
import PasswordDialog from '@/components/PasswordDialog.vue'
261283
import EncryptionDialog from '@/components/EncryptionDialog.vue'
262284
import LoadingOverlay from '@/components/LoadingOverlay.vue'
285+
import CustomTooltip from '@/components/CustomTooltip.vue'
263286
import md5 from 'js-md5'
264287
265288
const route = useRoute()
@@ -287,6 +310,7 @@ const showEncryptDialog = ref(false)
287310
const encryptError = ref('')
288311
const fontSize = ref(15)
289312
const lineHeight = ref(1.6)
313+
const wrapEnabled = ref(false)
290314
291315
// 顶部栏/底部栏显示模式:
292316
// - always:始终显示
@@ -718,7 +742,6 @@ function scrollToMatch(options = {}) {
718742
el.scrollLeft = targetScrollLeft
719743
}
720744
})
721-
722745
}
723746
724747
function searchNext() {
@@ -1128,6 +1151,16 @@ function handleWheel(e) {
11281151
}
11291152
}
11301153
1154+
function toggleWrap() {
1155+
wrapEnabled.value = !wrapEnabled.value
1156+
nextTick(() => {
1157+
if (linesRef.value && textareaRef.value) {
1158+
linesRef.value.scrollTop = textareaRef.value.scrollTop
1159+
updateCurrentLine()
1160+
}
1161+
})
1162+
}
1163+
11311164
onMounted(() => {
11321165
load()
11331166
document.addEventListener('selectionchange', onSelectionChange)
@@ -1228,10 +1261,16 @@ watch([fontSize, lineHeight], () => {
12281261
}
12291262
12301263
.editor-header-wrap.is-hidden {
1231-
/* 保留一条细条用于承载模式图标,避免图标只露出一半 */
12321264
height: 7px;
12331265
}
12341266
1267+
/* 顶部栏模式按钮的 Tooltip:让容器参与绝对定位,以便 tooltip 定位正确 */
1268+
.bars-mode-tooltip {
1269+
position: absolute;
1270+
right: 80px;
1271+
bottom: 0;
1272+
}
1273+
12351274
.editor-header {
12361275
user-select: none;
12371276
display: flex;
@@ -1553,7 +1592,7 @@ watch([fontSize, lineHeight], () => {
15531592
display: flex;
15541593
align-items: center;
15551594
gap: 16px;
1556-
padding: 8px 14px 8px 16px;
1595+
padding: 8px 16px;
15571596
border-radius: 10px;
15581597
background: var(--overlay-panel);
15591598
box-shadow: var(--shadow-card);
@@ -1566,7 +1605,10 @@ watch([fontSize, lineHeight], () => {
15661605
line-height: 1.4;
15671606
font-variant-numeric: tabular-nums;
15681607
transform: translateY(150%);
1569-
transition: transform 0.25s ease;
1608+
}
1609+
1610+
.editor-with-lines.is-wrap .editor-footer {
1611+
bottom: 2px;
15701612
}
15711613
15721614
.editor-footer.is-visible {
@@ -1700,10 +1742,61 @@ watch([fontSize, lineHeight], () => {
17001742
cursor: not-allowed;
17011743
}
17021744
1745+
/* 自动换行切换胶囊按钮 */
1746+
.wrap-toggle-btn {
1747+
flex-shrink: 0;
1748+
border: none;
1749+
background: transparent;
1750+
padding: 0;
1751+
margin-left: 6px;
1752+
cursor: pointer;
1753+
}
1754+
1755+
.wrap-toggle-pill {
1756+
display: inline-flex;
1757+
align-items: center;
1758+
gap: 6px;
1759+
padding: 6px 10px;
1760+
border-radius: 999px;
1761+
border: 1px solid var(--border);
1762+
background: var(--bg-card);
1763+
font-size: 12px;
1764+
color: var(--text-secondary);
1765+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
1766+
transition:
1767+
background 0.15s,
1768+
border-color 0.15s,
1769+
box-shadow 0.15s,
1770+
color 0.15s;
1771+
}
1772+
1773+
.wrap-toggle-btn.is-on .wrap-toggle-pill {
1774+
border-color: var(--accent-subtle);
1775+
background: var(--accent-subtle);
1776+
color: var(--accent);
1777+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
1778+
}
1779+
1780+
.wrap-toggle-dot {
1781+
width: 8px;
1782+
height: 8px;
1783+
border-radius: 999px;
1784+
background: var(--accent);
1785+
flex-shrink: 0;
1786+
}
1787+
1788+
.wrap-toggle-btn.is-on .wrap-toggle-dot {
1789+
background: var(--accent);
1790+
}
1791+
1792+
.wrap-toggle-text {
1793+
white-space: nowrap;
1794+
}
1795+
17031796
/* 顶部栏模式切换:顶部栏右下角小图标(下尖角 / 圆圈 / 上尖角)*/
17041797
.bars-mode-toggle {
17051798
position: absolute;
1706-
right: 24px;
1799+
right: -60px;
17071800
bottom: -10px;
17081801
width: 22px;
17091802
height: 26px;

0 commit comments

Comments
 (0)