@tnotesjs/core 0.1.28 → 0.2.0

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 (71) hide show
  1. package/README.md +2 -1
  2. package/package.json +3 -1
  3. package/templates/sub-repo/.gitattributes +30 -0
  4. package/templates/sub-repo/.github/workflows/deploy.yml +87 -0
  5. package/templates/sub-repo/.vitepress/config.mts +12 -0
  6. package/templates/sub-repo/.vitepress/env.d.ts +10 -0
  7. package/templates/sub-repo/.vitepress/theme/index.ts +12 -0
  8. package/templates/sub-repo/.vscode/settings.json +330 -0
  9. package/templates/sub-repo/.vscode/tnotes.code-snippets +110 -0
  10. package/templates/sub-repo/public/favicon.ico +0 -0
  11. package/templates/sub-repo/public/logo.png +0 -0
  12. package/templates/sub-repo/tsconfig.json +45 -0
  13. package/vitepress/assets/icons/icon__setting_dark.svg +4 -0
  14. package/vitepress/assets/icons/icon__setting_light.svg +4 -0
  15. package/vitepress/assets/icons/index.ts +2 -2
  16. package/vitepress/client/localSearchIndexBridge.ts +24 -0
  17. package/vitepress/components/Discussions/Discussions.vue +1 -1
  18. package/vitepress/components/Footprints/Footprints.vue +90 -25
  19. package/vitepress/components/Layout/CustomSidebar.vue +489 -117
  20. package/vitepress/components/Layout/Layout.vue +5 -0
  21. package/vitepress/components/Layout/NavBarSettingsTrigger.vue +10 -2
  22. package/vitepress/components/Layout/SidebarItems.vue +517 -342
  23. package/vitepress/components/Layout/SidebarNavBefore.vue +1 -50
  24. package/vitepress/components/Layout/SidebarResizeHandle.vue +6 -23
  25. package/vitepress/components/Layout/composables/docLayoutLogic.ts +59 -0
  26. package/vitepress/components/Layout/composables/useDocLayout.test.ts +69 -0
  27. package/vitepress/components/Layout/composables/useDocLayout.ts +128 -0
  28. package/vitepress/components/Layout/composables/useSidebarDrag.ts +514 -0
  29. package/vitepress/components/Layout/composables/useSidebarLayout.ts +25 -8
  30. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +8 -3
  31. package/vitepress/components/Layout/sidebarCollapsedState.test.ts +183 -0
  32. package/vitepress/components/Layout/sidebarCollapsedState.ts +241 -0
  33. package/vitepress/components/Layout/sidebarDragLogic.test.ts +1146 -0
  34. package/vitepress/components/Layout/sidebarDragLogic.ts +1307 -0
  35. package/vitepress/components/Layout/sidebarHitTest.test.ts +572 -0
  36. package/vitepress/components/Layout/sidebarHitTest.ts +801 -0
  37. package/vitepress/components/Layout/sidebarItemRenderLogic.test.ts +53 -0
  38. package/vitepress/components/Layout/sidebarItemRenderLogic.ts +50 -0
  39. package/vitepress/components/MarkMap/MarkMap.vue +67 -10
  40. package/vitepress/components/Settings/Settings.vue +78 -74
  41. package/vitepress/components/SidebarCard/FolderTreeItems.vue +268 -0
  42. package/vitepress/components/SidebarCard/SidebarCard.vue +125 -274
  43. package/vitepress/components/constants.ts +18 -6
  44. package/vitepress/components/sidebar.data.ts +13 -11
  45. package/vitepress/components/sidebarTreeHelpers.test.ts +142 -0
  46. package/vitepress/components/sidebarTreeHelpers.ts +168 -0
  47. package/vitepress/components/utils/vscodePaths.test.ts +41 -0
  48. package/vitepress/components/utils/vscodePaths.ts +52 -0
  49. package/vitepress/config/index.ts +2 -0
  50. package/vitepress/plugins/localSearchIndexBuilder.ts +209 -0
  51. package/vitepress/plugins/localSearchReindex.test.ts +46 -0
  52. package/vitepress/plugins/localSearchReindexLogic.ts +50 -0
  53. package/vitepress/plugins/localSearchReindexPatch.test.ts +30 -0
  54. package/vitepress/plugins/localSearchReindexPatch.ts +37 -0
  55. package/vitepress/plugins/localSearchReindexPlugin.ts +318 -0
  56. package/vitepress/plugins/sidebarStructurePlugin.ts +209 -63
  57. package/vitepress/theme/index.ts +8 -0
  58. package/vitepress/theme/styles/base.scss +0 -8
  59. package/vitepress/theme/styles/components/markmap.scss +23 -2
  60. package/vitepress/theme/styles/components/swiper.scss +1 -1
  61. package/vitepress/theme/styles/doc-layout.scss +212 -0
  62. package/vitepress/theme/styles/index.scss +3 -0
  63. package/vitepress/theme/styles/layout.scss +60 -10
  64. package/dist/chunk-4KG6RXFS.js +0 -4637
  65. package/dist/chunk-AGSL3VZE.js +0 -193
  66. package/dist/cli/index.js +0 -683
  67. package/dist/index.js +0 -8
  68. package/dist/vitepress/config/index.js +0 -1178
  69. package/vitepress/assets/icons/icon__mindmap.svg +0 -1
  70. package/vitepress/assets/icons/icon__setting.svg +0 -1
  71. package/vitepress/components/SidebarCard/MindMapView.vue +0 -488
@@ -14,16 +14,6 @@ vitepress/components/Layout/SidebarNavBefore.vue
14
14
  >
15
15
  <img :src="icon__fold" class="toggle-icon" alt="切换展开折叠" />
16
16
  </button>
17
- <a
18
- :class="[
19
- 'group-label',
20
- 'group-label-link',
21
- { 'is-active': isCatalogActive },
22
- ]"
23
- :href="catalogLink"
24
- >
25
- 目录
26
- </a>
27
17
  </div>
28
18
 
29
19
  <div class="group group-right">
@@ -54,9 +44,6 @@ vitepress/components/Layout/SidebarNavBefore.vue
54
44
  </template>
55
45
 
56
46
  <script setup lang="ts">
57
- import { useData, useRoute } from 'vitepress'
58
- import { computed } from 'vue'
59
-
60
47
  import {
61
48
  icon__fold,
62
49
  icon__number_purple,
@@ -74,26 +61,6 @@ defineEmits<{
74
61
  'toggle-note-id': []
75
62
  'focus-current': []
76
63
  }>()
77
-
78
- const { site } = useData()
79
- const route = useRoute()
80
-
81
- const base = computed(() => site.value.base || '/')
82
- const catalogLink = computed(() => `${base.value}README`)
83
- const catalogPath = computed(() => normalizeRoutePath(catalogLink.value))
84
- const isCatalogActive = computed(() => {
85
- const currentPath = normalizeRoutePath(route.path)
86
-
87
- return currentPath === catalogPath.value || currentPath === '/README'
88
- })
89
-
90
- function normalizeRoutePath(path: string): string {
91
- const normalizedPath = path.split(/[?#]/)[0].replace(/\.html$/, '')
92
-
93
- return normalizedPath.length > 1
94
- ? normalizedPath.replace(/\/$/, '')
95
- : normalizedPath
96
- }
97
64
  </script>
98
65
 
99
66
  <style scoped>
@@ -104,7 +71,7 @@ function normalizeRoutePath(path: string): string {
104
71
  display: flex;
105
72
  flex-direction: column;
106
73
  gap: 8px;
107
- padding: 10px 8px 12px;
74
+ padding: 10px 0 12px;
108
75
  /* border-bottom: 1px solid var(--vp-c-divider); */
109
76
  margin-bottom: 10px;
110
77
  background: var(--vp-sidebar-bg-color, var(--vp-c-bg));
@@ -123,22 +90,6 @@ function normalizeRoutePath(path: string): string {
123
90
  gap: 4px;
124
91
  }
125
92
 
126
- .group-label {
127
- font-size: 13px;
128
- color: var(--vp-c-text-2);
129
- text-decoration: none;
130
- user-select: none;
131
- }
132
-
133
- .group-label-link:hover {
134
- color: var(--vp-c-brand-1);
135
- }
136
-
137
- .group-label-link.is-active {
138
- color: var(--vp-c-brand-1);
139
- font-weight: 600;
140
- }
141
-
142
93
  .toggle-btn {
143
94
  display: inline-flex;
144
95
  align-items: center;
@@ -38,6 +38,7 @@ vitepress/components/Layout/SidebarResizeHandle.vue
38
38
  import { useRoute } from 'vitepress'
39
39
  import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
40
40
 
41
+ import { updateResponsiveLayout } from './composables/useDocLayout'
41
42
  import { useSidebarLayout } from './composables/useSidebarLayout'
42
43
  import { icon__next, icon__prev } from '../../assets/icons'
43
44
 
@@ -57,13 +58,11 @@ const isDragging = ref(false)
57
58
  const isContentFullscreen = ref(false)
58
59
  const isSidebarAvailable = ref(false)
59
60
  const shortcutText = ref('Ctrl + Alt + ,')
60
- const WIDE_LAYOUT_MIN_WIDTH = 1440
61
61
  let fullscreenObserver: MutationObserver | null = null
62
-
63
- const toggleIcon = computed(() => (hidden.value ? icon__next : icon__prev))
64
62
  const toggleActionText = computed(() =>
65
63
  hidden.value ? '展开侧边栏' : '收起侧边栏',
66
64
  )
65
+ const toggleIcon = computed(() => (hidden.value ? icon__next : icon__prev))
67
66
  const toggleTitle = computed(
68
67
  () => `${toggleActionText.value}\n${shortcutText.value}`,
69
68
  )
@@ -102,6 +101,7 @@ function resize(event: MouseEvent) {
102
101
  if (!isDragging.value) return
103
102
 
104
103
  setSidebarWidth(getSidebarWidthFromClientX(event.clientX))
104
+ updateResponsiveLayout()
105
105
  }
106
106
 
107
107
  function stopResize() {
@@ -112,6 +112,7 @@ function stopResize() {
112
112
  window.removeEventListener('mousemove', resize)
113
113
  window.removeEventListener('mouseup', stopResize)
114
114
  saveSidebarWidth()
115
+ updateResponsiveLayout()
115
116
  }
116
117
 
117
118
  function handleShortcut(event: KeyboardEvent) {
@@ -125,6 +126,7 @@ function handleShortcut(event: KeyboardEvent) {
125
126
 
126
127
  event.preventDefault()
127
128
  toggleSidebar()
129
+ updateResponsiveLayout()
128
130
  }
129
131
 
130
132
  watch(
@@ -155,18 +157,7 @@ function getSidebarWidthFromClientX(clientX: number): number {
155
157
  }
156
158
 
157
159
  function getSidebarLayoutOffset(): number {
158
- if (typeof window === 'undefined' || typeof document === 'undefined') return 0
159
- if (window.innerWidth < WIDE_LAYOUT_MIN_WIDTH) return 0
160
-
161
- const layoutMaxWidth = Number.parseFloat(
162
- window
163
- .getComputedStyle(document.documentElement)
164
- .getPropertyValue('--vp-layout-max-width'),
165
- )
166
-
167
- if (!Number.isFinite(layoutMaxWidth)) return 0
168
-
169
- return Math.max(0, (window.innerWidth - layoutMaxWidth) / 2)
160
+ return 0
170
161
  }
171
162
 
172
163
  function isToggleShortcut(event: KeyboardEvent): boolean {
@@ -361,12 +352,4 @@ function isEditableTarget(target: EventTarget | null): boolean {
361
352
  display: none;
362
353
  }
363
354
  }
364
-
365
- @media (min-width: 1440px) {
366
- .sidebar-resize-handle {
367
- left: calc(
368
- (100vw - var(--vp-layout-max-width)) / 2 + var(--tn-sidebar-width, 260px) - 5px
369
- );
370
- }
371
- }
372
355
  </style>
@@ -0,0 +1,59 @@
1
+ /**
2
+ * docLayoutLogic.ts
3
+ *
4
+ * 语雀式三栏布局的纯函数(可单测,不依赖 VitePress define)
5
+ */
6
+
7
+ export type ContentWidthMode = 'wide' | 'standard'
8
+
9
+ const ASIDE_WIDTH = 300
10
+ const STANDARD_CONTENT_WIDTH = 750
11
+ const WIDE_CONTENT_MIN_WIDTH = 640
12
+ const LAYOUT_PADDING = 16
13
+ /** VitePress 桌面侧栏断点;以下由 VP 自带移动顶栏/抽屉负责 */
14
+ export const DESKTOP_SIDEBAR_BREAKPOINT = 960
15
+
16
+ function getContentTargetWidth(mode: ContentWidthMode): number {
17
+ return mode === 'standard' ? STANDARD_CONTENT_WIDTH : WIDE_CONTENT_MIN_WIDTH
18
+ }
19
+
20
+ export function resolveResponsiveLayoutState(input: {
21
+ viewportWidth: number
22
+ sidebarWidth: number
23
+ userSidebarHidden: boolean
24
+ contentWidthMode: ContentWidthMode
25
+ hasAside: boolean
26
+ }): { asideAutoHidden: boolean; sidebarAutoHidden: boolean } {
27
+ // 移动端不使用桌面三栏降级,避免误隐藏顶栏站点标题
28
+ if (input.viewportWidth < DESKTOP_SIDEBAR_BREAKPOINT) {
29
+ return { asideAutoHidden: false, sidebarAutoHidden: false }
30
+ }
31
+
32
+ const sidebarWidth = input.userSidebarHidden ? 0 : input.sidebarWidth
33
+ const contentTarget = getContentTargetWidth(input.contentWidthMode)
34
+
35
+ if (!input.hasAside) {
36
+ const needSidebarAndContent =
37
+ sidebarWidth + contentTarget + LAYOUT_PADDING * 2
38
+
39
+ return {
40
+ asideAutoHidden: false,
41
+ sidebarAutoHidden: input.viewportWidth < needSidebarAndContent,
42
+ }
43
+ }
44
+
45
+ const needThreeColumns =
46
+ sidebarWidth + contentTarget + ASIDE_WIDTH + LAYOUT_PADDING * 2
47
+ const needSidebarAndContent =
48
+ sidebarWidth + contentTarget + LAYOUT_PADDING * 2
49
+
50
+ if (input.viewportWidth < needSidebarAndContent) {
51
+ return { asideAutoHidden: true, sidebarAutoHidden: true }
52
+ }
53
+
54
+ if (input.viewportWidth < needThreeColumns) {
55
+ return { asideAutoHidden: true, sidebarAutoHidden: false }
56
+ }
57
+
58
+ return { asideAutoHidden: false, sidebarAutoHidden: false }
59
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * useDocLayout.test.ts
3
+ */
4
+
5
+ import { describe, expect, it } from 'vitest'
6
+
7
+ import { resolveResponsiveLayoutState } from './docLayoutLogic'
8
+
9
+ describe('resolveResponsiveLayoutState', () => {
10
+ it('keeps three columns on wide viewport', () => {
11
+ expect(
12
+ resolveResponsiveLayoutState({
13
+ viewportWidth: 1600,
14
+ sidebarWidth: 260,
15
+ userSidebarHidden: false,
16
+ contentWidthMode: 'wide',
17
+ hasAside: true,
18
+ }),
19
+ ).toEqual({ asideAutoHidden: false, sidebarAutoHidden: false })
20
+ })
21
+
22
+ it('hides aside first when viewport is medium', () => {
23
+ expect(
24
+ resolveResponsiveLayoutState({
25
+ viewportWidth: 1300,
26
+ sidebarWidth: 260,
27
+ userSidebarHidden: false,
28
+ contentWidthMode: 'standard',
29
+ hasAside: true,
30
+ }),
31
+ ).toEqual({ asideAutoHidden: true, sidebarAutoHidden: false })
32
+ })
33
+
34
+ it('hides aside and sidebar when viewport is narrow', () => {
35
+ expect(
36
+ resolveResponsiveLayoutState({
37
+ viewportWidth: 1000,
38
+ sidebarWidth: 260,
39
+ userSidebarHidden: false,
40
+ contentWidthMode: 'standard',
41
+ hasAside: true,
42
+ }),
43
+ ).toEqual({ asideAutoHidden: true, sidebarAutoHidden: true })
44
+ })
45
+
46
+ it('does not apply auto-collapse on mobile viewport', () => {
47
+ expect(
48
+ resolveResponsiveLayoutState({
49
+ viewportWidth: 375,
50
+ sidebarWidth: 260,
51
+ userSidebarHidden: false,
52
+ contentWidthMode: 'wide',
53
+ hasAside: true,
54
+ }),
55
+ ).toEqual({ asideAutoHidden: false, sidebarAutoHidden: false })
56
+ })
57
+
58
+ it('does not reserve aside space when page has no outline on desktop', () => {
59
+ expect(
60
+ resolveResponsiveLayoutState({
61
+ viewportWidth: 1000,
62
+ sidebarWidth: 260,
63
+ userSidebarHidden: false,
64
+ contentWidthMode: 'standard',
65
+ hasAside: false,
66
+ }),
67
+ ).toEqual({ asideAutoHidden: false, sidebarAutoHidden: true })
68
+ })
69
+ })
@@ -0,0 +1,128 @@
1
+ /**
2
+ * vitepress/components/Layout/composables/useDocLayout.ts
3
+ *
4
+ * 语雀式三栏布局:正文页宽模式 + 视口不足时自动隐藏右栏/左栏
5
+ */
6
+
7
+ import { ref } from 'vue'
8
+
9
+
10
+ import {
11
+ resolveResponsiveLayoutState,
12
+ type ContentWidthMode,
13
+ } from './docLayoutLogic'
14
+ import { useSidebarLayout } from './useSidebarLayout'
15
+ import { CONTENT_WIDTH_MODE_KEY } from '../../constants'
16
+
17
+ export type { ContentWidthMode } from './docLayoutLogic'
18
+
19
+ const contentWidthMode = ref<ContentWidthMode>('wide')
20
+ const asideAutoHidden = ref(false)
21
+ let initialized = false
22
+ let resizeListenerAttached = false
23
+
24
+ function canUseDOM(): boolean {
25
+ return typeof window !== 'undefined' && typeof document !== 'undefined'
26
+ }
27
+
28
+ function readContentWidthMode(): ContentWidthMode {
29
+ if (!canUseDOM()) return 'wide'
30
+
31
+ try {
32
+ const saved = window.localStorage.getItem(CONTENT_WIDTH_MODE_KEY)
33
+ return saved === 'standard' ? 'standard' : 'wide'
34
+ } catch {
35
+ return 'wide'
36
+ }
37
+ }
38
+
39
+ function applyContentWidthMode(mode: ContentWidthMode) {
40
+ if (!canUseDOM()) return
41
+
42
+ const root = document.documentElement
43
+ root.classList.toggle('content-width-wide', mode === 'wide')
44
+ root.classList.toggle('content-width-standard', mode === 'standard')
45
+ }
46
+
47
+ function pageHasDocAside(): boolean {
48
+ if (!canUseDOM()) return false
49
+ return Boolean(document.querySelector('.VPDoc.has-aside'))
50
+ }
51
+
52
+ function applyAsideAutoHidden(nextHidden: boolean) {
53
+ if (!canUseDOM()) return
54
+
55
+ asideAutoHidden.value = nextHidden
56
+ document.documentElement.classList.toggle('aside-auto-hidden', nextHidden)
57
+ }
58
+
59
+ function applySidebarAutoHidden(nextHidden: boolean) {
60
+ if (!canUseDOM()) return
61
+
62
+ document.documentElement.classList.toggle('sidebar-auto-hidden', nextHidden)
63
+ const { setSidebarAutoHidden } = useSidebarLayout()
64
+ setSidebarAutoHidden(nextHidden)
65
+ }
66
+
67
+ export function updateResponsiveLayout() {
68
+ if (!canUseDOM()) return
69
+
70
+ const { hidden, width } = useSidebarLayout()
71
+ const next = resolveResponsiveLayoutState({
72
+ viewportWidth: window.innerWidth,
73
+ sidebarWidth: width.value,
74
+ userSidebarHidden: hidden.value,
75
+ contentWidthMode: contentWidthMode.value,
76
+ hasAside: pageHasDocAside(),
77
+ })
78
+
79
+ applyAsideAutoHidden(next.asideAutoHidden)
80
+ applySidebarAutoHidden(next.sidebarAutoHidden)
81
+ }
82
+
83
+ function handleLayoutResize() {
84
+ updateResponsiveLayout()
85
+ }
86
+
87
+ function attachResizeListener() {
88
+ if (!canUseDOM() || resizeListenerAttached) return
89
+
90
+ window.addEventListener('resize', handleLayoutResize, { passive: true })
91
+ resizeListenerAttached = true
92
+ }
93
+
94
+ function initDocLayout() {
95
+ if (!canUseDOM()) return
96
+
97
+ useSidebarLayout().initSidebarLayout()
98
+ contentWidthMode.value = readContentWidthMode()
99
+ applyContentWidthMode(contentWidthMode.value)
100
+
101
+ if (!initialized) {
102
+ initialized = true
103
+ attachResizeListener()
104
+ }
105
+
106
+ updateResponsiveLayout()
107
+ }
108
+
109
+ export function useDocLayout() {
110
+ return {
111
+ contentWidthMode,
112
+ asideAutoHidden,
113
+ initDocLayout,
114
+ updateResponsiveLayout,
115
+ readContentWidthMode,
116
+ applyContentWidthMode,
117
+ }
118
+ }
119
+
120
+ export function persistContentWidthMode(mode: ContentWidthMode) {
121
+ if (!canUseDOM()) return
122
+
123
+ try {
124
+ window.localStorage.setItem(CONTENT_WIDTH_MODE_KEY, mode)
125
+ } catch {
126
+ // ignore quota / privacy mode
127
+ }
128
+ }