/// import { Cell, computed, Default, handler, ifElse, NAME, navigateTo, pattern, UI, } from "commontools"; // Import Google Auth utility import { createGoogleAuth, type ScopeKey, } from "../core/util/google-auth-manager.tsx"; // Import markdown conversion utilities import { convertDocToMarkdown, extractDocTitle, } from "../core/util/google-docs-markdown.ts"; // Import Google Docs client import { extractFileId, type GoogleComment, GoogleDocsClient, } from "../core/util/google-docs-client.ts"; // Import Note pattern for "Save as Note" feature // NOTE: This requires deploying with --root packages/patterns to resolve the import import Note from "../../notes/note.tsx"; // ============================================================================= // SETUP REQUIREMENTS // ============================================================================= // // This pattern requires Google OAuth with specific scopes and APIs enabled: // // 1. GOOGLE AUTH CHARM // - Create and favorite a Google Auth piece with these scopes enabled: // - Drive (read/write files & comments) - for fetching comments // - Docs (read document content) - for fetching document content // // 2. GOOGLE CLOUD CONSOLE // The OAuth project must have these APIs enabled: // - Google Drive API (usually enabled by default) // - Google Docs API (must be explicitly enabled) // // ============================================================================= // ============================================================================= // Types // ============================================================================= interface Input { docUrl?: Cell>; markdown?: Cell>; docTitle?: Cell>; isFetching?: Cell>; lastError?: Cell>; includeComments?: Cell>; embedImages?: Cell>; } /** Google Docs Markdown Importer. Import Google Docs as Markdown with comments. #googleDocsImporter */ interface Output { docUrl: string; markdown: string; docTitle: string; } // ============================================================================= // Handlers // ============================================================================= // Fetch document and convert to markdown const importDocument = handler< unknown, { docUrl: Cell; auth: Cell; markdown: Cell; docTitle: Cell; isFetching: Cell; lastError: Cell; includeComments: Cell; embedImages: Cell; } >( async ( _, { docUrl, auth, markdown, docTitle, isFetching, lastError, includeComments, embedImages, }, ) => { const url = docUrl.get(); if (!url) { lastError.set("Please enter a Google Doc URL"); return; } const fileId = extractFileId(url); if (!fileId) { lastError.set("Could not extract file ID from URL"); return; } const authData = auth.get() as { token?: string } | null; const token = authData?.token; if (!token) { lastError.set("Please authenticate with Google first"); return; } isFetching.set(true); lastError.set(null); try { // Create client with auth Cell for automatic token refresh const client = new GoogleDocsClient(auth as Cell, { debugMode: true, }); // Fetch document content const doc = await client.getDocument(fileId); const title = extractDocTitle(doc); docTitle.set(title); // Fetch comments if enabled (client handles pagination and filtering) let comments: GoogleComment[] = []; if (includeComments.get()) { try { comments = await client.listComments( fileId, false, /* includeResolved */ ); } catch (e) { console.warn("[importDocument] Could not fetch comments:", e); // Non-fatal - we can still convert without comments } } // Convert to markdown const md = await convertDocToMarkdown(doc, comments, { includeComments: includeComments.get(), embedImages: embedImages.get(), token, }); markdown.set(md); } catch (e: unknown) { console.error("[importDocument] Error:", e); const errorMessage = e instanceof Error ? e.message : "Failed to import document"; lastError.set(errorMessage); } finally { isFetching.set(false); } }, ); // Save as Note piece const saveAsNote = handler< unknown, { markdown: Cell; docTitle: Cell } >((_, { markdown, docTitle }) => { const md = markdown.get(); const title = docTitle.get() || "Imported Document"; if (!md) { return; } // Create and navigate to a new Note piece with the imported content return navigateTo(Note({ title, content: md })); }); // Toggle include comments const toggleComments = handler }>( (_, { includeComments }) => { includeComments.set(!includeComments.get()); }, ); // Toggle embed images const toggleEmbedImages = handler }>( (_, { embedImages }) => { embedImages.set(!embedImages.get()); }, ); // ============================================================================= // Pattern // ============================================================================= export default pattern( ( { docUrl, markdown, docTitle, isFetching, lastError, includeComments, embedImages, }, ) => { // Save cell references const docUrlCell = docUrl; const markdownCell = markdown; const docTitleCell = docTitle; const isFetchingCell = isFetching; const lastErrorCell = lastError; const includeCommentsCell = includeComments; const embedImagesCell = embedImages; // Auth via createGoogleAuth utility (requires Drive and Docs scopes) const { auth, authInfo, fullUI: authFullUI, isReady: isAuthenticated, } = createGoogleAuth({ requiredScopes: ["drive", "docs"] as ScopeKey[], }); // Has markdown content const hasMarkdown = computed(() => { const md = markdownCell.get(); return md && md.trim().length > 0; }); // Has error const hasError = computed(() => !!lastErrorCell.get()); // Computed name based on doc title const pieceName = computed(() => { const title = docTitleCell.get(); return title ? `Import: ${title}` : "Google Docs Importer"; }); return { [NAME]: pieceName, [UI]: ( {/* Header */} Google Docs Markdown Importer {authInfo.statusText} {/* Main content */} {/* Auth UI */} {authFullUI} {/* Document URL input */} {ifElse( isAuthenticated, {ifElse( isFetchingCell, Importing... , "Import", )} , null, )} {/* Options */} {/* Error display */} {ifElse( hasError,
{lastErrorCell}
, null, )}
{/* Markdown preview */} {ifElse( hasMarkdown, Preview: {docTitleCell} Copy to Clipboard Save as Note
{markdownCell}
,
{ifElse( isAuthenticated, "Enter a Google Doc URL and click Import to convert it to Markdown", "Please authenticate with Google to import documents", )}
, )}
), docUrl, markdown, docTitle, }; }, );