import path from 'path' import fileUrl from 'file-url' import fs from 'fs' import { remote } from 'electron' import consts from 'browser/lib/consts' import Markdown from 'browser/lib/markdown' import attachmentManagement from './attachmentManagement' const { app } = remote const appPath = fileUrl(process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()) let markdownStyle = '' try { markdownStyle = require('!!css!stylus?sourceMap!../../../components/markdown.styl')[0][1] } catch (e) {} export const CSS_FILES = [ `${appPath}/node_modules/katex/dist/katex.min.css`, `${appPath}/node_modules/codemirror/lib/codemirror.css` ] const defaultFontFamily = ['helvetica', 'arial', 'sans-serif'] if (global.process.platform !== 'darwin') { defaultFontFamily.unshift('Microsoft YaHei') defaultFontFamily.unshift('meiryo') } const defaultCodeBlockFontFamily = [ 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace' ] function unprefix (file) { if (global.process.platform === 'win32') { return file.replace('file:///', '') } else { return file.replace('file://', '') } } /** * ``` * { * fontFamily, * fontSize, * lineNumber, * codeBlockFontFamily, * codeBlockTheme, * scrollPastEnd, * theme, * allowCustomCSS, * customCSS * smartQuotes, * sanitize, * breaks, * storagePath, * export, * indentSize * } * ``` */ export default function formatHTML (props) { const { fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = getStyleParams(props) const inlineStyles = buildStyle( fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS ) const { smartQuotes, sanitize, breaks } = props let indentSize = parseInt(props.indentSize, 10) if (!(indentSize > 0 && indentSize < 132)) { indentSize = 4 } const files = [getCodeThemeLink(codeBlockTheme), ...CSS_FILES] return function (note, targetPath, exportTasks) { let styles = files.map(file => ``).join('\n') let inlineScripts = '' let scripts = '' let lodash = false function addLodash () { if (lodash) { return } lodash = true exportTasks.push({ src: unprefix(`${appPath}/node_modules/lodash/lodash.min.js`), dst: 'js' }) scripts += `` } let codemirror = false function addCodeMirror () { if (codemirror) { return } codemirror = true addLodash() exportTasks.push({ src: unprefix(`${appPath}/node_modules/codemirror/lib/codemirror.js`), dst: 'js/codemirror' }, { src: unprefix(`${appPath}/node_modules/codemirror/mode/meta.js`), dst: 'js/codemirror/mode' }, { src: unprefix(`${appPath}/node_modules/codemirror/addon/mode/loadmode.js`), dst: 'js/codemirror/addon/mode' }, { src: unprefix(`${appPath}/node_modules/codemirror/addon/runmode/runmode.js`), dst: 'js/codemirror/addon/runmode' }) scripts += ` ` let className = `cm-s-${codeBlockTheme}` if (codeBlockTheme.indexOf('solarized') === 0) { const [refThema, color] = codeBlockTheme.split(' ') className = `cm-s-${refThema} cm-s-${color}` } inlineScripts += ` CodeMirror.modeURL = 'js/codemirror/mode/%N/%N.js'; function decodeEntities (text) { var entities = [ ['apos', '\\''], ['amp', '&'], ['lt', '<'], ['gt', '>'], ['#63', '\\?'], ['#36', '\\$'] ] for (var i = 0, max = entities.length; i < max; ++i) { text = text.replace(new RegExp(\`&\${entities[i][0]};\`, 'g'), entities[i][1]) } return text } function displayCodeBlocks () { _.forEach( document.querySelectorAll('.code code'), el => { let syntax = CodeMirror.findModeByName(el.className) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') CodeMirror.requireMode(syntax.mode, () => { const content = decodeEntities(el.innerHTML) el.innerHTML = '' el.parentNode.className += ' ${className}' CodeMirror.runMode(content, syntax.mime, el, { tabSize: ${indentSize} }) }) } ) } document.addEventListener('DOMContentLoaded', displayCodeBlocks); ` } const modes = {} const markdown = new Markdown({ typographer: smartQuotes, sanitize, breaks, onFence (type, mode) { if (type === 'code') { addCodeMirror() if (mode && modes[mode] !== true) { const file = unprefix(`${appPath}/node_modules/codemirror/mode/${mode}/${mode}.js`) if (fs.existsSync(file)) { exportTasks.push({ src: file, dst: `js/codemirror/mode/${mode}` }) modes[mode] = true } } } } }) let body = markdown.render(note.content) const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(note.content, props.storagePath) files.forEach(file => { exportTasks.push({ src: unprefix(file), dst: 'css' }) }) const destinationFolder = props.export.prefixAttachmentFolder ? `${path.parse(targetPath).name} - ${attachmentManagement.DESTINATION_FOLDER}` : attachmentManagement.DESTINATION_FOLDER attachmentsAbsolutePaths.forEach(attachment => { exportTasks.push({ src: attachment, dst: destinationFolder }) }) body = attachmentManagement.replaceStorageReferences(body, note.key, destinationFolder) return ` ${styles} ${scripts} ${body} ` } } export function getStyleParams (props) { const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = props let { fontFamily, codeBlockFontFamily } = props fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0 ? fontFamily .split(',') .map(fontName => fontName.trim()) .concat(defaultFontFamily) : defaultFontFamily codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0 ? codeBlockFontFamily .split(',') .map(fontName => fontName.trim()) .concat(defaultCodeBlockFontFamily) : defaultCodeBlockFontFamily return { fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } } export function getCodeThemeLink (theme) { if (consts.THEMES.some(_theme => _theme === theme)) { theme = theme !== 'default' ? theme : 'elegant' } return theme.startsWith('solarized') ? `${appPath}/node_modules/codemirror/theme/solarized.css` : `${appPath}/node_modules/codemirror/theme/${theme}.css` } export function buildStyle ( fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS ) { return ` @font-face { font-family: 'Lato'; src: url('${appPath}/resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */ url('${appPath}/resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */ url('${appPath}/resources/fonts/Lato-Regular.ttf') format('truetype'); font-style: normal; font-weight: normal; text-rendering: optimizeLegibility; } @font-face { font-family: 'Lato'; src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */ url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */ url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype'); font-style: normal; font-weight: 700; text-rendering: optimizeLegibility; } @font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; src: local('Material Icons'), local('MaterialIcons-Regular'), url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'), url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'), url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype'); } ${markdownStyle} body { font-family: '${fontFamily.join("','")}'; font-size: ${fontSize}px; ${scrollPastEnd && 'padding-bottom: 90vh;'} } @media print { body { padding-bottom: initial; } } code { font-family: '${codeBlockFontFamily.join("','")}'; background-color: rgba(0,0,0,0.04); } .lineNumber { ${lineNumber && 'display: block !important;'} font-family: '${codeBlockFontFamily.join("','")}'; } .clipboardButton { color: rgba(147,147,149,0.8);; fill: rgba(147,147,149,1);; border-radius: 50%; margin: 0px 10px; border: none; background-color: transparent; outline: none; height: 15px; width: 15px; cursor: pointer; } .clipboardButton:hover { transition: 0.2s; color: #939395; fill: #939395; background-color: rgba(0,0,0,0.1); } h1, h2 { border: none; } h1 { padding-bottom: 4px; margin: 1em 0 8px; } h2 { padding-bottom: 0.2em; margin: 1em 0 0.37em; } body p { white-space: normal; } @media print { body[data-theme="${theme}"] { color: #000; background-color: #fff; } .clipboardButton { display: none } } ${allowCustomCSS ? customCSS : ''} ` }