fix: Fixes rendering of non-Latin alphabet characters in PDF export (#2937)

* fix: Fixes rendering of non-Latin alphabet characters in PDF export

* chore: self-host font files

* chore: add fonts license
This commit is contained in:
Antonella Sgarlatta
2025-09-29 10:53:52 -03:00
committed by GitHub
parent 0055696c35
commit 6b408a670d
73 changed files with 611 additions and 147 deletions

View File

@@ -116,9 +116,10 @@
"@lexical/rich-text": "0.32.1",
"@lexical/utils": "0.32.1",
"@radix-ui/react-slot": "^1.0.1",
"@react-pdf/renderer": "^3.3.2",
"@react-pdf/renderer": "^4.3.0",
"comlink": "^4.4.1",
"fast-diff": "^1.3.0",
"lexical": "0.32.1"
"lexical": "0.32.1",
"unicode-script": "^1.2.0"
}
}

View File

@@ -0,0 +1,3 @@

View File

@@ -0,0 +1,94 @@
Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts)
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,302 @@
import { Font } from '@react-pdf/renderer'
import { LexicalNode } from 'lexical'
// @ts-expect-error No typing for this package
import { unicodeScripts } from 'unicode-script'
enum UnicodeScript {
Latin = 'Latin',
Common = 'Common',
Cyrillic = 'Cyrillic',
Greek = 'Greek',
Hebrew = 'Hebrew',
Arabic = 'Arabic',
Devanagari = 'Devanagari',
Bengali = 'Bengali',
Tamil = 'Tamil',
Telugu = 'Telugu',
Gujarati = 'Gujarati',
Gurmukhi = 'Gurmukhi',
Malayalam = 'Malayalam',
Sinhala = 'Sinhala',
Thai = 'Thai',
Armenian = 'Armenian',
Georgian = 'Georgian',
Ethiopic = 'Ethiopic',
Myanmar = 'Myanmar',
Khmer = 'Khmer',
Lao = 'Lao',
Tibetan = 'Tibetan',
Vietnamese = 'Vietnamese',
Chinese = 'Chinese',
Han = 'Han',
Japanese = 'Japanese',
Korean = 'Korean',
Hangul = 'Hangul',
}
export enum FontFamily {
NotoSans = 'Noto Sans',
NotoSansGreek = 'Noto Sans Greek',
NotoSansHebrew = 'Noto Sans Hebrew',
NotoSansArabic = 'Noto Sans Arabic',
NotoSansDevanagari = 'Noto Sans Devanagari',
NotoSansBengali = 'Noto Sans Bengali',
NotoSansTamil = 'Noto Sans Tamil',
NotoSansTelugu = 'Noto Sans Telugu',
NotoSansGujarati = 'Noto Sans Gujarati',
NotoSansGurmukhi = 'Noto Sans Gurmukhi',
NotoSansMalayalam = 'Noto Sans Malayalam',
NotoSansSinhala = 'Noto Sans Sinhala',
NotoSansThai = 'Noto Sans Thai',
NotoSansArmenian = 'Noto Sans Armenian',
NotoSansGeorgian = 'Noto Sans Georgian',
NotoSansEthiopic = 'Noto Sans Ethiopic',
NotoSansMyanmar = 'Noto Sans Myanmar',
NotoSansKhmer = 'Noto Sans Khmer',
NotoSansLao = 'Noto Sans Lao',
NotoSansTibetan = 'Noto Sans Tibetan',
NotoSansVietnamese = 'Noto Sans Vietnamese',
NotoSansSC = 'Noto Sans SC',
NotoSansJP = 'Noto Sans JP',
NotoSansKR = 'Noto Sans KR',
Courier = 'Courier',
Helvetica = 'Helvetica',
}
enum FontVariant {
Normal = 'normal',
Bold = 'bold',
Italic = 'italic',
BoldItalic = 'bolditalic',
}
type FontWeight = 'normal' | 'bold'
type FontStyle = 'normal' | 'italic'
const FONT_VARIANT_TO_FONT_OPTIONS: Record<FontVariant, { fontWeight: FontWeight; fontStyle: FontStyle }> = {
[FontVariant.Normal]: {
fontWeight: 'normal',
fontStyle: 'normal',
},
[FontVariant.Bold]: {
fontWeight: 'bold',
fontStyle: 'normal',
},
[FontVariant.Italic]: {
fontWeight: 'normal',
fontStyle: 'italic',
},
[FontVariant.BoldItalic]: {
fontWeight: 'bold',
fontStyle: 'italic',
},
}
const ASSET_FONT_BASE_PATH = '/assets/fonts'
const resolveAssetUrl = (path: string) => {
try {
const workerOrigin = (self as unknown as { location?: { origin?: string } })?.location?.origin
const browserOrigin = typeof window !== 'undefined' ? window.location?.origin : undefined
const origin = workerOrigin || browserOrigin
return origin ? new URL(path, origin).toString() : path
} catch {
return path
}
}
const FALLBACK_FONT_SOURCE = `${ASSET_FONT_BASE_PATH}/noto-sans/NotoSans-Regular.ttf`
export const FALLBACK_FONT_FAMILY = FontFamily.Helvetica
export const MONOSPACE_FONT_FAMILY = FontFamily.Courier
const FONT_FAMILY_TO_FONT_SOURCES: Partial<Record<FontFamily, Partial<Record<FontVariant, string>>>> = {
[FontFamily.NotoSans]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans/NotoSans-Regular.ttf`,
[FontVariant.Bold]: `${ASSET_FONT_BASE_PATH}/noto-sans/NotoSans-Bold.ttf`,
[FontVariant.Italic]: `${ASSET_FONT_BASE_PATH}/noto-sans/NotoSans-Italic.ttf`,
[FontVariant.BoldItalic]: `${ASSET_FONT_BASE_PATH}/noto-sans/NotoSans-BoldItalic.ttf`,
},
[FontFamily.NotoSansGreek]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-greek/NotoSansGreek-Regular.ttf`,
[FontVariant.Bold]: `${ASSET_FONT_BASE_PATH}/noto-sans-greek/NotoSansGreek-Bold.ttf`,
},
[FontFamily.NotoSansHebrew]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-hebrew/NotoSansHebrew-Regular.ttf`,
[FontVariant.Bold]: `${ASSET_FONT_BASE_PATH}/noto-sans-hebrew/NotoSansHebrew-Bold.ttf`,
},
[FontFamily.NotoSansArabic]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-arabic/NotoSansArabic-Regular.ttf`,
[FontVariant.Bold]: `${ASSET_FONT_BASE_PATH}/noto-sans-arabic/NotoSansArabic-Bold.ttf`,
},
[FontFamily.NotoSansDevanagari]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-devanagari/NotoSansDevanagari-Regular.ttf`,
[FontVariant.Bold]: `${ASSET_FONT_BASE_PATH}/noto-sans-devanagari/NotoSansDevanagari-Bold.ttf`,
},
[FontFamily.NotoSansBengali]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-bengali/NotoSansBengali-Regular.ttf`,
},
[FontFamily.NotoSansTamil]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-tamil/NotoSansTamil-Regular.ttf`,
},
[FontFamily.NotoSansTelugu]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-telugu/NotoSansTelugu-Regular.ttf`,
},
[FontFamily.NotoSansGujarati]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-gujarati/NotoSansGujarati-Regular.ttf`,
},
[FontFamily.NotoSansGurmukhi]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-gurmukhi/NotoSansGurmukhi-Regular.ttf`,
},
[FontFamily.NotoSansMalayalam]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-malayalam/NotoSansMalayalam-Regular.ttf`,
},
[FontFamily.NotoSansSinhala]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-sinhala/NotoSansSinhala-Regular.ttf`,
},
[FontFamily.NotoSansThai]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-thai/NotoSansThai-Regular.ttf`,
},
[FontFamily.NotoSansArmenian]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-armenian/NotoSansArmenian-Regular.ttf`,
},
[FontFamily.NotoSansGeorgian]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-georgian/NotoSansGeorgian-Regular.ttf`,
},
[FontFamily.NotoSansEthiopic]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-ethiopic/NotoSansEthiopic-Regular.ttf`,
},
[FontFamily.NotoSansMyanmar]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-myanmar/NotoSansMyanmar-Regular.ttf`,
},
[FontFamily.NotoSansKhmer]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-khmer/NotoSansKhmer-Regular.ttf`,
},
[FontFamily.NotoSansLao]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-lao/NotoSansLao-Regular.ttf`,
},
[FontFamily.NotoSansTibetan]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-tibetan/NotoSansTibetan-Regular.ttf`,
},
[FontFamily.NotoSansSC]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-sc/NotoSansSC-Regular.ttf`,
},
[FontFamily.NotoSansJP]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-jp/NotoSansJP-Regular.ttf`,
},
[FontFamily.NotoSansKR]: {
[FontVariant.Normal]: `${ASSET_FONT_BASE_PATH}/noto-sans-kr/NotoSansKR-Regular.ttf`,
},
}
export const getFontFamilyForUnicodeScript = (script: UnicodeScript): FontFamily => {
switch (script) {
// Common, shared scripts
case UnicodeScript.Common:
case UnicodeScript.Latin:
case UnicodeScript.Cyrillic:
case UnicodeScript.Greek:
case UnicodeScript.Vietnamese:
return FontFamily.NotoSans
case UnicodeScript.Hebrew:
return FontFamily.NotoSansHebrew
case UnicodeScript.Arabic:
return FontFamily.NotoSansArabic
case UnicodeScript.Devanagari:
return FontFamily.NotoSansDevanagari
case UnicodeScript.Bengali:
return FontFamily.NotoSansBengali
case UnicodeScript.Tamil:
return FontFamily.NotoSansTamil
case UnicodeScript.Telugu:
return FontFamily.NotoSansTelugu
case UnicodeScript.Gujarati:
return FontFamily.NotoSansGujarati
case UnicodeScript.Gurmukhi:
return FontFamily.NotoSansGurmukhi
case UnicodeScript.Malayalam:
return FontFamily.NotoSansMalayalam
case UnicodeScript.Sinhala:
return FontFamily.NotoSansSinhala
case UnicodeScript.Thai:
return FontFamily.NotoSansThai
case UnicodeScript.Armenian:
return FontFamily.NotoSansArmenian
case UnicodeScript.Georgian:
return FontFamily.NotoSansGeorgian
case UnicodeScript.Ethiopic:
return FontFamily.NotoSansEthiopic
case UnicodeScript.Myanmar:
return FontFamily.NotoSansMyanmar
case UnicodeScript.Khmer:
return FontFamily.NotoSansKhmer
case UnicodeScript.Lao:
return FontFamily.NotoSansLao
case UnicodeScript.Tibetan:
return FontFamily.NotoSansTibetan
case UnicodeScript.Chinese:
case UnicodeScript.Han:
return FontFamily.NotoSansSC
case UnicodeScript.Japanese:
return FontFamily.NotoSansJP
case UnicodeScript.Korean:
case UnicodeScript.Hangul:
return FontFamily.NotoSansKR
default:
return FontFamily.NotoSans
}
}
const getFontRegisterOptions = (fontFamily: FontFamily) => {
const fallback = FONT_FAMILY_TO_FONT_SOURCES[fontFamily]?.[FontVariant.Normal] ?? FALLBACK_FONT_SOURCE
return {
family: fontFamily,
fonts: Object.entries(FONT_VARIANT_TO_FONT_OPTIONS).map(([variant, fontOptions]) => ({
...fontOptions,
src: resolveAssetUrl(FONT_FAMILY_TO_FONT_SOURCES[fontFamily]?.[variant as FontVariant] ?? fallback),
})),
}
}
export const getFontFamiliesFromLexicalNode = (node: LexicalNode) => {
const scripts: UnicodeScript[] = Array.from(unicodeScripts(node.getTextContent()))
const fontFamilies = [FontFamily.NotoSans]
scripts.forEach((script) => {
const fontFamilyForScript = getFontFamilyForUnicodeScript(script)
if (!fontFamilies.includes(fontFamilyForScript)) {
fontFamilies.unshift(fontFamilyForScript)
}
})
const fontFamiliesSet = new Set(fontFamilies)
return Array.from(fontFamiliesSet)
}
export const registerPDFFonts = (fontFamilies: FontFamily[]) => {
const fontFamiliesToRegister = new Set(fontFamilies)
fontFamiliesToRegister.forEach((fontFamily) => {
const registerOptions = getFontRegisterOptions(fontFamily)
Font.register(registerOptions)
})
}

View File

@@ -23,6 +23,7 @@ import { $isCollapsibleTitleNode } from '../../../Plugins/CollapsiblePlugin/Coll
import PDFWorker, { PDFDataNode, PDFWorkerInterface } from './PDFWorker.worker'
import { wrap } from 'comlink'
import { PrefKey, PrefValue } from '@standardnotes/snjs'
import { FALLBACK_FONT_FAMILY, FontFamily, MONOSPACE_FONT_FAMILY, getFontFamiliesFromLexicalNode } from './FontConfig'
const styles = StyleSheet.create({
page: {
@@ -143,6 +144,12 @@ const getFontSizeForHeading = (heading: HeadingNode) => {
}
const getNodeTextAlignment = (node: ElementNode) => {
const direction = node.getDirection()
if (direction === 'rtl') {
return 'right'
}
const formatType = node.getFormatType()
if (!formatType) {
@@ -160,7 +167,12 @@ const getNodeTextAlignment = (node: ElementNode) => {
return formatType
}
const getPDFDataNodeFromLexicalNode = (node: LexicalNode): PDFDataNode => {
const getNodeDirection = (node: ElementNode) => {
const direction = node.getDirection()
return direction ?? 'ltr'
}
const getPDFDataNodeFromLexicalNode = (node: LexicalNode, fontFamilies: FontFamily[]): PDFDataNode => {
const parent = node.getParent()
if ($isLineBreakNode(node)) {
@@ -176,23 +188,23 @@ const getPDFDataNodeFromLexicalNode = (node: LexicalNode): PDFDataNode => {
const isBold = node.hasFormat('bold')
const isItalic = node.hasFormat('italic')
const isHighlight = node.hasFormat('highlight')
const nodeFontFamilies = getFontFamiliesFromLexicalNode(node)
let fontFamily: FontFamily[] | FontFamily = [...nodeFontFamilies, FALLBACK_FONT_FAMILY]
let font = isInlineCode || isCodeNodeText ? 'Courier' : 'Helvetica'
if (isBold || isItalic) {
font += '-'
if (isBold) {
font += 'Bold'
}
if (isItalic) {
font += 'Oblique'
}
if (isInlineCode && isCodeNodeText) {
fontFamily = MONOSPACE_FONT_FAMILY
} else {
fontFamilies.push(...nodeFontFamilies)
}
return {
type: 'Text',
children: node.getTextContent(),
style: {
fontFamily: font,
fontFamily,
fontWeight: isBold ? 'bold' : 'normal',
fontStyle: isItalic ? 'italic' : 'normal',
direction: $isElementNode(parent) ? getNodeDirection(parent) : 'ltr',
textDecoration: node.hasFormat('underline')
? 'underline'
: node.hasFormat('strikethrough')
@@ -236,7 +248,7 @@ const getPDFDataNodeFromLexicalNode = (node: LexicalNode): PDFDataNode => {
type: 'View',
style: [styles.row, styles.wrap],
children: line.map((child) => {
return getPDFDataNodeFromLexicalNode(child)
return getPDFDataNodeFromLexicalNode(child, fontFamilies)
}),
}
}),
@@ -266,7 +278,7 @@ const getPDFDataNodeFromLexicalNode = (node: LexicalNode): PDFDataNode => {
const children =
$isElementNode(node) || $isTableNode(node) || $isTableCellNode(node) || $isTableRowNode(node)
? node.getChildren().map((child) => {
return getPDFDataNodeFromLexicalNode(child)
return getPDFDataNodeFromLexicalNode(child, fontFamilies)
})
: undefined
@@ -414,8 +426,8 @@ const getPDFDataNodeFromLexicalNode = (node: LexicalNode): PDFDataNode => {
}
}
const getPDFDataNodesFromLexicalNodes = (nodes: LexicalNode[]): PDFDataNode[] => {
return nodes.map(getPDFDataNodeFromLexicalNode)
const getPDFDataNodesFromLexicalNodes = (nodes: LexicalNode[], fontFamilies: FontFamily[]): PDFDataNode[] => {
return nodes.map((node) => getPDFDataNodeFromLexicalNode(node, fontFamilies))
}
const pdfWorker = new PDFWorker()
@@ -425,17 +437,21 @@ const PDFWorkerComlink = wrap<PDFWorkerInterface>(pdfWorker)
* @returns The PDF as an object url
*/
export function $generatePDFFromNodes(editor: LexicalEditor, pageSize: PrefValue[PrefKey.SuperNoteExportPDFPageSize]) {
return new Promise<string>((resolve) => {
return new Promise<string>((resolve, reject) => {
editor.getEditorState().read(() => {
const root = $getRoot()
const nodes = root.getChildren()
const fontFamilies: FontFamily[] = []
const pdfDataNodes = getPDFDataNodesFromLexicalNodes(nodes, fontFamilies)
const pdfDataNodes = getPDFDataNodesFromLexicalNodes(nodes)
void PDFWorkerComlink.renderPDF(pdfDataNodes, pageSize).then((blob) => {
const url = URL.createObjectURL(blob)
resolve(url)
})
void PDFWorkerComlink.renderPDF(pdfDataNodes, pageSize, fontFamilies)
.then((blob) => {
const url = URL.createObjectURL(blob)
resolve(url)
})
.catch((error) => {
reject(error)
})
})
})
}

View File

@@ -17,6 +17,7 @@ import {
PageProps,
} from '@react-pdf/renderer'
import { expose } from 'comlink'
import { FontFamily, registerPDFFonts } from './FontConfig'
export type PDFDataNode =
| ((
@@ -94,7 +95,8 @@ const PDFDocument = ({ nodes, pageSize }: { nodes: PDFDataNode[]; pageSize: Page
)
}
const renderPDF = (nodes: PDFDataNode[], pageSize: PageProps['size']) => {
const renderPDF = (nodes: PDFDataNode[], pageSize: PageProps['size'], fontFamilies: FontFamily[]) => {
registerPDFFonts(fontFamilies)
return pdf(<PDFDocument pageSize={pageSize} nodes={nodes} />).toBlob()
}

View File

@@ -64,6 +64,7 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
}
},
): Promise<string> {
let didThrow = false
if (superString.length === 0) {
return superString
}
@@ -81,7 +82,7 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
let content: string | undefined
await new Promise<void>((resolve) => {
await new Promise<void>((resolve, reject) => {
const handleFileNodes = () => {
if (embedBehavior === 'reference') {
resolve()
@@ -136,12 +137,16 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
}),
)
.then(() => resolve())
.catch(console.error)
.catch((error) => {
didThrow = true
console.error(error)
reject(error)
})
}
this.exportEditor.update(handleFileNodes, { discrete: true })
})
await new Promise<void>((resolve) => {
await new Promise<void>((resolve, reject) => {
const convertToFormat = () => {
switch (toFormat) {
case 'txt':
@@ -164,10 +169,16 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
break
case 'pdf': {
void import('../Lexical/Utils/PDFExport/PDFExport').then(({ $generatePDFFromNodes }): void => {
void $generatePDFFromNodes(this.exportEditor, config?.pdf?.pageSize || 'A4').then((pdf) => {
content = pdf
resolve()
})
void $generatePDFFromNodes(this.exportEditor, config?.pdf?.pageSize || 'A4')
.then((pdf) => {
content = pdf
resolve()
})
.catch((error) => {
didThrow = true
console.error(error)
reject(error)
})
})
break
}
@@ -181,7 +192,7 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
this.exportEditor.update(convertToFormat, { discrete: true })
})
if (typeof content !== 'string') {
if (didThrow || typeof content !== 'string') {
throw new Error('Could not export note')
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable */
const path = require('path')
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
@@ -13,6 +14,7 @@ module.exports = (env) => {
const copyPluginPatterns = [
{ from: 'src/favicon', to: 'favicon' },
{ from: 'src/assets', to: 'assets' },
{ from: 'src/vendor', to: 'dist' },
{ from: 'src/404.html' },
{ from: 'src/422.html' },