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"
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">
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"
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"
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'
260282import PasswordDialog from ' @/components/PasswordDialog.vue'
261283import EncryptionDialog from ' @/components/EncryptionDialog.vue'
262284import LoadingOverlay from ' @/components/LoadingOverlay.vue'
285+ import CustomTooltip from ' @/components/CustomTooltip.vue'
263286import md5 from ' js-md5'
264287
265288const route = useRoute ()
@@ -287,6 +310,7 @@ const showEncryptDialog = ref(false)
287310const encryptError = ref (' ' )
288311const fontSize = ref (15 )
289312const 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
724747function 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+
11311164onMounted (() => {
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