diff --git a/README.md b/README.md
index d9753c7..71933b3 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,17 @@ If the directory does not exist, you can create it.
If you put a `default.yaml` file in the data directory, PanWriter will merge this with the YAML in your input file (to determine the command-line arguments to call pandoc with) and add the `--metadata-file` option. The YAML should be in the same format as above.
+To include CSS in your `default.yaml`, you can also use the same format as in-document metadata, for example:
+
+```yaml
+header-includes: |-
+
+```
+
### Document types / themes
You can e.g. put `type: letter` in the YAML of your input document. In that case, PanWriter will look for `letter.yaml` instead of `default.yaml` in the user data directory.
diff --git a/electron/dataDir.ts b/electron/dataDir.ts
new file mode 100644
index 0000000..3022a59
--- /dev/null
+++ b/electron/dataDir.ts
@@ -0,0 +1,28 @@
+import { readFile } from 'fs/promises'
+import * as jsYaml from 'js-yaml'
+import { app } from 'electron'
+import { basename, sep } from 'path'
+import { Meta } from '../src/appState/AppState'
+
+export const dataDir = [app.getPath('appData'), 'PanWriterUserData', ''].join(sep)
+
+/**
+ * reads the right default yaml file
+ * make sure this function is safe to expose in `preload.ts`
+ */
+export const readDataDirFile = async (fileName: string): Promise<[Meta | undefined, string]> => {
+ try {
+ // make sure only PanWriterUserData directory can be accessed
+ fileName = dataDir + basename(fileName)
+
+ const str = await readFile(fileName, 'utf8')
+ const yaml = jsYaml.safeLoad(str)
+ return [
+ typeof yaml === 'object' ? (yaml as Meta) : {},
+ fileName
+ ]
+ } catch(e) {
+ console.warn("Error loading or parsing YAML file." + e.message)
+ return [ undefined, fileName ]
+ }
+}
diff --git a/electron/ipc.ts b/electron/ipc.ts
index 85b9e71..daeebfb 100644
--- a/electron/ipc.ts
+++ b/electron/ipc.ts
@@ -1,5 +1,6 @@
import { BrowserWindow, ipcMain, shell } from 'electron'
import { Doc } from '../src/appState/AppState'
+import { readDataDirFile } from './dataDir'
import { Message } from './preload'
// this file contains the IPC functionality of the main process.
@@ -25,6 +26,11 @@ export const init = () => {
ipcMain.on('openLink', (_event, link: string) => {
shell.openExternal(link)
})
+
+ ipcMain.handle('readDataDirFile', async (_event, fileName: string) => {
+ const [ meta ] = await readDataDirFile(fileName)
+ return meta
+ })
}
export const getDoc = async (win: BrowserWindow): Promise => {
diff --git a/electron/pandoc/export.ts b/electron/pandoc/export.ts
index 6048748..321c32c 100644
--- a/electron/pandoc/export.ts
+++ b/electron/pandoc/export.ts
@@ -1,10 +1,8 @@
import { spawn, SpawnOptionsWithoutStdio } from 'child_process'
-import { app, BrowserWindow, clipboard, dialog } from 'electron'
-import { readFile } from 'fs'
-import * as jsYaml from 'js-yaml'
-import { basename, dirname, extname, sep } from 'path'
-import { promisify } from 'util'
+import { BrowserWindow, clipboard, dialog } from 'electron'
+import { basename, dirname, extname } from 'path'
import { Doc, JSON, Meta } from '../../src/appState/AppState'
+import { readDataDirFile } from '../dataDir';
interface ExportOptions {
outputPath?: string;
@@ -25,8 +23,6 @@ declare class CustomBrowserWindow extends Electron.BrowserWindow {
previousExportConfig?: ExportOptions;
}
-export const dataDir = [app.getPath('appData'), 'PanWriterUserData', ''].join(sep)
-
export const fileExportDialog = async (win: CustomBrowserWindow, doc: Doc) => {
const spawnOpts: SpawnOptionsWithoutStdio = {}
const inputPath = doc.filePath
@@ -95,11 +91,11 @@ const fileExport = async (win: BrowserWindow, doc: Doc, exp: ExportOptions) => {
const type = typeof docMeta.type === 'string'
? docMeta.type
: 'default'
- const [extMeta, fileArg] = await defaultMeta(type)
- const out = mergeAndValidate(docMeta, extMeta, exp.outputPath, exp.toClipboardFormat)
+ const [extMeta, fileName] = await readDataDirFile(type + '.yaml')
+ const out = mergeAndValidate(docMeta, extMeta || {}, exp.outputPath, exp.toClipboardFormat)
const cmd = 'pandoc'
- const args = fileArg.concat( toArgs(out) )
+ const args = (extMeta ? ['--metadata-file', fileName] : []).concat( toArgs(out) )
const cmdDebug = cmd + ' ' + args.map(a => a.includes(' ') ? `'${a}'` : a).join(' ')
let receivedError = false
@@ -224,27 +220,6 @@ const mergeAndValidate = (docMeta: Meta, extMeta: Meta, outputPath?: string, toC
return out;
}
-/**
- * reads the right default yaml file
- */
-const defaultMeta = async (type: string): Promise<[Meta, string[]]> => {
- try {
- const [str, fileName] = await readDataDirFile(type, '.yaml');
- const yaml = jsYaml.safeLoad(str)
- return [ typeof yaml === 'object' ? (yaml as Meta) : {}, ['--metadata-file', fileName] ]
- } catch(e) {
- console.warn("Error loading or parsing YAML file." + e.message);
- return [ {}, [] ];
- }
-}
-
-// reads file from data directory, throws exception when not found
-const readDataDirFile = async (type: string, suffix: string) => {
- const fileName = dataDir + type + suffix
- const str = await promisify(readFile)(fileName, 'utf8')
- return [str, fileName]
-}
-
// constructs commandline arguments from object
const toArgs = (out: Out) => {
const args: string[] = [];
diff --git a/electron/preload.ts b/electron/preload.ts
index 752fe30..c1732f3 100644
--- a/electron/preload.ts
+++ b/electron/preload.ts
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from 'electron'
-import { AppState, Doc, ViewSplit } from '../src/appState/AppState'
+import { AppState, Doc, Meta, ViewSplit } from '../src/appState/AppState'
import { Action } from '../src/appState/Action'
export type IpcApi = typeof ipcApi
@@ -33,6 +33,9 @@ ipcRenderer.on('dispatch', (_e, action: Message) => {
}
})
+const readDataDirFile = async (fileName: string): Promise =>
+ ipcRenderer.invoke('readDataDirFile', fileName)
+
const ipcApi = {
setStateAndDispatch: (s: AppState, d: Disp) => {
state = s
@@ -54,6 +57,7 @@ const ipcApi = {
, printFile: (cb: () => void) => ipcRenderer.on('printFile', cb)
, sendPlatform: (cb: (p: string) => void) => ipcRenderer.once('sendPlatform', (_e, p) => cb(p))
}
+, readDataDirFile
}
contextBridge.exposeInMainWorld('ipcApi', ipcApi)
diff --git a/src/components/MetaEditor/MetaEditor.tsx b/src/components/MetaEditor/MetaEditor.tsx
index a33ce61..c0245e5 100644
--- a/src/components/MetaEditor/MetaEditor.tsx
+++ b/src/components/MetaEditor/MetaEditor.tsx
@@ -1,7 +1,7 @@
import { Fragment } from 'react'
import { AppState } from '../../appState/AppState'
import { Action } from '../../appState/Action'
-import { defaultVars } from '../../renderPreview/templates/getCss'
+import { defaultVars, stripSurroundingStyleTags } from '../../renderPreview/templates/getCss'
import { ColorPicker } from '../ColorPicker/ColorPicker'
import back from './back.svg'
@@ -164,7 +164,7 @@ const layoutKvs: Kv[] = [{
name: 'header-includes'
, label: 'Include CSS'
, type: 'textarea'
-, onLoad: s => s.startsWith('') ? s.slice(8, -9) : s
+, onLoad: stripSurroundingStyleTags
, onDone: s => ``
, placeholder: `blockquote {
font-style: italic;
diff --git a/src/renderPreview/renderPreviewImpl.ts b/src/renderPreview/renderPreviewImpl.ts
index ac9e54a..040ac1e 100644
--- a/src/renderPreview/renderPreviewImpl.ts
+++ b/src/renderPreview/renderPreviewImpl.ts
@@ -114,7 +114,7 @@ const renderAndSwap = async (
export const renderPlain = async (doc: Doc, previewDiv: HTMLDivElement): Promise => {
const { contentWindow } = await setupSingleFrame(previewDiv);
const content = [
- ''
+ ''
, doc.meta['header-includes']
, doc.html
].join('')
@@ -156,7 +156,7 @@ const pagedjsStyleEl = createStyleEl(`
export const renderPaged = async (doc: Doc, previewDiv: HTMLDivElement): Promise => {
return renderAndSwap(previewDiv, async frameWindow => {
- const cssStr = getCss(doc)
+ const cssStr = await getCss(doc)
, metaHtml = doc.meta['header-includes']
, content = doc.html
, frameHead = frameWindow.document.head
diff --git a/src/renderPreview/templates/getCss.ts b/src/renderPreview/templates/getCss.ts
index 320307a..81052f6 100644
--- a/src/renderPreview/templates/getCss.ts
+++ b/src/renderPreview/templates/getCss.ts
@@ -7,5 +7,27 @@ const template = parseToTemplate(styles)
export const defaultVars = extractDefaultVars(template)
-export const getCss = (doc: Doc) =>
- interpolateTemplate(template, doc.meta)
+let headerIncludes = ''
+let docType: string | undefined
+const getHeaderIncludesCss = async (doc: Doc): Promise => {
+ let newDocType = doc.meta.type
+ if (typeof newDocType !== 'string') {
+ newDocType = 'default'
+ }
+ if (newDocType !== docType && window.ipcApi) {
+ // cache css
+ docType = newDocType
+ const meta = await window.ipcApi.readDataDirFile(docType + '.yaml')
+ const field = meta?.['header-includes']
+ headerIncludes = typeof field === 'string'
+ ? stripSurroundingStyleTags(field)
+ : ''
+ }
+ return headerIncludes
+}
+
+export const getCss = async (doc: Doc): Promise =>
+ interpolateTemplate(template, doc.meta) + (await getHeaderIncludesCss(doc))
+
+export const stripSurroundingStyleTags = (s: string): string =>
+ s.startsWith('') ? s.slice(8, -9) : s