diff --git a/api/src/main/resources/db/migration/V4__document_trash.sql b/api/src/main/resources/db/migration/V4__document_trash.sql
index aff9a83..07356b6 100644
--- a/api/src/main/resources/db/migration/V4__document_trash.sql
+++ b/api/src/main/resources/db/migration/V4__document_trash.sql
@@ -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;
diff --git a/web/components/DocToolbar.tsx b/web/components/DocToolbar.tsx
index f144f9c..de86627 100644
--- a/web/components/DocToolbar.tsx
+++ b/web/components/DocToolbar.tsx
@@ -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({
@@ -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);
@@ -152,6 +158,36 @@ export function DocToolbar({
{/* ── Top-right toolbar ── */}
+ {showTrashNotice && onRestore && (
+
+ This document is in the trash.
+
+ it to make edits.
+
+ )}
+
{shouldShowGuestNotice && (
>({});
- const isGuestSharedView = !isAuthenticated && isSharedDocument && accessLevel === 'VIEW';
+ const isGuestSharedView = !isAuthenticated && accessLevel === 'VIEW';
const isOffline = !isOnline;
const { pendingEdits } = useYjsPersistence(
documentId,
@@ -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);
@@ -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}
diff --git a/web/components/PopupMenuItem.tsx b/web/components/PopupMenuItem.tsx
index 24454f1..9fc1d9b 100644
--- a/web/components/PopupMenuItem.tsx
+++ b/web/components/PopupMenuItem.tsx
@@ -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';
diff --git a/web/components/SharePanel.tsx b/web/components/SharePanel.tsx
index 1e31f2a..9cde033 100644
--- a/web/components/SharePanel.tsx
+++ b/web/components/SharePanel.tsx
@@ -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
"
diff --git a/web/components/Sidebar.tsx b/web/components/Sidebar.tsx
index f65ea93..205a26a 100644
--- a/web/components/Sidebar.tsx
+++ b/web/components/Sidebar.tsx
@@ -185,7 +185,7 @@ function SidebarDocumentSection({
{[1, 2, 3].map((i) => (
))}
@@ -207,7 +207,7 @@ function SidebarDocumentSection({
>