add drive button

This commit is contained in:
Arjun 2026-05-27 22:45:24 +05:30
parent c548f6bd51
commit b1e597ee3c
3 changed files with 78 additions and 51 deletions

View file

@ -5,7 +5,7 @@ import { RunEvent, ListRunsResponse } from '@x/shared/src/runs.js';
import type { LanguageModelUsage, ToolUIPart } from 'ai';
import './App.css'
import z from 'zod';
import { Bug, CheckIcon, LoaderIcon, PanelLeftIcon, ArrowRight, MessageSquare, ChevronLeftIcon, ChevronRightIcon, MoreHorizontal, Plus, HistoryIcon, DownloadIcon, UploadCloud } from 'lucide-react';
import { Bug, CheckIcon, LoaderIcon, PanelLeftIcon, ArrowRight, MessageSquare, ChevronLeftIcon, ChevronRightIcon, MoreHorizontal, Plus, HistoryIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { MarkdownEditor, type MarkdownEditorHandle } from './components/markdown-editor';
import { ChatSidebar } from './components/chat-sidebar';
@ -1431,8 +1431,8 @@ function App() {
setEditorContent(markdown)
}, [setEditorCacheForPath])
const syncGoogleDocDown = useCallback(async () => {
const path = selectedPathRef.current
const syncGoogleDocDown = useCallback(async (targetPath?: string) => {
const path = targetPath ?? selectedPathRef.current
if (!path || !path.startsWith('knowledge/') || !path.endsWith('.md')) return
setGoogleDocSyncDirection('down')
@ -1450,8 +1450,8 @@ function App() {
}
}, [markRecentLocalMarkdownWrite, reloadMarkdownFileIntoEditor])
const syncGoogleDocUp = useCallback(async () => {
const path = selectedPathRef.current
const syncGoogleDocUp = useCallback(async (targetPath?: string) => {
const path = targetPath ?? selectedPathRef.current
if (!path || !path.startsWith('knowledge/') || !path.endsWith('.md')) return
const body = editorContentByPathRef.current.get(path) ?? editorContentRef.current
@ -5368,10 +5368,6 @@ function App() {
}
return markdownTabs
}, [fileTabs, selectedPath])
const selectedLinkedGoogleDoc = React.useMemo(() => {
if (!selectedPath?.startsWith('knowledge/') || !selectedPath.endsWith('.md')) return null
return parseLinkedGoogleDocFrontmatter(frontmatterByPathRef.current.get(selectedPath) ?? null)
}, [selectedPath, editorContent, editorContentByPath])
return (
<TooltipProvider delayDuration={0}>
<SidebarSectionProvider defaultSection="tasks" onSectionChange={(section) => {
@ -5481,46 +5477,6 @@ function App() {
) : null}
</div>
)}
{selectedLinkedGoogleDoc && (
<>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => { void syncGoogleDocDown() }}
disabled={googleDocSyncDirection !== null || isSaving || Boolean(viewingHistoricalVersion)}
className="titlebar-no-drag flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground transition-colors self-center shrink-0 disabled:pointer-events-none disabled:opacity-50"
aria-label="Sync down from Google Doc"
>
{googleDocSyncDirection === 'down' ? (
<LoaderIcon className="size-4 animate-spin" />
) : (
<DownloadIcon className="size-4" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="bottom">Sync down from Google Doc</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => { void syncGoogleDocUp() }}
disabled={googleDocSyncDirection !== null || isSaving || Boolean(viewingHistoricalVersion)}
className="titlebar-no-drag flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground transition-colors self-center shrink-0 disabled:pointer-events-none disabled:opacity-50"
aria-label="Sync up to Google Doc"
>
{googleDocSyncDirection === 'up' ? (
<LoaderIcon className="size-4 animate-spin" />
) : (
<UploadCloud className="size-4" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="bottom">Sync up to Google Doc</TooltipContent>
</Tooltip>
</>
)}
{selectedPath && selectedPath.startsWith('knowledge/') && selectedPath.endsWith('.md') && (
<Tooltip>
<TooltipTrigger asChild>
@ -5794,6 +5750,8 @@ function App() {
? tab.id === activeFileTabId || tab.path === selectedPath
: tab.path === selectedPath
const isViewingHistory = viewingHistoricalVersion && isActive && versionHistoryPath === tab.path
const tabFrontmatter = frontmatterByPathRef.current.get(tab.path) ?? null
const linkedGoogleDoc = parseLinkedGoogleDocFrontmatter(tabFrontmatter)
const tabContent = isViewingHistory
? viewingHistoricalVersion.content
: editorContentByPath[tab.path]
@ -5824,7 +5782,7 @@ function App() {
wikiLinks={wikiLinkConfig}
onImageUpload={handleImageUpload}
editorSessionKey={editorSessionByTabId[tab.id] ?? 0}
frontmatter={frontmatterByPathRef.current.get(tab.path) ?? null}
frontmatter={tabFrontmatter}
onFrontmatterChange={(newRaw) => {
frontmatterByPathRef.current.set(tab.path, newRaw)
// Write updated frontmatter to disk immediately
@ -5846,6 +5804,17 @@ function App() {
}
}}
editable={!isViewingHistory}
googleDoc={linkedGoogleDoc && !isViewingHistory ? {
title: linkedGoogleDoc.title,
isSyncing: isActive ? googleDocSyncDirection : null,
onOpen: () => {
if (linkedGoogleDoc.url) {
window.open(linkedGoogleDoc.url, '_blank')
}
},
onSyncDown: () => { void syncGoogleDocDown(tab.path) },
onSyncUp: () => { void syncGoogleDocUp(tab.path) },
} : undefined}
onExport={async (format) => {
const markdown = tabContent
const title = getBaseName(tab.path)

View file

@ -26,15 +26,21 @@ import {
Trash2Icon,
ImageIcon,
DownloadIcon,
ChevronDownIcon,
FileTextIcon,
FileIcon,
FileTypeIcon,
CloudDownloadIcon,
LoaderIcon,
TriangleIcon,
UploadCloudIcon,
Radio,
} from 'lucide-react'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
@ -45,6 +51,15 @@ interface EditorToolbarProps {
onExport?: (format: 'md' | 'pdf' | 'docx') => void
onOpenLiveNote?: () => void
liveState?: LivePillState
googleDoc?: GoogleDocToolbarState
}
export interface GoogleDocToolbarState {
title: string
isSyncing?: 'up' | 'down' | null
onOpen: () => void
onSyncDown: () => void
onSyncUp: () => void
}
export type LivePillVariant = 'passive' | 'idle' | 'running' | 'error'
@ -67,6 +82,7 @@ export function EditorToolbar({
onExport,
onOpenLiveNote,
liveState,
googleDoc,
}: EditorToolbarProps) {
const [linkUrl, setLinkUrl] = useState('')
const [isLinkPopoverOpen, setIsLinkPopoverOpen] = useState(false)
@ -404,6 +420,45 @@ export function EditorToolbar({
</>
)}
{googleDoc && (
<>
<div className="separator" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-7 gap-1 px-2"
title={`Google Doc: ${googleDoc.title}`}
disabled={Boolean(googleDoc.isSyncing)}
>
{googleDoc.isSyncing ? (
<LoaderIcon className="size-4 animate-spin" />
) : (
<TriangleIcon className="size-4" />
)}
<ChevronDownIcon className="size-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={googleDoc.onOpen}>
<TriangleIcon className="size-4 mr-2" />
Open Google Doc
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={googleDoc.onSyncDown} disabled={Boolean(googleDoc.isSyncing)}>
<CloudDownloadIcon className="size-4 mr-2" />
Sync down
</DropdownMenuItem>
<DropdownMenuItem onClick={googleDoc.onSyncUp} disabled={Boolean(googleDoc.isSyncing)}>
<UploadCloudIcon className="size-4 mr-2" />
Sync up
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
)}
{/* Live Note pill — pushed to far right */}
{onOpenLiveNote && liveState && (
<button

View file

@ -292,7 +292,7 @@ function computeWithinBlockOffset(
return 0
}
}
import { EditorToolbar, type LivePillState } from './editor-toolbar'
import { EditorToolbar, type GoogleDocToolbarState, type LivePillState } from './editor-toolbar'
import { useLiveNoteForPath } from '@/hooks/use-live-note-for-path'
import { formatRelativeTime } from '@/lib/relative-time'
import { FrontmatterProperties } from './frontmatter-properties'
@ -448,6 +448,7 @@ interface MarkdownEditorProps {
onFrontmatterChange?: (raw: string | null) => void
onExport?: (format: 'md' | 'pdf' | 'docx') => void
notePath?: string
googleDoc?: GoogleDocToolbarState
}
type WikiLinkMatch = {
@ -645,6 +646,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorPro
onFrontmatterChange,
onExport,
notePath,
googleDoc,
}, ref) {
const isInternalUpdate = useRef(false)
const wrapperRef = useRef<HTMLDivElement>(null)
@ -1628,6 +1630,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorPro
onSelectionHighlight={setSelectionHighlight}
onImageUpload={handleImageUploadWithPlaceholder}
onExport={onExport}
googleDoc={googleDoc}
onOpenLiveNote={notePath ? () => {
window.dispatchEvent(new CustomEvent('rowboat:open-live-note-panel', {
detail: { filePath: notePath },