From c1a2fa751bb673de502f3e93d25787318163fd9a Mon Sep 17 00:00:00 2001 From: Hugo Bois Date: Sun, 10 May 2026 23:56:24 +0200 Subject: [PATCH] fixed editor sync in read-only, added comments navigation to sidebar, tweaked some visuals --- components/board/BoardCanvas.module.css | 8 +-- .../editor/sidebar/CommentSidebarItem.tsx | 62 +++++++++++++++++++ .../editor/sidebar/CommentSidebarView.tsx | 58 +++++++++++++++++ .../EditorSidebarNavigation.module.css | 12 ++++ .../sidebar/EditorSidebarNavigation.tsx | 23 +++++-- .../editor/sidebar/ShelfSidebarItem.tsx | 44 ++++++------- .../editor/sidebar/ShelfSidebarView.tsx | 24 ++++--- messages/en.json | 6 +- .../extensions/placeholder-extension.ts | 2 +- 9 files changed, 193 insertions(+), 46 deletions(-) create mode 100644 components/editor/sidebar/CommentSidebarItem.tsx create mode 100644 components/editor/sidebar/CommentSidebarView.tsx diff --git a/components/board/BoardCanvas.module.css b/components/board/BoardCanvas.module.css index 37d4491e..157042be 100644 --- a/components/board/BoardCanvas.module.css +++ b/components/board/BoardCanvas.module.css @@ -397,7 +397,7 @@ transform 0.15s ease, background-color 0.15s ease; z-index: 10; - background: radial-gradient(circle, var(--secondary) 30%, transparent 30%); + background: radial-gradient(circle, white 30%, transparent 30%); } .connection_handle::before { @@ -408,7 +408,7 @@ width: 16px; height: 16px; transform: translate(-50%, -50%); - border: 2px solid var(--secondary); + border: 2px solid white; border-radius: 50%; opacity: 0.6; } @@ -419,7 +419,7 @@ .connection_handle:hover { transform: scale(1.2); - background: radial-gradient(circle, var(--secondary) 40%, transparent 40%); + background: radial-gradient(circle, white 40%, transparent 40%); } .connection_handle:hover::before { @@ -449,6 +449,6 @@ .card_connecting:hover { box-shadow: - 0 0 0 3px var(--secondary), + 0 0 0 3px white, 0 4px 16px rgba(0, 0, 0, 0.2); } diff --git a/components/editor/sidebar/CommentSidebarItem.tsx b/components/editor/sidebar/CommentSidebarItem.tsx new file mode 100644 index 00000000..a8d3efc3 --- /dev/null +++ b/components/editor/sidebar/CommentSidebarItem.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { memo, useCallback, useContext } from "react"; +import { ProjectContext } from "@src/context/ProjectContext"; +import { Comment } from "@src/lib/utils/types"; +import { getCommentPositions } from "@src/lib/screenplay/extensions/comment-highlight-extension"; +import { focusOnPosition } from "@src/lib/screenplay/editor"; +import { join } from "@src/lib/utils/misc"; +import nav_item from "./SidebarItem.module.css"; + +type CommentSidebarItemProps = { + comment: Comment; + isActive: boolean; + onActivate: () => void; +}; + +const CommentSidebarItem = memo(({ comment, isActive, onActivate }: CommentSidebarItemProps) => { + const { editor } = useContext(ProjectContext); + + const handleDoubleClick = useCallback(() => { + if (!editor) return; + const positions = getCommentPositions(editor); + const pos = positions.get(comment.id); + if (pos) { + focusOnPosition(editor, pos.from); + } + }, [comment.id, editor]); + + const handleClick = useCallback(() => { + onActivate(); + }, [onActivate]); + + // Format date similar to other places, keeping it short + const date = new Date(comment.createdAt); + const dateStr = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); + + const containerClass = join( + nav_item.container, + isActive ? nav_item.current : "", + ); + + return ( +
+
+
+

{comment.author}

+
+ {dateStr} +
+

{comment.text || "Empty comment"}

+
+ ); +}); + +CommentSidebarItem.displayName = "CommentSidebarItem"; + +export default CommentSidebarItem; diff --git a/components/editor/sidebar/CommentSidebarView.tsx b/components/editor/sidebar/CommentSidebarView.tsx new file mode 100644 index 00000000..ff6ae066 --- /dev/null +++ b/components/editor/sidebar/CommentSidebarView.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useContext, useMemo } from "react"; +import { useTranslations } from "next-intl"; +import { ProjectContext } from "@src/context/ProjectContext"; +import { SCREENPLAY_EDITOR_CONFIG } from "@src/lib/editor/document-editor-config"; +import { useDocumentComments } from "@src/lib/editor/use-document-comments"; +import { join } from "@src/lib/utils/misc"; +import { MessageSquare } from "lucide-react"; +import CommentSidebarItem from "./CommentSidebarItem"; + +import form from "./../../utils/Form.module.css"; +import sidebar_nav from "./EditorSidebarNavigation.module.css"; + +const CommentSidebarView = () => { + const t = useTranslations("editorSidebar"); + const { repository } = useContext(ProjectContext); + + const projectState = repository?.getState(); + const commentsMap = useMemo( + () => + projectState && SCREENPLAY_EDITOR_CONFIG.features.comments + ? SCREENPLAY_EDITOR_CONFIG.getCommentsMap(projectState) + : null, + [projectState], + ); + + const { comments, activeCommentId, setActiveCommentId } = useDocumentComments(commentsMap, repository); + + const unresolvedComments = useMemo(() => comments.filter((c) => !c.resolved), [comments]); + + return ( + <> +
+ +

{t("comments")}

+
+
+ {unresolvedComments.length !== 0 ? ( + unresolvedComments.map((comment) => ( + setActiveCommentId(comment.id)} + /> + )) + ) : ( +
+ {t("commentsEmpty")} +
+ )} +
+ + ); +}; + +export default CommentSidebarView; diff --git a/components/editor/sidebar/EditorSidebarNavigation.module.css b/components/editor/sidebar/EditorSidebarNavigation.module.css index f4bc011a..6587655f 100644 --- a/components/editor/sidebar/EditorSidebarNavigation.module.css +++ b/components/editor/sidebar/EditorSidebarNavigation.module.css @@ -54,6 +54,18 @@ overflow-x: hidden; } +.empty_state { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + text-align: center; + color: var(--secondary-text); + font-size: 12px; +} + .scene_list { flex: 1; } diff --git a/components/editor/sidebar/EditorSidebarNavigation.tsx b/components/editor/sidebar/EditorSidebarNavigation.tsx index ad5b68e2..dcca095e 100644 --- a/components/editor/sidebar/EditorSidebarNavigation.tsx +++ b/components/editor/sidebar/EditorSidebarNavigation.tsx @@ -7,9 +7,10 @@ import { ProjectContext } from "@src/context/ProjectContext"; import { useViewContext } from "@src/context/ViewContext"; import { Scene } from "@src/lib/screenplay/scenes"; import { focusOnPosition } from "@src/lib/screenplay/editor"; -import { Archive, Clapperboard } from "lucide-react"; +import { Archive, Clapperboard, MessageSquare } from "lucide-react"; import SidebarSceneItem from "./SidebarSceneItem"; import ShelfSidebarView from "./ShelfSidebarView"; +import CommentSidebarView from "./CommentSidebarView"; import form from "./../../utils/Form.module.css"; import sidebar_nav from "./EditorSidebarNavigation.module.css"; @@ -19,7 +20,7 @@ const EditorSidebarNavigation = () => { const { scenes, updateScenes, editor } = useContext(ProjectContext); const { leftSidebarOpen } = useViewContext(); - const [activeTab, setActiveTab] = useState<"scenes" | "shelf">("scenes"); + const [activeTab, setActiveTab] = useState<"scenes" | "shelf" | "comments">("scenes"); const [dragIndex, setDragIndex] = useState(null); // indicatorIndex represents the gap where the item will be inserted. @@ -193,7 +194,7 @@ const EditorSidebarNavigation = () => { className={join(sidebar_nav.list, sidebar_nav.scene_list)} onPointerMove={handlePointerMove} > - {scenes.length != 0 && + {scenes.length != 0 ? scenes.map((scene: Scene, index: number) => { const isNoOp = dragIndex === null || @@ -214,11 +215,17 @@ const EditorSidebarNavigation = () => { onDoubleClick={handleDoubleClick} /> ); - })} + }) : ( +
+ {t("scenesEmpty")} +
+ )} - ) : ( + ) : activeTab === "shelf" ? ( + ) : ( + )}
+ + {entry.versions.length}
{isExpanded && (
@@ -185,10 +175,7 @@ const ShelfSidebarItem = memo(({ nodeId, entry, isExpanded, onToggle }: ShelfSid onClick={(e) => e.stopPropagation()} /> ) : isConfirmingRestore ? ( -
e.stopPropagation()} - > +
e.stopPropagation()}> {t("confirmRestore")}
diff --git a/components/editor/sidebar/ShelfSidebarView.tsx b/components/editor/sidebar/ShelfSidebarView.tsx index 6aaa621e..17463ced 100644 --- a/components/editor/sidebar/ShelfSidebarView.tsx +++ b/components/editor/sidebar/ShelfSidebarView.tsx @@ -24,15 +24,21 @@ const ShelfSidebarView = () => {

{t("shelf")}

- {entries.map(([nodeId, entry]) => ( - setExpandedId(expandedId === nodeId ? null : nodeId)} - /> - ))} + {entries.length !== 0 ? ( + entries.map(([nodeId, entry]) => ( + setExpandedId(expandedId === nodeId ? null : nodeId)} + /> + )) + ) : ( +
+ {t("shelfEmpty")} +
+ )}
); diff --git a/messages/en.json b/messages/en.json index f6b085c3..797eb080 100644 --- a/messages/en.json +++ b/messages/en.json @@ -198,10 +198,14 @@ }, "editorSidebar": { "scenes": "Scenes", + "scenesEmpty": "No scenes yet", "characters": "Characters", "locations": "Locations", "shelf": "Shelf", - "shelfEmpty": "Select a shelved version to edit", + "shelfEmpty": "No shelved items yet", + "comments": "Comments", + "commentsEmpty": "No active comments", + "shelfEmptySelection": "Select a shelved version to edit", "goTo": "Go to in screenplay", "confirmRestore": "This will replace the matching content in your screenplay.", "restore": "Restore", diff --git a/src/lib/screenplay/extensions/placeholder-extension.ts b/src/lib/screenplay/extensions/placeholder-extension.ts index bac529a5..b10f7381 100644 --- a/src/lib/screenplay/extensions/placeholder-extension.ts +++ b/src/lib/screenplay/extensions/placeholder-extension.ts @@ -70,7 +70,7 @@ export const Placeholder = Extension.create({ emptyEditorClass: 'is-editor-empty', emptyNodeClass: 'is-empty', placeholder: 'Write something …', - showOnlyWhenEditable: true, + showOnlyWhenEditable: false, showOnlyCurrent: false, includeChildren: true, }