Skip to content

Commit c758ae0

Browse files
feat: add compact list mode for space lists (#705)
* feat(menu): add compact list mode toggle * feat(lists): add compact mode to code and notes * feat(math): add compact sheet list mode
1 parent 8bca41d commit c758ae0

16 files changed

Lines changed: 281 additions & 28 deletions

File tree

src/main/i18n/locales/en_US/menu.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"dateCreated": "Date Created",
5353
"name": "Name"
5454
},
55-
"compactMode": "Compact Mode"
55+
"compactMode": "Compact List"
5656
},
5757
"edit": {
5858
"label": "Edit",

src/main/i18n/locales/ru_RU/menu.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"dateCreated": "Дате создания",
5353
"name": "Имени"
5454
},
55-
"compactMode": "Компактный режим"
55+
"compactMode": "Компактный список"
5656
},
5757
"edit": {
5858
"label": "Правка",

src/main/menu/__tests__/main.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ describe('createMainMenu', () => {
5858
view: {
5959
layoutMode: 'all-panels',
6060
layoutModes: ['all-panels', 'list-editor', 'editor-only'],
61+
canToggleCompactMode: true,
6162
canToggleMindmap: false,
63+
isCompactMode: true,
6264
isMindmapShown: false,
6365
canTogglePresentation: false,
6466
isPresentationShown: false,
@@ -98,12 +100,107 @@ describe('createMainMenu', () => {
98100
'menu:view.layout.allPanels',
99101
'menu:view.layout.listEditor',
100102
'menu:view.layout.editorOnly',
103+
undefined,
104+
'menu:view.compactMode',
101105
])
102106
expect(viewMenu?.submenu?.some(item => Array.isArray(item.submenu))).toBe(
103107
false,
104108
)
105109
})
106110

111+
it('omits compact mode when the current space has no list', async () => {
112+
const { createMainMenu } = await import('../main')
113+
114+
const context: MainMenuContext = {
115+
file: {
116+
primaryAction: null,
117+
secondaryAction: null,
118+
canCreateFragment: false,
119+
},
120+
view: {
121+
layoutMode: null,
122+
layoutModes: [],
123+
canToggleCompactMode: false,
124+
canToggleMindmap: false,
125+
isCompactMode: false,
126+
isMindmapShown: false,
127+
canTogglePresentation: false,
128+
isPresentationShown: false,
129+
},
130+
editor: {
131+
kind: null,
132+
noteMode: null,
133+
canFormat: false,
134+
canPreviewCode: false,
135+
isCodePreviewShown: false,
136+
canPreviewJson: false,
137+
isJsonPreviewShown: false,
138+
canAdjustFontSize: false,
139+
},
140+
}
141+
142+
createMainMenu(context)
143+
144+
const template = buildFromTemplate.mock.calls[0]?.[0] as Array<{
145+
label?: string
146+
submenu?: Array<{ label?: string, type?: string, checked?: boolean }>
147+
}>
148+
const viewMenu = template.find(item => item.label === 'menu:view.label')
149+
const compactModeItem = viewMenu?.submenu?.find(
150+
item => item.label === 'menu:view.compactMode',
151+
)
152+
153+
expect(compactModeItem).toBeUndefined()
154+
})
155+
156+
it('marks compact mode as checked when enabled', async () => {
157+
const { createMainMenu } = await import('../main')
158+
159+
const context: MainMenuContext = {
160+
file: {
161+
primaryAction: 'new-sheet',
162+
secondaryAction: null,
163+
canCreateFragment: false,
164+
},
165+
view: {
166+
layoutMode: null,
167+
layoutModes: [],
168+
canToggleCompactMode: true,
169+
canToggleMindmap: false,
170+
isCompactMode: true,
171+
isMindmapShown: false,
172+
canTogglePresentation: false,
173+
isPresentationShown: false,
174+
},
175+
editor: {
176+
kind: null,
177+
noteMode: null,
178+
canFormat: false,
179+
canPreviewCode: false,
180+
isCodePreviewShown: false,
181+
canPreviewJson: false,
182+
isJsonPreviewShown: false,
183+
canAdjustFontSize: false,
184+
},
185+
}
186+
187+
createMainMenu(context)
188+
189+
const template = buildFromTemplate.mock.calls[0]?.[0] as Array<{
190+
label?: string
191+
submenu?: Array<{ label?: string, type?: string, checked?: boolean }>
192+
}>
193+
const viewMenu = template.find(item => item.label === 'menu:view.label')
194+
const compactModeItem = viewMenu?.submenu?.find(
195+
item => item.label === 'menu:view.compactMode',
196+
)
197+
198+
expect(compactModeItem).toMatchObject({
199+
checked: true,
200+
type: 'checkbox',
201+
})
202+
})
203+
107204
it('does not include space navigation items in the app menu', async () => {
108205
const { createMainMenu } = await import('../main')
109206

src/main/menu/main.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ const defaultMainMenuContext: MainMenuContext = {
3434
view: {
3535
layoutMode: null,
3636
layoutModes: [],
37+
canToggleCompactMode: false,
3738
canToggleMindmap: false,
39+
isCompactMode: false,
3840
isMindmapShown: false,
3941
canTogglePresentation: false,
4042
isPresentationShown: false,
@@ -394,6 +396,19 @@ function createLayoutMenuItems(context: MainMenuContext): MenuConfig[] {
394396
function createViewMenuItems(context: MainMenuContext): MenuConfig[] {
395397
const items = createLayoutMenuItems(context)
396398

399+
if (context.view.canToggleCompactMode) {
400+
if (items.length) {
401+
items.push({ type: 'separator' })
402+
}
403+
404+
items.push({
405+
label: i18n.t('menu:view.compactMode'),
406+
type: 'checkbox',
407+
checked: context.view.isCompactMode,
408+
click: () => send('main-menu:toggle-compact-mode'),
409+
})
410+
}
411+
397412
if (context.view.canToggleMindmap || context.view.canTogglePresentation) {
398413
if (items.length) {
399414
items.push({ type: 'separator' })

src/main/types/ipc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type MainMenuAction =
2626
| 'set-layout-mode'
2727
| 'set-notes-editor-mode'
2828
| 'toggle-sidebar'
29+
| 'toggle-compact-mode'
2930
| 'update-context'
3031
| 'goto-math-notebook'
3132

src/main/types/menu.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface MainMenuFileContext {
1818
export interface MainMenuViewContext {
1919
layoutMode: MainMenuLayoutMode | null
2020
layoutModes: MainMenuLayoutMode[]
21+
canToggleCompactMode: boolean
22+
isCompactMode: boolean
2123
canToggleMindmap: boolean
2224
isMindmapShown: boolean
2325
canTogglePresentation: boolean

src/renderer/components/math-notebook/SheetList.vue

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<script setup lang="ts">
22
import * as ContextMenu from '@/components/ui/shadcn/context-menu'
3-
import { useMathNotebook } from '@/composables'
3+
import { useApp, useMathNotebook } from '@/composables'
44
import { i18n } from '@/electron'
55
import { format } from 'date-fns'
66
import { FileText, Plus } from 'lucide-vue-next'
77
8+
const { isCompactListMode } = useApp()
89
const {
910
sheets,
1011
activeSheetId,
@@ -105,21 +106,27 @@ function cancelRename() {
105106
@click.stop
106107
>
107108
<template v-else>
108-
<UiText
109-
as="div"
110-
variant="sm"
111-
class="truncate leading-tight"
109+
<div
110+
:class="isCompactListMode ? 'flex items-center gap-2' : ''"
112111
>
113-
{{ sheet.name }}
114-
</UiText>
115-
<UiText
116-
as="div"
117-
variant="caption"
118-
class="leading-tight transition-colors"
119-
muted
120-
>
121-
{{ format(new Date(sheet.updatedAt), "dd.MM.yyyy") }}
122-
</UiText>
112+
<UiText
113+
as="div"
114+
variant="sm"
115+
class="truncate leading-tight"
116+
:class="isCompactListMode ? 'flex-1' : ''"
117+
>
118+
{{ sheet.name }}
119+
</UiText>
120+
<UiText
121+
as="div"
122+
variant="caption"
123+
class="leading-tight transition-colors"
124+
:class="isCompactListMode ? 'shrink-0' : ''"
125+
muted
126+
>
127+
{{ format(new Date(sheet.updatedAt), "dd.MM.yyyy") }}
128+
</UiText>
129+
</div>
123130
</template>
124131
</div>
125132
</div>

src/renderer/components/notes/NotesList.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script setup lang="ts">
2-
import { useNotes, useNotesApp, useNoteSearch } from '@/composables'
2+
import { useApp, useNotes, useNotesApp, useNoteSearch } from '@/composables'
33
import { i18n } from '@/electron'
44
import { LoaderCircle } from 'lucide-vue-next'
55
66
const NOTE_ITEM_SIZE = 61
7+
const NOTE_ITEM_COMPACT_SIZE = 37
78
9+
const { isCompactListMode } = useApp()
810
const { notesState } = useNotesApp()
911
const { isNotesLoading, isNotesLoadingVisible } = useNotes()
1012
const { displayedNotes } = useNoteSearch()
@@ -13,6 +15,9 @@ const noteScrollerRef = ref<{
1315
scrollToItem: (index: number) => void
1416
} | null>(null)
1517
const isInitialPositionRestored = ref(false)
18+
const noteItemSize = computed(() =>
19+
isCompactListMode.value ? NOTE_ITEM_COMPACT_SIZE : NOTE_ITEM_SIZE,
20+
)
1621
1722
watch(
1823
[displayedNotes, () => notesState.noteId, noteScrollerRef],
@@ -57,7 +62,7 @@ watch(
5762
v-slot="{ item }"
5863
class="scrollbar flex-grow px-2"
5964
:items="displayedNotes"
60-
:item-size="NOTE_ITEM_SIZE"
65+
:item-size="noteItemSize"
6166
key-field="id"
6267
>
6368
<NotesListItem :note="item" />

src/renderer/components/notes/NotesListItem.vue

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import * as ContextMenu from '@/components/ui/shadcn/context-menu'
3-
import { useNotes, useNotesApp } from '@/composables'
3+
import { useApp, useNotes, useNotesApp } from '@/composables'
44
import { i18n } from '@/electron'
55
import { onClickOutside } from '@vueuse/core'
66
import { format } from 'date-fns'
@@ -34,6 +34,7 @@ interface Props {
3434
3535
const props = defineProps<Props>()
3636
37+
const { isCompactListMode } = useApp()
3738
const { highlightedNoteIds, highlightedFolderIds, focusedNoteId, notesState }
3839
= useNotesApp()
3940
@@ -135,13 +136,31 @@ onClickOutside(noteRef, () => {
135136
>
136137
<ContextMenu.ContextMenu>
137138
<ContextMenu.ContextMenuTrigger>
138-
<div class="flex flex-col p-2 select-none">
139+
<div
140+
class="select-none"
141+
:class="
142+
isCompactListMode
143+
? 'flex items-center gap-2 px-2 py-1.5'
144+
: 'flex flex-col p-2'
145+
"
146+
>
139147
<div
140-
class="mb-2 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"
148+
class="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"
149+
:class="isCompactListMode ? 'flex-1' : 'mb-2'"
141150
>
142151
{{ note.name || i18n.t("notes.untitled") }}
143152
</div>
144153
<UiText
154+
v-if="isCompactListMode"
155+
as="div"
156+
variant="xs"
157+
muted
158+
class="meta shrink-0"
159+
>
160+
{{ format(new Date(note.updatedAt), "dd.MM.yyyy") }}
161+
</UiText>
162+
<UiText
163+
v-else
145164
as="div"
146165
variant="xs"
147166
muted

src/renderer/components/snippet/Item.vue

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const props = defineProps<Props>()
1616
const {
1717
highlightedSnippetIds,
1818
highlightedFolderIds,
19+
isCompactListMode,
1920
isFocusedSnippetName,
2021
focusedSnippetId,
2122
state,
@@ -252,13 +253,31 @@ onClickOutside(snippetRef, () => {
252253
>
253254
<ContextMenu.ContextMenu>
254255
<ContextMenu.ContextMenuTrigger>
255-
<div class="flex flex-col p-2 select-none">
256+
<div
257+
class="select-none"
258+
:class="
259+
isCompactListMode
260+
? 'flex items-center gap-2 px-2 py-1.5'
261+
: 'flex flex-col p-2'
262+
"
263+
>
256264
<div
257-
class="mb-2 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"
265+
class="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"
266+
:class="isCompactListMode ? 'flex-1' : 'mb-2'"
258267
>
259268
{{ snippet.name || i18n.t("snippet.untitled") }}
260269
</div>
261270
<UiText
271+
v-if="isCompactListMode"
272+
as="div"
273+
variant="xs"
274+
muted
275+
class="meta shrink-0"
276+
>
277+
{{ format(new Date(snippet.createdAt), "dd.MM.yyyy") }}
278+
</UiText>
279+
<UiText
280+
v-else
262281
as="div"
263282
variant="xs"
264283
muted

0 commit comments

Comments
 (0)