mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-09 19:45:17 +02:00
add drive button
This commit is contained in:
parent
c548f6bd51
commit
b1e597ee3c
3 changed files with 78 additions and 51 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue