1
- <script lang="ts">
2
- import { CalendarIcon , Cog8ToothIcon , DocumentPlusIcon , MoonIcon , SunIcon , UserCircleIcon , XMarkIcon } from ' @heroicons/vue/24/solid'
3
- import { computed , defineComponent , onMounted , onUnmounted } from ' vue'
4
- import GraphIcon from ' #root/assets/graph.svg?component'
5
- import LogoIcon from ' #root/assets/logo-icon.svg?component'
1
+ <script lang="ts" setup>
2
+ import { IconDotsVertical , IconMenu2 , IconMoon , IconPencilPlus , IconSun , IconSunMoon } from ' @tabler/icons-vue'
3
+ import { computed , onMounted , onUnmounted } from ' vue'
6
4
import CoreButton from ' #root/components/CoreButton.vue'
7
5
import CoreDivider from ' #root/components/CoreDivider.vue'
8
6
import CoreLink from ' #root/components/CoreLink.vue'
9
- import Key from ' #root/components/Key.vue'
10
7
import TheLeftSidebar from ' #root/pages/menu.vue'
11
8
import TheRightSidebar from ' #root/pages/docs/[docId]/meta.vue'
12
9
import { bindGlobal } from ' #root/src/common/keybindings'
13
10
14
- export default defineComponent ({
15
- components: {
16
- CalendarIcon ,
17
- Cog8ToothIcon ,
18
- CoreButton ,
19
- CoreDivider ,
20
- CoreLink ,
21
- DocumentPlusIcon ,
22
- GraphIcon ,
23
- Key ,
24
- LogoIcon ,
25
- MoonIcon ,
26
- SunIcon ,
27
- TheLeftSidebar ,
28
- TheRightSidebar ,
29
- UserCircleIcon ,
30
- XMarkIcon ,
31
- },
32
- setup() {
33
- const { isNuxt } = useIsNuxt ()
34
- const router = useRouter ()
35
- const { doc } = useDocs ()
36
- const { showMenu, showMeta, toggleMenu, toggleMeta } = useLayout ()
37
- const { pinnedDocs, unpinDoc } = usePinnedDocs ()
38
- const { isDesktop, isMobile, modKey } = useDevice ()
39
- const isDoc = computed (() => router .currentRoute .value .name === ' docs-docId' )
40
- const isNew = computed (() => router .currentRoute .value .path === ' /docs/new' )
41
- const { store : appearance, isAuto, isDark, isLight } = useAppearance ()
11
+ const { doc } = useDocs ()
12
+ const { isZen, toggleZen } = useZen ()
13
+ const { showMenu, showMeta, toggleMenu, toggleMeta } = useLayout ()
14
+ const { store : appearance, isAuto, isDark, isLight } = useAppearance ()
15
+ const isPrimaryGutterShowing = computed (() => ! isZen .value && showMenu .value )
16
+ const isSecondaryGutterShowing = computed (() => ! isZen .value && showMeta .value && doc .value )
42
17
43
- const handleQuickActionClose = () => {
44
- if ( doc . value ) {
45
- return router . push ({ path: ` /docs/${ doc . value . id } ` } )
46
- }
18
+ const scrollListener = (event : Event ) => {
19
+ event . preventDefault ()
20
+ event . stopImmediatePropagation ( )
21
+ event . stopPropagation ()
47
22
48
- router .push ({ path: ' /docs/new' })
49
- }
50
-
51
- const handleLayoutChange = () => {
52
- toggleMenu ()
53
- toggleMeta ()
54
- }
55
-
56
- onMounted (() => {
57
- // Todo: Migrate keybindings to composables.
58
- bindGlobal (' mod+\\ ' , () => {
59
- toggleMenu ()
60
- toggleMeta ()
61
- })
62
- })
63
-
64
- const handleTabClose = async (id : string ) => {
65
- if (doc .value ?.id === id ) {
66
- await router .push ({ path: ' /docs/new' })
67
- }
68
-
69
- unpinDoc (id )
70
- }
71
-
72
- const scrollListener = (event : Event ) => {
73
- event .preventDefault ()
74
- event .stopImmediatePropagation ()
75
- event .stopPropagation ()
23
+ window .scrollTo (0 , 0 )
24
+ }
76
25
77
- window .scrollTo (0 , 0 )
78
- }
26
+ const toggleAppearance = () => {
27
+ appearance .value = isAuto .value ? ' dark' : isDark .value ? ' light' : ' auto'
28
+ }
79
29
80
- const toggleAppearance = () => {
81
- appearance .value = isAuto .value ? ' dark' : isDark .value ? ' light' : ' auto'
82
- }
30
+ onMounted (() => {
31
+ window .addEventListener (' scroll' , scrollListener )
83
32
84
- onMounted (() => {
85
- window .addEventListener (' scroll' , scrollListener )
86
- })
33
+ // Todo: Migrate keybindings to composables.
34
+ bindGlobal (' mod+\\ ' , () => {
35
+ toggleZen ()
36
+ })
87
37
88
- onUnmounted (() => {
89
- window .removeEventListener (' scroll' , scrollListener )
90
- })
38
+ // Todo: Migrate keybindings to composables.
39
+ bindGlobal (' mod+shift+,' , () => {
40
+ toggleMenu ()
41
+ })
42
+ })
91
43
92
- return {
93
- CoreDivider ,
94
- CoreLink ,
95
- appearance ,
96
- doc ,
97
- handleLayoutChange ,
98
- handleQuickActionClose ,
99
- handleTabClose ,
100
- isAuto ,
101
- isDark ,
102
- isDesktop ,
103
- isDoc ,
104
- isLight ,
105
- isMobile ,
106
- isNew ,
107
- isNuxt ,
108
- modKey ,
109
- pinnedDocs ,
110
- showMenu ,
111
- showMeta ,
112
- toggleAppearance ,
113
- }
114
- },
44
+ onUnmounted (() => {
45
+ window .removeEventListener (' scroll' , scrollListener )
115
46
})
116
47
</script >
117
48
118
49
<template >
119
- <div class =" dashboard flex h-screen w-screen min-h-0 min-w-0 overflow-hidden" >
120
- <CoreLayer v-if =" isDesktop" as =" section" class =" flex flex-col items-center justify-between gap-4 h-full bg-layer md:flex" >
121
- <div class =" flex flex-col" >
122
- <div class =" flex flex-col flex-shrink-0 items-center justify-center p-1" >
123
- <CoreButton
124
- class =" flex items-center justify-center p-1"
125
- data-test-id =" toggle-sidebars"
126
- data-test-toggle-sidebars
127
- @click =" handleLayoutChange"
128
- >
129
- <LogoIcon class =" h-8 text-brand" />
50
+ <div class =" dashboard flex flex-col lg:flex-row h-screen w-screen min-h-0 min-w-0 overflow-hidden" >
51
+ <Gutter :show =" isPrimaryGutterShowing" :size =" 256" class =" hidden lg:flex" >
52
+ <TheLeftSidebar class =" flex flex-grow flex-shrink overflow-hidden w-full" />
53
+ </Gutter >
54
+ <FlexDivider class =" hidden lg:block" />
55
+ <CoreLayer class =" bg-layer basis-full flex flex-col flex-grow flex-shrink min-h-0" >
56
+ <div class =" flex p-1 items-center" >
57
+ <div class =" flex gap-1 flex-row-reverse lg:flex-row items-center basis-full justify-start" >
58
+ <CoreButton class =" hidden lg:flex" @click =" toggleMenu" >
59
+ <IconMenu2 :stroke-width =" 1.25" class =" h-6" />
60
+ <span >Menu</span >
61
+ </CoreButton >
62
+ <CoreButton :as =" CoreLink" :to =" { path: '/menu' }" class =" lg:hidden" >
63
+ <IconMenu2 :stroke-width =" 1.25" class =" h-6" />
64
+ <span >Menu</span >
65
+ </CoreButton >
66
+ <CoreButton :as =" CoreLink" :to =" { path: '/docs/new' }" >
67
+ <IconPencilPlus :stroke-width =" 1.25" class =" h-6" />
68
+ <span >New</span >
130
69
</CoreButton >
131
70
</div >
132
- <CoreDivider />
133
- <div class =" flex flex-col gap-1 p-1 items-center" >
134
- <CoreButtonLink :to =" { path: '/docs/new' }" :layer =" 1" :flat =" true" >
135
- <DocumentPlusIcon class =" w-6" />
136
- </CoreButtonLink >
137
- <CoreButtonLink :to =" { path: '/notepad' }" :layer =" 1" :flat =" true" >
138
- <CalendarIcon class =" w-6" />
139
- </CoreButtonLink >
140
- <CoreButtonLink :to =" { path: '/force-graph' }" :layer =" 1" :flat =" true" >
141
- <GraphIcon class =" w-6" />
142
- </CoreButtonLink >
71
+ <div class =" flex -order-1 lg:order-none items-center justify-center" >
72
+ <CoreLayer class =" flex text-xl gap-4" >
73
+ <svg class =" sq-7 transition-colors duration-300 cursor-pointer" :class =" isZen ? 'text-layer-bg' : 'text-brand'" viewBox =" 0 0 250 250" fill =" none" xmlns =" http://www.w3.org/2000/svg" @click =" toggleZen" >
74
+ <path d =" M125 250C194.036 250 250 194.036 250 125C250 55.9644 194.036 0 125 0C55.9644 0 0 55.9644 0 125C0 194.036 55.9644 250 125 250Z" fill =" currentColor" />
75
+ <template v-if =" isZen " >
76
+ <path class =" fill-layer-text-muted stroke-layer-text-muted" d =" M212.962 153.171C212.962 153.171 192.949 159.5 179.472 159.5C165.995 159.5 145.981 153.171 145.981 153.171C145.981 153.171 164.921 167.5 179.472 167.5C194.022 167.5 212.962 153.171 212.962 153.171Z" stroke-width =" 2" stroke-linejoin =" round" />
77
+ <path class =" fill-layer-text-muted stroke-layer-text-muted" d =" M37 153.171C37 153.171 57.0133 159.5 70.4906 159.5C83.9679 159.5 103.981 153.171 103.981 153.171C103.981 153.171 85.0412 167.5 70.4906 167.5C55.94 167.5 37 153.171 37 153.171Z" stroke-width =" 2" stroke-linejoin =" round" />
78
+ </template >
79
+ <template v-else >
80
+ <mask id =" mask0_508_218" style =" mask-type :alpha " maskUnits =" userSpaceOnUse" x =" 145" y =" 117" width =" 68" height =" 85" >
81
+ <ellipse cx =" 179.378" cy =" 159.462" rx =" 33.4292" ry =" 42.3835" fill =" #D9D9D9" />
82
+ </mask >
83
+ <g mask =" url(#mask0_508_218)" >
84
+ <ellipse cx =" 179.378" cy =" 159.462" rx =" 33.4292" ry =" 42.3835" fill =" white" />
85
+ <ellipse cx =" 163.865" cy =" 162.805" rx =" 23.3666" ry =" 29.6255" fill =" black" />
86
+ </g >
87
+ <mask id =" mask1_508_218" style =" mask-type :alpha " maskUnits =" userSpaceOnUse" x =" 37" y =" 117" width =" 68" height =" 85" >
88
+ <ellipse cx =" 70.7535" cy =" 159.462" rx =" 33.4292" ry =" 42.3836" fill =" #D9D9D9" />
89
+ </mask >
90
+ <g mask =" url(#mask1_508_218)" >
91
+ <ellipse cx =" 70.7535" cy =" 159.462" rx =" 33.4292" ry =" 42.3836" fill =" white" />
92
+ <ellipse cx =" 86.0505" cy =" 162.271" rx =" 23.3664" ry =" 29.6253" fill =" black" />
93
+ </g >
94
+ </template >
95
+ </svg >
96
+ </CoreLayer >
143
97
</div >
144
- </div >
145
- <div class =" flex flex-col gap-1 p-1 items-center" >
146
- <div class =" flex flex-col gap-1 pb-2 items-center text-layer-muted" >
147
- <CoreButton @click =" toggleAppearance" >
148
- <MoonIcon v-if =" isDark" class =" w-6" />
149
- <SunIcon v-else-if =" isLight" class =" w-6" />
150
- <div v-else class =" relative sq-6" >
151
- <SunIcon class =" w-4 absolute top-0 right-0" />
152
- <MoonIcon class =" w-4 absolute bottom-0 left-0" />
153
- </div >
98
+ <div class =" flex items-center gap-1 lg:basis-full justify-end" >
99
+ <CoreButton class =" hidden lg:flex" @click =" toggleAppearance" >
100
+ <IconMoon v-if =" isDark" :stroke-width =" 1.25" class =" w-6" />
101
+ <IconSun v-else-if =" isLight" :stroke-width =" 1.25" class =" w-6" />
102
+ <IconSunMoon v-else :stroke-width =" 1.25" class =" w-6" />
103
+ </CoreButton >
104
+ <CoreButton v-if =" doc" :as =" CoreLink" :to =" { path: `/docs/${doc.id}/meta` }" class =" lg:hidden" >
105
+ <IconDotsVertical :stroke-width =" 1.25" class =" h-6" />
106
+ </CoreButton >
107
+ <CoreButton v-if =" doc" class =" hidden lg:flex" @click =" toggleMeta" >
108
+ <IconDotsVertical :stroke-width =" 1.25" class =" h-6" />
154
109
</CoreButton >
155
110
</div >
156
- < CoreButtonLink :to = " { path: '/settings' } " :layer = " 1 " :flat = " true " >
157
- < Cog8ToothIcon class = " w-6 " />
158
- </ CoreButtonLink >
159
- <CoreButtonLink :to = " { path: '/account' } " :layer = " 1 " :flat = " true " >
160
- <UserCircleIcon class = " w-6 " />
161
- </CoreButtonLink >
111
+ </ div >
112
+ < CoreDivider />
113
+ < div class = " flex flex-grow flex-shrink min-h-0 overflow-hidden min-w-0 " >
114
+ <section class = " flex flex-grow flex-shrink min-h-0 overflow-hidden min-w-0 " >
115
+ <slot />
116
+ </section >
162
117
</div >
163
118
</CoreLayer >
164
- <CoreLayer v-if =" isDesktop" :as =" CoreDivider" :vertical =" true" />
165
- <section class =" flex flex-col flex-grow flex-shrink min-h-0 min-w-0" >
166
- <CoreLayer as =" nav" class =" flex items-center justify-between bg-layer" >
167
- <div v-if =" isMobile" class =" flex items-center justify-between flex-grow p-1" >
168
- <CoreLink :to =" { path: '/docs/new' }" class =" flex items-center justify-center p-1" >
169
- <LogoIcon class =" h-8 text-brand" />
170
- </CoreLink >
171
- <div class =" flex items-center gap-1" >
172
- <CoreButton :as =" CoreLink" :to =" { path: '/menu' }" role =" button" aria-haspopup =" true" aria-expanded =" false" >
173
- <svg height =" 1.25em" width =" 1.25em" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
174
- <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M4 6h16M4 12h16M4 18h16" />
175
- </svg >
176
- <span class =" ml-2" >Menu</span >
177
- </CoreButton >
178
- <CoreButton v-if =" isNew" :as =" CoreLink" :to =" { path: '/quick-action' }" >
179
- <svg height =" 1.25em" width =" 1.25em" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
180
- <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
181
- </svg >
182
- </CoreButton >
183
- <CoreButton v-else-if =" isDoc" :as =" CoreLink" :to =" { path: `/docs/${doc?.id}/meta` }" >
184
- <svg height =" 1.25em" width =" 1.25em" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
185
- <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
186
- </svg >
187
- </CoreButton >
188
- <CoreButton v-else @click =" handleQuickActionClose" >
189
- <svg height =" 1.25em" width =" 1.25em" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
190
- <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M6 18L18 6M6 6l12 12" />
191
- </svg >
192
- </CoreButton >
193
- </div >
194
- </div >
195
- <CoreNavPanel v-if =" isDesktop" class =" flex-shrink-0 w-64" >
196
- <CoreLink :to =" { path: '/docs' }" class =" sidebar-link justify-between w-full" >
197
- <div class =" flex gap-3 items-center" >
198
- <svg class =" w-5" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
199
- <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
200
- </svg >
201
- <span >Search</span >
202
- </div >
203
- <span class =" hidden md:flex text-layer-muted" >
204
- <Key >{{ modKey }}</Key >
205
- <Key >⇧</Key >
206
- <Key >f</Key >
207
- </span >
208
- </CoreLink >
209
- </CoreNavPanel >
210
- <CoreDivider v-if =" isDesktop" vertical />
211
- <CoreNavPanel v-if =" isDesktop" class =" flex flex-grow flex-shrink gap-2 min-w-0" horizontal >
212
- <CoreButton v-for =" pinnedDoc in pinnedDocs" :key =" pinnedDoc.id" :as =" CoreLink" :to =" { path: `/docs/${pinnedDoc.id}` }" class =" allow-link-active flex flex-shrink justify-between min-w-[4rem] max-w-[20rem]" >
213
- <span class =" text-ellipsis overflow-hidden" >{{ pinnedDoc.label }}</span >
214
- <XMarkIcon class =" w-4 transition hover:scale-125" @click.prevent.stop =" () => handleTabClose(pinnedDoc.id)" />
215
- </CoreButton >
216
- </CoreNavPanel >
217
- </CoreLayer >
218
- <CoreLayer :as =" CoreDivider" />
219
- <section class =" flex flex-grow flex-shrink overflow-hidden min-w-0" >
220
- <CoreLayer v-if =" (isDesktop && showMenu)" v-slot =" { layer }" template >
221
- <TheLeftSidebar class =" hidden w-64 md:flex flex-shrink-0 bg-layer" :class =" layer.class" />
222
- </CoreLayer >
223
- <CoreLayer v-if =" (isDesktop && showMenu)" :as =" CoreDivider" :vertical =" true" />
224
- <slot />
225
- <CoreLayer v-if =" (isDesktop && showMeta && doc && isDoc)" v-slot =" { layer }" template >
226
- <CoreDivider vertical />
227
- <TheRightSidebar class =" hidden w-64 bg-layer md:flex flex-shrink-0" :class =" layer.class" />
228
- </CoreLayer >
229
- </section >
230
- </section >
119
+ <FlexDivider class =" hidden lg:block" />
120
+ <Gutter :show =" isSecondaryGutterShowing" :size =" 256" class =" hidden lg:flex" >
121
+ <TheRightSidebar class =" hidden lg:flex flex-grow flex-shrink-0 w-full" />
122
+ </Gutter >
231
123
<ToastList class =" fixed bottom-8 right-8 m-auto" />
232
124
</div >
233
125
</template >
@@ -236,4 +128,12 @@ export default defineComponent({
236
128
.dashboard {
237
129
height : var (--app-height , 100vh );
238
130
}
131
+
132
+ .gutter-right {
133
+ direction : rtl ;
134
+ }
135
+
136
+ :deep(.gutter-right > * ) {
137
+ direction : ltr ;
138
+ }
239
139
</style >
0 commit comments