mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
export styles of code blocks
This commit is contained in:
@@ -187,6 +187,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
codeBlockTheme={config.preview.codeBlockTheme}
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
codeBlockFontFamily={config.editor.fontFamily}
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
smartArrows={config.preview.smartArrows}
|
smartArrows={config.preview.smartArrows}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ class Markdown {
|
|||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
breaks: config.preview.breaks,
|
breaks: config.preview.breaks,
|
||||||
sanitize: 'STRICT'
|
sanitize: 'STRICT',
|
||||||
|
onFence: () => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedOptions = Object.assign(defaultOptions, options)
|
const updatedOptions = Object.assign(defaultOptions, options)
|
||||||
@@ -141,30 +142,40 @@ class Markdown {
|
|||||||
token.parameters.format = 'yaml'
|
token.parameters.format = 'yaml'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedOptions.onFence('chart', token.parameters.format)
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
|
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
flowchart: token => {
|
flowchart: token => {
|
||||||
|
updatedOptions.onFence('flowchart')
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
mermaid: token => {
|
mermaid: token => {
|
||||||
|
updatedOptions.onFence('mermaid')
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
|
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
sequence: token => {
|
sequence: token => {
|
||||||
|
updatedOptions.onFence('sequence')
|
||||||
|
|
||||||
return `<pre class="fence" data-line="${token.map[0]}">
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
|
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
}
|
}
|
||||||
}, token => {
|
}, token => {
|
||||||
|
updatedOptions.onFence('code', token.langType)
|
||||||
|
|
||||||
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||||
<span class="filename">${token.fileName}</span>
|
<span class="filename">${token.fileName}</span>
|
||||||
${createGutter(token.content, token.firstLineNumber)}
|
${createGutter(token.content, token.firstLineNumber)}
|
||||||
|
|||||||
@@ -230,20 +230,6 @@ class StorageItem extends React.Component {
|
|||||||
folderKey: data.folderKey,
|
folderKey: data.folderKey,
|
||||||
fileType: data.fileType
|
fileType: data.fileType
|
||||||
})
|
})
|
||||||
return data
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
|
||||||
type: 'info',
|
|
||||||
message: 'Exported to "' + data.exportDir + '"'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
dialog.showErrorBox(
|
|
||||||
'Export error',
|
|
||||||
err ? err.message || err : 'Unexpected error during export'
|
|
||||||
)
|
|
||||||
throw err
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
dialog.showErrorBox(
|
dialog.showErrorBox(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const fs = require('fs')
|
import fs from 'fs'
|
||||||
const path = require('path')
|
import fx from 'fs-extra'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Copy a file from source to destination
|
* @description Copy a file from source to destination
|
||||||
@@ -14,7 +15,8 @@ function copyFile (srcPath, dstPath) {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const dstFolder = path.dirname(dstPath)
|
const dstFolder = path.dirname(dstPath)
|
||||||
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
|
|
||||||
|
fx.ensureDirSync(dstFolder)
|
||||||
|
|
||||||
const input = fs.createReadStream(decodeURI(srcPath))
|
const input = fs.createReadStream(decodeURI(srcPath))
|
||||||
const output = fs.createWriteStream(dstPath)
|
const output = fs.createWriteStream(dstPath)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ function exportFolder (storageKey, folderKey, fileType, exportDir, config) {
|
|||||||
codeBlockTheme: config.preview.codeBlockTheme,
|
codeBlockTheme: config.preview.codeBlockTheme,
|
||||||
codeBlockFontFamily: config.editor.fontFamily,
|
codeBlockFontFamily: config.editor.fontFamily,
|
||||||
lineNumber: config.preview.lineNumber,
|
lineNumber: config.preview.lineNumber,
|
||||||
|
indentSize: config.editor.indentSize,
|
||||||
scrollPastEnd: config.preview.scrollPastEnd,
|
scrollPastEnd: config.preview.scrollPastEnd,
|
||||||
smartQuotes: config.preview.smartQuotes,
|
smartQuotes: config.preview.smartQuotes,
|
||||||
breaks: config.preview.breaks,
|
breaks: config.preview.breaks,
|
||||||
|
|||||||
@@ -81,14 +81,14 @@ function rollbackExport (tasks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(fullpath)) {
|
if (fs.existsSync(fullpath)) {
|
||||||
fs.unlink(fullpath)
|
fs.unlinkSync(fullpath)
|
||||||
folders.add(path.dirname(fullpath))
|
folders.add(path.dirname(fullpath))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
folders.forEach((folder) => {
|
folders.forEach((folder) => {
|
||||||
if (fs.readdirSync(folder).length === 0) {
|
if (fs.readdirSync(folder).length === 0) {
|
||||||
fs.rmdir(folder)
|
fs.rmdirSync(folder)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ function exportStorage (storageKey, fileType, exportDir, config) {
|
|||||||
codeBlockTheme: config.preview.codeBlockTheme,
|
codeBlockTheme: config.preview.codeBlockTheme,
|
||||||
codeBlockFontFamily: config.editor.fontFamily,
|
codeBlockFontFamily: config.editor.fontFamily,
|
||||||
lineNumber: config.preview.lineNumber,
|
lineNumber: config.preview.lineNumber,
|
||||||
|
indentSize: config.editor.indentSize,
|
||||||
scrollPastEnd: config.preview.scrollPastEnd,
|
scrollPastEnd: config.preview.scrollPastEnd,
|
||||||
smartQuotes: config.preview.smartQuotes,
|
smartQuotes: config.preview.smartQuotes,
|
||||||
breaks: config.preview.breaks,
|
breaks: config.preview.breaks,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fileUrl from 'file-url'
|
import fileUrl from 'file-url'
|
||||||
|
import fs from 'fs'
|
||||||
import { remote } from 'electron'
|
import { remote } from 'electron'
|
||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import Markdown from 'browser/lib/markdown'
|
import Markdown from 'browser/lib/markdown'
|
||||||
@@ -33,6 +34,14 @@ const defaultCodeBlockFontFamily = [
|
|||||||
'monospace'
|
'monospace'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function unprefix (file) {
|
||||||
|
if (global.process.platform === 'win32') {
|
||||||
|
return file.replace('file:///', '')
|
||||||
|
} else {
|
||||||
|
return file.replace('file://', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ```
|
* ```
|
||||||
* {
|
* {
|
||||||
@@ -49,7 +58,8 @@ const defaultCodeBlockFontFamily = [
|
|||||||
* sanitize,
|
* sanitize,
|
||||||
* breaks,
|
* breaks,
|
||||||
* storagePath,
|
* storagePath,
|
||||||
* export
|
* export,
|
||||||
|
* indentSize
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@@ -76,29 +86,149 @@ export default function formatHTML (props) {
|
|||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS
|
customCSS
|
||||||
)
|
)
|
||||||
|
|
||||||
const { smartQuotes, sanitize, breaks } = props
|
const { smartQuotes, sanitize, breaks } = props
|
||||||
|
|
||||||
const markdown = new Markdown({
|
let indentSize = parseInt(props.indentSize, 10)
|
||||||
typographer: smartQuotes,
|
if (!(indentSize > 0 && indentSize < 132)) {
|
||||||
sanitize,
|
indentSize = 4
|
||||||
breaks
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const files = [getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
|
|
||||||
return function (note, targetPath, exportTasks) {
|
return function (note, targetPath, exportTasks) {
|
||||||
|
let styles = files.map(file => `<link rel="stylesheet" href="css/${path.basename(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 += `<script src="js/lodash.min.js"></script>`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 += `
|
||||||
|
<script src="js/codemirror/codemirror.js"></script>
|
||||||
|
<script src="js/codemirror/mode/meta.js"></script>
|
||||||
|
<script src="js/codemirror/addon/mode/loadmode.js"></script>
|
||||||
|
<script src="js/codemirror/addon/runmode/runmode.js"></script>
|
||||||
|
`
|
||||||
|
|
||||||
|
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)
|
let body = markdown.render(note.content)
|
||||||
|
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(note.content, props.storagePath)
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(note.content, props.storagePath)
|
||||||
|
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
if (global.process.platform === 'win32') {
|
|
||||||
file = file.replace('file:///', '')
|
|
||||||
} else {
|
|
||||||
file = file.replace('file://', '')
|
|
||||||
}
|
|
||||||
exportTasks.push({
|
exportTasks.push({
|
||||||
src: file,
|
src: unprefix(file),
|
||||||
dst: 'css'
|
dst: 'css'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -114,20 +244,21 @@ export default function formatHTML (props) {
|
|||||||
|
|
||||||
body = attachmentManagement.replaceStorageReferences(body, note.key, destinationFolder)
|
body = attachmentManagement.replaceStorageReferences(body, note.key, destinationFolder)
|
||||||
|
|
||||||
let styles = ''
|
return `
|
||||||
files.forEach(file => {
|
<html>
|
||||||
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
<head>
|
||||||
})
|
<meta charset="UTF-8">
|
||||||
|
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||||
return `<html>
|
<style id="style">${inlineStyles}</style>
|
||||||
<head>
|
${styles}
|
||||||
<meta charset="UTF-8">
|
${scripts}
|
||||||
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
<script>${inlineScripts}</script>
|
||||||
<style id="style">${inlineStyles}</style>
|
</head>
|
||||||
${styles}
|
<body>
|
||||||
</head>
|
${body}
|
||||||
<body>${body}</body>
|
</body>
|
||||||
</html>`
|
</html>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user