@tnotesjs/core 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +87 -87
  2. package/dist/{chunk-FVKQPBPP.js → chunk-4MO7F7Q4.js} +2005 -1769
  3. package/dist/cli/index.js +1 -1
  4. package/dist/vitepress/config/index.js +212 -4
  5. package/package.json +1 -1
  6. package/types/config.ts +61 -61
  7. package/types/index.ts +11 -11
  8. package/types/note.ts +33 -33
  9. package/vitepress/assets/icons/icon__check.svg +3 -3
  10. package/vitepress/assets/icons/icon__clipboard.svg +7 -7
  11. package/vitepress/assets/icons/icon__close.svg +1 -1
  12. package/vitepress/assets/icons/icon__collapse.svg +1 -1
  13. package/vitepress/assets/icons/icon__confirm.svg +1 -1
  14. package/vitepress/assets/icons/icon__copy.svg +4 -4
  15. package/vitepress/assets/icons/icon__focus.svg +1 -1
  16. package/vitepress/assets/icons/icon__fold.svg +3 -3
  17. package/vitepress/assets/icons/icon__folder.svg +1 -1
  18. package/vitepress/assets/icons/icon__fullscreen.svg +1 -1
  19. package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -1
  20. package/vitepress/assets/icons/icon__github.svg +3 -3
  21. package/vitepress/assets/icons/icon__mindmap.svg +1 -1
  22. package/vitepress/assets/icons/icon__next.svg +1 -1
  23. package/vitepress/assets/icons/icon__number_gray.svg +1 -1
  24. package/vitepress/assets/icons/icon__number_purple.svg +1 -1
  25. package/vitepress/assets/icons/icon__prev.svg +1 -1
  26. package/vitepress/assets/icons/icon__restore.svg +1 -1
  27. package/vitepress/assets/icons/icon__rotate.svg +4 -4
  28. package/vitepress/assets/icons/icon__search.svg +1 -1
  29. package/vitepress/assets/icons/icon__setting.svg +1 -0
  30. package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -1
  31. package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -1
  32. package/vitepress/assets/icons/icon__totop.svg +5 -5
  33. package/vitepress/assets/icons/icon__vscode.svg +5 -5
  34. package/vitepress/assets/icons/icon__zoom_fit.svg +1 -1
  35. package/vitepress/assets/icons/icon__zoom_in.svg +1 -1
  36. package/vitepress/assets/icons/icon__zoom_out.svg +1 -1
  37. package/vitepress/assets/icons/icon__zoom_reset.svg +1 -1
  38. package/vitepress/assets/icons/index.ts +39 -38
  39. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -20
  40. package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -373
  41. package/vitepress/components/CodeBlockFullscreen/index.ts +115 -115
  42. package/vitepress/components/CodeBlockFullscreen/styles.css +64 -64
  43. package/vitepress/components/Discussions/Discussions.module.scss +32 -32
  44. package/vitepress/components/Discussions/Discussions.vue +211 -211
  45. package/vitepress/components/EnWordList/EnWordList.module.scss +124 -124
  46. package/vitepress/components/EnWordList/EnWordList.vue +543 -543
  47. package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -22
  48. package/vitepress/components/EnWordList/RightClickMenu.vue +66 -66
  49. package/vitepress/components/Footprints/Footprints.module.scss +93 -93
  50. package/vitepress/components/Footprints/Footprints.vue +377 -377
  51. package/vitepress/components/Layout/AboutModal.module.scss +233 -233
  52. package/vitepress/components/Layout/AboutModal.vue +105 -105
  53. package/vitepress/components/Layout/AboutPanel.vue +270 -270
  54. package/vitepress/components/Layout/ContentCollapse.vue +603 -603
  55. package/vitepress/components/Layout/CustomSidebar.vue +878 -608
  56. package/vitepress/components/Layout/DocBeforeControls.vue +149 -149
  57. package/vitepress/components/Layout/DocFooter.vue +233 -233
  58. package/vitepress/components/Layout/ImagePreview.module.scss +201 -201
  59. package/vitepress/components/Layout/ImagePreview.vue +281 -281
  60. package/vitepress/components/Layout/Layout.module.scss +661 -661
  61. package/vitepress/components/Layout/Layout.vue +618 -560
  62. package/vitepress/components/Layout/NavBarSettingsTrigger.vue +59 -0
  63. package/vitepress/components/Layout/NoteStatus.vue +140 -140
  64. package/vitepress/components/Layout/SidebarItems.vue +1083 -268
  65. package/vitepress/components/Layout/SidebarNavBefore.vue +171 -114
  66. package/vitepress/components/Layout/Swiper.vue +167 -167
  67. package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -11
  68. package/vitepress/components/Layout/ToggleFullContent.vue +35 -35
  69. package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -11
  70. package/vitepress/components/Layout/ToggleSidebar.vue +36 -36
  71. package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -88
  72. package/vitepress/components/Layout/composables/useNoteConfig.ts +119 -119
  73. package/vitepress/components/Layout/composables/useNoteSave.ts +178 -178
  74. package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -85
  75. package/vitepress/components/Layout/composables/useRedirect.ts +113 -113
  76. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -85
  77. package/vitepress/components/Layout/homeReadme.data.ts +124 -124
  78. package/vitepress/components/MarkMap/MarkMap.module.scss +159 -159
  79. package/vitepress/components/MarkMap/MarkMap.vue +405 -405
  80. package/vitepress/components/Mermaid/Mermaid.module.scss +275 -275
  81. package/vitepress/components/Mermaid/Mermaid.vue +365 -365
  82. package/vitepress/components/NotesTable/NotesTable.module.scss +77 -77
  83. package/vitepress/components/NotesTable/NotesTable.vue +98 -98
  84. package/vitepress/components/NotesTable/README.md +67 -67
  85. package/vitepress/components/Settings/Settings.module.scss +375 -433
  86. package/vitepress/components/Settings/Settings.vue +376 -306
  87. package/vitepress/components/SidebarCard/MindMapView.vue +484 -484
  88. package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -108
  89. package/vitepress/components/SidebarCard/SidebarCard.vue +946 -946
  90. package/vitepress/components/Tooltip/Tooltip.vue +70 -70
  91. package/vitepress/components/constants.ts +109 -91
  92. package/vitepress/components/notesConfig.data.ts +73 -73
  93. package/vitepress/components/sidebar.data.ts +59 -59
  94. package/vitepress/components/tnotes-config.data.ts +21 -21
  95. package/vitepress/components/utils.ts +26 -26
  96. package/vitepress/config/index.ts +169 -167
  97. package/vitepress/configs/constants.ts +26 -26
  98. package/vitepress/configs/head.config.ts +25 -25
  99. package/vitepress/configs/index.ts +9 -9
  100. package/vitepress/configs/markdown-it.d.ts +23 -23
  101. package/vitepress/configs/markdown.config.ts +366 -366
  102. package/vitepress/configs/theme.config.ts +108 -108
  103. package/vitepress/plugins/buildProgressPlugin.ts +434 -434
  104. package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -107
  105. package/vitepress/plugins/renameNotePlugin.ts +68 -68
  106. package/vitepress/plugins/sidebarStructurePlugin.ts +260 -0
  107. package/vitepress/plugins/updateConfigPlugin.ts +63 -63
  108. package/vitepress/theme/index.ts +137 -137
  109. package/vitepress/theme/styles/base.scss +50 -50
  110. package/vitepress/theme/styles/components/404.scss +31 -31
  111. package/vitepress/theme/styles/components/collapse.scss +175 -175
  112. package/vitepress/theme/styles/components/markmap.scss +101 -101
  113. package/vitepress/theme/styles/components/swiper.scss +255 -255
  114. package/vitepress/theme/styles/index.scss +25 -25
  115. package/vitepress/theme/styles/layout.scss +70 -62
  116. package/vitepress/theme/styles/utilities.scss +39 -39
  117. package/vitepress/theme/styles/vitepress-override.scss +25 -25
@@ -1,374 +1,374 @@
1
- <template>
2
- <Teleport to="body">
3
- <Transition name="fullscreen-fade">
4
- <div
5
- v-if="isFullscreen"
6
- class="code-fullscreen-overlay"
7
- :class="{ 'is-landscape': isLandscape }"
8
- @click.self="closeFullscreen"
9
- >
10
- <div class="code-fullscreen-container">
11
- <!-- 工具栏 -->
12
- <div class="code-fullscreen-toolbar">
13
- <div class="code-fullscreen-title">
14
- <span v-if="language" class="language-tag">{{ language }}</span>
15
- <span v-if="filename" class="filename">{{ filename }}</span>
16
- </div>
17
- <div class="code-fullscreen-actions">
18
- <!-- 旋转按钮(仅移动端) -->
19
- <button
20
- class="action-btn rotate-btn mobile-only"
21
- @click="toggleOrientation"
22
- :title="isLandscape ? '切换为竖屏' : '切换为横屏'"
23
- >
24
- <img :src="icon__rotate" alt="旋转" />
25
- </button>
26
- <!-- 复制按钮 -->
27
- <button
28
- class="action-btn copy-btn"
29
- @click="copyCode"
30
- :title="copied ? '已复制' : '复制代码'"
31
- >
32
- <img v-if="!copied" :src="icon__copy" alt="复制" />
33
- <img v-else :src="icon__check" alt="已复制" />
34
- </button>
35
- <!-- 关闭按钮 -->
36
- <button
37
- class="action-btn close-btn"
38
- @click="closeFullscreen"
39
- title="退出全屏 (ESC)"
40
- >
41
- <img :src="icon__close" alt="关闭" />
42
- </button>
43
- </div>
44
- </div>
45
-
46
- <!-- 代码内容 -->
47
- <div class="code-fullscreen-content" ref="contentRef">
48
- <div v-html="codeHtml"></div>
49
- </div>
50
- </div>
51
- </div>
52
- </Transition>
53
- </Teleport>
54
- </template>
55
-
56
- <script setup lang="ts">
1
+ <template>
2
+ <Teleport to="body">
3
+ <Transition name="fullscreen-fade">
4
+ <div
5
+ v-if="isFullscreen"
6
+ class="code-fullscreen-overlay"
7
+ :class="{ 'is-landscape': isLandscape }"
8
+ @click.self="closeFullscreen"
9
+ >
10
+ <div class="code-fullscreen-container">
11
+ <!-- 工具栏 -->
12
+ <div class="code-fullscreen-toolbar">
13
+ <div class="code-fullscreen-title">
14
+ <span v-if="language" class="language-tag">{{ language }}</span>
15
+ <span v-if="filename" class="filename">{{ filename }}</span>
16
+ </div>
17
+ <div class="code-fullscreen-actions">
18
+ <!-- 旋转按钮(仅移动端) -->
19
+ <button
20
+ class="action-btn rotate-btn mobile-only"
21
+ @click="toggleOrientation"
22
+ :title="isLandscape ? '切换为竖屏' : '切换为横屏'"
23
+ >
24
+ <img :src="icon__rotate" alt="旋转" />
25
+ </button>
26
+ <!-- 复制按钮 -->
27
+ <button
28
+ class="action-btn copy-btn"
29
+ @click="copyCode"
30
+ :title="copied ? '已复制' : '复制代码'"
31
+ >
32
+ <img v-if="!copied" :src="icon__copy" alt="复制" />
33
+ <img v-else :src="icon__check" alt="已复制" />
34
+ </button>
35
+ <!-- 关闭按钮 -->
36
+ <button
37
+ class="action-btn close-btn"
38
+ @click="closeFullscreen"
39
+ title="退出全屏 (ESC)"
40
+ >
41
+ <img :src="icon__close" alt="关闭" />
42
+ </button>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- 代码内容 -->
47
+ <div class="code-fullscreen-content" ref="contentRef">
48
+ <div v-html="codeHtml"></div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </Transition>
53
+ </Teleport>
54
+ </template>
55
+
56
+ <script setup lang="ts">
57
57
  import { ref, watch, onMounted, onUnmounted } from 'vue'
58
-
59
- import { icon__copy, icon__check, icon__close, icon__rotate } from '../../assets/icons'
60
-
61
- const ORIENTATION_KEY = 'code-fullscreen-orientation'
62
-
63
- interface Props {
64
- isFullscreen: boolean
65
- codeHtml: string
66
- language?: string
67
- filename?: string
68
- }
69
-
70
- const props = defineProps<Props>()
71
- const emit = defineEmits<{
72
- 'update:isFullscreen': [value: boolean]
73
- }>()
74
-
75
- const contentRef = ref<HTMLElement | null>(null)
76
- const copied = ref(false)
77
- const isLandscape = ref(false)
78
-
79
- // 检测是否为移动端
80
- function isMobile() {
81
- return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
82
- navigator.userAgent
83
- )
84
- }
85
-
86
- // 切换横竖屏
87
- function toggleOrientation() {
88
- isLandscape.value = !isLandscape.value
89
- // 保存用户偏好
90
- if (typeof window !== 'undefined') {
91
- localStorage.setItem(
92
- ORIENTATION_KEY,
93
- isLandscape.value ? 'landscape' : 'portrait'
94
- )
95
- }
96
- }
97
-
98
- function closeFullscreen() {
99
- emit('update:isFullscreen', false)
100
- }
101
-
102
- function copyCode() {
103
- if (!contentRef.value) return
104
-
105
- const codeElement = contentRef.value.querySelector('pre code')
106
- if (!codeElement) return
107
-
108
- const text = codeElement.textContent || ''
109
- navigator.clipboard.writeText(text).then(() => {
110
- copied.value = true
111
- setTimeout(() => {
112
- copied.value = false
113
- }, 2000)
114
- })
115
- }
116
-
117
- function handleKeydown(e: KeyboardEvent) {
118
- if (e.key === 'Escape' && props.isFullscreen) {
119
- closeFullscreen()
120
- }
121
- }
122
-
123
- watch(
124
- () => props.isFullscreen,
125
- (newVal) => {
126
- if (newVal) {
127
- document.body.style.overflow = 'hidden'
128
- // 读取用户上次选择的方向
129
- if (typeof window !== 'undefined') {
130
- const savedOrientation = localStorage.getItem(ORIENTATION_KEY)
131
- // 如果有保存的偏好,使用保存的值;否则默认为竖屏
132
- if (savedOrientation) {
133
- isLandscape.value = savedOrientation === 'landscape'
134
- } else {
135
- isLandscape.value = false
136
- }
137
- }
138
- } else {
139
- document.body.style.overflow = ''
140
- }
141
- }
142
- )
143
-
144
- onMounted(() => {
145
- document.addEventListener('keydown', handleKeydown)
146
- })
147
-
148
- onUnmounted(() => {
149
- document.removeEventListener('keydown', handleKeydown)
150
- document.body.style.overflow = ''
151
- })
152
- </script>
153
-
154
- <style scoped>
155
- .code-fullscreen-overlay {
156
- position: fixed;
157
- top: 0;
158
- left: 0;
159
- right: 0;
160
- bottom: 0;
161
- width: 100vw;
162
- height: 100vh;
163
- background-color: rgba(0, 0, 0, 0.9);
164
- z-index: 9999;
165
- display: flex;
166
- align-items: center;
167
- justify-content: center;
168
- }
169
-
170
- .code-fullscreen-container {
171
- width: 100%;
172
- height: 100%;
173
- background-color: var(--vp-c-bg);
174
- display: flex;
175
- flex-direction: column;
176
- overflow: hidden;
177
- transition: transform 0.3s ease;
178
- }
179
-
180
- /* 横屏模式 */
181
- .code-fullscreen-overlay.is-landscape {
182
- overflow: hidden;
183
- }
184
-
185
- .code-fullscreen-overlay.is-landscape .code-fullscreen-container {
186
- position: absolute;
187
- top: 0;
188
- left: 0;
189
- right: 0;
190
- bottom: 0;
191
- /* 关键:宽度取视口高度,高度取视口宽度 */
192
- /* 这样旋转90度后,宽度(原高度100vh)会填满屏幕宽度 */
193
- width: 100vh;
194
- height: 100vw;
195
- /* 以左上角为中心旋转,然后平移到正确位置 */
196
- transform-origin: top left;
197
- transform: rotate(90deg) translateY(-100%);
198
- max-width: none;
199
- max-height: none;
200
- }
201
-
202
- .code-fullscreen-toolbar {
203
- display: flex;
204
- align-items: center;
205
- justify-content: space-between;
206
- padding: 12px 20px;
207
- background-color: var(--vp-c-bg-soft);
208
- border-bottom: 1px solid var(--vp-c-divider);
209
- flex-shrink: 0;
210
- }
211
-
212
- .code-fullscreen-title {
213
- display: flex;
214
- align-items: center;
215
- gap: 12px;
216
- font-size: 14px;
217
- }
218
-
219
- .language-tag {
220
- display: inline-block;
221
- padding: 4px 8px;
222
- background-color: var(--vp-c-brand-soft);
223
- color: var(--vp-c-brand-1);
224
- border-radius: 4px;
225
- font-family: var(--vp-font-family-mono);
226
- font-size: 12px;
227
- font-weight: 600;
228
- text-transform: uppercase;
229
- }
230
-
231
- .filename {
232
- color: var(--vp-c-text-2);
233
- font-family: var(--vp-font-family-mono);
234
- font-size: 13px;
235
- }
236
-
237
- .code-fullscreen-actions {
238
- display: flex;
239
- gap: 8px;
240
- }
241
-
242
- .action-btn {
243
- display: flex;
244
- align-items: center;
245
- justify-content: center;
246
- width: 36px;
247
- height: 36px;
248
- border: none;
249
- background-color: transparent;
250
- color: var(--vp-c-text-2);
251
- border-radius: 6px;
252
- cursor: pointer;
253
- transition: all 0.2s;
254
- }
255
-
256
- .action-btn img {
257
- width: 16px;
258
- height: 16px;
259
- filter: var(--vp-icon-filter, none);
260
- }
261
-
262
- .action-btn:hover {
263
- background-color: var(--vp-c-default-soft);
264
- color: var(--vp-c-text-1);
265
- }
266
-
267
- .close-btn img {
268
- width: 20px;
269
- height: 20px;
270
- }
271
-
272
- /* 旋转按钮仅移动端显示 */
273
- .mobile-only {
274
- display: none;
275
- }
276
-
277
- .code-fullscreen-content {
278
- flex: 1;
279
- overflow: auto;
280
- padding: 20px;
281
- background-color: var(--vp-code-block-bg);
282
- }
283
-
284
- .code-fullscreen-content :deep(pre) {
285
- margin: 0 !important;
286
- background-color: transparent !important;
287
- padding: 0 !important;
288
- }
289
-
290
- .code-fullscreen-content :deep(code) {
291
- font-size: 14px !important;
292
- line-height: 1.7 !important;
293
- }
294
-
295
- /* 移动端优化 */
296
- @media (max-width: 768px) {
297
- .mobile-only {
298
- display: flex;
299
- }
300
-
301
- .code-fullscreen-overlay {
302
- padding: 0;
303
- }
304
-
305
- .code-fullscreen-container {
306
- max-width: 100%;
307
- max-height: 100vh;
308
- border-radius: 0;
309
- }
310
-
311
- .code-fullscreen-toolbar {
312
- padding: 10px 16px;
313
- }
314
-
315
- .code-fullscreen-title {
316
- font-size: 13px;
317
- }
318
-
319
- .language-tag {
320
- font-size: 11px;
321
- padding: 3px 6px;
322
- }
323
-
324
- .filename {
325
- font-size: 12px;
326
- overflow: hidden;
327
- text-overflow: ellipsis;
328
- white-space: nowrap;
329
- max-width: 120px;
330
- }
331
-
332
- .code-fullscreen-content {
333
- padding: 16px;
334
- }
335
-
336
- .code-fullscreen-content :deep(code) {
337
- font-size: 12px !important;
338
- }
339
-
340
- /* 横屏模式下的字体调整 */
341
- .code-fullscreen-overlay.is-landscape .code-fullscreen-content :deep(code) {
342
- font-size: 14px !important;
343
- }
344
- }
345
-
346
- /* 过渡动画 */
347
- .fullscreen-fade-enter-active,
348
- .fullscreen-fade-leave-active {
349
- transition: opacity 0.3s ease;
350
- }
351
-
352
- .fullscreen-fade-enter-from,
353
- .fullscreen-fade-leave-to {
354
- opacity: 0;
355
- }
356
-
357
- .fullscreen-fade-enter-active .code-fullscreen-container,
358
- .fullscreen-fade-leave-active .code-fullscreen-container {
359
- transition: transform 0.3s ease;
360
- }
361
-
362
- .fullscreen-fade-enter-from .code-fullscreen-container {
363
- transform: scale(0.95);
364
- }
365
-
366
- .fullscreen-fade-leave-to .code-fullscreen-container {
367
- transform: scale(0.95);
368
- }
369
-
370
- /* 暗色模式图标适配 */
371
- .dark .action-btn img {
372
- filter: invert(1);
373
- }
374
- </style>
58
+
59
+ import { icon__copy, icon__check, icon__close, icon__rotate } from '../../assets/icons'
60
+
61
+ const ORIENTATION_KEY = 'code-fullscreen-orientation'
62
+
63
+ interface Props {
64
+ isFullscreen: boolean
65
+ codeHtml: string
66
+ language?: string
67
+ filename?: string
68
+ }
69
+
70
+ const props = defineProps<Props>()
71
+ const emit = defineEmits<{
72
+ 'update:isFullscreen': [value: boolean]
73
+ }>()
74
+
75
+ const contentRef = ref<HTMLElement | null>(null)
76
+ const copied = ref(false)
77
+ const isLandscape = ref(false)
78
+
79
+ // 检测是否为移动端
80
+ function isMobile() {
81
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
82
+ navigator.userAgent
83
+ )
84
+ }
85
+
86
+ // 切换横竖屏
87
+ function toggleOrientation() {
88
+ isLandscape.value = !isLandscape.value
89
+ // 保存用户偏好
90
+ if (typeof window !== 'undefined') {
91
+ localStorage.setItem(
92
+ ORIENTATION_KEY,
93
+ isLandscape.value ? 'landscape' : 'portrait'
94
+ )
95
+ }
96
+ }
97
+
98
+ function closeFullscreen() {
99
+ emit('update:isFullscreen', false)
100
+ }
101
+
102
+ function copyCode() {
103
+ if (!contentRef.value) return
104
+
105
+ const codeElement = contentRef.value.querySelector('pre code')
106
+ if (!codeElement) return
107
+
108
+ const text = codeElement.textContent || ''
109
+ navigator.clipboard.writeText(text).then(() => {
110
+ copied.value = true
111
+ setTimeout(() => {
112
+ copied.value = false
113
+ }, 2000)
114
+ })
115
+ }
116
+
117
+ function handleKeydown(e: KeyboardEvent) {
118
+ if (e.key === 'Escape' && props.isFullscreen) {
119
+ closeFullscreen()
120
+ }
121
+ }
122
+
123
+ watch(
124
+ () => props.isFullscreen,
125
+ (newVal) => {
126
+ if (newVal) {
127
+ document.body.style.overflow = 'hidden'
128
+ // 读取用户上次选择的方向
129
+ if (typeof window !== 'undefined') {
130
+ const savedOrientation = localStorage.getItem(ORIENTATION_KEY)
131
+ // 如果有保存的偏好,使用保存的值;否则默认为竖屏
132
+ if (savedOrientation) {
133
+ isLandscape.value = savedOrientation === 'landscape'
134
+ } else {
135
+ isLandscape.value = false
136
+ }
137
+ }
138
+ } else {
139
+ document.body.style.overflow = ''
140
+ }
141
+ }
142
+ )
143
+
144
+ onMounted(() => {
145
+ document.addEventListener('keydown', handleKeydown)
146
+ })
147
+
148
+ onUnmounted(() => {
149
+ document.removeEventListener('keydown', handleKeydown)
150
+ document.body.style.overflow = ''
151
+ })
152
+ </script>
153
+
154
+ <style scoped>
155
+ .code-fullscreen-overlay {
156
+ position: fixed;
157
+ top: 0;
158
+ left: 0;
159
+ right: 0;
160
+ bottom: 0;
161
+ width: 100vw;
162
+ height: 100vh;
163
+ background-color: rgba(0, 0, 0, 0.9);
164
+ z-index: 9999;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ }
169
+
170
+ .code-fullscreen-container {
171
+ width: 100%;
172
+ height: 100%;
173
+ background-color: var(--vp-c-bg);
174
+ display: flex;
175
+ flex-direction: column;
176
+ overflow: hidden;
177
+ transition: transform 0.3s ease;
178
+ }
179
+
180
+ /* 横屏模式 */
181
+ .code-fullscreen-overlay.is-landscape {
182
+ overflow: hidden;
183
+ }
184
+
185
+ .code-fullscreen-overlay.is-landscape .code-fullscreen-container {
186
+ position: absolute;
187
+ top: 0;
188
+ left: 0;
189
+ right: 0;
190
+ bottom: 0;
191
+ /* 关键:宽度取视口高度,高度取视口宽度 */
192
+ /* 这样旋转90度后,宽度(原高度100vh)会填满屏幕宽度 */
193
+ width: 100vh;
194
+ height: 100vw;
195
+ /* 以左上角为中心旋转,然后平移到正确位置 */
196
+ transform-origin: top left;
197
+ transform: rotate(90deg) translateY(-100%);
198
+ max-width: none;
199
+ max-height: none;
200
+ }
201
+
202
+ .code-fullscreen-toolbar {
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: space-between;
206
+ padding: 12px 20px;
207
+ background-color: var(--vp-c-bg-soft);
208
+ border-bottom: 1px solid var(--vp-c-divider);
209
+ flex-shrink: 0;
210
+ }
211
+
212
+ .code-fullscreen-title {
213
+ display: flex;
214
+ align-items: center;
215
+ gap: 12px;
216
+ font-size: 14px;
217
+ }
218
+
219
+ .language-tag {
220
+ display: inline-block;
221
+ padding: 4px 8px;
222
+ background-color: var(--vp-c-brand-soft);
223
+ color: var(--vp-c-brand-1);
224
+ border-radius: 4px;
225
+ font-family: var(--vp-font-family-mono);
226
+ font-size: 12px;
227
+ font-weight: 600;
228
+ text-transform: uppercase;
229
+ }
230
+
231
+ .filename {
232
+ color: var(--vp-c-text-2);
233
+ font-family: var(--vp-font-family-mono);
234
+ font-size: 13px;
235
+ }
236
+
237
+ .code-fullscreen-actions {
238
+ display: flex;
239
+ gap: 8px;
240
+ }
241
+
242
+ .action-btn {
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ width: 36px;
247
+ height: 36px;
248
+ border: none;
249
+ background-color: transparent;
250
+ color: var(--vp-c-text-2);
251
+ border-radius: 6px;
252
+ cursor: pointer;
253
+ transition: all 0.2s;
254
+ }
255
+
256
+ .action-btn img {
257
+ width: 16px;
258
+ height: 16px;
259
+ filter: var(--vp-icon-filter, none);
260
+ }
261
+
262
+ .action-btn:hover {
263
+ background-color: var(--vp-c-default-soft);
264
+ color: var(--vp-c-text-1);
265
+ }
266
+
267
+ .close-btn img {
268
+ width: 20px;
269
+ height: 20px;
270
+ }
271
+
272
+ /* 旋转按钮仅移动端显示 */
273
+ .mobile-only {
274
+ display: none;
275
+ }
276
+
277
+ .code-fullscreen-content {
278
+ flex: 1;
279
+ overflow: auto;
280
+ padding: 20px;
281
+ background-color: var(--vp-code-block-bg);
282
+ }
283
+
284
+ .code-fullscreen-content :deep(pre) {
285
+ margin: 0 !important;
286
+ background-color: transparent !important;
287
+ padding: 0 !important;
288
+ }
289
+
290
+ .code-fullscreen-content :deep(code) {
291
+ font-size: 14px !important;
292
+ line-height: 1.7 !important;
293
+ }
294
+
295
+ /* 移动端优化 */
296
+ @media (max-width: 768px) {
297
+ .mobile-only {
298
+ display: flex;
299
+ }
300
+
301
+ .code-fullscreen-overlay {
302
+ padding: 0;
303
+ }
304
+
305
+ .code-fullscreen-container {
306
+ max-width: 100%;
307
+ max-height: 100vh;
308
+ border-radius: 0;
309
+ }
310
+
311
+ .code-fullscreen-toolbar {
312
+ padding: 10px 16px;
313
+ }
314
+
315
+ .code-fullscreen-title {
316
+ font-size: 13px;
317
+ }
318
+
319
+ .language-tag {
320
+ font-size: 11px;
321
+ padding: 3px 6px;
322
+ }
323
+
324
+ .filename {
325
+ font-size: 12px;
326
+ overflow: hidden;
327
+ text-overflow: ellipsis;
328
+ white-space: nowrap;
329
+ max-width: 120px;
330
+ }
331
+
332
+ .code-fullscreen-content {
333
+ padding: 16px;
334
+ }
335
+
336
+ .code-fullscreen-content :deep(code) {
337
+ font-size: 12px !important;
338
+ }
339
+
340
+ /* 横屏模式下的字体调整 */
341
+ .code-fullscreen-overlay.is-landscape .code-fullscreen-content :deep(code) {
342
+ font-size: 14px !important;
343
+ }
344
+ }
345
+
346
+ /* 过渡动画 */
347
+ .fullscreen-fade-enter-active,
348
+ .fullscreen-fade-leave-active {
349
+ transition: opacity 0.3s ease;
350
+ }
351
+
352
+ .fullscreen-fade-enter-from,
353
+ .fullscreen-fade-leave-to {
354
+ opacity: 0;
355
+ }
356
+
357
+ .fullscreen-fade-enter-active .code-fullscreen-container,
358
+ .fullscreen-fade-leave-active .code-fullscreen-container {
359
+ transition: transform 0.3s ease;
360
+ }
361
+
362
+ .fullscreen-fade-enter-from .code-fullscreen-container {
363
+ transform: scale(0.95);
364
+ }
365
+
366
+ .fullscreen-fade-leave-to .code-fullscreen-container {
367
+ transform: scale(0.95);
368
+ }
369
+
370
+ /* 暗色模式图标适配 */
371
+ .dark .action-btn img {
372
+ filter: invert(1);
373
+ }
374
+ </style>