Files
outline/app/hooks/useEditorClickHandlers.ts
T
Hemachandar d3eb3db7ba feat: Public sharing of collections (#9529)
* shares.info, collections.info, documents.info

* shares.list, shares.create, shares.update

* shares.sitemap

* parity with existing document shared screen

* collection share popover

* parent share and table

* collection scene

* collection link in sidebar

* sidebar and breadcrumb collection link click

* collection link click in editor

* meta

* more meta + 404 page

* map internal link, remove showLastUpdated option

* fix shares.list pagination

* show last updated

* shareLoader tests

* lint

* sidebar context for collection link

* badge in shares table

* fix existing tests

* tsc

* update failing test snapshot

* env

* signed url for collection attachments

* include collection content in SSR for screen readers

* search

* drafts can be shared

* review

* tsc, remove old shared-doc scene

* tweaks

* DRY

* refactor loader

* Remove share/collection urls

* fix: Collection overview should not be editable when viewing shared link and logged in

* Tweak public breadcrumb

* fix: Deleted documents should never be exposed through share

* empty sharedTree array where includeChildDocuments is false

* revert includeChildDocs guard for logical correctness + SSR bug fix

* fix: check document is part of share

---------

Co-authored-by: Tom Moor <tom@getoutline.com>
2025-08-03 13:07:39 -04:00

89 lines
2.6 KiB
TypeScript

import { useCallback } from "react";
import { useHistory } from "react-router-dom";
import { isModKey } from "@shared/utils/keyboard";
import { isDocumentUrl, isInternalUrl } from "@shared/utils/urls";
import { sharedModelPath } from "~/utils/routeHelpers";
import { isHash } from "~/utils/urls";
import useStores from "./useStores";
type Params = {
/** The share ID of the document being viewed, if any */
shareId?: string;
};
export default function useEditorClickHandlers({ shareId }: Params) {
const history = useHistory();
const { documents } = useStores();
const handleClickLink = useCallback(
(href: string, event?: MouseEvent) => {
// on page hash
if (isHash(href)) {
window.location.href = href;
return;
}
let navigateTo = href;
if (isInternalUrl(href)) {
// probably absolute
if (href[0] !== "/") {
try {
const url = new URL(href);
navigateTo = url.pathname + url.hash;
} catch (_err) {
navigateTo = href;
}
}
// Link to our own API should be opened in a new tab, not in the app
if (navigateTo.startsWith("/api/")) {
window.open(href, "_blank");
return;
}
// parse shareId from link
const linkShareId = navigateTo.match(/\/s\/([^/]+)\/doc\//)?.[1];
// If we're navigating to an internal document link then prepend the
// share route to the URL so that the document is loaded in context
if (
shareId &&
(!linkShareId || linkShareId === shareId) &&
(navigateTo.includes("/doc/") ||
navigateTo.includes("/collection/")) &&
!navigateTo.includes(shareId)
) {
navigateTo = sharedModelPath(shareId, navigateTo);
}
if (isDocumentUrl(navigateTo)) {
const document = documents.getByUrl(navigateTo);
if (document) {
navigateTo = document.path;
}
}
// If we're navigating to a share link from a non-share link then open it in a new tab
if (!shareId && navigateTo.startsWith("/s/")) {
window.open(href, "_blank");
return;
}
if (
!event ||
(!isModKey(event) && !event.shiftKey && event.button !== 1)
) {
history.push(navigateTo, { sidebarContext: "collections" }); // optimistic preference of "collections"
} else {
window.open(navigateTo, "_blank");
}
} else {
window.open(href, "_blank");
}
},
[history, shareId]
);
return { handleClickLink };
}