From d76b7235db4ee61e957b8ff8061e37a6f2fb9981 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 15 Dec 2018 12:42:39 +0100 Subject: [PATCH] export styles of code blocks --- browser/components/MarkdownSplitEditor.js | 1 + browser/lib/markdown.js | 13 +- browser/main/SideNav/StorageItem.js | 14 -- browser/main/lib/dataApi/copyFile.js | 8 +- browser/main/lib/dataApi/exportFolder.js | 1 + browser/main/lib/dataApi/exportNote.js | 4 +- browser/main/lib/dataApi/exportStorage.js | 1 + browser/main/lib/dataApi/formatHTML.js | 183 +++++++++++++++++++--- 8 files changed, 179 insertions(+), 46 deletions(-) diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 5eb476ff..79fb0aa7 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -187,6 +187,7 @@ class MarkdownSplitEditor extends React.Component { codeBlockTheme={config.preview.codeBlockTheme} codeBlockFontFamily={config.editor.fontFamily} lineNumber={config.preview.lineNumber} + indentSize={editorIndentSize} scrollPastEnd={config.preview.scrollPastEnd} smartQuotes={config.preview.smartQuotes} smartArrows={config.preview.smartArrows} diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 2a7b66b0..a7de2abc 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -28,7 +28,8 @@ class Markdown { html: true, xhtmlOut: true, breaks: config.preview.breaks, - sanitize: 'STRICT' + sanitize: 'STRICT', + onFence: () => {} } const updatedOptions = Object.assign(defaultOptions, options) @@ -141,30 +142,40 @@ class Markdown { token.parameters.format = 'yaml' } + updatedOptions.onFence('chart', token.parameters.format) + return `
           ${token.fileName}
           
${token.content}
` }, flowchart: token => { + updatedOptions.onFence('flowchart') + return `
           ${token.fileName}
           
${token.content}
` }, mermaid: token => { + updatedOptions.onFence('mermaid') + return `
           ${token.fileName}
           
${token.content}
` }, sequence: token => { + updatedOptions.onFence('sequence') + return `
           ${token.fileName}
           
${token.content}
` } }, token => { + updatedOptions.onFence('code', token.langType) + return `
         ${token.fileName}
         ${createGutter(token.content, token.firstLineNumber)}
diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js
index 595efd85..ae86e5be 100644
--- a/browser/main/SideNav/StorageItem.js
+++ b/browser/main/SideNav/StorageItem.js
@@ -230,20 +230,6 @@ class StorageItem extends React.Component {
               folderKey: data.folderKey,
               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 => {
             dialog.showErrorBox(
diff --git a/browser/main/lib/dataApi/copyFile.js b/browser/main/lib/dataApi/copyFile.js
index 6f23aae2..f079c7db 100755
--- a/browser/main/lib/dataApi/copyFile.js
+++ b/browser/main/lib/dataApi/copyFile.js
@@ -1,5 +1,6 @@
-const fs = require('fs')
-const path = require('path')
+import fs from 'fs'
+import fx from 'fs-extra'
+import path from 'path'
 
 /**
  * @description Copy a file from source to destination
@@ -14,7 +15,8 @@ function copyFile (srcPath, dstPath) {
 
   return new Promise((resolve, reject) => {
     const dstFolder = path.dirname(dstPath)
-    if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
+
+    fx.ensureDirSync(dstFolder)
 
     const input = fs.createReadStream(decodeURI(srcPath))
     const output = fs.createWriteStream(dstPath)
diff --git a/browser/main/lib/dataApi/exportFolder.js b/browser/main/lib/dataApi/exportFolder.js
index aef7bd8e..63b5c3a1 100644
--- a/browser/main/lib/dataApi/exportFolder.js
+++ b/browser/main/lib/dataApi/exportFolder.js
@@ -55,6 +55,7 @@ function exportFolder (storageKey, folderKey, fileType, exportDir, config) {
           codeBlockTheme: config.preview.codeBlockTheme,
           codeBlockFontFamily: config.editor.fontFamily,
           lineNumber: config.preview.lineNumber,
+          indentSize: config.editor.indentSize,
           scrollPastEnd: config.preview.scrollPastEnd,
           smartQuotes: config.preview.smartQuotes,
           breaks: config.preview.breaks,
diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js
index 2130e94d..f17e06c4 100755
--- a/browser/main/lib/dataApi/exportNote.js
+++ b/browser/main/lib/dataApi/exportNote.js
@@ -81,14 +81,14 @@ function rollbackExport (tasks) {
     }
 
     if (fs.existsSync(fullpath)) {
-      fs.unlink(fullpath)
+      fs.unlinkSync(fullpath)
       folders.add(path.dirname(fullpath))
     }
   })
 
   folders.forEach((folder) => {
     if (fs.readdirSync(folder).length === 0) {
-      fs.rmdir(folder)
+      fs.rmdirSync(folder)
     }
   })
 }
diff --git a/browser/main/lib/dataApi/exportStorage.js b/browser/main/lib/dataApi/exportStorage.js
index 1bcacd51..fab3aee2 100644
--- a/browser/main/lib/dataApi/exportStorage.js
+++ b/browser/main/lib/dataApi/exportStorage.js
@@ -54,6 +54,7 @@ function exportStorage (storageKey, fileType, exportDir, config) {
           codeBlockTheme: config.preview.codeBlockTheme,
           codeBlockFontFamily: config.editor.fontFamily,
           lineNumber: config.preview.lineNumber,
+          indentSize: config.editor.indentSize,
           scrollPastEnd: config.preview.scrollPastEnd,
           smartQuotes: config.preview.smartQuotes,
           breaks: config.preview.breaks,
diff --git a/browser/main/lib/dataApi/formatHTML.js b/browser/main/lib/dataApi/formatHTML.js
index de99421f..eb44e9ce 100644
--- a/browser/main/lib/dataApi/formatHTML.js
+++ b/browser/main/lib/dataApi/formatHTML.js
@@ -1,5 +1,6 @@
 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'
@@ -33,6 +34,14 @@ const defaultCodeBlockFontFamily = [
   '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,
  *   breaks,
  *   storagePath,
- *   export
+ *   export,
+ *   indentSize
  * }
  * ```
  */
@@ -76,29 +86,149 @@ export default function formatHTML (props) {
     allowCustomCSS,
     customCSS
   )
+
   const { smartQuotes, sanitize, breaks } = props
 
-  const markdown = new Markdown({
-    typographer: smartQuotes,
-    sanitize,
-    breaks
-  })
+  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 => {
-      if (global.process.platform === 'win32') {
-        file = file.replace('file:///', '')
-      } else {
-        file = file.replace('file://', '')
-      }
       exportTasks.push({
-        src: file,
+        src: unprefix(file),
         dst: 'css'
       })
     })
@@ -114,20 +244,21 @@ export default function formatHTML (props) {
 
     body = attachmentManagement.replaceStorageReferences(body, note.key, destinationFolder)
 
-    let styles = ''
-    files.forEach(file => {
-      styles += ``
-    })
-
-    return `
-                
-                  
-                  
-                  
-                  ${styles}
-                
-                ${body}
-            `
+    return `
+
+
+  
+  
+  
+  ${styles}
+  ${scripts}
+  
+
+
+${body}
+
+
+`
   }
 }