feat: Added "Page size" option when exporting Super notes as PDF

This commit is contained in:
Aman Harwara
2024-01-27 16:04:20 +05:30
parent ff3c45ba35
commit 853fab53ab
10 changed files with 82 additions and 26 deletions

View File

@@ -128,7 +128,7 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
/* Use custom title bar. Take the sn-titlebar-height off of /* Use custom title bar. Take the sn-titlebar-height off of
the app content height so its not overflowing */ the app content height so its not overflowing */
sheet.insertRule( sheet.insertRule(
'[role="dialog"]:not(.challenge-modal) { height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; margin-top: var(--sn-desktop-titlebar-height); }', '[role="dialog"]:not(.challenge-modal) { max-height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; margin-top: var(--sn-desktop-titlebar-height); }',
sheet.cssRules.length, sheet.cssRules.length,
) )
sheet.insertRule( sheet.insertRule(

View File

@@ -1,8 +1,18 @@
import { FileItem, PrefKey, PrefValue } from '@standardnotes/models'
export interface SuperConverterServiceInterface { export interface SuperConverterServiceInterface {
isValidSuperString(superString: string): boolean isValidSuperString(superString: string): boolean
convertSuperStringToOtherFormat: ( convertSuperStringToOtherFormat: (
superString: string, superString: string,
toFormat: 'txt' | 'md' | 'html' | 'json' | 'pdf', toFormat: 'txt' | 'md' | 'html' | 'json' | 'pdf',
config?: {
embedBehavior?: PrefValue[PrefKey.SuperNoteExportEmbedBehavior]
getFileItem?: (id: string) => FileItem | undefined
getFileBase64?: (id: string) => Promise<string | undefined>
pdf?: {
pageSize?: PrefValue[PrefKey.SuperNoteExportPDFPageSize]
}
},
) => Promise<string> ) => Promise<string>
convertOtherFormatToSuperString: ( convertOtherFormatToSuperString: (
otherFormatString: string, otherFormatString: string,

View File

@@ -42,6 +42,7 @@ export const PrefDefaults = {
[PrefKey.SuperNoteExportFormat]: 'json', [PrefKey.SuperNoteExportFormat]: 'json',
[PrefKey.SuperNoteExportEmbedBehavior]: 'reference', [PrefKey.SuperNoteExportEmbedBehavior]: 'reference',
[PrefKey.SuperNoteExportUseMDFrontmatter]: true, [PrefKey.SuperNoteExportUseMDFrontmatter]: true,
[PrefKey.SuperNoteExportPDFPageSize]: 'A4',
[PrefKey.SystemViewPreferences]: {}, [PrefKey.SystemViewPreferences]: {},
[PrefKey.AuthenticatorNames]: '', [PrefKey.AuthenticatorNames]: '',
[PrefKey.ComponentPreferences]: {}, [PrefKey.ComponentPreferences]: {},

View File

@@ -43,6 +43,7 @@ export enum PrefKey {
SuperNoteExportFormat = 'superNoteExportFormat', SuperNoteExportFormat = 'superNoteExportFormat',
SuperNoteExportEmbedBehavior = 'superNoteExportEmbedBehavior', SuperNoteExportEmbedBehavior = 'superNoteExportEmbedBehavior',
SuperNoteExportUseMDFrontmatter = 'superNoteExportUseMDFrontmatter', SuperNoteExportUseMDFrontmatter = 'superNoteExportUseMDFrontmatter',
SuperNoteExportPDFPageSize = 'superNoteExportPDFPageSize',
AuthenticatorNames = 'authenticatorNames', AuthenticatorNames = 'authenticatorNames',
PaneGesturesEnabled = 'paneGesturesEnabled', PaneGesturesEnabled = 'paneGesturesEnabled',
ComponentPreferences = 'componentPreferences', ComponentPreferences = 'componentPreferences',
@@ -90,6 +91,7 @@ export type PrefValue = {
[PrefKey.SuperNoteExportFormat]: 'json' | 'md' | 'html' | 'pdf' [PrefKey.SuperNoteExportFormat]: 'json' | 'md' | 'html' | 'pdf'
[PrefKey.SuperNoteExportEmbedBehavior]: 'reference' | 'inline' | 'separate' [PrefKey.SuperNoteExportEmbedBehavior]: 'reference' | 'inline' | 'separate'
[PrefKey.SuperNoteExportUseMDFrontmatter]: boolean [PrefKey.SuperNoteExportUseMDFrontmatter]: boolean
[PrefKey.SuperNoteExportPDFPageSize]: 'A3' | 'A4' | 'LETTER' | 'LEGAL' | 'TABLOID'
[PrefKey.AuthenticatorNames]: string [PrefKey.AuthenticatorNames]: string
[PrefKey.PaneGesturesEnabled]: boolean [PrefKey.PaneGesturesEnabled]: boolean
[PrefKey.ComponentPreferences]: AllComponentPreferences [PrefKey.ComponentPreferences]: AllComponentPreferences

View File

@@ -499,7 +499,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
</> </>
)} )}
<ModalOverlay isOpen={showExportSuperModal} close={closeSuperExportModal}> <ModalOverlay isOpen={showExportSuperModal} close={closeSuperExportModal} className="md:max-w-[25vw]">
<SuperExportModal notes={notes} exportNotes={downloadSelectedItems} close={closeSuperExportModal} /> <SuperExportModal notes={notes} exportNotes={downloadSelectedItems} close={closeSuperExportModal} />
</ModalOverlay> </ModalOverlay>
</> </>

View File

@@ -6,6 +6,7 @@ import RadioButtonGroup from '../RadioButtonGroup/RadioButtonGroup'
import { useEffect } from 'react' import { useEffect } from 'react'
import Switch from '../Switch/Switch' import Switch from '../Switch/Switch'
import { noteHasEmbeddedFiles } from '@/Utils/NoteExportUtils' import { noteHasEmbeddedFiles } from '@/Utils/NoteExportUtils'
import Dropdown from '../Dropdown/Dropdown'
type Props = { type Props = {
notes: SNNote[] notes: SNNote[]
@@ -19,6 +20,7 @@ const SuperExportModal = ({ notes, exportNotes, close }: Props) => {
const superNoteExportFormat = usePreference(PrefKey.SuperNoteExportFormat) const superNoteExportFormat = usePreference(PrefKey.SuperNoteExportFormat)
const superNoteExportEmbedBehavior = usePreference(PrefKey.SuperNoteExportEmbedBehavior) const superNoteExportEmbedBehavior = usePreference(PrefKey.SuperNoteExportEmbedBehavior)
const superNoteExportUseMDFrontmatter = usePreference(PrefKey.SuperNoteExportUseMDFrontmatter) const superNoteExportUseMDFrontmatter = usePreference(PrefKey.SuperNoteExportUseMDFrontmatter)
const superNoteExportPDFPageSize = usePreference(PrefKey.SuperNoteExportPDFPageSize)
useEffect(() => { useEffect(() => {
if (superNoteExportFormat === 'json' && superNoteExportEmbedBehavior === 'separate') { if (superNoteExportFormat === 'json' && superNoteExportEmbedBehavior === 'separate') {
@@ -60,23 +62,25 @@ const SuperExportModal = ({ notes, exportNotes, close }: Props) => {
]} ]}
> >
<div className="mb-2"> <div className="mb-2">
<div className="mb-3 text-base">We detected your selection includes Super notes.</div> <div className="mb-2 flex items-center justify-between">
<div className="mb-1">What format do you want to export them in?</div> <div className="text-base">Choose export format {notes.length > 1 ? 'for Super notes' : ''}</div>
<RadioButtonGroup <Dropdown
items={[ label="Export format"
{ label: 'Super (.json)', value: 'json' }, items={[
{ label: 'Markdown (.md)', value: 'md' }, { label: 'Super (.json)', value: 'json' },
{ label: 'HTML', value: 'html' }, { label: 'Markdown (.md)', value: 'md' },
{ label: 'PDF', value: 'pdf' }, { label: 'HTML', value: 'html' },
]} { label: 'PDF', value: 'pdf' },
value={superNoteExportFormat} ]}
onChange={(value) => { value={superNoteExportFormat}
void application.setPreference( onChange={(value) => {
PrefKey.SuperNoteExportFormat, void application.setPreference(
value as PrefValue[PrefKey.SuperNoteExportFormat], PrefKey.SuperNoteExportFormat,
) value as PrefValue[PrefKey.SuperNoteExportFormat],
}} )
/> }}
/>
</div>
{superNoteExportFormat === 'md' && ( {superNoteExportFormat === 'md' && (
<div className="mt-2 text-xs text-passive-0"> <div className="mt-2 text-xs text-passive-0">
Note that conversion to Markdown is not lossless. Some features like collapsible blocks and formatting like Note that conversion to Markdown is not lossless. Some features like collapsible blocks and formatting like
@@ -84,6 +88,33 @@ const SuperExportModal = ({ notes, exportNotes, close }: Props) => {
</div> </div>
)} )}
</div> </div>
{superNoteExportFormat === 'pdf' && (
<div className="mt-4 flex items-center justify-between">
<div className="text-base">Page size</div>
<Dropdown
label="Page size"
items={
[
{ label: 'A3', value: 'A3' },
{ label: 'A4', value: 'A4' },
{ label: 'Letter', value: 'LETTER' },
{ label: 'Legal', value: 'LEGAL' },
{ label: 'Tabloid', value: 'TABLOID' },
] satisfies {
label: string
value: PrefValue[PrefKey.SuperNoteExportPDFPageSize]
}[]
}
value={superNoteExportPDFPageSize}
onChange={(value) => {
void application.setPreference(
PrefKey.SuperNoteExportPDFPageSize,
value as PrefValue[PrefKey.SuperNoteExportPDFPageSize],
)
}}
/>
</div>
)}
{superNoteExportFormat === 'md' && ( {superNoteExportFormat === 'md' && (
<div className="mt-4"> <div className="mt-4">
<Switch <Switch

View File

@@ -22,6 +22,7 @@ import { $isCollapsibleTitleNode } from '../../../Plugins/CollapsiblePlugin/Coll
import { PDFDataNode, PDFWorker } from './PDFWorker' import { PDFDataNode, PDFWorker } from './PDFWorker'
import { wrap } from 'comlink' import { wrap } from 'comlink'
import { getBase64FromBlob } from '@/Utils' import { getBase64FromBlob } from '@/Utils'
import { PrefKey, PrefValue } from '@standardnotes/snjs'
const styles = StyleSheet.create({ const styles = StyleSheet.create({
page: { page: {
@@ -422,7 +423,7 @@ const PDFWorkerComlink = wrap<PDFWorker>(new Worker(new URL('./PDFWorker.tsx', i
/** /**
* @returns The PDF as a base64 string * @returns The PDF as a base64 string
*/ */
export function $generatePDFFromNodes(editor: LexicalEditor) { export function $generatePDFFromNodes(editor: LexicalEditor, pageSize: PrefValue[PrefKey.SuperNoteExportPDFPageSize]) {
return new Promise<string>((resolve) => { return new Promise<string>((resolve) => {
editor.getEditorState().read(() => { editor.getEditorState().read(() => {
const root = $getRoot() const root = $getRoot()
@@ -430,7 +431,7 @@ export function $generatePDFFromNodes(editor: LexicalEditor) {
const pdfDataNodes = getPDFDataNodesFromLexicalNodes(nodes) const pdfDataNodes = getPDFDataNodesFromLexicalNodes(nodes)
void PDFWorkerComlink.renderPDF(pdfDataNodes).then((blob) => { void PDFWorkerComlink.renderPDF(pdfDataNodes, pageSize).then((blob) => {
void getBase64FromBlob(blob).then((base64) => { void getBase64FromBlob(blob).then((base64) => {
resolve(base64) resolve(base64)
}) })

View File

@@ -14,6 +14,7 @@ import {
TextProps, TextProps,
SVGProps, SVGProps,
ImageWithSrcProp, ImageWithSrcProp,
PageProps,
} from '@react-pdf/renderer' } from '@react-pdf/renderer'
import { expose } from 'comlink' import { expose } from 'comlink'
@@ -72,10 +73,11 @@ const Node = ({ node }: { node: PDFDataNode }) => {
} }
} }
const PDFDocument = ({ nodes }: { nodes: PDFDataNode[] }) => { const PDFDocument = ({ nodes, pageSize }: { nodes: PDFDataNode[]; pageSize: PageProps['size'] }) => {
return ( return (
<Document> <Document>
<Page <Page
size={pageSize}
style={{ style={{
paddingVertical: 35, paddingVertical: 35,
paddingHorizontal: 35, paddingHorizontal: 35,
@@ -92,8 +94,8 @@ const PDFDocument = ({ nodes }: { nodes: PDFDataNode[] }) => {
) )
} }
const renderPDF = (nodes: PDFDataNode[]) => { const renderPDF = (nodes: PDFDataNode[], pageSize: PageProps['size']) => {
return pdf(<PDFDocument nodes={nodes} />).toBlob() return pdf(<PDFDocument pageSize={pageSize} nodes={nodes} />).toBlob()
} }
expose({ expose({

View File

@@ -56,6 +56,9 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
embedBehavior?: PrefValue[PrefKey.SuperNoteExportEmbedBehavior] embedBehavior?: PrefValue[PrefKey.SuperNoteExportEmbedBehavior]
getFileItem?: (id: string) => FileItem | undefined getFileItem?: (id: string) => FileItem | undefined
getFileBase64?: (id: string) => Promise<string | undefined> getFileBase64?: (id: string) => Promise<string | undefined>
pdf?: {
pageSize?: PrefValue[PrefKey.SuperNoteExportPDFPageSize]
}
}, },
): Promise<string> { ): Promise<string> {
if (superString.length === 0) { if (superString.length === 0) {
@@ -145,8 +148,8 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
resolve() resolve()
break break
case 'pdf': { case 'pdf': {
void import('../Lexical/Utils/PDFExport/PDFExport').then(({ $generatePDFFromNodes }) => { void import('../Lexical/Utils/PDFExport/PDFExport').then(({ $generatePDFFromNodes }): void => {
void $generatePDFFromNodes(this.exportEditor).then((pdf) => { void $generatePDFFromNodes(this.exportEditor, config?.pdf?.pageSize || 'A4').then((pdf) => {
content = pdf content = pdf
resolve() resolve()
}) })

View File

@@ -99,6 +99,12 @@ export const getNoteBlob = async (
} }
return await getBase64FromBlob(fileBlob) return await getBase64FromBlob(fileBlob)
}, },
pdf: {
pageSize: application.getPreference(
PrefKey.SuperNoteExportPDFPageSize,
PrefDefaults[PrefKey.SuperNoteExportPDFPageSize],
),
},
}) })
const useMDFrontmatter = const useMDFrontmatter =
format === 'md' && format === 'md' &&