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
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@hookform/resolvers": "^5.2.2",
"@repo/backend": "workspace:*",
"@repo/editor": "workspace:*",
"@repo/embed-pdf": "workspace:*",
"@repo/lib": "workspace:*",
"@repo/sdk": "workspace:*",
"@repo/shared": "workspace:*",
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ html > body.fullscreen {
}
}

body:has([contenteditable="true"]) {
pointer-events: auto !important;
}

@media (pointer: coarse) and (max-width: 40rem) {
body:has([contenteditable="true"]) [data-radix-popper-content-wrapper]:has(>[data-slot="context-menu-content"]) {
margin-top: calc(var(--spacing) * 6);
}
}

@media print {
* {
-webkit-print-color-adjust: exact;
Expand Down
9 changes: 2 additions & 7 deletions apps/web/src/components/AttachmentViewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useMemo, useState } from 'react';
import { Download, FileQuestion, AlertCircle } from '@repo/ui/components/icons';
import { Button } from '@repo/ui/components/button';
import { cn } from '@repo/ui/lib/utils';
import { PDFViewer } from '@repo/embed-pdf/viewer';

type MediaType = 'image' | 'video' | 'audio' | 'pdf' | 'unknown';

Expand Down Expand Up @@ -130,13 +131,7 @@ export function AttachmentViewer({
case 'pdf':
return (
<div className={cn('flex flex-col h-full', className)}>
<iframe
src={url}
title={name}
className="flex-1 w-full min-h-[80vh] border-0"
onError={handleError}
onLoad={handleLoad}
/>
<PDFViewer url={url} />
</div>
);

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { getHomeAllDocumentsQueryOptions } from '@/queries/documents';
import { useEffect } from 'react';

export const HomePage = () => {
const homeSorts = useSelector((state) => state.homeSorts);
const homeSorts = useSelector((state) => state.ui.homeSorts);
const { setHomeSorts } = useActions();
const { time } = useTime();
const user = useSelector((state) => state.user);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/Layout/SplitPaneRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ export function SplitPaneRouter({ tab, type }: SplitPaneRouterProps) {
const isSyncingRef = useRef(false);

// Created once per mount (component remounts when tab.id changes via key).
// Dynamic values (session, etc.) are passed via the RouterProvider context
// prop below so they propagate on every render without recreating the router.
const [splitRouter] = useState(() =>
createRouter({
routeTree,
Expand All @@ -62,6 +60,7 @@ export function SplitPaneRouter({ tab, type }: SplitPaneRouterProps) {
session: { data: null, isLoading: true },
isSplitPane: true as const,
splitPaneType: type,
tabId: tab.id,
},
}),
);
Expand Down Expand Up @@ -110,6 +109,7 @@ export function SplitPaneRouter({ tab, type }: SplitPaneRouterProps) {
session: { data: session, isLoading: isPending || isRefetching },
isSplitPane: true as const,
splitPaneType: type,
tabId: tab.id,
}}
/>
);
Expand Down
20 changes: 18 additions & 2 deletions apps/web/src/components/Layout/app-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import { Link } from '@tanstack/react-router';

export const AppHeader = () => {
const handleResize = () => {
const keyboardInsetHeight =
window.innerHeight - (window.visualViewport?.height || window.innerHeight);
const keyboardInsetHeight = Math.ceil(
window.innerHeight - (window.visualViewport?.height || window.innerHeight),
);
document.documentElement.style.setProperty(
'--keyboard-inset-height',
`${keyboardInsetHeight}px`,
Expand All @@ -29,6 +30,21 @@ export const AppHeader = () => {
};
}, []);

const handleGeometryChange = (event: any) => {
const { height } = event.target.boundingRect;
document.documentElement.style.setProperty('--keyboard-inset-height', `${height}px`);
};

useEffect(() => {
if (!('virtualKeyboard' in navigator)) return;
const virtualKeyboard = navigator.virtualKeyboard as any;
virtualKeyboard.overlaysContent = true;
virtualKeyboard.addEventListener('geometrychange', handleGeometryChange);
return () => {
virtualKeyboard.removeEventListener('geometrychange', handleGeometryChange);
};
}, []);

return (
<>
<header
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ export function ContainerDocumentItem({

// Clipboard state management
const { setDocumentsClipboard } = useActions();
const clipboardDocument = useSelector((state) => state.documentsClipboard);
const clipboardDocument = useSelector((state) => state.wordy.documentsClipboard);
const isCutThisItem =
clipboardDocument?.type === 'move' && clipboardDocument.document.id === document.id;
const inlineCreate = useSelector((state) => state.inlineCreate);
const inlineCreate = useSelector((state) => state.wordy.inlineCreate);
const { clearInlineCreate } = useActions();

const isPlaceholder = document.id === 'new-doc';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useSelector, useActions } from '@/store';
import { SidebarMenuButton } from '@repo/ui/components/sidebar';

export function CreateDocumentSection() {
const isHidden = useSelector((state) => state.createDocumentSectionHidden);
const isHidden = useSelector((state) => state.ui.createDocumentSectionHidden);
const { setCreateDocumentSectionHidden } = useActions();
const [isAlertOpen, setIsAlertOpen] = React.useState(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ type NavDocumentsContextMenuProps = {

export function NavDocumentsContextMenu({ children }: NavDocumentsContextMenuProps) {
// Keep selector if needed in future; currently not used
// const activeSpace = useSelector((state) => state.activeSpace);
// const activeSpace = useSelector((state) => state.wordy.activeSpace);
const queryClient = useQueryClient();
const navigate = useNavigate();
const { setInlineCreate } = useActions();
const { isMobile: isMobileSidebar, setOpenMobile } = useSidebar();
const clipboardDocument = useSelector((state) => state.documentsClipboard);
const activeSpace = useSelector((state) => state.activeSpace[state.tabs.activePane]);
const clipboardDocument = useSelector((state) => state.wordy.documentsClipboard);
const activeSpace = useSelector((state) => state.wordy.activeSpace[state.tabs.activePane]);
// Mutations for paste functionality
const copyDocumentMutation = useCopyDocumentMutation({
spaceId: activeSpace?.id ?? '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ import {
CopyPlus,
SquarePen,
FileOutput,
SquareArrowOutUpRight,
SquareSplitVerticalIcon,
MousePointerClickIcon,
SquareSplitHorizontalIcon,
} from '@repo/ui/components/icons';
import { SidebarMenuButton, SidebarMenuItem, useSidebar } from '@repo/ui/components/sidebar';
import { cachedDocuments } from '@/queries/caches/documents';
Expand All @@ -40,9 +44,12 @@ import {
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from '@repo/ui/components/context-menu';
import { dispatchEscapeKey } from '@/utils/keyboard';
import { IS_APPLE } from '@repo/shared/environment';
import { useMediaQuery } from '@repo/ui/hooks/use-media-query';

interface RegularDocumentItemProps {
document: DocumentData;
Expand Down Expand Up @@ -75,7 +82,7 @@ export function RegularDocumentItem({
const { pathname } = useLocation();
const activeDocumentHandle = decodeURIComponent(pathname.split('/').pop() ?? '');
const isActive = activeDocumentHandle === document.handle;
const clipboardDocument = useSelector((state) => state.documentsClipboard);
const clipboardDocument = useSelector((state) => state.wordy.documentsClipboard);
const isCutThisItem =
clipboardDocument?.type === 'move' && clipboardDocument.document.id === document.id;
const isCreating = document.id === document.clientId;
Expand Down Expand Up @@ -104,7 +111,7 @@ export function RegularDocumentItem({
});

// Clipboard state management
const { setDocumentsClipboard } = useActions();
const { setDocumentsClipboard, openTab } = useActions();
const isPlaceholder = document.id === 'new-doc' && !document.isContainer;

const removePlaceholderHandler = React.useCallback(() => {
Expand Down Expand Up @@ -236,6 +243,8 @@ export function RegularDocumentItem({
};
}, [isCreating]);

const isPortrait = useMediaQuery('(orientation: portrait)');

// Return DocumentNameInput directly for placeholder or renaming mode
if (isPlaceholder || isRenaming) {
return (
Expand Down Expand Up @@ -343,6 +352,42 @@ export function RegularDocumentItem({
}}
ref={contextMenuContentRef}
>
<ContextMenuItem
onSelect={() => {
openTab({
pathname: `/view/${document.handle ?? document.id}`,
search: document.handle ? undefined : { id: true },
});
}}
>
<SquareArrowOutUpRight className="size-4 group-hover:text-foreground" />
Open in new tab
<ContextMenuShortcut className="flex">
{IS_APPLE ? '⌘' : 'Ctrl'}
<MousePointerClickIcon />
</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
openTab({
pathname: `/view/${document.handle ?? document.id}`,
search: document.handle ? undefined : { id: true },
pane: 'opposite',
});
}}
>
{isPortrait ? (
<SquareSplitVerticalIcon className="size-4 group-hover:text-foreground" />
) : (
<SquareSplitHorizontalIcon className="size-4 group-hover:text-foreground" />
)}
Open to the side
<ContextMenuShortcut className="flex">
{IS_APPLE ? '⇧' : 'Shift'}
<MousePointerClickIcon />
</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSeparator />
{isIconPickerOpen ? (
<>
<IconPicker
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/Layout/document-tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { getSiblings, sortByPosition, generatePositionKeyBetween } from '@repo/l
import { ScrollArea } from '@repo/ui/components/scroll-area';

export function DocumentTree() {
const activeSpace = useSelector((state) => state.activeSpace[state.tabs.activePane]);
const activeSpace = useSelector((state) => state.wordy.activeSpace[state.tabs.activePane]);
const spaceId = activeSpace?.id ?? '';
const { data: documentsData } = useQuery(getAllDocumentsQueryOptions(spaceId!));

Expand All @@ -42,7 +42,7 @@ export function DocumentTree() {
} = useDocumentTree(documentsWithPlaceholder);

const rootDocuments = documentsTree?.children ?? [];
const inlineCreate = useSelector((s) => s.inlineCreate);
const inlineCreate = useSelector((state) => state.wordy.inlineCreate);
const { clearInlineCreate } = useActions();

const insertPlaceholder = React.useCallback(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Layout/nav-documents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Link } from '@tanstack/react-router';
import { useSelector } from '@/store';

export function NavDocuments(props: React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
const activeSpace = useSelector((state) => state.activeSpace[state.tabs.activePane]);
const activeSpace = useSelector((state) => state.wordy.activeSpace[state.tabs.activePane]);
return (
<SidebarGroup
{...props}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ export function SpaceContextMenu({
<DropdownMenuSeparator />
<DropdownMenuItem className="group" onClick={() => onRemoveFromFavorites(space)}>
<Star
className={cn('mr-2 h-4 w-4 fill-current group-hover:text-foreground', {
'fill-amber-400': isFavorite,
})}
className={cn(
'mr-2 h-4 w-4 fill-current group-hover:text-foreground',
isFavorite && 'fill-amber-400',
)}
/>
Remove from Favorites
</DropdownMenuItem>
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/components/Layout/space-switcher/SpaceItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,14 @@ function ContainerSpaceItem({
}) {
const navigate = useNavigate();
const queryClient = useQueryClient();
const activeSpace = useSelector((state) => state.activeSpace[state.tabs.activePane]);
const activeSpace = useSelector((state) => state.wordy.activeSpace[state.tabs.activePane]);
const [isIconPickerOpen, setIsIconPickerOpen] = React.useState(false);
const [isRenaming, setIsRenaming] = React.useState(false);
const isCreating = space.id === space.clientId;
const { updateSpaceIcon: updateIcon } = useUpdateSpaceIconMutation();
// Favorites not used in container context menu
const { setSpacesClipboard } = useActions();
const clipboardSpace = useSelector((state) => state.spacesClipboard);
const clipboardSpace = useSelector((state) => state.wordy.spacesClipboard);
const canPaste =
!!clipboardSpace &&
(clipboardSpace.type === 'copy' || clipboardSpace.type === 'move') &&
Expand Down Expand Up @@ -885,7 +885,7 @@ function RegularSpaceItem({
const { updateSpaceIcon: updateIcon } = useUpdateSpaceIconMutation();
const { addToFavorites, removeFromFavorites } = useSpaceFavoritesMutation();
const isActive = useSelector(
(state) => state.activeSpace[state.tabs.activePane]?.id === space.id,
(state) => state.wordy.activeSpace[state.tabs.activePane]?.id === space.id,
);
const isCreating = space.id === (space.clientId ?? '');
const { setSpacesClipboard } = useActions();
Expand All @@ -894,7 +894,7 @@ function RegularSpaceItem({
space: space as any,
});
const contextMenuContentRef = React.useRef<HTMLDivElement>(null);
const clipboardSpaceRegular = useSelector((state) => state.spacesClipboard);
const clipboardSpaceRegular = useSelector((state) => state.wordy.spacesClipboard);
const isCutThisSpaceRegular =
clipboardSpaceRegular?.type === 'move' && clipboardSpaceRegular.space.id === space.id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function SpaceSwitcher() {

const rootSpaces = spacesTree?.children ?? [];

const clipboardSpace = useSelector((state) => state.spacesClipboard);
const clipboardSpace = useSelector((state) => state.wordy.spacesClipboard);
// Root-level paste mutations
const copySpaceMutation = useCopySpaceMutation(null);
const moveSpaceMutation = useMoveSpaceMutation(null);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Layout/space-switcher/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function useSpaceSwitcher(overrideSpaces?: ListSpaceResult): UseSpaceSwit
const { data, isLoading } = useQuery(getAllSpacesQueryOptions);
const spaces = overrideSpaces ?? data ?? [];
const spacesTree = React.useMemo(() => arrayToTree(spaces as Space[]), [spaces]);
const activeSpace = useSelector((state) => state.activeSpace[state.tabs.activePane]);
const activeSpace = useSelector((state) => state.wordy.activeSpace[state.tabs.activePane]);

const [expandedSpaces, setExpandedSpaces] = React.useState<Set<string>>(new Set([]));
const [openMenuSpaceId, setOpenMenuSpaceId] = React.useState<string | null>(null);
Expand Down
22 changes: 16 additions & 6 deletions apps/web/src/components/Layout/tabs/PaneContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useUrlDropOnPaneContent } from './useUrlDropOnPaneContent';
import { useCallback, useEffect, useState, type ReactNode } from 'react';
import { useMediaQuery } from '@repo/ui/hooks/use-media-query';
import { ScrollArea } from '@repo/ui/components/scroll-area';
import { hasUrlInDataTransfer } from './utils';
import { getLocationFromDragEvent } from './utils';

export const PANE_CONTENT_SPLIT_PRIMARY = 'pane-content-split-primary';
export const PANE_CONTENT_SPLIT_SECONDARY = 'pane-content-split-secondary';
Expand All @@ -28,7 +28,8 @@ export interface PaneContentProps {
* move others to secondary; right = move tab to secondary.
*/
export function PaneContent({ pane, children, className }: PaneContentProps) {
const hasSplit = useSelector((state) => state.tabs.secondaryTabIds.length > 0);
const isPortrait = useMediaQuery('(orientation: portrait)');
const hasSplit = useSelector((state) => state.tabs.paneTabIds.secondary.length > 0);
const isEditorTab = useSelector((state) =>
state.tabs.tabList
.find((t) => t.id === state.tabs.activeTabId[pane])
Expand All @@ -51,8 +52,8 @@ export function PaneContent({ pane, children, className }: PaneContentProps) {
const [isLinkDragging, setIsLinkDragging] = useState(false);
const [isShiftHeld, setIsShiftHeld] = useState(false);
const handleDrag = useCallback((e: DragEvent) => {
const hasLink = hasUrlInDataTransfer(e.dataTransfer);
setIsLinkDragging(hasLink);
const location = getLocationFromDragEvent(e);
setIsLinkDragging(location !== null);
setIsShiftHeld(e.shiftKey);
}, []);
const handleDragEnd = useCallback(() => {
Expand Down Expand Up @@ -82,8 +83,17 @@ export function PaneContent({ pane, children, className }: PaneContentProps) {
(isTabDropTarget || (isLinkDragging && (!isShiftHeld || !isEditorTab)));

return (
<div ref={setNodeRef} className={cn('flex-1 min-h-0 relative', className)}>
<ScrollArea className="h-full [&>[data-radix-scroll-area-viewport]>div[style]]:block!">
<div
ref={setNodeRef}
className={cn(
'flex-1 min-h-0 relative',
{
'pb-(--keyboard-inset-height)': !hasSplit || !isPortrait || pane === 'secondary',
},
className,
)}
>
<ScrollArea className="h-full *:data-radix-scroll-area-viewport:*:block! *:data-radix-scroll-area-viewport:*:h-full">
{children}
</ScrollArea>
{showTabSplitZones && <TabSplitDropZones />}
Expand Down
Loading