@tnotesjs/core 0.0.6 → 0.0.8

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