Merge branch 'release/v2.6.0' into feature/refacroting-selector
This commit is contained in:
commit
d6de247212
@ -74,6 +74,7 @@
|
||||
"@babel/preset-typescript": "^7.21.0",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@types/eslint": "^8.44.7",
|
||||
"@types/he": "^1.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"babel-loader": "^8.3.0",
|
||||
|
@ -21,6 +21,5 @@
|
||||
"UploadFromPortalDescription": "Upload any type files from Documents or Rooms",
|
||||
"UploadFromPortalTitle": "Upload from {{productName}}",
|
||||
"UploadPDFFormOptionDescription": "Select a ready PDF form available in {{productName}} and upload it to the room.",
|
||||
"UserEmptyDescription": "Files and folders uploaded by admins will appeared here.",
|
||||
"UserEmptyTitle": "No docs here yet"
|
||||
"UserEmptyDescription": "Files and folders uploaded by admins will appeared here."
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ const EmptyFolderContainer = ({
|
||||
isFolder={!isRoom}
|
||||
folderId={folderId}
|
||||
parentRoomType={parentRoomType}
|
||||
isArchiveFolderRoot={isArchiveFolderRoot}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ export const getDescription = (
|
||||
isFolder: boolean,
|
||||
folderType: Nullable<FolderType>,
|
||||
parentRoomType: Nullable<FolderType>,
|
||||
isArchiveFolderRoot: boolean,
|
||||
): string => {
|
||||
const isCollaborator = access === ShareAccessRights.Collaborator;
|
||||
|
||||
@ -88,8 +89,13 @@ export const getDescription = (
|
||||
],
|
||||
() => t("Files:EmptyFormSubFolderHeaderText"),
|
||||
)
|
||||
.with([FolderType.FormRoom, null, P.when(() => isNotAdmin)], () =>
|
||||
t("EmptyView:FormFolderDefaultUserDescription"),
|
||||
.with(
|
||||
[
|
||||
FolderType.FormRoom,
|
||||
null,
|
||||
P.when(() => isNotAdmin || isArchiveFolderRoot),
|
||||
],
|
||||
() => t("EmptyView:FormFolderDefaultUserDescription"),
|
||||
)
|
||||
.with([FolderType.FormRoom, null, P._], () =>
|
||||
t("EmptyView:FormFolderDefaultDescription", {
|
||||
@ -99,7 +105,8 @@ export const getDescription = (
|
||||
.otherwise(() => "");
|
||||
}
|
||||
|
||||
if (isNotAdmin) return t("EmptyView:UserEmptyDescription");
|
||||
if (isNotAdmin || isArchiveFolderRoot)
|
||||
return t("EmptyView:UserEmptyDescription");
|
||||
|
||||
if (isCollaborator) return t("EmptyView:CollaboratorEmptyDesciprtion");
|
||||
|
||||
@ -113,6 +120,7 @@ export const getTitle = (
|
||||
isFolder: boolean,
|
||||
folderType: Nullable<FolderType>,
|
||||
parentRoomType: Nullable<FolderType>,
|
||||
isArchiveFolderRoot: boolean,
|
||||
): string => {
|
||||
const isCollaborator = access === ShareAccessRights.Collaborator;
|
||||
|
||||
@ -135,8 +143,13 @@ export const getTitle = (
|
||||
.with([P._, FolderType.SubFolderInProgress, P._], () =>
|
||||
t("Files:EmptyFormSubFolderProgressDescriptionText"),
|
||||
)
|
||||
.with([FolderType.FormRoom, null, P.when(() => isNotAdmin)], () =>
|
||||
t("EmptyView:FormFolderDefaultUserTitle"),
|
||||
.with(
|
||||
[
|
||||
FolderType.FormRoom,
|
||||
null,
|
||||
P.when(() => isNotAdmin || isArchiveFolderRoot),
|
||||
],
|
||||
() => t("EmptyView:FormFolderDefaultUserTitle"),
|
||||
)
|
||||
.with([FolderType.FormRoom, null, P._], () =>
|
||||
t("EmptyView:FormFolderDefaultTitle"),
|
||||
@ -146,7 +159,7 @@ export const getTitle = (
|
||||
|
||||
if (isCollaborator) return t("EmptyView:CollaboratorEmptyTitle");
|
||||
|
||||
if (isNotAdmin) return t("EmptyView:UserEmptyTitle");
|
||||
if (isNotAdmin || isArchiveFolderRoot) return t("Files:EmptyScreenFolder");
|
||||
|
||||
switch (type) {
|
||||
case RoomsType.FormRoom:
|
||||
@ -317,6 +330,7 @@ export const getOptions = (
|
||||
isFolder: boolean,
|
||||
folderType: Nullable<FolderType>,
|
||||
parentRoomType: Nullable<FolderType>,
|
||||
isArchiveFolderRoot: boolean,
|
||||
actions: OptionActions,
|
||||
): EmptyViewItemType[] => {
|
||||
const isFormFiller = access === ShareAccessRights.FormFilling;
|
||||
@ -417,6 +431,8 @@ export const getOptions = (
|
||||
],
|
||||
};
|
||||
|
||||
if (isArchiveFolderRoot) return [];
|
||||
|
||||
if (isFolder) {
|
||||
return match([parentRoomType, folderType, access])
|
||||
.with(
|
||||
|
@ -30,6 +30,7 @@ const EmptyViewContainer = observer(
|
||||
folderType,
|
||||
selectedFolder,
|
||||
parentRoomType,
|
||||
isArchiveFolderRoot,
|
||||
onClickInviteUsers,
|
||||
onCreateAndCopySharedLink,
|
||||
setSelectFileFormRoomDialogVisible,
|
||||
@ -95,6 +96,7 @@ const EmptyViewContainer = observer(
|
||||
isFolder,
|
||||
folderType,
|
||||
parentRoomType,
|
||||
isArchiveFolderRoot,
|
||||
);
|
||||
const title = getTitle(
|
||||
type,
|
||||
@ -103,6 +105,7 @@ const EmptyViewContainer = observer(
|
||||
isFolder,
|
||||
folderType,
|
||||
parentRoomType,
|
||||
isArchiveFolderRoot,
|
||||
);
|
||||
const icon = getIcon(
|
||||
type,
|
||||
@ -114,7 +117,16 @@ const EmptyViewContainer = observer(
|
||||
);
|
||||
|
||||
return { description, title, icon };
|
||||
}, [type, t, theme.isBase, access, isFolder, folderType, parentRoomType]);
|
||||
}, [
|
||||
type,
|
||||
t,
|
||||
theme.isBase,
|
||||
access,
|
||||
isFolder,
|
||||
folderType,
|
||||
parentRoomType,
|
||||
isArchiveFolderRoot,
|
||||
]);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
@ -126,6 +138,7 @@ const EmptyViewContainer = observer(
|
||||
isFolder,
|
||||
folderType,
|
||||
parentRoomType,
|
||||
isArchiveFolderRoot,
|
||||
{
|
||||
inviteUser,
|
||||
onCreate,
|
||||
@ -141,6 +154,7 @@ const EmptyViewContainer = observer(
|
||||
isFolder,
|
||||
folderType,
|
||||
parentRoomType,
|
||||
isArchiveFolderRoot,
|
||||
t,
|
||||
inviteUser,
|
||||
uploadFromDocspace,
|
||||
|
@ -28,6 +28,7 @@ export interface EmptyViewContainerProps {
|
||||
parentRoomType: Nullable<FolderType>;
|
||||
folderType: Nullable<FolderType>;
|
||||
isFolder: boolean;
|
||||
isArchiveFolderRoot: boolean;
|
||||
onClickInviteUsers?: (folderId: string | number, roomType: RoomsType) => void;
|
||||
setSelectFileFormRoomDialogVisible?: TStore["dialogsStore"]["setSelectFileFormRoomDialogVisible"];
|
||||
onCreateAndCopySharedLink?: TStore["contextOptionsStore"]["onCreateAndCopySharedLink"];
|
||||
|
@ -109,6 +109,7 @@ const ConfirmRoute = ({
|
||||
|
||||
switch (validationResult) {
|
||||
case ValidationResult.Ok:
|
||||
case ValidationResult.UserExisted:
|
||||
const confirmHeader = search.slice(1);
|
||||
const linkData = {
|
||||
...confirmLinkData,
|
||||
@ -129,7 +130,10 @@ const ConfirmRoute = ({
|
||||
setState((val) => ({ ...val, isLoaded: true, linkData, roomData }));
|
||||
break;
|
||||
case ValidationResult.Invalid:
|
||||
console.error("invlid link", { confirmLinkData, validationResult });
|
||||
console.error("invalid link", {
|
||||
confirmLinkData,
|
||||
validationResult,
|
||||
});
|
||||
window.location.href = combineUrl(
|
||||
window.ClientConfig?.proxy?.url,
|
||||
path,
|
||||
@ -159,6 +163,24 @@ const ConfirmRoute = ({
|
||||
"/error?messageKey=20",
|
||||
);
|
||||
break;
|
||||
case ValidationResult.QuotaFailed:
|
||||
console.error("access below quota", {
|
||||
confirmLinkData,
|
||||
validationResult,
|
||||
});
|
||||
window.location.href = combineUrl(
|
||||
window.ClientConfig?.proxy?.url,
|
||||
path,
|
||||
"/error",
|
||||
);
|
||||
break;
|
||||
case ValidationResult.UserExcluded:
|
||||
console.error("user excluded", {
|
||||
confirmLinkData,
|
||||
validationResult,
|
||||
});
|
||||
window.location.replace(defaultPage);
|
||||
break;
|
||||
default:
|
||||
console.error("unknown link", {
|
||||
confirmLinkData,
|
||||
|
@ -39,4 +39,7 @@ export const enum ValidationResult {
|
||||
Invalid = 1,
|
||||
Expired = 2,
|
||||
TariffLimit = 3,
|
||||
UserExisted = 4,
|
||||
UserExcluded = 5,
|
||||
QuotaFailed = 6,
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ const CreateUserForm = (props) => {
|
||||
|
||||
const emailFromLink = linkData?.email ? linkData.email : "";
|
||||
const roomName = roomData?.title;
|
||||
const roomId = roomData?.roomId;
|
||||
|
||||
const [email, setEmail] = useState(emailFromLink);
|
||||
const [emailValid, setEmailValid] = useState(true);
|
||||
@ -199,6 +200,7 @@ const CreateUserForm = (props) => {
|
||||
roomName,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
linkData: linkData,
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -209,6 +211,14 @@ const CreateUserForm = (props) => {
|
||||
"max-age": COOKIE_EXPIRATION_YEAR,
|
||||
});
|
||||
|
||||
const finalUrl = roomId
|
||||
? `/rooms/shared/filter?folder=${roomId}`
|
||||
: defaultPage;
|
||||
|
||||
if (roomId) {
|
||||
sessionStorage.setItem("referenceUrl", finalUrl);
|
||||
}
|
||||
|
||||
window.location.href = combineUrl(
|
||||
window.ClientConfig?.proxy?.url,
|
||||
"/login",
|
||||
|
@ -24,13 +24,14 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { decode } from "he";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
import { Link } from "@docspace/shared/components/link";
|
||||
import { toastr } from "@docspace/shared/components/toast";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
import { RoomsType } from "@docspace/shared/enums";
|
||||
import { TTranslation } from "@docspace/shared/types";
|
||||
|
||||
import { StyledHistoryLink } from "../../../styles/history";
|
||||
@ -65,7 +66,9 @@ interface HistoryRoomExternalLinkProps {
|
||||
isEdit: boolean;
|
||||
link: TFeedData;
|
||||
roomId: number | string;
|
||||
isFormRoom?: boolean;
|
||||
}) => void;
|
||||
isFormRoom?: boolean;
|
||||
}
|
||||
|
||||
const HistoryRoomExternalLink = ({
|
||||
@ -75,6 +78,7 @@ const HistoryRoomExternalLink = ({
|
||||
setEditLinkPanelIsVisible,
|
||||
setLinkParams,
|
||||
roomId,
|
||||
isFormRoom,
|
||||
}: HistoryRoomExternalLinkProps) => {
|
||||
const onEditLink = () => {
|
||||
if (!feedData.sharedTo) {
|
||||
@ -82,7 +86,12 @@ const HistoryRoomExternalLink = ({
|
||||
return;
|
||||
}
|
||||
|
||||
setLinkParams({ isEdit: true, link: feedData, roomId });
|
||||
setLinkParams({
|
||||
isEdit: true,
|
||||
link: feedData,
|
||||
roomId,
|
||||
isFormRoom,
|
||||
});
|
||||
setEditLinkPanelIsVisible(true);
|
||||
};
|
||||
|
||||
@ -90,29 +99,31 @@ const HistoryRoomExternalLink = ({
|
||||
<StyledHistoryLink>
|
||||
{canEditLink ? (
|
||||
<Link className="text link" onClick={onEditLink}>
|
||||
{decode(feedData.title || feedData.sharedTo?.title)}
|
||||
{decode((feedData.title || feedData.sharedTo?.title) ?? "")}
|
||||
</Link>
|
||||
) : (
|
||||
<Text as="span" className="text">
|
||||
{decode(feedData.title || feedData.sharedTo?.title)}
|
||||
{decode((feedData.title || feedData.sharedTo?.title) ?? "")}
|
||||
</Text>
|
||||
)}
|
||||
</StyledHistoryLink>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ userStore, dialogsStore, infoPanelStore }) => {
|
||||
export default inject<TStore>(({ userStore, dialogsStore, infoPanelStore }) => {
|
||||
const { infoPanelSelection } = infoPanelStore;
|
||||
const { setLinkParams, setEditLinkPanelIsVisible } = dialogsStore;
|
||||
const { user } = userStore;
|
||||
const { id } = infoPanelSelection;
|
||||
const { id, roomType } = infoPanelSelection!;
|
||||
|
||||
const cannotEdit = user.isVisitor || user.isCollaborator;
|
||||
const isFormRoom = roomType === RoomsType.FormRoom;
|
||||
const cannotEdit = user?.isVisitor || user?.isCollaborator;
|
||||
|
||||
return {
|
||||
canEditLink: !cannotEdit,
|
||||
setEditLinkPanelIsVisible,
|
||||
setLinkParams,
|
||||
roomId: id,
|
||||
isFormRoom,
|
||||
};
|
||||
})(withTranslation(["InfoPanel"])(observer(HistoryRoomExternalLink)));
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { ReactSVG } from "react-svg";
|
||||
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
|
||||
@ -90,9 +91,10 @@ const PresetTile = (props) => {
|
||||
<Text fontSize="16px" lineHeight="22px" fontWeight={700}>
|
||||
{title}
|
||||
</Text>
|
||||
<img height={180} width={310} src={image} alt={title} />
|
||||
<ReactSVG src={image} />
|
||||
<Text lineHeight="20px">{description}</Text>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="navigationButton"
|
||||
label={t("SetUp")}
|
||||
|
@ -105,7 +105,7 @@ const confirmRoutes = [
|
||||
{
|
||||
path: "LinkInvite",
|
||||
element: (
|
||||
<ConfirmRoute doAuthenticated={AuthenticatedAction.Redirect}>
|
||||
<ConfirmRoute doAuthenticated={AuthenticatedAction.None}>
|
||||
<CreateUserForm />
|
||||
</ConfirmRoute>
|
||||
),
|
||||
|
@ -48,7 +48,7 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
async function Page({ searchParams }: RootPageProps) {
|
||||
const { fileId, fileid, version, doc, action, share, editorType } =
|
||||
const { fileId, fileid, version, doc, action, share, editorType, error } =
|
||||
searchParams ?? initialSearchParams;
|
||||
|
||||
const startDate = new Date();
|
||||
@ -64,6 +64,9 @@ async function Page({ searchParams }: RootPageProps) {
|
||||
|
||||
const timer = new Date().getTime() - startDate.getTime();
|
||||
|
||||
if (data.error?.status === "not-found" && error) {
|
||||
data.error.message = error;
|
||||
}
|
||||
return <Root {...data} timer={timer} />;
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
|
||||
import IConfig from "@onlyoffice/document-editor-react/dist/esm/types/model/config";
|
||||
|
||||
@ -83,6 +84,9 @@ const useEditorEvents = ({
|
||||
openOnNewPage,
|
||||
t,
|
||||
}: UseEventsProps) => {
|
||||
const searchParams = useSearchParams();
|
||||
const pathname = usePathname();
|
||||
|
||||
const [events, setEvents] = React.useState<IConfigEvents>({});
|
||||
const [documentReady, setDocumentReady] = React.useState(false);
|
||||
const [createUrl, setCreateUrl] = React.useState<Nullable<string>>(null);
|
||||
@ -163,7 +167,24 @@ const useEditorEvents = ({
|
||||
if (config?.Error) docEditor?.showMessage?.(config.Error);
|
||||
}
|
||||
}
|
||||
}, [config?.Error, errorMessage, isSkipError, t]);
|
||||
|
||||
const message = searchParams.get("message");
|
||||
|
||||
if (message) {
|
||||
docEditor?.showMessage?.(message);
|
||||
let search = "?";
|
||||
let idx = 0;
|
||||
searchParams.forEach((value, key) => {
|
||||
if (key !== "message") {
|
||||
if (idx) search += "&";
|
||||
idx++;
|
||||
search += `${key}=${value}`;
|
||||
}
|
||||
});
|
||||
|
||||
history.pushState({}, "", `${pathname}${search}`);
|
||||
}
|
||||
}, [config?.Error, errorMessage, isSkipError, searchParams, pathname, t]);
|
||||
|
||||
const onDocumentReady = React.useCallback(() => {
|
||||
// console.log("onDocumentReady", { docEditor });
|
||||
|
@ -68,6 +68,7 @@ export type RootPageProps = {
|
||||
action: ActionType;
|
||||
share: string;
|
||||
editorType: string;
|
||||
error?: string;
|
||||
}>;
|
||||
};
|
||||
export type TDocumentInfo = {
|
||||
|
@ -46,12 +46,12 @@ import { createPasswordHash } from "@docspace/shared/utils/common";
|
||||
import { checkPwd } from "@docspace/shared/utils/desktop";
|
||||
import { login } from "@docspace/shared/utils/loginUtils";
|
||||
import { toastr } from "@docspace/shared/components/toast";
|
||||
import { thirdPartyLogin } from "@docspace/shared/api/user";
|
||||
import { thirdPartyLogin, checkConfirmLink } from "@docspace/shared/api/user";
|
||||
import { setWithCredentialsStatus } from "@docspace/shared/api/client";
|
||||
import { TValidate } from "@docspace/shared/components/email-input/EmailInput.types";
|
||||
|
||||
import { LoginFormProps } from "@/types";
|
||||
import { getEmailFromInvitation } from "@/utils";
|
||||
import { getEmailFromInvitation, getConfirmDataFromInvitation } from "@/utils";
|
||||
|
||||
import EmailContainer from "./sub-components/EmailContainer";
|
||||
import PasswordContainer from "./sub-components/PasswordContainer";
|
||||
@ -250,13 +250,18 @@ const LoginForm = ({
|
||||
const hash = !isLdapLoginChecked
|
||||
? createPasswordHash(pass, hashSettings)
|
||||
: undefined;
|
||||
|
||||
const pwd = isLdapLoginChecked ? pass : undefined;
|
||||
|
||||
const confirmData = getConfirmDataFromInvitation(loginData);
|
||||
|
||||
isDesktop && checkPwd();
|
||||
const session = !isChecked;
|
||||
|
||||
login(user, hash, pwd, session, captchaToken, currentCulture, reCaptchaType)
|
||||
.then((res: string | object) => {
|
||||
checkConfirmLink(confirmData);
|
||||
|
||||
const isConfirm = typeof res === "string" && res.includes("confirm");
|
||||
const redirectPath =
|
||||
referenceUrl || sessionStorage.getItem("referenceUrl");
|
||||
|
@ -126,6 +126,12 @@ export const getInvitationLinkData = (encodeString: string) => {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
type: string;
|
||||
linkData?: {
|
||||
confirmHeader?: string;
|
||||
key: string;
|
||||
type: string;
|
||||
uid?: string;
|
||||
};
|
||||
};
|
||||
|
||||
return queryParams;
|
||||
@ -140,3 +146,15 @@ export const getEmailFromInvitation = (encodeString: Nullable<string>) => {
|
||||
|
||||
return queryParams.email;
|
||||
};
|
||||
|
||||
export const getConfirmDataFromInvitation = (
|
||||
encodeString: Nullable<string>,
|
||||
) => {
|
||||
if (!encodeString) return "";
|
||||
|
||||
const queryParams = getInvitationLinkData(encodeString);
|
||||
|
||||
if (!queryParams || !queryParams.linkData) return {};
|
||||
|
||||
return queryParams.linkData;
|
||||
};
|
||||
|
@ -310,6 +310,12 @@ const SubMenu = (props: {
|
||||
onItemClick(e, item);
|
||||
};
|
||||
|
||||
const onMouseDown = (e: React.MouseEvent) => {
|
||||
if (e.button !== 1) return;
|
||||
|
||||
onClick(e);
|
||||
};
|
||||
|
||||
let content = (
|
||||
<a
|
||||
href={item.url || "#"}
|
||||
@ -355,6 +361,7 @@ const SubMenu = (props: {
|
||||
className={className || ""}
|
||||
style={{ ...item.style, ...style }}
|
||||
onClick={onClick}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseEnter={(e) => onItemMouseEnter(e, item)}
|
||||
>
|
||||
{content}
|
||||
@ -376,6 +383,7 @@ const SubMenu = (props: {
|
||||
className={className || ""}
|
||||
style={{ ...item.style, ...style }}
|
||||
onClick={onClick}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseEnter={(e) => onItemMouseEnter(e, item)}
|
||||
>
|
||||
{content}
|
||||
|
@ -2581,6 +2581,7 @@ __metadata:
|
||||
"@codemirror/lang-javascript": "npm:^6.2.2"
|
||||
"@svgr/webpack": "npm:^5.5.0"
|
||||
"@types/eslint": "npm:^8.44.7"
|
||||
"@types/he": "npm:^1.2.3"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^6.12.0"
|
||||
"@typescript-eslint/parser": "npm:^6.12.0"
|
||||
"@uiw/codemirror-theme-github": "npm:^4.21.25"
|
||||
@ -7756,6 +7757,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/he@npm:^1.2.3":
|
||||
version: 1.2.3
|
||||
resolution: "@types/he@npm:1.2.3"
|
||||
checksum: 10/e77851c73dd7b9902d92fe0118a26246a7f3676a3a1c6eb1408305187ef73b57c22550b1435946b983267f961d935554d5d0e1b458416932552f31e763e1aa41
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/hoist-non-react-statics@npm:*":
|
||||
version: 3.3.5
|
||||
resolution: "@types/hoist-non-react-statics@npm:3.3.5"
|
||||
|
Loading…
Reference in New Issue
Block a user