Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/web/ce/hooks/pages/use-pages-pane-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { EditorRefApi } from "@plane/editor";
import {
PAGE_NAVIGATION_PANE_TAB_KEYS,
PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM,
PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM,
} from "@/components/pages/navigation-pane";
import { useAppRouter } from "@/hooks/use-app-router";
import { useQueryParams } from "@/hooks/use-query-params";
Expand Down Expand Up @@ -43,10 +44,18 @@ export const usePagesPaneExtensions = (_params: TPageExtensionHookParams) => {

const navigationPaneExtensions: INavigationPaneExtension[] = [];

const handleCloseNavigationPane = useCallback(() => {
const updatedRoute = updateQueryParams({
paramsToRemove: [PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM, PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM],
});
router.push(updatedRoute);
}, [router, updateQueryParams]);

return {
editorExtensionHandlers,
navigationPaneExtensions,
handleOpenNavigationPane,
isNavigationPaneOpen,
handleCloseNavigationPane,
};
};
105 changes: 68 additions & 37 deletions apps/web/core/components/editor/lite-text/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import { useEditorConfig, useEditorMention } from "@/hooks/editor";
import { useMember } from "@/hooks/store/use-member";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
// plane web services
// plane web service
import { WorkspaceService } from "@/plane-web/services";
import { LiteToolbar } from "./lite-toolbar";
const workspaceService = new WorkspaceService();

type LiteTextEditorWrapperProps = MakeOptional<
Expand All @@ -31,9 +32,10 @@ type LiteTextEditorWrapperProps = MakeOptional<
showSubmitButton?: boolean;
isSubmitting?: boolean;
showToolbarInitially?: boolean;
showToolbar?: boolean;
variant?: "full" | "lite" | "none";
issue_id?: string;
parentClassName?: string;
editorClassName?: string;
} & (
| {
editable: false;
Expand All @@ -59,14 +61,17 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
showSubmitButton = true,
isSubmitting = false,
showToolbarInitially = true,
showToolbar = true,
variant = "full",
parentClassName = "",
placeholder = t("issue.comments.placeholder"),
disabledExtensions: additionalDisabledExtensions = [],
editorClassName = "",
...rest
} = props;
// states
const [isFocused, setIsFocused] = useState(showToolbarInitially);
const isLiteVariant = variant === "lite";
const isFullVariant = variant === "full";
const [isFocused, setIsFocused] = useState(isFullVariant ? showToolbarInitially : true);
// editor flaggings
const { liteText: liteTextEditorExtensions } = useEditorFlagging({
workspaceSlug: workspaceSlug?.toString() ?? "",
Expand Down Expand Up @@ -95,43 +100,69 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
className={cn(
"relative border border-custom-border-200 rounded",
{
"p-3": editable,
"p-3": editable && !isLiteVariant,
},
parentClassName
)}
onFocus={() => !showToolbarInitially && setIsFocused(true)}
onBlur={() => !showToolbarInitially && setIsFocused(false)}
onFocus={() => isFullVariant && !showToolbarInitially && setIsFocused(true)}
onBlur={() => isFullVariant && !showToolbarInitially && setIsFocused(false)}
>
<LiteTextEditorWithRef
ref={ref}
disabledExtensions={[...liteTextEditorExtensions.disabled, ...additionalDisabledExtensions]}
editable={editable}
flaggedExtensions={liteTextEditorExtensions.flagged}
fileHandler={getEditorFileHandlers({
projectId,
uploadFile: editable ? props.uploadFile : async () => "",
workspaceId,
workspaceSlug,
})}
mentionHandler={{
searchCallback: async (query) => {
const res = await fetchMentions(query);
if (!res) throw new Error("Failed in fetching mentions");
return res;
},
renderComponent: EditorMentionsRoot,
getMentionedEntityDetails: (id) => ({
display_name: getUserDetails(id)?.display_name ?? "",
}),
}}
placeholder={placeholder}
containerClassName={cn(containerClassName, "relative", {
"p-2": !editable,
})}
extendedEditorProps={{}}
{...rest}
/>
{showToolbar && editable && (
{/* Wrapper for lite toolbar layout */}
<div className={cn(isLiteVariant && editable ? "flex items-end gap-1" : "")}>
{/* Main Editor - always rendered once */}
<div className={cn(isLiteVariant && editable ? "flex-1 min-w-0" : "")}>
<LiteTextEditorWithRef
ref={ref}
disabledExtensions={[...liteTextEditorExtensions.disabled, ...additionalDisabledExtensions]}
editable={editable}
flaggedExtensions={liteTextEditorExtensions.flagged}
fileHandler={getEditorFileHandlers({
projectId,
uploadFile: editable ? props.uploadFile : async () => "",
workspaceId,
workspaceSlug,
})}
mentionHandler={{
searchCallback: async (query) => {
const res = await fetchMentions(query);
if (!res) throw new Error("Failed in fetching mentions");
return res;
},
renderComponent: EditorMentionsRoot,
getMentionedEntityDetails: (id) => ({
display_name: getUserDetails(id)?.display_name ?? "",
}),
}}
placeholder={placeholder}
containerClassName={cn(containerClassName, "relative", {
"p-2": !editable,
})}
extendedEditorProps={{}}
editorClassName={editorClassName}
{...rest}
/>
</div>

{/* Lite Toolbar - conditionally rendered */}
{isLiteVariant && editable && (
<LiteToolbar
executeCommand={(item) => {
// TODO: update this while toolbar homogenization
// @ts-expect-error type mismatch here
editorRef?.executeMenuItemCommand({
itemKey: item.itemKey,
...item.extraProps,
});
}}
onSubmit={(e) => rest.onEnterKeyPress?.(e)}
isSubmitting={isSubmitting}
isEmpty={isEmpty}
/>
)}
</div>

{/* Full Toolbar - conditionally rendered */}
{isFullVariant && editable && (
<div
className={cn(
"transition-all duration-300 ease-out origin-top overflow-hidden",
Expand Down
33 changes: 33 additions & 0 deletions apps/web/core/components/editor/lite-text/lite-toolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import { ArrowUp, Paperclip } from "lucide-react";
// constants
import { IMAGE_ITEM, ToolbarMenuItem } from "@/constants/editor";

type LiteToolbarProps = {
onSubmit: (e: React.KeyboardEvent<HTMLDivElement> | React.MouseEvent<HTMLButtonElement>) => void;
isSubmitting: boolean;
isEmpty: boolean;
executeCommand: (item: ToolbarMenuItem) => void;
};

export const LiteToolbar = ({ onSubmit, isSubmitting, isEmpty, executeCommand }: LiteToolbarProps) => (
<div className="flex items-center gap-2 pb-1">
<button
onClick={() => executeCommand(IMAGE_ITEM)}
type="button"
className="p-1 text-custom-text-300 hover:text-custom-text-200 transition-colors"
>
<Paperclip className="size-3" />
</button>
<button
type="button"
onClick={(e) => onSubmit(e)}
disabled={isEmpty || isSubmitting}
className="p-1 bg-custom-primary-100 hover:bg-custom-primary-200 disabled:bg-custom-text-400 disabled:text-custom-text-200 text-custom-text-100 rounded transition-colors"
>
<ArrowUp className="size-3" />
</button>
</div>
);

export type { LiteToolbarProps };
30 changes: 11 additions & 19 deletions apps/web/core/components/pages/editor/page-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@ import type { TDocumentPayload, TPage, TPageVersion, TWebhookConnectionQueryPara
// hooks
import { useAppRouter } from "@/hooks/use-app-router";
import { usePageFallback } from "@/hooks/use-page-fallback";
import { useQueryParams } from "@/hooks/use-query-params";
// plane web import
import { PageModals } from "@/plane-web/components/pages";
import { usePagesPaneExtensions, useExtendedEditorProps } from "@/plane-web/hooks/pages";
import { EPageStoreType } from "@/plane-web/hooks/store";
// store
import type { TPageInstance } from "@/store/pages/base-page";
// local imports
import {
PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM,
PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM,
PageNavigationPaneRoot,
} from "../navigation-pane";
import { PageNavigationPaneRoot } from "../navigation-pane";
import { PageVersionsOverlay } from "../version";
import { PagesVersionEditor } from "../version/editor";
import { PageEditorBody, type TEditorBodyConfig, type TEditorBodyHandlers } from "./editor-body";
Expand Down Expand Up @@ -66,7 +61,6 @@ export const PageRoot = observer((props: TPageRootProps) => {
hasConnectionFailed,
updatePageDescription: handlers.updateDescription,
});
const { updateQueryParams } = useQueryParams();

const handleEditorReady = useCallback(
(status: boolean) => {
Expand All @@ -85,11 +79,16 @@ export const PageRoot = observer((props: TPageRootProps) => {
}, [isContentEditable, setEditorRef]);

// Get extensions and navigation logic from hook
const { editorExtensionHandlers, navigationPaneExtensions, handleOpenNavigationPane, isNavigationPaneOpen } =
usePagesPaneExtensions({
page,
editorRef,
});
const {
editorExtensionHandlers,
navigationPaneExtensions,
handleOpenNavigationPane,
handleCloseNavigationPane,
isNavigationPaneOpen,
} = usePagesPaneExtensions({
page,
editorRef,
});

// Get extended editor extensions configuration
const extendedEditorProps = useExtendedEditorProps({
Expand Down Expand Up @@ -118,13 +117,6 @@ export const PageRoot = observer((props: TPageRootProps) => {
[setEditorRef]
);

const handleCloseNavigationPane = useCallback(() => {
const updatedRoute = updateQueryParams({
paramsToRemove: [PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM, PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM],
});
router.push(updatedRoute);
}, [router, updateQueryParams]);

return (
<div className="relative size-full overflow-hidden flex transition-all duration-300 ease-in-out">
<div className="size-full flex flex-col overflow-hidden">
Expand Down
11 changes: 10 additions & 1 deletion apps/web/core/constants/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,18 @@ const USER_ACTION_ITEMS: ToolbarMenuItem<"quote" | "code">[] = [
{ itemKey: "code", renderKey: "code", name: "Code", icon: Code2, editors: ["lite", "document"] },
];

export const IMAGE_ITEM = {
itemKey: "image",
renderKey: "image",
name: "Image",
icon: Image,
editors: ["lite", "document"],
extraProps: {},
} as ToolbarMenuItem<"image">;

const COMPLEX_ITEMS: ToolbarMenuItem<"table" | "image">[] = [
{ itemKey: "table", renderKey: "table", name: "Table", icon: Table, editors: ["document"] },
{ itemKey: "image", renderKey: "image", name: "Image", icon: Image, editors: ["lite", "document"], extraProps: {} },
IMAGE_ITEM,
];

export const TOOLBAR_ITEMS: {
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/tsdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default defineConfig({
outDir: "dist",
format: ["esm", "cjs"],
dts: true,
clean: true,
clean: false,
sourcemap: true,
copy: ["src/styles"],
});
Loading