mirror of
https://github.com/outline/outline.git
synced 2025-12-20 10:09:43 -06:00
chore: Refactor useActionContext to use React context (#10140)
* chore: Refactor useActionContext to use React context * Self review * PR feedback
This commit is contained in:
@@ -27,8 +27,8 @@ export const createApiKey = createAction({
|
||||
|
||||
export const revokeApiKeyFactory = ({ apiKey }: { apiKey: ApiKey }) =>
|
||||
createActionV2({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu
|
||||
name: ({ t, isMenu }) =>
|
||||
isMenu
|
||||
? apiKey.isExpired
|
||||
? t("Delete")
|
||||
: `${t("Revoke")}…`
|
||||
|
||||
@@ -81,8 +81,7 @@ export const createCollection = createAction({
|
||||
});
|
||||
|
||||
export const editCollection = createActionV2({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu ? `${t("Edit")}…` : t("Edit collection"),
|
||||
name: ({ t, isMenu }) => (isMenu ? `${t("Edit")}…` : t("Edit collection")),
|
||||
analyticsName: "Edit collection",
|
||||
section: ActiveCollectionSection,
|
||||
icon: <EditIcon />,
|
||||
@@ -107,8 +106,8 @@ export const editCollection = createActionV2({
|
||||
});
|
||||
|
||||
export const editCollectionPermissions = createActionV2({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu ? `${t("Permissions")}…` : t("Collection permissions"),
|
||||
name: ({ t, isMenu }) =>
|
||||
isMenu ? `${t("Permissions")}…` : t("Collection permissions"),
|
||||
analyticsName: "Collection permissions",
|
||||
section: ActiveCollectionSection,
|
||||
icon: <PadlockIcon />,
|
||||
|
||||
@@ -384,8 +384,8 @@ export const subscribeDocument = createActionV2({
|
||||
analyticsName: "Subscribe to document",
|
||||
section: ActiveDocumentSection,
|
||||
icon: <SubscribeIcon />,
|
||||
tooltip: ({ activeCollectionId, isContextMenu, stores, t }) => {
|
||||
if (!isContextMenu || !activeCollectionId) {
|
||||
tooltip: ({ activeCollectionId, isMenu, stores, t }) => {
|
||||
if (!isMenu || !activeCollectionId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -393,8 +393,8 @@ export const subscribeDocument = createActionV2({
|
||||
? t("Subscription inherited from collection")
|
||||
: undefined;
|
||||
},
|
||||
disabled: ({ activeCollectionId, isContextMenu, stores }) => {
|
||||
if (!isContextMenu || !activeCollectionId) {
|
||||
disabled: ({ activeCollectionId, isMenu, stores }) => {
|
||||
if (!isMenu || !activeCollectionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -430,8 +430,8 @@ export const unsubscribeDocument = createActionV2({
|
||||
analyticsName: "Unsubscribe from document",
|
||||
section: ActiveDocumentSection,
|
||||
icon: <UnsubscribeIcon />,
|
||||
tooltip: ({ activeCollectionId, isContextMenu, stores, t }) => {
|
||||
if (!isContextMenu || !activeCollectionId) {
|
||||
tooltip: ({ activeCollectionId, isMenu, stores, t }) => {
|
||||
if (!isMenu || !activeCollectionId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -439,8 +439,8 @@ export const unsubscribeDocument = createActionV2({
|
||||
? t("Subscription inherited from collection")
|
||||
: undefined;
|
||||
},
|
||||
disabled: ({ activeCollectionId, isContextMenu, stores }) => {
|
||||
if (!isContextMenu || !activeCollectionId) {
|
||||
disabled: ({ activeCollectionId, isMenu, stores }) => {
|
||||
if (!isMenu || !activeCollectionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -571,8 +571,7 @@ export const downloadDocumentAsMarkdown = createActionV2({
|
||||
});
|
||||
|
||||
export const downloadDocument = createActionV2WithChildren({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu ? t("Download") : t("Download document"),
|
||||
name: ({ t, isMenu }) => (isMenu ? t("Download") : t("Download document")),
|
||||
analyticsName: "Download document",
|
||||
section: ActiveDocumentSection,
|
||||
icon: <DownloadIcon />,
|
||||
@@ -678,8 +677,7 @@ export const copyDocument = createActionV2WithChildren({
|
||||
});
|
||||
|
||||
export const duplicateDocument = createActionV2({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu ? t("Duplicate") : t("Duplicate document"),
|
||||
name: ({ t, isMenu }) => (isMenu ? t("Duplicate") : t("Duplicate document")),
|
||||
analyticsName: "Duplicate document",
|
||||
section: ActiveDocumentSection,
|
||||
icon: <DuplicateIcon />,
|
||||
@@ -829,8 +827,7 @@ export const searchInDocument = createInternalLinkActionV2({
|
||||
});
|
||||
|
||||
export const printDocument = createActionV2({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu ? t("Print") : t("Print document"),
|
||||
name: ({ t, isMenu }) => (isMenu ? t("Print") : t("Print document")),
|
||||
analyticsName: "Print document",
|
||||
section: ActiveDocumentSection,
|
||||
icon: <PrintIcon />,
|
||||
|
||||
@@ -131,8 +131,8 @@ export const navigateToTemplateSettings = createAction({
|
||||
});
|
||||
|
||||
export const navigateToNotificationSettings = createInternalLinkActionV2({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu ? t("Notification settings") : t("Notifications"),
|
||||
name: ({ t, isMenu }) =>
|
||||
isMenu ? t("Notification settings") : t("Notifications"),
|
||||
analyticsName: "Navigate to notification settings",
|
||||
section: NavigationSection,
|
||||
iconInContextMenu: false,
|
||||
|
||||
@@ -37,8 +37,7 @@ export const changeToSystemTheme = createActionV2({
|
||||
});
|
||||
|
||||
export const changeTheme = createActionV2WithChildren({
|
||||
name: ({ t, isContextMenu }) =>
|
||||
isContextMenu ? t("Appearance") : t("Change theme"),
|
||||
name: ({ t, isMenu }) => (isMenu ? t("Appearance") : t("Change theme")),
|
||||
analyticsName: "Change theme",
|
||||
placeholder: ({ t }) => t("Change theme to"),
|
||||
icon: ({ stores }) =>
|
||||
|
||||
@@ -3,12 +3,8 @@ import * as React from "react";
|
||||
import Tooltip, { Props as TooltipProps } from "~/components/Tooltip";
|
||||
import { performAction, performActionV2, resolve } from "~/actions";
|
||||
import useIsMounted from "~/hooks/useIsMounted";
|
||||
import {
|
||||
Action,
|
||||
ActionContext,
|
||||
ActionV2Variant,
|
||||
ActionV2WithChildren,
|
||||
} from "~/types";
|
||||
import { Action, ActionV2Variant, ActionV2WithChildren } from "~/types";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
|
||||
export type Props = React.HTMLAttributes<HTMLButtonElement> & {
|
||||
/** Show the button in a disabled state */
|
||||
@@ -17,8 +13,6 @@ export type Props = React.HTMLAttributes<HTMLButtonElement> & {
|
||||
hideOnActionDisabled?: boolean;
|
||||
/** Action to use on button */
|
||||
action?: Action | Exclude<ActionV2Variant, ActionV2WithChildren>;
|
||||
/** Context of action, must be provided with action */
|
||||
context?: ActionContext;
|
||||
/** If tooltip props are provided the button will be wrapped in a tooltip */
|
||||
tooltip?: Omit<TooltipProps, "children">;
|
||||
};
|
||||
@@ -28,22 +22,20 @@ export type Props = React.HTMLAttributes<HTMLButtonElement> & {
|
||||
*/
|
||||
const ActionButton = React.forwardRef<HTMLButtonElement, Props>(
|
||||
function _ActionButton(
|
||||
{ action, context, tooltip, hideOnActionDisabled, ...rest }: Props,
|
||||
{ action, tooltip, hideOnActionDisabled, ...rest }: Props,
|
||||
ref: React.Ref<HTMLButtonElement>
|
||||
) {
|
||||
const actionContext = useActionContext({
|
||||
isButton: true,
|
||||
});
|
||||
const isMounted = useIsMounted();
|
||||
const [executing, setExecuting] = React.useState(false);
|
||||
const disabled = rest.disabled;
|
||||
|
||||
if (action && !context) {
|
||||
throw new Error("Context must be provided with action");
|
||||
}
|
||||
if (!context || !action) {
|
||||
if (!actionContext || !action) {
|
||||
return <button {...rest} ref={ref} />;
|
||||
}
|
||||
|
||||
const actionContext = { ...context, isButton: true };
|
||||
|
||||
if (
|
||||
action.visible &&
|
||||
!resolve<boolean>(action.visible, actionContext) &&
|
||||
|
||||
@@ -25,7 +25,7 @@ function Breadcrumb(
|
||||
{ actions, highlightFirstItem, children, max = 2 }: Props,
|
||||
ref: React.RefObject<HTMLDivElement> | null
|
||||
) {
|
||||
const actionContext = useActionContext({ isContextMenu: true });
|
||||
const actionContext = useActionContext({ isMenu: true });
|
||||
|
||||
const visibleActions = useComputed(
|
||||
() =>
|
||||
|
||||
@@ -104,7 +104,7 @@ export function filterTemplateItems(items: TMenuItem[]): TMenuItem[] {
|
||||
|
||||
function Template({ items, actions, context, showIcons, ...menu }: Props) {
|
||||
const ctx = useActionContext({
|
||||
isContextMenu: true,
|
||||
isMenu: true,
|
||||
});
|
||||
|
||||
const templateItems = actions
|
||||
|
||||
@@ -25,7 +25,7 @@ import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import { documentPath } from "~/utils/routeHelpers";
|
||||
import { determineSidebarContext } from "./Sidebar/components/SidebarContext";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import { ActionContextProvider } from "~/hooks/useActionContext";
|
||||
import { useDocumentMenuAction } from "~/hooks/useDocumentMenuAction";
|
||||
import { ContextMenu } from "./Menu/ContextMenu";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -94,97 +94,99 @@ function DocumentListItem(
|
||||
currentContext: locationSidebarContext,
|
||||
});
|
||||
|
||||
const actionContext = useActionContext({
|
||||
isContextMenu: true,
|
||||
activeDocumentId: document.id,
|
||||
activeCollectionId:
|
||||
!isShared && document.collectionId ? document.collectionId : undefined,
|
||||
});
|
||||
|
||||
const contextMenuAction = useDocumentMenuAction({ document });
|
||||
|
||||
return (
|
||||
<ContextMenu
|
||||
action={contextMenuAction}
|
||||
context={actionContext}
|
||||
ariaLabel={t("Document options")}
|
||||
onOpen={handleMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
<ActionContextProvider
|
||||
value={{
|
||||
activeDocumentId: document.id,
|
||||
activeCollectionId:
|
||||
!isShared && document.collectionId
|
||||
? document.collectionId
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<DocumentLink
|
||||
ref={itemRef}
|
||||
dir={document.dir}
|
||||
$isStarred={document.isStarred}
|
||||
$menuOpen={menuOpen}
|
||||
to={{
|
||||
pathname: documentPath(document),
|
||||
state: {
|
||||
title: document.titleWithDefault,
|
||||
sidebarContext,
|
||||
},
|
||||
}}
|
||||
{...rest}
|
||||
{...rovingTabIndex}
|
||||
<ContextMenu
|
||||
action={contextMenuAction}
|
||||
ariaLabel={t("Document options")}
|
||||
onOpen={handleMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
>
|
||||
<Content>
|
||||
<Heading dir={document.dir}>
|
||||
{document.icon && (
|
||||
<>
|
||||
<Icon
|
||||
value={document.icon}
|
||||
color={document.color ?? undefined}
|
||||
initial={document.initial}
|
||||
/>
|
||||
|
||||
</>
|
||||
)}
|
||||
<Title
|
||||
text={document.titleWithDefault}
|
||||
highlight={highlight}
|
||||
dir={document.dir}
|
||||
/>
|
||||
{document.isBadgedNew && document.createdBy?.id !== user.id && (
|
||||
<Badge yellow>{t("New")}</Badge>
|
||||
)}
|
||||
{document.isDraft && showDraft && (
|
||||
<Tooltip content={t("Only visible to you")} placement="top">
|
||||
<Badge>{t("Draft")}</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
{canStar && (
|
||||
<StarPositioner>
|
||||
<StarButton document={document} />
|
||||
</StarPositioner>
|
||||
)}
|
||||
{document.isTemplate && showTemplate && (
|
||||
<Badge primary>{t("Template")}</Badge>
|
||||
)}
|
||||
</Heading>
|
||||
<DocumentLink
|
||||
ref={itemRef}
|
||||
dir={document.dir}
|
||||
$isStarred={document.isStarred}
|
||||
$menuOpen={menuOpen}
|
||||
to={{
|
||||
pathname: documentPath(document),
|
||||
state: {
|
||||
title: document.titleWithDefault,
|
||||
sidebarContext,
|
||||
},
|
||||
}}
|
||||
{...rest}
|
||||
{...rovingTabIndex}
|
||||
>
|
||||
<Content>
|
||||
<Heading dir={document.dir}>
|
||||
{document.icon && (
|
||||
<>
|
||||
<Icon
|
||||
value={document.icon}
|
||||
color={document.color ?? undefined}
|
||||
initial={document.initial}
|
||||
/>
|
||||
|
||||
</>
|
||||
)}
|
||||
<Title
|
||||
text={document.titleWithDefault}
|
||||
highlight={highlight}
|
||||
dir={document.dir}
|
||||
/>
|
||||
{document.isBadgedNew && document.createdBy?.id !== user.id && (
|
||||
<Badge yellow>{t("New")}</Badge>
|
||||
)}
|
||||
{document.isDraft && showDraft && (
|
||||
<Tooltip content={t("Only visible to you")} placement="top">
|
||||
<Badge>{t("Draft")}</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
{canStar && (
|
||||
<StarPositioner>
|
||||
<StarButton document={document} />
|
||||
</StarPositioner>
|
||||
)}
|
||||
{document.isTemplate && showTemplate && (
|
||||
<Badge primary>{t("Template")}</Badge>
|
||||
)}
|
||||
</Heading>
|
||||
|
||||
{!queryIsInTitle && (
|
||||
<ResultContext
|
||||
text={context}
|
||||
highlight={highlight ? SEARCH_RESULT_REGEX : undefined}
|
||||
processResult={replaceResultMarks}
|
||||
{!queryIsInTitle && (
|
||||
<ResultContext
|
||||
text={context}
|
||||
highlight={highlight ? SEARCH_RESULT_REGEX : undefined}
|
||||
processResult={replaceResultMarks}
|
||||
/>
|
||||
)}
|
||||
<DocumentMeta
|
||||
document={document}
|
||||
showCollection={showCollection}
|
||||
showPublished={showPublished}
|
||||
showParentDocuments={showParentDocuments}
|
||||
showLastViewed
|
||||
/>
|
||||
)}
|
||||
<DocumentMeta
|
||||
document={document}
|
||||
showCollection={showCollection}
|
||||
showPublished={showPublished}
|
||||
showParentDocuments={showParentDocuments}
|
||||
showLastViewed
|
||||
/>
|
||||
</Content>
|
||||
<Actions>
|
||||
<DocumentMenu
|
||||
document={document}
|
||||
onOpen={handleMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
/>
|
||||
</Actions>
|
||||
</DocumentLink>
|
||||
</ContextMenu>
|
||||
</Content>
|
||||
<Actions>
|
||||
<DocumentMenu
|
||||
document={document}
|
||||
onOpen={handleMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
/>
|
||||
</Actions>
|
||||
</DocumentLink>
|
||||
</ContextMenu>
|
||||
</ActionContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { actionV2ToMenuItem } from "~/actions";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import { ActionContext, ActionV2Variant, ActionV2WithChildren } from "~/types";
|
||||
import { ActionV2Variant, ActionV2WithChildren } from "~/types";
|
||||
import { toMenuItems } from "./transformer";
|
||||
import { observer } from "mobx-react";
|
||||
import { useComputed } from "~/hooks/useComputed";
|
||||
@@ -12,8 +12,6 @@ import { MenuProvider } from "~/components/primitives/Menu/MenuContext";
|
||||
type Props = {
|
||||
/** Root action with children representing the menu items */
|
||||
action: ActionV2WithChildren;
|
||||
/** Action context to use - new context will be created if not provided */
|
||||
context?: ActionContext;
|
||||
/** Trigger for the menu */
|
||||
children: React.ReactNode;
|
||||
/** ARIA label for the menu */
|
||||
@@ -25,15 +23,12 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ContextMenu = observer(
|
||||
({ action, children, ariaLabel, context, onOpen, onClose }: Props) => {
|
||||
({ action, children, ariaLabel, onOpen, onClose }: Props) => {
|
||||
const isMobile = useMobile();
|
||||
const contentRef = React.useRef<React.ElementRef<typeof MenuContent>>(null);
|
||||
|
||||
const actionContext =
|
||||
context ??
|
||||
useActionContext({
|
||||
isContextMenu: true,
|
||||
});
|
||||
const actionContext = useActionContext({
|
||||
isMenu: true,
|
||||
});
|
||||
|
||||
const menuItems = useComputed(() => {
|
||||
if (!open) {
|
||||
|
||||
@@ -14,7 +14,6 @@ import { actionV2ToMenuItem } from "~/actions";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import {
|
||||
ActionContext,
|
||||
ActionV2Variant,
|
||||
ActionV2WithChildren,
|
||||
MenuItem,
|
||||
@@ -27,8 +26,6 @@ import { useComputed } from "~/hooks/useComputed";
|
||||
type Props = {
|
||||
/** Root action with children representing the menu items */
|
||||
action: ActionV2WithChildren;
|
||||
/** Action context to use - new context will be created if not provided */
|
||||
context?: ActionContext;
|
||||
/** Trigger for the menu */
|
||||
children: React.ReactNode;
|
||||
/** Alignment w.r.t trigger - defaults to start */
|
||||
@@ -49,7 +46,6 @@ export const DropdownMenu = observer(
|
||||
(
|
||||
{
|
||||
action,
|
||||
context,
|
||||
children,
|
||||
align = "start",
|
||||
ariaLabel,
|
||||
@@ -64,12 +60,9 @@ export const DropdownMenu = observer(
|
||||
const isMobile = useMobile();
|
||||
const contentRef =
|
||||
React.useRef<React.ElementRef<typeof MenuContent>>(null);
|
||||
|
||||
const actionContext =
|
||||
context ??
|
||||
useActionContext({
|
||||
isContextMenu: true,
|
||||
});
|
||||
const actionContext = useActionContext({
|
||||
isMenu: true,
|
||||
});
|
||||
|
||||
const menuItems = useComputed(() => {
|
||||
if (!open) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import styled from "styled-components";
|
||||
import { s, hover } from "@shared/styles";
|
||||
import Notification from "~/models/Notification";
|
||||
import { markNotificationsAsRead } from "~/actions/definitions/notifications";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import NotificationMenu from "~/menus/NotificationMenu";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
@@ -32,7 +31,6 @@ function Notifications(
|
||||
{ onRequestClose }: Props,
|
||||
ref: React.RefObject<HTMLDivElement>
|
||||
) {
|
||||
const context = useActionContext();
|
||||
const { notifications } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const isEmpty = notifications.active.length === 0;
|
||||
@@ -69,7 +67,6 @@ function Notifications(
|
||||
<Tooltip content={t("Mark all as read")}>
|
||||
<Button
|
||||
action={markNotificationsAsRead}
|
||||
context={context}
|
||||
aria-label={t("Mark all as read")}
|
||||
>
|
||||
<MarkAsReadIcon />
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Inner } from "~/components/Button";
|
||||
import ButtonSmall from "~/components/ButtonSmall";
|
||||
import Fade from "~/components/Fade";
|
||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import { Action, Permission } from "~/types";
|
||||
|
||||
export function PermissionAction({
|
||||
@@ -21,7 +20,6 @@ export function PermissionAction({
|
||||
onChange: (permission: CollectionPermission | DocumentPermission) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext();
|
||||
|
||||
return (
|
||||
<Fade timing="150ms" key="invite">
|
||||
@@ -31,9 +29,7 @@ export function PermissionAction({
|
||||
onChange={onChange}
|
||||
value={permission}
|
||||
/>
|
||||
<ButtonSmall action={action} context={context}>
|
||||
{t("Add")}
|
||||
</ButtonSmall>
|
||||
<ButtonSmall action={action}>{t("Add")}</ButtonSmall>
|
||||
</Flex>
|
||||
</Fade>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ type Props = {
|
||||
|
||||
function SidebarAction({ action, ...rest }: Props) {
|
||||
const context = useActionContext({
|
||||
isContextMenu: false,
|
||||
isMenu: false,
|
||||
isCommandBar: false,
|
||||
activeCollectionId: undefined,
|
||||
activeDocumentId: undefined,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
unstarCollection,
|
||||
} from "~/actions/definitions/collections";
|
||||
import { starDocument, unstarDocument } from "~/actions/definitions/documents";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import { ActionContextProvider } from "~/hooks/useActionContext";
|
||||
import NudeButton from "./NudeButton";
|
||||
|
||||
type Props = {
|
||||
@@ -27,10 +27,6 @@ type Props = {
|
||||
function Star({ size, document, collection, color, ...rest }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const context = useActionContext({
|
||||
activeDocumentId: document?.id,
|
||||
activeCollectionId: collection?.id,
|
||||
});
|
||||
|
||||
const target = document || collection;
|
||||
|
||||
@@ -39,37 +35,43 @@ function Star({ size, document, collection, color, ...rest }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<NudeButton
|
||||
context={context}
|
||||
hideOnActionDisabled
|
||||
tooltip={{
|
||||
content: target.isStarred ? t("Unstar document") : t("Star document"),
|
||||
delay: 500,
|
||||
<ActionContextProvider
|
||||
value={{
|
||||
activeDocumentId: document?.id,
|
||||
activeCollectionId: collection?.id,
|
||||
}}
|
||||
action={
|
||||
collection
|
||||
? collection.isStarred
|
||||
? unstarCollection
|
||||
: starCollection
|
||||
: document
|
||||
? document.isStarred
|
||||
? unstarDocument
|
||||
: starDocument
|
||||
: undefined
|
||||
}
|
||||
size={size}
|
||||
{...rest}
|
||||
>
|
||||
{target.isStarred ? (
|
||||
<AnimatedStar size={size} color={theme.yellow} />
|
||||
) : (
|
||||
<AnimatedStar
|
||||
size={size}
|
||||
color={color ?? theme.textTertiary}
|
||||
as={UnstarredIcon}
|
||||
/>
|
||||
)}
|
||||
</NudeButton>
|
||||
<NudeButton
|
||||
hideOnActionDisabled
|
||||
tooltip={{
|
||||
content: target.isStarred ? t("Unstar document") : t("Star document"),
|
||||
delay: 500,
|
||||
}}
|
||||
action={
|
||||
collection
|
||||
? collection.isStarred
|
||||
? unstarCollection
|
||||
: starCollection
|
||||
: document
|
||||
? document.isStarred
|
||||
? unstarDocument
|
||||
: starDocument
|
||||
: undefined
|
||||
}
|
||||
size={size}
|
||||
{...rest}
|
||||
>
|
||||
{target.isStarred ? (
|
||||
<AnimatedStar size={size} color={theme.yellow} />
|
||||
) : (
|
||||
<AnimatedStar
|
||||
size={size}
|
||||
color={color ?? theme.textTertiary}
|
||||
as={UnstarredIcon}
|
||||
/>
|
||||
)}
|
||||
</NudeButton>
|
||||
</ActionContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation } from "react-router";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { ActionContext } from "~/types";
|
||||
|
||||
/**
|
||||
* Hook to get the current action context, an object that is passed to all
|
||||
* action definitions.
|
||||
*
|
||||
* @param overrides Overides of the default action context.
|
||||
* @returns The current action context.
|
||||
*/
|
||||
export default function useActionContext(
|
||||
overrides?: Partial<ActionContext>
|
||||
): ActionContext {
|
||||
const stores = useStores();
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
return {
|
||||
isContextMenu: false,
|
||||
isCommandBar: false,
|
||||
isButton: false,
|
||||
activeCollectionId: stores.ui.activeCollectionId ?? undefined,
|
||||
activeDocumentId: stores.ui.activeDocumentId,
|
||||
currentUserId: stores.auth.user?.id,
|
||||
currentTeamId: stores.auth.team?.id,
|
||||
...overrides,
|
||||
location,
|
||||
stores,
|
||||
t,
|
||||
};
|
||||
}
|
||||
102
app/hooks/useActionContext.tsx
Normal file
102
app/hooks/useActionContext.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { observer } from "mobx-react";
|
||||
import React, { createContext, useContext, ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation } from "react-router";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { ActionContext as ActionContextType } from "~/types";
|
||||
|
||||
export const ActionContext = createContext<ActionContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
type ActionContextProviderProps = {
|
||||
children: ReactNode;
|
||||
value?: Partial<ActionContextType>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider that allows overriding the action context at different levels
|
||||
* of the React component tree.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* // Override context for a command bar
|
||||
* <ActionContextProvider value={{ isCommandBar: true }}>
|
||||
* <CommandBar />
|
||||
* </ActionContextProvider>
|
||||
*
|
||||
* // Nested overrides
|
||||
* <ActionContextProvider value={{ activeCollectionId: "collection-1" }}>
|
||||
* <CollectionView />
|
||||
* <ActionContextProvider value={{ activeDocumentId: "doc-1" }}>
|
||||
* <DocumentView />
|
||||
* </ActionContextProvider>
|
||||
* </ActionContextProvider>
|
||||
* ```
|
||||
*/
|
||||
export const ActionContextProvider = observer(function ActionContextProvider_({
|
||||
children,
|
||||
value = {},
|
||||
}: ActionContextProviderProps) {
|
||||
const parentContext = useContext(ActionContext);
|
||||
const stores = useStores();
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
// Create the base context if we don't have a parent context
|
||||
const baseContext: ActionContextType = parentContext ?? {
|
||||
isMenu: false,
|
||||
isCommandBar: false,
|
||||
isButton: false,
|
||||
activeCollectionId: stores.ui.activeCollectionId ?? undefined,
|
||||
activeDocumentId: stores.ui.activeDocumentId ?? undefined,
|
||||
currentUserId: stores.auth.user?.id,
|
||||
currentTeamId: stores.auth.team?.id,
|
||||
location,
|
||||
stores,
|
||||
t,
|
||||
};
|
||||
|
||||
// Merge the parent context with the provided overrides
|
||||
const contextValue: ActionContextType = {
|
||||
...baseContext,
|
||||
...value,
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</ActionContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Hook to get the current action context, an object that is passed to all
|
||||
* action definitions.
|
||||
*
|
||||
* This hook respects the ActionContextProvider hierarchy, merging values from:
|
||||
* 1. Default system context (stores, location, translation)
|
||||
* 2. Parent ActionContextProvider values (if any)
|
||||
* 3. Local overrides parameter (highest priority)
|
||||
*
|
||||
* @param overrides Optional overrides of the action context. These will be
|
||||
* merged with any provider context and take highest priority.
|
||||
* @returns The current action context with all overrides applied.
|
||||
*/
|
||||
export default function useActionContext(
|
||||
overrides?: Partial<ActionContextType>
|
||||
): ActionContextType {
|
||||
const contextValue = useContext(ActionContext);
|
||||
|
||||
// If we have a context value from a provider, use it as the base
|
||||
if (contextValue) {
|
||||
return {
|
||||
...contextValue,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"useActionContext must be used within an ActionContextProvider"
|
||||
);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import Logger from "./utils/Logger";
|
||||
import { PluginManager } from "./utils/PluginManager";
|
||||
import history from "./utils/history";
|
||||
import { initSentry } from "./utils/sentry";
|
||||
import { ActionContextProvider } from "./hooks/useActionContext";
|
||||
|
||||
// Load plugins as soon as possible
|
||||
void PluginManager.loadPlugins();
|
||||
@@ -53,25 +54,27 @@ if (element) {
|
||||
<Provider {...stores}>
|
||||
<Analytics>
|
||||
<Theme>
|
||||
<ErrorBoundary showTitle>
|
||||
<KBarProvider actions={[]} options={commandBarOptions}>
|
||||
<LazyPolyfill>
|
||||
<LazyMotion features={loadFeatures}>
|
||||
<Router history={history}>
|
||||
<PageScroll>
|
||||
<PageTheme />
|
||||
<ScrollToTop>
|
||||
<Routes />
|
||||
</ScrollToTop>
|
||||
<Toasts />
|
||||
<Dialogs />
|
||||
<Desktop />
|
||||
</PageScroll>
|
||||
</Router>
|
||||
</LazyMotion>
|
||||
</LazyPolyfill>
|
||||
</KBarProvider>
|
||||
</ErrorBoundary>
|
||||
<Router history={history}>
|
||||
<ErrorBoundary showTitle>
|
||||
<KBarProvider actions={[]} options={commandBarOptions}>
|
||||
<LazyPolyfill>
|
||||
<LazyMotion features={loadFeatures}>
|
||||
<ActionContextProvider>
|
||||
<PageScroll>
|
||||
<PageTheme />
|
||||
<ScrollToTop>
|
||||
<Routes />
|
||||
</ScrollToTop>
|
||||
<Toasts />
|
||||
<Dialogs />
|
||||
<Desktop />
|
||||
</PageScroll>
|
||||
</ActionContextProvider>
|
||||
</LazyMotion>
|
||||
</LazyPolyfill>
|
||||
</KBarProvider>
|
||||
</ErrorBoundary>
|
||||
</Router>
|
||||
</Theme>
|
||||
</Analytics>
|
||||
</Provider>
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
createDocument,
|
||||
exportCollection,
|
||||
} from "~/actions/definitions/collections";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import { ActionContextProvider } from "~/hooks/useActionContext";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useRequest from "~/hooks/useRequest";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -130,11 +130,6 @@ function CollectionMenu({
|
||||
);
|
||||
|
||||
const can = usePolicy(collection);
|
||||
const context = useActionContext({
|
||||
isContextMenu: true,
|
||||
activeCollectionId: collection.id,
|
||||
});
|
||||
|
||||
const sortAlphabetical = collection.sort.field === "title";
|
||||
const sortDir = collection.sort.direction;
|
||||
|
||||
@@ -228,7 +223,7 @@ function CollectionMenu({
|
||||
const rootAction = useMenuAction(actions);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionContextProvider value={{ activeCollectionId: collection.id }}>
|
||||
<VisuallyHidden.Root>
|
||||
<label>
|
||||
{t("Import document")}
|
||||
@@ -244,7 +239,6 @@ function CollectionMenu({
|
||||
</VisuallyHidden.Root>
|
||||
<DropdownMenu
|
||||
action={rootAction}
|
||||
context={context}
|
||||
align={align}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
@@ -255,7 +249,7 @@ function CollectionMenu({
|
||||
onPointerEnter={handlePointerEnter}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
</ActionContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Document from "~/models/Document";
|
||||
import { DropdownMenu } from "~/components/Menu/DropdownMenu";
|
||||
import { OverflowMenuButton } from "~/components/Menu/OverflowMenuButton";
|
||||
import Switch from "~/components/Switch";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import { ActionContextProvider } from "~/hooks/useActionContext";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
@@ -132,13 +132,6 @@ function DocumentMenu({
|
||||
onSelectTemplate,
|
||||
});
|
||||
|
||||
const context = useActionContext({
|
||||
isContextMenu: true,
|
||||
activeDocumentId: document.id,
|
||||
activeCollectionId:
|
||||
!isShared && document.collectionId ? document.collectionId : undefined,
|
||||
});
|
||||
|
||||
const toggleSwitches = React.useMemo<React.ReactNode>(() => {
|
||||
if (!can.update || !(showDisplayOptions || showToggleEmbeds)) {
|
||||
return;
|
||||
@@ -203,20 +196,29 @@ function DocumentMenu({
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
action={rootAction}
|
||||
context={context}
|
||||
align={align}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
ariaLabel={t("Document options")}
|
||||
append={toggleSwitches}
|
||||
<ActionContextProvider
|
||||
value={{
|
||||
activeDocumentId: document.id,
|
||||
activeCollectionId:
|
||||
!isShared && document.collectionId
|
||||
? document.collectionId
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<OverflowMenuButton
|
||||
neutral={neutral}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
action={rootAction}
|
||||
align={align}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
ariaLabel={t("Document options")}
|
||||
append={toggleSwitches}
|
||||
>
|
||||
<OverflowMenuButton
|
||||
neutral={neutral}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</ActionContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
copyLinkToRevision,
|
||||
restoreRevision,
|
||||
} from "~/actions/definitions/revisions";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import { useMemo } from "react";
|
||||
import { useMenuAction } from "~/hooks/useMenuAction";
|
||||
import { ActionContextProvider } from "~/hooks/useActionContext";
|
||||
|
||||
type Props = {
|
||||
document: Document;
|
||||
@@ -19,11 +19,6 @@ type Props = {
|
||||
|
||||
function RevisionMenu({ document }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext({
|
||||
isContextMenu: true,
|
||||
activeDocumentId: document.id,
|
||||
});
|
||||
|
||||
const actions = useMemo(
|
||||
() => [restoreRevision, ActionV2Separator, copyLinkToRevision],
|
||||
[]
|
||||
@@ -32,14 +27,15 @@ function RevisionMenu({ document }: Props) {
|
||||
const rootAction = useMenuAction(actions);
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
action={rootAction}
|
||||
context={context}
|
||||
align="end"
|
||||
ariaLabel={t("Revision options")}
|
||||
>
|
||||
<OverflowMenuButton />
|
||||
</DropdownMenu>
|
||||
<ActionContextProvider value={{ activeDocumentId: document.id }}>
|
||||
<DropdownMenu
|
||||
action={rootAction}
|
||||
align="end"
|
||||
ariaLabel={t("Revision options")}
|
||||
>
|
||||
<OverflowMenuButton />
|
||||
</DropdownMenu>
|
||||
</ActionContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ type Props = {
|
||||
|
||||
const TeamMenu: React.FC = ({ children }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext({ isContextMenu: true });
|
||||
const context = useActionContext({ isMenu: true });
|
||||
|
||||
// NOTE: it's useful to memoize on the team id and session because the action
|
||||
// menu is not cached at all.
|
||||
|
||||
@@ -8,7 +8,6 @@ import { AvatarSize } from "~/components/Avatar";
|
||||
import Facepile from "~/components/Facepile";
|
||||
import Fade from "~/components/Fade";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
@@ -24,7 +23,6 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { memberships, groupMemberships, users } = useStores();
|
||||
const collectionUsers = users.inCollection(collection.id);
|
||||
const context = useActionContext();
|
||||
const isMobile = useMobile();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -72,7 +70,6 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
|
||||
|
||||
return (
|
||||
<NudeButton
|
||||
context={context}
|
||||
tooltip={{
|
||||
content:
|
||||
usersCount > 0
|
||||
|
||||
@@ -24,7 +24,6 @@ import Text from "~/components/Text";
|
||||
import Time from "~/components/Time";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import { resolveCommentFactory } from "~/actions/definitions/comments";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import CommentMenu from "~/menus/CommentMenu";
|
||||
@@ -312,14 +311,12 @@ const ResolveButton = ({
|
||||
comment: Comment;
|
||||
onUpdate: (attrs: { resolved: boolean }) => void;
|
||||
}) => {
|
||||
const context = useActionContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tooltip content={t("Mark as resolved")} placement="top">
|
||||
<Action
|
||||
as={NudeButton}
|
||||
context={context}
|
||||
action={resolveCommentFactory({
|
||||
comment,
|
||||
onResolve: () => onUpdate({ resolved: true }),
|
||||
|
||||
@@ -11,12 +11,12 @@ import Revision from "~/models/Revision";
|
||||
import { openDocumentInsights } from "~/actions/definitions/documents";
|
||||
import DocumentMeta from "~/components/DocumentMeta";
|
||||
import Fade from "~/components/Fade";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { documentPath } from "~/utils/routeHelpers";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
|
||||
type Props = {
|
||||
/* The document to display meta data for */
|
||||
@@ -36,9 +36,6 @@ function TitleDocumentMeta({ to, document, revision, ...rest }: Props) {
|
||||
const onlyYou = totalViewers === 1 && documentViews[0].userId;
|
||||
const viewsLoadedOnMount = useRef(totalViewers > 0);
|
||||
const can = usePolicy(document);
|
||||
const actionContext = useActionContext({
|
||||
activeDocumentId: document.id,
|
||||
});
|
||||
|
||||
const Wrapper = viewsLoadedOnMount.current ? Fragment : Fade;
|
||||
|
||||
@@ -70,9 +67,7 @@ function TitleDocumentMeta({ to, document, revision, ...rest }: Props) {
|
||||
!document.isTemplate ? (
|
||||
<Wrapper>
|
||||
•
|
||||
<InsightsButton
|
||||
onClick={() => openDocumentInsights.perform(actionContext)}
|
||||
>
|
||||
<InsightsButton action={openDocumentInsights}>
|
||||
{t("Viewed by")}{" "}
|
||||
{onlyYou
|
||||
? t("only you")
|
||||
@@ -91,7 +86,7 @@ const CommentLink = styled(Link)`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const InsightsButton = styled.button`
|
||||
const InsightsButton = styled(NudeButton)`
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
@@ -23,7 +23,6 @@ import Tooltip from "~/components/Tooltip";
|
||||
import { publishDocument } from "~/actions/definitions/documents";
|
||||
import { navigateToTemplateSettings } from "~/actions/definitions/navigation";
|
||||
import { restoreRevision } from "~/actions/definitions/revisions";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useEditingFocus from "~/hooks/useEditingFocus";
|
||||
@@ -109,10 +108,6 @@ function DocumentHeader({
|
||||
}
|
||||
}, [ui, isShare]);
|
||||
|
||||
const context = useActionContext({
|
||||
activeDocumentId: document?.id,
|
||||
});
|
||||
|
||||
const can = usePolicy(document);
|
||||
const { isDeleted, isTemplate } = document;
|
||||
const isTemplateEditable = can.update && isTemplate;
|
||||
@@ -279,7 +274,6 @@ function DocumentHeader({
|
||||
placement="bottom"
|
||||
>
|
||||
<Button
|
||||
context={context}
|
||||
action={isTemplate ? navigateToTemplateSettings : undefined}
|
||||
onClick={isTemplate ? undefined : handleSave}
|
||||
disabled={savingIsDisabled}
|
||||
@@ -308,12 +302,7 @@ function DocumentHeader({
|
||||
{revision && revision.createdAt !== document.updatedAt && (
|
||||
<Action>
|
||||
<Tooltip content={t("Restore version")} placement="bottom">
|
||||
<Button
|
||||
action={restoreRevision}
|
||||
context={context}
|
||||
neutral
|
||||
hideOnActionDisabled
|
||||
>
|
||||
<Button action={restoreRevision} neutral hideOnActionDisabled>
|
||||
{t("Restore")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -323,7 +312,6 @@ function DocumentHeader({
|
||||
<Action>
|
||||
<Button
|
||||
action={publishDocument}
|
||||
context={context}
|
||||
disabled={publishingIsDisabled}
|
||||
hideOnActionDisabled
|
||||
hideIcon
|
||||
|
||||
@@ -6,12 +6,10 @@ import Empty from "~/components/Empty";
|
||||
import Heading from "~/components/Heading";
|
||||
import Scene from "~/components/Scene";
|
||||
import { navigateToHome } from "~/actions/definitions/navigation";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
|
||||
const Error403 = () => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const context = useActionContext();
|
||||
|
||||
return (
|
||||
<Scene title={t("No access to this doc")}>
|
||||
@@ -24,7 +22,7 @@ const Error403 = () => {
|
||||
{t("Please request access from the document owner.")}
|
||||
</Empty>
|
||||
<Flex gap={8}>
|
||||
<Button action={navigateToHome} context={context} hideIcon>
|
||||
<Button action={navigateToHome} hideIcon>
|
||||
{t("Home")}
|
||||
</Button>
|
||||
<Button onClick={history.goBack} neutral>
|
||||
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
navigateToHome,
|
||||
navigateToSearch,
|
||||
} from "~/actions/definitions/navigation";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
|
||||
const Error404 = () => {
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext();
|
||||
|
||||
return (
|
||||
<Scene title={t("Not found")}>
|
||||
@@ -25,10 +23,10 @@ const Error404 = () => {
|
||||
</Trans>
|
||||
</Empty>
|
||||
<Flex gap={8}>
|
||||
<Button action={navigateToHome} context={context} neutral hideIcon>
|
||||
<Button action={navigateToHome} neutral hideIcon>
|
||||
{t("Home")}
|
||||
</Button>
|
||||
<Button action={navigateToSearch} context={context} neutral>
|
||||
<Button action={navigateToSearch} neutral>
|
||||
{t("Search")}…
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -6,11 +6,9 @@ import Empty from "~/components/Empty";
|
||||
import Heading from "~/components/Heading";
|
||||
import PageTitle from "~/components/PageTitle";
|
||||
import { navigateToHome } from "~/actions/definitions/navigation";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
|
||||
const ErrorUnknown = () => {
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext();
|
||||
|
||||
return (
|
||||
<CenteredContent>
|
||||
@@ -24,7 +22,7 @@ const ErrorUnknown = () => {
|
||||
</Trans>
|
||||
</Empty>
|
||||
<Flex gap={8}>
|
||||
<Button action={navigateToHome} context={context} neutral hideIcon>
|
||||
<Button action={navigateToHome} neutral hideIcon>
|
||||
{t("Home")}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -11,7 +11,6 @@ import Scene from "~/components/Scene";
|
||||
import Text from "~/components/Text";
|
||||
import { createApiKey } from "~/actions/definitions/apiKeys";
|
||||
import env from "~/env";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
@@ -25,7 +24,6 @@ function APIAndApps() {
|
||||
const { t } = useTranslation();
|
||||
const { apiKeys, oauthAuthentications } = useStores();
|
||||
const can = usePolicy(team);
|
||||
const context = useActionContext();
|
||||
const appName = env.APP_NAME;
|
||||
|
||||
return (
|
||||
@@ -40,7 +38,6 @@ function APIAndApps() {
|
||||
type="submit"
|
||||
value={`${t("New API key")}…`}
|
||||
action={createApiKey}
|
||||
context={context}
|
||||
/>
|
||||
</Action>
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,6 @@ import PaginatedList from "~/components/PaginatedList";
|
||||
import Scene from "~/components/Scene";
|
||||
import Text from "~/components/Text";
|
||||
import { createApiKey } from "~/actions/definitions/apiKeys";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -20,7 +19,6 @@ function ApiKeys() {
|
||||
const { t } = useTranslation();
|
||||
const { apiKeys } = useStores();
|
||||
const can = usePolicy(team);
|
||||
const context = useActionContext();
|
||||
|
||||
return (
|
||||
<Scene
|
||||
@@ -34,7 +32,6 @@ function ApiKeys() {
|
||||
type="submit"
|
||||
value={`${t("New API key")}…`}
|
||||
action={createApiKey}
|
||||
context={context}
|
||||
/>
|
||||
</Action>
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,6 @@ import PaginatedList from "~/components/PaginatedList";
|
||||
import Scene from "~/components/Scene";
|
||||
import Text from "~/components/Text";
|
||||
import { createOAuthClient } from "~/actions/definitions/oauthClients";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -20,7 +19,6 @@ function Applications() {
|
||||
const { t } = useTranslation();
|
||||
const { oauthClients } = useStores();
|
||||
const can = usePolicy(team);
|
||||
const context = useActionContext();
|
||||
|
||||
return (
|
||||
<Scene
|
||||
@@ -34,7 +32,6 @@ function Applications() {
|
||||
type="submit"
|
||||
value={`${t("New App")}…`}
|
||||
action={createOAuthClient}
|
||||
context={context}
|
||||
/>
|
||||
</Action>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,6 @@ import Scene from "~/components/Scene";
|
||||
import Text from "~/components/Text";
|
||||
import { inviteUser } from "~/actions/definitions/users";
|
||||
import env from "~/env";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useQuery from "~/hooks/useQuery";
|
||||
@@ -32,7 +31,6 @@ function Members() {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const team = useCurrentTeam();
|
||||
const context = useActionContext();
|
||||
const { users } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const params = useQuery();
|
||||
@@ -128,7 +126,6 @@ function Members() {
|
||||
data-event-category="invite"
|
||||
data-event-action="peoplePage"
|
||||
action={inviteUser}
|
||||
context={context}
|
||||
icon={<PlusIcon />}
|
||||
>
|
||||
{t("Invite people")}…
|
||||
|
||||
@@ -8,24 +8,19 @@ import PaginatedDocumentList from "~/components/PaginatedDocumentList";
|
||||
import Scene from "~/components/Scene";
|
||||
import Subheading from "~/components/Subheading";
|
||||
import { permanentlyDeleteDocumentsInTrash } from "~/actions/definitions/documents";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
function Trash() {
|
||||
const { t } = useTranslation();
|
||||
const { documents } = useStores();
|
||||
const context = useActionContext();
|
||||
|
||||
return (
|
||||
<Scene
|
||||
icon={<TrashIcon />}
|
||||
title={t("Trash")}
|
||||
actions={
|
||||
documents.deleted.length > 0 && (
|
||||
<Button
|
||||
neutral
|
||||
action={permanentlyDeleteDocumentsInTrash}
|
||||
context={context}
|
||||
>
|
||||
<Button neutral action={permanentlyDeleteDocumentsInTrash}>
|
||||
{t("Empty trash")}
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -92,7 +92,7 @@ export type MenuItem =
|
||||
| MenuGroup;
|
||||
|
||||
export type ActionContext = {
|
||||
isContextMenu: boolean;
|
||||
isMenu: boolean;
|
||||
isCommandBar: boolean;
|
||||
isButton: boolean;
|
||||
sidebarContext?: SidebarContextType;
|
||||
|
||||
@@ -15,7 +15,6 @@ import Input from "~/components/Input";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { disconnectAnalyticsIntegrationFactory } from "~/actions/definitions/integrations";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import Flex from "~/components/Flex";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -26,7 +25,6 @@ type FormData = {
|
||||
function GoogleAnalytics() {
|
||||
const { integrations } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext();
|
||||
|
||||
const integration = find(integrations.orderedData, {
|
||||
type: IntegrationType.Analytics,
|
||||
@@ -108,7 +106,6 @@ function GoogleAnalytics() {
|
||||
|
||||
<Button
|
||||
action={disconnectAnalyticsIntegrationFactory(integration)}
|
||||
context={context}
|
||||
disabled={formState.isSubmitting}
|
||||
neutral
|
||||
hideIcon
|
||||
|
||||
@@ -15,7 +15,6 @@ import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import Icon from "./Icon";
|
||||
import { disconnectAnalyticsIntegrationFactory } from "~/actions/definitions/integrations";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import Flex from "~/components/Flex";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -27,7 +26,6 @@ type FormData = {
|
||||
function Matomo() {
|
||||
const { integrations } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext();
|
||||
|
||||
const integration = find(integrations.orderedData, {
|
||||
type: IntegrationType.Analytics,
|
||||
@@ -129,7 +127,6 @@ function Matomo() {
|
||||
|
||||
<Button
|
||||
action={disconnectAnalyticsIntegrationFactory(integration)}
|
||||
context={context}
|
||||
disabled={formState.isSubmitting}
|
||||
neutral
|
||||
hideIcon
|
||||
|
||||
@@ -16,7 +16,6 @@ import useStores from "~/hooks/useStores";
|
||||
import Icon from "./Icon";
|
||||
import Flex from "~/components/Flex";
|
||||
import { disconnectAnalyticsIntegrationFactory } from "~/actions/definitions/integrations";
|
||||
import useActionContext from "~/hooks/useActionContext";
|
||||
import styled from "styled-components";
|
||||
|
||||
type FormData = {
|
||||
@@ -28,7 +27,6 @@ type FormData = {
|
||||
function Umami() {
|
||||
const { integrations } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const context = useActionContext();
|
||||
|
||||
const integration = find(integrations.orderedData, {
|
||||
type: IntegrationType.Analytics,
|
||||
@@ -149,7 +147,6 @@ function Umami() {
|
||||
|
||||
<Button
|
||||
action={disconnectAnalyticsIntegrationFactory(integration)}
|
||||
context={context}
|
||||
disabled={formState.isSubmitting}
|
||||
neutral
|
||||
hideIcon
|
||||
|
||||
Reference in New Issue
Block a user