base
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// plane imports
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useTheme } from "next-themes";
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import type { TOAuthConfigs, TOAuthOption } from "@plane/types";
|
||||
// assets
|
||||
import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
|
||||
import githubLightLogo from "@/app/assets/logos/github-black.png?url";
|
||||
import githubDarkLogo from "@/app/assets/logos/github-dark.svg?url";
|
||||
import gitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
|
||||
import googleLogo from "@/app/assets/logos/google-logo.svg?url";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store/use-instance";
|
||||
|
||||
export const useCoreOAuthConfig = (oauthActionText: string): TOAuthConfigs => {
|
||||
//router
|
||||
const searchParams = useSearchParams();
|
||||
// query params
|
||||
const next_path = searchParams.get("next_path");
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { config } = useInstance();
|
||||
// derived values
|
||||
const isOAuthEnabled =
|
||||
(config &&
|
||||
(config?.is_google_enabled ||
|
||||
config?.is_github_enabled ||
|
||||
config?.is_gitlab_enabled ||
|
||||
config?.is_gitea_enabled)) ||
|
||||
false;
|
||||
const oAuthOptions: TOAuthOption[] = [
|
||||
{
|
||||
id: "google",
|
||||
text: `${oauthActionText} with Google`,
|
||||
icon: <img src={googleLogo} height={18} width={18} alt="Google Logo" />,
|
||||
onClick: () => {
|
||||
window.location.assign(`${API_BASE_URL}/auth/google/${next_path ? `?next_path=${next_path}` : ``}`);
|
||||
},
|
||||
enabled: config?.is_google_enabled,
|
||||
},
|
||||
{
|
||||
id: "github",
|
||||
text: `${oauthActionText} with GitHub`,
|
||||
icon: (
|
||||
<img
|
||||
src={resolvedTheme === "dark" ? githubLightLogo : githubDarkLogo}
|
||||
height={18}
|
||||
width={18}
|
||||
alt="GitHub Logo"
|
||||
/>
|
||||
),
|
||||
onClick: () => {
|
||||
window.location.assign(`${API_BASE_URL}/auth/github/${next_path ? `?next_path=${next_path}` : ``}`);
|
||||
},
|
||||
enabled: config?.is_github_enabled,
|
||||
},
|
||||
{
|
||||
id: "gitlab",
|
||||
text: `${oauthActionText} with GitLab`,
|
||||
icon: <img src={gitlabLogo} height={18} width={18} alt="GitLab Logo" />,
|
||||
onClick: () => {
|
||||
window.location.assign(`${API_BASE_URL}/auth/gitlab/${next_path ? `?next_path=${next_path}` : ``}`);
|
||||
},
|
||||
enabled: config?.is_gitlab_enabled,
|
||||
},
|
||||
{
|
||||
id: "gitea",
|
||||
text: `${oauthActionText} with Gitea`,
|
||||
icon: <img src={giteaLogo} height={18} width={18} alt="Gitea Logo" />,
|
||||
onClick: () => {
|
||||
window.location.assign(`${API_BASE_URL}/auth/gitea/${next_path ? `?next_path=${next_path}` : ``}`);
|
||||
},
|
||||
enabled: config?.is_gitea_enabled,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
isOAuthEnabled,
|
||||
oAuthOptions,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// plane imports
|
||||
import type { TOAuthConfigs } from "@plane/types";
|
||||
|
||||
export const useExtendedOAuthConfig = (_oauthActionText: string): TOAuthConfigs => ({
|
||||
isOAuthEnabled: false,
|
||||
oAuthOptions: [],
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// plane imports
|
||||
import type { TOAuthConfigs } from "@plane/types";
|
||||
// local imports
|
||||
import { useCoreOAuthConfig } from "./core";
|
||||
import { useExtendedOAuthConfig } from "./extended";
|
||||
|
||||
export const useOAuthConfig = (oauthActionText: string = "Continue"): TOAuthConfigs => {
|
||||
const coreOAuthConfig = useCoreOAuthConfig(oauthActionText);
|
||||
const extendedOAuthConfig = useExtendedOAuthConfig(oauthActionText);
|
||||
return {
|
||||
isOAuthEnabled: coreOAuthConfig.isOAuthEnabled || extendedOAuthConfig.isOAuthEnabled,
|
||||
oAuthOptions: [...coreOAuthConfig.oAuthOptions, ...extendedOAuthConfig.oAuthOptions],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export * from "./use-publish-list";
|
||||
export * from "./use-publish";
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IPublishListStore } from "@/store/publish/publish_list.store";
|
||||
|
||||
export const usePublishList = (): IPublishListStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("usePublishList must be used within StoreProvider");
|
||||
return context.publishList;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { PublishStore } from "@/store/publish/publish.store";
|
||||
|
||||
export const usePublish = (anchor: string): PublishStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("usePublish must be used within StoreProvider");
|
||||
return context.publishList.publishMap?.[anchor] ?? {};
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { ICycleStore } from "@/store/cycle.store";
|
||||
|
||||
export const useCycle = (): ICycleStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useCycle must be used within StoreProvider");
|
||||
return context.cycle;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IInstanceStore } from "@/store/instance.store";
|
||||
|
||||
export const useInstance = (): IInstanceStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
|
||||
return context.instance;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IIssueDetailStore } from "@/store/issue-detail.store";
|
||||
|
||||
export const useIssueDetails = (): IIssueDetailStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
|
||||
return context.issueDetail;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IIssueFilterStore } from "@/store/issue-filters.store";
|
||||
|
||||
export const useIssueFilter = (): IIssueFilterStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
|
||||
return context.issueFilter;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IIssueStore } from "@/store/issue.store";
|
||||
|
||||
export const useIssue = (): IIssueStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useIssue must be used within StoreProvider");
|
||||
return context.issue;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IIssueLabelStore } from "@/store/label.store";
|
||||
|
||||
export const useLabel = (): IIssueLabelStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useLabel must be used within StoreProvider");
|
||||
return context.label;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IIssueMemberStore } from "@/store/members.store";
|
||||
|
||||
export const useMember = (): IIssueMemberStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useMember must be used within StoreProvider");
|
||||
return context.member;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IIssueModuleStore } from "@/store/module.store";
|
||||
|
||||
export const useModule = (): IIssueModuleStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useModule must be used within StoreProvider");
|
||||
return context.module;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IStateStore } from "@/store/state.store";
|
||||
|
||||
export const useStates = (): IStateStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useState must be used within StoreProvider");
|
||||
return context.state;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IProfileStore } from "@/store/profile.store";
|
||||
|
||||
export const useUserProfile = (): IProfileStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
|
||||
return context.user.profile;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// lib
|
||||
import { StoreContext } from "@/lib/store-provider";
|
||||
// store
|
||||
import type { IUserStore } from "@/store/user.store";
|
||||
|
||||
export const useUser = (): IUserStore => {
|
||||
const context = useContext(StoreContext);
|
||||
if (context === undefined) throw new Error("useUser must be used within StoreProvider");
|
||||
return context.user;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useClipboardWritePermission = () => {
|
||||
const [isClipboardWriteAllowed, setClipboardWriteAllowed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkClipboardWriteAccess = () => {
|
||||
navigator.permissions
|
||||
|
||||
.query({ name: "clipboard-write" as PermissionName })
|
||||
.then((result) => {
|
||||
if (result.state === "granted") {
|
||||
setClipboardWriteAllowed(true);
|
||||
} else {
|
||||
setClipboardWriteAllowed(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setClipboardWriteAllowed(false);
|
||||
});
|
||||
};
|
||||
|
||||
checkClipboardWriteAccess();
|
||||
}, []);
|
||||
|
||||
return isClipboardWriteAllowed;
|
||||
};
|
||||
|
||||
export default useClipboardWritePermission;
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// editor
|
||||
import type { TExtensions } from "@plane/editor";
|
||||
|
||||
export type TEditorFlaggingHookReturnType = {
|
||||
document: {
|
||||
disabled: TExtensions[];
|
||||
flagged: TExtensions[];
|
||||
};
|
||||
liteText: {
|
||||
disabled: TExtensions[];
|
||||
flagged: TExtensions[];
|
||||
};
|
||||
richText: {
|
||||
disabled: TExtensions[];
|
||||
flagged: TExtensions[];
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @description extensions disabled in various editors
|
||||
*/
|
||||
export const useEditorFlagging = (_anchor: string): TEditorFlaggingHookReturnType => ({
|
||||
document: {
|
||||
disabled: [],
|
||||
flagged: [],
|
||||
},
|
||||
liteText: {
|
||||
disabled: [],
|
||||
flagged: [],
|
||||
},
|
||||
richText: {
|
||||
disabled: [],
|
||||
flagged: [],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { RefObject } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export type UseIntersectionObserverProps = {
|
||||
containerRef: RefObject<HTMLDivElement | null> | undefined;
|
||||
elementRef: HTMLElement | null;
|
||||
callback: () => void;
|
||||
rootMargin?: string;
|
||||
};
|
||||
|
||||
export const useIntersectionObserver = (
|
||||
containerRef: RefObject<HTMLDivElement | null>,
|
||||
elementRef: HTMLElement | null,
|
||||
callback: (() => void) | undefined,
|
||||
rootMargin?: string
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (elementRef) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[entries.length - 1].isIntersecting) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
root: containerRef?.current,
|
||||
rootMargin,
|
||||
}
|
||||
);
|
||||
observer.observe(elementRef);
|
||||
return () => {
|
||||
if (elementRef) {
|
||||
observer.unobserve(elementRef);
|
||||
}
|
||||
};
|
||||
}
|
||||
// When i am passing callback as a dependency, it is causing infinite loop,
|
||||
// Please make sure you fix this eslint lint disable error with caution
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rootMargin, callback, elementRef, containerRef.current]);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useIsInIframe = () => {
|
||||
const [isInIframe, setIsInIframe] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkIfInIframe = () => {
|
||||
setIsInIframe(window.self !== window.top);
|
||||
};
|
||||
|
||||
checkIfInIframe();
|
||||
}, []);
|
||||
|
||||
return isInIframe;
|
||||
};
|
||||
|
||||
export default useIsInIframe;
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useRef, useEffect } from "react";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { UserService } from "@plane/services";
|
||||
import type { IUser } from "@plane/types";
|
||||
|
||||
export const useMention = () => {
|
||||
const userService = new UserService();
|
||||
const { data: user, isLoading: userDataLoading } = useSWR("currentUser", async () => userService.me());
|
||||
|
||||
const userRef = useRef<IUser | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (userRef) {
|
||||
userRef.current = user;
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const waitForUserDate = async () =>
|
||||
new Promise<IUser>((resolve) => {
|
||||
const checkData = () => {
|
||||
if (userRef.current) {
|
||||
resolve(userRef.current);
|
||||
} else {
|
||||
setTimeout(checkData, 100);
|
||||
}
|
||||
};
|
||||
checkData();
|
||||
});
|
||||
|
||||
const mentionHighlights = async () => {
|
||||
if (!userDataLoading && userRef.current) {
|
||||
return [userRef.current.id];
|
||||
} else {
|
||||
const user = await waitForUserDate();
|
||||
return [user.id];
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
mentionHighlights,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useCallback } from "react";
|
||||
// helpers
|
||||
import type { TCustomComponentsMetaData } from "@plane/utils";
|
||||
// helpers
|
||||
import { getEditorAssetSrc } from "@/helpers/editor.helper";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
|
||||
type TArgs = {
|
||||
anchor: string;
|
||||
};
|
||||
|
||||
export const useParseEditorContent = (args: TArgs) => {
|
||||
const { anchor } = args;
|
||||
// store hooks
|
||||
const { getMemberById } = useMember();
|
||||
|
||||
const getEditorMetaData = useCallback(
|
||||
(htmlContent: string): TCustomComponentsMetaData => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlContent, "text/html");
|
||||
const imageMetaData: TCustomComponentsMetaData["file_assets"] = [];
|
||||
// process image components
|
||||
const imageComponents = doc.querySelectorAll("image-component");
|
||||
imageComponents.forEach((element) => {
|
||||
const src = element.getAttribute("src");
|
||||
if (src) {
|
||||
const assetSrc = src.startsWith("http") ? src : getEditorAssetSrc(anchor, src);
|
||||
if (assetSrc) {
|
||||
imageMetaData.push({
|
||||
id: src,
|
||||
name: src,
|
||||
url: assetSrc,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// process user mentions
|
||||
const userMentions: TCustomComponentsMetaData["user_mentions"] = [];
|
||||
const mentionComponents = doc.querySelectorAll("mention-component");
|
||||
mentionComponents.forEach((element) => {
|
||||
const id = element.getAttribute("entity_identifier");
|
||||
if (id) {
|
||||
const userDetails = getMemberById(id);
|
||||
const originUrl = typeof window !== "undefined" && (window.location.origin ?? "");
|
||||
const path = `profile/${id}`;
|
||||
const url = `${originUrl}/${path}`;
|
||||
if (userDetails) {
|
||||
userMentions.push({
|
||||
id,
|
||||
display_name: userDetails.member__display_name,
|
||||
url,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
file_assets: imageMetaData,
|
||||
user_mentions: userMentions,
|
||||
};
|
||||
},
|
||||
[anchor, getMemberById]
|
||||
);
|
||||
|
||||
return {
|
||||
getEditorMetaData,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const TIMER = 30;
|
||||
|
||||
const useTimer = (initialValue: number = TIMER) => {
|
||||
const [timer, setTimer] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTimer((prev) => prev - 1);
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return { timer, setTimer };
|
||||
};
|
||||
|
||||
export default useTimer;
|
||||
Reference in New Issue
Block a user