diff --git a/src/routes/LinkDetailPage.tsx b/src/routes/LinkDetailPage.tsx
index f7dc947..812711e 100644
--- a/src/routes/LinkDetailPage.tsx
+++ b/src/routes/LinkDetailPage.tsx
@@ -63,6 +63,31 @@ const PERIODS: { id: StatsPeriod; label: string }[] = [
];
const COPY_SUCCESS_NOTICE_KEY = 'heygo.copy-success-notice';
+const EDIT_QUERY_PARAM = 'edit';
+
+function isEditRequestedInHash(): boolean {
+ const hash = window.location.hash;
+ const queryStart = hash.indexOf('?');
+ if (queryStart === -1) return false;
+ return new URLSearchParams(hash.slice(queryStart + 1)).has(EDIT_QUERY_PARAM);
+}
+
+function setEditInHash(edit: boolean) {
+ const hash = window.location.hash || '#/';
+ const queryStart = hash.indexOf('?');
+ const path = queryStart === -1 ? hash : hash.slice(0, queryStart);
+ const params = new URLSearchParams(queryStart === -1 ? '' : hash.slice(queryStart + 1));
+ if (edit) {
+ params.set(EDIT_QUERY_PARAM, '1');
+ } else {
+ params.delete(EDIT_QUERY_PARAM);
+ }
+ const query = params.toString();
+ const nextHash = query ? `${path}?${query}` : path;
+ if (window.location.hash !== nextHash) {
+ window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}${nextHash}`);
+ }
+}
export default function LinkDetailPage({ linkId, linkScope, currentUser, linkBaseUrls, onBack }: LinkDetailPageProps) {
const [link, setLink] = useState(null);
@@ -80,7 +105,7 @@ export default function LinkDetailPage({ linkId, linkScope, currentUser, linkBas
const [shortLinkNotice, setShortLinkNotice] = useState(null);
const [showPropose, setShowPropose] = useState(false);
const [proposeNotice, setProposeNotice] = useState(null);
- const [editing, setEditing] = useState(false);
+ const [editing, setEditing] = useState(() => isEditRequestedInHash());
const [editInput, setEditInput] = useState(null);
const [editError, setEditError] = useState(null);
const [saving, setSaving] = useState(false);
@@ -105,6 +130,20 @@ export default function LinkDetailPage({ linkId, linkScope, currentUser, linkBas
});
}, [linkId, linkScope]);
+ // Seed the edit form when arriving with ?edit in the URL and the link has
+ // just finished loading (covers a page refresh while editing).
+ useEffect(() => {
+ if (editing && link && !editInput) {
+ setEditInput({
+ alias: link.alias,
+ linkType: link.linkType,
+ targetUrl: link.targetUrl ?? '',
+ contentMarkdown: link.contentMarkdown ?? '',
+ description: link.description ?? '',
+ });
+ }
+ }, [editing, link, editInput]);
+
useEffect(() => {
setCopyError(null);
setShowCopyDialog(false);
@@ -114,12 +153,21 @@ export default function LinkDetailPage({ linkId, linkScope, currentUser, linkBas
useEffect(() => {
setShowPropose(false);
setProposeNotice(null);
- setEditing(false);
+ const editRequested = isEditRequestedInHash();
+ setEditing(editRequested);
setEditInput(null);
setEditError(null);
setTemplateValues({});
}, [linkId, linkScope]);
+ // Keep editing state in sync when the hash query changes (e.g. browser
+ // back/forward between ?edit and the plain detail URL) without remounting.
+ useEffect(() => {
+ const onHashChange = () => setEditing(isEditRequestedInHash());
+ window.addEventListener('hashchange', onHashChange);
+ return () => window.removeEventListener('hashchange', onHashChange);
+ }, []);
+
useEffect(() => {
if (!shortLinkNotice) {
return;
@@ -388,6 +436,7 @@ export default function LinkDetailPage({ linkId, linkScope, currentUser, linkBas
contentMarkdown: link.contentMarkdown ?? '',
description: link.description ?? '',
});
+ setEditInHash(true);
setEditing(true);
}
@@ -411,6 +460,7 @@ export default function LinkDetailPage({ linkId, linkScope, currentUser, linkBas
? await updatePrivateLink(link.id, payload)
: await updatePublicLink(link.id, payload);
setLink(result.link);
+ setEditInHash(false);
setEditing(false);
setEditInput(null);
} catch (err) {
@@ -593,7 +643,7 @@ export default function LinkDetailPage({ linkId, linkScope, currentUser, linkBas
-