feat: init
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
chuan
2025-11-11 01:56:44 +08:00
commit bba4bb40c8
4638 changed files with 447437 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
import { EModalWidth, ModalCore } from "@plane/ui";
import { Stickies } from "./stickies";
type TProps = {
isOpen: boolean;
handleClose: () => void;
};
export const AllStickiesModal = (props: TProps) => {
const { isOpen, handleClose } = props;
return (
<ModalCore isOpen={isOpen} handleClose={handleClose} width={EModalWidth.VXL}>
<Stickies handleClose={handleClose} />
</ModalCore>
);
};

View File

@@ -0,0 +1,101 @@
"use client";
import type { FC } from "react";
import { useCallback, useRef, useState } from "react";
import { debounce } from "lodash-es";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Search, X } from "lucide-react";
// plane hooks
import { useOutsideClickDetector } from "@plane/hooks";
// helpers
import { useTranslation } from "@plane/i18n";
import { cn } from "@plane/utils";
import { useSticky } from "@/hooks/use-stickies";
export const StickySearch: FC = observer(() => {
// router
const { workspaceSlug } = useParams();
// hooks
const { searchQuery, updateSearchQuery, fetchWorkspaceStickies } = useSticky();
const { t } = useTranslation();
// refs
const inputRef = useRef<HTMLInputElement>(null);
// states
const [isSearchOpen, setIsSearchOpen] = useState(false);
// outside click detector hook
useOutsideClickDetector(inputRef, () => {
if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false);
});
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
if (searchQuery && searchQuery.trim() !== "") {
updateSearchQuery("");
fetchStickies();
} else setIsSearchOpen(false);
}
};
const fetchStickies = async () => {
await fetchWorkspaceStickies(workspaceSlug.toString());
};
const debouncedSearch = useCallback(
debounce(async () => {
await fetchStickies();
}, 500),
[fetchWorkspaceStickies]
);
return (
<div className="flex items-center mr-2 my-auto">
{!isSearchOpen && (
<button
type="button"
className="-mr-1 p-1 hover:bg-custom-background-80 rounded text-custom-text-400 grid place-items-center"
onClick={() => {
setIsSearchOpen(true);
inputRef.current?.focus();
}}
>
<Search className=" size-4 " />
</button>
)}
<div
className={cn(
"ml-auto flex items-center justify-start gap-1 rounded-md border border-transparent bg-custom-background-100 text-custom-text-400 w-0 transition-[width] ease-linear overflow-hidden opacity-0",
{
"w-30 md:w-64 px-2.5 py-1.5 border-custom-border-200 opacity-100": isSearchOpen,
}
)}
>
<Search className="h-3.5 w-3.5" />
<input
ref={inputRef}
className="w-full max-w-[234px] border-none bg-transparent text-sm text-custom-text-100 placeholder:text-custom-text-400 focus:outline-none"
placeholder={t("stickies.search_placeholder")}
value={searchQuery}
onChange={(e) => {
updateSearchQuery(e.target.value);
debouncedSearch();
}}
onKeyDown={handleInputKeyDown}
/>
{isSearchOpen && (
<button
type="button"
className="grid place-items-center"
onClick={() => {
updateSearchQuery("");
setIsSearchOpen(false);
fetchStickies();
}}
>
<X className="h-3 w-3" />
</button>
)}
</div>
</div>
);
});

View File

@@ -0,0 +1,74 @@
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Plus, X } from "lucide-react";
// plane ui
import { RecentStickyIcon } from "@plane/propel/icons";
// hooks
import { useSticky } from "@/hooks/use-stickies";
// components
import { StickiesTruncated } from "../layout/stickies-truncated";
import { useStickyOperations } from "../sticky/use-operations";
import { StickySearch } from "./search";
type TProps = {
handleClose?: () => void;
};
export const Stickies = observer((props: TProps) => {
const { handleClose } = props;
// navigation
const { workspaceSlug } = useParams();
// store hooks
const { creatingSticky, toggleShowNewSticky } = useSticky();
// sticky operations
const { stickyOperations } = useStickyOperations({ workspaceSlug: workspaceSlug?.toString() });
return (
<div className="p-6 pb-0 min-h-[620px]">
{/* header */}
<div className="flex items-center justify-between mb-6">
{/* Title */}
<div className="text-custom-text-200 flex items-center gap-2">
<RecentStickyIcon className="size-5 rotate-90 flex-shrink-0" />
<p className="text-xl font-medium">Your stickies</p>
</div>
{/* actions */}
<div className="flex gap-2">
<StickySearch />
<button
onClick={() => {
toggleShowNewSticky(true);
stickyOperations.create();
}}
className="flex gap-1 text-sm font-medium text-custom-primary-100 my-auto"
disabled={creatingSticky}
>
<Plus className="size-4 my-auto" /> <span>Add sticky</span>
{creatingSticky && (
<div className="flex items-center justify-center ml-2">
<div
className={`w-4 h-4 border-2 border-t-transparent rounded-full animate-spin border-custom-primary-100`}
role="status"
aria-label="loading"
/>
</div>
)}
</button>
{handleClose && (
<button
type="button"
onClick={handleClose}
className="flex-shrink-0 grid place-items-center text-custom-text-300 hover:text-custom-text-100 hover:bg-custom-background-80 rounded p-1 transition-colors my-auto"
>
<X className="text-custom-text-400 size-4" />
</button>
)}
</div>
</div>
{/* content */}
<div className="mb-4 max-h-[625px] overflow-scroll">
<StickiesTruncated handleClose={handleClose} />
</div>
</div>
);
});