chore(deps-dev): bump prettier from 2.1.2 to 2.8.8 (#5372)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -25,9 +25,9 @@ function CommandBar() {
|
|||||||
|
|
||||||
const { rootAction } = useKBar((state) => ({
|
const { rootAction } = useKBar((state) => ({
|
||||||
rootAction: state.currentRootActionId
|
rootAction: state.currentRootActionId
|
||||||
? ((state.actions[
|
? (state.actions[
|
||||||
state.currentRootActionId
|
state.currentRootActionId
|
||||||
] as unknown) as CommandBarAction)
|
] as unknown as CommandBarAction)
|
||||||
: undefined,
|
: undefined,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -85,27 +85,33 @@ const ContentEditable = React.forwardRef(
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const wrappedEvent = (
|
const wrappedEvent =
|
||||||
callback:
|
(
|
||||||
| React.FocusEventHandler<HTMLSpanElement>
|
callback:
|
||||||
| React.FormEventHandler<HTMLSpanElement>
|
| React.FocusEventHandler<HTMLSpanElement>
|
||||||
| React.KeyboardEventHandler<HTMLSpanElement>
|
| React.FormEventHandler<HTMLSpanElement>
|
||||||
| undefined
|
| React.KeyboardEventHandler<HTMLSpanElement>
|
||||||
) => (event: any) => {
|
| undefined
|
||||||
const text = contentRef.current?.innerText || "";
|
) =>
|
||||||
|
(event: any) => {
|
||||||
|
const text = contentRef.current?.innerText || "";
|
||||||
|
|
||||||
if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) {
|
if (
|
||||||
event?.preventDefault();
|
maxLength &&
|
||||||
return;
|
isPrintableKeyEvent(event) &&
|
||||||
}
|
text.length >= maxLength
|
||||||
|
) {
|
||||||
|
event?.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (text !== lastValue.current) {
|
if (text !== lastValue.current) {
|
||||||
lastValue.current = text;
|
lastValue.current = text;
|
||||||
onChange && onChange(text);
|
onChange && onChange(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
callback?.(event);
|
callback?.(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is to account for being within a React.Suspense boundary, in this
|
// This is to account for being within a React.Suspense boundary, in this
|
||||||
// case the component may be rendered with display: none. React 18 may solve
|
// case the component may be rendered with display: none. React 18 may solve
|
||||||
|
|||||||
@@ -24,8 +24,12 @@ type Positions = {
|
|||||||
export default function MouseSafeArea(props: {
|
export default function MouseSafeArea(props: {
|
||||||
parentRef: React.RefObject<HTMLElement | null>;
|
parentRef: React.RefObject<HTMLElement | null>;
|
||||||
}) {
|
}) {
|
||||||
const { x = 0, y = 0, height: h = 0, width: w = 0 } =
|
const {
|
||||||
props.parentRef.current?.getBoundingClientRect() || {};
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
height: h = 0,
|
||||||
|
width: w = 0,
|
||||||
|
} = props.parentRef.current?.getBoundingClientRect() || {};
|
||||||
const [mouseX, mouseY] = useMousePosition();
|
const [mouseX, mouseY] = useMousePosition();
|
||||||
const positions = { x, y, h, w, mouseX, mouseY };
|
const positions = { x, y, h, w, mouseX, mouseY };
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
|
|||||||
const [selectedNode, selectNode] = React.useState<NavigationNode | null>(
|
const [selectedNode, selectNode] = React.useState<NavigationNode | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [initialScrollOffset, setInitialScrollOffset] = React.useState<number>(
|
const [initialScrollOffset, setInitialScrollOffset] =
|
||||||
0
|
React.useState<number>(0);
|
||||||
);
|
|
||||||
const [activeNode, setActiveNode] = React.useState<number>(0);
|
const [activeNode, setActiveNode] = React.useState<number>(0);
|
||||||
const [expandedNodes, setExpandedNodes] = React.useState<string[]>([]);
|
const [expandedNodes, setExpandedNodes] = React.useState<string[]>([]);
|
||||||
const [itemRefs, setItemRefs] = React.useState<
|
const [itemRefs, setItemRefs] = React.useState<
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ const Actions = styled(EventBoundary)`
|
|||||||
color: ${s("textSecondary")};
|
color: ${s("textSecondary")};
|
||||||
|
|
||||||
${NudeButton} {
|
${NudeButton} {
|
||||||
&: ${hover}, &[aria-expanded= "true" ] {
|
&: ${hover}, &[aria-expanded= "true"] {
|
||||||
background: ${s("sidebarControlHoverBackground")};
|
background: ${s("sidebarControlHoverBackground")};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ function DocumentViews({ document, isOpen }: Props) {
|
|||||||
documentViews,
|
documentViews,
|
||||||
(view) => !presentIds.includes(view.user.id)
|
(view) => !presentIds.includes(view.user.id)
|
||||||
);
|
);
|
||||||
const users = React.useMemo(() => sortedViews.map((v) => v.user), [
|
const users = React.useMemo(
|
||||||
sortedViews,
|
() => sortedViews.map((v) => v.user),
|
||||||
]);
|
[sortedViews]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -67,10 +67,8 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
|
|||||||
const localRef = React.useRef<SharedEditor>();
|
const localRef = React.useRef<SharedEditor>();
|
||||||
const preferences = auth.user?.preferences;
|
const preferences = auth.user?.preferences;
|
||||||
const previousHeadings = React.useRef<Heading[] | null>(null);
|
const previousHeadings = React.useRef<Heading[] | null>(null);
|
||||||
const [
|
const [activeLinkElement, setActiveLink] =
|
||||||
activeLinkElement,
|
React.useState<HTMLAnchorElement | null>(null);
|
||||||
setActiveLink,
|
|
||||||
] = React.useState<HTMLAnchorElement | null>(null);
|
|
||||||
const previousCommentIds = React.useRef<string[]>();
|
const previousCommentIds = React.useRef<string[]>();
|
||||||
|
|
||||||
const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => {
|
const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => {
|
||||||
|
|||||||
@@ -28,11 +28,8 @@ type Props = {
|
|||||||
function GroupListItem({ group, showFacepile, renderActions }: Props) {
|
function GroupListItem({ group, showFacepile, renderActions }: Props) {
|
||||||
const { groupMemberships } = useStores();
|
const { groupMemberships } = useStores();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [
|
const [membersModalOpen, setMembersModalOpen, setMembersModalClosed] =
|
||||||
membersModalOpen,
|
useBoolean();
|
||||||
setMembersModalOpen,
|
|
||||||
setMembersModalClosed,
|
|
||||||
] = useBoolean();
|
|
||||||
const memberCount = group.memberCount;
|
const memberCount = group.memberCount;
|
||||||
const membershipsInGroup = groupMemberships.inGroup(group.id);
|
const membershipsInGroup = groupMemberships.inGroup(group.id);
|
||||||
const users = membershipsInGroup
|
const users = membershipsInGroup
|
||||||
|
|||||||
@@ -76,9 +76,8 @@ function SearchPopover({ shareId }: Props) {
|
|||||||
[popover, cachedQuery]
|
[popover, cachedQuery]
|
||||||
);
|
);
|
||||||
|
|
||||||
const searchInputRef = popover.unstable_referenceRef as React.RefObject<
|
const searchInputRef =
|
||||||
HTMLInputElement
|
popover.unstable_referenceRef as React.RefObject<HTMLInputElement>;
|
||||||
>;
|
|
||||||
|
|
||||||
const firstSearchItem = React.useRef<HTMLAnchorElement>(null);
|
const firstSearchItem = React.useRef<HTMLAnchorElement>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ function DocumentLink(
|
|||||||
!!node.children.length || activeDocument?.parentDocumentId === node.id;
|
!!node.children.length || activeDocument?.parentDocumentId === node.id;
|
||||||
const document = documents.get(node.id);
|
const document = documents.get(node.id);
|
||||||
|
|
||||||
const showChildren = React.useMemo(() => !!hasChildDocuments, [
|
const showChildren = React.useMemo(
|
||||||
hasChildDocuments,
|
() => !!hasChildDocuments,
|
||||||
]);
|
[hasChildDocuments]
|
||||||
|
);
|
||||||
|
|
||||||
const [expanded, setExpanded] = React.useState(showChildren);
|
const [expanded, setExpanded] = React.useState(showChildren);
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ type SocketWithAuthentication = Socket & {
|
|||||||
authenticated?: boolean;
|
authenticated?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WebsocketContext = React.createContext<SocketWithAuthentication | null>(
|
export const WebsocketContext =
|
||||||
null
|
React.createContext<SocketWithAuthentication | null>(null);
|
||||||
);
|
|
||||||
|
|
||||||
type Props = RootStore;
|
type Props = RootStore;
|
||||||
|
|
||||||
|
|||||||
@@ -272,16 +272,15 @@ class LinkEditor extends React.Component<Props, State> {
|
|||||||
view.focus();
|
view.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSelectLink = (url: string, title: string) => (
|
handleSelectLink =
|
||||||
event: React.MouseEvent
|
(url: string, title: string) => (event: React.MouseEvent) => {
|
||||||
) => {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
this.save(url, title);
|
||||||
this.save(url, title);
|
|
||||||
|
|
||||||
if (this.initialSelectionLength) {
|
if (this.initialSelectionLength) {
|
||||||
this.moveSelectionToEnd();
|
this.moveSelectionToEnd();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
moveSelectionToEnd = () => {
|
moveSelectionToEnd = () => {
|
||||||
const { to, view } = this.props;
|
const { to, view } = this.props;
|
||||||
|
|||||||
@@ -197,10 +197,8 @@ export default function SelectionToolbar(props: Props) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colIndex = getColumnIndex(
|
const colIndex = getColumnIndex(state.selection as unknown as CellSelection);
|
||||||
(state.selection as unknown) as CellSelection
|
const rowIndex = getRowIndex(state.selection as unknown as CellSelection);
|
||||||
);
|
|
||||||
const rowIndex = getRowIndex((state.selection as unknown) as CellSelection);
|
|
||||||
const isTableSelection = colIndex !== undefined && rowIndex !== undefined;
|
const isTableSelection = colIndex !== undefined && rowIndex !== undefined;
|
||||||
const link = isMarkActive(state.schema.marks.link)(state);
|
const link = isMarkActive(state.schema.marks.link)(state);
|
||||||
const range = getMarkRange(selection.$from, state.schema.marks.link);
|
const range = getMarkRange(selection.$from, state.schema.marks.link);
|
||||||
|
|||||||
@@ -464,9 +464,8 @@ export class Editor extends React.PureComponent<
|
|||||||
nodeViews: this.nodeViews,
|
nodeViews: this.nodeViews,
|
||||||
dispatchTransaction(transaction) {
|
dispatchTransaction(transaction) {
|
||||||
// callback is bound to have the view instance as its this binding
|
// callback is bound to have the view instance as its this binding
|
||||||
const { state, transactions } = this.state.applyTransaction(
|
const { state, transactions } =
|
||||||
transaction
|
this.state.applyTransaction(transaction);
|
||||||
);
|
|
||||||
|
|
||||||
this.updateState(state);
|
this.updateState(state);
|
||||||
|
|
||||||
@@ -520,9 +519,8 @@ export class Editor extends React.PureComponent<
|
|||||||
return trim ? content.trim() : content;
|
return trim ? content.trim() : content;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (trim
|
return (
|
||||||
? ProsemirrorHelper.trim(this.view.state.doc)
|
trim ? ProsemirrorHelper.trim(this.view.state.doc) : this.view.state.doc
|
||||||
: this.view.state.doc
|
|
||||||
).toJSON();
|
).toJSON();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
export default function useComponentSize(
|
export default function useComponentSize(ref: React.RefObject<HTMLElement>): {
|
||||||
ref: React.RefObject<HTMLElement>
|
width: number;
|
||||||
): { width: number; height: number } {
|
height: number;
|
||||||
|
} {
|
||||||
const [size, setSize] = useState({
|
const [size, setSize] = useState({
|
||||||
width: ref.current?.clientWidth || 0,
|
width: ref.current?.clientWidth || 0,
|
||||||
height: ref.current?.clientHeight || 0,
|
height: ref.current?.clientHeight || 0,
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ type MousePosition = [number, number];
|
|||||||
export const useMousePosition = () => {
|
export const useMousePosition = () => {
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
const [mousePosition, setMousePosition] = React.useState<MousePosition>([
|
const [mousePosition, setMousePosition] = React.useState<MousePosition>([
|
||||||
0,
|
0, 0,
|
||||||
0,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const updateMousePosition = React.useMemo(
|
const updateMousePosition = React.useMemo(
|
||||||
|
|||||||
@@ -25,20 +25,12 @@ type Props = {
|
|||||||
function AddGroupsToCollection(props: Props) {
|
function AddGroupsToCollection(props: Props) {
|
||||||
const { collection } = props;
|
const { collection } = props;
|
||||||
|
|
||||||
const [
|
const [newGroupModalOpen, handleNewGroupModalOpen, handleNewGroupModalClose] =
|
||||||
newGroupModalOpen,
|
useBoolean(false);
|
||||||
handleNewGroupModalOpen,
|
|
||||||
handleNewGroupModalClose,
|
|
||||||
] = useBoolean(false);
|
|
||||||
const [query, setQuery] = React.useState("");
|
const [query, setQuery] = React.useState("");
|
||||||
|
|
||||||
const {
|
const { auth, collectionGroupMemberships, groups, policies, toasts } =
|
||||||
auth,
|
useStores();
|
||||||
collectionGroupMemberships,
|
|
||||||
groups,
|
|
||||||
policies,
|
|
||||||
toasts,
|
|
||||||
} = useStores();
|
|
||||||
const { fetchPage: fetchGroups } = groups;
|
const { fetchPage: fetchGroups } = groups;
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -27,11 +27,8 @@ function AddPeopleToCollection({ collection }: Props) {
|
|||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [
|
const [inviteModalOpen, setInviteModalOpen, setInviteModalClosed] =
|
||||||
inviteModalOpen,
|
useBoolean();
|
||||||
setInviteModalOpen,
|
|
||||||
setInviteModalClosed,
|
|
||||||
] = useBoolean();
|
|
||||||
const [query, setQuery] = React.useState("");
|
const [query, setQuery] = React.useState("");
|
||||||
|
|
||||||
const handleFilter = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFilter = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
|||||||
@@ -44,11 +44,8 @@ function CollectionPermissions({ collectionId }: Props) {
|
|||||||
const collection = collections.get(collectionId);
|
const collection = collections.get(collectionId);
|
||||||
invariant(collection, "Collection not found");
|
invariant(collection, "Collection not found");
|
||||||
|
|
||||||
const [
|
const [addGroupModalOpen, handleAddGroupModalOpen, handleAddGroupModalClose] =
|
||||||
addGroupModalOpen,
|
useBoolean();
|
||||||
handleAddGroupModalOpen,
|
|
||||||
handleAddGroupModalClose,
|
|
||||||
] = useBoolean();
|
|
||||||
|
|
||||||
const [
|
const [
|
||||||
addMemberModalOpen,
|
addMemberModalOpen,
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ const Menu = styled(CommentMenu)<{ dir?: "rtl" | "ltr" }>`
|
|||||||
transition: opacity 100ms ease-in-out;
|
transition: opacity 100ms ease-in-out;
|
||||||
color: ${s("textSecondary")};
|
color: ${s("textSecondary")};
|
||||||
|
|
||||||
&: ${hover}, &[aria-expanded= "true" ] {
|
&: ${hover}, &[aria-expanded= "true"] {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: ${s("sidebarActiveBackground")};
|
background: ${s("sidebarActiveBackground")};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,16 +376,8 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { document, revision, readOnly, abilities, auth, ui, shareId, t } =
|
||||||
document,
|
this.props;
|
||||||
revision,
|
|
||||||
readOnly,
|
|
||||||
abilities,
|
|
||||||
auth,
|
|
||||||
ui,
|
|
||||||
shareId,
|
|
||||||
t,
|
|
||||||
} = this.props;
|
|
||||||
const team = auth.team;
|
const team = auth.team;
|
||||||
const isShare = !!shareId;
|
const isShare = !!shareId;
|
||||||
const embedsDisabled =
|
const embedsDisabled =
|
||||||
|
|||||||
@@ -48,10 +48,8 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
|||||||
const { presence, ui } = useStores();
|
const { presence, ui } = useStores();
|
||||||
const token = useCurrentToken();
|
const token = useCurrentToken();
|
||||||
const [showCursorNames, setShowCursorNames] = React.useState(false);
|
const [showCursorNames, setShowCursorNames] = React.useState(false);
|
||||||
const [
|
const [remoteProvider, setRemoteProvider] =
|
||||||
remoteProvider,
|
React.useState<HocuspocusProvider | null>(null);
|
||||||
setRemoteProvider,
|
|
||||||
] = React.useState<HocuspocusProvider | null>(null);
|
|
||||||
const [isLocalSynced, setLocalSynced] = React.useState(false);
|
const [isLocalSynced, setLocalSynced] = React.useState(false);
|
||||||
const [isRemoteSynced, setRemoteSynced] = React.useState(false);
|
const [isRemoteSynced, setRemoteSynced] = React.useState(false);
|
||||||
const [ydoc] = React.useState(() => new Y.Doc());
|
const [ydoc] = React.useState(() => new Y.Doc());
|
||||||
|
|||||||
@@ -28,11 +28,8 @@ function AddPeopleToGroup(props: Props) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [query, setQuery] = React.useState("");
|
const [query, setQuery] = React.useState("");
|
||||||
const [
|
const [inviteModalOpen, handleInviteModalOpen, handleInviteModalClose] =
|
||||||
inviteModalOpen,
|
useBoolean(false);
|
||||||
handleInviteModalOpen,
|
|
||||||
handleInviteModalClose,
|
|
||||||
] = useBoolean(false);
|
|
||||||
|
|
||||||
const { fetchPage: fetchUsers } = users;
|
const { fetchPage: fetchUsers } = users;
|
||||||
const debouncedFetch = React.useMemo(
|
const debouncedFetch = React.useMemo(
|
||||||
|
|||||||
@@ -24,11 +24,8 @@ function Groups() {
|
|||||||
const { groups } = useStores();
|
const { groups } = useStores();
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const can = usePolicy(team);
|
const can = usePolicy(team);
|
||||||
const [
|
const [newGroupModalOpen, handleNewGroupModalOpen, handleNewGroupModalClose] =
|
||||||
newGroupModalOpen,
|
useBoolean();
|
||||||
handleNewGroupModalOpen,
|
|
||||||
handleNewGroupModalClose,
|
|
||||||
] = useBoolean();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Scene
|
<Scene
|
||||||
|
|||||||
@@ -28,11 +28,8 @@ import UserStatusFilter from "./components/UserStatusFilter";
|
|||||||
function Members() {
|
function Members() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [
|
const [inviteModalOpen, handleInviteModalOpen, handleInviteModalClose] =
|
||||||
inviteModalOpen,
|
useBoolean();
|
||||||
handleInviteModalOpen,
|
|
||||||
handleInviteModalClose,
|
|
||||||
] = useBoolean();
|
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const { users } = useStores();
|
const { users } = useStores();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ function Security() {
|
|||||||
inviteRequired: team.inviteRequired,
|
inviteRequired: team.inviteRequired,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: providers, loading, request } = useRequest(() =>
|
const {
|
||||||
authenticationProviders.fetchPage({})
|
data: providers,
|
||||||
);
|
loading,
|
||||||
|
request,
|
||||||
|
} = useRequest(() => authenticationProviders.fetchPage({}));
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!providers && !loading) {
|
if (!providers && !loading) {
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ function DomainManagement({ onSuccess }: Props) {
|
|||||||
allowedDomains.length
|
allowedDomains.length
|
||||||
);
|
);
|
||||||
|
|
||||||
const [existingDomainsTouched, setExistingDomainsTouched] = React.useState(
|
const [existingDomainsTouched, setExistingDomainsTouched] =
|
||||||
false
|
React.useState(false);
|
||||||
);
|
|
||||||
|
|
||||||
const handleSaveDomains = React.useCallback(async () => {
|
const handleSaveDomains = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -67,19 +66,18 @@ function DomainManagement({ onSuccess }: Props) {
|
|||||||
setAllowedDomains(newDomains);
|
setAllowedDomains(newDomains);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createOnDomainChangedHandler = (index: number) => (
|
const createOnDomainChangedHandler =
|
||||||
ev: React.ChangeEvent<HTMLInputElement>
|
(index: number) => (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
) => {
|
const newDomains = allowedDomains.slice();
|
||||||
const newDomains = allowedDomains.slice();
|
|
||||||
|
|
||||||
newDomains[index] = ev.currentTarget.value;
|
newDomains[index] = ev.currentTarget.value;
|
||||||
setAllowedDomains(newDomains);
|
setAllowedDomains(newDomains);
|
||||||
|
|
||||||
const touchedExistingDomain = index < lastKnownDomainCount;
|
const touchedExistingDomain = index < lastKnownDomainCount;
|
||||||
if (touchedExistingDomain) {
|
if (touchedExistingDomain) {
|
||||||
setExistingDomainsTouched(true);
|
setExistingDomainsTouched(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showSaveChanges =
|
const showSaveChanges =
|
||||||
existingDomainsTouched ||
|
existingDomainsTouched ||
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ function UserDelete() {
|
|||||||
const { auth } = useStores();
|
const { auth } = useStores();
|
||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { register, handleSubmit: formHandleSubmit, formState } = useForm<
|
const {
|
||||||
FormData
|
register,
|
||||||
>();
|
handleSubmit: formHandleSubmit,
|
||||||
|
formState,
|
||||||
|
} = useForm<FormData>();
|
||||||
|
|
||||||
const handleRequestDelete = React.useCallback(
|
const handleRequestDelete = React.useCallback(
|
||||||
async (ev: React.SyntheticEvent) => {
|
async (ev: React.SyntheticEvent) => {
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import AuthenticationProvider from "~/models/AuthenticationProvider";
|
|||||||
import BaseStore, { RPCAction } from "./BaseStore";
|
import BaseStore, { RPCAction } from "./BaseStore";
|
||||||
import RootStore from "./RootStore";
|
import RootStore from "./RootStore";
|
||||||
|
|
||||||
export default class AuthenticationProvidersStore extends BaseStore<
|
export default class AuthenticationProvidersStore extends BaseStore<AuthenticationProvider> {
|
||||||
AuthenticationProvider
|
|
||||||
> {
|
|
||||||
actions = [RPCAction.List, RPCAction.Update];
|
actions = [RPCAction.List, RPCAction.Update];
|
||||||
|
|
||||||
constructor(rootStore: RootStore) {
|
constructor(rootStore: RootStore) {
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import { client } from "~/utils/ApiClient";
|
|||||||
import BaseStore, { PAGINATION_SYMBOL, RPCAction } from "./BaseStore";
|
import BaseStore, { PAGINATION_SYMBOL, RPCAction } from "./BaseStore";
|
||||||
import RootStore from "./RootStore";
|
import RootStore from "./RootStore";
|
||||||
|
|
||||||
export default class CollectionGroupMembershipsStore extends BaseStore<
|
export default class CollectionGroupMembershipsStore extends BaseStore<CollectionGroupMembership> {
|
||||||
CollectionGroupMembership
|
|
||||||
> {
|
|
||||||
actions = [RPCAction.Create, RPCAction.Delete];
|
actions = [RPCAction.Create, RPCAction.Delete];
|
||||||
|
|
||||||
constructor(rootStore: RootStore) {
|
constructor(rootStore: RootStore) {
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import WebhookSubscription from "~/models/WebhookSubscription";
|
|||||||
import BaseStore, { RPCAction } from "./BaseStore";
|
import BaseStore, { RPCAction } from "./BaseStore";
|
||||||
import RootStore from "./RootStore";
|
import RootStore from "./RootStore";
|
||||||
|
|
||||||
export default class WebhookSubscriptionsStore extends BaseStore<
|
export default class WebhookSubscriptionsStore extends BaseStore<WebhookSubscription> {
|
||||||
WebhookSubscription
|
|
||||||
> {
|
|
||||||
actions = [
|
actions = [
|
||||||
RPCAction.List,
|
RPCAction.List,
|
||||||
RPCAction.Create,
|
RPCAction.Create,
|
||||||
|
|||||||
@@ -328,7 +328,7 @@
|
|||||||
"lint-staged": "^13.1.0",
|
"lint-staged": "^13.1.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.8.8",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"rimraf": "^2.5.4",
|
"rimraf": "^2.5.4",
|
||||||
"rollup-plugin-webpack-stats": "^0.2.0",
|
"rollup-plugin-webpack-stats": "^0.2.0",
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ type Props = {
|
|||||||
const WebhookSubscriptionListItem = ({ webhook }: Props) => {
|
const WebhookSubscriptionListItem = ({ webhook }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { dialogs } = useStores();
|
const { dialogs } = useStores();
|
||||||
const [
|
const [editModalOpen, handleEditModalOpen, handleEditModalClose] =
|
||||||
editModalOpen,
|
useBoolean();
|
||||||
handleEditModalOpen,
|
|
||||||
handleEditModalClose,
|
|
||||||
] = useBoolean();
|
|
||||||
|
|
||||||
const showDeletionConfirmation = React.useCallback(() => {
|
const showDeletionConfirmation = React.useCallback(() => {
|
||||||
dialogs.openModal({
|
dialogs.openModal({
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const emptyFn = function () {};
|
|||||||
const callableHandlers = {
|
const callableHandlers = {
|
||||||
get<T, P extends keyof T>(_target: T, _prop: P, _receiver: any): T[P] {
|
get<T, P extends keyof T>(_target: T, _prop: P, _receiver: any): T[P] {
|
||||||
const newMock = new Proxy(emptyFn, callableHandlers);
|
const newMock = new Proxy(emptyFn, callableHandlers);
|
||||||
return (newMock as any) as T[P];
|
return newMock as any as T[P];
|
||||||
},
|
},
|
||||||
|
|
||||||
apply<T extends (...args: any) => any, A extends Parameters<T>>(
|
apply<T extends (...args: any) => any, A extends Parameters<T>>(
|
||||||
@@ -16,7 +16,7 @@ const callableHandlers = {
|
|||||||
_args: A
|
_args: A
|
||||||
): ReturnType<T> {
|
): ReturnType<T> {
|
||||||
const newMock = new Proxy(emptyFn, callableHandlers);
|
const newMock = new Proxy(emptyFn, callableHandlers);
|
||||||
return (newMock as any) as ReturnType<T>;
|
return newMock as any as ReturnType<T>;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -88,9 +88,8 @@ export default class PersistenceExtension implements Extension {
|
|||||||
// Find the collaborators that have modified the document since it was last
|
// Find the collaborators that have modified the document since it was last
|
||||||
// persisted and clear the map, if there's no collaborators then we don't
|
// persisted and clear the map, if there's no collaborators then we don't
|
||||||
// need to persist the document.
|
// need to persist the document.
|
||||||
const documentCollaboratorIds = this.documentCollaboratorIds.get(
|
const documentCollaboratorIds =
|
||||||
documentName
|
this.documentCollaboratorIds.get(documentName);
|
||||||
);
|
|
||||||
if (!documentCollaboratorIds) {
|
if (!documentCollaboratorIds) {
|
||||||
Logger.debug("multiplayer", `No changes for ${documentName}`);
|
Logger.debug("multiplayer", `No changes for ${documentName}`);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ const importMapping: ImportableFile[] = [
|
|||||||
getMarkdown: docxToMarkdown,
|
getMarkdown: docxToMarkdown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type:
|
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
getMarkdown: docxToMarkdown,
|
getMarkdown: docxToMarkdown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -171,12 +171,12 @@ class Logger {
|
|||||||
|
|
||||||
if (isString(input)) {
|
if (isString(input)) {
|
||||||
if (sensitiveFields.some((field) => input.includes(field))) {
|
if (sensitiveFields.some((field) => input.includes(field))) {
|
||||||
return ("[Filtered]" as any) as T;
|
return "[Filtered]" as any as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArray(input)) {
|
if (isArray(input)) {
|
||||||
return (input.map(this.sanitize) as any) as T;
|
return input.map(this.sanitize) as any as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isObject(input)) {
|
if (isObject(input)) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import env from "@server/env";
|
|||||||
import tracer from "./tracer";
|
import tracer from "./tracer";
|
||||||
import * as Tracing from "./tracer";
|
import * as Tracing from "./tracer";
|
||||||
|
|
||||||
type DDTag = typeof DDTags[keyof typeof DDTags];
|
type DDTag = (typeof DDTags)[keyof typeof DDTags];
|
||||||
|
|
||||||
type Tags = {
|
type Tags = {
|
||||||
[tag in DDTag]?: any;
|
[tag in DDTag]?: any;
|
||||||
@@ -55,72 +55,74 @@ interface TraceConfig {
|
|||||||
*
|
*
|
||||||
* @param config Optional configuration for the span that will be created for this trace.
|
* @param config Optional configuration for the span that will be created for this trace.
|
||||||
*/
|
*/
|
||||||
export const traceFunction = (config: TraceConfig) => <
|
export const traceFunction =
|
||||||
F extends (...args: any[]) => any,
|
(config: TraceConfig) =>
|
||||||
P extends Parameters<F>,
|
<
|
||||||
R extends ReturnType<F>
|
F extends (...args: any[]) => any,
|
||||||
>(
|
P extends Parameters<F>,
|
||||||
target: F
|
R extends ReturnType<F>
|
||||||
): F =>
|
>(
|
||||||
env.ENVIRONMENT === "test"
|
target: F
|
||||||
? target
|
): F =>
|
||||||
: (function wrapperFn(this: any, ...args: P): R {
|
env.ENVIRONMENT === "test"
|
||||||
const { className, methodName = target.name, tags } = config;
|
? target
|
||||||
const childOf = config.isRoot
|
: (function wrapperFn(this: any, ...args: P): R {
|
||||||
? undefined
|
const { className, methodName = target.name, tags } = config;
|
||||||
: tracer.scope().active() || undefined;
|
const childOf = config.isRoot
|
||||||
|
? undefined
|
||||||
|
: tracer.scope().active() || undefined;
|
||||||
|
|
||||||
const spanName = config.spanName || className || "DEFAULT_SPAN_NAME";
|
const spanName = config.spanName || className || "DEFAULT_SPAN_NAME";
|
||||||
|
|
||||||
const resourceName = config.resourceName
|
const resourceName = config.resourceName
|
||||||
? config.resourceName
|
? config.resourceName
|
||||||
: methodName;
|
: methodName;
|
||||||
const spanOptions: SpanOptions = {
|
const spanOptions: SpanOptions = {
|
||||||
childOf,
|
childOf,
|
||||||
tags: {
|
tags: {
|
||||||
[DDTags.RESOURCE_NAME]: resourceName,
|
[DDTags.RESOURCE_NAME]: resourceName,
|
||||||
...tags,
|
...tags,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const span = tracer.startSpan(spanName, spanOptions);
|
const span = tracer.startSpan(spanName, spanOptions);
|
||||||
|
|
||||||
if (!span) {
|
if (!span) {
|
||||||
return target.call(this, ...args);
|
return target.call(this, ...args);
|
||||||
}
|
|
||||||
|
|
||||||
if (config.serviceName) {
|
|
||||||
span.setTag(
|
|
||||||
DDTags.SERVICE_NAME,
|
|
||||||
`${env.DD_SERVICE}-${config.serviceName}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.makeSearchable) {
|
|
||||||
span.setTag(DDTags.ANALYTICS, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The callback fn needs to be wrapped in an arrow fn as the activate fn clobbers `this`
|
|
||||||
return tracer.scope().activate(span, () => {
|
|
||||||
const output = target.call(this, ...args);
|
|
||||||
|
|
||||||
if (output && typeof output.then === "function") {
|
|
||||||
output
|
|
||||||
.catch((error: Error | undefined) => {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
Tracing.setError(error, span);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
span.finish();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
span.finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
if (config.serviceName) {
|
||||||
});
|
span.setTag(
|
||||||
} as F);
|
DDTags.SERVICE_NAME,
|
||||||
|
`${env.DD_SERVICE}-${config.serviceName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.makeSearchable) {
|
||||||
|
span.setTag(DDTags.ANALYTICS, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The callback fn needs to be wrapped in an arrow fn as the activate fn clobbers `this`
|
||||||
|
return tracer.scope().activate(span, () => {
|
||||||
|
const output = target.call(this, ...args);
|
||||||
|
|
||||||
|
if (output && typeof output.then === "function") {
|
||||||
|
output
|
||||||
|
.catch((error: Error | undefined) => {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
Tracing.setError(error, span);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
span.finish();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
span.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
});
|
||||||
|
} as F);
|
||||||
|
|
||||||
const traceMethod = (config?: TraceConfig) =>
|
const traceMethod = (config?: TraceConfig) =>
|
||||||
function <R, A extends any[], F extends (...args: A) => R>(
|
function <R, A extends any[], F extends (...args: A) => R>(
|
||||||
|
|||||||
@@ -98,8 +98,9 @@ class AuthenticationProvider extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disable = async (options?: SaveOptions<AuthenticationProvider>) => {
|
disable = async (options?: SaveOptions<AuthenticationProvider>) => {
|
||||||
const res = await (this
|
const res = await (
|
||||||
.constructor as typeof AuthenticationProvider).findAndCountAll({
|
this.constructor as typeof AuthenticationProvider
|
||||||
|
).findAndCountAll({
|
||||||
...options,
|
...options,
|
||||||
where: {
|
where: {
|
||||||
teamId: this.teamId,
|
teamId: this.teamId,
|
||||||
|
|||||||
@@ -527,8 +527,9 @@ class Document extends ParanoidModel {
|
|||||||
const getChildDocumentIds = async (
|
const getChildDocumentIds = async (
|
||||||
...parentDocumentId: string[]
|
...parentDocumentId: string[]
|
||||||
): Promise<string[]> => {
|
): Promise<string[]> => {
|
||||||
const childDocuments = await (this
|
const childDocuments = await (
|
||||||
.constructor as typeof Document).findAll({
|
this.constructor as typeof Document
|
||||||
|
).findAll({
|
||||||
attributes: ["id"],
|
attributes: ["id"],
|
||||||
where: {
|
where: {
|
||||||
parentDocumentId,
|
parentDocumentId,
|
||||||
@@ -560,8 +561,9 @@ class Document extends ParanoidModel {
|
|||||||
|
|
||||||
// Helper to archive all child documents for a document
|
// Helper to archive all child documents for a document
|
||||||
const archiveChildren = async (parentDocumentId: string) => {
|
const archiveChildren = async (parentDocumentId: string) => {
|
||||||
const childDocuments = await (this
|
const childDocuments = await (
|
||||||
.constructor as typeof Document).findAll({
|
this.constructor as typeof Document
|
||||||
|
).findAll({
|
||||||
where: {
|
where: {
|
||||||
parentDocumentId,
|
parentDocumentId,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
/* eslint-disable @typescript-eslint/ban-types */
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
|
|
||||||
const Deprecated = (message?: string) => (
|
const Deprecated =
|
||||||
target: Object,
|
(message?: string) => (target: Object, propertyKey: string) => {
|
||||||
propertyKey: string
|
if (process.env[propertyKey]) {
|
||||||
) => {
|
// eslint-disable-next-line no-console
|
||||||
if (process.env[propertyKey]) {
|
console.warn(
|
||||||
// eslint-disable-next-line no-console
|
`The environment variable ${propertyKey} is deprecated and will be removed in a future release. ${message}`
|
||||||
console.warn(
|
);
|
||||||
`The environment variable ${propertyKey} is deprecated and will be removed in a future release. ${message}`
|
}
|
||||||
);
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Deprecated;
|
export default Deprecated;
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import NotificationHelper from "@server/models/helpers/NotificationHelper";
|
|||||||
import { CollectionEvent } from "@server/types";
|
import { CollectionEvent } from "@server/types";
|
||||||
import BaseTask, { TaskPriority } from "./BaseTask";
|
import BaseTask, { TaskPriority } from "./BaseTask";
|
||||||
|
|
||||||
export default class CollectionCreatedNotificationsTask extends BaseTask<
|
export default class CollectionCreatedNotificationsTask extends BaseTask<CollectionEvent> {
|
||||||
CollectionEvent
|
|
||||||
> {
|
|
||||||
public async perform(event: CollectionEvent) {
|
public async perform(event: CollectionEvent) {
|
||||||
const collection = await Collection.findByPk(event.collectionId);
|
const collection = await Collection.findByPk(event.collectionId);
|
||||||
|
|
||||||
@@ -15,10 +13,11 @@ export default class CollectionCreatedNotificationsTask extends BaseTask<
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipients = await NotificationHelper.getCollectionNotificationRecipients(
|
const recipients =
|
||||||
collection,
|
await NotificationHelper.getCollectionNotificationRecipients(
|
||||||
NotificationEventType.CreateCollection
|
collection,
|
||||||
);
|
NotificationEventType.CreateCollection
|
||||||
|
);
|
||||||
|
|
||||||
for (const recipient of recipients) {
|
for (const recipient of recipients) {
|
||||||
// Suppress notifications for suspended users
|
// Suppress notifications for suspended users
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import ProsemirrorHelper from "@server/models/helpers/ProsemirrorHelper";
|
|||||||
import { CommentEvent } from "@server/types";
|
import { CommentEvent } from "@server/types";
|
||||||
import BaseTask, { TaskPriority } from "./BaseTask";
|
import BaseTask, { TaskPriority } from "./BaseTask";
|
||||||
|
|
||||||
export default class CommentCreatedNotificationsTask extends BaseTask<
|
export default class CommentCreatedNotificationsTask extends BaseTask<CommentEvent> {
|
||||||
CommentEvent
|
|
||||||
> {
|
|
||||||
public async perform(event: CommentEvent) {
|
public async perform(event: CommentEvent) {
|
||||||
const [document, comment] = await Promise.all([
|
const [document, comment] = await Promise.all([
|
||||||
Document.scope("withCollection").findOne({
|
Document.scope("withCollection").findOne({
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import ProsemirrorHelper from "@server/models/helpers/ProsemirrorHelper";
|
|||||||
import { CommentEvent, CommentUpdateEvent } from "@server/types";
|
import { CommentEvent, CommentUpdateEvent } from "@server/types";
|
||||||
import BaseTask, { TaskPriority } from "./BaseTask";
|
import BaseTask, { TaskPriority } from "./BaseTask";
|
||||||
|
|
||||||
export default class CommentUpdatedNotificationsTask extends BaseTask<
|
export default class CommentUpdatedNotificationsTask extends BaseTask<CommentEvent> {
|
||||||
CommentEvent
|
|
||||||
> {
|
|
||||||
public async perform(event: CommentUpdateEvent) {
|
public async perform(event: CommentUpdateEvent) {
|
||||||
const [document, comment] = await Promise.all([
|
const [document, comment] = await Promise.all([
|
||||||
Document.scope("withCollection").findOne({
|
Document.scope("withCollection").findOne({
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import NotificationHelper from "@server/models/helpers/NotificationHelper";
|
|||||||
import { DocumentEvent } from "@server/types";
|
import { DocumentEvent } from "@server/types";
|
||||||
import BaseTask, { TaskPriority } from "./BaseTask";
|
import BaseTask, { TaskPriority } from "./BaseTask";
|
||||||
|
|
||||||
export default class DocumentPublishedNotificationsTask extends BaseTask<
|
export default class DocumentPublishedNotificationsTask extends BaseTask<DocumentEvent> {
|
||||||
DocumentEvent
|
|
||||||
> {
|
|
||||||
public async perform(event: DocumentEvent) {
|
public async perform(event: DocumentEvent) {
|
||||||
const document = await Document.findByPk(event.documentId, {
|
const document = await Document.findByPk(event.documentId, {
|
||||||
includeState: true,
|
includeState: true,
|
||||||
|
|||||||
@@ -287,7 +287,8 @@ export default class ImportNotionTask extends ImportTask {
|
|||||||
/**
|
/**
|
||||||
* Regex to find markdown images of all types
|
* Regex to find markdown images of all types
|
||||||
*/
|
*/
|
||||||
private ImageRegex = /!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<title>[^\][”]+)?”?\)/g;
|
private ImageRegex =
|
||||||
|
/!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<title>[^\][”]+)?”?\)/g;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex to find markdown links containing ID's that look like UUID's with the
|
* Regex to find markdown links containing ID's that look like UUID's with the
|
||||||
@@ -298,5 +299,6 @@ export default class ImportNotionTask extends ImportTask {
|
|||||||
/**
|
/**
|
||||||
* Regex to find Notion document UUID's in the title of a document.
|
* Regex to find Notion document UUID's in the title of a document.
|
||||||
*/
|
*/
|
||||||
private NotionUUIDRegex = /\s([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}|[0-9a-fA-F]{32})$/;
|
private NotionUUIDRegex =
|
||||||
|
/\s([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}|[0-9a-fA-F]{32})$/;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import NotificationHelper from "@server/models/helpers/NotificationHelper";
|
|||||||
import { RevisionEvent } from "@server/types";
|
import { RevisionEvent } from "@server/types";
|
||||||
import BaseTask, { TaskPriority } from "./BaseTask";
|
import BaseTask, { TaskPriority } from "./BaseTask";
|
||||||
|
|
||||||
export default class RevisionCreatedNotificationsTask extends BaseTask<
|
export default class RevisionCreatedNotificationsTask extends BaseTask<RevisionEvent> {
|
||||||
RevisionEvent
|
|
||||||
> {
|
|
||||||
public async perform(event: RevisionEvent) {
|
public async perform(event: RevisionEvent) {
|
||||||
const [document, revision] = await Promise.all([
|
const [document, revision] = await Promise.all([
|
||||||
Document.findByPk(event.documentId, { includeState: true }),
|
Document.findByPk(event.documentId, { includeState: true }),
|
||||||
|
|||||||
@@ -133,14 +133,12 @@ router.post("auth.info", auth(), async (ctx: APIContext) => {
|
|||||||
includeDetails: true,
|
includeDetails: true,
|
||||||
}),
|
}),
|
||||||
team: presentTeam(team),
|
team: presentTeam(team),
|
||||||
availableTeams: uniqBy(
|
availableTeams: uniqBy([...signedInTeams, ...availableTeams], "id").map(
|
||||||
[...signedInTeams, ...availableTeams],
|
(team) =>
|
||||||
"id"
|
presentAvailableTeam(
|
||||||
).map((team) =>
|
team,
|
||||||
presentAvailableTeam(
|
signedInTeamIds.includes(team.id) || team.id === user.teamId
|
||||||
team,
|
)
|
||||||
signedInTeamIds.includes(team.id) || team.id === user.teamId
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
policies: presentPolicies(user, [team]),
|
policies: presentPolicies(user, [team]),
|
||||||
|
|||||||
@@ -168,10 +168,8 @@ router.post(
|
|||||||
rateLimiter(RateLimiterStrategy.TenPerHour),
|
rateLimiter(RateLimiterStrategy.TenPerHour),
|
||||||
auth(),
|
auth(),
|
||||||
async (ctx: APIContext) => {
|
async (ctx: APIContext) => {
|
||||||
const {
|
const { attachmentId, format = FileOperationFormat.MarkdownZip } =
|
||||||
attachmentId,
|
ctx.request.body;
|
||||||
format = FileOperationFormat.MarkdownZip,
|
|
||||||
} = ctx.request.body;
|
|
||||||
assertUuid(attachmentId, "attachmentId is required");
|
assertUuid(attachmentId, "attachmentId is required");
|
||||||
|
|
||||||
const { user } = ctx.state.auth;
|
const { user } = ctx.state.auth;
|
||||||
@@ -630,16 +628,8 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.post("collections.update", auth(), async (ctx: APIContext) => {
|
router.post("collections.update", auth(), async (ctx: APIContext) => {
|
||||||
const {
|
const { id, name, description, icon, permission, color, sort, sharing } =
|
||||||
id,
|
ctx.request.body;
|
||||||
name,
|
|
||||||
description,
|
|
||||||
icon,
|
|
||||||
permission,
|
|
||||||
color,
|
|
||||||
sort,
|
|
||||||
sharing,
|
|
||||||
} = ctx.request.body;
|
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
assertHexColor(color, "Invalid hex value (please use format #FFFFFF)");
|
assertHexColor(color, "Invalid hex value (please use format #FFFFFF)");
|
||||||
|
|||||||
@@ -995,21 +995,18 @@ router.post(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { documents, collections, collectionChanged } =
|
||||||
documents,
|
await sequelize.transaction(async (transaction) =>
|
||||||
collections,
|
documentMover({
|
||||||
collectionChanged,
|
user,
|
||||||
} = await sequelize.transaction(async (transaction) =>
|
document,
|
||||||
documentMover({
|
collectionId,
|
||||||
user,
|
parentDocumentId,
|
||||||
document,
|
index,
|
||||||
collectionId,
|
ip: ctx.request.ip,
|
||||||
parentDocumentId,
|
transaction,
|
||||||
index,
|
})
|
||||||
ip: ctx.request.ip,
|
);
|
||||||
transaction,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -39,50 +39,49 @@ export default function init(
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on("upgrade", function (
|
server.on(
|
||||||
req: IncomingMessage,
|
"upgrade",
|
||||||
socket: Duplex,
|
function (req: IncomingMessage, socket: Duplex, head: Buffer) {
|
||||||
head: Buffer
|
if (req.url?.startsWith(path)) {
|
||||||
) {
|
// parse document id and close connection if not present in request
|
||||||
if (req.url?.startsWith(path)) {
|
const documentId = url
|
||||||
// parse document id and close connection if not present in request
|
.parse(req.url)
|
||||||
const documentId = url
|
.pathname?.replace(path, "")
|
||||||
.parse(req.url)
|
.split("/")
|
||||||
.pathname?.replace(path, "")
|
.pop();
|
||||||
.split("/")
|
|
||||||
.pop();
|
|
||||||
|
|
||||||
if (documentId) {
|
if (documentId) {
|
||||||
wss.handleUpgrade(req, socket, head, (client) => {
|
wss.handleUpgrade(req, socket, head, (client) => {
|
||||||
// Handle websocket connection errors as soon as the client is upgraded
|
// Handle websocket connection errors as soon as the client is upgraded
|
||||||
client.on("error", (error) => {
|
client.on("error", (error) => {
|
||||||
Logger.error(
|
Logger.error(
|
||||||
`Websocket error`,
|
`Websocket error`,
|
||||||
error,
|
error,
|
||||||
{
|
{
|
||||||
documentId,
|
documentId,
|
||||||
},
|
},
|
||||||
req
|
req
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
hocuspocus.handleConnection(client, req, documentId);
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hocuspocus.handleConnection(client, req, documentId);
|
if (
|
||||||
});
|
req.url?.startsWith("/realtime") &&
|
||||||
|
serviceNames.includes("websockets")
|
||||||
|
) {
|
||||||
|
// Nothing to do, the websockets service will handle this request
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
// If the collaboration service is running it will close the connection
|
||||||
req.url?.startsWith("/realtime") &&
|
socket.end(`HTTP/1.1 400 Bad Request\r\n`);
|
||||||
serviceNames.includes("websockets")
|
|
||||||
) {
|
|
||||||
// Nothing to do, the websockets service will handle this request
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// If the collaboration service is running it will close the connection
|
|
||||||
socket.end(`HTTP/1.1 400 Bad Request\r\n`);
|
|
||||||
});
|
|
||||||
|
|
||||||
ShutdownHelper.add("collaboration", ShutdownOrder.normal, () =>
|
ShutdownHelper.add("collaboration", ShutdownOrder.normal, () =>
|
||||||
hocuspocus.destroy()
|
hocuspocus.destroy()
|
||||||
|
|||||||
@@ -53,25 +53,24 @@ export default function init(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
server.on("upgrade", function (
|
server.on(
|
||||||
req: IncomingMessage,
|
"upgrade",
|
||||||
socket: Duplex,
|
function (req: IncomingMessage, socket: Duplex, head: Buffer) {
|
||||||
head: Buffer
|
if (req.url?.startsWith(path)) {
|
||||||
) {
|
invariant(ioHandleUpgrade, "Existing upgrade handler must exist");
|
||||||
if (req.url?.startsWith(path)) {
|
ioHandleUpgrade(req, socket, head);
|
||||||
invariant(ioHandleUpgrade, "Existing upgrade handler must exist");
|
return;
|
||||||
ioHandleUpgrade(req, socket, head);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serviceNames.includes("collaboration")) {
|
if (serviceNames.includes("collaboration")) {
|
||||||
// Nothing to do, the collaboration service will handle this request
|
// Nothing to do, the collaboration service will handle this request
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the collaboration service isn't running then we need to close the connection
|
// If the collaboration service isn't running then we need to close the connection
|
||||||
socket.end(`HTTP/1.1 400 Bad Request\r\n`);
|
socket.end(`HTTP/1.1 400 Bad Request\r\n`);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
ShutdownHelper.add("websockets", ShutdownOrder.normal, async () => {
|
ShutdownHelper.add("websockets", ShutdownOrder.normal, async () => {
|
||||||
Metrics.gaugePerInstance("websockets.count", 0);
|
Metrics.gaugePerInstance("websockets.count", 0);
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ it("should parse attachment ID from markdown with title", () => {
|
|||||||
it("should parse multiple attachment IDs from markdown", () => {
|
it("should parse multiple attachment IDs from markdown", () => {
|
||||||
const uuid = uuidv4();
|
const uuid = uuidv4();
|
||||||
const uuid2 = uuidv4();
|
const uuid2 = uuidv4();
|
||||||
const results = parseAttachmentIds(`
|
const results =
|
||||||
|
parseAttachmentIds(`
|
||||||
|
|
||||||
some text
|
some text
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { uniq, compact } from "lodash";
|
import { uniq, compact } from "lodash";
|
||||||
|
|
||||||
const attachmentRedirectRegex = /\/api\/attachments\.redirect\?id=(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;
|
const attachmentRedirectRegex =
|
||||||
const attachmentPublicRegex = /public\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;
|
/\/api\/attachments\.redirect\?id=(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;
|
||||||
|
const attachmentPublicRegex =
|
||||||
|
/public\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;
|
||||||
|
|
||||||
export default function parseAttachmentIds(
|
export default function parseAttachmentIds(
|
||||||
text: string,
|
text: string,
|
||||||
|
|||||||
@@ -14,17 +14,13 @@ const UPDATES_KEY = "UPDATES_KEY";
|
|||||||
export async function checkUpdates() {
|
export async function checkUpdates() {
|
||||||
const secret = env.SECRET_KEY.slice(0, 6) + env.URL;
|
const secret = env.SECRET_KEY.slice(0, 6) + env.URL;
|
||||||
const id = crypto.createHash("sha256").update(secret).digest("hex");
|
const id = crypto.createHash("sha256").update(secret).digest("hex");
|
||||||
const [
|
const [userCount, teamCount, collectionCount, documentCount] =
|
||||||
userCount,
|
await Promise.all([
|
||||||
teamCount,
|
User.count(),
|
||||||
collectionCount,
|
Team.count(),
|
||||||
documentCount,
|
Collection.count(),
|
||||||
] = await Promise.all([
|
Document.count(),
|
||||||
User.count(),
|
]);
|
||||||
Team.count(),
|
|
||||||
Collection.count(),
|
|
||||||
Document.count(),
|
|
||||||
]);
|
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
id,
|
id,
|
||||||
version: 1,
|
version: 1,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function CannotUseWithout(
|
|||||||
options: validationOptions,
|
options: validationOptions,
|
||||||
validator: {
|
validator: {
|
||||||
validate<T>(value: T, args: ValidationArguments) {
|
validate<T>(value: T, args: ValidationArguments) {
|
||||||
const object = (args.object as unknown) as T;
|
const object = args.object as unknown as T;
|
||||||
const required = args.constraints[0] as string;
|
const required = args.constraints[0] as string;
|
||||||
return object[required] !== undefined;
|
return object[required] !== undefined;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -96,9 +96,8 @@ export const newlineInCode = (state: EditorState, dispatch: Dispatch) => {
|
|||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
const splitByNewLine = text.split("\n");
|
const splitByNewLine = text.split("\n");
|
||||||
const numOfSpaces = splitByNewLine[splitByNewLine.length - 1].search(
|
const numOfSpaces =
|
||||||
/\S|$/
|
splitByNewLine[splitByNewLine.length - 1].search(/\S|$/);
|
||||||
);
|
|
||||||
newText += " ".repeat(numOfSpaces);
|
newText += " ".repeat(numOfSpaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,18 +100,18 @@ const Image = (
|
|||||||
document.removeEventListener("mousemove", handlePointerMove);
|
document.removeEventListener("mousemove", handlePointerMove);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePointerDown = (dragging: "left" | "right") => (
|
const handlePointerDown =
|
||||||
event: React.PointerEvent<HTMLDivElement>
|
(dragging: "left" | "right") =>
|
||||||
) => {
|
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setSizeAtDragStart({
|
setSizeAtDragStart({
|
||||||
width: constrainWidth(size.width),
|
width: constrainWidth(size.width),
|
||||||
height: size.height,
|
height: size.height,
|
||||||
});
|
});
|
||||||
setOffset(event.pageX);
|
setOffset(event.pageX);
|
||||||
setDragging(dragging);
|
setDragging(dragging);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
@@ -170,11 +170,14 @@ const Image = (
|
|||||||
onClick={dragging ? undefined : props.onClick}
|
onClick={dragging ? undefined : props.onClick}
|
||||||
style={widthStyle}
|
style={widthStyle}
|
||||||
>
|
>
|
||||||
{!dragging && size.width > 60 && size.height > 60 && props.onDownload && (
|
{!dragging &&
|
||||||
<Button onClick={props.onDownload}>
|
size.width > 60 &&
|
||||||
<DownloadIcon />
|
size.height > 60 &&
|
||||||
</Button>
|
props.onDownload && (
|
||||||
)}
|
<Button onClick={props.onDownload}>
|
||||||
|
<DownloadIcon />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<ImageZoom zoomMargin={24}>
|
<ImageZoom zoomMargin={24}>
|
||||||
<img
|
<img
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
export default function useComponentSize(
|
export default function useComponentSize(ref: React.RefObject<HTMLElement>): {
|
||||||
ref: React.RefObject<HTMLElement>
|
width: number;
|
||||||
): { width: number; height: number } {
|
height: number;
|
||||||
|
} {
|
||||||
const [size, setSize] = useState({
|
const [size, setSize] = useState({
|
||||||
width: ref.current?.clientWidth || 0,
|
width: ref.current?.clientWidth || 0,
|
||||||
height: ref.current?.clientHeight || 0,
|
height: ref.current?.clientHeight || 0,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import * as React from "react";
|
|||||||
import Frame from "../components/Frame";
|
import Frame from "../components/Frame";
|
||||||
import { EmbedProps as Props } from ".";
|
import { EmbedProps as Props } from ".";
|
||||||
|
|
||||||
const URL_REGEX = /(?:https?:\/\/)?(www\.bilibili\.com)\/video\/([\w\d]+)?(\?\S+)?/i;
|
const URL_REGEX =
|
||||||
|
/(?:https?:\/\/)?(www\.bilibili\.com)\/video\/([\w\d]+)?(\?\S+)?/i;
|
||||||
|
|
||||||
export default function Bilibili(props: Props) {
|
export default function Bilibili(props: Props) {
|
||||||
const { matches } = props.attrs;
|
const { matches } = props.attrs;
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import Frame from "../components/Frame";
|
|||||||
import ImageZoom from "../components/ImageZoom";
|
import ImageZoom from "../components/ImageZoom";
|
||||||
import { EmbedProps as Props } from ".";
|
import { EmbedProps as Props } from ".";
|
||||||
|
|
||||||
const IFRAME_REGEX = /^https:\/\/(invis\.io\/.*)|(projects\.invisionapp\.com\/share\/.*)$/;
|
const IFRAME_REGEX =
|
||||||
const IMAGE_REGEX = /^https:\/\/(opal\.invisionapp\.com\/static-signed\/live-embed\/.*)$/;
|
/^https:\/\/(invis\.io\/.*)|(projects\.invisionapp\.com\/share\/.*)$/;
|
||||||
|
const IMAGE_REGEX =
|
||||||
|
/^https:\/\/(opal\.invisionapp\.com\/static-signed\/live-embed\/.*)$/;
|
||||||
|
|
||||||
function InVision(props: Props) {
|
function InVision(props: Props) {
|
||||||
if (IMAGE_REGEX.test(props.attrs.href)) {
|
if (IMAGE_REGEX.test(props.attrs.href)) {
|
||||||
|
|||||||
@@ -164,9 +164,8 @@ export default function Mermaid({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (diagramToggled) {
|
if (diagramToggled) {
|
||||||
pluginState.diagramVisibility[
|
pluginState.diagramVisibility[mermaidMeta.toggleDiagram] =
|
||||||
mermaidMeta.toggleDiagram
|
!pluginState.diagramVisibility[mermaidMeta.toggleDiagram];
|
||||||
] = !pluginState.diagramVisibility[mermaidMeta.toggleDiagram];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -114,15 +114,13 @@ export default class Embed extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commands({ type }: { type: NodeType }) {
|
commands({ type }: { type: NodeType }) {
|
||||||
return (attrs: Record<string, any>) => (
|
return (attrs: Record<string, any>) =>
|
||||||
state: EditorState,
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
dispatch: Dispatch
|
dispatch(
|
||||||
) => {
|
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
|
||||||
dispatch(
|
);
|
||||||
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
|
return true;
|
||||||
);
|
};
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ export default class Emoji extends Suggestion {
|
|||||||
return {
|
return {
|
||||||
type: SuggestionsMenuType.Emoji,
|
type: SuggestionsMenuType.Emoji,
|
||||||
openRegex: /(?:^|\s):([0-9a-zA-Z_+-]+)?$/,
|
openRegex: /(?:^|\s):([0-9a-zA-Z_+-]+)?$/,
|
||||||
closeRegex: /(?:^|\s):(([0-9a-zA-Z_+-]*\s+)|(\s+[0-9a-zA-Z_+-]+)|[^0-9a-zA-Z_+-]+)$/,
|
closeRegex:
|
||||||
|
/(?:^|\s):(([0-9a-zA-Z_+-]*\s+)|(\s+[0-9a-zA-Z_+-]+)|[^0-9a-zA-Z_+-]+)$/,
|
||||||
enabledInTable: true,
|
enabledInTable: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -77,24 +78,22 @@ export default class Emoji extends Suggestion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commands({ type }: { type: NodeType; schema: Schema }) {
|
commands({ type }: { type: NodeType; schema: Schema }) {
|
||||||
return (attrs: Record<string, string>) => (
|
return (attrs: Record<string, string>) =>
|
||||||
state: EditorState,
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
dispatch: Dispatch
|
const { selection } = state;
|
||||||
) => {
|
const position =
|
||||||
const { selection } = state;
|
selection instanceof TextSelection
|
||||||
const position =
|
? selection.$cursor?.pos
|
||||||
selection instanceof TextSelection
|
: selection.$to.pos;
|
||||||
? selection.$cursor?.pos
|
if (position === undefined) {
|
||||||
: selection.$to.pos;
|
return false;
|
||||||
if (position === undefined) {
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = type.create(attrs);
|
const node = type.create(attrs);
|
||||||
const transaction = state.tr.insert(position, node);
|
const transaction = state.tr.insert(position, node);
|
||||||
dispatch(transaction);
|
dispatch(transaction);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
||||||
|
|||||||
@@ -28,15 +28,13 @@ export default class HorizontalRule extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commands({ type }: { type: NodeType }) {
|
commands({ type }: { type: NodeType }) {
|
||||||
return (attrs: Record<string, any>) => (
|
return (attrs: Record<string, any>) =>
|
||||||
state: EditorState,
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
dispatch: Dispatch
|
dispatch(
|
||||||
) => {
|
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
|
||||||
dispatch(
|
);
|
||||||
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
|
return true;
|
||||||
);
|
};
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keys({ type }: { type: NodeType }) {
|
keys({ type }: { type: NodeType }) {
|
||||||
|
|||||||
@@ -189,35 +189,31 @@ export default class Image extends SimpleImage {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeSize = ({
|
handleChangeSize =
|
||||||
node,
|
({ node, getPos }: { node: ProsemirrorNode; getPos: () => number }) =>
|
||||||
getPos,
|
({ width, height }: { width: number; height?: number }) => {
|
||||||
}: {
|
const { view } = this.editor;
|
||||||
node: ProsemirrorNode;
|
const { tr } = view.state;
|
||||||
getPos: () => number;
|
|
||||||
}) => ({ width, height }: { width: number; height?: number }) => {
|
|
||||||
const { view } = this.editor;
|
|
||||||
const { tr } = view.state;
|
|
||||||
|
|
||||||
const pos = getPos();
|
const pos = getPos();
|
||||||
const transaction = tr
|
const transaction = tr
|
||||||
.setNodeMarkup(pos, undefined, {
|
.setNodeMarkup(pos, undefined, {
|
||||||
...node.attrs,
|
...node.attrs,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
})
|
})
|
||||||
.setMeta("addToHistory", true);
|
.setMeta("addToHistory", true);
|
||||||
const $pos = transaction.doc.resolve(getPos());
|
const $pos = transaction.doc.resolve(getPos());
|
||||||
view.dispatch(transaction.setSelection(new NodeSelection($pos)));
|
view.dispatch(transaction.setSelection(new NodeSelection($pos)));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDownload = ({ node }: { node: ProsemirrorNode }) => (
|
handleDownload =
|
||||||
event: React.MouseEvent
|
({ node }: { node: ProsemirrorNode }) =>
|
||||||
) => {
|
(event: React.MouseEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
downloadImageNode(node);
|
downloadImageNode(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
component = (props: ComponentProps) => (
|
component = (props: ComponentProps) => (
|
||||||
<ImageComponent
|
<ImageComponent
|
||||||
@@ -360,7 +356,8 @@ export default class Image extends SimpleImage {
|
|||||||
*  -> [, "", "image.jpg", "small"]
|
*  -> [, "", "image.jpg", "small"]
|
||||||
*  -> [, "Lorem", "image.jpg", "small"]
|
*  -> [, "Lorem", "image.jpg", "small"]
|
||||||
*/
|
*/
|
||||||
const IMAGE_INPUT_REGEX = /!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/;
|
const IMAGE_INPUT_REGEX =
|
||||||
|
/!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => {
|
new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => {
|
||||||
|
|||||||
@@ -80,24 +80,22 @@ export default class Mention extends Suggestion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commands({ type }: { type: NodeType; schema: Schema }) {
|
commands({ type }: { type: NodeType; schema: Schema }) {
|
||||||
return (attrs: Record<string, string>) => (
|
return (attrs: Record<string, string>) =>
|
||||||
state: EditorState,
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
dispatch: Dispatch
|
const { selection } = state;
|
||||||
) => {
|
const position =
|
||||||
const { selection } = state;
|
selection instanceof TextSelection
|
||||||
const position =
|
? selection.$cursor?.pos
|
||||||
selection instanceof TextSelection
|
: selection.$to.pos;
|
||||||
? selection.$cursor?.pos
|
if (position === undefined) {
|
||||||
: selection.$to.pos;
|
return false;
|
||||||
if (position === undefined) {
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = type.create(attrs);
|
const node = type.create(attrs);
|
||||||
const transaction = state.tr.insert(position, node);
|
const transaction = state.tr.insert(position, node);
|
||||||
dispatch(transaction);
|
dispatch(transaction);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
||||||
|
|||||||
@@ -75,73 +75,65 @@ export default class SimpleImage extends Node {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = ({
|
handleKeyDown =
|
||||||
node,
|
({ node, getPos }: { node: ProsemirrorNode; getPos: () => number }) =>
|
||||||
getPos,
|
(event: React.KeyboardEvent<HTMLSpanElement>) => {
|
||||||
}: {
|
// Pressing Enter in the caption field should move the cursor/selection
|
||||||
node: ProsemirrorNode;
|
// below the image
|
||||||
getPos: () => number;
|
if (event.key === "Enter") {
|
||||||
}) => (event: React.KeyboardEvent<HTMLSpanElement>) => {
|
event.preventDefault();
|
||||||
// Pressing Enter in the caption field should move the cursor/selection
|
|
||||||
// below the image
|
const { view } = this.editor;
|
||||||
if (event.key === "Enter") {
|
const $pos = view.state.doc.resolve(getPos() + node.nodeSize);
|
||||||
|
view.dispatch(
|
||||||
|
view.state.tr.setSelection(new TextSelection($pos)).split($pos.pos)
|
||||||
|
);
|
||||||
|
view.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pressing Backspace in an an empty caption field should remove the entire
|
||||||
|
// image, leaving an empty paragraph
|
||||||
|
if (event.key === "Backspace" && event.currentTarget.innerText === "") {
|
||||||
|
const { view } = this.editor;
|
||||||
|
const $pos = view.state.doc.resolve(getPos());
|
||||||
|
const tr = view.state.tr.setSelection(new NodeSelection($pos));
|
||||||
|
view.dispatch(tr.deleteSelection());
|
||||||
|
view.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleBlur =
|
||||||
|
({ node, getPos }: { node: ProsemirrorNode; getPos: () => number }) =>
|
||||||
|
(event: React.FocusEvent<HTMLSpanElement>) => {
|
||||||
|
const caption = event.currentTarget.innerText;
|
||||||
|
if (caption === node.attrs.alt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { view } = this.editor;
|
||||||
|
const { tr } = view.state;
|
||||||
|
|
||||||
|
// update meta on object
|
||||||
|
const pos = getPos();
|
||||||
|
const transaction = tr.setNodeMarkup(pos, undefined, {
|
||||||
|
...node.attrs,
|
||||||
|
alt: caption,
|
||||||
|
});
|
||||||
|
view.dispatch(transaction);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelect =
|
||||||
|
({ getPos }: { getPos: () => number }) =>
|
||||||
|
(event: React.MouseEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const { view } = this.editor;
|
|
||||||
const $pos = view.state.doc.resolve(getPos() + node.nodeSize);
|
|
||||||
view.dispatch(
|
|
||||||
view.state.tr.setSelection(new TextSelection($pos)).split($pos.pos)
|
|
||||||
);
|
|
||||||
view.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pressing Backspace in an an empty caption field should remove the entire
|
|
||||||
// image, leaving an empty paragraph
|
|
||||||
if (event.key === "Backspace" && event.currentTarget.innerText === "") {
|
|
||||||
const { view } = this.editor;
|
const { view } = this.editor;
|
||||||
const $pos = view.state.doc.resolve(getPos());
|
const $pos = view.state.doc.resolve(getPos());
|
||||||
const tr = view.state.tr.setSelection(new NodeSelection($pos));
|
const transaction = view.state.tr.setSelection(new NodeSelection($pos));
|
||||||
view.dispatch(tr.deleteSelection());
|
view.dispatch(transaction);
|
||||||
view.focus();
|
};
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleBlur = ({
|
|
||||||
node,
|
|
||||||
getPos,
|
|
||||||
}: {
|
|
||||||
node: ProsemirrorNode;
|
|
||||||
getPos: () => number;
|
|
||||||
}) => (event: React.FocusEvent<HTMLSpanElement>) => {
|
|
||||||
const caption = event.currentTarget.innerText;
|
|
||||||
if (caption === node.attrs.alt) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { view } = this.editor;
|
|
||||||
const { tr } = view.state;
|
|
||||||
|
|
||||||
// update meta on object
|
|
||||||
const pos = getPos();
|
|
||||||
const transaction = tr.setNodeMarkup(pos, undefined, {
|
|
||||||
...node.attrs,
|
|
||||||
alt: caption,
|
|
||||||
});
|
|
||||||
view.dispatch(transaction);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSelect = ({ getPos }: { getPos: () => number }) => (
|
|
||||||
event: React.MouseEvent
|
|
||||||
) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const { view } = this.editor;
|
|
||||||
const $pos = view.state.doc.resolve(getPos());
|
|
||||||
const transaction = view.state.tr.setSelection(new NodeSelection($pos));
|
|
||||||
view.dispatch(transaction);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseDown = (ev: React.MouseEvent<HTMLParagraphElement>) => {
|
handleMouseDown = (ev: React.MouseEvent<HTMLParagraphElement>) => {
|
||||||
if (document.activeElement !== ev.currentTarget) {
|
if (document.activeElement !== ev.currentTarget) {
|
||||||
@@ -189,12 +181,8 @@ export default class SimpleImage extends Node {
|
|||||||
}
|
}
|
||||||
const { view } = this.editor;
|
const { view } = this.editor;
|
||||||
const { node } = state.selection;
|
const { node } = state.selection;
|
||||||
const {
|
const { uploadFile, onFileUploadStart, onFileUploadStop, onShowToast } =
|
||||||
uploadFile,
|
this.editor.props;
|
||||||
onFileUploadStart,
|
|
||||||
onFileUploadStop,
|
|
||||||
onShowToast,
|
|
||||||
} = this.editor.props;
|
|
||||||
|
|
||||||
if (!uploadFile) {
|
if (!uploadFile) {
|
||||||
throw new Error("uploadFile prop is required to replace images");
|
throw new Error("uploadFile prop is required to replace images");
|
||||||
@@ -225,24 +213,23 @@ export default class SimpleImage extends Node {
|
|||||||
inputElement.click();
|
inputElement.click();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
createImage: (attrs: Record<string, any>) => (
|
createImage:
|
||||||
state: EditorState,
|
(attrs: Record<string, any>) =>
|
||||||
dispatch: Dispatch
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
) => {
|
const { selection } = state;
|
||||||
const { selection } = state;
|
const position =
|
||||||
const position =
|
selection instanceof TextSelection
|
||||||
selection instanceof TextSelection
|
? selection.$cursor?.pos
|
||||||
? selection.$cursor?.pos
|
: selection.$to.pos;
|
||||||
: selection.$to.pos;
|
if (position === undefined) {
|
||||||
if (position === undefined) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const node = type.create(attrs);
|
const node = type.create(attrs);
|
||||||
const transaction = state.tr.insert(position, node);
|
const transaction = state.tr.insert(position, node);
|
||||||
dispatch(transaction);
|
dispatch(transaction);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +242,8 @@ export default class SimpleImage extends Node {
|
|||||||
*  -> [, "", "image.jpg", "small"]
|
*  -> [, "", "image.jpg", "small"]
|
||||||
*  -> [, "Lorem", "image.jpg", "small"]
|
*  -> [, "Lorem", "image.jpg", "small"]
|
||||||
*/
|
*/
|
||||||
const IMAGE_INPUT_REGEX = /!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/;
|
const IMAGE_INPUT_REGEX =
|
||||||
|
/!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => {
|
new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => {
|
||||||
|
|||||||
@@ -60,56 +60,47 @@ export default class Table extends Node {
|
|||||||
|
|
||||||
commands({ schema }: { schema: Schema }) {
|
commands({ schema }: { schema: Schema }) {
|
||||||
return {
|
return {
|
||||||
createTable: ({
|
createTable:
|
||||||
rowsCount,
|
({ rowsCount, colsCount }: { rowsCount: number; colsCount: number }) =>
|
||||||
colsCount,
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
}: {
|
const offset = state.tr.selection.anchor + 1;
|
||||||
rowsCount: number;
|
const nodes = createTable(schema, rowsCount, colsCount);
|
||||||
colsCount: number;
|
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
|
||||||
}) => (state: EditorState, dispatch: Dispatch) => {
|
const resolvedPos = tr.doc.resolve(offset);
|
||||||
const offset = state.tr.selection.anchor + 1;
|
|
||||||
const nodes = createTable(schema, rowsCount, colsCount);
|
|
||||||
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
|
|
||||||
const resolvedPos = tr.doc.resolve(offset);
|
|
||||||
|
|
||||||
tr.setSelection(TextSelection.near(resolvedPos));
|
tr.setSelection(TextSelection.near(resolvedPos));
|
||||||
dispatch(tr);
|
dispatch(tr);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
setColumnAttr: ({
|
setColumnAttr:
|
||||||
index,
|
({ index, alignment }: { index: number; alignment: string }) =>
|
||||||
alignment,
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
}: {
|
const cells = getCellsInColumn(index)(state.selection) || [];
|
||||||
index: number;
|
let transaction = state.tr;
|
||||||
alignment: string;
|
cells.forEach(({ pos }) => {
|
||||||
}) => (state: EditorState, dispatch: Dispatch) => {
|
transaction = transaction.setNodeMarkup(pos, undefined, {
|
||||||
const cells = getCellsInColumn(index)(state.selection) || [];
|
alignment,
|
||||||
let transaction = state.tr;
|
});
|
||||||
cells.forEach(({ pos }) => {
|
|
||||||
transaction = transaction.setNodeMarkup(pos, undefined, {
|
|
||||||
alignment,
|
|
||||||
});
|
});
|
||||||
});
|
dispatch(transaction);
|
||||||
dispatch(transaction);
|
return true;
|
||||||
return true;
|
},
|
||||||
},
|
|
||||||
addColumnBefore: () => addColumnBefore,
|
addColumnBefore: () => addColumnBefore,
|
||||||
addColumnAfter: () => addColumnAfter,
|
addColumnAfter: () => addColumnAfter,
|
||||||
deleteColumn: () => deleteColumn,
|
deleteColumn: () => deleteColumn,
|
||||||
addRowAfter: ({ index }: { index: number }) => (
|
addRowAfter:
|
||||||
state: EditorState,
|
({ index }: { index: number }) =>
|
||||||
dispatch: Dispatch
|
(state: EditorState, dispatch: Dispatch) => {
|
||||||
) => {
|
if (index === 0) {
|
||||||
if (index === 0) {
|
// A little hack to avoid cloning the heading row by cloning the row
|
||||||
// A little hack to avoid cloning the heading row by cloning the row
|
// beneath and then moving it to the right index.
|
||||||
// beneath and then moving it to the right index.
|
const tr = addRowAt(index + 2, true)(state.tr);
|
||||||
const tr = addRowAt(index + 2, true)(state.tr);
|
dispatch(moveRow(index + 2, index + 1)(tr));
|
||||||
dispatch(moveRow(index + 2, index + 1)(tr));
|
} else {
|
||||||
} else {
|
dispatch(addRowAt(index + 1, true)(state.tr));
|
||||||
dispatch(addRowAt(index + 1, true)(state.tr));
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
},
|
||||||
},
|
|
||||||
deleteRow: () => deleteRow,
|
deleteRow: () => deleteRow,
|
||||||
deleteTable: () => deleteTable,
|
deleteTable: () => deleteTable,
|
||||||
toggleHeaderColumn: () => toggleHeaderColumn,
|
toggleHeaderColumn: () => toggleHeaderColumn,
|
||||||
@@ -127,7 +118,7 @@ export default class Table extends Node {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const index = getRowIndexFromText(
|
const index = getRowIndexFromText(
|
||||||
(state.selection as unknown) as CellSelection
|
state.selection as unknown as CellSelection
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { MarkType } from "prosemirror-model";
|
import { MarkType } from "prosemirror-model";
|
||||||
import { EditorState } from "prosemirror-state";
|
import { EditorState } from "prosemirror-state";
|
||||||
|
|
||||||
const isMarkActive = (type: MarkType) => (state: EditorState): boolean => {
|
const isMarkActive =
|
||||||
if (!type) {
|
(type: MarkType) =>
|
||||||
return false;
|
(state: EditorState): boolean => {
|
||||||
}
|
if (!type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const { from, $from, to, empty } = state.selection;
|
const { from, $from, to, empty } = state.selection;
|
||||||
|
|
||||||
return !!(empty
|
return !!(empty
|
||||||
? type.isInSet(state.storedMarks || $from.marks())
|
? type.isInSet(state.storedMarks || $from.marks())
|
||||||
: state.doc.rangeHasMark(from, to, type));
|
: state.doc.rangeHasMark(from, to, type));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default isMarkActive;
|
export default isMarkActive;
|
||||||
|
|||||||
@@ -2,22 +2,22 @@ import { NodeType } from "prosemirror-model";
|
|||||||
import { EditorState } from "prosemirror-state";
|
import { EditorState } from "prosemirror-state";
|
||||||
import { findParentNode, findSelectedNodeOfType } from "prosemirror-utils";
|
import { findParentNode, findSelectedNodeOfType } from "prosemirror-utils";
|
||||||
|
|
||||||
const isNodeActive = (type: NodeType, attrs: Record<string, any> = {}) => (
|
const isNodeActive =
|
||||||
state: EditorState
|
(type: NodeType, attrs: Record<string, any> = {}) =>
|
||||||
) => {
|
(state: EditorState) => {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node =
|
const node =
|
||||||
findSelectedNodeOfType(type)(state.selection) ||
|
findSelectedNodeOfType(type)(state.selection) ||
|
||||||
findParentNode((node) => node.type === type)(state.selection);
|
findParentNode((node) => node.type === type)(state.selection);
|
||||||
|
|
||||||
if (!Object.keys(attrs).length || !node) {
|
if (!Object.keys(attrs).length || !node) {
|
||||||
return !!node;
|
return !!node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return node.node.hasMarkup(type, { ...node.node.attrs, ...attrs });
|
return node.node.hasMarkup(type, { ...node.node.attrs, ...attrs });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default isNodeActive;
|
export default isNodeActive;
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ export const ellipsis = () => `
|
|||||||
*
|
*
|
||||||
* @returns a theme value
|
* @returns a theme value
|
||||||
*/
|
*/
|
||||||
export const s = (key: keyof DefaultTheme) => (props: {
|
export const s =
|
||||||
theme: DefaultTheme;
|
(key: keyof DefaultTheme) => (props: { theme: DefaultTheme }) =>
|
||||||
}) => String(props.theme[key]);
|
String(props.theme[key]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mixin to hide scrollbars.
|
* Mixin to hide scrollbars.
|
||||||
|
|||||||
@@ -10568,10 +10568,10 @@ prettier-linter-helpers@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff "^1.1.2"
|
fast-diff "^1.1.2"
|
||||||
|
|
||||||
prettier@^2.0.5:
|
prettier@^2.8.8:
|
||||||
version "2.1.2"
|
version "2.8.8"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||||
integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==
|
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||||
|
|
||||||
pretty-bytes@^5.3.0:
|
pretty-bytes@^5.3.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user