fix(ui): preserve upstream sidebar and selection behavior
This commit is contained in:
@@ -445,6 +445,52 @@ describe('MarkdownRenderer DOM mount performance contract', () => {
|
|||||||
clearDetachedMarkdownDomCache();
|
clearDetachedMarkdownDomCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('does not detach Markdown DOM that intersects the active selection', async () => {
|
||||||
|
clearDetachedMarkdownDomCache();
|
||||||
|
const content = 'selected content';
|
||||||
|
const host = document.createElement('div');
|
||||||
|
document.body.replaceChildren(host);
|
||||||
|
const root = createRoot(host);
|
||||||
|
await act(async () => {
|
||||||
|
root.render(
|
||||||
|
<MarkdownRenderer
|
||||||
|
content={content}
|
||||||
|
messageId="message-selected"
|
||||||
|
part={{
|
||||||
|
id: 'part-selected',
|
||||||
|
sessionID: 'session-selected',
|
||||||
|
messageID: 'message-selected',
|
||||||
|
type: 'text',
|
||||||
|
text: content,
|
||||||
|
time: { start: 0, end: 1 },
|
||||||
|
}}
|
||||||
|
isAnimated={false}
|
||||||
|
enableFileReferences={false}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
await waitForSettledEffects();
|
||||||
|
});
|
||||||
|
const markdown = host.querySelector<HTMLElement>('[data-markdown-content]');
|
||||||
|
if (!markdown) throw new Error('Expected mounted Markdown content');
|
||||||
|
const originalGetSelection = window.getSelection;
|
||||||
|
Object.defineProperty(window, 'getSelection', {
|
||||||
|
configurable: true,
|
||||||
|
value: () => ({
|
||||||
|
rangeCount: 1,
|
||||||
|
isCollapsed: false,
|
||||||
|
getRangeAt: () => ({ intersectsNode: (node: Node) => node === markdown }),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await act(async () => root.unmount());
|
||||||
|
expect(detachedMarkdownDomCacheStats().entries).toBe(0);
|
||||||
|
} finally {
|
||||||
|
Object.defineProperty(window, 'getSelection', { configurable: true, value: originalGetSelection });
|
||||||
|
clearDetachedMarkdownDomCache();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('defers and batches Mermaid controller initialization after Markdown mount', async () => {
|
test('defers and batches Mermaid controller initialization after Markdown mount', async () => {
|
||||||
const mounted = await mountFixture(fixtureWorkload.rendererCount);
|
const mounted = await mountFixture(fixtureWorkload.rendererCount);
|
||||||
const critical = mounted.counts;
|
const critical = mounted.counts;
|
||||||
|
|||||||
@@ -843,6 +843,8 @@ const useMorphdomMarkdown = ({
|
|||||||
if (target.childNodes.length === 0 || shouldRefreshMermaidViewers(target)) return;
|
if (target.childNodes.length === 0 || shouldRefreshMermaidViewers(target)) return;
|
||||||
if (Array.from(target.children).some((block) => !block.hasAttribute('data-md-id'))) return;
|
if (Array.from(target.children).some((block) => !block.hasAttribute('data-md-id'))) return;
|
||||||
if (target.querySelector('[data-md-copy-pending]')) return;
|
if (target.querySelector('[data-md-copy-pending]')) return;
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection?.rangeCount && !selection.isCollapsed && selection.getRangeAt(0).intersectsNode(target)) return;
|
||||||
const openMenu = target.querySelector<HTMLElement>('[data-md-menu]:not(.hidden)');
|
const openMenu = target.querySelector<HTMLElement>('[data-md-menu]:not(.hidden)');
|
||||||
const copiedButton = Array.from(target.querySelectorAll<HTMLButtonElement>('[data-md-action]'))
|
const copiedButton = Array.from(target.querySelectorAll<HTMLButtonElement>('[data-md-action]'))
|
||||||
.some((button) => button.getAttribute('title') === mountedDom.copiedLabel);
|
.some((button) => button.getAttribute('title') === mountedDom.copiedLabel);
|
||||||
|
|||||||
@@ -55,4 +55,14 @@ describe('selectFolderIdsForProjection', () => {
|
|||||||
expect([...selectFolderIdsForProjection(folders, { archivedBucket: true, searchQuery: 'matching' })])
|
expect([...selectFolderIdsForProjection(folders, { archivedBucket: true, searchQuery: 'matching' })])
|
||||||
.toEqual(['root', 'child']);
|
.toEqual(['root', 'child']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('keeps a fuzzy folder match and its ancestor', () => {
|
||||||
|
const folders = [
|
||||||
|
{ id: 'root', name: 'Root', parentId: null, nodeCount: 0 },
|
||||||
|
{ id: 'child', name: 'Release Notes', parentId: 'root', nodeCount: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect([...selectFolderIdsForProjection(folders, { archivedBucket: false, searchQuery: 'release-notes' })])
|
||||||
|
.toEqual(['root', 'child']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getRuntimeKey } from '@/lib/runtime-switch';
|
import { getRuntimeKey } from '@/lib/runtime-switch';
|
||||||
|
import { matchesRankQuery } from '@/lib/search/fuzzySearch';
|
||||||
import { normalizePath } from '@/lib/pathNormalization';
|
import { normalizePath } from '@/lib/pathNormalization';
|
||||||
import { resolveGlobalSessionDirectory } from '@/stores/useGlobalSessionsStore';
|
import { resolveGlobalSessionDirectory } from '@/stores/useGlobalSessionsStore';
|
||||||
import { getPinnedSessionKey } from '@/stores/useSessionPinnedStore';
|
import { getPinnedSessionKey } from '@/stores/useSessionPinnedStore';
|
||||||
@@ -227,7 +228,7 @@ export const selectFolderIdsForProjection = (
|
|||||||
keep = (childIdsByParentId.get(folderId) ?? []).some(shouldKeep);
|
keep = (childIdsByParentId.get(folderId) ?? []).some(shouldKeep);
|
||||||
} else {
|
} else {
|
||||||
if (!keep && !options.searchQuery) keep = true;
|
if (!keep && !options.searchQuery) keep = true;
|
||||||
if (!keep && (entry.nodeCount > 0 || entry.name.toLowerCase().includes(options.searchQuery))) keep = true;
|
if (!keep && (entry.nodeCount > 0 || matchesRankQuery([entry.name], options.searchQuery))) keep = true;
|
||||||
if (!keep) keep = (childIdsByParentId.get(folderId) ?? []).some(shouldKeep);
|
if (!keep) keep = (childIdsByParentId.get(folderId) ?? []).some(shouldKeep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user