feat(ui): browser back and forward step through the app
The app's place (page, open note, workspace view, zoomed bullet, on phones list or note) goes into the browser history (lib/navHistory.ts), so the back button of the mouse, the browser or Android steps back inside the app: out of a zoom, to the previous note, to the previous page. Changes from one action become one entry, the app's own choice of a first note replaces the entry, and following a step adds nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b711730a7
commit
1eef388fa5
5 files changed
+185
-6
No files matched your search
@@ -62,6 +62,7 @@ import { EmojiIconButton } from '../../components/EmojiIconButton';
|
||||
import { LazyMount } from '../../components/LazyMount';
|
||||
import { extractHashtags } from '../../lib/hashtags';
|
||||
import { isPhoneWidth } from '../../lib/breakpoints';
|
||||
import { reportNav, onNavStep, restoringNavPlace } from '../../lib/navHistory';
|
||||
|
||||
interface WorkspaceViewProps {
|
||||
currentUser?: User | null;
|
||||
@@ -333,14 +334,23 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
return list.sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
|
||||
}, [docs, serverTagsSummary]);
|
||||
|
||||
// Coming back here by the browser's back/forward: the note and view of that step
|
||||
const navStep = useMemo(() => {
|
||||
const place = restoringNavPlace();
|
||||
return place && place.tab === 'workspace' ? place : null;
|
||||
}, []);
|
||||
const [selectedDocId, setSelectedDocId] = useState<string | null>(
|
||||
initialDocId || readWorkspaceSession().docId || null
|
||||
navStep ? navStep.docId ?? null : initialDocId || readWorkspaceSession().docId || null
|
||||
);
|
||||
const [targetBlockId, setTargetBlockId] = useState<string | null>(initialBlockId || null);
|
||||
const [newlyCreatedDocId, setNewlyCreatedDocId] = useState<string | null>(null);
|
||||
// Journal open and the last regular note survive switching to other tabs (the view remounts)
|
||||
const [subView, setSubView] = useState<WorkspaceSubView>(() =>
|
||||
!initialDocId && isJournalEnabled() && readWorkspaceSession().journal ? 'journal' : 'editor'
|
||||
navStep?.view
|
||||
? (navStep.view as WorkspaceSubView)
|
||||
: !initialDocId && isJournalEnabled() && readWorkspaceSession().journal
|
||||
? 'journal'
|
||||
: 'editor'
|
||||
);
|
||||
const [trash, setTrash] = useState<TrashedDocument[]>([]);
|
||||
// Bumped after a restore so the open editor reloads the note without the trash banner
|
||||
@@ -418,7 +428,24 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
setShowNewFolderModal(true);
|
||||
};
|
||||
const [mobileShowDetail, setMobileShowDetail] = useState<boolean>(
|
||||
Boolean(initialDocId) || subView === 'journal'
|
||||
navStep ? Boolean(navStep.detail) : Boolean(initialDocId) || subView === 'journal'
|
||||
);
|
||||
// Browser back/forward: the open note and view (and on phones list or note) are steps of their own
|
||||
const autoSelectedDocRef = useRef(false);
|
||||
useEffect(() => {
|
||||
const replace = autoSelectedDocRef.current;
|
||||
autoSelectedDocRef.current = false;
|
||||
reportNav({ docId: selectedDocId, view: subView, detail: isPhoneWidth() ? mobileShowDetail : undefined }, { replace });
|
||||
}, [selectedDocId, subView, mobileShowDetail]);
|
||||
useEffect(
|
||||
() =>
|
||||
onNavStep((place) => {
|
||||
if (place.tab !== 'workspace') return;
|
||||
setSelectedDocId(place.docId ?? null);
|
||||
if (place.view) setSubView(place.view as WorkspaceSubView);
|
||||
if (isPhoneWidth()) setMobileShowDetail(Boolean(place.detail));
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState<boolean>(() => {
|
||||
try {
|
||||
@@ -525,6 +552,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
if (prevId && docsRes.some((d) => d.id === prevId)) {
|
||||
return prevId;
|
||||
}
|
||||
autoSelectedDocRef.current = true; // chosen by the app, not a step of its own in the history
|
||||
// Not a journal day: those open in the journal
|
||||
const first = docsRes.find((d) => !(d.isOwner && journalIds.has(d.id)));
|
||||
return first ? first.id : null;
|
||||
|
||||
Reference in new issue
Block a user