forked from github/plane
d511799f31
* feat: created custom mention component * feat: added mention suggestions and suggestion highlights * feat: created mention suggestion list for displaying mention suggestions * feat: created custom mention text component, for handling click event * feat: exposed mention component * feat: integrated and exposed `mentions` componenet with `editor-core` * feat: integrated mentions extension with the core editor package * feat: exposed suggestion types from mentions * feat: added `mention-suggestion` parameters in `r-t-e` and `l-t-e` * feat: added `IssueMention` model in apiserver models * chore: updated activities background job and added bs4 in requirements * feat: added mention removal logic in issue_activity * chore: exposed mention types from `r-t-e` and `l-t-e` * feat: integrated mentions in side peek view description form * feat: added mentions in issue modal form * feat: created custom react-hook for editor suggestions * feat: integrated mention suggestions block in RichTextEditor * feat: added `mentions` integration in `lite-text-editor` instances * fix: tailwind loading nodemodules from packages * feat: added styles for the mention suggestion list * fix: update module import to resolve build failure * feat: added mentions as an issue filter * feat: added UI Changes to Implement `mention` filters * feat: added `mentions` as a filter option in the header * feat: added mentions in the filter list options * feat: added mentions in default display filter options * feat: added filters in applied and issue params in store * feat: modified types for adding mentions as a filter option * feat: modified `notification-card` to display message when it exists in object * feat: rewrote user mention management upon the changes made in develop * chore: merged debounce PR with the current PR for tracing changes * fix: mentions_filters updated with the new setup * feat: updated requirements for bs4 * feat: modified `mentions-filter` to remove many to many dependency * feat: implemented list manipulation instead of for loop * feat: added readonly functionality in `read-only` editor core * feat: added UI Changes for read-only mode * feat: added mentions store in web Root Store * chore: renamed `use-editor-suggestions` hook * feat: UI Improvements for conditional highlights w.r.t readonly in mentionNode * fix: removed mentions from `filter_set` parameters * fix: minor merge fixes * fix: package lock updates --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
504 lines
13 KiB
TypeScript
504 lines
13 KiB
TypeScript
import { createContext, useCallback, useEffect, useReducer } from "react";
|
|
import { useRouter } from "next/router";
|
|
import useSWR, { mutate } from "swr";
|
|
// components
|
|
import ToastAlert from "components/toast-alert";
|
|
// services
|
|
import { ProjectService } from "services/project";
|
|
import { CycleService } from "services/cycle.service";
|
|
import { ModuleService } from "services/module.service";
|
|
import { ViewService } from "services/view.service";
|
|
// hooks
|
|
import useUserAuth from "hooks/use-user-auth";
|
|
// types
|
|
import { IIssueFilterOptions, IProjectMember, IUser, IIssueDisplayFilterOptions, IProjectViewProps } from "types";
|
|
// fetch-keys
|
|
import { CYCLE_DETAILS, MODULE_DETAILS, USER_PROJECT_VIEW, VIEW_DETAILS } from "constants/fetch-keys";
|
|
|
|
const projectService = new ProjectService();
|
|
const cycleService = new CycleService();
|
|
const moduleService = new ModuleService();
|
|
const viewService = new ViewService();
|
|
|
|
export const issueViewContext = createContext<ContextType>({} as ContextType);
|
|
|
|
type ReducerActionType = {
|
|
type: "REHYDRATE_THEME" | "SET_DISPLAY_FILTERS" | "SET_FILTERS" | "RESET_TO_DEFAULT";
|
|
payload?: Partial<IProjectViewProps>;
|
|
};
|
|
|
|
type ContextType = IProjectViewProps & {
|
|
setDisplayFilters: (displayFilter: Partial<IIssueDisplayFilterOptions>) => void;
|
|
setFilters: (filters: Partial<IIssueFilterOptions>, saveToServer?: boolean) => void;
|
|
resetFilterToDefault: () => void;
|
|
setNewFilterDefaultView: () => void;
|
|
};
|
|
|
|
type StateType = IProjectViewProps;
|
|
type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType;
|
|
|
|
export const initialState: StateType = {
|
|
display_filters: {
|
|
group_by: null,
|
|
layout: "list",
|
|
order_by: "-created_at",
|
|
show_empty_groups: true,
|
|
sub_issue: true,
|
|
},
|
|
filters: {
|
|
priority: null,
|
|
assignees: null,
|
|
labels: null,
|
|
state: null,
|
|
state_group: null,
|
|
mentions: null,
|
|
subscriber: null,
|
|
created_by: null,
|
|
start_date: null,
|
|
target_date: null,
|
|
},
|
|
};
|
|
|
|
export const reducer: ReducerFunctionType = (state, action) => {
|
|
const { type, payload } = action;
|
|
|
|
switch (type) {
|
|
case "REHYDRATE_THEME": {
|
|
let collapsed: any = localStorage.getItem("collapsed");
|
|
collapsed = collapsed ? JSON.parse(collapsed) : false;
|
|
|
|
return { ...initialState, ...payload, collapsed };
|
|
}
|
|
|
|
case "SET_DISPLAY_FILTERS": {
|
|
const newState = {
|
|
...state,
|
|
display_filters: {
|
|
...state.display_filters,
|
|
...payload,
|
|
},
|
|
issueView: payload?.display_filters?.layout || "list",
|
|
};
|
|
|
|
return {
|
|
...state,
|
|
...newState,
|
|
};
|
|
}
|
|
|
|
case "SET_FILTERS": {
|
|
const newState = {
|
|
...state,
|
|
filters: {
|
|
...state.filters,
|
|
...payload?.filters,
|
|
},
|
|
};
|
|
|
|
return {
|
|
...state,
|
|
...newState,
|
|
};
|
|
}
|
|
|
|
case "RESET_TO_DEFAULT": {
|
|
return {
|
|
...initialState,
|
|
...payload,
|
|
};
|
|
}
|
|
|
|
default: {
|
|
return state;
|
|
}
|
|
}
|
|
};
|
|
|
|
const saveDataToServer = async (workspaceSlug: string, projectId: string, state: IProjectViewProps) => {
|
|
mutate<IProjectMember>(
|
|
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null,
|
|
(prevData) => {
|
|
if (!prevData) return prevData;
|
|
|
|
return {
|
|
...prevData,
|
|
view_props: state,
|
|
};
|
|
},
|
|
false
|
|
);
|
|
|
|
await projectService.setProjectView(workspaceSlug, projectId, {
|
|
view_props: state,
|
|
});
|
|
};
|
|
|
|
const saveCycleFilters = async (
|
|
workspaceSlug: string,
|
|
projectId: string,
|
|
cycleId: string,
|
|
state: any,
|
|
user: IUser | undefined
|
|
) => {
|
|
await cycleService.patchCycle(
|
|
workspaceSlug,
|
|
projectId,
|
|
cycleId,
|
|
{
|
|
...state,
|
|
},
|
|
user
|
|
);
|
|
};
|
|
|
|
const saveModuleFilters = async (
|
|
workspaceSlug: string,
|
|
projectId: string,
|
|
moduleId: string,
|
|
state: any,
|
|
user: IUser | undefined
|
|
) => {
|
|
await moduleService.patchModule(
|
|
workspaceSlug,
|
|
projectId,
|
|
moduleId,
|
|
{
|
|
...state,
|
|
},
|
|
user
|
|
);
|
|
};
|
|
|
|
const saveViewFilters = async (
|
|
workspaceSlug: string,
|
|
projectId: string,
|
|
viewId: string,
|
|
state: any,
|
|
user: IUser | undefined
|
|
) => {
|
|
await viewService.patchView(
|
|
workspaceSlug,
|
|
projectId,
|
|
viewId,
|
|
{
|
|
...state,
|
|
},
|
|
user
|
|
);
|
|
};
|
|
|
|
const setNewDefault = async (workspaceSlug: string, projectId: string, state: any) => {
|
|
mutate<IProjectMember>(
|
|
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null,
|
|
(prevData) => {
|
|
if (!prevData) return prevData;
|
|
|
|
return {
|
|
...prevData,
|
|
view_props: state,
|
|
};
|
|
},
|
|
false
|
|
);
|
|
|
|
await projectService.setProjectView(workspaceSlug, projectId, {
|
|
view_props: state,
|
|
default_props: state,
|
|
});
|
|
};
|
|
|
|
export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
|
|
const router = useRouter();
|
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
|
|
|
const { user } = useUserAuth();
|
|
|
|
const { data: myViewProps, mutate: mutateMyViewProps } = useSWR(
|
|
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null,
|
|
workspaceSlug && projectId
|
|
? () => projectService.projectMemberMe(workspaceSlug as string, projectId as string)
|
|
: null
|
|
);
|
|
|
|
const { data: viewDetails, mutate: mutateViewDetails } = useSWR(
|
|
workspaceSlug && projectId && viewId ? VIEW_DETAILS(viewId as string) : null,
|
|
workspaceSlug && projectId && viewId
|
|
? () => viewService.getViewDetails(workspaceSlug as string, projectId as string, viewId as string)
|
|
: null
|
|
);
|
|
|
|
const { data: cycleDetails, mutate: mutateCycleDetails } = useSWR(
|
|
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId as string) : null,
|
|
workspaceSlug && projectId && cycleId
|
|
? () => cycleService.getCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString())
|
|
: null
|
|
);
|
|
|
|
const { data: moduleDetails, mutate: mutateModuleDetails } = useSWR(
|
|
workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null,
|
|
workspaceSlug && projectId && moduleId
|
|
? () => moduleService.getModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
|
|
: null
|
|
);
|
|
|
|
const setDisplayFilters = useCallback(
|
|
(displayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
|
dispatch({
|
|
type: "SET_DISPLAY_FILTERS",
|
|
payload: {
|
|
display_filters: {
|
|
...state.display_filters,
|
|
...displayFilter,
|
|
},
|
|
},
|
|
});
|
|
|
|
const additionalProperties: Partial<IIssueDisplayFilterOptions> = {
|
|
group_by: displayFilter.group_by ?? state.display_filters?.group_by,
|
|
order_by: displayFilter.order_by ?? state.display_filters?.order_by,
|
|
};
|
|
|
|
if (displayFilter.layout && displayFilter.layout === "kanban" && state.display_filters?.group_by === null) {
|
|
additionalProperties.group_by = "state";
|
|
dispatch({
|
|
type: "SET_DISPLAY_FILTERS",
|
|
payload: {
|
|
display_filters: {
|
|
group_by: "state",
|
|
},
|
|
},
|
|
});
|
|
}
|
|
if (displayFilter.layout && displayFilter.layout === "calendar") {
|
|
additionalProperties.group_by = null;
|
|
dispatch({
|
|
type: "SET_DISPLAY_FILTERS",
|
|
payload: {
|
|
display_filters: {
|
|
group_by: null,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
if (displayFilter.layout && displayFilter.layout === "gantt_chart") {
|
|
additionalProperties.order_by = "sort_order";
|
|
dispatch({
|
|
type: "SET_DISPLAY_FILTERS",
|
|
payload: {
|
|
display_filters: {
|
|
order_by: "sort_order",
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
if (!workspaceSlug || !projectId) return;
|
|
|
|
saveDataToServer(workspaceSlug as string, projectId as string, {
|
|
...state,
|
|
display_filters: {
|
|
...state.display_filters,
|
|
...displayFilter,
|
|
...additionalProperties,
|
|
},
|
|
});
|
|
},
|
|
[workspaceSlug, projectId, state]
|
|
);
|
|
|
|
const setFilters = useCallback(
|
|
(property: Partial<IIssueFilterOptions>, saveToServer = true) => {
|
|
Object.keys(property).forEach((key) => {
|
|
if (property[key as keyof typeof property]?.length === 0) property[key as keyof typeof property] = null;
|
|
});
|
|
|
|
dispatch({
|
|
type: "SET_FILTERS",
|
|
payload: {
|
|
filters: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!workspaceSlug || !projectId) return;
|
|
|
|
if (cycleId) {
|
|
mutateCycleDetails((prevData: any) => {
|
|
if (!prevData) return prevData;
|
|
|
|
return {
|
|
...prevData,
|
|
view_props: {
|
|
filters: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
},
|
|
};
|
|
}, false);
|
|
|
|
saveCycleFilters(
|
|
workspaceSlug.toString(),
|
|
projectId.toString(),
|
|
cycleId.toString(),
|
|
{
|
|
view_props: {
|
|
filters: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
},
|
|
},
|
|
user
|
|
);
|
|
} else if (moduleId) {
|
|
mutateModuleDetails((prevData: any) => {
|
|
if (!prevData) return prevData;
|
|
|
|
return {
|
|
...prevData,
|
|
view_props: {
|
|
filters: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
},
|
|
};
|
|
}, false);
|
|
|
|
saveModuleFilters(
|
|
workspaceSlug.toString(),
|
|
projectId.toString(),
|
|
moduleId.toString(),
|
|
{
|
|
view_props: {
|
|
filters: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
},
|
|
},
|
|
user
|
|
);
|
|
} else if (viewId) {
|
|
mutateViewDetails((prevData: any) => {
|
|
if (!prevData) return prevData;
|
|
return {
|
|
...prevData,
|
|
query_data: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
};
|
|
}, false);
|
|
if (saveToServer)
|
|
saveViewFilters(
|
|
workspaceSlug as string,
|
|
projectId as string,
|
|
viewId as string,
|
|
{
|
|
query_data: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
},
|
|
user
|
|
);
|
|
} else {
|
|
mutateMyViewProps((prevData: any) => {
|
|
if (!prevData) return prevData;
|
|
|
|
return {
|
|
...prevData,
|
|
view_props: {
|
|
...state,
|
|
filters: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
},
|
|
};
|
|
}, false);
|
|
|
|
saveDataToServer(workspaceSlug as string, projectId as string, {
|
|
...state,
|
|
filters: {
|
|
...state.filters,
|
|
...property,
|
|
},
|
|
});
|
|
}
|
|
},
|
|
[
|
|
projectId,
|
|
workspaceSlug,
|
|
state,
|
|
mutateMyViewProps,
|
|
cycleId,
|
|
mutateCycleDetails,
|
|
moduleId,
|
|
mutateModuleDetails,
|
|
viewId,
|
|
mutateViewDetails,
|
|
user,
|
|
]
|
|
);
|
|
|
|
const setNewDefaultView = useCallback(() => {
|
|
if (!workspaceSlug || !projectId) return;
|
|
|
|
setNewDefault(workspaceSlug as string, projectId as string, state).then(() => {
|
|
mutateMyViewProps();
|
|
});
|
|
}, [projectId, workspaceSlug, state, mutateMyViewProps]);
|
|
|
|
const resetToDefault = useCallback(() => {
|
|
dispatch({
|
|
type: "RESET_TO_DEFAULT",
|
|
payload: myViewProps?.default_props,
|
|
});
|
|
|
|
if (!workspaceSlug || !projectId || !myViewProps) return;
|
|
|
|
saveDataToServer(workspaceSlug as string, projectId as string, myViewProps.default_props);
|
|
}, [projectId, workspaceSlug, myViewProps]);
|
|
|
|
useEffect(() => {
|
|
dispatch({
|
|
type: "REHYDRATE_THEME",
|
|
payload: {
|
|
...myViewProps?.view_props,
|
|
filters: {
|
|
...(cycleId
|
|
? cycleDetails?.view_props.filters
|
|
: moduleId
|
|
? moduleDetails?.view_props.filters
|
|
: viewId
|
|
? viewDetails?.query_data
|
|
: myViewProps?.view_props?.filters),
|
|
} as any,
|
|
},
|
|
});
|
|
}, [myViewProps, cycleId, cycleDetails, moduleId, moduleDetails, viewId, viewDetails]);
|
|
|
|
return (
|
|
<issueViewContext.Provider
|
|
value={{
|
|
display_filters: state.display_filters,
|
|
setDisplayFilters,
|
|
filters: state.filters,
|
|
setFilters,
|
|
resetFilterToDefault: resetToDefault,
|
|
setNewFilterDefaultView: setNewDefaultView,
|
|
}}
|
|
>
|
|
<ToastAlert />
|
|
{children}
|
|
</issueViewContext.Provider>
|
|
);
|
|
};
|