mirror of
https://github.com/outline/outline.git
synced 2026-06-14 03:45:00 +03:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a97863d941 | |||
| 06b48d4276 | |||
| e3e44260de | |||
| 68fe78a20e | |||
| e978f63217 | |||
| 56ceff055e | |||
| 1cf9d4c6cb | |||
| 84bc70dfd8 | |||
| c97e5fd181 | |||
| 8e56f58102 | |||
| 3347101c84 | |||
| 6d387c61c3 | |||
| 2f06ae9e48 | |||
| 2a962efe57 | |||
| 879c568a2c | |||
| 76b54fc234 | |||
| 2e53145532 | |||
| f6f831f3f6 | |||
| 3700342b35 | |||
| 0244ac2a84 | |||
| bf54e639a7 | |||
| 083e5bb7c4 | |||
| ca5c51a712 | |||
| a3b3e9e0be | |||
| ecd5afa6cd | |||
| 4562edfda0 | |||
| af74535333 | |||
| 18ffccc333 | |||
| 120a3388ed | |||
| 4689d5e88d | |||
| 2183c6d1d2 | |||
| 75f173c6ff | |||
| 8b6d9589f9 | |||
| 92bd67c104 | |||
| 84c6bd18ce | |||
| 89099ccf58 | |||
| 0536c108eb | |||
| d1ad2f20e1 | |||
| ca0e37063c | |||
| 98366e55e9 | |||
| a9c4dd43d6 | |||
| c9f25546e8 | |||
| 276ca1bbf2 |
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [20.x, 22.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
cache: 'yarn'
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn lint
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
cache: 'yarn'
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn tsc
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
cache: 'yarn'
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn test:${{ matrix.test-group }}
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
cache: 'yarn'
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn sequelize db:migrate
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
cache: 'yarn'
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Set environment to production
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ ARG APP_PATH
|
||||
WORKDIR $APP_PATH
|
||||
|
||||
# ---
|
||||
FROM node:20-slim AS runner
|
||||
FROM node:22-slim AS runner
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/outline/outline"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import invariant from "invariant";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
import {
|
||||
DownloadIcon,
|
||||
DuplicateIcon,
|
||||
@@ -84,8 +85,9 @@ export const openDocument = createAction({
|
||||
(acc, node) => [...acc, ...node.children],
|
||||
[] as NavigationNode[]
|
||||
);
|
||||
const documents = stores.documents.orderedData;
|
||||
|
||||
return nodes.map((item) => ({
|
||||
return uniqBy([...documents, ...nodes], "id").map((item) => ({
|
||||
// Note: using url which includes the slug rather than id here to bust
|
||||
// cache if the document is renamed
|
||||
id: item.url,
|
||||
@@ -1081,17 +1083,13 @@ export const openDocumentComments = createAction({
|
||||
analyticsName: "Open comments",
|
||||
section: ActiveDocumentSection,
|
||||
icon: <CommentIcon />,
|
||||
visible: ({ activeCollectionId, activeDocumentId, stores }) => {
|
||||
visible: ({ activeDocumentId, stores }) => {
|
||||
const can = stores.policies.abilities(activeDocumentId ?? "");
|
||||
const collection = activeCollectionId
|
||||
? stores.collections.get(activeCollectionId)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
!!activeDocumentId &&
|
||||
can.comment &&
|
||||
(collection?.canCreateComment ??
|
||||
!!stores.auth.team?.getPreference(TeamPreference.Commenting))
|
||||
!!stores.auth.team?.getPreference(TeamPreference.Commenting)
|
||||
);
|
||||
},
|
||||
perform: ({ activeDocumentId, stores }) => {
|
||||
|
||||
@@ -42,9 +42,7 @@ export const changeTheme = createAction({
|
||||
isContextMenu ? t("Appearance") : t("Change theme"),
|
||||
analyticsName: "Change theme",
|
||||
placeholder: ({ t }) => t("Change theme to"),
|
||||
icon: function _Icon() {
|
||||
return stores.ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />;
|
||||
},
|
||||
icon: stores.ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />,
|
||||
keywords: "appearance display",
|
||||
section: SettingsSection,
|
||||
children: [changeToLightTheme, changeToDarkTheme, changeToSystemTheme],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ArrowIcon, PlusIcon } from "outline-icons";
|
||||
import styled from "styled-components";
|
||||
import { stringToColor } from "@shared/utils/color";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import { LoginDialog } from "~/scenes/Login/components/LoginDialog";
|
||||
import TeamNew from "~/scenes/TeamNew";
|
||||
import TeamLogo from "~/components/TeamLogo";
|
||||
@@ -10,27 +9,25 @@ import { ActionContext } from "~/types";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import { TeamSection } from "../sections";
|
||||
|
||||
export const switchTeamsList = ({ stores }: { stores: RootStore }) =>
|
||||
export const switchTeamsList = ({ stores }: ActionContext) =>
|
||||
stores.auth.availableTeams?.map((session) => ({
|
||||
id: `switch-${session.id}`,
|
||||
name: session.name,
|
||||
analyticsName: "Switch workspace",
|
||||
section: TeamSection,
|
||||
keywords: "change switch workspace organization team",
|
||||
icon: function _Icon() {
|
||||
return (
|
||||
<StyledTeamLogo
|
||||
alt={session.name}
|
||||
model={{
|
||||
initial: session.name[0],
|
||||
avatarUrl: session.avatarUrl,
|
||||
id: session.id,
|
||||
color: stringToColor(session.id),
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
);
|
||||
},
|
||||
icon: (
|
||||
<StyledTeamLogo
|
||||
alt={session.name}
|
||||
model={{
|
||||
initial: session.name[0],
|
||||
avatarUrl: session.avatarUrl,
|
||||
id: session.id,
|
||||
color: stringToColor(session.id),
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
visible: ({ currentTeamId }: ActionContext) => currentTeamId !== session.id,
|
||||
perform: () => (window.location.href = session.url),
|
||||
})) ?? [];
|
||||
|
||||
@@ -45,8 +45,8 @@ export const updateUserRoleActionFactory = (user: User, role: UserRole) =>
|
||||
return UserRoleHelper.isRoleHigher(role, user.role)
|
||||
? can.promote
|
||||
: UserRoleHelper.isRoleLower(role, user.role)
|
||||
? can.demote
|
||||
: false;
|
||||
? can.demote
|
||||
: false;
|
||||
},
|
||||
perform: ({ t }) => {
|
||||
stores.dialogs.openModal({
|
||||
|
||||
@@ -27,8 +27,8 @@ export function createAction(definition: Optional<Action, "id">): Action {
|
||||
context: context.isButton
|
||||
? "button"
|
||||
: context.isCommandBar
|
||||
? "commandbar"
|
||||
: "contextmenu",
|
||||
? "commandbar"
|
||||
: "contextmenu",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ export function actionToKBar(
|
||||
|
||||
const sectionPriority =
|
||||
typeof action.section !== "string" && "priority" in action.section
|
||||
? (action.section.priority as number) ?? 0
|
||||
? ((action.section.priority as number) ?? 0)
|
||||
: 0;
|
||||
|
||||
return [
|
||||
|
||||
@@ -10,7 +10,9 @@ type Props = {
|
||||
};
|
||||
|
||||
// TODO: Refactor this component to allow injection from plugins
|
||||
const Analytics: React.FC = ({ children }: Props) => {
|
||||
const Analytics: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
// Google Analytics 3
|
||||
React.useEffect(() => {
|
||||
if (!env.GOOGLE_ANALYTICS_ID?.startsWith("UA-")) {
|
||||
|
||||
@@ -48,8 +48,10 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const AuthenticatedLayout: React.FC = ({ children }: Props) => {
|
||||
const { ui, auth, collections } = useStores();
|
||||
const AuthenticatedLayout: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const { ui, auth } = useStores();
|
||||
const location = useLocation();
|
||||
const layoutRef = React.useRef<HTMLDivElement>(null);
|
||||
const can = usePolicy(ui.activeDocumentId);
|
||||
@@ -108,16 +110,15 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
|
||||
can.comment &&
|
||||
ui.activeDocumentId &&
|
||||
ui.commentsExpanded &&
|
||||
(ui.activeCollectionId
|
||||
? collections.get(ui.activeCollectionId)?.canCreateComment
|
||||
: !!team.getPreference(TeamPreference.Commenting));
|
||||
!!team.getPreference(TeamPreference.Commenting);
|
||||
|
||||
const sidebarRight = (
|
||||
// @ts-expect-error - framer-motion v4 has TypeScript compatibility issues with React 18
|
||||
<AnimatePresence
|
||||
initial={false}
|
||||
key={ui.activeDocumentId ? "active" : "inactive"}
|
||||
>
|
||||
{(showHistory || showInsights || showComments) && (
|
||||
{showHistory || showInsights || showComments ? (
|
||||
<Route path={`/doc/${slug}`}>
|
||||
<SidebarRight>
|
||||
<React.Suspense fallback={null}>
|
||||
@@ -127,7 +128,7 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
|
||||
</React.Suspense>
|
||||
</SidebarRight>
|
||||
</Route>
|
||||
)}
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ const Badge = styled.span<{ yellow?: boolean; primary?: boolean }>`
|
||||
primary
|
||||
? theme.accentText
|
||||
: yellow
|
||||
? theme.almostBlack
|
||||
: theme.textTertiary};
|
||||
? theme.almostBlack
|
||||
: theme.textTertiary};
|
||||
border: 1px solid
|
||||
${({ primary, yellow, theme }) =>
|
||||
primary || yellow
|
||||
|
||||
@@ -176,7 +176,7 @@ const Button = <T extends React.ElementType = "button">(
|
||||
...rest
|
||||
} = props;
|
||||
const hasText = !!children || value !== undefined;
|
||||
const ic = hideIcon ? undefined : action?.icon ?? icon;
|
||||
const ic = hideIcon ? undefined : (action?.icon ?? icon);
|
||||
const hasIcon = ic !== undefined;
|
||||
|
||||
return (
|
||||
|
||||
@@ -30,7 +30,7 @@ const Content = styled.div<ContentProps>`
|
||||
`};
|
||||
`;
|
||||
|
||||
const CenteredContent: React.FC<Props> = ({
|
||||
const CenteredContent: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
maxWidth,
|
||||
...rest
|
||||
|
||||
@@ -54,9 +54,10 @@ function Collaborators(props: Props) {
|
||||
const { document } = props;
|
||||
const { observingUserId } = ui;
|
||||
const documentPresence = presence.get(document.id);
|
||||
const documentPresenceArray = documentPresence
|
||||
? Array.from(documentPresence.values())
|
||||
: [];
|
||||
const documentPresenceArray = useMemo(
|
||||
() => (documentPresence ? Array.from(documentPresence.values()) : []),
|
||||
[documentPresence]
|
||||
);
|
||||
|
||||
// Use Set for O(1) lookups and stable references
|
||||
const presentIds = useMemo(
|
||||
@@ -115,11 +116,11 @@ function Collaborators(props: Props) {
|
||||
// Memoize onClick handler to avoid inline function creation
|
||||
const handleAvatarClick = useCallback(
|
||||
(
|
||||
collaboratorId: string,
|
||||
isPresent: boolean,
|
||||
isObserving: boolean,
|
||||
isObservable: boolean
|
||||
) =>
|
||||
collaboratorId: string,
|
||||
isPresent: boolean,
|
||||
isObserving: boolean,
|
||||
isObservable: boolean
|
||||
) =>
|
||||
(ev: React.MouseEvent) => {
|
||||
if (isObservable && isPresent) {
|
||||
ev.preventDefault();
|
||||
@@ -131,7 +132,7 @@ function Collaborators(props: Props) {
|
||||
);
|
||||
|
||||
const renderAvatar = useCallback(
|
||||
({ model: collaborator, ...rest }) => {
|
||||
({ model: collaborator, ...rest }: any) => {
|
||||
const isPresent = presentIds.has(collaborator.id);
|
||||
const isEditing = editingIds.has(collaborator.id);
|
||||
const isObserving = observingUserId === collaborator.id;
|
||||
|
||||
@@ -22,7 +22,6 @@ import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { EmptySelectValue } from "~/types";
|
||||
import { createSwitchRegister } from "~/utils/forms";
|
||||
|
||||
const IconPicker = createLazyComponent(() => import("~/components/IconPicker"));
|
||||
|
||||
@@ -182,22 +181,36 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
)}
|
||||
|
||||
{team.sharing && (
|
||||
<Switch
|
||||
id="sharing"
|
||||
label={t("Public document sharing")}
|
||||
note={t(
|
||||
"Allow documents within this collection to be shared publicly on the internet."
|
||||
<Controller
|
||||
control={control}
|
||||
name="sharing"
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="sharing"
|
||||
label={t("Public document sharing")}
|
||||
note={t(
|
||||
"Allow documents within this collection to be shared publicly on the internet."
|
||||
)}
|
||||
checked={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
{...createSwitchRegister(register, "sharing")}
|
||||
/>
|
||||
)}
|
||||
|
||||
{team.getPreference(TeamPreference.Commenting) && (
|
||||
<Switch
|
||||
id="commenting"
|
||||
label={t("Commenting")}
|
||||
note={t("Allow commenting on documents within this collection.")}
|
||||
{...createSwitchRegister(register, "commenting")}
|
||||
<Controller
|
||||
control={control}
|
||||
name="commenting"
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="commenting"
|
||||
label={t("Commenting")}
|
||||
note={t("Allow commenting on documents within this collection.")}
|
||||
checked={!!field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -211,8 +224,8 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
? `${t("Saving")}…`
|
||||
: t("Save")
|
||||
: formState.isSubmitting
|
||||
? `${t("Creating")}…`
|
||||
: t("Create")}
|
||||
? `${t("Creating")}…`
|
||||
: t("Create")}
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
|
||||
@@ -11,7 +11,9 @@ type Props = {
|
||||
collection: Collection;
|
||||
};
|
||||
|
||||
export const CollectionBreadcrumb: React.FC<Props> = ({ collection }) => {
|
||||
export const CollectionBreadcrumb: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
collection,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
|
||||
@@ -52,7 +52,9 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const KBarPortal: React.FC = ({ children }: Props) => {
|
||||
const KBarPortal: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const { showing } = useKBar((state) => ({
|
||||
showing: state.visualState !== "hidden",
|
||||
}));
|
||||
|
||||
@@ -21,7 +21,7 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const ConfirmationDialog: React.FC<Props> = ({
|
||||
const ConfirmationDialog: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
onSubmit,
|
||||
children,
|
||||
submitText,
|
||||
@@ -64,7 +64,7 @@ const ConfirmationDialog: React.FC<Props> = ({
|
||||
danger={danger}
|
||||
autoFocus
|
||||
>
|
||||
{isSaving && savingText ? savingText : submitText ?? t("Confirm")}
|
||||
{isSaving && savingText ? savingText : (submitText ?? t("Confirm"))}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -21,7 +21,7 @@ type Props = {
|
||||
to?: LocationDescriptor;
|
||||
href?: string;
|
||||
target?: "_blank";
|
||||
as?: string | React.ComponentType<any>;
|
||||
as?: string | React.ComponentType<React.PropsWithChildren<any>>;
|
||||
hide?: () => void;
|
||||
level?: number;
|
||||
icon?: React.ReactNode;
|
||||
@@ -45,7 +45,7 @@ const MenuItem = (
|
||||
ref: React.Ref<HTMLAnchorElement>
|
||||
) => {
|
||||
const content = React.useCallback(
|
||||
(props) => {
|
||||
(props: any) => {
|
||||
// Preventing default mousedown otherwise menu items do not work in Firefox,
|
||||
// which triggers the hideOnClickOutside handler first via mousedown – hiding
|
||||
// and un-rendering the menu contents.
|
||||
|
||||
@@ -56,7 +56,7 @@ type Props = MenuStateReturn & {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const ContextMenu: React.FC<Props> = ({
|
||||
const ContextMenu: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
menuRef,
|
||||
children,
|
||||
onOpen,
|
||||
|
||||
@@ -138,8 +138,8 @@ function DocumentBreadcrumb(
|
||||
? output.slice(-depth)
|
||||
: output
|
||||
: depth !== undefined
|
||||
? output.slice(0, depth)
|
||||
: output;
|
||||
? output.slice(0, depth)
|
||||
: output;
|
||||
}, [t, path, category, sidebarContext, collectionNode, reverse, depth]);
|
||||
|
||||
if (!collections.isLoaded) {
|
||||
|
||||
@@ -64,7 +64,7 @@ function DocumentCard(props: Props) {
|
||||
};
|
||||
|
||||
const handleUnpin = useCallback(
|
||||
async (ev) => {
|
||||
async (ev: any) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
await pin?.delete();
|
||||
|
||||
@@ -177,7 +177,9 @@ const Actions = styled(EventBoundary)`
|
||||
color: ${s("textSecondary")};
|
||||
|
||||
${NudeButton} {
|
||||
&: ${hover}, &[aria-expanded= "true"] {
|
||||
&:
|
||||
${hover},
|
||||
&[aria-expanded= "true"] {
|
||||
background: ${s("sidebarControlHoverBackground")};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type Props = {
|
||||
to?: LocationDescriptor;
|
||||
};
|
||||
|
||||
const DocumentMeta: React.FC<Props> = ({
|
||||
const DocumentMeta: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
showPublished,
|
||||
showCollection,
|
||||
showLastViewed,
|
||||
|
||||
@@ -41,27 +41,27 @@ function EditableTitle(
|
||||
setValue(title);
|
||||
}, [title]);
|
||||
|
||||
const handleChange = React.useCallback((event) => {
|
||||
const handleChange = React.useCallback((event: any) => {
|
||||
setValue(event.target.value);
|
||||
}, []);
|
||||
|
||||
const handleDoubleClick = React.useCallback((event) => {
|
||||
const handleDoubleClick = React.useCallback((event: any) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsEditing(true);
|
||||
}, []);
|
||||
|
||||
const stopPropagation = React.useCallback((event) => {
|
||||
const stopPropagation = React.useCallback((event: any) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
const handleFocus = React.useCallback((event) => {
|
||||
const handleFocus = React.useCallback((event: any) => {
|
||||
event.target.select();
|
||||
}, []);
|
||||
|
||||
const handleSave = React.useCallback(
|
||||
async (ev) => {
|
||||
async (ev: any) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
@@ -89,7 +89,7 @@ function EditableTitle(
|
||||
);
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
async (ev) => {
|
||||
async (ev: any) => {
|
||||
if (ev.nativeEvent.isComposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
|
||||
}, [onCreateCommentMark, onDeleteCommentMark, comments.orderedData]);
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
onChange?.(event);
|
||||
updateComments();
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ type Props = WithTranslation & {
|
||||
/** Whether to show a title heading. */
|
||||
showTitle?: boolean;
|
||||
/** The wrapping component to use. */
|
||||
component?: React.ComponentType | string;
|
||||
component?: React.ComponentType<React.PropsWithChildren<unknown>> | string;
|
||||
};
|
||||
|
||||
@observer
|
||||
|
||||
+16
-11
@@ -1,10 +1,10 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import User from "~/models/User";
|
||||
import { Avatar, AvatarSize } from "~/components/Avatar";
|
||||
import Flex from "~/components/Flex";
|
||||
import Initials from "./Avatar/Initials";
|
||||
|
||||
type Props = {
|
||||
/** The users to display */
|
||||
@@ -17,9 +17,11 @@ type Props = {
|
||||
limit?: number;
|
||||
/** A component to render the avatar, defaults to Avatar. */
|
||||
renderAvatar?: React.ComponentType<
|
||||
React.ComponentProps<typeof Avatar> & {
|
||||
model: User;
|
||||
}
|
||||
React.PropsWithChildren<
|
||||
React.ComponentProps<typeof Avatar> & {
|
||||
model: User;
|
||||
}
|
||||
>
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -31,19 +33,22 @@ function Facepile({
|
||||
renderAvatar = Avatar,
|
||||
...rest
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const filtered = users.filter(Boolean).slice(-limit);
|
||||
const Component = renderAvatar;
|
||||
|
||||
if (overflow > 0) {
|
||||
filtered.unshift({
|
||||
id: "overflow",
|
||||
initial: `${users.length ? "+" : ""}${overflow}`,
|
||||
name: t(`{{count}} more user`, { count: overflow }),
|
||||
} as User);
|
||||
}
|
||||
|
||||
return (
|
||||
<Avatars {...rest}>
|
||||
{overflow > 0 && (
|
||||
<Initials size={size} content={String(overflow)}>
|
||||
{users.length ? "+" : ""}
|
||||
{overflow}
|
||||
</Initials>
|
||||
)}
|
||||
{filtered.map((model, index) => {
|
||||
const lastChild = index === 0 && overflow <= 0;
|
||||
const lastChild = index === 0;
|
||||
return (
|
||||
<Component
|
||||
key={model.id}
|
||||
|
||||
@@ -57,7 +57,7 @@ const FilterOptions = ({
|
||||
: "";
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
(option) => (
|
||||
(option: any) => (
|
||||
<MenuItem
|
||||
key={option.key}
|
||||
onClick={() => {
|
||||
|
||||
@@ -12,7 +12,7 @@ type Props = {
|
||||
onRequestClose: () => void;
|
||||
};
|
||||
|
||||
const Guide: React.FC<Props> = ({
|
||||
const Guide: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
isOpen,
|
||||
title = "Untitled",
|
||||
@@ -98,7 +98,9 @@ const Scene = styled.div`
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
transform: translateX(16px);
|
||||
transition: transform 250ms ease, opacity 250ms ease;
|
||||
transition:
|
||||
transform 250ms ease,
|
||||
opacity 250ms ease;
|
||||
|
||||
&[data-enter] {
|
||||
opacity: 1;
|
||||
|
||||
@@ -20,7 +20,8 @@ export const Preview = styled(Link)`
|
||||
cursor: ${(props: { as?: string }) =>
|
||||
props.as === "div" ? "default" : "var(--pointer)"};
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 30px 90px -20px rgba(0, 0, 0, 0.3),
|
||||
box-shadow:
|
||||
0 30px 90px -20px rgba(0, 0, 0, 0.3),
|
||||
0 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
|
||||
@@ -115,7 +115,9 @@ const CategoryName = styled(Text)`
|
||||
`;
|
||||
|
||||
const Icon = styled.svg`
|
||||
transition: color 150ms ease-in-out, fill 150ms ease-in-out;
|
||||
transition:
|
||||
color 150ms ease-in-out,
|
||||
fill 150ms ease-in-out;
|
||||
transition-delay: var(--delay);
|
||||
`;
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ export const PopoverButton = styled(NudeButton)<{ $borderOnHover?: boolean }>`
|
||||
$borderOnHover &&
|
||||
css`
|
||||
background: ${s("buttonNeutralBackground")};
|
||||
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px,
|
||||
box-shadow:
|
||||
rgba(0, 0, 0, 0.07) 0px 1px 2px,
|
||||
${s("buttonNeutralBorder")} 0 0 0 1px inset;
|
||||
`};
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const SkinTonePicker = ({
|
||||
});
|
||||
|
||||
const handleSkinClick = useCallback(
|
||||
(emojiSkin) => {
|
||||
(emojiSkin: any) => {
|
||||
menu.hide();
|
||||
onChange(emojiSkin);
|
||||
},
|
||||
|
||||
@@ -107,8 +107,8 @@ export const Outline = styled(Flex)<{
|
||||
props.hasError
|
||||
? props.theme.danger
|
||||
: props.focused
|
||||
? props.theme.inputBorderFocused
|
||||
: props.theme.inputBorder};
|
||||
? props.theme.inputBorderFocused
|
||||
: props.theme.inputBorder};
|
||||
border-radius: 4px;
|
||||
font-weight: normal;
|
||||
align-items: center;
|
||||
|
||||
@@ -17,7 +17,11 @@ type Props = Omit<InputProps, "onChange"> & {
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const InputColor: React.FC<Props> = ({ value, onChange, ...rest }: Props) => {
|
||||
const InputColor: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
value,
|
||||
onChange,
|
||||
...rest
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const menu = useMenuState({
|
||||
modal: true,
|
||||
|
||||
@@ -35,4 +35,4 @@ const Select = styled(InputSelect)`
|
||||
select {
|
||||
margin: 0;
|
||||
}
|
||||
` as React.ComponentType<SelectProps>;
|
||||
` as React.ComponentType<React.PropsWithChildren<SelectProps>>;
|
||||
|
||||
@@ -29,7 +29,7 @@ function InputSearch(
|
||||
} = props;
|
||||
|
||||
const handleFocus = React.useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
setIsFocused(true);
|
||||
onFocus?.(event);
|
||||
},
|
||||
@@ -37,7 +37,7 @@ function InputSearch(
|
||||
);
|
||||
|
||||
const handleBlur = React.useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
setIsFocused(false);
|
||||
onBlur?.(event);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as React from "react";
|
||||
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||
|
||||
export interface LazyComponent<T extends React.ComponentType<any>> {
|
||||
export interface LazyComponent<
|
||||
T extends React.ComponentType<React.PropsWithChildren<any>>,
|
||||
> {
|
||||
Component: React.LazyExoticComponent<T>;
|
||||
preload: () => Promise<{ default: T }>;
|
||||
}
|
||||
@@ -34,7 +36,9 @@ interface LazyLoadOptions {
|
||||
* MyComponent.preload();
|
||||
* ```
|
||||
*/
|
||||
export function createLazyComponent<T extends React.ComponentType<any>>(
|
||||
export function createLazyComponent<
|
||||
T extends React.ComponentType<React.PropsWithChildren<any>>,
|
||||
>(
|
||||
factory: () => Promise<{ default: T }>,
|
||||
options: LazyLoadOptions = {}
|
||||
): LazyComponent<T> {
|
||||
|
||||
@@ -9,7 +9,9 @@ type Props = {
|
||||
/**
|
||||
* Asyncronously load required polyfills. Should wrap the React tree.
|
||||
*/
|
||||
export const LazyPolyfill: React.FC = ({ children }: Props) => {
|
||||
export const LazyPolyfill: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const [isLoaded, setIsLoaded] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -12,7 +12,10 @@ export type Props = {
|
||||
format?: Partial<Record<keyof typeof locales, string>>;
|
||||
};
|
||||
|
||||
const LocaleTime: React.FC<Props> = ({ children, ...rest }: Props) => {
|
||||
const LocaleTime: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
...rest
|
||||
}: Props) => {
|
||||
const { tooltipContent, content } = useLocaleTime(rest);
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,7 @@ type Props = {
|
||||
onRequestClose: () => void;
|
||||
};
|
||||
|
||||
const Modal: React.FC<Props> = ({
|
||||
const Modal: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
isOpen,
|
||||
fullscreen = true,
|
||||
|
||||
@@ -10,7 +10,11 @@ type Props = {
|
||||
description?: JSX.Element;
|
||||
};
|
||||
|
||||
const Notice: React.FC<Props> = ({ children, icon, description }: Props) => (
|
||||
const Notice: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
icon,
|
||||
description,
|
||||
}: Props) => (
|
||||
<Container as="div">
|
||||
<Flex as="span" gap={8}>
|
||||
{icon}
|
||||
|
||||
@@ -12,7 +12,9 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const NotificationsPopover: React.FC = ({ children }: Props) => {
|
||||
const NotificationsPopover: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const scrollableRef = React.useRef<HTMLDivElement>(null);
|
||||
const closeRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
@@ -8,7 +8,6 @@ import ImageInput from "~/scenes/Settings/components/ImageInput";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input, { LabelText } from "~/components/Input";
|
||||
import { createSwitchRegister } from "~/utils/forms";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
import Switch from "../Switch";
|
||||
|
||||
@@ -116,10 +115,17 @@ export const OAuthClientForm = observer(function OAuthClientForm_({
|
||||
)}
|
||||
/>
|
||||
{isCloudHosted && (
|
||||
<Switch
|
||||
{...createSwitchRegister(register, "published")}
|
||||
label={t("Published")}
|
||||
note={t("Allow this app to be installed by other workspaces")}
|
||||
<Controller
|
||||
control={control}
|
||||
name="published"
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
label={t("Published")}
|
||||
note={t("Allow this app to be installed by other workspaces")}
|
||||
checked={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -134,8 +140,8 @@ export const OAuthClientForm = observer(function OAuthClientForm_({
|
||||
? `${t("Saving")}…`
|
||||
: t("Save")
|
||||
: formState.isSubmitting
|
||||
? `${t("Creating")}…`
|
||||
: t("Create")}
|
||||
? `${t("Creating")}…`
|
||||
: t("Create")}
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
|
||||
@@ -150,12 +150,15 @@ const PaginatedList = <T extends PaginatedItem>({
|
||||
offset,
|
||||
...options,
|
||||
});
|
||||
if (!results) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset !== 0) {
|
||||
setRenderCount((prevCount) => prevCount + limit);
|
||||
}
|
||||
|
||||
if (results && (results.length === 0 || results.length < limit)) {
|
||||
if (results.length === 0 || results.length < limit) {
|
||||
setAllowLoadMore(false);
|
||||
} else {
|
||||
setOffset((prevOffset) => prevOffset + limit);
|
||||
@@ -275,8 +278,8 @@ const PaginatedList = <T extends PaginatedItem>({
|
||||
"updatedAt" in item && item.updatedAt
|
||||
? item.updatedAt
|
||||
: "createdAt" in item && item.createdAt
|
||||
? item.createdAt
|
||||
: previousHeading;
|
||||
? item.createdAt
|
||||
: previousHeading;
|
||||
const currentHeading = dateToHeading(
|
||||
currentDate,
|
||||
t,
|
||||
@@ -305,7 +308,10 @@ const PaginatedList = <T extends PaginatedItem>({
|
||||
</ArrowKeyNavigation>
|
||||
{allowLoadMore && (
|
||||
<div style={{ height: "1px" }}>
|
||||
<Waypoint key={renderCount} onEnter={loadMoreResults} />
|
||||
<Waypoint
|
||||
key={items?.length + renderCount}
|
||||
onEnter={loadMoreResults}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -86,8 +86,8 @@ function PinnedDocuments({
|
||||
overPos === 0
|
||||
? fractionalIndex(null, overIndex)
|
||||
: activePos > overPos
|
||||
? fractionalIndex(prevIndex, overIndex)
|
||||
: fractionalIndex(overIndex, nextIndex),
|
||||
? fractionalIndex(prevIndex, overIndex)
|
||||
: fractionalIndex(overIndex, nextIndex),
|
||||
})
|
||||
.catch(() => setItems(existing));
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ const useTooltipContent = ({
|
||||
}
|
||||
};
|
||||
|
||||
const Reaction: React.FC<Props> = ({
|
||||
const Reaction: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
reaction,
|
||||
reactedUsers,
|
||||
disabled,
|
||||
|
||||
@@ -22,7 +22,7 @@ type Props = {
|
||||
picker?: React.ReactElement;
|
||||
};
|
||||
|
||||
const ReactionList: React.FC<Props> = ({
|
||||
const ReactionList: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
model,
|
||||
onAddReaction,
|
||||
onRemoveReaction,
|
||||
|
||||
@@ -29,7 +29,7 @@ type Props = {
|
||||
size?: number;
|
||||
};
|
||||
|
||||
const ReactionPicker: React.FC<Props> = ({
|
||||
const ReactionPicker: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
onSelect,
|
||||
onOpen,
|
||||
onClose,
|
||||
|
||||
@@ -19,7 +19,9 @@ type Props = {
|
||||
model: Comment;
|
||||
};
|
||||
|
||||
const ViewReactionsDialog: React.FC<Props> = ({ model }) => {
|
||||
const ViewReactionsDialog: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
model,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { users } = useStores();
|
||||
const tab = useTabState();
|
||||
|
||||
@@ -23,7 +23,7 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const Scene: React.FC<Props> = ({
|
||||
const Scene: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
title,
|
||||
icon,
|
||||
textTitle,
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
import { useKBar } from "kbar";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Minute } from "@shared/utils/time";
|
||||
import { searchDocumentsForQuery } from "~/actions/definitions/documents";
|
||||
import { navigateToRecentSearchQuery } from "~/actions/definitions/navigation";
|
||||
|
||||
import useCommandBarActions from "~/hooks/useCommandBarActions";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
// Type for cache entries
|
||||
interface CacheEntry {
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
// Cache configuration
|
||||
const cacheTTL = Minute.ms * 5;
|
||||
|
||||
export default function SearchActions() {
|
||||
const { searches } = useStores();
|
||||
const { searches, documents } = useStores();
|
||||
|
||||
// Cache structure: Map of search queries to timestamp of last search
|
||||
const searchCache = useRef<Map<string, CacheEntry>>(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
if (!searches.isLoaded && !searches.isFetching) {
|
||||
@@ -21,6 +32,23 @@ export default function SearchActions() {
|
||||
searchQuery: state.searchQuery,
|
||||
}));
|
||||
|
||||
// Search for matching documents
|
||||
useEffect(() => {
|
||||
if (searchQuery) {
|
||||
const now = Date.now();
|
||||
const cachedEntry = searchCache.current.get(searchQuery);
|
||||
const isExpired = cachedEntry
|
||||
? now - cachedEntry.timestamp > cacheTTL
|
||||
: true;
|
||||
|
||||
if (!cachedEntry || isExpired) {
|
||||
void documents.searchTitles({ query: searchQuery }).then(() => {
|
||||
searchCache.current.set(searchQuery, { timestamp: now });
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [documents, searchQuery]);
|
||||
|
||||
useCommandBarActions(
|
||||
searchQuery ? [searchDocumentsForQuery(searchQuery)] : [],
|
||||
[searchQuery]
|
||||
|
||||
@@ -53,7 +53,7 @@ function SearchPopover({ shareId, className }: Props) {
|
||||
}, [searchResults, query, show]);
|
||||
|
||||
const performSearch = React.useCallback(
|
||||
async ({ query: searchQuery, ...options }) => {
|
||||
async ({ query: searchQuery, ...options }: any) => {
|
||||
if (searchQuery?.length > 0) {
|
||||
const response = await documents.search({
|
||||
query: searchQuery,
|
||||
|
||||
@@ -111,7 +111,7 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
|
||||
}, [pendingIds, prevPendingIds]);
|
||||
|
||||
const handleQuery = React.useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
showPicker();
|
||||
setQuery(event.target.value);
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ function DocumentMembersList({ document, invitedInSession }: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
const handleRemoveUser = React.useCallback(
|
||||
async (item) => {
|
||||
async (item: any) => {
|
||||
try {
|
||||
await userMemberships.delete({
|
||||
documentId: document.id,
|
||||
@@ -62,7 +62,7 @@ function DocumentMembersList({ document, invitedInSession }: Props) {
|
||||
);
|
||||
|
||||
const handleUpdateUser = React.useCallback(
|
||||
async (userToUpdate, permission) => {
|
||||
async (userToUpdate: any, permission: any) => {
|
||||
try {
|
||||
await userMemberships.create({
|
||||
documentId: document.id,
|
||||
|
||||
@@ -129,7 +129,7 @@ function PublicAccess({ document, share, sharedParent }: Props) {
|
||||
|
||||
const shareUrl = sharedParent?.url
|
||||
? `${sharedParent.url}${document.url}`
|
||||
: share?.url ?? "";
|
||||
: (share?.url ?? "");
|
||||
|
||||
const copyButton = (
|
||||
<Tooltip content={t("Copy public link")} placement="top">
|
||||
|
||||
@@ -230,7 +230,7 @@ function SharePopover({ document, onRequestClose, visible }: Props) {
|
||||
);
|
||||
|
||||
const handleQuery = React.useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
showPicker();
|
||||
setQuery(event.target.value);
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@ export const SearchInput = React.forwardRef(function _SearchInput(
|
||||
const isMobile = useMobile();
|
||||
|
||||
const focusInput = React.useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
if (event.target.closest("button")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ export const Suggestions = observer(
|
||||
.notInDocument(document.id, query)
|
||||
.filter((u) => u.id !== user.id)
|
||||
: collection
|
||||
? users.notInCollection(collection.id, query)
|
||||
: users.activeOrInvited
|
||||
? users.notInCollection(collection.id, query)
|
||||
: users.activeOrInvited
|
||||
).filter((u) => !u.isSuspended);
|
||||
|
||||
if (isEmail(query)) {
|
||||
@@ -109,8 +109,8 @@ export const Suggestions = observer(
|
||||
...(document
|
||||
? groups.notInDocument(document.id, query)
|
||||
: collection
|
||||
? groups.notInCollection(collection.id, query)
|
||||
: []),
|
||||
? groups.notInCollection(collection.id, query)
|
||||
: []),
|
||||
...filtered,
|
||||
];
|
||||
}, [
|
||||
@@ -133,7 +133,7 @@ export const Suggestions = observer(
|
||||
.map((id) =>
|
||||
isEmail(id)
|
||||
? getSuggestionForEmail(id)
|
||||
: users.get(id) ?? groups.get(id)
|
||||
: (users.get(id) ?? groups.get(id))
|
||||
)
|
||||
.filter(Boolean) as User[],
|
||||
[users, groups, getSuggestionForEmail, pendingIds]
|
||||
@@ -158,8 +158,8 @@ export const Suggestions = observer(
|
||||
subtitle: suggestion.email
|
||||
? suggestion.email
|
||||
: suggestion.isViewer
|
||||
? t("Viewer")
|
||||
: t("Editor"),
|
||||
? t("Viewer")
|
||||
: t("Editor"),
|
||||
image: <Avatar model={suggestion} size={AvatarSize.Medium} />,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ function AppSidebar() {
|
||||
}, [documents, collections, user.isViewer]);
|
||||
|
||||
const [dndArea, setDndArea] = useState();
|
||||
const handleSidebarRef = useCallback((node) => setDndArea(node), []);
|
||||
const handleSidebarRef = useCallback((node: any) => setDndArea(node), []);
|
||||
const html5Options = useMemo(
|
||||
() => ({
|
||||
rootElement: dndArea,
|
||||
|
||||
@@ -50,7 +50,7 @@ function Right({ children, border, className }: Props) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleMouseDown = React.useCallback((event) => {
|
||||
const handleMouseDown = React.useCallback((event: any) => {
|
||||
event.preventDefault();
|
||||
setResizing(true);
|
||||
}, []);
|
||||
|
||||
@@ -121,7 +121,9 @@ const ToggleWrapper = styled.div`
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
transition: opacity 100ms ease-out, transform 100ms ease-out;
|
||||
transition:
|
||||
opacity 100ms ease-out,
|
||||
transform 100ms ease-out;
|
||||
`;
|
||||
|
||||
const StyledSidebar = styled(Sidebar)<{ $hoverTransition: boolean }>`
|
||||
|
||||
@@ -101,7 +101,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
}, []);
|
||||
|
||||
const handleMouseDown = React.useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
event.preventDefault();
|
||||
if (!document.hasFocus()) {
|
||||
return;
|
||||
@@ -126,7 +126,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
}, [ui.sidebarIsClosed]);
|
||||
|
||||
const handlePointerLeave = React.useCallback(
|
||||
(ev) => {
|
||||
(ev: any) => {
|
||||
if (hasPointerMoved) {
|
||||
// clear any previous timeout
|
||||
if (hoverTimeoutRef.current) {
|
||||
@@ -294,8 +294,8 @@ const hoverStyles = (props: ContainerProps) => `
|
||||
props.$collapsed
|
||||
? "rgba(0, 0, 0, 0.2) 1px 0 4px"
|
||||
: props.$isSmallerThanMinimum
|
||||
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
|
||||
: "none"
|
||||
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
|
||||
: "none"
|
||||
};
|
||||
|
||||
${ToggleButton} {
|
||||
@@ -309,7 +309,9 @@ const Container = styled(Flex)<ContainerProps>`
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: ${s("sidebarBackground")};
|
||||
transition: box-shadow 150ms ease-in-out, transform 150ms ease-out,
|
||||
transition:
|
||||
box-shadow 150ms ease-in-out,
|
||||
transform 150ms ease-out,
|
||||
${(props: ContainerProps) =>
|
||||
props.$isAnimating ? `,width ${ANIMATION_MS}ms ease-out` : ""};
|
||||
transform: translateX(
|
||||
|
||||
@@ -51,7 +51,7 @@ function ArchiveLink() {
|
||||
}
|
||||
}, [expanded, request]);
|
||||
|
||||
const handleDisclosureClick = useCallback((ev) => {
|
||||
const handleDisclosureClick = useCallback((ev: any) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setExpanded((e) => !e);
|
||||
|
||||
@@ -15,7 +15,7 @@ export function ArchivedCollectionLink({ collection, depth }: Props) {
|
||||
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const handleDisclosureClick = useCallback((ev) => {
|
||||
const handleDisclosureClick = useCallback((ev: any) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setExpanded((e) => !e);
|
||||
|
||||
@@ -36,7 +36,7 @@ type Props = {
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
const CollectionLink: React.FC<Props> = ({
|
||||
const CollectionLink: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
collection,
|
||||
expanded,
|
||||
onDisclosureClick,
|
||||
@@ -88,7 +88,7 @@ const CollectionLink: React.FC<Props> = ({
|
||||
useBoolean();
|
||||
|
||||
const handleNewDoc = React.useCallback(
|
||||
async (input) => {
|
||||
async (input: any) => {
|
||||
const newDocument = await documents.create(
|
||||
{
|
||||
collectionId: collection.id,
|
||||
|
||||
@@ -54,7 +54,10 @@ const Button = styled(NudeButton)<{ $root?: boolean }>`
|
||||
const StyledCollapsedIcon = styled(CollapsedIcon)<{
|
||||
expanded?: boolean;
|
||||
}>`
|
||||
transition: opacity 100ms ease, transform 100ms ease, fill 50ms !important;
|
||||
transition:
|
||||
opacity 100ms ease,
|
||||
transform 100ms ease,
|
||||
fill 50ms !important;
|
||||
${(props) => !props.expanded && "transform: rotate(-90deg);"};
|
||||
`;
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ function InnerDocumentLink(
|
||||
}, [setCollapsed, expanded, hasChildDocuments]);
|
||||
|
||||
const handleDisclosureClick = React.useCallback(
|
||||
(ev) => {
|
||||
(ev: any) => {
|
||||
ev?.preventDefault();
|
||||
if (expanded) {
|
||||
setCollapsed();
|
||||
@@ -233,7 +233,7 @@ function InnerDocumentLink(
|
||||
useBoolean();
|
||||
|
||||
const handleNewDoc = React.useCallback(
|
||||
async (input) => {
|
||||
async (input: any) => {
|
||||
const newDocument = await documents.create(
|
||||
{
|
||||
collectionId: collection?.id,
|
||||
|
||||
@@ -93,7 +93,7 @@ function DraggableCollectionLink({
|
||||
locationSidebarContext,
|
||||
]);
|
||||
|
||||
const handleDisclosureClick = useCallback((ev) => {
|
||||
const handleDisclosureClick = useCallback((ev: any) => {
|
||||
ev?.preventDefault();
|
||||
setExpanded((e) => !e);
|
||||
}, []);
|
||||
|
||||
@@ -6,7 +6,10 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const Folder: React.FC<Props> = ({ expanded, children }: Props) => {
|
||||
const Folder: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
expanded,
|
||||
children,
|
||||
}: Props) => {
|
||||
const [openedOnce, setOpenedOnce] = React.useState(expanded);
|
||||
|
||||
// allows us to avoid rendering all children when the folder hasn't been opened
|
||||
|
||||
@@ -14,14 +14,14 @@ type Props = {
|
||||
group: Group;
|
||||
};
|
||||
|
||||
const GroupLink: React.FC<Props> = ({ group }) => {
|
||||
const GroupLink: React.FC<React.PropsWithChildren<Props>> = ({ group }) => {
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
const sidebarContext = groupSidebarContext(group.id);
|
||||
const [expanded, setExpanded] = React.useState(
|
||||
locationSidebarContext === sidebarContext
|
||||
);
|
||||
|
||||
const handleDisclosureClick = React.useCallback((ev) => {
|
||||
const handleDisclosureClick = React.useCallback((ev: any) => {
|
||||
ev?.preventDefault();
|
||||
setExpanded((e) => !e);
|
||||
}, []);
|
||||
|
||||
@@ -19,7 +19,11 @@ export function getHeaderExpandedKey(id: string) {
|
||||
/**
|
||||
* Toggleable sidebar header
|
||||
*/
|
||||
export const Header: React.FC<Props> = ({ id, title, children }: Props) => {
|
||||
export const Header: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
id,
|
||||
title,
|
||||
children,
|
||||
}: Props) => {
|
||||
const [firstRender, setFirstRender] = React.useState(true);
|
||||
const [expanded, setExpanded] = usePersistedState<boolean>(
|
||||
getHeaderExpandedKey(id ?? ""),
|
||||
@@ -91,7 +95,10 @@ const Button = styled.button`
|
||||
`;
|
||||
|
||||
const Disclosure = styled(CollapsedIcon)<{ expanded?: boolean }>`
|
||||
transition: opacity 100ms ease, transform 100ms ease, fill 50ms !important;
|
||||
transition:
|
||||
opacity 100ms ease,
|
||||
transform 100ms ease,
|
||||
fill 50ms !important;
|
||||
${({ expanded }) => !expanded && "transform: rotate(-90deg);"};
|
||||
opacity: 0;
|
||||
`;
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface Props extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
location?: Location;
|
||||
strict?: boolean;
|
||||
to: LocationDescriptor;
|
||||
component?: React.ComponentType;
|
||||
component?: React.ComponentType<React.PropsWithChildren<unknown>>;
|
||||
onBeforeClick?: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ export function useDragDocument(
|
||||
depth,
|
||||
icon: icon ? <Icon value={icon} color={color} /> : undefined,
|
||||
collectionId: document?.collectionId || "",
|
||||
} as DragObject),
|
||||
}) as DragObject,
|
||||
canDrag: () => !!document?.isActive && !isEditing,
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
@@ -505,7 +505,7 @@ export function useDragMembership(
|
||||
id,
|
||||
title,
|
||||
icon,
|
||||
} as DragObject),
|
||||
}) as DragObject,
|
||||
collect: (monitor) => ({
|
||||
isDragging: !!monitor.isDragging(),
|
||||
}),
|
||||
|
||||
@@ -52,10 +52,10 @@ function Star({ size, document, collection, color, ...rest }: Props) {
|
||||
? unstarCollection
|
||||
: starCollection
|
||||
: document
|
||||
? document.isStarred
|
||||
? unstarDocument
|
||||
: starDocument
|
||||
: undefined
|
||||
? document.isStarred
|
||||
? unstarDocument
|
||||
: starDocument
|
||||
: undefined
|
||||
}
|
||||
size={size}
|
||||
{...rest}
|
||||
|
||||
@@ -34,7 +34,11 @@ const Background = styled.div<{ sticky?: boolean }>`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const Subheading: React.FC<Props> = ({ children, sticky, ...rest }: Props) => (
|
||||
const Subheading: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
sticky,
|
||||
...rest
|
||||
}: Props) => (
|
||||
<Background sticky={sticky}>
|
||||
<H3 {...rest}>
|
||||
<Underline>{children}</Underline>
|
||||
|
||||
@@ -58,7 +58,7 @@ const transition = {
|
||||
damping: 30,
|
||||
};
|
||||
|
||||
const Tab: React.FC<Props> = ({
|
||||
const Tab: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
exact,
|
||||
exactQueryString,
|
||||
|
||||
@@ -60,7 +60,9 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const Tabs: React.FC = ({ children }: Props) => {
|
||||
const Tabs: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const ref = React.useRef<any>();
|
||||
const [shadowVisible, setShadow] = React.useState(false);
|
||||
const { width } = useWindowSize();
|
||||
|
||||
@@ -11,7 +11,9 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const Theme: React.FC = ({ children }: Props) => {
|
||||
const Theme: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const { auth, ui } = useStores();
|
||||
const theme = useBuildTheme(
|
||||
auth.team?.getPreference(TeamPreference.CustomTheme) ||
|
||||
|
||||
@@ -6,12 +6,14 @@ import useStores from "~/hooks/useStores";
|
||||
type StoreProps = keyof RootStore;
|
||||
|
||||
function withStores<
|
||||
P extends React.ComponentType<ResolvedProps & RootStore>,
|
||||
P extends React.ComponentType<
|
||||
React.PropsWithChildren<ResolvedProps & RootStore>
|
||||
>,
|
||||
ResolvedProps = JSX.LibraryManagedAttributes<
|
||||
P,
|
||||
Omit<React.ComponentProps<P>, StoreProps>
|
||||
>
|
||||
>(WrappedComponent: P): React.FC<ResolvedProps> {
|
||||
>,
|
||||
>(WrappedComponent: P): React.FC<React.PropsWithChildren<ResolvedProps>> {
|
||||
const ComponentWithStore = (props: ResolvedProps) => {
|
||||
const stores = useStores();
|
||||
return <WrappedComponent {...(props as any)} {...stores} />;
|
||||
|
||||
@@ -23,7 +23,7 @@ type ComponentViewConstructor = {
|
||||
|
||||
export default class ComponentView {
|
||||
/** The React component to render. */
|
||||
component: FunctionComponent<ComponentProps>;
|
||||
component: FunctionComponent<React.PropsWithChildren<ComponentProps>>;
|
||||
/** The editor instance. */
|
||||
editor: Editor;
|
||||
/** The extension the view belongs to. */
|
||||
@@ -45,7 +45,7 @@ export default class ComponentView {
|
||||
|
||||
// See https://prosemirror.net/docs/ref/#view.NodeView
|
||||
constructor(
|
||||
component: FunctionComponent<ComponentProps>,
|
||||
component: FunctionComponent<React.PropsWithChildren<ComponentProps>>,
|
||||
{
|
||||
editor,
|
||||
extension,
|
||||
|
||||
@@ -249,7 +249,7 @@ export default function FindAndReplace({
|
||||
);
|
||||
|
||||
const handleReplace = React.useCallback(
|
||||
(ev) => {
|
||||
(ev: any) => {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
@@ -260,7 +260,7 @@ export default function FindAndReplace({
|
||||
);
|
||||
|
||||
const handleReplaceAll = React.useCallback(
|
||||
(ev) => {
|
||||
(ev: any) => {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ function usePosition({
|
||||
const position = codeBlock
|
||||
? codeBlock.pos
|
||||
: noticeBlock
|
||||
? noticeBlock.pos
|
||||
: null;
|
||||
? noticeBlock.pos
|
||||
: null;
|
||||
|
||||
if (position !== null) {
|
||||
const element = view.nodeDOM(position);
|
||||
@@ -345,7 +345,8 @@ const Wrapper = styled.div<WrapperProps>`
|
||||
box-shadow: ${s("menuShadow")};
|
||||
border-radius: 4px;
|
||||
transform: scale(0.95);
|
||||
transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
transition:
|
||||
opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
transition-delay: 150ms;
|
||||
line-height: 0;
|
||||
|
||||
@@ -42,7 +42,7 @@ type Props = {
|
||||
view: EditorView;
|
||||
};
|
||||
|
||||
const LinkEditor: React.FC<Props> = ({
|
||||
const LinkEditor: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
mark,
|
||||
from,
|
||||
to,
|
||||
|
||||
@@ -98,7 +98,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
|
||||
actorId,
|
||||
label: user.name,
|
||||
},
|
||||
} as MentionItem)
|
||||
}) as MentionItem
|
||||
)
|
||||
.concat(
|
||||
documents
|
||||
@@ -130,7 +130,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
|
||||
actorId,
|
||||
label: doc.title,
|
||||
},
|
||||
} as MentionItem)
|
||||
}) as MentionItem
|
||||
)
|
||||
)
|
||||
.concat(
|
||||
@@ -158,7 +158,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
|
||||
actorId,
|
||||
label: collection.name,
|
||||
},
|
||||
} as MentionItem)
|
||||
}) as MentionItem
|
||||
)
|
||||
)
|
||||
.concat([
|
||||
|
||||
@@ -8,7 +8,7 @@ export class NodeViewRenderer<T extends object> {
|
||||
|
||||
public constructor(
|
||||
public element: HTMLElement,
|
||||
private Component: FunctionComponent,
|
||||
private Component: FunctionComponent<React.PropsWithChildren<unknown>>,
|
||||
props: T
|
||||
) {
|
||||
this.props = props;
|
||||
|
||||
@@ -251,7 +251,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
|
||||
);
|
||||
|
||||
const handleClickItem = React.useCallback(
|
||||
(item) => {
|
||||
(item: any) => {
|
||||
props.onSelect?.(item);
|
||||
|
||||
switch (item.name) {
|
||||
@@ -470,7 +470,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
|
||||
item,
|
||||
section:
|
||||
"section" in item && item.section && "priority" in item.section
|
||||
? (item.section.priority as number) ?? 0
|
||||
? ((item.section.priority as number) ?? 0)
|
||||
: 0,
|
||||
priority: "priority" in item ? item.priority : 0,
|
||||
score:
|
||||
@@ -596,8 +596,8 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
|
||||
"placeholder" in insertItem
|
||||
? insertItem.placeholder
|
||||
: insertItem.title
|
||||
? dictionary.pasteLinkWithTitle(insertItem.title)
|
||||
: dictionary.pasteLink
|
||||
? dictionary.pasteLinkWithTitle(insertItem.title)
|
||||
: dictionary.pasteLink
|
||||
}
|
||||
onKeyDown={handleLinkInputKeydown}
|
||||
onPaste={handleLinkInputPaste}
|
||||
@@ -740,11 +740,14 @@ export const Wrapper = styled(Scrollable)<{
|
||||
left: ${(props) => props.left}px;
|
||||
background: ${s("menuBackground")};
|
||||
border-radius: 6px;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px,
|
||||
rgba(0, 0, 0, 0.08) 0px 4px 8px, rgba(0, 0, 0, 0.08) 0px 2px 4px;
|
||||
box-shadow:
|
||||
rgba(0, 0, 0, 0.05) 0px 0px 0px 1px,
|
||||
rgba(0, 0, 0, 0.08) 0px 4px 8px,
|
||||
rgba(0, 0, 0, 0.08) 0px 2px 4px;
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
transition:
|
||||
opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
transition-delay: 150ms;
|
||||
line-height: 0;
|
||||
|
||||
@@ -36,7 +36,7 @@ function SuggestionsMenuItem({
|
||||
}: Props) {
|
||||
const portal = usePortalContext();
|
||||
const ref = React.useCallback(
|
||||
(node) => {
|
||||
(node: any) => {
|
||||
if (selected && node) {
|
||||
scrollIntoView(node, {
|
||||
scrollMode: "if-needed",
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Tooltip, { Props } from "~/components/Tooltip";
|
||||
|
||||
const WrappedTooltip: React.FC<Props> = ({
|
||||
const WrappedTooltip: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
children,
|
||||
content,
|
||||
...rest
|
||||
|
||||
@@ -2,7 +2,7 @@ import { action } from "mobx";
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import ReactDOM from "react-dom";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { WidgetProps } from "@shared/editor/lib/Extension";
|
||||
import { PlaceholderPlugin } from "@shared/editor/plugins/PlaceholderPlugin";
|
||||
import { findParentNode } from "@shared/editor/queries/findParentNode";
|
||||
@@ -27,7 +27,8 @@ export default class BlockMenuExtension extends Suggestion {
|
||||
const button = document.createElement("button");
|
||||
button.className = "block-menu-trigger";
|
||||
button.type = "button";
|
||||
ReactDOM.render(<PlusIcon />, button);
|
||||
const root = createRoot(button);
|
||||
root.render(<PlusIcon />);
|
||||
|
||||
return [
|
||||
...super.plugins,
|
||||
|
||||
@@ -6,8 +6,10 @@ import {
|
||||
TextSelection,
|
||||
EditorState,
|
||||
Command,
|
||||
Transaction,
|
||||
} from "prosemirror-state";
|
||||
import Extension from "@shared/editor/lib/Extension";
|
||||
import { getCurrentBlock } from "@shared/editor/queries/getCurrentBlock";
|
||||
import { isInCode } from "@shared/editor/queries/isInCode";
|
||||
|
||||
export default class Keys extends Extension {
|
||||
@@ -24,6 +26,93 @@ export default class Keys extends Extension {
|
||||
return false;
|
||||
};
|
||||
|
||||
const moveBlockUp = (
|
||||
state: EditorState,
|
||||
dispatch?: (tr: Transaction) => void
|
||||
) => {
|
||||
if (!state.selection.empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = getCurrentBlock(state);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [currentBlock, currentPos] = result;
|
||||
const $pos = state.doc.resolve(currentPos);
|
||||
|
||||
// Check if there's a previous sibling block
|
||||
if (!$pos.nodeBefore || !$pos.nodeBefore.isBlock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prevBlock = $pos.nodeBefore;
|
||||
const prevBlockPos = currentPos - prevBlock.nodeSize;
|
||||
|
||||
if (!dispatch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { tr } = state;
|
||||
|
||||
// Move current block before the previous block
|
||||
dispatch(
|
||||
tr
|
||||
.delete(currentPos, currentPos + currentBlock.nodeSize)
|
||||
.insert(prevBlockPos, currentBlock)
|
||||
.setSelection(TextSelection.near(tr.doc.resolve(prevBlockPos + 1)))
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const moveBlockDown = (
|
||||
state: EditorState,
|
||||
dispatch?: (tr: Transaction) => void
|
||||
) => {
|
||||
if (!state.selection.empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = getCurrentBlock(state);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [currentBlock, currentPos] = result;
|
||||
const $pos = state.doc.resolve(currentPos + currentBlock.nodeSize);
|
||||
|
||||
// Check if there's a next sibling block
|
||||
if (!$pos.nodeAfter || !$pos.nodeAfter.isBlock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextBlock = $pos.nodeAfter;
|
||||
const nextBlockEndPos =
|
||||
currentPos + currentBlock.nodeSize + nextBlock.nodeSize;
|
||||
|
||||
if (!dispatch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { tr } = state;
|
||||
|
||||
// Move current block after the next block
|
||||
dispatch(
|
||||
tr
|
||||
.insert(nextBlockEndPos, currentBlock)
|
||||
.delete(currentPos, currentPos + currentBlock.nodeSize)
|
||||
.setSelection(
|
||||
TextSelection.near(
|
||||
tr.doc.resolve(nextBlockEndPos - currentBlock.nodeSize + 1)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return {
|
||||
// Shortcuts for when editor has separate edit mode
|
||||
"Mod-Escape": onCancel,
|
||||
@@ -46,6 +135,9 @@ export default class Keys extends Extension {
|
||||
(this.editor.view.dom as HTMLElement).blur();
|
||||
return true;
|
||||
},
|
||||
// Block movement shortcuts
|
||||
"Mod-Alt-ArrowUp": moveBlockUp,
|
||||
"Mod-Alt-ArrowDown": moveBlockDown,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,12 @@ export default function useBuildTheme(
|
||||
isPrinting
|
||||
? buildLightTheme(customTheme)
|
||||
: isMobile
|
||||
? resolvedTheme === "dark"
|
||||
? buildPitchBlackTheme(customTheme)
|
||||
: buildLightTheme(customTheme)
|
||||
: resolvedTheme === "dark"
|
||||
? buildDarkTheme(customTheme)
|
||||
: buildLightTheme(customTheme),
|
||||
? resolvedTheme === "dark"
|
||||
? buildPitchBlackTheme(customTheme)
|
||||
: buildLightTheme(customTheme)
|
||||
: resolvedTheme === "dark"
|
||||
? buildDarkTheme(customTheme)
|
||||
: buildLightTheme(customTheme),
|
||||
[customTheme, isMobile, isPrinting, resolvedTheme]
|
||||
);
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ const createKeyPredicate = (keyFilter: KeyFilter) =>
|
||||
typeof keyFilter === "function"
|
||||
? keyFilter
|
||||
: typeof keyFilter === "string"
|
||||
? (event: KeyboardEvent) => event.key === keyFilter
|
||||
: keyFilter
|
||||
? (_event: KeyboardEvent) => true
|
||||
: (_event: KeyboardEvent) => false;
|
||||
? (event: KeyboardEvent) => event.key === keyFilter
|
||||
: keyFilter
|
||||
? (_event: KeyboardEvent) => true
|
||||
: (_event: KeyboardEvent) => false;
|
||||
|
||||
export default function useKeyDown(
|
||||
key: KeyFilter,
|
||||
|
||||
@@ -18,7 +18,9 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const MenuProvider: React.FC = ({ children }: Props) => {
|
||||
export const MenuProvider: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
||||
|
||||
const registerMenu = React.useCallback(
|
||||
|
||||
@@ -17,8 +17,8 @@ export function usePinnedDocuments(urlId: UrlId, collectionId?: string) {
|
||||
return urlId === "home"
|
||||
? pins.home
|
||||
: collectionId
|
||||
? pins.inCollection(collectionId)
|
||||
: [];
|
||||
? pins.inCollection(collectionId)
|
||||
: [];
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -49,8 +49,8 @@ const Templates = lazy(() => import("~/scenes/Settings/Templates"));
|
||||
export type ConfigItem = {
|
||||
name: string;
|
||||
path: string;
|
||||
icon: React.FC<ComponentProps<typeof Icon>>;
|
||||
component: React.ComponentType;
|
||||
icon: React.FC<React.PropsWithChildren<ComponentProps<typeof Icon>>>;
|
||||
component: React.ComponentType<React.PropsWithChildren<unknown>>;
|
||||
description?: string;
|
||||
preload?: () => void;
|
||||
enabled: boolean;
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@ import { LazyMotion } from "framer-motion";
|
||||
import { KBarProvider } from "kbar";
|
||||
import { Provider } from "mobx-react";
|
||||
import { StrictMode } from "react";
|
||||
import { render } from "react-dom";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
import { Router } from "react-router-dom";
|
||||
import stores from "~/stores";
|
||||
@@ -79,7 +79,8 @@ if (element) {
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
render(<App />, element);
|
||||
const root = createRoot(element);
|
||||
root.render(<App />);
|
||||
}
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
|
||||
@@ -25,7 +25,9 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const AccountMenu: React.FC = ({ children }: Props) => {
|
||||
const AccountMenu: React.FC<React.PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}: Props) => {
|
||||
const menu = useMenuState({
|
||||
placement: "bottom-end",
|
||||
modal: true,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user