diff --git a/src/application/i18n/messages/en.json b/src/application/i18n/messages/en.json index 5c39816e..1716f503 100644 --- a/src/application/i18n/messages/en.json +++ b/src/application/i18n/messages/en.json @@ -49,6 +49,27 @@ "availabilityTitle": "Availability", "availabilityCaption": "Should the Note be available by its URL for people who knows it?", "availabilityRowTitle": "Note is published", + "appearance": "Appearance", + "sidebar": { + "title": "Sidebar", + "caption": "Choose how the sidebar is shown for this Note", + "hint": "Applies to this note for everyone who opens it, including readers of the published version.", + "parentOnly": "Sidebar settings are only available in the parent note's settings.", + "options": { + "none": { + "title": "No sidebar", + "subtitle": "Content only, full window" + }, + "edge": { + "title": "Pinned to page edge", + "subtitle": "Sidebar sticks to the left of the window" + }, + "content": { + "title": "Pinned to content", + "subtitle": "Sidebar follows the centered column" + } + } + }, "inviteCollaboratorTitle": "Invite a collaborator", "inviteCollaboratorCaption": "Send this link to someone you want to add as an editor or reader.", "revokeHashButton": "Revoke", diff --git a/src/application/services/useNote.ts b/src/application/services/useNote.ts index 75ba92d5..60ac55d9 100644 --- a/src/application/services/useNote.ts +++ b/src/application/services/useNote.ts @@ -200,14 +200,22 @@ export default function (options: UseNoteComposableOptions): UseNoteComposableSt */ async function load(id: NoteId): Promise { try { - const response = await noteService.getNoteById(id); + /** + * Load the note and its hierarchy in parallel so the sidebar position + * is known before the page is rendered, preventing a layout flash + */ + const [response] = await Promise.all([ + noteService.getNoteById(id), + getNoteHierarchy(id).catch(() => { + noteHierarchy.value = null; + }), + ]); note.value = response.note; canEdit.value = response.accessRights.canEdit; noteTools.value = response.tools; parentNote.value = response.parentNote; noteParents.value = response.parents; - void getNoteHierarchy(id); } catch (error) { deleteOpenedPageByUrl(route.path); if (error instanceof DomainError) { diff --git a/src/application/services/useNoteSettings.ts b/src/application/services/useNoteSettings.ts index bd488b3a..b2722ce4 100644 --- a/src/application/services/useNoteSettings.ts +++ b/src/application/services/useNoteSettings.ts @@ -1,5 +1,6 @@ import { ref, type Ref } from 'vue'; import type NoteSettings from '@/domain/entities/NoteSettings'; +import type { SidebarPosition } from '@/domain/entities/NoteSettings'; import type { Note, NoteId } from '@/domain/entities/Note'; import { noteSettingsService, noteService } from '@/domain'; import type { UserId } from '@/domain/entities/User'; @@ -33,6 +34,13 @@ interface UseNoteSettingsComposableState { */ updateIsPublic: (id: NoteId, newIsPublicValue: boolean) => Promise; + /** + * Update sidebar position in note settings + * @param id - note id + * @param sidebarPosition - new sidebar position + */ + updateSidebarPosition: (id: NoteId, sidebarPosition: SidebarPosition) => Promise; + /** * Revoke invitation hash * @param id - note id @@ -124,6 +132,17 @@ export default function (): UseNoteSettingsComposableState { } } + /** + * Update sidebar position in note settings + * @param id - Note id + * @param sidebarPosition - new sidebar position + */ + async function updateSidebarPosition(id: NoteId, sidebarPosition: SidebarPosition): Promise { + const response = await noteSettingsService.patchNoteSettingsByNoteId(id, { sidebarPosition }); + + noteSettings.value = response; + } + /** * Revoke invitation hash * @param id - Note id @@ -213,6 +232,7 @@ export default function (): UseNoteSettingsComposableState { noteSettings, load, updateIsPublic, + updateSidebarPosition, revokeHash, changeRole, deleteNoteById, diff --git a/src/domain/entities/NoteHierarchy.ts b/src/domain/entities/NoteHierarchy.ts index 9fc178c6..8d6b78f4 100644 --- a/src/domain/entities/NoteHierarchy.ts +++ b/src/domain/entities/NoteHierarchy.ts @@ -1,4 +1,5 @@ import { type NoteId } from './Note'; +import type { SidebarPosition } from './NoteSettings'; /** * Note Tree entity @@ -15,6 +16,11 @@ export interface NoteHierarchy { */ noteTitle: string; + /** + * Position of the sidebar relative to the note content + */ + sidebarPosition: SidebarPosition; + /** * child notes */ diff --git a/src/domain/entities/NoteSettings.ts b/src/domain/entities/NoteSettings.ts index 93b1551b..d3fe570a 100644 --- a/src/domain/entities/NoteSettings.ts +++ b/src/domain/entities/NoteSettings.ts @@ -5,6 +5,11 @@ import type { Team } from './Team'; */ export type InvitationHash = string; +/** + * Position of the sidebar relative to the note content + */ +export type SidebarPosition = 'content' | 'edge' | 'none'; + /** * NoteSettings entity */ @@ -38,4 +43,9 @@ export default interface NoteSettings { * Note cover image id */ cover: string; + + /** + * Position of the sidebar, 'content' by default + */ + sidebarPosition: SidebarPosition; } diff --git a/src/presentation/components/noteSettings/NoteSidebarSetting.vue b/src/presentation/components/noteSettings/NoteSidebarSetting.vue new file mode 100644 index 00000000..1c55c0b2 --- /dev/null +++ b/src/presentation/components/noteSettings/NoteSidebarSetting.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/src/presentation/pages/Note.vue b/src/presentation/pages/Note.vue index d04c68a5..66fbff2a 100644 --- a/src/presentation/pages/Note.vue +++ b/src/presentation/pages/Note.vue @@ -44,11 +44,16 @@
-