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
6 changes: 6 additions & 0 deletions packages/agent-use/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @eigenpal/docx-editor-agents

## 0.0.33

### Patch Changes

- Add i18n

## 0.0.32

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-use/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eigenpal/docx-editor-agents",
"version": "0.0.32",
"version": "0.0.33",
"description": "Agent-friendly API for DOCX document review — read, comment, propose changes, accept/reject tracked changes",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
6 changes: 6 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @eigenpal/docx-js-editor

## 0.0.33

### Patch Changes

- Add i18n

## 0.0.32

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eigenpal/docx-js-editor",
"version": "0.0.32",
"version": "0.0.33",
"description": "A browser-based DOCX template editor with variable insertion support",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
47 changes: 46 additions & 1 deletion packages/react/src/components/DocxEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3898,6 +3898,51 @@ body { background: white; }
if (view) {
const selectionState = extractSelectionState(view.state);
handleSelectionChange(selectionState);

// Detect comment/tracked-change marks at cursor to open sidebar card.
// Collect marks from all sources — inclusive:false marks aren't
// reported by $from.marks() at boundaries, and empty arrays are
// truthy so an OR chain would short-circuit.
const $from = view.state.selection.$from;
const marks = [
...(view.state.storedMarks ?? []),
...($from.nodeAfter?.marks ?? []),
...($from.nodeBefore?.marks ?? []),
...$from.marks(),
];
let cursorSidebarItem: string | null = null;
for (const mark of marks) {
if (mark.type.name === 'comment' && mark.attrs.commentId != null) {
cursorSidebarItem = `comment-${mark.attrs.commentId}`;
break;
}
if (
(mark.type.name === 'insertion' || mark.type.name === 'deletion') &&
mark.attrs.revisionId != null
) {
const revId = String(mark.attrs.revisionId);
const prefix = `tc-${revId}-`;
let match = commentSidebarItems.find((i) =>
i.id.startsWith(prefix)
);
// Insertion side of a replacement has a different revisionId;
// check alias map to find the correct sidebar card.
if (!match && revisionIdAliases) {
const aliasedId = revisionIdAliases.get(revId);
if (aliasedId) {
match = commentSidebarItems.find((i) => i.id === aliasedId);
}
}
if (match) {
cursorSidebarItem = match.id;
break;
}
}
}
if (cursorSidebarItem) {
setShowCommentsSidebar(true);
}
setExpandedSidebarItem(cursorSidebarItem);
} else {
handleSelectionChange(null);
}
Expand Down Expand Up @@ -3929,7 +3974,7 @@ body { background: white; }
zoom={state.zoom}
editorContainerRef={scrollContainerRef}
onExpandedItemChange={setExpandedSidebarItem}
revisionIdAliases={revisionIdAliases}
activeItemId={expandedSidebarItem}
/>
)}
<CommentMarginMarkers
Expand Down
72 changes: 14 additions & 58 deletions packages/react/src/components/UnifiedSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export interface UnifiedSidebarProps {
zoom: number;
editorContainerRef: React.RefObject<HTMLDivElement | null>;
onExpandedItemChange?: (itemId: string | null) => void;
/** Maps alternate revision IDs to sidebar item IDs (e.g. insertion side of replacements). */
revisionIdAliases?: Map<string, string>;
/** Controlled: sidebar item to expand based on cursor position. */
activeItemId?: string | null;
}

export function UnifiedSidebar({
Expand All @@ -33,15 +33,14 @@ export function UnifiedSidebar({
zoom,
editorContainerRef,
onExpandedItemChange,
revisionIdAliases,
activeItemId,
}: UnifiedSidebarProps) {
const { t } = useTranslation();
const [expandedItem, setExpandedItem] = useState<string | null>(null);

// Notify parent when expanded item changes (via effect, not in setState updater)
useEffect(() => {
onExpandedItemChange?.(expandedItem);
}, [expandedItem, onExpandedItemChange]);
// Fully controlled: parent owns expansion state via activeItemId
const expandedItem = activeItemId ?? null;
// Ref keeps toggleExpand stable so card children don't re-render on every cursor move
const expandedItemRef = useRef(expandedItem);
expandedItemRef.current = expandedItem;

const [initialPositionsDone, setInitialPositionsDone] = useState(false);
const cardHeightsRef = useRef<Map<string, number>>(new Map());
Expand Down Expand Up @@ -147,52 +146,6 @@ export function UnifiedSidebar({
};
}, [expandedItem]);

useEffect(() => {
const container = editorContainerRef?.current;
if (!container) return;

const pagesEl = container.querySelector('.paged-editor__pages');
if (!pagesEl) return;

const handleDocClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (sidebarRef.current?.contains(target)) return;

if (pagesEl.contains(target)) {
const commentEl = target.closest('[data-comment-id]') as HTMLElement | null;
if (commentEl?.dataset.commentId) {
setExpandedItem(`comment-${commentEl.dataset.commentId}`);
return;
}
const changeEl =
(target.closest('.docx-insertion') as HTMLElement | null) ||
(target.closest('.docx-deletion') as HTMLElement | null);
if (changeEl?.dataset.revisionId) {
const revId = changeEl.dataset.revisionId;
const prefix = `tc-${revId}-`;
let match = items.find((i) => i.id.startsWith(prefix));
// For replacement tracked changes, the insertion part has a different
// revisionId than the card. Look up the alias to find the correct card.
if (!match && revisionIdAliases) {
const aliasedItemId = revisionIdAliases.get(revId);
if (aliasedItemId) {
match = items.find((i) => i.id === aliasedItemId);
}
}
if (match) {
setExpandedItem(match.id);
return;
}
}
}

setExpandedItem(null);
};

container.addEventListener('click', handleDocClick);
return () => container.removeEventListener('click', handleDocClick);
}, [editorContainerRef, items, revisionIdAliases]);

const getMeasureRef = useCallback((itemId: string): ((el: HTMLDivElement | null) => void) => {
let fn = measureRefsRef.current.get(itemId);
if (!fn) {
Expand All @@ -210,9 +163,12 @@ export function UnifiedSidebar({
return fn;
}, []);

const toggleExpand = useCallback((itemId: string) => {
setExpandedItem((prev) => (prev === itemId ? null : itemId));
}, []);
const toggleExpand = useCallback(
(itemId: string) => {
onExpandedItemChange?.(expandedItemRef.current === itemId ? null : itemId);
},
[onExpandedItemChange]
);

if (items.length === 0) return null;

Expand Down