inkdrop-export-utils
Version:
Utilities for exporting notes from Inkdrop
178 lines (162 loc) • 5.08 kB
Flow
// @flow
import fs from 'fs'
import path from 'path'
import * as React from 'react'
import ReactDOM from 'react-dom'
import { markdownRenderer, logger, PrintingContext } from 'inkdrop'
import { Provider } from 'react-redux'
import { addTitleToMarkdown } from './add-title-to-markdown'
import type { Note } from 'inkdrop-model'
export { addTitleToMarkdown }
export async function renderHTML(markdown: string): Promise<string> {
const file = await markdownRenderer.render(markdown)
const container = document.createElement('div')
container.classList.add('inkdrop-export')
container.style.position = 'absolute'
container.style.zIndex = '-1000'
const { body } = document
if (body) {
body.appendChild(container)
ReactDOM.render(
<Provider store={inkdrop.store}>
<PrintingContext.Provider value={{ printing: true }}>
{file.result}
</PrintingContext.Provider>
</Provider>,
container
)
// wait for `useEffect` update
await new Promise(resolve => setTimeout(resolve, 30))
const html = container.innerHTML
body.removeChild(container)
return html
} else {
throw new Error('Unexpected error')
}
}
export function getStylesheets(): string {
return inkdrop.styles
.getStyleElements()
.filter(el => {
return (
typeof el.context === 'undefined' || el.context === 'inkdrop-preview'
)
})
.reduce((styles, el) => {
return styles + el.outerHTML
}, '')
}
export async function replaceImages(
body: string,
dirToSave: string,
basePath?: string
): Promise<string> {
// find attachments
const uris = body.match(/inkdrop:\/\/file:[^) "']*/g) || []
for (let i = 0; i < uris.length; ++i) {
const uri = uris[i]
let imagePath = await exportImage(uri, dirToSave)
if (typeof imagePath === 'string') {
if (basePath) imagePath = path.relative(basePath, imagePath)
body = body.replace(uri, imagePath)
}
}
return body
}
export async function replaceHTMLImagesWithDataURI(
html: string
): Promise<string> {
const { File: IDFile } = require('inkdrop').models
const m = html.match(/inkdrop-file:\/\/file:[^"]*/g)
if (m instanceof Array && m.length > 0) {
for (const uri of m) {
try {
const [, docId] = uri.match(/\/\/(file:.*)$/)
const file = await IDFile.loadWithId(docId)
const base64 = file.getAsBase64()
html = html.replace(
uri,
() => `data:${file.contentType};base64,${base64}`
)
} catch (e) {
logger.error('Failed to replace images with data URI:', uri)
logger.error(e)
}
}
}
return html
}
export async function exportImage(
uri: string,
dirToSave: string
): Promise<?string | false> {
try {
const { dataStore } = inkdrop.main
const db = dataStore.getLocalDB()
const [, docId] = uri.match(/^inkdrop:\/\/(file:.*)$/) || []
if (docId) {
const file = await db.files.get(docId)
const buffer = await db.utils.getBufferFromFile(docId)
const name = file.name || 'index'
const fileName = docId.split(':')[1] + '-' + name
const filePath = path.join(dirToSave, fileName)
fs.writeFileSync(filePath, buffer)
return filePath
}
} catch (e) {
console.error('Failed to export image file:', e)
return false
}
}
export async function createHTML(
note: Note,
options: {
addTitle?: boolean,
templateHtml?: string
} = { addTitle: true }
): Promise<string> {
const templateFilePath = require.resolve(
path.join('inkdrop-export-utils', 'assets', 'template.html')
)
const templateHtml =
options.templateHtml || fs.readFileSync(templateFilePath, 'utf-8')
const markdown = options.addTitle
? addTitleToMarkdown(note.body, note.title)
: note.body
const htmlBody = await renderHTML(markdown)
const htmlStyles = getStylesheets()
const outputHtml = templateHtml
.replace('{%body%}', () => htmlBody)
.replace('{%styles%}', () => htmlStyles)
.replace('{%title%}', () => note.title)
return outputHtml
}
export async function createWebView(note: Note): Object {
const outputHtml = await createHTML(note, { addTitle: true })
const fn = saveHTMLToTmp(outputHtml)
const webView: Object = document.createElement('webview')
window.document.body.appendChild(webView)
webView.style.position = 'absolute'
webView.src = fn
await new Promise((resolve, reject) => {
let resolved = false
const done = () => {
if (!resolved) {
resolved = true
resolve()
}
}
webView.addEventListener('did-finish-load', done)
webView.addEventListener('did-fail-load', reject)
setTimeout(done, 1000 * 5)
})
return webView
}
export function removeWebView(webView: Object, delay: number = 30 * 60 * 1000) {
setTimeout(() => window.document.body.removeChild(webView), delay)
}
export function saveHTMLToTmp(html: string): string {
const fn = path.join(require('os').tmpdir(), 'inkdrop-export.html')
fs.writeFileSync(fn, html, 'utf-8')
return fn
}