snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useCallback, useEffect, useRef } from "react";
import { usePathname } from "next/navigation";
import { Controller, useForm } from "react-hook-form";
// plane imports
import type { EditorRefApi } from "@plane/editor";
import type { TSticky } from "@plane/types";
import { cn, isCommentEmpty } from "@plane/utils";
import { StickyEditor } from "@/components/editor/sticky-editor";
// hooks
import { useWorkspace } from "@/hooks/store/use-workspace";
// const StickyEditor = dynamic(() => import("../../editor/sticky-editor").then((mod) => mod.StickyEditor), {
// ssr: false,
// });
type TProps = {
stickyData: Partial<TSticky> | undefined;
workspaceSlug: string;
handleUpdate: (payload: Partial<TSticky>) => void;
stickyId: string | undefined;
showToolbar?: boolean;
handleChange: (data: Partial<TSticky>) => Promise<void>;
handleDelete: () => void;
};
export function StickyInput(props: TProps) {
const { stickyData, workspaceSlug, handleUpdate, stickyId, handleDelete, handleChange, showToolbar } = props;
// refs
const editorRef = useRef<EditorRefApi>(null);
// navigation
const pathname = usePathname();
// store hooks
const { getWorkspaceBySlug } = useWorkspace();
// derived values
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id?.toString() ?? "";
const isStickiesPage = pathname?.includes("stickies");
// form info
const { handleSubmit, reset, control } = useForm<TSticky>({
defaultValues: {
description_html: stickyData?.description_html,
},
});
// handle description update
const handleFormSubmit = useCallback(
async (formdata: Partial<TSticky>) => {
await handleUpdate({
description_html: formdata.description_html ?? "<p></p>",
});
},
[handleUpdate]
);
// reset form values
useEffect(() => {
if (!stickyId) return;
reset({
id: stickyId,
description_html: stickyData?.description_html?.trim() === "" ? "<p></p>" : stickyData?.description_html,
});
}, [stickyData, stickyId, reset]);
return (
<div className="flex-1">
<Controller
name="description_html"
control={control}
render={({ field: { onChange } }) => (
<StickyEditor
id={`description-${stickyId}`}
initialValue={stickyData?.description_html ?? ""}
value={null}
workspaceSlug={workspaceSlug}
workspaceId={workspaceId}
onChange={(_description, description_html) => {
onChange(description_html);
handleSubmit(handleFormSubmit)();
}}
placeholder={(_, value) => {
const isContentEmpty = isCommentEmpty(value);
if (!isContentEmpty) return "";
return "Click to type here";
}}
containerClassName={cn(
"vertical-scrollbar scrollbar-sm max-h-[540px] min-h-[256px] w-full overflow-y-scroll p-4 text-14",
{
"max-h-[588px]": isStickiesPage,
}
)}
uploadFile={async () => ""}
duplicateFile={async () => ""}
showToolbar={showToolbar}
parentClassName="border-none p-0"
handleDelete={handleDelete}
handleColorChange={handleChange}
ref={editorRef}
/>
)}
/>
</div>
);
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useCallback, useState } from "react";
import { debounce } from "lodash-es";
import { observer } from "mobx-react";
import { ArrowCollapseOutline } from "@makeplane/propel/icons";
// plane types
import type { TSticky } from "@plane/types";
// plane utils
import { cn } from "@plane/utils";
// hooks
import { useSticky } from "@/hooks/use-stickies";
// components
import { STICKY_COLORS_LIST } from "../../editor/sticky-editor/color-palette";
import { StickyDeleteModal } from "../delete-modal";
import { StickyInput } from "./inputs";
import { getRandomStickyColor, useStickyOperations } from "./use-operations";
type TProps = {
onClose?: () => void;
workspaceSlug: string;
className?: string;
stickyId: string | undefined;
showToolbar?: boolean;
handleLayout?: () => void;
};
export const StickyNote = observer(function StickyNote(props: TProps) {
const { onClose, workspaceSlug, className = "", stickyId, showToolbar, handleLayout } = props;
// navigation
// const pathName = usePathname();
// states
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
// store hooks
const { stickies } = useSticky();
// sticky operations
const { stickyOperations } = useStickyOperations({ workspaceSlug });
// derived values
const stickyData: Partial<TSticky> = stickyId ? stickies[stickyId] : { background_color: getRandomStickyColor() };
// const isStickiesPage = pathName?.includes("stickies");
const backgroundColor =
STICKY_COLORS_LIST.find((c) => c.key === stickyData?.background_color)?.backgroundColor ||
STICKY_COLORS_LIST[0].backgroundColor;
const handleChange = useCallback(
async (payload: Partial<TSticky>) => {
if (stickyId) {
await stickyOperations.update(stickyId, payload);
} else {
await stickyOperations.create({
...stickyData,
...payload,
});
}
},
[stickyId, stickyOperations]
);
const debouncedFormSave = useCallback(
debounce(async (payload: Partial<TSticky>) => {
await handleChange(payload);
}, 500),
[stickyOperations, stickyData, handleChange]
);
const handleDelete = async () => {
if (!stickyId) return;
onClose?.();
stickyOperations.remove(stickyId);
};
return (
<>
<StickyDeleteModal
isOpen={isDeleteModalOpen}
handleSubmit={handleDelete}
handleClose={() => setIsDeleteModalOpen(false)}
/>
<div
className={cn("group/sticky flex h-fit w-full flex-col overflow-y-scroll rounded-sm", className)}
style={{
backgroundColor,
}}
>
{/* {isStickiesPage && <StickyItemDragHandle isDragging={false} />}{" "} */}
{onClose && (
<button type="button" className="flex flex-shrink-0 justify-end p-2.5" onClick={onClose}>
<ArrowCollapseOutline className="size-4" />
</button>
)}
{/* inputs */}
<div className="-mt-2">
<StickyInput
stickyData={stickyData}
workspaceSlug={workspaceSlug}
handleUpdate={(payload) => {
handleLayout?.();
debouncedFormSave(payload);
}}
stickyId={stickyId}
handleDelete={() => setIsDeleteModalOpen(true)}
handleChange={handleChange}
showToolbar={showToolbar}
/>
</div>
</div>
</>
);
});

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
// ui
import { DragHandle } from "@plane/ui";
// helper
import { cn } from "@plane/utils";
type Props = {
isDragging: boolean;
};
export const StickyItemDragHandle = observer(function StickyItemDragHandle(props: Props) {
const { isDragging } = props;
return (
<div
className={cn(
"absolute top-3 left-1/2 mr-2 hidden -translate-x-1/2 rotate-90 cursor-grab items-center justify-center rounded-sm text-placeholder group-hover/sticky:flex",
{
"cursor-grabbing": isDragging,
}
)}
>
<DragHandle className="bg-transparent" />
</div>
);
});

View File

@@ -0,0 +1,154 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useMemo } from "react";
// plane types
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { InstructionType, TSticky } from "@plane/types";
// plane utils
import { isCommentEmpty } from "@plane/utils";
// components
import { STICKY_COLORS_LIST } from "@/components/editor/sticky-editor/color-palette";
// hooks
import { useSticky } from "@/hooks/use-stickies";
export type TOperations = {
create: (data?: Partial<TSticky>) => Promise<void>;
update: (stickyId: string, data: Partial<TSticky>) => Promise<void>;
remove: (stickyId: string) => Promise<void>;
updatePosition: (
workspaceSlug: string,
sourceId: string,
droppedId: string,
instruction: InstructionType
) => Promise<void>;
};
type TProps = {
workspaceSlug: string;
};
export const getRandomStickyColor = (): string => {
const randomIndex = Math.floor(Math.random() * STICKY_COLORS_LIST.length);
return STICKY_COLORS_LIST[randomIndex].key;
};
export const useStickyOperations = (props: TProps) => {
const { workspaceSlug } = props;
// store hooks
const { stickies, getWorkspaceStickyIds, createSticky, updateSticky, deleteSticky, updateStickyPosition } =
useSticky();
const { t } = useTranslation();
const isValid = (data: Partial<TSticky>) => {
if (data.name && data.name.length > 100) {
setToast({
type: TOAST_TYPE.ERROR,
title: t("stickies.toasts.not_updated.title"),
message: t("stickies.toasts.errors.wrong_name"),
});
return false;
}
return true;
};
const stickyOperations: TOperations = useMemo(
() => ({
create: async (data?: Partial<TSticky>) => {
try {
const payload: Partial<TSticky> = {
background_color: getRandomStickyColor(),
...data,
};
const workspaceStickIds = getWorkspaceStickyIds(workspaceSlug);
// check if latest sticky is empty
if (workspaceStickIds && workspaceStickIds.length >= 0) {
const latestSticky = stickies[workspaceStickIds[0]];
if (latestSticky && (!latestSticky.description_html || isCommentEmpty(latestSticky.description_html))) {
setToast({
message: t("stickies.toasts.errors.already_exists"),
type: TOAST_TYPE.WARNING,
title: t("stickies.toasts.not_created.title"),
});
return;
}
}
if (!workspaceSlug) throw new Error("Missing required fields");
if (!isValid(payload)) return;
await createSticky(workspaceSlug, payload);
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("stickies.toasts.created.title"),
message: t("stickies.toasts.created.message"),
});
} catch (error: any) {
console.error("Error in creating sticky:", error);
setToast({
type: TOAST_TYPE.ERROR,
title: t("stickies.toasts.not_created.title"),
message: error?.data?.error ?? t("stickies.toasts.not_created.message"),
});
}
},
update: async (stickyId: string, data: Partial<TSticky>) => {
try {
if (!workspaceSlug) throw new Error("Missing required fields");
if (!isValid(data)) return;
await updateSticky(workspaceSlug, stickyId, data);
} catch (error) {
console.error("Error in updating sticky:", error);
setToast({
type: TOAST_TYPE.ERROR,
title: t("stickies.toasts.not_updated.title"),
message: t("stickies.toasts.not_updated.message"),
});
}
},
remove: async (stickyId: string) => {
try {
if (!workspaceSlug) throw new Error("Missing required fields");
await deleteSticky(workspaceSlug, stickyId);
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("stickies.toasts.removed.title"),
message: t("stickies.toasts.removed.message"),
});
} catch (error) {
console.error("Error in removing sticky:", error);
setToast({
type: TOAST_TYPE.ERROR,
title: t("stickies.toasts.not_removed.title"),
message: t("stickies.toasts.not_removed.message"),
});
}
},
updatePosition: async (
workspaceSlug: string,
sourceId: string,
droppedId: string,
instruction: InstructionType
) => {
try {
if (!workspaceSlug) throw new Error("Missing required fields");
await updateStickyPosition(workspaceSlug, sourceId, droppedId, instruction);
} catch (error) {
console.error("Error in updating sticky position:", error);
setToast({
type: TOAST_TYPE.ERROR,
title: t("stickies.toasts.not_updated.title"),
message: t("stickies.toasts.not_updated.message"),
});
}
},
}),
[createSticky, deleteSticky, getWorkspaceStickyIds, stickies, updateSticky, updateStickyPosition, workspaceSlug]
);
return {
stickyOperations,
};
};