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,39 @@
"use client";
import { observer } from "mobx-react";
// plane web components
import { PageLockControl } from "@/plane-web/components/pages/header/lock-control";
import { PageMoveControl } from "@/plane-web/components/pages/header/move-control";
import { PageShareControl } from "@/plane-web/components/pages/header/share-control";
// plane web hooks
import type { EPageStoreType } from "@/plane-web/hooks/store";
// store
import type { TPageInstance } from "@/store/pages/base-page";
// local imports
import { PageOptionsDropdown } from "../editor/toolbar";
import { PageArchivedBadge } from "./archived-badge";
import { PageCopyLinkControl } from "./copy-link-control";
import { PageFavoriteControl } from "./favorite-control";
import { PageOfflineBadge } from "./offline-badge";
type Props = {
page: TPageInstance;
storeType: EPageStoreType;
};
export const PageHeaderActions: React.FC<Props> = observer((props) => {
const { page, storeType } = props;
return (
<div className="flex items-center gap-1">
<PageArchivedBadge page={page} />
<PageOfflineBadge page={page} />
<PageLockControl page={page} />
<PageMoveControl page={page} />
<PageCopyLinkControl page={page} />
<PageFavoriteControl page={page} />
<PageShareControl page={page} storeType={storeType} />
<PageOptionsDropdown page={page} storeType={storeType} />
</div>
);
});

View File

@@ -0,0 +1,21 @@
import { observer } from "mobx-react";
// plane imports
import { ArchiveIcon } from "@plane/propel/icons";
import { renderFormattedDate } from "@plane/utils";
// store
import type { TPageInstance } from "@/store/pages/base-page";
type Props = {
page: TPageInstance;
};
export const PageArchivedBadge = observer(({ page }: Props) => {
if (!page.archived_at) return null;
return (
<div className="flex-shrink-0 h-6 flex items-center gap-1 px-2 rounded text-custom-primary-100 bg-custom-primary-100/20">
<ArchiveIcon className="flex-shrink-0 size-3.5" />
<span className="text-xs font-medium">Archived at {renderFormattedDate(page.archived_at)}</span>
</div>
);
});

View File

@@ -0,0 +1,27 @@
import { observer } from "mobx-react";
import { Link } from "lucide-react";
// hooks
import { usePageOperations } from "@/hooks/use-page-operations";
// store
import type { TPageInstance } from "@/store/pages/base-page";
type Props = {
page: TPageInstance;
};
export const PageCopyLinkControl = observer(({ page }: Props) => {
// page operations
const { pageOperations } = usePageOperations({
page,
});
return (
<button
type="button"
onClick={pageOperations.copyLink}
className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 transition-colors"
>
<Link className="size-3.5" />
</button>
);
});

View File

@@ -0,0 +1,40 @@
import { observer } from "mobx-react";
// constants
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
// ui
import { FavoriteStar } from "@plane/ui";
// helpers
import { captureClick } from "@/helpers/event-tracker.helper";
// hooks
import { usePageOperations } from "@/hooks/use-page-operations";
// store
import type { TPageInstance } from "@/store/pages/base-page";
type Props = {
page: TPageInstance;
};
export const PageFavoriteControl = observer(({ page }: Props) => {
// derived values
const { is_favorite, canCurrentUserFavoritePage } = page;
// page operations
const { pageOperations } = usePageOperations({
page,
});
if (!canCurrentUserFavoritePage) return null;
return (
<FavoriteStar
selected={is_favorite}
onClick={() => {
captureClick({
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.FAVORITE_BUTTON,
});
pageOperations.toggleFavorite();
}}
buttonClassName="flex-shrink-0 size-6 group rounded hover:bg-custom-background-80 transition-colors"
iconClassName="size-3.5 text-custom-text-200 group-hover:text-custom-text-10"
/>
);
});

View File

@@ -0,0 +1 @@
export * from "./root";

View File

@@ -0,0 +1,30 @@
import { observer } from "mobx-react";
// plane imports
import { Tooltip } from "@plane/propel/tooltip";
// hooks
import useOnlineStatus from "@/hooks/use-online-status";
// store
import type { TPageInstance } from "@/store/pages/base-page";
type Props = {
page: TPageInstance;
};
export const PageOfflineBadge = observer(({ page }: Props) => {
// use online status
const { isOnline } = useOnlineStatus();
if (!page.isContentEditable || isOnline) return null;
return (
<Tooltip
tooltipHeading="You are offline."
tooltipContent="You can continue making changes. They will be synced when you are back online."
>
<div className="flex-shrink-0 flex h-7 items-center gap-2 rounded-full bg-custom-background-80 px-3 py-0.5 text-xs font-medium text-custom-text-300">
<span className="flex-shrink-0 size-1.5 rounded-full bg-custom-text-300" />
<span>Offline</span>
</div>
</Tooltip>
);
});

View File

@@ -0,0 +1,101 @@
import { useCallback } from "react";
import { observer } from "mobx-react";
import { ListFilter } from "lucide-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import type { TPageFilterProps, TPageNavigationTabs } from "@plane/types";
import { Header, EHeaderVariant } from "@plane/ui";
import { calculateTotalFilters } from "@plane/utils";
// components
import { FiltersDropdown } from "@/components/issues/issue-layouts/filters";
// hooks
import { useMember } from "@/hooks/store/use-member";
// plane web hooks
import type { EPageStoreType } from "@/plane-web/hooks/store";
import { usePageStore } from "@/plane-web/hooks/store";
// local imports
import { PageAppliedFiltersList } from "../list/applied-filters";
import { PageFiltersSelection } from "../list/filters";
import { PageOrderByDropdown } from "../list/order-by";
import { PageSearchInput } from "../list/search-input";
import { PageTabNavigation } from "../list/tab-navigation";
type Props = {
pageType: TPageNavigationTabs;
projectId: string;
storeType: EPageStoreType;
workspaceSlug: string;
};
export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
const { pageType, projectId, storeType, workspaceSlug } = props;
const { t } = useTranslation();
// store hooks
const { filters, updateFilters, clearAllFilters } = usePageStore(storeType);
const {
workspace: { workspaceMemberIds },
} = useMember();
const handleRemoveFilter = useCallback(
(key: keyof TPageFilterProps, value: string | null) => {
let newValues = filters.filters?.[key];
if (key === "favorites") newValues = !!value;
if (Array.isArray(newValues)) {
if (!value) newValues = [];
else newValues = newValues.filter((val) => val !== value);
}
updateFilters("filters", { [key]: newValues });
},
[filters.filters, updateFilters]
);
const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
return (
<>
<Header variant={EHeaderVariant.SECONDARY}>
<Header.LeftItem>
<PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
</Header.LeftItem>
<Header.RightItem className="items-center">
<PageSearchInput
searchQuery={filters.searchQuery}
updateSearchQuery={(val) => updateFilters("searchQuery", val)}
/>
<PageOrderByDropdown
sortBy={filters.sortBy}
sortKey={filters.sortKey}
onChange={(val) => {
if (val.key) updateFilters("sortKey", val.key);
if (val.order) updateFilters("sortBy", val.order);
}}
/>
<FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title={t("common.filters")}
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<PageFiltersSelection
filters={filters}
handleFiltersUpdate={updateFilters}
memberIds={workspaceMemberIds ?? undefined}
/>
</FiltersDropdown>
</Header.RightItem>
</Header>
{calculateTotalFilters(filters?.filters ?? {}) !== 0 && (
<Header variant={EHeaderVariant.TERNARY}>
<PageAppliedFiltersList
appliedFilters={filters.filters ?? {}}
handleClearAllFilters={clearAllFilters}
handleRemoveFilter={handleRemoveFilter}
alwaysAllowEditing
/>
</Header>
)}
</>
);
});