diff --git a/src/ts/core/features/vim-mode/commands/panel-commands.ts b/src/ts/core/features/vim-mode/commands/panel-commands.ts index 8f9f4113..ed197e1b 100644 --- a/src/ts/core/features/vim-mode/commands/panel-commands.ts +++ b/src/ts/core/features/vim-mode/commands/panel-commands.ts @@ -5,10 +5,10 @@ import {RoamPanel} from 'src/core/features/vim-mode/roam/roam-panel' import {RoamBlock} from 'src/core/features/vim-mode/roam/roam-block' export const panelCommands = [ - nmap('h', 'Select Panel Left', () => RoamPanel.select('MAIN')), + nmap('h', 'Select Panel Left', () => RoamPanel.selectPreviousPanel()), nmap('l', 'Select Panel Right', () => { if (document.querySelector(Selectors.sidebarContent)) { - RoamPanel.select('SIDE') + RoamPanel.selectNextPanel() } }), map('Control+w', 'Close Page in Side Bar', () => { diff --git a/src/ts/core/features/vim-mode/roam/roam-panel.ts b/src/ts/core/features/vim-mode/roam/roam-panel.ts index 2a093ef5..94bf2bf6 100644 --- a/src/ts/core/features/vim-mode/roam/roam-panel.ts +++ b/src/ts/core/features/vim-mode/roam/roam-panel.ts @@ -5,20 +5,24 @@ import {assumeExists} from 'src/core/common/assert' import {RoamBlock, BlockElement, BlockId} from 'src/core/features/vim-mode/roam/roam-block' import {relativeItem} from 'src/core/common/array' + type BlockNavigationState = { - panels: Map - focusedPanel: PanelId + panels: Array + panelToBlock: Map + focusedPanel: PanelIndex } const state: BlockNavigationState = { - panels: new Map([ - ['MAIN', null], - ['SIDE', null], + panels: ['__MAIN_PANEL__'], + panelToBlock: new Map([ + ['__MAIN_PANEL__', null], ]), - focusedPanel: 'MAIN', + focusedPanel: 0, } -export type PanelId = 'MAIN' | 'SIDE' +type PanelId = '__MAIN_PANEL__' | PageName +type PageName = string +type PanelIndex = number type PanelElement = HTMLElement @@ -32,10 +36,10 @@ type PanelElement = HTMLElement * The generically reusable parts of this should probably move to core/roam */ export class RoamPanel { - private readonly panelId: PanelId + private readonly panelIndex: number - constructor(panelId: PanelId) { - this.panelId = panelId + constructor(panelIndex: number) { + this.panelIndex = panelIndex } private blocks = (): BlockElement[] => Array.from(this.element().querySelectorAll(Selectors.block)) @@ -47,7 +51,7 @@ export class RoamPanel { } selectedBlockId(): BlockId { - const blockId = state.panels.get(this.panelId) + const blockId = state.panelToBlock.get(this.panelId()) if (!blockId || !document.getElementById(blockId)) { // Fallback to selecting the first block, @@ -65,13 +69,13 @@ export class RoamPanel { } selectBlock(blockId: string) { - state.panels.set(this.panelId, blockId) + state.panelToBlock.set(this.panelId(), blockId) } selectRelativeBlock(blocksToJump: number) { const block = this.selectedBlock().element() this.selectBlock(this.relativeBlockId(block.id, blocksToJump)) - this.scrollUntilBlockIsVisible(block) + this.scrollUntilBlockIsVisible(this.selectedBlock().element()) } scrollUntilBlockIsVisible(block: BlockElement) { @@ -79,28 +83,52 @@ export class RoamPanel { } element(): PanelElement { - if (this.panelId === 'SIDE') { - return assumeExists(document.querySelector(Selectors.sidebarContent) as HTMLElement) - } else { + // 0 means the main panel + if (this.panelIndex === 0) { const articleElement = assumeExists(document.querySelector(Selectors.mainContent)) return assumeExists(articleElement.parentElement) } + + const sidePanel = assumeExists(document.querySelector(Selectors.sidebarContent) as HTMLElement) + return assumeExists(sidePanel.children[this.panelIndex - 1] as HTMLElement) } firstBlockId(): BlockId { return assumeExists(this.element().querySelector(Selectors.block)).id } - static get(panelId: PanelId): RoamPanel { - return new RoamPanel(panelId) + private panelId(): PanelId { + return state.panels[this.panelIndex] } static selected(): RoamPanel { - return this.get(state.focusedPanel) + // Select the next closest panel when closing the last panel + state.focusedPanel = Math.min(state.focusedPanel, state.panels.length - 1) + return new RoamPanel(state.focusedPanel) } - static select(panelId: PanelId) { - state.focusedPanel = panelId + static selectPanel(panelIndex: PanelIndex) { + state.focusedPanel = panelIndex + this.selected().element().scrollIntoView({ behavior: 'smooth' }) + } + + static selectPreviousPanel() { + this.selectPanel(Math.max(state.focusedPanel - 1, 0)) + } + + static selectNextPanel() { + this.selectPanel(Math.min(state.focusedPanel + 1, state.panels.length - 1)) + } + + static updateSidePanels() { + const sidePanel = document.querySelector(Selectors.sidebarContent) + if (sidePanel) { + const sidePanelPages = Array.from(assumeExists(sidePanel.children)) + const sidePanelPageNames = sidePanelPages.map(page => assumeExists(page.querySelector('h1')).innerText) + state.panels = ['__MAIN_PANEL__'].concat(sidePanelPageNames) + } else { + state.panels = ['__MAIN_PANEL__'] + } } scrollAndReselectBlockToStayVisible(scrollPx: number) { diff --git a/src/ts/core/features/vim-mode/vim-init.ts b/src/ts/core/features/vim-mode/vim-init.ts index f86013af..c4f4c2f6 100644 --- a/src/ts/core/features/vim-mode/vim-init.ts +++ b/src/ts/core/features/vim-mode/vim-init.ts @@ -19,7 +19,11 @@ export const initializeBlockNavigationMode = async () => { // Select block when clicked RoamEvent.onFocusBlock(blockElement => { - RoamPanel.select(blockElement.closest(Selectors.mainContent) ? 'MAIN' : 'SIDE') + if (blockElement.closest(Selectors.mainContent)) { + RoamPanel.selectPreviousPanel() + } else { + RoamPanel.selectNextPanel() + } RoamPanel.selected().selectBlock(blockElement.id) updateBlockNavigationView() }) @@ -30,12 +34,16 @@ export const initializeBlockNavigationMode = async () => { // Re-select main panel block after the closing right panel RoamEvent.onRightPanelToggle(isRightPanelOn => { if (!isRightPanelOn) { - RoamPanel.select('MAIN') + RoamPanel.selectPreviousPanel() } + RoamPanel.updateSidePanels() updateBlockNavigationView() }) // Select first block in right panel when closing pages in right panel - RoamEvent.onRightPanelChange(updateBlockNavigationView) + RoamEvent.onRightPanelChange(() => { + RoamPanel.updateSidePanels() + updateBlockNavigationView() + }) // Select first block when switching pages RoamEvent.onChangePage(updateBlockNavigationView)