mirror of
https://github.com/outline/outline.git
synced 2026-06-13 19:35:02 +03:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 878860215a | |||
| cb5ca55d9e | |||
| 8b2ccc6caf | |||
| 2db7690e27 | |||
| 06b89635be | |||
| 1ff23756ac | |||
| a00b677076 | |||
| 6c1e4a5b40 | |||
| 59078704c8 | |||
| f1a20b27fd | |||
| 313b046e4e |
@@ -53,9 +53,13 @@ export const resolveCommentFactory = ({
|
||||
perform: async ({ t }) => {
|
||||
await comment.resolve();
|
||||
|
||||
const locationState = history.location.state as Record<string, unknown>;
|
||||
history.replace({
|
||||
...history.location,
|
||||
state: null,
|
||||
state: {
|
||||
sidebarContext: locationState["sidebarContext"],
|
||||
commentId: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
onResolve();
|
||||
@@ -81,9 +85,13 @@ export const unresolveCommentFactory = ({
|
||||
perform: async () => {
|
||||
await comment.unresolve();
|
||||
|
||||
const locationState = history.location.state as Record<string, unknown>;
|
||||
history.replace({
|
||||
...history.location,
|
||||
state: null,
|
||||
state: {
|
||||
sidebarContext: locationState["sidebarContext"],
|
||||
commentId: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
onUnresolve();
|
||||
|
||||
@@ -8,15 +8,11 @@ import Document from "~/models/Document";
|
||||
import Breadcrumb from "~/components/Breadcrumb";
|
||||
import Icon from "~/components/Icon";
|
||||
import CollectionIcon from "~/components/Icons/CollectionIcon";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { MenuInternalLink } from "~/types";
|
||||
import {
|
||||
archivePath,
|
||||
collectionPath,
|
||||
settingsPath,
|
||||
trashPath,
|
||||
} from "~/utils/routeHelpers";
|
||||
import { archivePath, settingsPath, trashPath } from "~/utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
@@ -64,6 +60,7 @@ function DocumentBreadcrumb(
|
||||
const { collections } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const category = useCategory(document);
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const collection = document.collectionId
|
||||
? collections.get(document.collectionId)
|
||||
: undefined;
|
||||
@@ -80,7 +77,10 @@ function DocumentBreadcrumb(
|
||||
type: "route",
|
||||
title: collection.name,
|
||||
icon: <CollectionIcon collection={collection} expanded />,
|
||||
to: collectionPath(collection.path),
|
||||
to: {
|
||||
pathname: collection.path,
|
||||
state: { sidebarContext },
|
||||
},
|
||||
};
|
||||
} else if (document.isCollectionDeleted) {
|
||||
collectionNode = {
|
||||
@@ -114,11 +114,14 @@ function DocumentBreadcrumb(
|
||||
) : (
|
||||
node.title
|
||||
),
|
||||
to: node.url,
|
||||
to: {
|
||||
pathname: node.url,
|
||||
state: { sidebarContext },
|
||||
},
|
||||
});
|
||||
});
|
||||
return output;
|
||||
}, [path, category, collectionNode]);
|
||||
}, [path, category, sidebarContext, collectionNode]);
|
||||
|
||||
if (!collections.isLoaded) {
|
||||
return null;
|
||||
|
||||
@@ -15,6 +15,7 @@ import scrollIntoView from "scroll-into-view-if-needed";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { NavigationNode } from "@shared/types";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import DocumentExplorerNode from "~/components/DocumentExplorerNode";
|
||||
import DocumentExplorerSearchResult from "~/components/DocumentExplorerSearchResult";
|
||||
import Flex from "~/components/Flex";
|
||||
@@ -25,7 +26,6 @@ import InputSearch from "~/components/InputSearch";
|
||||
import Text from "~/components/Text";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
import { ancestors, descendants } from "~/utils/tree";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -21,9 +21,11 @@ import StarButton, { AnimatedStar } from "~/components/Star";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import { hover } from "~/styles";
|
||||
import { documentPath } from "~/utils/routeHelpers";
|
||||
import { determineSidebarContext } from "./Sidebar/components/SidebarContext";
|
||||
|
||||
type Props = {
|
||||
document: Document;
|
||||
@@ -50,6 +52,7 @@ function DocumentListItem(
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const user = useCurrentUser();
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
||||
|
||||
let itemRef: React.Ref<HTMLAnchorElement> =
|
||||
@@ -78,6 +81,12 @@ function DocumentListItem(
|
||||
!!document.title.toLowerCase().includes(highlight.toLowerCase());
|
||||
const canStar = !document.isArchived && !document.isTemplate;
|
||||
|
||||
const sidebarContext = determineSidebarContext({
|
||||
document,
|
||||
user,
|
||||
currentContext: locationSidebarContext,
|
||||
});
|
||||
|
||||
return (
|
||||
<DocumentLink
|
||||
ref={itemRef}
|
||||
@@ -89,6 +98,7 @@ function DocumentListItem(
|
||||
pathname: documentPath(document),
|
||||
state: {
|
||||
title: document.titleWithDefault,
|
||||
sidebarContext,
|
||||
},
|
||||
}}
|
||||
{...rest}
|
||||
|
||||
@@ -19,6 +19,7 @@ import Event from "~/models/Event";
|
||||
import { Avatar } from "~/components/Avatar";
|
||||
import Item, { Actions, Props as ItemProps } from "~/components/List/Item";
|
||||
import Time from "~/components/Time";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import RevisionMenu from "~/menus/RevisionMenu";
|
||||
import { hover } from "~/styles";
|
||||
@@ -35,6 +36,7 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { revisions } = useStores();
|
||||
const location = useLocation();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const opts = {
|
||||
userName: event.actor.name,
|
||||
};
|
||||
@@ -66,7 +68,10 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
|
||||
);
|
||||
to = {
|
||||
pathname: documentHistoryPath(document, event.modelId || "latest"),
|
||||
state: { retainScrollPosition: true },
|
||||
state: {
|
||||
sidebarContext,
|
||||
retainScrollPosition: true,
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
|
||||
+34
-31
@@ -17,6 +17,7 @@ import useMobile from "~/hooks/useMobile";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import { TooltipProvider } from "./TooltipContext";
|
||||
|
||||
export const HEADER_HEIGHT = 64;
|
||||
|
||||
@@ -71,38 +72,40 @@ function Header(
|
||||
const isCompact = size.width < 1000 || breadcrumbMakesCompact;
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
ref={mergeRefs([ref, internalRef])}
|
||||
align="center"
|
||||
shrink={false}
|
||||
className={className}
|
||||
$passThrough={passThrough}
|
||||
$insetTitleAdjust={ui.sidebarIsClosed && Desktop.hasInsetTitlebar()}
|
||||
>
|
||||
{left || hasMobileSidebar ? (
|
||||
<Breadcrumbs ref={setBreadcrumbRef}>
|
||||
{hasMobileSidebar && (
|
||||
<MobileMenuButton
|
||||
onClick={ui.toggleMobileSidebar}
|
||||
icon={<MenuIcon />}
|
||||
neutral
|
||||
/>
|
||||
)}
|
||||
{left}
|
||||
</Breadcrumbs>
|
||||
) : null}
|
||||
<TooltipProvider>
|
||||
<Wrapper
|
||||
ref={mergeRefs([ref, internalRef])}
|
||||
align="center"
|
||||
shrink={false}
|
||||
className={className}
|
||||
$passThrough={passThrough}
|
||||
$insetTitleAdjust={ui.sidebarIsClosed && Desktop.hasInsetTitlebar()}
|
||||
>
|
||||
{left || hasMobileSidebar ? (
|
||||
<Breadcrumbs ref={setBreadcrumbRef}>
|
||||
{hasMobileSidebar && (
|
||||
<MobileMenuButton
|
||||
onClick={ui.toggleMobileSidebar}
|
||||
icon={<MenuIcon />}
|
||||
neutral
|
||||
/>
|
||||
)}
|
||||
{left}
|
||||
</Breadcrumbs>
|
||||
) : null}
|
||||
|
||||
{isScrolled && !isCompact ? (
|
||||
<Title onClick={handleClickTitle}>
|
||||
<Fade>{title}</Fade>
|
||||
</Title>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<Actions align="center" justify="flex-end">
|
||||
{typeof actions === "function" ? actions({ isCompact }) : actions}
|
||||
</Actions>
|
||||
</Wrapper>
|
||||
{isScrolled && !isCompact ? (
|
||||
<Title onClick={handleClickTitle}>
|
||||
<Fade>{title}</Fade>
|
||||
</Title>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<Actions align="center" justify="flex-end">
|
||||
{typeof actions === "function" ? actions({ isCompact }) : actions}
|
||||
</Actions>
|
||||
</Wrapper>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
import { searchPath } from "~/utils/routeHelpers";
|
||||
import Input, { Outline } from "./Input";
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Helmet } from "react-helmet-async";
|
||||
import styled, { DefaultTheme } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { s } from "@shared/styles";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import Flex from "~/components/Flex";
|
||||
import { LoadingIndicatorBar } from "~/components/LoadingIndicator";
|
||||
import SkipNavContent from "~/components/SkipNavContent";
|
||||
@@ -13,7 +14,6 @@ import useAutoRefresh from "~/hooks/useAutoRefresh";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import { MenuProvider } from "~/hooks/useMenuContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -10,13 +10,23 @@ import { fadeAndScaleIn } from "~/styles/animations";
|
||||
|
||||
type Props = PopoverProps & {
|
||||
children: React.ReactNode;
|
||||
/** The width of the popover, defaults to 380px. */
|
||||
width?: number;
|
||||
/** The minimum width of the popover, use instead of width if contents adjusts size. */
|
||||
minWidth?: number;
|
||||
/** Shrink the padding of the popover */
|
||||
shrink?: boolean;
|
||||
/** Make the popover flex */
|
||||
flex?: boolean;
|
||||
/** The tab index of the popover */
|
||||
tabIndex?: number;
|
||||
/** Whether the popover should be scrollable, defaults to true. */
|
||||
scrollable?: boolean;
|
||||
/** The position of the popover on mobile, defaults to "top". */
|
||||
mobilePosition?: "top" | "bottom";
|
||||
/** Function to show the popover */
|
||||
show: () => void;
|
||||
/** Function to hide the popover */
|
||||
hide: () => void;
|
||||
};
|
||||
|
||||
@@ -25,6 +35,7 @@ const Popover = (
|
||||
children,
|
||||
shrink,
|
||||
width = 380,
|
||||
minWidth,
|
||||
scrollable = true,
|
||||
flex,
|
||||
mobilePosition,
|
||||
@@ -71,6 +82,7 @@ const Popover = (
|
||||
ref={ref}
|
||||
$shrink={shrink}
|
||||
$width={width}
|
||||
$minWidth={minWidth}
|
||||
$scrollable={scrollable}
|
||||
$flex={flex}
|
||||
>
|
||||
@@ -83,6 +95,7 @@ const Popover = (
|
||||
type ContentsProps = {
|
||||
$shrink?: boolean;
|
||||
$width?: number;
|
||||
$minWidth?: number;
|
||||
$flex?: boolean;
|
||||
$scrollable: boolean;
|
||||
$mobilePosition?: "top" | "bottom";
|
||||
@@ -101,7 +114,8 @@ const Contents = styled.div<ContentsProps>`
|
||||
padding: ${(props) => (props.$shrink ? "6px 0" : "12px 24px")};
|
||||
max-height: 75vh;
|
||||
box-shadow: ${s("menuShadow")};
|
||||
width: ${(props) => props.$width}px;
|
||||
${(props) => props.$width && `width: ${props.$width}px`};
|
||||
${(props) => props.$minWidth && `min-width: ${props.$minWidth}px`};
|
||||
|
||||
${(props) =>
|
||||
props.$scrollable
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { metaDisplay } from "@shared/utils/keyboard";
|
||||
import Flex from "~/components/Flex";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import Text from "~/components/Text";
|
||||
@@ -14,7 +15,6 @@ import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import OrganizationMenu from "~/menus/OrganizationMenu";
|
||||
import { metaDisplay } from "~/utils/keyboard";
|
||||
import { homePath, draftsPath, searchPath } from "~/utils/routeHelpers";
|
||||
import TeamLogo from "../TeamLogo";
|
||||
import Tooltip from "../Tooltip";
|
||||
|
||||
@@ -5,12 +5,12 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import { metaDisplay } from "@shared/utils/keyboard";
|
||||
import Flex from "~/components/Flex";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import useSettingsConfig from "~/hooks/useSettingsConfig";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
import { metaDisplay } from "~/utils/keyboard";
|
||||
import { settingsPath } from "~/utils/routeHelpers";
|
||||
import Tooltip from "../Tooltip";
|
||||
import Sidebar from "./Sidebar";
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { NavigationNode } from "@shared/types";
|
||||
import { metaDisplay } from "@shared/utils/keyboard";
|
||||
import Flex from "~/components/Flex";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import SearchPopover from "~/components/SearchPopover";
|
||||
@@ -12,7 +13,6 @@ import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { hover } from "~/styles";
|
||||
import history from "~/utils/history";
|
||||
import { metaDisplay } from "~/utils/keyboard";
|
||||
import { homePath, sharedDocumentPath } from "~/utils/routeHelpers";
|
||||
import { useTeamContext } from "../TeamContext";
|
||||
import TeamLogo from "../TeamLogo";
|
||||
|
||||
@@ -17,6 +17,7 @@ import { fadeIn } from "~/styles/animations";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import NotificationIcon from "../Notifications/NotificationIcon";
|
||||
import NotificationsPopover from "../Notifications/NotificationsPopover";
|
||||
import { TooltipProvider } from "../TooltipContext";
|
||||
import ResizeBorder from "./components/ResizeBorder";
|
||||
import SidebarButton, { SidebarButtonProps } from "./components/SidebarButton";
|
||||
import ToggleButton from "./components/ToggleButton";
|
||||
@@ -194,7 +195,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TooltipProvider>
|
||||
<Container
|
||||
ref={ref}
|
||||
style={style}
|
||||
@@ -242,7 +243,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
/>
|
||||
</Container>
|
||||
{ui.mobileSidebarVisible && <Backdrop onClick={ui.toggleMobileSidebar} />}
|
||||
</>
|
||||
</TooltipProvider>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import Header from "./Header";
|
||||
import PlaceholderCollections from "./PlaceholderCollections";
|
||||
import Relative from "./Relative";
|
||||
import SidebarAction from "./SidebarAction";
|
||||
import SidebarContext from "./SidebarContext";
|
||||
import { DragObject } from "./SidebarLink";
|
||||
|
||||
function Collections() {
|
||||
@@ -49,38 +50,40 @@ function Collections() {
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
<Header id="collections" title={t("Collections")}>
|
||||
<Relative>
|
||||
<PaginatedList
|
||||
options={params}
|
||||
aria-label={t("Collections")}
|
||||
items={collections.allActive}
|
||||
loading={<PlaceholderCollections />}
|
||||
heading={
|
||||
isDraggingAnyCollection ? (
|
||||
<DropCursor
|
||||
isActiveDrop={isCollectionDropping}
|
||||
innerRef={dropToReorderCollection}
|
||||
position="top"
|
||||
<SidebarContext.Provider value="collections">
|
||||
<Flex column>
|
||||
<Header id="collections" title={t("Collections")}>
|
||||
<Relative>
|
||||
<PaginatedList
|
||||
options={params}
|
||||
aria-label={t("Collections")}
|
||||
items={collections.allActive}
|
||||
loading={<PlaceholderCollections />}
|
||||
heading={
|
||||
isDraggingAnyCollection ? (
|
||||
<DropCursor
|
||||
isActiveDrop={isCollectionDropping}
|
||||
innerRef={dropToReorderCollection}
|
||||
position="top"
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
renderError={(props) => <StyledError {...props} />}
|
||||
renderItem={(item: Collection, index) => (
|
||||
<DraggableCollectionLink
|
||||
key={item.id}
|
||||
collection={item}
|
||||
activeDocument={documents.active}
|
||||
prefetchDocument={documents.prefetchDocument}
|
||||
belowCollection={orderedCollections[index + 1]}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
renderError={(props) => <StyledError {...props} />}
|
||||
renderItem={(item: Collection, index) => (
|
||||
<DraggableCollectionLink
|
||||
key={item.id}
|
||||
collection={item}
|
||||
activeDocument={documents.active}
|
||||
prefetchDocument={documents.prefetchDocument}
|
||||
belowCollection={orderedCollections[index + 1]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<SidebarAction action={createCollection} depth={0} />
|
||||
</Relative>
|
||||
</Header>
|
||||
</Flex>
|
||||
)}
|
||||
/>
|
||||
<SidebarAction action={createCollection} depth={0} />
|
||||
</Relative>
|
||||
</Header>
|
||||
</Flex>
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import styled from "styled-components";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
import CollectionIcon from "~/components/Icons/CollectionIcon";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { useLocationState } from "../hooks/useLocationState";
|
||||
import CollectionLink from "./CollectionLink";
|
||||
import CollectionLinkChildren from "./CollectionLinkChildren";
|
||||
import DropCursor from "./DropCursor";
|
||||
@@ -29,7 +29,7 @@ function DraggableCollectionLink({
|
||||
prefetchDocument,
|
||||
belowCollection,
|
||||
}: Props) {
|
||||
const locationSidebarContext = useLocationState();
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
const sidebarContext = useSidebarContext();
|
||||
const { ui, policies, collections } = useStores();
|
||||
const [expanded, setExpanded] = React.useState(
|
||||
|
||||
@@ -2,10 +2,11 @@ import { observer } from "mobx-react";
|
||||
import { GroupIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import Group from "~/models/Group";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import Folder from "./Folder";
|
||||
import Relative from "./Relative";
|
||||
import SharedWithMeLink from "./SharedWithMeLink";
|
||||
import SidebarContext from "./SidebarContext";
|
||||
import SidebarContext, { groupSidebarContext } from "./SidebarContext";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
|
||||
type Props = {
|
||||
@@ -14,13 +15,23 @@ type Props = {
|
||||
};
|
||||
|
||||
const GroupLink: React.FC<Props> = ({ group }) => {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
const sidebarContext = groupSidebarContext(group.id);
|
||||
const [expanded, setExpanded] = React.useState(
|
||||
locationSidebarContext === sidebarContext
|
||||
);
|
||||
|
||||
const handleDisclosureClick = React.useCallback((ev) => {
|
||||
ev?.preventDefault();
|
||||
setExpanded((e) => !e);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (locationSidebarContext === sidebarContext) {
|
||||
setExpanded(true);
|
||||
}
|
||||
}, [sidebarContext, locationSidebarContext, setExpanded]);
|
||||
|
||||
return (
|
||||
<Relative>
|
||||
<SidebarLink
|
||||
@@ -30,7 +41,7 @@ const GroupLink: React.FC<Props> = ({ group }) => {
|
||||
onClick={handleDisclosureClick}
|
||||
depth={0}
|
||||
/>
|
||||
<SidebarContext.Provider value={group.id}>
|
||||
<SidebarContext.Provider value={sidebarContext}>
|
||||
<Folder expanded={expanded}>
|
||||
{group.documentMemberships.map((membership) => (
|
||||
<SharedWithMeLink
|
||||
|
||||
@@ -9,6 +9,7 @@ import GroupMembership from "~/models/GroupMembership";
|
||||
import UserMembership from "~/models/UserMembership";
|
||||
import Fade from "~/components/Fade";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import {
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
useDropToReorderUserMembership,
|
||||
useDropToReparentDocument,
|
||||
} from "../hooks/useDragAndDrop";
|
||||
import { useLocationState } from "../hooks/useLocationState";
|
||||
import { useSidebarLabelAndIcon } from "../hooks/useSidebarLabelAndIcon";
|
||||
import DocumentLink from "./DocumentLink";
|
||||
import DropCursor from "./DropCursor";
|
||||
@@ -36,7 +36,7 @@ function SharedWithMeLink({ membership, depth = 0 }: Props) {
|
||||
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
||||
const { documentId } = membership;
|
||||
const isActiveDocument = documentId === ui.activeDocumentId;
|
||||
const locationSidebarContext = useLocationState();
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
const sidebarContext = useSidebarContext();
|
||||
const document = documentId ? documents.get(documentId) : undefined;
|
||||
|
||||
|
||||
@@ -1,9 +1,57 @@
|
||||
import * as React from "react";
|
||||
import Document from "~/models/Document";
|
||||
import User from "~/models/User";
|
||||
|
||||
export type SidebarContextType = "collections" | "starred" | string | undefined;
|
||||
export type SidebarContextType =
|
||||
| "collections"
|
||||
| "shared"
|
||||
| `group-${string}`
|
||||
| `starred-${string}`
|
||||
| undefined;
|
||||
|
||||
const SidebarContext = React.createContext<SidebarContextType>(undefined);
|
||||
|
||||
export const useSidebarContext = () => React.useContext(SidebarContext);
|
||||
|
||||
export const groupSidebarContext = (groupId: string): SidebarContextType =>
|
||||
`group-${groupId}`;
|
||||
|
||||
export const starredSidebarContext = (modelId: string): SidebarContextType =>
|
||||
`starred-${modelId}`;
|
||||
|
||||
export const determineSidebarContext = ({
|
||||
document,
|
||||
user,
|
||||
currentContext,
|
||||
}: {
|
||||
document: Document;
|
||||
user: User;
|
||||
currentContext?: SidebarContextType;
|
||||
}): SidebarContextType => {
|
||||
const isStarred = document.isStarred || !!document.collection?.isStarred;
|
||||
const preferStarred = !currentContext || currentContext.startsWith("starred");
|
||||
|
||||
if (isStarred && preferStarred) {
|
||||
const currentlyInStarredCollection =
|
||||
currentContext === starredSidebarContext(document.collectionId ?? "");
|
||||
|
||||
return document.isStarred && !currentlyInStarredCollection
|
||||
? starredSidebarContext(document.id)
|
||||
: starredSidebarContext(document.collectionId!);
|
||||
}
|
||||
|
||||
if (document.collection) {
|
||||
return "collections";
|
||||
} else if (
|
||||
user.documentMemberships.find((m) => m.documentId === document.id)
|
||||
) {
|
||||
return "shared";
|
||||
} else {
|
||||
const group = user.groupsWithDocumentMemberships.find(
|
||||
(g) => !!g.documentMemberships.find((m) => m.documentId === document.id)
|
||||
);
|
||||
return groupSidebarContext(group?.id ?? "");
|
||||
}
|
||||
};
|
||||
|
||||
export default SidebarContext;
|
||||
|
||||
@@ -15,7 +15,6 @@ import DropCursor from "./DropCursor";
|
||||
import Header from "./Header";
|
||||
import PlaceholderCollections from "./PlaceholderCollections";
|
||||
import Relative from "./Relative";
|
||||
import SidebarContext from "./SidebarContext";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import StarredLink from "./StarredLink";
|
||||
|
||||
@@ -42,48 +41,46 @@ function Starred() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value="starred">
|
||||
<Flex column>
|
||||
<Header id="starred" title={t("Starred")}>
|
||||
<Relative>
|
||||
{reorderStarProps.isDragging && (
|
||||
<DropCursor
|
||||
isActiveDrop={reorderStarProps.isOverCursor}
|
||||
innerRef={dropToReorder}
|
||||
position="top"
|
||||
/>
|
||||
)}
|
||||
{createStarProps.isDragging && (
|
||||
<DropCursor
|
||||
isActiveDrop={createStarProps.isOverCursor}
|
||||
innerRef={dropToStarRef}
|
||||
position="top"
|
||||
/>
|
||||
)}
|
||||
{stars.orderedData
|
||||
.slice(0, page * STARRED_PAGINATION_LIMIT)
|
||||
.map((star) => (
|
||||
<StarredLink key={star.id} star={star} />
|
||||
))}
|
||||
{!end && (
|
||||
<SidebarLink
|
||||
onClick={next}
|
||||
label={`${t("Show more")}…`}
|
||||
disabled={stars.isFetching}
|
||||
depth={0}
|
||||
/>
|
||||
)}
|
||||
{loading && (
|
||||
<Flex column>
|
||||
<DelayedMount>
|
||||
<PlaceholderCollections />
|
||||
</DelayedMount>
|
||||
</Flex>
|
||||
)}
|
||||
</Relative>
|
||||
</Header>
|
||||
</Flex>
|
||||
</SidebarContext.Provider>
|
||||
<Flex column>
|
||||
<Header id="starred" title={t("Starred")}>
|
||||
<Relative>
|
||||
{reorderStarProps.isDragging && (
|
||||
<DropCursor
|
||||
isActiveDrop={reorderStarProps.isOverCursor}
|
||||
innerRef={dropToReorder}
|
||||
position="top"
|
||||
/>
|
||||
)}
|
||||
{createStarProps.isDragging && (
|
||||
<DropCursor
|
||||
isActiveDrop={createStarProps.isOverCursor}
|
||||
innerRef={dropToStarRef}
|
||||
position="top"
|
||||
/>
|
||||
)}
|
||||
{stars.orderedData
|
||||
.slice(0, page * STARRED_PAGINATION_LIMIT)
|
||||
.map((star) => (
|
||||
<StarredLink key={star.id} star={star} />
|
||||
))}
|
||||
{!end && (
|
||||
<SidebarLink
|
||||
onClick={next}
|
||||
label={`${t("Show more")}…`}
|
||||
disabled={stars.isFetching}
|
||||
depth={0}
|
||||
/>
|
||||
)}
|
||||
{loading && (
|
||||
<Flex column>
|
||||
<DelayedMount>
|
||||
<PlaceholderCollections />
|
||||
</DelayedMount>
|
||||
</Flex>
|
||||
)}
|
||||
</Relative>
|
||||
</Header>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import styled, { useTheme } from "styled-components";
|
||||
import Star from "~/models/Star";
|
||||
import Fade from "~/components/Fade";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import {
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
useDropToCreateStar,
|
||||
useDropToReorderStar,
|
||||
} from "../hooks/useDragAndDrop";
|
||||
import { useLocationState } from "../hooks/useLocationState";
|
||||
import { useSidebarLabelAndIcon } from "../hooks/useSidebarLabelAndIcon";
|
||||
import CollectionLink from "./CollectionLink";
|
||||
import CollectionLinkChildren from "./CollectionLinkChildren";
|
||||
@@ -25,7 +25,7 @@ import Folder from "./Folder";
|
||||
import Relative from "./Relative";
|
||||
import SidebarContext, {
|
||||
SidebarContextType,
|
||||
useSidebarContext,
|
||||
starredSidebarContext,
|
||||
} from "./SidebarContext";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
|
||||
@@ -39,10 +39,14 @@ function StarredLink({ star }: Props) {
|
||||
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
||||
const { documentId, collectionId } = star;
|
||||
const collection = collections.get(collectionId);
|
||||
const locationSidebarContext = useLocationState();
|
||||
const sidebarContext = useSidebarContext();
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
const sidebarContext = starredSidebarContext(
|
||||
star.documentId ?? star.collectionId
|
||||
);
|
||||
const [expanded, setExpanded] = useState(
|
||||
star.collectionId === ui.activeCollectionId &&
|
||||
(star.documentId
|
||||
? star.documentId === ui.activeDocumentId
|
||||
: star.collectionId === ui.activeCollectionId) &&
|
||||
sidebarContext === locationSidebarContext
|
||||
);
|
||||
|
||||
@@ -159,7 +163,7 @@ function StarredLink({ star }: Props) {
|
||||
}
|
||||
/>
|
||||
</Draggable>
|
||||
<SidebarContext.Provider value={document.id}>
|
||||
<SidebarContext.Provider value={sidebarContext}>
|
||||
<Relative>
|
||||
<Folder expanded={displayChildDocuments}>
|
||||
{childDocuments.map((node, index) => (
|
||||
@@ -183,7 +187,7 @@ function StarredLink({ star }: Props) {
|
||||
|
||||
if (collection) {
|
||||
return (
|
||||
<>
|
||||
<SidebarContext.Provider value={sidebarContext}>
|
||||
<Draggable key={star?.id} ref={draggableRef} $isDragging={isDragging}>
|
||||
<CollectionLink
|
||||
collection={collection}
|
||||
@@ -193,16 +197,14 @@ function StarredLink({ star }: Props) {
|
||||
isDraggingAnyCollection={reorderStarProps.isDragging}
|
||||
/>
|
||||
</Draggable>
|
||||
<SidebarContext.Provider value={collection.id}>
|
||||
<Relative>
|
||||
<CollectionLinkChildren
|
||||
collection={collection}
|
||||
expanded={displayChildDocuments}
|
||||
/>
|
||||
{cursor}
|
||||
</Relative>
|
||||
</SidebarContext.Provider>
|
||||
</>
|
||||
<Relative>
|
||||
<CollectionLinkChildren
|
||||
collection={collection}
|
||||
expanded={displayChildDocuments}
|
||||
/>
|
||||
{cursor}
|
||||
</Relative>
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import Tippy, { TippyProps } from "@tippyjs/react";
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
import styled, { createGlobalStyle } from "styled-components";
|
||||
import { roundArrow } from "tippy.js";
|
||||
import { s } from "@shared/styles";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import { useTooltipContext } from "./TooltipContext";
|
||||
@@ -12,6 +14,13 @@ export type Props = Omit<TippyProps, "content" | "theme"> & {
|
||||
shortcut?: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* A tooltip component that wraps Tippy and provides a consistent look and feel. Optionally
|
||||
* displays a keyboard shortcut next to the content.
|
||||
*
|
||||
* Wrap this component in a TooltipProvider to allow multiple tooltips to share the same
|
||||
* singleton instance (delay, animation, etc).
|
||||
*/
|
||||
function Tooltip({ shortcut, content: tooltip, delay = 500, ...rest }: Props) {
|
||||
const isMobile = useMobile();
|
||||
const singleton = useTooltipContext();
|
||||
@@ -25,28 +34,49 @@ function Tooltip({ shortcut, content: tooltip, delay = 500, ...rest }: Props) {
|
||||
if (shortcut) {
|
||||
content = (
|
||||
<>
|
||||
{tooltip} · <Shortcut>{shortcut}</Shortcut>
|
||||
{tooltip}{" "}
|
||||
{typeof shortcut === "string" ? (
|
||||
shortcut
|
||||
.split("+")
|
||||
.map((key) => (
|
||||
<Shortcut key={key}>
|
||||
{key.length === 1 ? key.toUpperCase() : key}
|
||||
</Shortcut>
|
||||
))
|
||||
) : (
|
||||
<Shortcut>{shortcut}</Shortcut>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tippy content={content} delay={delay} singleton={singleton} {...rest} />
|
||||
<Tippy
|
||||
arrow={roundArrow}
|
||||
content={content}
|
||||
delay={delay}
|
||||
animation="shift-away"
|
||||
singleton={singleton}
|
||||
duration={[200, 150]}
|
||||
inertia
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Shortcut = styled.kbd`
|
||||
position: relative;
|
||||
top: -2px;
|
||||
top: -1px;
|
||||
|
||||
margin-left: 2px;
|
||||
display: inline-block;
|
||||
padding: 2px 4px;
|
||||
font: 10px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier,
|
||||
monospace;
|
||||
font-size: 12px;
|
||||
font-family: ${s("fontFamilyMono")};
|
||||
line-height: 10px;
|
||||
color: ${s("tooltipBackground")};
|
||||
color: ${s("tooltipText")};
|
||||
border: 1px solid ${(props) => transparentize(0.75, props.theme.tooltipText)};
|
||||
vertical-align: middle;
|
||||
background-color: ${s("tooltipText")};
|
||||
border-radius: 3px;
|
||||
`;
|
||||
|
||||
|
||||
@@ -11,9 +11,13 @@ export function useTooltipContext() {
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
/** Props to pass to the Tippy component */
|
||||
tippyProps?: TippyProps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap a collection of tooltips in a provider to allow them to share the same singleton instance.
|
||||
*/
|
||||
export function TooltipProvider({ children, tippyProps }: Props) {
|
||||
const [source, target] = useSingleton();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { usePopoverState } from "reakit/Popover";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import { depths, s } from "@shared/styles";
|
||||
import { altDisplay, isModKey, metaDisplay } from "@shared/utils/keyboard";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input from "~/components/Input";
|
||||
@@ -21,7 +22,6 @@ import Tooltip from "~/components/Tooltip";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import useOnClickOutside from "~/hooks/useOnClickOutside";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import { altDisplay, isModKey, metaDisplay } from "~/utils/keyboard";
|
||||
import { useEditor } from "./EditorContext";
|
||||
|
||||
type Props = {
|
||||
@@ -314,7 +314,8 @@ export default function FindAndReplace({
|
||||
style={style}
|
||||
aria-label={t("Find and replace")}
|
||||
scrollable={false}
|
||||
width={420}
|
||||
minWidth={420}
|
||||
width={0}
|
||||
>
|
||||
<Content column>
|
||||
<Flex gap={4}>
|
||||
|
||||
@@ -108,8 +108,9 @@ function ToolbarMenu(props: Props) {
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={item.label === item.tooltip ? undefined : item.tooltip}
|
||||
key={index}
|
||||
shortcut={item.shortcut}
|
||||
content={item.label === item.tooltip ? undefined : item.tooltip}
|
||||
>
|
||||
{item.children ? (
|
||||
<ToolbarDropdown active={isActive && !item.label} item={item} />
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
|
||||
type Props = {
|
||||
/** The content to display in the tooltip. */
|
||||
content?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
import Tooltip, { Props } from "~/components/Tooltip";
|
||||
|
||||
const WrappedTooltip: React.FC<Props> = ({
|
||||
children,
|
||||
|
||||
@@ -26,8 +26,8 @@ import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Image from "@shared/editor/components/Img";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { metaDisplay } from "@shared/utils/keyboard";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
import { metaDisplay } from "~/utils/keyboard";
|
||||
|
||||
const Img = styled(Image)`
|
||||
border-radius: 2px;
|
||||
|
||||
@@ -28,6 +28,7 @@ import { isInList } from "@shared/editor/queries/isInList";
|
||||
import { isMarkActive } from "@shared/editor/queries/isMarkActive";
|
||||
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { metaDisplay } from "@shared/utils/keyboard";
|
||||
import CircleIcon from "~/components/Icons/CircleIcon";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
|
||||
@@ -63,6 +64,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "strong",
|
||||
tooltip: dictionary.strong,
|
||||
shortcut: `${metaDisplay}+B`,
|
||||
icon: <BoldIcon />,
|
||||
active: isMarkActive(schema.marks.strong),
|
||||
visible: !isCode && (!isMobile || !isEmpty),
|
||||
@@ -70,6 +72,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "em",
|
||||
tooltip: dictionary.em,
|
||||
shortcut: `${metaDisplay}+I`,
|
||||
icon: <ItalicIcon />,
|
||||
active: isMarkActive(schema.marks.em),
|
||||
visible: !isCode && (!isMobile || !isEmpty),
|
||||
@@ -77,12 +80,14 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "strikethrough",
|
||||
tooltip: dictionary.strikethrough,
|
||||
shortcut: `${metaDisplay}+D`,
|
||||
icon: <StrikethroughIcon />,
|
||||
active: isMarkActive(schema.marks.strikethrough),
|
||||
visible: !isCode && (!isMobile || !isEmpty),
|
||||
},
|
||||
{
|
||||
tooltip: dictionary.mark,
|
||||
shortcut: `${metaDisplay}+Ctrl+H`,
|
||||
icon: highlight ? (
|
||||
<CircleIcon color={highlight.mark.attrs.color || Highlight.colors[0]} />
|
||||
) : (
|
||||
@@ -114,6 +119,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "code_inline",
|
||||
tooltip: dictionary.codeInline,
|
||||
shortcut: `${metaDisplay}+E`,
|
||||
icon: <CodeIcon />,
|
||||
active: isMarkActive(schema.marks.code_inline),
|
||||
visible: !isCodeBlock && (!isMobile || !isEmpty),
|
||||
@@ -125,6 +131,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "heading",
|
||||
tooltip: dictionary.heading,
|
||||
shortcut: `⇧+Ctrl+1`,
|
||||
icon: <Heading1Icon />,
|
||||
active: isNodeActive(schema.nodes.heading, { level: 1 }),
|
||||
attrs: { level: 1 },
|
||||
@@ -133,6 +140,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "heading",
|
||||
tooltip: dictionary.subheading,
|
||||
shortcut: `⇧+Ctrl+2`,
|
||||
icon: <Heading2Icon />,
|
||||
active: isNodeActive(schema.nodes.heading, { level: 2 }),
|
||||
attrs: { level: 2 },
|
||||
@@ -141,6 +149,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "heading",
|
||||
tooltip: dictionary.subheading,
|
||||
shortcut: `⇧+Ctrl+3`,
|
||||
icon: <Heading3Icon />,
|
||||
active: isNodeActive(schema.nodes.heading, { level: 3 }),
|
||||
attrs: { level: 3 },
|
||||
@@ -149,6 +158,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "blockquote",
|
||||
tooltip: dictionary.quote,
|
||||
shortcut: `${metaDisplay}+]`,
|
||||
icon: <BlockQuoteIcon />,
|
||||
active: isNodeActive(schema.nodes.blockquote),
|
||||
attrs: { level: 2 },
|
||||
@@ -161,6 +171,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "checkbox_list",
|
||||
tooltip: dictionary.checkboxList,
|
||||
shortcut: `⇧+Ctrl+7`,
|
||||
icon: <TodoListIcon />,
|
||||
keywords: "checklist checkbox task",
|
||||
active: isNodeActive(schema.nodes.checkbox_list),
|
||||
@@ -169,6 +180,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "bullet_list",
|
||||
tooltip: dictionary.bulletList,
|
||||
shortcut: `⇧+Ctrl+8`,
|
||||
icon: <BulletedListIcon />,
|
||||
active: isNodeActive(schema.nodes.bullet_list),
|
||||
visible: !isCodeBlock && (!isMobile || isEmpty),
|
||||
@@ -176,6 +188,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "ordered_list",
|
||||
tooltip: dictionary.orderedList,
|
||||
shortcut: `⇧+Ctrl+9`,
|
||||
icon: <OrderedListIcon />,
|
||||
active: isNodeActive(schema.nodes.ordered_list),
|
||||
visible: !isCodeBlock && (!isMobile || isEmpty),
|
||||
@@ -183,6 +196,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "outdentList",
|
||||
tooltip: dictionary.outdent,
|
||||
shortcut: `⇧+Tab`,
|
||||
icon: <OutdentIcon />,
|
||||
visible:
|
||||
isMobile && isInList(state, { types: ["ordered_list", "bullet_list"] }),
|
||||
@@ -190,6 +204,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "indentList",
|
||||
tooltip: dictionary.indent,
|
||||
shortcut: `Tab`,
|
||||
icon: <IndentIcon />,
|
||||
visible:
|
||||
isMobile && isInList(state, { types: ["ordered_list", "bullet_list"] }),
|
||||
@@ -197,12 +212,14 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "outdentCheckboxList",
|
||||
tooltip: dictionary.outdent,
|
||||
shortcut: `⇧+Tab`,
|
||||
icon: <OutdentIcon />,
|
||||
visible: isMobile && isInList(state, { types: ["checkbox_list"] }),
|
||||
},
|
||||
{
|
||||
name: "indentCheckboxList",
|
||||
tooltip: dictionary.indent,
|
||||
shortcut: `Tab`,
|
||||
icon: <IndentIcon />,
|
||||
visible: isMobile && isInList(state, { types: ["checkbox_list"] }),
|
||||
},
|
||||
@@ -213,6 +230,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "link",
|
||||
tooltip: dictionary.createLink,
|
||||
shortcut: `${metaDisplay}+K`,
|
||||
icon: <LinkIcon />,
|
||||
attrs: { href: "" },
|
||||
visible: !isCodeBlock && (!isMobile || !isEmpty),
|
||||
@@ -220,6 +238,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "comment",
|
||||
tooltip: dictionary.comment,
|
||||
shortcut: `${metaDisplay}+⌥+M`,
|
||||
icon: <CommentIcon />,
|
||||
label: isCodeBlock ? dictionary.comment : undefined,
|
||||
active: isMarkActive(schema.marks.comment, { resolved: false }),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import { isInternalUrl } from "@shared/utils/urls";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
import { sharedDocumentPath } from "~/utils/routeHelpers";
|
||||
import { isHash } from "~/utils/urls";
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function useEditorClickHandlers({ shareId }: Params) {
|
||||
}
|
||||
|
||||
if (!isModKey(event) && !event.shiftKey) {
|
||||
history.push(navigateTo);
|
||||
history.push(navigateTo, { sidebarContext: "collections" }); // optimistic preference of "collections"
|
||||
} else {
|
||||
window.open(navigateTo, "_blank");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import isTextInput from "~/utils/isTextInput";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
|
||||
type Callback = (event: KeyboardEvent) => void;
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { SidebarContextType } from "../components/SidebarContext";
|
||||
import { SidebarContextType } from "../components/Sidebar/components/SidebarContext";
|
||||
|
||||
/**
|
||||
* Hook to retrieve the sidebar context from the current location state.
|
||||
*/
|
||||
export function useLocationState() {
|
||||
export function useLocationSidebarContext() {
|
||||
const location = useLocation<{
|
||||
sidebarContext?: SidebarContextType;
|
||||
}>();
|
||||
+15
-18
@@ -20,7 +20,6 @@ import { initI18n } from "~/utils/i18n";
|
||||
import Desktop from "./components/DesktopEventHandler";
|
||||
import LazyPolyfill from "./components/LazyPolyfills";
|
||||
import PageScroll from "./components/PageScroll";
|
||||
import { TooltipProvider } from "./components/TooltipContext";
|
||||
import Routes from "./routes";
|
||||
import Logger from "./utils/Logger";
|
||||
import { PluginManager } from "./utils/PluginManager";
|
||||
@@ -56,23 +55,21 @@ if (element) {
|
||||
<Theme>
|
||||
<ErrorBoundary showTitle>
|
||||
<KBarProvider actions={[]} options={commandBarOptions}>
|
||||
<TooltipProvider>
|
||||
<LazyPolyfill>
|
||||
<LazyMotion features={loadFeatures}>
|
||||
<Router history={history}>
|
||||
<PageScroll>
|
||||
<PageTheme />
|
||||
<ScrollToTop>
|
||||
<Routes />
|
||||
</ScrollToTop>
|
||||
<Toasts />
|
||||
<Dialogs />
|
||||
<Desktop />
|
||||
</PageScroll>
|
||||
</Router>
|
||||
</LazyMotion>
|
||||
</LazyPolyfill>
|
||||
</TooltipProvider>
|
||||
<LazyPolyfill>
|
||||
<LazyMotion features={loadFeatures}>
|
||||
<Router history={history}>
|
||||
<PageScroll>
|
||||
<PageTheme />
|
||||
<ScrollToTop>
|
||||
<Routes />
|
||||
</ScrollToTop>
|
||||
<Toasts />
|
||||
<Dialogs />
|
||||
<Desktop />
|
||||
</PageScroll>
|
||||
</Router>
|
||||
</LazyMotion>
|
||||
</LazyPolyfill>
|
||||
</KBarProvider>
|
||||
</ErrorBoundary>
|
||||
</Theme>
|
||||
|
||||
@@ -136,14 +136,14 @@ type MenuContentProps = {
|
||||
showToggleEmbeds?: boolean;
|
||||
};
|
||||
|
||||
const MenuContent: React.FC<MenuContentProps> = ({
|
||||
const MenuContent: React.FC<MenuContentProps> = observer(function MenuContent_({
|
||||
onOpen,
|
||||
onClose,
|
||||
onFindAndReplace,
|
||||
onRename,
|
||||
showDisplayOptions,
|
||||
showToggleEmbeds,
|
||||
}) => {
|
||||
}) {
|
||||
const user = useCurrentUser();
|
||||
const { model: document, menuState } = useMenuContext<Document>();
|
||||
const can = usePolicy(document);
|
||||
@@ -348,7 +348,7 @@ const MenuContent: React.FC<MenuContentProps> = ({
|
||||
)}
|
||||
</ContextMenu>
|
||||
) : null;
|
||||
};
|
||||
});
|
||||
|
||||
function DocumentMenu({
|
||||
document,
|
||||
|
||||
@@ -7,12 +7,14 @@ import { s } from "@shared/styles";
|
||||
import { UserPreference } from "@shared/types";
|
||||
import InputSelect from "~/components/InputSelect";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useQuery from "~/hooks/useQuery";
|
||||
import { CommentSortType } from "~/types";
|
||||
|
||||
const CommentSortMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const history = useHistory();
|
||||
const user = useCurrentUser();
|
||||
const params = useQuery();
|
||||
@@ -39,6 +41,7 @@ const CommentSortMenu = () => {
|
||||
resolved: "",
|
||||
}),
|
||||
pathname: location.pathname,
|
||||
state: { sidebarContext },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -49,6 +52,7 @@ const CommentSortMenu = () => {
|
||||
resolved: undefined,
|
||||
}),
|
||||
pathname: location.pathname,
|
||||
state: { sidebarContext },
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useDocumentContext } from "~/components/DocumentContext";
|
||||
import Facepile from "~/components/Facepile";
|
||||
import Fade from "~/components/Fade";
|
||||
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useOnClickOutside from "~/hooks/useOnClickOutside";
|
||||
import usePersistedState from "~/hooks/usePersistedState";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
@@ -61,6 +62,7 @@ function CommentThread({
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const [autoFocus, setAutoFocus] = React.useState(thread.isNew);
|
||||
|
||||
const can = usePolicy(document);
|
||||
@@ -101,7 +103,10 @@ function CommentThread({
|
||||
history.replace({
|
||||
search: location.search,
|
||||
pathname: location.pathname,
|
||||
state: { commentId: undefined },
|
||||
state: {
|
||||
commentId: undefined,
|
||||
sidebarContext,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -115,7 +120,10 @@ function CommentThread({
|
||||
// Clear any commentId from the URL when explicitly focusing a thread
|
||||
search: thread.isResolved ? "resolved=" : "",
|
||||
pathname: location.pathname.replace(/\/history$/, ""),
|
||||
state: { commentId: thread.id },
|
||||
state: {
|
||||
commentId: thread.id,
|
||||
sidebarContext,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import history from "~/utils/history";
|
||||
import { matchDocumentEdit, settingsPath } from "~/utils/routeHelpers";
|
||||
import Loading from "./Loading";
|
||||
import MarkAsViewed from "./MarkAsViewed";
|
||||
|
||||
type Params = {
|
||||
/** The document urlId + slugified title */
|
||||
@@ -222,16 +223,19 @@ function DataLoader({ match, children }: Props) {
|
||||
const readOnly = !isEditing || !canEdit;
|
||||
|
||||
return (
|
||||
<React.Fragment key={canEdit ? "edit" : "read"}>
|
||||
{children({
|
||||
document,
|
||||
revision,
|
||||
abilities: can,
|
||||
readOnly,
|
||||
onCreateLink,
|
||||
sharedTree,
|
||||
})}
|
||||
</React.Fragment>
|
||||
<>
|
||||
{!shareId && !revision && <MarkAsViewed document={document} />}
|
||||
<React.Fragment key={canEdit ? "edit" : "read"}>
|
||||
{children({
|
||||
document,
|
||||
revision,
|
||||
abilities: can,
|
||||
readOnly,
|
||||
onCreateLink,
|
||||
sharedTree,
|
||||
})}
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
|
||||
import { TextHelper } from "@shared/utils/TextHelper";
|
||||
import { parseDomain } from "@shared/utils/domains";
|
||||
import { determineIconType } from "@shared/utils/icon";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import Document from "~/models/Document";
|
||||
import Revision from "~/models/Revision";
|
||||
@@ -41,12 +42,12 @@ import LoadingIndicator from "~/components/LoadingIndicator";
|
||||
import PageTitle from "~/components/PageTitle";
|
||||
import PlaceholderDocument from "~/components/PlaceholderDocument";
|
||||
import RegisterKeyDown from "~/components/RegisterKeyDown";
|
||||
import { SidebarContextType } from "~/components/Sidebar/components/SidebarContext";
|
||||
import withStores from "~/components/withStores";
|
||||
import type { Editor as TEditor } from "~/editor";
|
||||
import { SearchResult } from "~/editor/components/LinkEditor";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import { emojiToUrl } from "~/utils/emoji";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
|
||||
import {
|
||||
documentHistoryPath,
|
||||
@@ -58,7 +59,6 @@ import Contents from "./Contents";
|
||||
import Editor from "./Editor";
|
||||
import Header from "./Header";
|
||||
import KeyboardShortcutsButton from "./KeyboardShortcutsButton";
|
||||
import MarkAsViewed from "./MarkAsViewed";
|
||||
import { MeasuredContainer } from "./MeasuredContainer";
|
||||
import Notices from "./Notices";
|
||||
import PublicReferences from "./PublicReferences";
|
||||
@@ -77,6 +77,7 @@ type LocationState = {
|
||||
title?: string;
|
||||
restore?: boolean;
|
||||
revisionId?: string;
|
||||
sidebarContext?: SidebarContextType;
|
||||
};
|
||||
|
||||
type Props = WithTranslation &
|
||||
@@ -252,7 +253,10 @@ class DocumentScene extends React.Component<Props> {
|
||||
const { document, abilities } = this.props;
|
||||
|
||||
if (abilities.update) {
|
||||
this.props.history.push(documentEditPath(document));
|
||||
this.props.history.push({
|
||||
pathname: documentEditPath(document),
|
||||
state: { sidebarContext: this.props.location.state?.sidebarContext },
|
||||
});
|
||||
}
|
||||
} else if (this.editor.current?.isBlurred) {
|
||||
ev.preventDefault();
|
||||
@@ -271,9 +275,15 @@ class DocumentScene extends React.Component<Props> {
|
||||
const { document, location } = this.props;
|
||||
|
||||
if (location.pathname.endsWith("history")) {
|
||||
this.props.history.push(document.url);
|
||||
this.props.history.push({
|
||||
pathname: document.url,
|
||||
state: { sidebarContext: this.props.location.state?.sidebarContext },
|
||||
});
|
||||
} else {
|
||||
this.props.history.push(documentHistoryPath(document));
|
||||
this.props.history.push({
|
||||
pathname: documentHistoryPath(document),
|
||||
state: { sidebarContext: this.props.location.state?.sidebarContext },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -339,10 +349,16 @@ class DocumentScene extends React.Component<Props> {
|
||||
this.isEditorDirty = false;
|
||||
|
||||
if (options.done) {
|
||||
this.props.history.push(savedDocument.url);
|
||||
this.props.history.push({
|
||||
pathname: savedDocument.url,
|
||||
state: { sidebarContext: this.props.location.state?.sidebarContext },
|
||||
});
|
||||
this.props.ui.setActiveDocument(savedDocument);
|
||||
} else if (document.isNew) {
|
||||
this.props.history.push(documentEditPath(savedDocument));
|
||||
this.props.history.push({
|
||||
pathname: documentEditPath(savedDocument),
|
||||
state: { sidebarContext: this.props.location.state?.sidebarContext },
|
||||
});
|
||||
this.props.ui.setActiveDocument(savedDocument);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -396,7 +412,10 @@ class DocumentScene extends React.Component<Props> {
|
||||
|
||||
goBack = () => {
|
||||
if (!this.props.readOnly) {
|
||||
this.props.history.push(this.props.document.url);
|
||||
this.props.history.push({
|
||||
pathname: this.props.document.url,
|
||||
state: { sidebarContext: this.props.location.state?.sidebarContext },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -563,7 +582,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
canUpdate={abilities.update}
|
||||
canComment={abilities.comment}
|
||||
>
|
||||
{shareId && (
|
||||
{shareId ? (
|
||||
<ReferencesWrapper>
|
||||
<PublicReferences
|
||||
shareId={shareId}
|
||||
@@ -571,15 +590,11 @@ class DocumentScene extends React.Component<Props> {
|
||||
sharedTree={this.props.sharedTree}
|
||||
/>
|
||||
</ReferencesWrapper>
|
||||
)}
|
||||
{!isShare && !revision && (
|
||||
<>
|
||||
<MarkAsViewed document={document} />
|
||||
<ReferencesWrapper>
|
||||
<References document={document} />
|
||||
</ReferencesWrapper>
|
||||
</>
|
||||
)}
|
||||
) : !revision ? (
|
||||
<ReferencesWrapper>
|
||||
<References document={document} />
|
||||
</ReferencesWrapper>
|
||||
) : null}
|
||||
</Editor>
|
||||
</MeasuredContainer>
|
||||
</>
|
||||
|
||||
@@ -11,6 +11,7 @@ import Revision from "~/models/Revision";
|
||||
import DocumentMeta from "~/components/DocumentMeta";
|
||||
import Fade from "~/components/Fade";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { documentPath, documentInsightsPath } from "~/utils/routeHelpers";
|
||||
@@ -27,6 +28,7 @@ function TitleDocumentMeta({ to, document, revision, ...rest }: Props) {
|
||||
const { views, comments, ui } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const match = useRouteMatch();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const team = useCurrentTeam();
|
||||
const documentViews = useObserver(() => views.inDocument(document.id));
|
||||
const totalViewers = documentViews.length;
|
||||
@@ -45,7 +47,10 @@ function TitleDocumentMeta({ to, document, revision, ...rest }: Props) {
|
||||
<>
|
||||
•
|
||||
<CommentLink
|
||||
to={documentPath(document)}
|
||||
to={{
|
||||
pathname: documentPath(document),
|
||||
state: { sidebarContext },
|
||||
}}
|
||||
onClick={() => ui.toggleComments()}
|
||||
>
|
||||
<CommentIcon size={18} />
|
||||
@@ -62,9 +67,13 @@ function TitleDocumentMeta({ to, document, revision, ...rest }: Props) {
|
||||
<Wrapper>
|
||||
•
|
||||
<Link
|
||||
to={
|
||||
match.url === insightsPath ? documentPath(document) : insightsPath
|
||||
}
|
||||
to={{
|
||||
pathname:
|
||||
match.url === insightsPath
|
||||
? documentPath(document)
|
||||
: insightsPath,
|
||||
state: { sidebarContext },
|
||||
}}
|
||||
>
|
||||
{t("Viewed by")}{" "}
|
||||
{onlyYou
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getCurrentDateTimeAsString,
|
||||
getCurrentTimeAsString,
|
||||
} from "@shared/utils/date";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import { DocumentValidation } from "@shared/validations";
|
||||
import ContentEditable, { RefHandle } from "~/components/ContentEditable";
|
||||
import { useDocumentContext } from "~/components/DocumentContext";
|
||||
@@ -22,7 +23,6 @@ import Icon, { IconTitleWrapper } from "~/components/Icon";
|
||||
import { PopoverButton } from "~/components/IconPicker/components/PopoverButton";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import { isModKey } from "~/utils/keyboard";
|
||||
|
||||
const IconPicker = React.lazy(() => import("~/components/IconPicker"));
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import SmartText from "~/editor/extensions/SmartText";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useFocusedComment from "~/hooks/useFocusedComment";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useQuery from "~/hooks/useQuery";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -82,6 +83,7 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
||||
const user = useCurrentUser({ rejectOnEmpty: false });
|
||||
const team = useCurrentTeam({ rejectOnEmpty: false });
|
||||
const history = useHistory();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const params = useQuery();
|
||||
const {
|
||||
document,
|
||||
@@ -113,12 +115,15 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
||||
history.replace({
|
||||
search: focusedComment.isResolved ? "resolved=" : "",
|
||||
pathname: location.pathname,
|
||||
state: { commentId: focusedComment.id },
|
||||
state: {
|
||||
commentId: focusedComment.id,
|
||||
sidebarContext,
|
||||
},
|
||||
});
|
||||
}
|
||||
ui.set({ commentsExpanded: true });
|
||||
}
|
||||
}, [focusedComment, ui, document.id, history, params]);
|
||||
}, [focusedComment, ui, document.id, history, params, sidebarContext]);
|
||||
|
||||
// Save document when blurring title, but delay so that if clicking on a
|
||||
// button this is allowed to execute first.
|
||||
@@ -143,10 +148,10 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
||||
(commentId: string) => {
|
||||
history.replace({
|
||||
pathname: window.location.pathname.replace(/\/history$/, ""),
|
||||
state: { commentId },
|
||||
state: { commentId, sidebarContext },
|
||||
});
|
||||
},
|
||||
[history]
|
||||
[history, sidebarContext]
|
||||
);
|
||||
|
||||
// Create a Comment model in local store when a comment mark is created, this
|
||||
@@ -171,10 +176,10 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
||||
|
||||
history.replace({
|
||||
pathname: window.location.pathname.replace(/\/history$/, ""),
|
||||
state: { commentId },
|
||||
state: { commentId, sidebarContext },
|
||||
});
|
||||
},
|
||||
[comments, user?.id, props.id, history]
|
||||
[comments, user?.id, props.id, history, sidebarContext]
|
||||
);
|
||||
|
||||
// Soft delete the Comment model when associated mark is totally removed.
|
||||
@@ -238,11 +243,13 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
||||
{!shareId && (
|
||||
<DocumentMeta
|
||||
document={document}
|
||||
to={
|
||||
match.path === matchDocumentHistory
|
||||
? documentPath(document)
|
||||
: documentHistoryPath(document)
|
||||
}
|
||||
to={{
|
||||
pathname:
|
||||
match.path === matchDocumentHistory
|
||||
? documentPath(document)
|
||||
: documentHistoryPath(document),
|
||||
state: { sidebarContext },
|
||||
}}
|
||||
rtl={
|
||||
titleRef.current?.getComputedDirection() === "rtl" ? true : false
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import { NavigationNode } from "@shared/types";
|
||||
import { altDisplay, metaDisplay } from "@shared/utils/keyboard";
|
||||
import { Theme } from "~/stores/UiStore";
|
||||
import Document from "~/models/Document";
|
||||
import Revision from "~/models/Revision";
|
||||
@@ -35,6 +36,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import useEditingFocus from "~/hooks/useEditingFocus";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -42,7 +44,6 @@ import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import NewChildDocumentMenu from "~/menus/NewChildDocumentMenu";
|
||||
import TableOfContentsMenu from "~/menus/TableOfContentsMenu";
|
||||
import TemplatesMenu from "~/menus/TemplatesMenu";
|
||||
import { altDisplay, metaDisplay } from "~/utils/keyboard";
|
||||
import { documentEditPath } from "~/utils/routeHelpers";
|
||||
import ObservingBanner from "./ObservingBanner";
|
||||
import PublicBreadcrumb from "./PublicBreadcrumb";
|
||||
@@ -91,6 +92,7 @@ function DocumentHeader({
|
||||
const isRevision = !!revision;
|
||||
const isEditingFocus = useEditingFocus();
|
||||
const { hasHeadings, editor } = useDocumentContext();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const ref = React.useRef<HTMLDivElement | null>(null);
|
||||
const size = useComponentSize(ref);
|
||||
const isMobile = isMobileMedia || size.width < 700;
|
||||
@@ -132,7 +134,7 @@ function DocumentHeader({
|
||||
? t("Show contents")
|
||||
: `${t("Show contents")} (${t("available when headings are added")})`
|
||||
}
|
||||
shortcut={`ctrl+${altDisplay}+h`}
|
||||
shortcut={`Ctrl+${altDisplay}+h`}
|
||||
placement="bottom"
|
||||
>
|
||||
<Button
|
||||
@@ -155,7 +157,10 @@ function DocumentHeader({
|
||||
<Button
|
||||
as={Link}
|
||||
icon={<EditIcon />}
|
||||
to={documentEditPath(document)}
|
||||
to={{
|
||||
pathname: documentEditPath(document),
|
||||
state: { sidebarContext },
|
||||
}}
|
||||
neutral
|
||||
>
|
||||
{isMobile ? null : t("Edit")}
|
||||
|
||||
@@ -9,6 +9,7 @@ import Event from "~/models/Event";
|
||||
import Empty from "~/components/Empty";
|
||||
import PaginatedEventList from "~/components/PaginatedEventList";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { documentPath } from "~/utils/routeHelpers";
|
||||
import Sidebar from "./SidebarLayout";
|
||||
@@ -20,6 +21,7 @@ function History() {
|
||||
const { t } = useTranslation();
|
||||
const match = useRouteMatch<{ documentSlug: string }>();
|
||||
const history = useHistory();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const document = documents.getByUrl(match.params.documentSlug);
|
||||
|
||||
const eventsInDocument = document
|
||||
@@ -28,7 +30,10 @@ function History() {
|
||||
|
||||
const onCloseHistory = () => {
|
||||
if (document) {
|
||||
history.push(documentPath(document));
|
||||
history.push({
|
||||
pathname: documentPath(document),
|
||||
state: { sidebarContext },
|
||||
});
|
||||
} else {
|
||||
history.goBack();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import PaginatedList from "~/components/PaginatedList";
|
||||
import Text from "~/components/Text";
|
||||
import Time from "~/components/Time";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useTextSelection from "~/hooks/useTextSelection";
|
||||
@@ -28,6 +29,7 @@ function Insights() {
|
||||
const { t } = useTranslation();
|
||||
const match = useRouteMatch<{ documentSlug: string }>();
|
||||
const history = useHistory();
|
||||
const sidebarContext = useLocationSidebarContext();
|
||||
const selectedText = useTextSelection();
|
||||
const document = documents.getByUrl(match.params.documentSlug);
|
||||
const { editor } = useDocumentContext();
|
||||
@@ -38,7 +40,10 @@ function Insights() {
|
||||
|
||||
const onCloseInsights = () => {
|
||||
if (document) {
|
||||
history.push(documentPath(document));
|
||||
history.push({
|
||||
pathname: documentPath(document),
|
||||
state: { sidebarContext },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { determineIconType } from "@shared/utils/icon";
|
||||
import Document from "~/models/Document";
|
||||
import Flex from "~/components/Flex";
|
||||
import Icon from "~/components/Icon";
|
||||
import { SidebarContextType } from "~/components/Sidebar/components/SidebarContext";
|
||||
import { hover } from "~/styles";
|
||||
import { sharedDocumentPath } from "~/utils/routeHelpers";
|
||||
|
||||
@@ -17,6 +18,7 @@ type Props = {
|
||||
document: Document | NavigationNode;
|
||||
anchor?: string;
|
||||
showCollection?: boolean;
|
||||
sidebarContext?: SidebarContextType;
|
||||
};
|
||||
|
||||
const DocumentLink = styled(Link)`
|
||||
@@ -57,6 +59,7 @@ function ReferenceListItem({
|
||||
showCollection,
|
||||
anchor,
|
||||
shareId,
|
||||
sidebarContext,
|
||||
...rest
|
||||
}: Props) {
|
||||
const { icon, color } = document;
|
||||
@@ -73,6 +76,7 @@ function ReferenceListItem({
|
||||
hash: anchor ? `d-${anchor}` : undefined,
|
||||
state: {
|
||||
title: document.title,
|
||||
sidebarContext,
|
||||
},
|
||||
}}
|
||||
{...rest}
|
||||
|
||||
@@ -5,8 +5,11 @@ import { useLocation } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import Document from "~/models/Document";
|
||||
import Fade from "~/components/Fade";
|
||||
import { determineSidebarContext } from "~/components/Sidebar/components/SidebarContext";
|
||||
import Tab from "~/components/Tab";
|
||||
import Tabs from "~/components/Tabs";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import ReferenceListItem from "./ReferenceListItem";
|
||||
|
||||
@@ -16,7 +19,9 @@ type Props = {
|
||||
|
||||
function References({ document }: Props) {
|
||||
const { collections, documents } = useStores();
|
||||
const user = useCurrentUser();
|
||||
const location = useLocation();
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
void documents.fetchBacklinks(document.id);
|
||||
@@ -40,12 +45,24 @@ function References({ document }: Props) {
|
||||
<Component>
|
||||
<Tabs>
|
||||
{showChildDocuments && (
|
||||
<Tab to="#children" isActive={() => !isBacklinksTab}>
|
||||
<Tab
|
||||
to={{
|
||||
hash: "#children",
|
||||
state: { sidebarContext: locationSidebarContext },
|
||||
}}
|
||||
isActive={() => !isBacklinksTab}
|
||||
>
|
||||
<Trans>Documents</Trans>
|
||||
</Tab>
|
||||
)}
|
||||
{showBacklinks && (
|
||||
<Tab to="#backlinks" isActive={() => isBacklinksTab}>
|
||||
<Tab
|
||||
to={{
|
||||
hash: "#backlinks",
|
||||
state: { sidebarContext: locationSidebarContext },
|
||||
}}
|
||||
isActive={() => isBacklinksTab}
|
||||
>
|
||||
<Trans>Backlinks</Trans>
|
||||
</Tab>
|
||||
)}
|
||||
@@ -61,6 +78,11 @@ function References({ document }: Props) {
|
||||
showCollection={
|
||||
backlinkedDocument.collectionId !== document.collectionId
|
||||
}
|
||||
sidebarContext={determineSidebarContext({
|
||||
document: backlinkedDocument,
|
||||
user,
|
||||
currentContext: locationSidebarContext,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
@@ -76,6 +98,7 @@ function References({ document }: Props) {
|
||||
key={node.id}
|
||||
document={document || node}
|
||||
showCollection={false}
|
||||
sidebarContext={locationSidebarContext}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { StaticContext } from "react-router";
|
||||
import { StaticContext, useHistory } from "react-router";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { SidebarContextType } from "~/components/Sidebar/components/SidebarContext";
|
||||
import { useLastVisitedPath } from "~/hooks/useLastVisitedPath";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DataLoader from "./components/DataLoader";
|
||||
@@ -16,12 +17,14 @@ type LocationState = {
|
||||
title?: string;
|
||||
restore?: boolean;
|
||||
revisionId?: string;
|
||||
sidebarContext?: SidebarContextType;
|
||||
};
|
||||
|
||||
type Props = RouteComponentProps<Params, StaticContext, LocationState>;
|
||||
|
||||
export default function DocumentScene(props: Props) {
|
||||
const { ui } = useStores();
|
||||
const history = useHistory();
|
||||
const { documentSlug, revisionId } = props.match.params;
|
||||
const currentPath = props.location.pathname;
|
||||
const [, setLastVisitedPath] = useLastVisitedPath();
|
||||
@@ -32,6 +35,16 @@ export default function DocumentScene(props: Props) {
|
||||
|
||||
React.useEffect(() => () => ui.clearActiveDocument(), [ui]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// When opening a document directly on app load, sidebarContext will not be set.
|
||||
if (!props.location.state?.sidebarContext) {
|
||||
history.replace({
|
||||
...props.location,
|
||||
state: { ...props.location.state, sidebarContext: "collections" }, // optimistic preference of "collections"
|
||||
});
|
||||
}
|
||||
}, [props.location, history]);
|
||||
|
||||
// the urlId portion of the url does not include the slugified title
|
||||
// we only want to force a re-mount of the document component when the
|
||||
// document changes, not when the title does so only this portion is used
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import { isMac } from "@shared/utils/browser";
|
||||
import { metaDisplay, altDisplay } from "@shared/utils/keyboard";
|
||||
import Flex from "~/components/Flex";
|
||||
import InputSearch from "~/components/InputSearch";
|
||||
import Key from "~/components/Key";
|
||||
import { metaDisplay, altDisplay } from "~/utils/keyboard";
|
||||
|
||||
function KeyboardShortcuts() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import escapeRegExp from "lodash/escapeRegExp";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { MarkType } from "prosemirror-model";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { getMarksBetween } from "../queries/getMarksBetween";
|
||||
|
||||
/**
|
||||
* A factory function for creating Prosemirror plugins that automatically apply a mark to text
|
||||
* A factory function for creating a Prosemirror InputRule that automatically apply a mark to text
|
||||
* that matches a given regular expression.
|
||||
*
|
||||
* @param regexp The regular expression to match
|
||||
* @param markType The mark type to apply
|
||||
* @param getAttrs A function that returns the attributes to apply to the mark
|
||||
* Assumes the mark is not already applied, and that the regex includes two named capture groups:
|
||||
* `remove` and `text`. The `remove` group is used to determine what text should be removed from
|
||||
* the document before applying the mark, and the `text` group is used to determine what text
|
||||
* should be marked.
|
||||
*
|
||||
* @param regexp The regular expression to match.
|
||||
* @param markType The mark type to apply.
|
||||
* @param getAttrs An optional function that returns the attributes to apply to the new mark.
|
||||
* @returns The input rule
|
||||
*/
|
||||
export default function markInputRule(
|
||||
@@ -19,15 +25,20 @@ export default function markInputRule(
|
||||
): InputRule {
|
||||
return new InputRule(
|
||||
regexp,
|
||||
(state: EditorState, match: string[], start: number, end: number) => {
|
||||
(
|
||||
state: EditorState,
|
||||
match: RegExpMatchArray,
|
||||
start: number,
|
||||
end: number
|
||||
) => {
|
||||
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
|
||||
const { tr } = state;
|
||||
const captureGroup = match[match.length - 1];
|
||||
const captureGroup = match.groups?.text ?? match[match.length - 1];
|
||||
const removalGroup = match.groups?.remove ?? match[match.length - 2];
|
||||
const fullMatch = match[0];
|
||||
const startSpaces = fullMatch.search(/\S/);
|
||||
|
||||
if (captureGroup) {
|
||||
const matchStart = start + fullMatch.indexOf(captureGroup);
|
||||
const matchStart = start + fullMatch.lastIndexOf(removalGroup);
|
||||
const textStart = start + fullMatch.lastIndexOf(captureGroup);
|
||||
const textEnd = textStart + captureGroup.length;
|
||||
|
||||
@@ -43,14 +54,41 @@ export default function markInputRule(
|
||||
tr.delete(textEnd, end);
|
||||
}
|
||||
if (textStart > start) {
|
||||
tr.delete(start + startSpaces, textStart);
|
||||
tr.delete(matchStart, textStart);
|
||||
}
|
||||
end = start + startSpaces + captureGroup.length;
|
||||
|
||||
start = matchStart;
|
||||
end = start + captureGroup.length;
|
||||
}
|
||||
|
||||
tr.addMark(start + startSpaces, end, markType.create(attrs));
|
||||
tr.addMark(start, end, markType.create(attrs));
|
||||
tr.removeStoredMark(markType);
|
||||
return tr;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory function for creating a Prosemirror InputRule that automatically applies a mark to
|
||||
* text that is surrounded by a given pattern.
|
||||
*
|
||||
* @param pattern The pattern to match.
|
||||
* @param markType The mark type to apply.
|
||||
* @param getAttrs An optional function that returns the attributes to apply to the new mark.
|
||||
* @returns The input rule
|
||||
*/
|
||||
export function markInputRuleForPattern(
|
||||
pattern: string,
|
||||
markType: MarkType,
|
||||
getAttrs?: (match: string[]) => Record<string, unknown>
|
||||
): InputRule {
|
||||
const escapedPattern = escapeRegExp(pattern);
|
||||
|
||||
return markInputRule(
|
||||
new RegExp(
|
||||
`(?:^|[\\s\\[\\{\\(])(?<remove>${escapedPattern}(?<text>[^${escapedPattern}]+)${escapedPattern})$`
|
||||
),
|
||||
markType,
|
||||
getAttrs
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { MarkSpec, MarkType } from "prosemirror-model";
|
||||
import markInputRule from "../lib/markInputRule";
|
||||
import { markInputRuleForPattern } from "../lib/markInputRule";
|
||||
import Mark from "./Mark";
|
||||
|
||||
const heavyWeightRegex = /^(bold(er)?|[5-9]\d{2,})$/;
|
||||
@@ -33,7 +33,7 @@ export default class Bold extends Mark {
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: MarkType }): InputRule[] {
|
||||
return [markInputRule(/(?:\*\*)([^*]+)(?:\*\*)$/, type)];
|
||||
return [markInputRuleForPattern("**", type)];
|
||||
}
|
||||
|
||||
keys({ type }: { type: MarkType }) {
|
||||
|
||||
+47
-24
@@ -7,33 +7,13 @@ import {
|
||||
Mark as ProsemirrorMark,
|
||||
Slice,
|
||||
} from "prosemirror-model";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import { Plugin, TextSelection } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import markInputRule from "../lib/markInputRule";
|
||||
import { markInputRuleForPattern } from "../lib/markInputRule";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { isInCode } from "../queries/isInCode";
|
||||
import Mark from "./Mark";
|
||||
|
||||
function backticksFor(node: ProsemirrorNode, side: -1 | 1) {
|
||||
const ticks = /`+/g;
|
||||
let match: RegExpMatchArray | null;
|
||||
let len = 0;
|
||||
|
||||
if (node.isText) {
|
||||
while ((match = ticks.exec(node.text || ""))) {
|
||||
len = Math.max(len, match[0].length);
|
||||
}
|
||||
}
|
||||
|
||||
let result = len > 0 && side > 0 ? " `" : "`";
|
||||
for (let i = 0; i < len; i++) {
|
||||
result += "`";
|
||||
}
|
||||
if (len > 0 && side < 0) {
|
||||
result += " ";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default class Code extends Mark {
|
||||
get name() {
|
||||
return "code_inline";
|
||||
@@ -48,7 +28,7 @@ export default class Code extends Mark {
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: MarkType }) {
|
||||
return [markInputRule(/(?:^|\s)((?:`)((?:[^`]+))(?:`))$/, type)];
|
||||
return [markInputRuleForPattern("`", type)];
|
||||
}
|
||||
|
||||
keys({ type }: { type: MarkType }) {
|
||||
@@ -140,12 +120,55 @@ export default class Code extends Mark {
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Triple clicking inside of an inline code mark will select the entire
|
||||
// code mark.
|
||||
handleTripleClickOn: (view: EditorView, pos: number) => {
|
||||
const { state } = view;
|
||||
const inCodeMark = isInCode(state, { onlyMark: true });
|
||||
|
||||
if (inCodeMark) {
|
||||
const $pos = state.doc.resolve(pos);
|
||||
const before = $pos.nodeBefore?.nodeSize ?? 0;
|
||||
const after = $pos.nodeAfter?.nodeSize ?? 0;
|
||||
const $from = state.doc.resolve(pos - before);
|
||||
const $to = state.doc.resolve(pos + after);
|
||||
|
||||
view.dispatch(
|
||||
state.tr.setSelection(TextSelection.between($from, $to))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
toMarkdown() {
|
||||
function backticksFor(node: ProsemirrorNode, side: -1 | 1) {
|
||||
const ticks = /`+/g;
|
||||
let match: RegExpMatchArray | null;
|
||||
let len = 0;
|
||||
|
||||
if (node.isText) {
|
||||
while ((match = ticks.exec(node.text || ""))) {
|
||||
len = Math.max(len, match[0].length);
|
||||
}
|
||||
}
|
||||
|
||||
let result = len > 0 && side > 0 ? " `" : "`";
|
||||
for (let i = 0; i < len; i++) {
|
||||
result += "`";
|
||||
}
|
||||
if (len > 0 && side < 0) {
|
||||
result += " ";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
open(
|
||||
_state: MarkdownSerializerState,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { rgba } from "polished";
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import { MarkSpec, MarkType } from "prosemirror-model";
|
||||
import markInputRule from "../lib/markInputRule";
|
||||
import { markInputRuleForPattern } from "../lib/markInputRule";
|
||||
import markRule from "../rules/mark";
|
||||
import Mark from "./Mark";
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class Highlight extends Mark {
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: MarkType }) {
|
||||
return [markInputRule(/(?:==)([^=]+)(?:==)$/, type)];
|
||||
return [markInputRuleForPattern("==", type)];
|
||||
}
|
||||
|
||||
keys({ type }: { type: MarkType }) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { toggleMark } from "prosemirror-commands";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { MarkSpec, MarkType } from "prosemirror-model";
|
||||
import { Command } from "prosemirror-state";
|
||||
import markInputRule from "../lib/markInputRule";
|
||||
import { markInputRuleForPattern } from "../lib/markInputRule";
|
||||
import Mark from "./Mark";
|
||||
|
||||
export default class Italic extends Mark {
|
||||
@@ -25,29 +25,9 @@ export default class Italic extends Mark {
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: MarkType }): InputRule[] {
|
||||
/**
|
||||
* Due to use of snake_case strings common in docs the matching conditions
|
||||
* are a bit more strict than e.g. the ** bold syntax to help prevent
|
||||
* false positives.
|
||||
*
|
||||
* Matches:
|
||||
* _1_
|
||||
* _123_
|
||||
* (_one_
|
||||
* [_one_
|
||||
*
|
||||
* No match:
|
||||
* __
|
||||
* __123_
|
||||
* __123__
|
||||
* _123
|
||||
* one_123_
|
||||
* ONE_123_
|
||||
* 1_123_
|
||||
*/
|
||||
return [
|
||||
markInputRule(/(?:^|[^_a-zA-Z0-9])(_([^_]+)_)$/, type),
|
||||
markInputRule(/(?:^|[^*a-zA-Z0-9])(\*([^*]+)\*)$/, type),
|
||||
markInputRuleForPattern("_", type),
|
||||
markInputRuleForPattern("*", type),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import { MarkSpec, MarkType } from "prosemirror-model";
|
||||
import markInputRule from "../lib/markInputRule";
|
||||
import { markInputRuleForPattern } from "../lib/markInputRule";
|
||||
import Mark from "./Mark";
|
||||
|
||||
export default class Strikethrough extends Mark {
|
||||
@@ -36,7 +36,7 @@ export default class Strikethrough extends Mark {
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: MarkType }) {
|
||||
return [markInputRule(/~([^~]+)~$/, type)];
|
||||
return [markInputRuleForPattern("~", type)];
|
||||
}
|
||||
|
||||
toMarkdown() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import { MarkSpec, MarkType } from "prosemirror-model";
|
||||
import markInputRule from "../lib/markInputRule";
|
||||
import { markInputRuleForPattern } from "../lib/markInputRule";
|
||||
import underlinesRule from "../rules/underlines";
|
||||
import Mark from "./Mark";
|
||||
|
||||
@@ -32,7 +32,7 @@ export default class Underline extends Mark {
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: MarkType }) {
|
||||
return [markInputRule(/(?:__)([^_]+)(?:__)$/, type)];
|
||||
return [markInputRuleForPattern("__", type)];
|
||||
}
|
||||
|
||||
keys({ type }: { type: MarkType }) {
|
||||
|
||||
@@ -294,7 +294,7 @@ export default class CodeFence extends Node {
|
||||
if (
|
||||
$from.sameParent($to) &&
|
||||
event.detail === 3 &&
|
||||
isInCode(view.state)
|
||||
isInCode(view.state, { onlyBlock: true })
|
||||
) {
|
||||
dispatch?.(
|
||||
state.tr
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isMac } from "@shared/utils/browser";
|
||||
import { isMac } from "./browser";
|
||||
|
||||
export const altDisplay = isMac() ? "⌥" : "Alt";
|
||||
|
||||
@@ -6910,12 +6910,7 @@ convert-source-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity "sha1-S1YPZJ/E6RjdCrdc9JYei8iC2Co= sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
||||
|
||||
cookie@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.0.tgz#2148f68a77245d5c2c0005d264bc3e08cfa0655d"
|
||||
integrity sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==
|
||||
|
||||
cookie@~0.7.2:
|
||||
cookie@^0.7.0, cookie@~0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
||||
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
||||
|
||||
Reference in New Issue
Block a user