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
12 changes: 12 additions & 0 deletions packages/editor/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ pre code.hljs .hljs-code {
background: oklch(0.70 0.20 60 / 0.15);
}

.annotation-highlight.focused {
background: oklch(0.70 0.20 200 / 0.45) !important;
box-shadow: 0 0 8px oklch(0.70 0.20 200 / 0.4);
border-bottom: 2px solid oklch(0.70 0.20 200);
filter: none;
}

.light .annotation-highlight.focused {
background: oklch(0.70 0.22 200 / 0.3) !important;
box-shadow: 0 0 6px oklch(0.60 0.20 200 / 0.3);
}

.annotation-highlight:hover {
filter: brightness(1.2);
cursor: pointer;
Expand Down
13 changes: 12 additions & 1 deletion packages/ui/components/AnnotationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,19 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
onDeleteEditorAnnotation,
}) => {
const [copied, setCopied] = useState(false);
const listRef = useRef<HTMLDivElement>(null);
const sortedAnnotations = [...annotations].sort((a, b) => a.createdA - b.createdA);
const totalCount = annotations.length + (editorAnnotations?.length ?? 0);

// Scroll selected annotation card into view
useEffect(() => {
if (!selectedId || !listRef.current) return;
const card = listRef.current.querySelector(`[data-annotation-id="${selectedId}"]`);
if (card) {
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, [selectedId]);

const handleQuickShare = async () => {
if (!shareUrl) return;
try {
Expand Down Expand Up @@ -65,7 +75,7 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
</div>

{/* List */}
<div className="flex-1 overflow-y-auto p-2 space-y-1.5">
<div ref={listRef} className="flex-1 overflow-y-auto p-2 space-y-1.5">
{totalCount === 0 ? (
<div className="flex flex-col items-center justify-center h-40 text-center px-4">
<div className="w-10 h-10 rounded-full bg-muted/50 flex items-center justify-center mb-3">
Expand Down Expand Up @@ -276,6 +286,7 @@ const AnnotationCard: React.FC<{

return (
<div
data-annotation-id={annotation.id}
onClick={onSelect}
className={`
group relative p-2.5 rounded-lg border cursor-pointer transition-all
Expand Down
49 changes: 49 additions & 0 deletions packages/ui/components/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
const modeRef = useRef<EditorMode>(mode);
const onAddAnnotationRef = useRef(onAddAnnotation);
const pendingSourceRef = useRef<any>(null);
const justCreatedIdRef = useRef<string | null>(null);
const [toolbarState, setToolbarState] = useState<{
element: HTMLElement;
source: any;
Expand Down Expand Up @@ -260,6 +261,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
highlighter.addClass('comment', source.id);
}

justCreatedIdRef.current = newAnnotation.id;
onAddAnnotationRef.current(newAnnotation);
};

Expand Down Expand Up @@ -572,6 +574,52 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
});
}, [annotations]);

// Scroll to and focus the selected annotation's highlight in the content
useEffect(() => {
if (!containerRef.current) return;

// Clear all previously focused highlights
containerRef.current.querySelectorAll('.annotation-highlight.focused').forEach(el => {
el.classList.remove('focused');
});

if (!selectedAnnotationId) return;

// Skip scroll+focus when annotation was just created (user is already looking at it)
if (justCreatedIdRef.current === selectedAnnotationId) {
justCreatedIdRef.current = null;
return;
}

// Find highlight elements: try web-highlighter first, then manual marks
const highlighter = highlighterRef.current;
let targetElements: Element[] = [];

if (highlighter) {
try {
const doms = highlighter.getDoms(selectedAnnotationId);
if (doms && doms.length > 0) {
targetElements = Array.from(doms);
}
} catch (e) {}
}

if (targetElements.length === 0) {
const manualMarks = containerRef.current.querySelectorAll(
`[data-bind-id="${selectedAnnotationId}"]`
);
if (manualMarks.length > 0) {
targetElements = Array.from(manualMarks);
}
}

if (targetElements.length === 0) return;

// Apply focused class to all elements and scroll the first one into view
targetElements.forEach(el => el.classList.add('focused'));
targetElements[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
}, [selectedAnnotationId]);

const handleAnnotate = (type: AnnotationType) => {
const highlighter = highlighterRef.current;
if (!toolbarState || !highlighter) return;
Expand Down Expand Up @@ -622,6 +670,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
images,
};

justCreatedIdRef.current = newAnnotation.id;
onAddAnnotationRef.current(newAnnotation);
window.getSelection()?.removeAllRanges();
};
Expand Down