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
78 changes: 11 additions & 67 deletions src/components/sidebar-file-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,8 @@ import { getDocumentTitle, addCopyToTitle } from "@/lib/document-utils"
import { exportDocument, saveDocumentAs, type ExportAsset } from "@/lib/export"
import type { MarkdownEditorRef } from "@/editor/editor"

import {
findThemeByName,
getThemeName,
getPresetName,
findPresetByName,
getThemePresets,
findPresetByAppearance,
type LoadedThemes,
} from "@/lib/document-theme"
import { buildPrintableHtml, openPrintWindow } from "@/lib/pdf-export"
import { Marked } from "marked"
import { type LoadedThemes } from "@/lib/document-theme"
import { printToPdf } from "@/lib/print-to-pdf"

export { SidebarFileMenu }

Expand Down Expand Up @@ -109,6 +100,13 @@ function SidebarFileMenu({ doc, editor, me, spaceId }: SidebarFileMenuProps) {
let isAdmin = docGroup?.myRole() === "admin"
let isPinned = parseFrontmatter(content).frontmatter?.pinned === true
let isPresentation = getPresentationMode(content)
let handlePrintPdf = makePrintPdf(
content,
meWithThemes.$isLoaded ? meWithThemes.root?.themes : undefined,
meWithThemes.$isLoaded
? (meWithThemes.root?.settings?.defaultPreviewTheme ?? null)
: null,
)

return (
<>
Expand Down Expand Up @@ -190,15 +188,7 @@ function SidebarFileMenu({ doc, editor, me, spaceId }: SidebarFileMenuProps) {
Save as...
<DropdownMenuShortcut>{modKey}S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem
onClick={makePrintPdf(
content,
meWithThemes.$isLoaded ? meWithThemes.root?.themes : undefined,
meWithThemes.$isLoaded
? (meWithThemes.root?.settings?.defaultPreviewTheme ?? null)
: null,
)}
>
<DropdownMenuItem onClick={handlePrintPdf}>
Print to PDF
<DropdownMenuShortcut>{modKey}P</DropdownMenuShortcut>
</DropdownMenuItem>
Expand Down Expand Up @@ -518,53 +508,7 @@ function makePrintPdf(
defaultPreviewTheme: string | null,
) {
return async function handlePrintPdf() {
let { body } = parseFrontmatter(content)
let title = getDocumentTitle(content)

// Resolve theme and preset from frontmatter (same logic as useDocumentTheme)
let themeName = getThemeName(content)
let presetName = getPresetName(content)

// Handle "light"/"dark" as appearance-only, not theme names
let isAppearanceOnlyTheme = themeName === "light" || themeName === "dark"
let effectiveThemeName = isAppearanceOnlyTheme ? null : themeName

// Fall back to default preview theme from settings
if (!effectiveThemeName && defaultPreviewTheme) {
effectiveThemeName = defaultPreviewTheme
}

let theme = effectiveThemeName
? findThemeByName(themes ?? null, effectiveThemeName)
: null
let preset = null

if (theme && presetName) {
preset = findPresetByName(theme, presetName)
} else if (theme) {
// PDF always uses light mode
preset = findPresetByAppearance(theme, "light")
if (!preset) {
let presets = getThemePresets(theme)
preset = presets[0] ?? null
}
}

// Render markdown to HTML
let marked = new Marked()
marked.setOptions({ gfm: true, breaks: true })
let htmlContent = await marked.parse(body)

// Build printable HTML with theme styles
let printableHtml = await buildPrintableHtml({
title,
htmlContent,
theme,
preset,
})

// Open print window
openPrintWindow(printableHtml)
await printToPdf({ content, themes, defaultPreviewTheme })
}
}

Expand Down
34 changes: 31 additions & 3 deletions src/lib/editor-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getDocumentTitle } from "@/lib/document-utils"
import { copyDocumentToMyList } from "@/lib/documents"
import { saveDocumentAs } from "@/lib/export"
import { useCoState, useAccount } from "jazz-tools/react"
import { toast } from "sonner"

export {
makeUploadImage,
Expand Down Expand Up @@ -280,8 +281,27 @@ function setupKeyboardShortcuts(opts: {
toggleRight: () => void
toggleFocusMode: () => void
openFind?: () => void
onPrintPdf?: () => void
docWithContent: MaybeDocWithContent
}) {
function downloadCurrentDocument() {
if (!opts.docWithContent?.$isLoaded) return
let title = getDocumentTitle(opts.docWithContent)
saveDocumentAs(opts.docWithContent.content?.toString() ?? "", title)
}

function showAutosaveToast() {
toast("Alkalyte saves automatically", {
description:
"Changes are saved locally and synced to the cloud while you type.",
action: {
label: "Download",
onClick: downloadCurrentDocument,
},
id: "editor-save-shortcut",
})
}

function handleKeyDown(e: KeyboardEvent) {
if (
(e.metaKey || e.ctrlKey) &&
Expand Down Expand Up @@ -320,11 +340,19 @@ function setupKeyboardShortcuts(opts: {
opts.openFind?.()
return
}
if (
(e.metaKey || e.ctrlKey) &&
!e.shiftKey &&
!e.altKey &&
e.key.toLowerCase() === "p"
) {
e.preventDefault()
opts.onPrintPdf?.()
return
}
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
e.preventDefault()
if (!opts.docWithContent?.$isLoaded) return
let title = getDocumentTitle(opts.docWithContent)
saveDocumentAs(opts.docWithContent.content?.toString() ?? "", title)
showAutosaveToast()
}
}

Expand Down
63 changes: 63 additions & 0 deletions src/lib/print-to-pdf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Marked } from "marked"
import { parseFrontmatter } from "@/editor/frontmatter"
import {
findThemeByName,
getThemeName,
getPresetName,
findPresetByName,
getThemePresets,
findPresetByAppearance,
type LoadedThemes,
} from "@/lib/document-theme"
import { getDocumentTitle } from "@/lib/document-utils"
import { buildPrintableHtml, openPrintWindow } from "@/lib/pdf-export"

export { printToPdf }

async function printToPdf(params: {
content: string
themes: LoadedThemes | undefined
defaultPreviewTheme: string | null
}) {
let { content, themes, defaultPreviewTheme } = params
let { body } = parseFrontmatter(content)
let title = getDocumentTitle(content)

let themeName = getThemeName(content)
let presetName = getPresetName(content)

let isAppearanceOnlyTheme = themeName === "light" || themeName === "dark"
let effectiveThemeName = isAppearanceOnlyTheme ? null : themeName

if (!effectiveThemeName && defaultPreviewTheme) {
effectiveThemeName = defaultPreviewTheme
}

let theme = effectiveThemeName
? findThemeByName(themes ?? null, effectiveThemeName)
: null
let preset = null

if (theme && presetName) {
preset = findPresetByName(theme, presetName)
} else if (theme) {
preset = findPresetByAppearance(theme, "light")
if (!preset) {
let presets = getThemePresets(theme)
preset = presets[0] ?? null
}
}

let marked = new Marked()
marked.setOptions({ gfm: true, breaks: true })
let htmlContent = await marked.parse(body)

let printableHtml = await buildPrintableHtml({
title,
htmlContent,
theme,
preset,
})

openPrintWindow(printableHtml)
}
24 changes: 23 additions & 1 deletion src/routes/doc.$id.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import { Button } from "@/components/ui/button"
import { usePWA } from "@/lib/pwa"
import { HelpMenu } from "@/components/help-menu"
import { useTrackLastOpened } from "@/lib/use-track-last-opened"
import { printToPdf } from "@/lib/print-to-pdf"
export { Route }

let Route = createFileRoute("/doc/$id/")({
Expand Down Expand Up @@ -174,6 +175,9 @@ let personalMeResolve = {
root: {
documents: { $each: { content: true } },
settings: true,
themes: {
$each: { css: true, template: true, assets: { $each: { data: true } } },
},
},
} as const

Expand Down Expand Up @@ -322,9 +326,27 @@ function EditorContent({ doc, docId }: EditorContentProps) {
document.documentElement.dataset.focusMode = String(!current)
},
openFind: () => editor.current?.openFind(),
onPrintPdf: () => {
void printToPdf({
content,
themes: me.$isLoaded ? me.root?.themes : undefined,
defaultPreviewTheme: me.$isLoaded
? (me.root?.settings?.defaultPreviewTheme ?? null)
: null,
})
},
docWithContent,
})
}, [navigate, docId, toggleLeft, toggleRight, docWithContent, editor])
}, [
navigate,
docId,
toggleLeft,
toggleRight,
content,
me,
docWithContent,
editor,
])

let allDocs = getPersonalDocs(me)

Expand Down
34 changes: 32 additions & 2 deletions src/routes/doc.$id.preview.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useEffect } from "react"
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"
import { useCoState } from "jazz-tools/react"
import { useCoState, useAccount } from "jazz-tools/react"
import { type ResolveQuery } from "jazz-tools"
import { Document } from "@/schema"
import { Document, UserAccount } from "@/schema"
import { getDocumentTitle } from "@/lib/document-utils"
import { altModKey } from "@/lib/platform"
import { EllipsisIcon, Pencil } from "lucide-react"
Expand All @@ -27,6 +28,7 @@ import {
useDocTitles,
type ResolvedDoc,
} from "@/lib/doc-resolver"
import { printToPdf } from "@/lib/print-to-pdf"

export { Route }

Expand All @@ -35,6 +37,15 @@ let resolve = {
assets: { $each: { image: true } },
} as const satisfies ResolveQuery<typeof Document>

let themesResolve = {
root: {
settings: true,
themes: {
$each: { css: true, template: true, assets: { $each: { data: true } } },
},
},
} as const

let Route = createFileRoute("/doc/$id/preview")({
loader: async ({ params }) => {
let doc = await Document.load(params.id, {
Expand Down Expand Up @@ -68,6 +79,7 @@ function PreviewPage() {
let navigate = useNavigate()

let subscribedDoc = useCoState(Document, id, { resolve })
let meWithThemes = useAccount(UserAccount, { resolve: themesResolve })

// Extract content for wikilinks (use loader data as fallback, empty if neither)
let content =
Expand All @@ -76,6 +88,24 @@ function PreviewPage() {
let wikilinkIds = parseWikiLinks(content).map(w => w.id)
let wikilinkCache = useDocTitles(wikilinkIds, data.wikilinkCache)

useEffect(() => {
function handleKeyDown(e: KeyboardEvent) {
if (!(e.metaKey || e.ctrlKey) || e.shiftKey || e.altKey) return
if (e.key.toLowerCase() !== "p") return
e.preventDefault()
void printToPdf({
content,
themes: meWithThemes.$isLoaded ? meWithThemes.root?.themes : undefined,
defaultPreviewTheme: meWithThemes.$isLoaded
? (meWithThemes.root?.settings?.defaultPreviewTheme ?? null)
: null,
})
}

document.addEventListener("keydown", handleKeyDown)
return () => document.removeEventListener("keydown", handleKeyDown)
}, [content, meWithThemes])

// Error states from loader
if (!data.doc) {
if (data.loadingState === "unauthorized") return <DocumentUnauthorized />
Expand Down
24 changes: 23 additions & 1 deletion src/routes/spaces.$spaceId.doc.$id.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import { Button } from "@/components/ui/button"
import { usePWA } from "@/lib/pwa"
import { HelpMenu } from "@/components/help-menu"
import { useTrackLastOpened } from "@/lib/use-track-last-opened"
import { printToPdf } from "@/lib/print-to-pdf"

export { Route }

Expand Down Expand Up @@ -339,9 +340,27 @@ function SpaceEditorContent({
document.documentElement.dataset.focusMode = String(!current)
},
openFind: () => editor.current?.openFind(),
onPrintPdf: () => {
void printToPdf({
content,
themes: me.$isLoaded ? me.root?.themes : undefined,
defaultPreviewTheme: me.$isLoaded
? (me.root?.settings?.defaultPreviewTheme ?? null)
: null,
})
},
docWithContent,
})
}, [navigate, docId, toggleLeft, toggleRight, docWithContent, editor])
}, [
navigate,
docId,
toggleLeft,
toggleRight,
content,
me,
docWithContent,
editor,
])

let allDocs = getSpaceDocs(space)
let spaceDocs = space.documents?.$isLoaded ? space.documents : null
Expand Down Expand Up @@ -641,6 +660,9 @@ let spaceMeResolve = {
root: {
documents: { $each: { content: true } },
settings: true,
themes: {
$each: { css: true, template: true, assets: { $each: { data: true } } },
},
},
} as const

Expand Down