Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/mainPage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ export async function refreshUI (outliner: OutlineManager) {
// Folder explorers adapt through CSS. Recreating one from the page's
// history entry discards its internal resource/pane selection on resize.
const themeUnchanged = currentSignature.split('|')[1] === previousSignature.split('|')[1]
if (envChanged && themeUnchanged && document.querySelector('#MainContent folder-pane-view')) {
const preserveExplorer = document.querySelector(
'#MainContent folder-pane-view, #MainContent [data-preserve-pane-on-resize="true"]'
)
if (envChanged && themeUnchanged && preserveExplorer) {
if (activeExplorerRefreshPending) return
activeExplorerRefreshPending = true
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()))
Expand Down
2 changes: 2 additions & 0 deletions src/outline/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@

// Represent an object in summary form as a content block

function appendRemoveIcon (node, subject, removeNode) {

Check warning on line 134 in src/outline/manager.js

View workflow job for this annotation

GitHub Actions / build (24)

'appendRemoveIcon' is defined but never used

Check warning on line 134 in src/outline/manager.js

View workflow job for this annotation

GitHub Actions / build (22)

'appendRemoveIcon' is defined but never used
const image = UI.utils.AJARImage(
outlineIcons.src.icon_remove_node,
'remove',
Expand Down Expand Up @@ -313,6 +313,8 @@

const currentPane = provider.querySelector(':scope > .paneDiv')
if (!currentPane) continue
if (currentPane.matches('[data-preserve-pane-on-resize="true"]') ||
currentPane.querySelector('[data-preserve-pane-on-resize="true"]')) continue
// Re-rendering the folder shell recreates its view and loses the resource
// selected inside it. Nested providers can refresh their active resource
// pane without replacing the shell.
Expand Down
25 changes: 25 additions & 0 deletions test/unit/mainPage/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,31 @@ describe('browser navigation without a reload', () => {
expect(GotoSubject).toHaveBeenCalledOnce()
})

it('refreshes explorers instead of replacing the page during an active edit', async () => {
const GotoSubject = await setupLoaded()
const main = document.createElement('main')
main.id = 'MainContent'
main.innerHTML = '<div data-preserve-pane-on-resize="true"></div>'
document.body.append(main)
const outliner = getOutliner()
const refresh = vi.fn()
outliner.refreshActiveExplorerPanes = refresh
outliner.__lastRenderEnvSignature = 'desktop|light|pointer|1280|800'
outliner.context = {
session: { store },
environment: { ...environment, layout: 'mobile', viewport: { width: 320, height: 800 } }
}

await refreshUI(outliner)

expect(refresh).toHaveBeenCalledOnce()
expect(GotoSubject).not.toHaveBeenCalled()

outliner.context.environment = { ...outliner.context.environment, theme: 'dark' }
await refreshUI(outliner)
expect(GotoSubject).toHaveBeenCalledOnce()
})

it('falls back to popstate when the Navigation API is unavailable', async () => {
Reflect.deleteProperty(window, 'navigation')
vi.resetModules()
Expand Down
31 changes: 31 additions & 0 deletions test/unit/outline/manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,37 @@ describe('manager', () => {
expect(provider.pane).toBe(pane)
})

it.each([false, true])('preserves an editing pane and refreshes unrelated panes (nested marker: %s)', async nested => {
const pane = { ...MockPane, render: vi.fn(MockPane.render) }
const manager = new OutlineManager({
dom: document,
session: { paneRegistry: { list: [pane], byName: findPane }, store },
environment: { layout: 'desktop' }
})
const editing = manager.propertyTable(container, undefined, pane, {})
const unrelated = manager.propertyTable(sym(`${window.location.origin}/other.ttl`), undefined, pane, {})
document.body.append(editing, unrelated)
await vi.waitFor(() => expect(unrelated.querySelector('.paneDiv')).not.toBeNull())
const current = editing.querySelector<HTMLElement>('.paneDiv')!
const other = unrelated.querySelector('.paneDiv')
const marker = nested ? current.appendChild(document.createElement('div')) : current
marker.setAttribute('data-preserve-pane-on-resize', 'true')
const draft = current.appendChild(document.createElement('textarea'))
draft.value = 'unsaved draft'
pane.render.mockClear()

manager.refreshActiveExplorerPanes()

expect(editing.querySelector('.paneDiv')).toBe(current)
expect(draft.value).toBe('unsaved draft')
expect(unrelated.querySelector('.paneDiv')).not.toBe(other)
expect(pane.render).toHaveBeenCalledOnce()

marker.removeAttribute('data-preserve-pane-on-resize')
manager.refreshActiveExplorerPanes()
expect(editing.querySelector('.paneDiv')).not.toBe(current)
})

it('does not re-render the folder shell while refreshing a nested resource pane', async () => {
const folderPane = { ...MockPane, name: 'folder', render: vi.fn(MockPane.render) }
const subject = sym(`${window.location.origin}/storage/`)
Expand Down
Loading