fix: sticky build (#2219)

This commit is contained in:
Akshita Goyal
2025-01-16 21:33:48 +05:30
committed by GitHub
parent c37617fcd1
commit 5dea83e73b
8 changed files with 0 additions and 445 deletions

View File

@@ -1,2 +1 @@
export * from "./sticky-editor";
export * from "ce/components/editor";

View File

@@ -1,36 +0,0 @@
import { TSticky } from "@plane/types";
export const STICKY_COLORS = [
"#D4DEF7", // light periwinkle
"#B4E4FF", // light blue
"#FFF2B4", // light yellow
"#E3E3E3", // light gray
"#FFE2DD", // light pink
"#F5D1A5", // light orange
"#D1F7C4", // light green
"#E5D4FF", // light purple
];
type TProps = {
handleUpdate: (data: Partial<TSticky>) => Promise<void>;
};
export const ColorPalette = (props: TProps) => {
const { handleUpdate } = props;
return (
<div className="absolute z-10 bottom-5 left-0 w-56 shadow p-2 rounded-md bg-custom-background-100 mb-2">
<div className="text-sm font-semibold text-custom-text-400 mb-2">Background colors</div>
<div className="flex flex-wrap gap-2">
{STICKY_COLORS.map((color, index) => (
<button
key={index}
type="button"
onClick={() => handleUpdate({ color })}
className="h-6 w-6 rounded-md hover:ring-2 hover:ring-custom-primary focus:outline-none focus:ring-2 focus:ring-custom-primary transition-all"
style={{ backgroundColor: color }}
/>
))}
</div>
</div>
);
};

View File

@@ -1,109 +0,0 @@
import React, { useState } from "react";
// plane constants
import { EIssueCommentAccessSpecifier } from "@plane/constants";
// plane editor
import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor";
// components
import { TSticky } from "@plane/types";
// helpers
import { cn } from "@/helpers/common.helper";
import { getEditorFileHandlers } from "@/helpers/editor.helper";
// hooks
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
import { useFileSize } from "@/plane-web/hooks/use-file-size";
import { Toolbar } from "./toolbar";
interface StickyEditorWrapperProps
extends Omit<ILiteTextEditor, "disabledExtensions" | "fileHandler" | "mentionHandler"> {
workspaceSlug: string;
workspaceId: string;
projectId?: string;
accessSpecifier?: EIssueCommentAccessSpecifier;
handleAccessChange?: (accessKey: EIssueCommentAccessSpecifier) => void;
showAccessSpecifier?: boolean;
showSubmitButton?: boolean;
isSubmitting?: boolean;
showToolbarInitially?: boolean;
showToolbar?: boolean;
uploadFile: (file: File) => Promise<string>;
parentClassName?: string;
handleColorChange: (data: Partial<TSticky>) => Promise<void>;
handleDelete: () => Promise<void>;
}
export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperProps>((props, ref) => {
const {
containerClassName,
workspaceSlug,
workspaceId,
projectId,
handleDelete,
handleColorChange,
showToolbarInitially = true,
showToolbar = true,
parentClassName = "",
placeholder = "Add comment...",
uploadFile,
...rest
} = props;
// states
const [isFocused, setIsFocused] = useState(showToolbarInitially);
// editor flaggings
const { liteTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString());
// file size
const { maxFileSize } = useFileSize();
function isMutableRefObject<T>(ref: React.ForwardedRef<T>): ref is React.MutableRefObject<T | null> {
return !!ref && typeof ref === "object" && "current" in ref;
}
// derived values
const editorRef = isMutableRefObject<EditorRefApi>(ref) ? ref.current : null;
return (
<div
className={cn("relative border border-custom-border-200 rounded p-3", parentClassName)}
onFocus={() => !showToolbarInitially && setIsFocused(true)}
onBlur={() => !showToolbarInitially && setIsFocused(false)}
>
<LiteTextEditorWithRef
ref={ref}
disabledExtensions={[...disabledExtensions, "enter-key"]}
fileHandler={getEditorFileHandlers({
maxFileSize,
projectId,
uploadFile,
workspaceId,
workspaceSlug,
})}
mentionHandler={{
renderComponent: () => <></>,
}}
placeholder={placeholder}
containerClassName={cn(containerClassName, "relative")}
{...rest}
/>
<div
className={cn(
"transition-all duration-300 ease-out origin-top",
isFocused ? "max-h-[200px] opacity-100 scale-y-100 mt-3" : "max-h-0 opacity-0 scale-y-0 invisible"
)}
>
<Toolbar
executeCommand={(item) => {
// TODO: update this while toolbar homogenization
// @ts-expect-error type mismatch here
editorRef?.executeMenuItemCommand({
itemKey: item.itemKey,
...item.extraProps,
});
}}
handleDelete={handleDelete}
handleColorChange={handleColorChange}
editorRef={editorRef}
/>
</div>
</div>
);
});
StickyEditor.displayName = "StickyEditor";

View File

@@ -1,2 +0,0 @@
export * from "./editor";
export * from "./toolbar";

View File

@@ -1,115 +0,0 @@
"use client";
import React, { useEffect, useState, useCallback } from "react";
import { Palette, Trash2 } from "lucide-react";
// editor
import { EditorRefApi } from "@plane/editor";
// ui
import { useOutsideClickDetector } from "@plane/hooks";
import { TSticky } from "@plane/types";
import { Tooltip } from "@plane/ui";
// constants
import { TOOLBAR_ITEMS, ToolbarMenuItem } from "@/constants/editor";
// helpers
import { cn } from "@/helpers/common.helper";
import { ColorPalette } from "./color-pallete";
type Props = {
executeCommand: (item: ToolbarMenuItem) => void;
editorRef: EditorRefApi | null;
handleColorChange: (data: Partial<TSticky>) => Promise<void>;
handleDelete: () => void;
};
const toolbarItems = TOOLBAR_ITEMS.sticky;
export const Toolbar: React.FC<Props> = (props) => {
const { executeCommand, editorRef, handleColorChange, handleDelete } = props;
// State to manage active states of toolbar items
const [activeStates, setActiveStates] = useState<Record<string, boolean>>({});
const [showColorPalette, setShowColorPalette] = useState(false);
const colorPaletteRef = React.useRef<HTMLDivElement>(null);
// Function to update active states
const updateActiveStates = useCallback(() => {
if (!editorRef) return;
const newActiveStates: Record<string, boolean> = {};
Object.values(toolbarItems)
.flat()
.forEach((item) => {
// TODO: update this while toolbar homogenization
// @ts-expect-error type mismatch here
newActiveStates[item.renderKey] = editorRef.isMenuItemActive({
itemKey: item.itemKey,
...item.extraProps,
});
});
setActiveStates(newActiveStates);
}, [editorRef]);
// useEffect to call updateActiveStates when isActive prop changes
useEffect(() => {
if (!editorRef) return;
const unsubscribe = editorRef.onStateChange(updateActiveStates);
updateActiveStates();
return () => unsubscribe();
}, [editorRef, updateActiveStates]);
useOutsideClickDetector(colorPaletteRef, () => setShowColorPalette(false));
return (
<div className="flex w-full justify-between mt-2 h-full">
<div className="flex my-auto gap-4" ref={colorPaletteRef}>
{/* color palette */}
{showColorPalette && <ColorPalette handleUpdate={handleColorChange} />}
<button onClick={() => setShowColorPalette(!showColorPalette)} className="flex text-custom-text-300">
<Palette className="size-4 my-auto" />
</button>
<div className="flex w-fit items-stretch justify-between gap-4 rounded p-1 my-auto">
<div className="flex items-stretch my-auto gap-4">
{Object.keys(toolbarItems).map((key) => (
<div key={key} className={cn("flex items-stretch gap-4", {})}>
{toolbarItems[key].map((item) => {
const isItemActive = activeStates[item.renderKey];
return (
<Tooltip
key={item.renderKey}
tooltipContent={
<p className="flex flex-col gap-1 text-center text-xs">
<span className="font-medium">{item.name}</span>
{item.shortcut && <kbd className="text-custom-text-400">{item.shortcut.join(" + ")}</kbd>}
</p>
}
>
<button
type="button"
onClick={() => executeCommand(item)}
className={cn(
"grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-300",
{}
)}
>
<item.icon
className={cn("h-3.5 w-3.5", {
"font-extrabold": isItemActive,
})}
strokeWidth={2.5}
/>
</button>
</Tooltip>
);
})}
</div>
))}
</div>
</div>
</div>
{/* delete action */}
<button onClick={handleDelete} className="my-auto text-custom-text-300">
<Trash2 className="size-4" />
</button>
</div>
);
};

View File

@@ -1,11 +0,0 @@
import { useContext } from "react";
// context
import { StoreContext } from "@/lib/store-context";
import { IStickyStore } from "@/plane-web/store/sticky/sticky.store";
// plane web stores
export const useSticky = (): IStickyStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useSticky must be used within StoreProvider");
return context.stickyStore;
};

View File

@@ -67,7 +67,6 @@ import {
import { IPiChatStore, PiChatStore } from "./pi-chat/pi-chat";
// timeline
import { IProjectStore, ProjectStore } from "./projects/projects";
import { IStickyStore, StickyStore } from "./sticky/sticky.store";
import { ITimelineStore } from "./timeline";
export class RootStore extends CoreRootStore {
@@ -101,7 +100,6 @@ export class RootStore extends CoreRootStore {
gitlabIntegration: IGitlabStore;
initiativeFilterStore: IInitiativeFilterStore;
initiativeStore: IInitiativeStore;
stickyStore: IStickyStore;
constructor() {
super();
@@ -123,7 +121,6 @@ export class RootStore extends CoreRootStore {
this.projectDetails = new ProjectStore(this);
this.teamRoot = new TeamRootStore(this);
this.workspaceNotification = new WorkspaceNotificationStore(this);
this.stickyStore = new StickyStore();
// importers
this.jiraImporter = new JiraStore(this);
this.jiraServerImporter = new JiraServerStore(this);
@@ -157,7 +154,6 @@ export class RootStore extends CoreRootStore {
this.timelineStore = new TimeLineStore(this);
this.projectDetails = new ProjectStore(this);
this.teamRoot = new TeamRootStore(this);
this.stickyStore = new StickyStore();
// importers
this.jiraImporter = new JiraStore(this);
this.jiraServerImporter = new JiraServerStore(this);

View File

@@ -1,167 +0,0 @@
import { observable, action, makeObservable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
import { v4 } from "uuid";
import { TSticky } from "@plane/types";
import { StickyService } from "@/plane-web/services/sticky.service";
export interface IStickyStore {
creatingSticky: boolean;
fetchingWorkspaceStickies: boolean;
workspaceStickies: Record<string, string[]>; // workspaceId -> stickyIds
stickies: Record<string, TSticky>; // stickyId -> sticky
searchQuery: string;
activeStickyId: string | undefined;
recentStickyId: string | undefined;
showAddNewSticky: boolean;
currentPage: number;
totalPages: number;
// computed
getWorkspaceStickies: (workspaceSlug: string) => string[];
// actions
toggleShowNewSticky: (value: boolean) => void;
updateSearchQuery: (query: string) => void;
fetchWorkspaceStickies: (workspaceSlug: string, cursor?: string, per_page?: number) => void;
createSticky: (workspaceSlug: string, sticky: Partial<TSticky>) => void;
updateSticky: (workspaceSlug: string, id: string, updates: Partial<TSticky>) => void;
deleteSticky: (workspaceSlug: string, id: string) => void;
updateActiveStickyId: (id: string | undefined) => void;
fetchRecentSticky: (workspaceSlug: string) => void;
incrementPage: () => void;
}
export class StickyStore implements IStickyStore {
creatingSticky = false;
fetchingWorkspaceStickies = true;
workspaceStickies: Record<string, string[]> = {};
stickies: Record<string, TSticky> = {};
recentStickyId: string | undefined = undefined;
searchQuery = "";
activeStickyId: string | undefined = undefined;
showAddNewSticky = false;
currentPage = 0;
totalPages = 0;
// services
stickyService;
constructor() {
makeObservable(this, {
// observables
creatingSticky: observable,
fetchingWorkspaceStickies: observable,
activeStickyId: observable,
showAddNewSticky: observable,
recentStickyId: observable,
workspaceStickies: observable,
stickies: observable,
searchQuery: observable,
currentPage: observable,
totalPages: observable,
// actions
updateSearchQuery: action,
updateSticky: action,
deleteSticky: action,
incrementPage: action,
});
this.stickyService = new StickyService();
}
getWorkspaceStickies = computedFn((workspaceSlug: string) => {
let filteredStickies = (this.workspaceStickies[workspaceSlug] || []).map((stickyId) => this.stickies[stickyId]);
if (this.searchQuery) {
filteredStickies = filteredStickies.filter(
(sticky) => sticky.name && sticky.name.toLowerCase().includes(this.searchQuery.toLowerCase())
);
}
return filteredStickies.map((sticky) => sticky.id);
});
toggleShowNewSticky = (value: boolean) => {
this.showAddNewSticky = value;
};
updateSearchQuery = (query: string) => {
this.searchQuery = query;
};
updateActiveStickyId = (id: string | undefined) => {
this.activeStickyId = id;
};
incrementPage = () => {
this.currentPage += 1;
};
fetchRecentSticky = async (workspaceSlug: string) => {
const response = await this.stickyService.getStickies(workspaceSlug, "1:0:0", 1);
runInAction(() => {
this.recentStickyId = response.results[0]?.id;
this.stickies[response.results[0]?.id] = response.results[0];
});
};
fetchWorkspaceStickies = async (workspaceSlug: string, cursor?: string, per_page?: number) => {
this.fetchingWorkspaceStickies = true;
const response = await this.stickyService.getStickies(workspaceSlug, cursor, per_page);
runInAction(() => {
response.results.forEach((sticky) => {
if (!this.workspaceStickies[workspaceSlug]?.includes(sticky.id)) {
this.workspaceStickies[workspaceSlug] = [...(this.workspaceStickies[workspaceSlug] || []), sticky.id];
}
this.stickies[sticky.id] = sticky;
});
this.totalPages = response.total_pages;
this.fetchingWorkspaceStickies = false;
});
};
createSticky = async (workspaceSlug: string, sticky: Partial<TSticky>) => {
if (!this.showAddNewSticky) return;
this.showAddNewSticky = false;
this.creatingSticky = true;
const workspaceStickies = this.workspaceStickies[workspaceSlug] || [];
const response = await this.stickyService.createSticky(workspaceSlug, sticky);
runInAction(() => {
this.stickies[response.id] = response;
this.workspaceStickies[workspaceSlug] = [response.id, ...workspaceStickies];
this.activeStickyId = response.id;
this.recentStickyId = response.id;
this.creatingSticky = false;
});
};
updateSticky = async (workspaceSlug: string, id: string, updates: Partial<TSticky>) => {
const sticky = this.stickies[id];
if (!sticky) return;
try {
this.stickies[id] = {
...sticky,
...updates,
updatedAt: new Date(),
};
this.recentStickyId = id;
await this.stickyService.updateSticky(workspaceSlug, id, updates);
} catch (e) {
console.log(e);
this.stickies[id] = sticky;
}
};
deleteSticky = async (workspaceSlug: string, id: string) => {
const sticky = this.stickies[id];
if (!sticky) return;
try {
this.workspaceStickies[workspaceSlug] = this.workspaceStickies[workspaceSlug].filter(
(stickyId) => stickyId !== id
);
if (this.activeStickyId === id) this.activeStickyId = undefined;
delete this.stickies[id];
this.recentStickyId = this.workspaceStickies[workspaceSlug][0];
await this.stickyService.deleteSticky(workspaceSlug, id);
} catch (e) {
console.log(e);
this.stickies[id] = sticky;
}
};
}