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
7 changes: 0 additions & 7 deletions api/src/main/resources/db/migration/V4__document_trash.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
ALTER TABLE documents
ADD COLUMN deleted_at TIMESTAMPTZ NULL;

DROP INDEX IF EXISTS uq_documents_user_source_local;
DROP INDEX IF EXISTS idx_documents_user_source_local;

CREATE UNIQUE INDEX uq_documents_user_source_local
ON documents(user_id, source_local_id)
WHERE source_local_id IS NOT NULL AND deleted_at IS NULL;

CREATE INDEX idx_documents_trash_purge
ON documents(deleted_at)
WHERE deleted_at IS NOT NULL;
40 changes: 38 additions & 2 deletions web/components/DocToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ interface DocToolbarProps {
showGuestNotice?: boolean;
/** Callback for the guest notice auth CTA */
onGuestNoticeCtaClick?: () => void;
/** Whether to show a trash notice in the top toolbar */
showTrashNotice?: boolean;
/** Callback to restore the document from trash */
onRestore?: () => void;
}

export function DocToolbar({
Expand All @@ -65,6 +69,8 @@ export function DocToolbar({
onCommentsToggle,
showGuestNotice = false,
onGuestNoticeCtaClick,
showTrashNotice = false,
onRestore,
}: DocToolbarProps) {
const [isShareOpen, setIsShareOpen] = useState(false);
const [showOfflineTooltip, setShowOfflineTooltip] = useState(false);
Expand Down Expand Up @@ -152,6 +158,36 @@ export function DocToolbar({

{/* ── Top-right toolbar ── */}
<div className="nd-doc-toolbar fixed top-2 right-2 z-40 flex items-center gap-1.5">
{showTrashNotice && onRestore && (
<div
className="
inline-flex items-center gap-1.5 rounded-lg
border border-border/70 bg-background/85 backdrop-blur-sm
px-3 py-1.5
text-[12px] text-muted-foreground
shadow-sm
whitespace-nowrap
"
>
<span>This document is in the trash.</span>
<button
type="button"
onClick={onRestore}
className="
rounded-sm px-0.5
font-medium text-foreground
underline decoration-foreground/30 underline-offset-3
hover:text-primary hover:decoration-primary/60
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50
transition-colors cursor-pointer
"
>
Restore
</button>
<span>it to make edits.</span>
</div>
)}

{shouldShowGuestNotice && (
<div
className="
Expand Down Expand Up @@ -198,7 +234,7 @@ export function DocToolbar({
aria-pressed={isCommentsSidebarOpen}
aria-label={`${isCommentsSidebarOpen ? 'Close' : 'Open'} comments sidebar. ${commentsSummary}.`}
className={[
'inline-flex items-center justify-center rounded-sm py-1 px-2 transition-colors duration-150',
'inline-flex items-center justify-center rounded-sm py-[5px] px-2 transition-colors duration-150',
isCommentsButtonDisabled
? 'text-muted-foreground opacity-60 cursor-not-allowed'
: isCommentsSidebarOpen
Expand All @@ -222,7 +258,7 @@ export function DocToolbar({
className="
inline-flex items-center gap-1.5 rounded-sm
border border-border bg-background
px-3 py-1.5
px-2.5 py-[5px]
text-[12.5px] font-medium leading-none text-foreground
transition-colors duration-150
hover:bg-[var(--nd-toolbar-hover-bg)]
Expand Down
22 changes: 21 additions & 1 deletion web/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export default function Editor() {
isLoading,
error,
updateMeta,
restore,
} = useDocument(effectiveDocumentId, { isSharedDocument });
const [showLoading, setShowLoading] = useState(false);
const [showCommentsSidebar, setShowCommentsSidebar] = useState(false);
Expand All @@ -153,7 +154,7 @@ export default function Editor() {
const [commentStatsByDocument, setCommentStatsByDocument] = useState<
Record<string, CommentThreadStats>
>({});
const isGuestSharedView = !isAuthenticated && isSharedDocument && accessLevel === 'VIEW';
const isGuestSharedView = !isAuthenticated && accessLevel === 'VIEW';
const isOffline = !isOnline;
const { pendingEdits } = useYjsPersistence(
documentId,
Expand All @@ -167,6 +168,23 @@ export default function Editor() {
window.dispatchEvent(new CustomEvent('open-auth-modal'));
}, []);

const [isRestoring, setIsRestoring] = useState(false);

const handleRestore = useCallback(async () => {
if (isRestoring) {
return;
}
setIsRestoring(true);
try {
await restore();
} catch (error) {
console.error('Failed to restore document:', error);
alert('Failed to restore document. Please try again.');
} finally {
setIsRestoring(false);
}
}, [restore, isRestoring]);

// Look at the comment in useOfflineDocumentSelect file to know why we need this workaround.
useOfflineDocumentSelect(setOfflineSelectedDocumentId);

Expand Down Expand Up @@ -276,6 +294,8 @@ export default function Editor() {
pendingEdits={pendingEdits}
showGuestNotice={isGuestSharedView}
onGuestNoticeCtaClick={openAuthModal}
showTrashNotice={!!meta?.deletedAt}
onRestore={handleRestore}
showCommentsButton={showCommentsButton}
isCommentsSidebarOpen={isCommentsSidebarOpen}
openCommentsCount={activeCommentStats.open}
Expand Down
2 changes: 1 addition & 1 deletion web/components/PopupMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type PopupMenuItemProps = {
};

const baseClassName =
'w-full rounded-lg px-3 py-1.5 text-left text-[13px] transition-colors cursor-pointer flex items-center gap-2';
'w-full rounded-sm px-2 py-1.5 text-left text-[13px] transition-colors cursor-pointer flex items-center gap-2';

const iconSlotClassName = 'inline-flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center';

Expand Down
2 changes: 1 addition & 1 deletion web/components/SharePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ export function SharePanel({ documentId, isOpen, onClose, anchorRef }: SharePane
className="
flex-shrink-0 rounded-full bg-[#d7897f] hover:bg-[#C97B71]
focus-visible:ring-2 focus-visible:ring-[#C06D5B]/50 focus:bg-[#F2BEB6]
px-5 py-2 text-black/85 font-semibold tracking-wide
px-5 py-2 text-black/85 font-medium tracking-wide
text-[13px]
active:bg-[#B86D63] active:scale-95 transition-all cursor-pointer
"
Expand Down
Loading