mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
40 Commits
v0.16.0
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14fafc8b96 | ||
|
|
58c4a78be1 | ||
|
|
2603dfc1ed | ||
|
|
2df590600b | ||
|
|
ef20a8f3e5 | ||
|
|
3e405e1abf | ||
|
|
553832bdfa | ||
|
|
18d65d999a | ||
|
|
3b5eff582a | ||
|
|
85d09b3b3d | ||
|
|
8958e67fcf | ||
|
|
47b796909a | ||
|
|
67d76abdfa | ||
|
|
d75d68ba72 | ||
|
|
323be6b72d | ||
|
|
031a113338 | ||
|
|
b50c5386a6 | ||
|
|
65777b1d56 | ||
|
|
fe728874ac | ||
|
|
bd9b1306b1 | ||
|
|
0ca18d8ca5 | ||
|
|
87a530612f | ||
|
|
f4259bb4d0 | ||
|
|
aa8b589569 | ||
|
|
febc98c101 | ||
|
|
b678c3bd89 | ||
|
|
80b8948433 | ||
|
|
5414fe3384 | ||
|
|
db4016385d | ||
|
|
9d43e34cfa | ||
|
|
fa157f6f76 | ||
|
|
d6a54b8a26 | ||
|
|
9813412c8e | ||
|
|
d76b7235db | ||
|
|
418a789568 | ||
|
|
2d941c3ea3 | ||
|
|
e723d4cd59 | ||
|
|
9e770ef357 | ||
|
|
c796b3b30e | ||
|
|
168fe212f5 |
@@ -63,7 +63,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
ipcRenderer.send('editor:focused', true)
|
ipcRenderer.send('editor:focused', true)
|
||||||
}
|
}
|
||||||
const debouncedDeletionOfAttachments = _.debounce(
|
this.debouncedDeletionOfAttachments = _.debounce(
|
||||||
attachmentManagement.deleteAttachmentsNotPresentInNote,
|
attachmentManagement.deleteAttachmentsNotPresentInNote,
|
||||||
30000
|
30000
|
||||||
)
|
)
|
||||||
@@ -80,7 +80,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
const { storageKey, noteKey } = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
if (this.props.deleteUnusedAttachments === true) {
|
if (this.props.deleteUnusedAttachments === true) {
|
||||||
debouncedDeletionOfAttachments(
|
this.debouncedDeletionOfAttachments(
|
||||||
this.editor.getValue(),
|
this.editor.getValue(),
|
||||||
storageKey,
|
storageKey,
|
||||||
noteKey
|
noteKey
|
||||||
@@ -810,6 +810,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleChange(editor, changeObject) {
|
handleChange(editor, changeObject) {
|
||||||
|
this.debouncedDeletionOfAttachments.cancel()
|
||||||
|
|
||||||
spellcheck.handleChange(editor, changeObject)
|
spellcheck.handleChange(editor, changeObject)
|
||||||
|
|
||||||
// The current note contains an toc. We'll check for changes on headlines.
|
// The current note contains an toc. We'll check for changes on headlines.
|
||||||
|
|||||||
@@ -323,6 +323,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
storageKey,
|
storageKey,
|
||||||
noteKey,
|
noteKey,
|
||||||
linesHighlighted,
|
linesHighlighted,
|
||||||
|
getNote,
|
||||||
RTL
|
RTL
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
@@ -426,6 +427,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
|
getNote={getNote}
|
||||||
|
export={config.export}
|
||||||
onDrop={e => this.handleDropImage(e)}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,258 +18,30 @@ import convertModeName from 'browser/lib/convertModeName'
|
|||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import mdurl from 'mdurl'
|
import mdurl from 'mdurl'
|
||||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||||
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
import formatMarkdown from 'browser/main/lib/dataApi/formatMarkdown'
|
||||||
|
import formatHTML, {
|
||||||
|
CSS_FILES,
|
||||||
|
buildStyle,
|
||||||
|
getCodeThemeLink,
|
||||||
|
getStyleParams,
|
||||||
|
escapeHtmlCharactersInCodeTag
|
||||||
|
} from 'browser/main/lib/dataApi/formatHTML'
|
||||||
|
import formatPDF from 'browser/main/lib/dataApi/formatPDF'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import path from 'path'
|
||||||
|
import { remote, shell } from 'electron'
|
||||||
|
import attachmentManagement from '../main/lib/dataApi/attachmentManagement'
|
||||||
|
import filenamify from 'filenamify'
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
import Carousel from 'react-image-carousel'
|
import Carousel from 'react-image-carousel'
|
||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
import ConfigManager from '../main/lib/ConfigManager'
|
import ConfigManager from '../main/lib/ConfigManager'
|
||||||
import uiThemes from 'browser/lib/ui-themes'
|
import uiThemes from 'browser/lib/ui-themes'
|
||||||
import i18n from 'browser/lib/i18n'
|
import { buildMarkdownPreviewContextMenu } from 'browser/lib/contextMenuBuilder'
|
||||||
|
|
||||||
const { remote, shell } = require('electron')
|
|
||||||
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
|
||||||
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder')
|
|
||||||
.buildMarkdownPreviewContextMenu
|
|
||||||
|
|
||||||
const { app } = remote
|
|
||||||
const path = require('path')
|
|
||||||
const fileUrl = require('file-url')
|
|
||||||
|
|
||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
|
||||||
const appPath = fileUrl(
|
|
||||||
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
|
|
||||||
)
|
|
||||||
const CSS_FILES = [
|
|
||||||
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
|
||||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`,
|
|
||||||
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {String} opts.fontFamily
|
|
||||||
* @param {Numberl} opts.fontSize
|
|
||||||
* @param {String} opts.codeBlockFontFamily
|
|
||||||
* @param {String} opts.theme
|
|
||||||
* @param {Boolean} [opts.lineNumber] Should show line number
|
|
||||||
* @param {Boolean} [opts.scrollPastEnd]
|
|
||||||
* @param {Boolean} [opts.allowCustomCSS] Should add custom css
|
|
||||||
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
function buildStyle(opts) {
|
|
||||||
const {
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
codeBlockFontFamily,
|
|
||||||
lineNumber,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
} = opts
|
|
||||||
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;
|
|
||||||
box-sizing: border-box;
|
|
||||||
`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
${RTL ? 'direction: rtl;' : ''}
|
|
||||||
${RTL ? 'text-align: right;' : ''}
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
body {
|
|
||||||
padding-bottom: initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
font-family: '${codeBlockFontFamily.join("','")}';
|
|
||||||
background-color: rgba(0,0,0,0.04);
|
|
||||||
text-align: left;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
p code,
|
|
||||||
li code,
|
|
||||||
td code
|
|
||||||
{
|
|
||||||
padding: 2px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
[data-theme="default"] p code,
|
|
||||||
[data-theme="default"] li code,
|
|
||||||
[data-theme="default"] td code
|
|
||||||
{
|
|
||||||
background-color: #F4F4F4;
|
|
||||||
border-color: #d9d9d9;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
[data-theme="white"] p code,
|
|
||||||
[data-theme="white"] li code,
|
|
||||||
[data-theme="white"] td code
|
|
||||||
{
|
|
||||||
background-color: #F4F4F4;
|
|
||||||
border-color: #d9d9d9;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] p code,
|
|
||||||
[data-theme="dark"] li code,
|
|
||||||
[data-theme="dark"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="dracula"] p code,
|
|
||||||
[data-theme="dracula"] li code,
|
|
||||||
[data-theme="dracula"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="monokai"] p code,
|
|
||||||
[data-theme="monokai"] li code,
|
|
||||||
[data-theme="monokai"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="nord"] p code,
|
|
||||||
[data-theme="nord"] li code,
|
|
||||||
[data-theme="nord"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="solarized-dark"] p code,
|
|
||||||
[data-theme="solarized-dark"] li code,
|
|
||||||
[data-theme="solarized-dark"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
[data-theme="vulcan"] p code,
|
|
||||||
[data-theme="vulcan"] li code,
|
|
||||||
[data-theme="vulcan"] td code
|
|
||||||
{
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #555;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 1em 0 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4, h5, h6 {
|
|
||||||
margin: 1.1em 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
padding: 0.2em 0 0.2em;
|
|
||||||
margin: 1em 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
padding: 0.2em 0 0.2em;
|
|
||||||
margin: 1em 0 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body p {
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
body[data-theme="${theme}"] {
|
|
||||||
color: #000;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
.clipboardButton {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
${allowCustomCSS ? customCSS : ''}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollBarStyle = `
|
const scrollBarStyle = `
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
${config.get().ui.showScrollBar ? '' : 'display: none;'}
|
||||||
@@ -301,22 +73,6 @@ const scrollBarDarkStyle = `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
|
||||||
|
|
||||||
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
|
||||||
if (!OSX) {
|
|
||||||
defaultFontFamily.unshift('Microsoft YaHei')
|
|
||||||
defaultFontFamily.unshift('meiryo')
|
|
||||||
}
|
|
||||||
const defaultCodeBlockFontFamily = [
|
|
||||||
'Monaco',
|
|
||||||
'Menlo',
|
|
||||||
'Ubuntu Mono',
|
|
||||||
'Consolas',
|
|
||||||
'source-code-pro',
|
|
||||||
'monospace'
|
|
||||||
]
|
|
||||||
|
|
||||||
// return the line number of the line that used to generate the specified element
|
// return the line number of the line that used to generate the specified element
|
||||||
// return -1 if the line is not found
|
// return -1 if the line is not found
|
||||||
function getSourceLineNumberByElement(element) {
|
function getSourceLineNumberByElement(element) {
|
||||||
@@ -430,94 +186,15 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsMd() {
|
handleSaveAsMd() {
|
||||||
this.exportAsDocument('md')
|
this.exportAsDocument('md', formatMarkdown(this.props))
|
||||||
}
|
|
||||||
|
|
||||||
htmlContentFormatter(noteContent, exportTasks, targetDir) {
|
|
||||||
const {
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
codeBlockFontFamily,
|
|
||||||
lineNumber,
|
|
||||||
codeBlockTheme,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
} = this.getStyleParams()
|
|
||||||
|
|
||||||
const inlineStyles = buildStyle({
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
codeBlockFontFamily,
|
|
||||||
lineNumber,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
})
|
|
||||||
let body = this.refs.root.contentWindow.document.body.innerHTML
|
|
||||||
body = attachmentManagement.fixLocalURLS(body, this.props.storagePath)
|
|
||||||
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
|
||||||
files.forEach(file => {
|
|
||||||
if (global.process.platform === 'win32') {
|
|
||||||
file = file.replace('file:///', '')
|
|
||||||
} else {
|
|
||||||
file = file.replace('file://', '')
|
|
||||||
}
|
|
||||||
exportTasks.push({
|
|
||||||
src: file,
|
|
||||||
dst: 'css'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let styles = ''
|
|
||||||
files.forEach(file => {
|
|
||||||
styles += `<link rel="stylesheet" href="../css/${path.basename(file)}">`
|
|
||||||
})
|
|
||||||
|
|
||||||
return `<html>
|
|
||||||
<head>
|
|
||||||
<base href="file://${targetDir}/">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
|
||||||
<style id="style">${inlineStyles}</style>
|
|
||||||
${styles}
|
|
||||||
</head>
|
|
||||||
<body>${body}</body>
|
|
||||||
</html>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsHtml() {
|
handleSaveAsHtml() {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) =>
|
this.exportAsDocument('html', formatHTML(this.props))
|
||||||
Promise.resolve(
|
|
||||||
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsPdf() {
|
handleSaveAsPdf() {
|
||||||
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
|
this.exportAsDocument('pdf', formatPDF(this.props))
|
||||||
const printout = new remote.BrowserWindow({
|
|
||||||
show: false,
|
|
||||||
webPreferences: { webSecurity: false, javascript: false }
|
|
||||||
})
|
|
||||||
printout.loadURL(
|
|
||||||
'data:text/html;charset=UTF-8,' +
|
|
||||||
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
|
|
||||||
)
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
printout.webContents.on('did-finish-load', () => {
|
|
||||||
printout.webContents.printToPDF({}, (err, data) => {
|
|
||||||
if (err) reject(err)
|
|
||||||
else resolve(data)
|
|
||||||
printout.destroy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePrint() {
|
handlePrint() {
|
||||||
@@ -525,18 +202,21 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exportAsDocument(fileType, contentFormatter) {
|
exportAsDocument(fileType, contentFormatter) {
|
||||||
|
const note = this.props.getNote()
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
defaultPath: filenamify(note.title, {
|
||||||
|
replacement: '_'
|
||||||
|
}),
|
||||||
filters: [{ name: 'Documents', extensions: [fileType] }],
|
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const content = this.props.value
|
const storagePath = this.props.storagePath
|
||||||
const storage = this.props.storagePath
|
|
||||||
const nodeKey = this.props.noteKey
|
|
||||||
|
|
||||||
exportNote(nodeKey, storage, content, filename, contentFormatter)
|
exportNote(storagePath, note, filename, contentFormatter)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
@@ -567,32 +247,6 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Convert special characters between three ```
|
|
||||||
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
|
|
||||||
* @returns {string} HTML in which special characters between three ``` have been converted
|
|
||||||
*/
|
|
||||||
escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
|
|
||||||
for (let index = 0; index < splitWithCodeTag.length; index++) {
|
|
||||||
const codeTagRequired =
|
|
||||||
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
|
|
||||||
if (codeTagRequired) {
|
|
||||||
splitWithCodeTag.splice(index + 1, 0, '```')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let inCodeTag = false
|
|
||||||
let result = ''
|
|
||||||
for (let content of splitWithCodeTag) {
|
|
||||||
if (content === '```') {
|
|
||||||
inCodeTag = !inCodeTag
|
|
||||||
} else if (inCodeTag) {
|
|
||||||
content = escapeHtmlCharacters(content)
|
|
||||||
}
|
|
||||||
result += content
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
getScrollBarStyle() {
|
getScrollBarStyle() {
|
||||||
const { theme } = this.props
|
const { theme } = this.props
|
||||||
|
|
||||||
@@ -743,47 +397,6 @@ class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams() {
|
|
||||||
const {
|
|
||||||
fontSize,
|
|
||||||
lineNumber,
|
|
||||||
codeBlockTheme,
|
|
||||||
scrollPastEnd,
|
|
||||||
theme,
|
|
||||||
allowCustomCSS,
|
|
||||||
customCSS,
|
|
||||||
RTL
|
|
||||||
} = this.props
|
|
||||||
let { fontFamily, codeBlockFontFamily } = this.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,
|
|
||||||
RTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyStyle() {
|
applyStyle() {
|
||||||
const {
|
const {
|
||||||
fontFamily,
|
fontFamily,
|
||||||
@@ -796,12 +409,13 @@ class MarkdownPreview extends React.Component {
|
|||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS,
|
customCSS,
|
||||||
RTL
|
RTL
|
||||||
} = this.getStyleParams()
|
} = getStyleParams(this.props)
|
||||||
|
|
||||||
this.getWindow().document.getElementById(
|
this.getWindow().document.getElementById(
|
||||||
'codeTheme'
|
'codeTheme'
|
||||||
).href = this.getCodeThemeLink(codeBlockTheme)
|
).href = getCodeThemeLink(codeBlockTheme)
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle({
|
|
||||||
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
codeBlockFontFamily,
|
codeBlockFontFamily,
|
||||||
@@ -811,15 +425,7 @@ class MarkdownPreview extends React.Component {
|
|||||||
allowCustomCSS,
|
allowCustomCSS,
|
||||||
customCSS,
|
customCSS,
|
||||||
RTL
|
RTL
|
||||||
})
|
)
|
||||||
}
|
|
||||||
|
|
||||||
getCodeThemeLink(name) {
|
|
||||||
const theme = consts.THEMES.find(theme => theme.name === name)
|
|
||||||
|
|
||||||
return theme != null
|
|
||||||
? theme.path
|
|
||||||
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe() {
|
rewriteIframe() {
|
||||||
@@ -853,7 +459,7 @@ class MarkdownPreview extends React.Component {
|
|||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
if (sanitize === 'NONE') {
|
if (sanitize === 'NONE') {
|
||||||
const splitWithCodeTag = value.split('```')
|
const splitWithCodeTag = value.split('```')
|
||||||
value = this.escapeHtmlCharactersInCodeTag(splitWithCodeTag)
|
value = escapeHtmlCharactersInCodeTag(splitWithCodeTag)
|
||||||
}
|
}
|
||||||
const renderedHTML = this.markdown.render(value)
|
const renderedHTML = this.markdown.render(value)
|
||||||
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
||||||
@@ -916,13 +522,9 @@ class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const opts = {}
|
const opts = {}
|
||||||
// if (this.props.theme === 'dark') {
|
|
||||||
// opts['font-color'] = '#DDD'
|
|
||||||
// opts['line-color'] = '#DDD'
|
|
||||||
// opts['element-color'] = '#DDD'
|
|
||||||
// opts['fill'] = '#3A404C'
|
|
||||||
// }
|
|
||||||
_.forEach(
|
_.forEach(
|
||||||
this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
|
this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
|
||||||
el => {
|
el => {
|
||||||
|
|||||||
@@ -336,6 +336,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
storageKey,
|
storageKey,
|
||||||
noteKey,
|
noteKey,
|
||||||
linesHighlighted,
|
linesHighlighted,
|
||||||
|
getNote,
|
||||||
isStacking,
|
isStacking,
|
||||||
RTL
|
RTL
|
||||||
} = this.props
|
} = this.props
|
||||||
@@ -470,6 +471,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}
|
||||||
@@ -486,6 +488,8 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
|
getNote={getNote}
|
||||||
|
export={config.export}
|
||||||
RTL={RTL}
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import markdownItTocAndAnchor from '@hikerpig/markdown-it-toc-and-anchor'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import { lastFindInArray } from './utils'
|
import { escapeHtmlCharacters, lastFindInArray } from './utils'
|
||||||
|
|
||||||
function createGutter(str, firstLineNumber) {
|
function createGutter(str, firstLineNumber) {
|
||||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||||
@@ -31,7 +31,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)
|
||||||
@@ -266,22 +267,26 @@ 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="${
|
<div class="chart" data-height="${
|
||||||
token.parameters.height
|
token.parameters.height
|
||||||
}" data-format="${token.parameters.format || 'json'}">${
|
}" data-format="${token.parameters.format || 'json'}">${
|
||||||
token.content
|
token.content
|
||||||
}</div>
|
}</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}">${
|
<div class="flowchart" data-height="${token.parameters.height}">${
|
||||||
token.content
|
token.content
|
||||||
}</div>
|
}</div>
|
||||||
</pre>`
|
</pre>`
|
||||||
},
|
},
|
||||||
gallery: token => {
|
gallery: token => {
|
||||||
const content = token.content
|
const content = token.content
|
||||||
@@ -298,35 +303,41 @@ class Markdown {
|
|||||||
.join('\n')
|
.join('\n')
|
||||||
|
|
||||||
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="gallery" data-autoplay="${
|
<div class="gallery" data-autoplay="${
|
||||||
token.parameters.autoplay
|
token.parameters.autoplay
|
||||||
}" data-height="${token.parameters.height}">${content}</div>
|
}" data-height="${token.parameters.height}">${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}">${
|
<div class="mermaid" data-height="${token.parameters.height}">${
|
||||||
token.content
|
token.content
|
||||||
}</div>
|
}</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}">${
|
<div class="sequence" data-height="${token.parameters.height}">${
|
||||||
token.content
|
token.content
|
||||||
}</div>
|
}</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)}
|
||||||
<code class="${token.langType}">${token.content}</code>
|
<code class="${token.langType}">${token.content}</code>
|
||||||
</pre>`
|
</pre>`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -468,6 +479,16 @@ class Markdown {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.md.renderer.rules.code_inline = function(tokens, idx) {
|
||||||
|
const token = tokens[idx]
|
||||||
|
|
||||||
|
return (
|
||||||
|
'<code class="inline">' +
|
||||||
|
escapeHtmlCharacters(token.content) +
|
||||||
|
'</code>'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (config.preview.smartArrows) {
|
if (config.preview.smartArrows) {
|
||||||
this.md.use(smartArrows)
|
this.md.use(smartArrows)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
|
||||||
this.generateToc = this.handleGenerateToc.bind(this)
|
this.generateToc = this.handleGenerateToc.bind(this)
|
||||||
|
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||||
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
||||||
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
|
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
|
||||||
|
this.getNote = this.getNote.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
@@ -441,6 +442,10 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNote() {
|
||||||
|
return this.state.note
|
||||||
|
}
|
||||||
|
|
||||||
renderEditor() {
|
renderEditor() {
|
||||||
const { config, ignorePreviewPointerEvents } = this.props
|
const { config, ignorePreviewPointerEvents } = this.props
|
||||||
const { note, isStacking } = this.state
|
const { note, isStacking } = this.state
|
||||||
@@ -456,8 +461,8 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
noteKey={note.key}
|
noteKey={note.key}
|
||||||
linesHighlighted={note.linesHighlighted}
|
linesHighlighted={note.linesHighlighted}
|
||||||
onChange={this.handleUpdateContent}
|
onChange={this.handleUpdateContent}
|
||||||
isLocked={this.state.isLocked}
|
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
getNote={this.getNote}
|
||||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -473,6 +478,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
linesHighlighted={note.linesHighlighted}
|
linesHighlighted={note.linesHighlighted}
|
||||||
onChange={this.handleUpdateContent}
|
onChange={this.handleUpdateContent}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
getNote={this.getNote}
|
||||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { getLocales } from 'browser/lib/Languages'
|
|||||||
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
||||||
import { chooseTheme, applyTheme } from 'browser/main/lib/ThemeManager'
|
import { chooseTheme, applyTheme } from 'browser/main/lib/ThemeManager'
|
||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
|
import { ipcRenderer } from 'electron'
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
@@ -184,6 +185,7 @@ class Main extends React.Component {
|
|||||||
this.toggleMenuBarVisible.bind(this)
|
this.toggleMenuBarVisible.bind(this)
|
||||||
)
|
)
|
||||||
eventEmitter.on('dispatch:push', this.changeRoutePush.bind(this))
|
eventEmitter.on('dispatch:push', this.changeRoutePush.bind(this))
|
||||||
|
eventEmitter.on('update', () => ipcRenderer.send('update-check', 'manual'))
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import Markdown from '../../lib/markdown'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
|
import filenamify from 'filenamify'
|
||||||
import queryString from 'query-string'
|
import queryString from 'query-string'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
@@ -634,6 +635,38 @@ class NoteList extends React.Component {
|
|||||||
this.selectNextNote()
|
this.selectNextNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleExportClick(e, note, fileType) {
|
||||||
|
const options = {
|
||||||
|
defaultPath: filenamify(note.title, {
|
||||||
|
replacement: '_'
|
||||||
|
}),
|
||||||
|
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||||
|
properties: ['openFile', 'createDirectory']
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
||||||
|
if (filename) {
|
||||||
|
const { config } = this.props
|
||||||
|
|
||||||
|
dataApi
|
||||||
|
.exportNoteAs(note, filename, fileType, config)
|
||||||
|
.then(res => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: `Exported to ${filename}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
err ? err.message || err : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleNoteContextMenu(e, uniqueKey) {
|
handleNoteContextMenu(e, uniqueKey) {
|
||||||
const { location } = this.props
|
const { location } = this.props
|
||||||
const { selectedNoteKeys } = this.state
|
const { selectedNoteKeys } = this.state
|
||||||
@@ -689,9 +722,40 @@ class NoteList extends React.Component {
|
|||||||
click: this.copyNoteLink.bind(this, note)
|
click: this.copyNoteLink.bind(this, note)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
|
templates.push(
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export Note'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'txt')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportClick(e, note, 'pdf')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
if (note.blog && note.blog.blogLink && note.blog.blogId) {
|
||||||
templates.push(
|
templates.push(
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: updateLabel,
|
label: updateLabel,
|
||||||
click: this.publishMarkdown.bind(this)
|
click: this.publishMarkdown.bind(this)
|
||||||
@@ -702,10 +766,15 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
templates.push({
|
templates.push(
|
||||||
label: publishLabel,
|
{
|
||||||
click: this.publishMarkdown.bind(this)
|
type: 'separator'
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
label: publishLabel,
|
||||||
|
click: this.publishMarkdown.bind(this)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,12 +43,20 @@ class StorageItem extends React.Component {
|
|||||||
label: i18n.__('Export Storage'),
|
label: i18n.__('Export Storage'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as txt'),
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
click: e => this.handleExportStorageClick(e, 'txt')
|
click: e => this.handleExportStorageClick(e, 'txt')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as md'),
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
click: e => this.handleExportStorageClick(e, 'md')
|
click: e => this.handleExportStorageClick(e, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportStorageClick(e, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportStorageClick(e, 'pdf')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -97,14 +105,28 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
if (paths && paths.length === 1) {
|
if (paths && paths.length === 1) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch, config } = this.props
|
||||||
dataApi.exportStorage(storage.key, fileType, paths[0]).then(data => {
|
dataApi
|
||||||
dispatch({
|
.exportStorage(storage.key, fileType, paths[0], config)
|
||||||
type: 'EXPORT_STORAGE',
|
.then(data => {
|
||||||
storage: data.storage,
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
fileType: data.fileType
|
type: 'info',
|
||||||
|
message: `Exported to ${paths[0]}`
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'EXPORT_STORAGE',
|
||||||
|
storage: data.storage,
|
||||||
|
fileType: data.fileType
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
error ? error.message || error : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw error
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -166,12 +188,20 @@ class StorageItem extends React.Component {
|
|||||||
label: i18n.__('Export Folder'),
|
label: i18n.__('Export Folder'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as txt'),
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
click: e => this.handleExportFolderClick(e, folder, 'txt')
|
click: e => this.handleExportFolderClick(e, folder, 'txt')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.__('Export as md'),
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
click: e => this.handleExportFolderClick(e, folder, 'md')
|
click: e => this.handleExportFolderClick(e, folder, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportFolderClick(e, folder, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportFolderClick(e, folder, 'pdf')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -202,30 +232,28 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
if (paths && paths.length === 1) {
|
if (paths && paths.length === 1) {
|
||||||
const { storage, dispatch } = this.props
|
const { storage, dispatch, config } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.exportFolder(storage.key, folder.key, fileType, paths[0])
|
.exportFolder(storage.key, folder.key, fileType, paths[0], config)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: `Exported to ${paths[0]}`
|
||||||
|
})
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EXPORT_FOLDER',
|
type: 'EXPORT_FOLDER',
|
||||||
storage: data.storage,
|
storage: data.storage,
|
||||||
folderKey: data.folderKey,
|
folderKey: data.folderKey,
|
||||||
fileType: data.fileType
|
fileType: data.fileType
|
||||||
})
|
})
|
||||||
return data
|
|
||||||
})
|
})
|
||||||
.then(data => {
|
.catch(error => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
|
||||||
type: 'info',
|
|
||||||
message: 'Exported to "' + data.exportDir + '"'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
dialog.showErrorBox(
|
dialog.showErrorBox(
|
||||||
'Export error',
|
'Export error',
|
||||||
err ? err.message || err : 'Unexpected error during export'
|
error ? error.message || error : 'Unexpected error during export'
|
||||||
)
|
)
|
||||||
throw err
|
throw error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
|||||||
import ColorPicker from 'browser/components/ColorPicker'
|
import ColorPicker from 'browser/components/ColorPicker'
|
||||||
import { every, sortBy } from 'lodash'
|
import { every, sortBy } from 'lodash'
|
||||||
|
|
||||||
|
const { dialog } = remote
|
||||||
|
|
||||||
function matchActiveTags(tags, activeTags) {
|
function matchActiveTags(tags, activeTags) {
|
||||||
return every(activeTags, v => tags.indexOf(v) >= 0)
|
return every(activeTags, v => tags.indexOf(v) >= 0)
|
||||||
}
|
}
|
||||||
@@ -63,15 +65,12 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteTag(tag) {
|
deleteTag(tag) {
|
||||||
const selectedButton = remote.dialog.showMessageBox(
|
const selectedButton = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
remote.getCurrentWindow(),
|
type: 'warning',
|
||||||
{
|
message: i18n.__('Confirm tag deletion'),
|
||||||
type: 'warning',
|
detail: i18n.__('This will permanently remove this tag.'),
|
||||||
message: i18n.__('Confirm tag deletion'),
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
detail: i18n.__('This will permanently remove this tag.'),
|
})
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (selectedButton === 0) {
|
if (selectedButton === 0) {
|
||||||
const {
|
const {
|
||||||
@@ -155,28 +154,80 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTagContextMenu(e, tag) {
|
handleTagContextMenu(e, tag) {
|
||||||
const menu = []
|
context.popup([
|
||||||
|
{
|
||||||
|
label: i18n.__('Rename Tag'),
|
||||||
|
click: this.handleRenameTagClick.bind(this, tag)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Customize Color'),
|
||||||
|
click: this.displayColorPicker.bind(
|
||||||
|
this,
|
||||||
|
tag,
|
||||||
|
e.target.getBoundingClientRect()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export Tag'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Plain Text (.txt)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'txt')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as Markdown (.md)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'md')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as HTML (.html)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Export as PDF (.pdf)'),
|
||||||
|
click: e => this.handleExportTagClick(e, tag, 'pdf')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('Delete Tag'),
|
||||||
|
click: this.deleteTag.bind(this, tag)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
menu.push({
|
handleExportTagClick(e, tag, fileType) {
|
||||||
label: i18n.__('Delete Tag'),
|
const options = {
|
||||||
click: this.deleteTag.bind(this, tag)
|
properties: ['openDirectory', 'createDirectory'],
|
||||||
|
buttonLabel: i18n.__('Select directory'),
|
||||||
|
title: i18n.__('Select a folder to export the files to'),
|
||||||
|
multiSelections: false
|
||||||
|
}
|
||||||
|
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||||
|
if (paths && paths.length === 1) {
|
||||||
|
const { data, config } = this.props
|
||||||
|
dataApi
|
||||||
|
.exportTag(data, tag, fileType, paths[0], config)
|
||||||
|
.then(data => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: `Exported to ${paths[0]}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
error ? error.message || error : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.push({
|
|
||||||
label: i18n.__('Customize Color'),
|
|
||||||
click: this.displayColorPicker.bind(
|
|
||||||
this,
|
|
||||||
tag,
|
|
||||||
e.target.getBoundingClientRect()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
menu.push({
|
|
||||||
label: i18n.__('Rename Tag'),
|
|
||||||
click: this.handleRenameTagClick.bind(this, tag)
|
|
||||||
})
|
|
||||||
|
|
||||||
context.popup(menu)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissColorPicker() {
|
dismissColorPicker() {
|
||||||
@@ -330,6 +381,7 @@ class SideNav extends React.Component {
|
|||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||||
useDragHandle
|
useDragHandle
|
||||||
|
config={config}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import DevTools from './DevTools'
|
|||||||
require('./lib/ipcClient')
|
require('./lib/ipcClient')
|
||||||
require('../lib/customMeta')
|
require('../lib/customMeta')
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import ConfigManager from './lib/ConfigManager'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
|
|
||||||
@@ -107,6 +108,22 @@ function updateApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadUpdate() {
|
||||||
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Update Boostnote'),
|
||||||
|
detail: i18n.__('New Boostnote is ready to be downloaded.'),
|
||||||
|
buttons: [i18n.__('Download now'), i18n.__('Ignore updates')]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
ipcRenderer.send('update-download-confirm')
|
||||||
|
} else if (index === 1) {
|
||||||
|
ipcRenderer.send('update-cancel')
|
||||||
|
ConfigManager.set({ autoUpdateEnabled: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
@@ -147,8 +164,12 @@ ReactDOM.render(
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcRenderer.on('update-found', function() {
|
ipcRenderer.on('update-found', function() {
|
||||||
notify('Update found!', {
|
downloadUpdate()
|
||||||
body: 'Preparing to update...'
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('update-not-found', function(_, msg) {
|
||||||
|
notify('Update not found!', {
|
||||||
|
body: msg
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ const DEFAULT_MARKDOWN_LINT_CONFIG = `{
|
|||||||
|
|
||||||
const DEFAULT_CSS_CONFIG = `
|
const DEFAULT_CSS_CONFIG = `
|
||||||
/* Drop Your Custom CSS Code Here */
|
/* Drop Your Custom CSS Code Here */
|
||||||
[data-theme="default"] p code,
|
[data-theme="default"] p code.inline,
|
||||||
[data-theme="default"] li code,
|
[data-theme="default"] li code.inline,
|
||||||
[data-theme="default"] td code
|
[data-theme="default"] td code.inline
|
||||||
{
|
{
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
@@ -144,6 +144,11 @@ export const DEFAULT_CONFIG = {
|
|||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: ''
|
||||||
},
|
},
|
||||||
|
export: {
|
||||||
|
metadata: 'DONT_EXPORT', // 'DONT_EXPORT', 'MERGE_HEADER', 'MERGE_VARIABLE'
|
||||||
|
variable: 'boostnote',
|
||||||
|
prefixAttachmentFolder: false
|
||||||
|
},
|
||||||
coloredTags: {},
|
coloredTags: {},
|
||||||
wakatime: {
|
wakatime: {
|
||||||
key: null
|
key: null
|
||||||
|
|||||||
@@ -706,14 +706,15 @@ function replaceNoteKeyWithNewNoteKey(noteContent, oldNoteKey, newNoteKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Deletes all :storage and noteKey references from the given input.
|
* @description replace all :storage references with given destination folder.
|
||||||
* @param input Input in which the references should be deleted
|
* @param input Input in which the references should be replaced
|
||||||
* @param noteKey Key of the current note
|
* @param noteKey Key of the current note
|
||||||
|
* @param destinationFolder Destination folder of the attachements
|
||||||
* @returns {String} Input without the references
|
* @returns {String} Input without the references
|
||||||
*/
|
*/
|
||||||
function removeStorageAndNoteReferences(input, noteKey) {
|
function replaceStorageReferences(input, noteKey, destinationFolder) {
|
||||||
return input.replace(
|
return input.replace(
|
||||||
new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?("|\\))', 'g'),
|
new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '[^"\\)<\\s]+', 'g'),
|
||||||
function(match) {
|
function(match) {
|
||||||
return match
|
return match
|
||||||
.replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), path.posix.sep)
|
.replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), path.posix.sep)
|
||||||
@@ -735,7 +736,7 @@ function removeStorageAndNoteReferences(input, noteKey) {
|
|||||||
')?',
|
')?',
|
||||||
'g'
|
'g'
|
||||||
),
|
),
|
||||||
DESTINATION_FOLDER
|
destinationFolder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1101,8 +1102,8 @@ module.exports = {
|
|||||||
getAttachmentsInMarkdownContent,
|
getAttachmentsInMarkdownContent,
|
||||||
getAbsolutePathsOfAttachmentsInContent,
|
getAbsolutePathsOfAttachmentsInContent,
|
||||||
importAttachments,
|
importAttachments,
|
||||||
removeStorageAndNoteReferences,
|
|
||||||
removeAttachmentsByPaths,
|
removeAttachmentsByPaths,
|
||||||
|
replaceStorageReferences,
|
||||||
deleteAttachmentFolder,
|
deleteAttachmentFolder,
|
||||||
deleteAttachmentsNotPresentInNote,
|
deleteAttachmentsNotPresentInNote,
|
||||||
getAttachmentsPathAndStatus,
|
getAttachmentsPathAndStatus,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { findStorage } from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
import resolveStorageData from './resolveStorageData'
|
import resolveStorageData from './resolveStorageData'
|
||||||
import resolveStorageNotes from './resolveStorageNotes'
|
import resolveStorageNotes from './resolveStorageNotes'
|
||||||
|
import getFilename from './getFilename'
|
||||||
import exportNote from './exportNote'
|
import exportNote from './exportNote'
|
||||||
import filenamify from 'filenamify'
|
import getContentFormatter from './getContentFormatter'
|
||||||
import * as path from 'path'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
* @param {String} folderKey
|
* @param {String} folderKey
|
||||||
* @param {String} fileType
|
* @param {String} fileType
|
||||||
* @param {String} exportDir
|
* @param {String} exportDir
|
||||||
|
* @param {Object} config
|
||||||
*
|
*
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
* ```
|
* ```
|
||||||
@@ -22,7 +23,7 @@ import * as path from 'path'
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function exportFolder(storageKey, folderKey, fileType, exportDir) {
|
function exportFolder(storageKey, folderKey, fileType, exportDir, config) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -30,39 +31,34 @@ function exportFolder(storageKey, folderKey, fileType, exportDir) {
|
|||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deduplicator = {}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(function assignNotes(storage) {
|
.then(storage => {
|
||||||
return resolveStorageNotes(storage).then(notes => {
|
return resolveStorageNotes(storage).then(notes => ({
|
||||||
return {
|
storage,
|
||||||
storage,
|
notes: notes.filter(
|
||||||
notes
|
note =>
|
||||||
}
|
note.folder === folderKey &&
|
||||||
})
|
!note.isTrashed &&
|
||||||
|
note.type === 'MARKDOWN_NOTE'
|
||||||
|
)
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
.then(function exportNotes(data) {
|
.then(({ storage, notes }) => {
|
||||||
const { storage, notes } = data
|
const contentFormatter = getContentFormatter(storage, fileType, config)
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
notes
|
notes.map(note => {
|
||||||
.filter(
|
const targetPath = getFilename(
|
||||||
note =>
|
note,
|
||||||
note.folder === folderKey &&
|
fileType,
|
||||||
note.isTrashed === false &&
|
exportDir,
|
||||||
note.type === 'MARKDOWN_NOTE'
|
deduplicator
|
||||||
)
|
)
|
||||||
.map(note => {
|
|
||||||
const notePath = path.join(
|
return exportNote(storage.key, note, targetPath, contentFormatter)
|
||||||
exportDir,
|
})
|
||||||
`${filenamify(note.title, { replacement: '_' })}.${fileType}`
|
|
||||||
)
|
|
||||||
return exportNote(
|
|
||||||
note.key,
|
|
||||||
storage.path,
|
|
||||||
note.content,
|
|
||||||
notePath,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
})
|
|
||||||
).then(() => ({
|
).then(() => ({
|
||||||
storage,
|
storage,
|
||||||
folderKey,
|
folderKey,
|
||||||
|
|||||||
@@ -4,58 +4,35 @@ import { findStorage } from 'browser/lib/findStorage'
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const attachmentManagement = require('./attachmentManagement')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export note together with attachments
|
* Export note together with attachments
|
||||||
*
|
*
|
||||||
* If attachments are stored in the storage, creates 'attachments' subfolder in target directory
|
* If attachments are stored in the storage, creates 'attachments' subfolder in target directory
|
||||||
* and copies attachments to it. Changes links to images in the content of the note
|
* and copies attachments to it. Changes links to images in the content of the note
|
||||||
*
|
*
|
||||||
* @param {String} nodeKey key of the node that should be exported
|
|
||||||
* @param {String} storageKey or storage path
|
* @param {String} storageKey or storage path
|
||||||
* @param {String} noteContent Content to export
|
* @param {Object} note Note to export
|
||||||
* @param {String} targetPath Path to exported file
|
* @param {String} targetPath Path to exported file
|
||||||
* @param {function} outputFormatter
|
* @param {function} outputFormatter
|
||||||
* @return {Promise.<*[]>}
|
* @return {Promise.<*[]>}
|
||||||
*/
|
*/
|
||||||
function exportNote(
|
function exportNote(storageKey, note, targetPath, outputFormatter) {
|
||||||
nodeKey,
|
|
||||||
storageKey,
|
|
||||||
noteContent,
|
|
||||||
targetPath,
|
|
||||||
outputFormatter
|
|
||||||
) {
|
|
||||||
const storagePath = path.isAbsolute(storageKey)
|
const storagePath = path.isAbsolute(storageKey)
|
||||||
? storageKey
|
? storageKey
|
||||||
: findStorage(storageKey).path
|
: findStorage(storageKey).path
|
||||||
|
|
||||||
const exportTasks = []
|
const exportTasks = []
|
||||||
|
|
||||||
if (!storagePath) {
|
if (!storagePath) {
|
||||||
throw new Error('Storage path is not found')
|
throw new Error('Storage path is not found')
|
||||||
}
|
}
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
|
||||||
noteContent,
|
|
||||||
storagePath
|
|
||||||
)
|
|
||||||
attachmentsAbsolutePaths.forEach(attachment => {
|
|
||||||
exportTasks.push({
|
|
||||||
src: attachment,
|
|
||||||
dst: attachmentManagement.DESTINATION_FOLDER
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let exportedData = attachmentManagement.removeStorageAndNoteReferences(
|
const exportedData = Promise.resolve(
|
||||||
noteContent,
|
outputFormatter
|
||||||
nodeKey
|
? outputFormatter(note, targetPath, exportTasks)
|
||||||
|
: note.content
|
||||||
)
|
)
|
||||||
|
|
||||||
if (outputFormatter) {
|
|
||||||
exportedData = outputFormatter(exportedData, exportTasks, targetPath)
|
|
||||||
} else {
|
|
||||||
exportedData = Promise.resolve(exportedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
||||||
|
|
||||||
return Promise.all(tasks.map(task => copyFile(task.src, task.dst)))
|
return Promise.all(tasks.map(task => copyFile(task.src, task.dst)))
|
||||||
@@ -63,9 +40,9 @@ function exportNote(
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
return saveToFile(data, targetPath)
|
return saveToFile(data, targetPath)
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(error => {
|
||||||
rollbackExport(tasks)
|
rollbackExport(tasks)
|
||||||
throw err
|
throw error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,14 +84,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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
19
browser/main/lib/dataApi/exportNoteAs.js
Normal file
19
browser/main/lib/dataApi/exportNoteAs.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
import exportNote from './exportNote'
|
||||||
|
import getContentFormatter from './getContentFormatter'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} note
|
||||||
|
* @param {String} filename
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
|
||||||
|
function exportNoteAs(note, filename, fileType, config) {
|
||||||
|
const storage = findStorage(note.storage)
|
||||||
|
const contentFormatter = getContentFormatter(storage, fileType, config)
|
||||||
|
|
||||||
|
return exportNote(storage.key, note, filename, contentFormatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exportNoteAs
|
||||||
@@ -2,13 +2,17 @@ import { findStorage } from 'browser/lib/findStorage'
|
|||||||
import resolveStorageData from './resolveStorageData'
|
import resolveStorageData from './resolveStorageData'
|
||||||
import resolveStorageNotes from './resolveStorageNotes'
|
import resolveStorageNotes from './resolveStorageNotes'
|
||||||
import filenamify from 'filenamify'
|
import filenamify from 'filenamify'
|
||||||
import * as path from 'path'
|
import path from 'path'
|
||||||
import * as fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import exportNote from './exportNote'
|
||||||
|
import getContentFormatter from './getContentFormatter'
|
||||||
|
import getFilename from './getFilename'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
* @param {String} fileType
|
* @param {String} fileType
|
||||||
* @param {String} exportDir
|
* @param {String} exportDir
|
||||||
|
* @param {Object} config
|
||||||
*
|
*
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
* ```
|
* ```
|
||||||
@@ -20,7 +24,7 @@ import * as fs from 'fs'
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function exportStorage(storageKey, fileType, exportDir) {
|
function exportStorage(storageKey, fileType, exportDir, config) {
|
||||||
let targetStorage
|
let targetStorage
|
||||||
try {
|
try {
|
||||||
targetStorage = findStorage(storageKey)
|
targetStorage = findStorage(storageKey)
|
||||||
@@ -29,39 +33,52 @@ function exportStorage(storageKey, fileType, exportDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolveStorageData(targetStorage)
|
return resolveStorageData(targetStorage)
|
||||||
.then(storage =>
|
.then(storage => {
|
||||||
resolveStorageNotes(storage).then(notes => ({ storage, notes }))
|
return resolveStorageNotes(storage).then(notes => ({
|
||||||
)
|
storage,
|
||||||
.then(function exportNotes(data) {
|
notes: notes.filter(
|
||||||
const { storage, notes } = data
|
note => !note.isTrashed && note.type === 'MARKDOWN_NOTE'
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.then(({ storage, notes }) => {
|
||||||
|
const contentFormatter = getContentFormatter(storage, fileType, config)
|
||||||
|
|
||||||
const folderNamesMapping = {}
|
const folderNamesMapping = {}
|
||||||
|
const deduplicators = {}
|
||||||
|
|
||||||
storage.folders.forEach(folder => {
|
storage.folders.forEach(folder => {
|
||||||
const folderExportedDir = path.join(
|
const folderExportedDir = path.join(
|
||||||
exportDir,
|
exportDir,
|
||||||
filenamify(folder.name, { replacement: '_' })
|
filenamify(folder.name, { replacement: '_' })
|
||||||
)
|
)
|
||||||
|
|
||||||
folderNamesMapping[folder.key] = folderExportedDir
|
folderNamesMapping[folder.key] = folderExportedDir
|
||||||
|
|
||||||
// make sure directory exists
|
// make sure directory exists
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(folderExportedDir)
|
fs.mkdirSync(folderExportedDir)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
})
|
|
||||||
notes
|
|
||||||
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
|
|
||||||
.forEach(markdownNote => {
|
|
||||||
const folderExportedDir = folderNamesMapping[markdownNote.folder]
|
|
||||||
const snippetName = `${filenamify(markdownNote.title, {
|
|
||||||
replacement: '_'
|
|
||||||
})}.${fileType}`
|
|
||||||
const notePath = path.join(folderExportedDir, snippetName)
|
|
||||||
fs.writeFileSync(notePath, markdownNote.content)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
deduplicators[folder.key] = {}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
notes.map(note => {
|
||||||
|
const targetPath = getFilename(
|
||||||
|
note,
|
||||||
|
fileType,
|
||||||
|
folderNamesMapping[note.folder],
|
||||||
|
deduplicators[note.folder]
|
||||||
|
)
|
||||||
|
|
||||||
|
return exportNote(storage.key, note, targetPath, contentFormatter)
|
||||||
|
})
|
||||||
|
).then(() => ({
|
||||||
storage,
|
storage,
|
||||||
fileType,
|
fileType,
|
||||||
exportDir
|
exportDir
|
||||||
}
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
28
browser/main/lib/dataApi/exportTag.js
Normal file
28
browser/main/lib/dataApi/exportTag.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import exportNoteAs from './exportNoteAs'
|
||||||
|
import getFilename from './getFilename'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {String} tag
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {String} exportDir
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
|
||||||
|
function exportTag(data, tag, fileType, exportDir, config) {
|
||||||
|
const notes = data.noteMap
|
||||||
|
.map(note => note)
|
||||||
|
.filter(note => note.tags.indexOf(tag) !== -1)
|
||||||
|
|
||||||
|
const deduplicator = {}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
notes.map(note => {
|
||||||
|
const filename = getFilename(note, fileType, exportDir, deduplicator)
|
||||||
|
|
||||||
|
return exportNoteAs(note, filename, fileType, config)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exportTag
|
||||||
796
browser/main/lib/dataApi/formatHTML.js
Normal file
796
browser/main/lib/dataApi/formatHTML.js
Normal file
@@ -0,0 +1,796 @@
|
|||||||
|
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'
|
||||||
|
import { version as codemirrorVersion } from 'codemirror/package.json'
|
||||||
|
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||||
|
|
||||||
|
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`,
|
||||||
|
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
|
||||||
|
]
|
||||||
|
|
||||||
|
const macos = global.process.platform === 'darwin'
|
||||||
|
|
||||||
|
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||||
|
if (!macos) {
|
||||||
|
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) {
|
||||||
|
const styles = files
|
||||||
|
.map(file => `<link rel="stylesheet" href="css/${path.basename(file)}">`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
let inlineScripts = ''
|
||||||
|
let scripts = ''
|
||||||
|
|
||||||
|
let decodeEntities = false
|
||||||
|
function addDecodeEntities() {
|
||||||
|
if (decodeEntities) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeEntities = true
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
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
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 raphael = false
|
||||||
|
function addRaphael() {
|
||||||
|
if (raphael) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raphael = true
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/raphael/raphael.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/raphael.min.js"></script>`
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = false
|
||||||
|
function addYAML() {
|
||||||
|
if (yaml) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
yaml = true
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/js-yaml/dist/js-yaml.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/js-yaml.min.js"></script>`
|
||||||
|
}
|
||||||
|
|
||||||
|
let chart = false
|
||||||
|
function addChart() {
|
||||||
|
if (chart) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chart = true
|
||||||
|
|
||||||
|
addLodash()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/chart.js/dist/Chart.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/Chart.min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displayCharts() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.chart'),
|
||||||
|
el => {
|
||||||
|
try {
|
||||||
|
const format = el.attributes.getNamedItem('data-format').value
|
||||||
|
const chartConfig = format === 'yaml' ? jsyaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
|
||||||
|
el.innerHTML = ''
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
el.appendChild(canvas)
|
||||||
|
|
||||||
|
const height = el.attributes.getNamedItem('data-height')
|
||||||
|
if (height && height.value !== 'undefined') {
|
||||||
|
el.style.height = height.value + 'vh'
|
||||||
|
canvas.height = height.value + 'vh'
|
||||||
|
}
|
||||||
|
|
||||||
|
const chart = new Chart(canvas, chartConfig)
|
||||||
|
} catch (e) {
|
||||||
|
el.className = 'chart-error'
|
||||||
|
el.innerHTML = 'chartjs diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayCharts);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let codemirror = false
|
||||||
|
function addCodeMirror() {
|
||||||
|
if (codemirror) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
codemirror = true
|
||||||
|
|
||||||
|
addDecodeEntities()
|
||||||
|
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 = 'https://cdn.jsdelivr.net/npm/codemirror@${codemirrorVersion}/mode/%N/%N.js';
|
||||||
|
|
||||||
|
function displayCodeBlocks() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.code code'),
|
||||||
|
el => {
|
||||||
|
el.parentNode.className += ' ${className}'
|
||||||
|
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 = ''
|
||||||
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
|
tabSize: ${indentSize}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayCodeBlocks);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let flowchart = false
|
||||||
|
function addFlowchart() {
|
||||||
|
if (flowchart) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flowchart = true
|
||||||
|
|
||||||
|
addDecodeEntities()
|
||||||
|
addLodash()
|
||||||
|
addRaphael()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(
|
||||||
|
`${appPath}/node_modules/flowchart.js/release/flowchart.min.js`
|
||||||
|
),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/flowchart.min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displayFlowcharts() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.flowchart'),
|
||||||
|
el => {
|
||||||
|
try {
|
||||||
|
const diagram = flowchart.parse(
|
||||||
|
decodeEntities(el.innerHTML)
|
||||||
|
)
|
||||||
|
el.innerHTML = ''
|
||||||
|
diagram.drawSVG(el)
|
||||||
|
} catch (e) {
|
||||||
|
el.className = 'flowchart-error'
|
||||||
|
el.innerHTML = 'Flowchart parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayFlowcharts);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let mermaid = false
|
||||||
|
function addMermaid() {
|
||||||
|
if (mermaid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mermaid = true
|
||||||
|
|
||||||
|
addLodash()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(`${appPath}/node_modules/mermaid/dist/mermaid.min.js`),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/mermaid.min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displayMermaids() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.mermaid'),
|
||||||
|
el => {
|
||||||
|
const height = el.attributes.getNamedItem('data-height')
|
||||||
|
if (height && height.value !== 'undefined') {
|
||||||
|
el.style.height = height.value + 'vh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displayMermaids);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
let sequence = false
|
||||||
|
function addSequence() {
|
||||||
|
if (sequence) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence = true
|
||||||
|
|
||||||
|
addDecodeEntities()
|
||||||
|
addLodash()
|
||||||
|
addRaphael()
|
||||||
|
|
||||||
|
exportTasks.push({
|
||||||
|
src: unprefix(
|
||||||
|
`${appPath}/node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js`
|
||||||
|
),
|
||||||
|
dst: 'js'
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts += `<script src="js/sequence-diagram-min.js"></script>`
|
||||||
|
|
||||||
|
inlineScripts += `
|
||||||
|
function displaySequences() {
|
||||||
|
_.forEach(
|
||||||
|
document.querySelectorAll('.sequence'),
|
||||||
|
el => {
|
||||||
|
try {
|
||||||
|
const diagram = Diagram.parse(
|
||||||
|
decodeEntities(el.innerHTML)
|
||||||
|
)
|
||||||
|
el.innerHTML = ''
|
||||||
|
diagram.drawSVG(el, { theme: 'simple' })
|
||||||
|
} catch (e) {
|
||||||
|
el.className = 'sequence-error'
|
||||||
|
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', displaySequences);
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const modes = {}
|
||||||
|
const markdown = new Markdown({
|
||||||
|
typographer: smartQuotes,
|
||||||
|
sanitize,
|
||||||
|
breaks,
|
||||||
|
onFence(type, mode) {
|
||||||
|
if (type === 'chart') {
|
||||||
|
addChart()
|
||||||
|
|
||||||
|
if (mode === 'yaml') {
|
||||||
|
addYAML()
|
||||||
|
}
|
||||||
|
} else 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === 'flowchart') {
|
||||||
|
addFlowchart()
|
||||||
|
} else if (type === 'mermaid') {
|
||||||
|
addMermaid()
|
||||||
|
} else if (type === 'sequence') {
|
||||||
|
addSequence()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let body = note.content
|
||||||
|
|
||||||
|
if (sanitize === 'NONE') {
|
||||||
|
body = escapeHtmlCharactersInCodeTag(body.split('```'))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||||
|
<style id="style">${inlineStyles}</style>
|
||||||
|
${styles}
|
||||||
|
${scripts}
|
||||||
|
<script>${inlineScripts}</script>
|
||||||
|
</head>
|
||||||
|
<body data-theme="${theme}">
|
||||||
|
${body}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStyleParams(props) {
|
||||||
|
const {
|
||||||
|
fontSize,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS,
|
||||||
|
RTL
|
||||||
|
} = 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,
|
||||||
|
RTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodeThemeLink(name) {
|
||||||
|
const theme = consts.THEMES.find(theme => theme.name === name)
|
||||||
|
|
||||||
|
return theme != null
|
||||||
|
? theme.path
|
||||||
|
: `${appPath}/node_modules/codemirror/theme/elegant.css`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildStyle(
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS,
|
||||||
|
RTL
|
||||||
|
) {
|
||||||
|
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;box-sizing: border-box;'}
|
||||||
|
${RTL && 'direction: rtl;text-align: right;'}
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
padding-bottom: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
|
background-color: rgba(0,0,0,0.04);
|
||||||
|
text-align: left;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
p code.inline,
|
||||||
|
li code.inline,
|
||||||
|
td code.inline
|
||||||
|
{
|
||||||
|
padding: 2px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
[data-theme="default"] p code.inline,
|
||||||
|
[data-theme="default"] li code.inline,
|
||||||
|
[data-theme="default"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
[data-theme="white"] p code.inline,
|
||||||
|
[data-theme="white"] li code.inline,
|
||||||
|
[data-theme="white"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] p code.inline,
|
||||||
|
[data-theme="dark"] li code.inline,
|
||||||
|
[data-theme="dark"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="dracula"] p code.inline,
|
||||||
|
[data-theme="dracula"] li code.inline,
|
||||||
|
[data-theme="dracula"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="monokai"] p code.inline,
|
||||||
|
[data-theme="monokai"] li code.inline,
|
||||||
|
[data-theme="monokai"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="nord"] p code.inline,
|
||||||
|
[data-theme="nord"] li code.inline,
|
||||||
|
[data-theme="nord"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="solarized-dark"] p code.inline,
|
||||||
|
[data-theme="solarized-dark"] li code.inline,
|
||||||
|
[data-theme="solarized-dark"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
[data-theme="vulcan"] p code.inline,
|
||||||
|
[data-theme="vulcan"] li code.inline,
|
||||||
|
[data-theme="vulcan"] td code.inline
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #555;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 : ''}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Convert special characters between three ```
|
||||||
|
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
|
||||||
|
* @returns {string} HTML in which special characters between three ``` have been converted
|
||||||
|
*/
|
||||||
|
export function escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
|
||||||
|
for (let index = 0; index < splitWithCodeTag.length; index++) {
|
||||||
|
const codeTagRequired =
|
||||||
|
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
|
||||||
|
if (codeTagRequired) {
|
||||||
|
splitWithCodeTag.splice(index + 1, 0, '```')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let inCodeTag = false
|
||||||
|
let result = ''
|
||||||
|
for (let content of splitWithCodeTag) {
|
||||||
|
if (content === '```') {
|
||||||
|
inCodeTag = !inCodeTag
|
||||||
|
} else if (inCodeTag) {
|
||||||
|
content = escapeHtmlCharacters(content)
|
||||||
|
}
|
||||||
|
result += content
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
103
browser/main/lib/dataApi/formatMarkdown.js
Normal file
103
browser/main/lib/dataApi/formatMarkdown.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import attachmentManagement from './attachmentManagement'
|
||||||
|
import yaml from 'js-yaml'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const delimiterRegExp = /^\-{3}/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* storagePath,
|
||||||
|
* export
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default function formatMarkdown(props) {
|
||||||
|
return function(note, targetPath, exportTasks) {
|
||||||
|
let result = note.content
|
||||||
|
|
||||||
|
if (props.storagePath && note.key) {
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
|
result,
|
||||||
|
props.storagePath
|
||||||
|
)
|
||||||
|
|
||||||
|
const destinationFolder = props.export.prefixAttachmentFolder
|
||||||
|
? `${path.parse(targetPath).name} - ${
|
||||||
|
attachmentManagement.DESTINATION_FOLDER
|
||||||
|
}`
|
||||||
|
: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
|
||||||
|
attachmentsAbsolutePaths.forEach(attachment => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: destinationFolder
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
result = attachmentManagement.replaceStorageReferences(
|
||||||
|
result,
|
||||||
|
note.key,
|
||||||
|
destinationFolder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.export.metadata === 'MERGE_HEADER') {
|
||||||
|
const metadata = getFrontMatter(result)
|
||||||
|
|
||||||
|
const values = Object.assign({}, note)
|
||||||
|
delete values.content
|
||||||
|
delete values.isTrashed
|
||||||
|
|
||||||
|
for (const key in values) {
|
||||||
|
metadata[key] = values[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
result = replaceFrontMatter(result, metadata)
|
||||||
|
} else if (props.export.metadata === 'MERGE_VARIABLE') {
|
||||||
|
const metadata = getFrontMatter(result)
|
||||||
|
|
||||||
|
const values = Object.assign({}, note)
|
||||||
|
delete values.content
|
||||||
|
delete values.isTrashed
|
||||||
|
|
||||||
|
if (props.export.variable) {
|
||||||
|
metadata[props.export.variable] = values
|
||||||
|
} else {
|
||||||
|
for (const key in values) {
|
||||||
|
metadata[key] = values[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = replaceFrontMatter(result, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFrontMatter(markdown) {
|
||||||
|
const lines = markdown.split('\n')
|
||||||
|
|
||||||
|
if (delimiterRegExp.test(lines[0])) {
|
||||||
|
let line = 0
|
||||||
|
while (++line < lines.length && !delimiterRegExp.test(lines[line])) {}
|
||||||
|
|
||||||
|
return yaml.load(lines.slice(1, line).join('\n')) || {}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceFrontMatter(markdown, metadata) {
|
||||||
|
const lines = markdown.split('\n')
|
||||||
|
|
||||||
|
if (delimiterRegExp.test(lines[0])) {
|
||||||
|
let line = 0
|
||||||
|
while (++line < lines.length && !delimiterRegExp.test(lines[line])) {}
|
||||||
|
|
||||||
|
return `---\n${yaml.dump(metadata)}---\n${lines.slice(line + 1).join('\n')}`
|
||||||
|
} else {
|
||||||
|
return `---\n${yaml.dump(metadata)}---\n\n${markdown}`
|
||||||
|
}
|
||||||
|
}
|
||||||
26
browser/main/lib/dataApi/formatPDF.js
Normal file
26
browser/main/lib/dataApi/formatPDF.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import formatHTML from './formatHTML'
|
||||||
|
import { remote } from 'electron'
|
||||||
|
|
||||||
|
export default function formatPDF(props) {
|
||||||
|
return function(note, targetPath, exportTasks) {
|
||||||
|
const printout = new remote.BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
webPreferences: { webSecurity: false, javascript: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
printout.loadURL(
|
||||||
|
'data:text/html;charset=UTF-8,' +
|
||||||
|
formatHTML(props)(note, targetPath, exportTasks)
|
||||||
|
)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
printout.webContents.on('did-finish-load', () => {
|
||||||
|
printout.webContents.printToPDF({}, (err, data) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(data)
|
||||||
|
printout.destroy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
58
browser/main/lib/dataApi/getContentFormatter.js
Normal file
58
browser/main/lib/dataApi/getContentFormatter.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import formatMarkdown from './formatMarkdown'
|
||||||
|
import formatHTML from './formatHTML'
|
||||||
|
import formatPDF from './formatPDF'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} storage
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function getContentFormatter(storage, fileType, config) {
|
||||||
|
if (fileType === 'md') {
|
||||||
|
return formatMarkdown({
|
||||||
|
storagePath: storage.path,
|
||||||
|
export: config.export
|
||||||
|
})
|
||||||
|
} else if (fileType === 'html') {
|
||||||
|
return formatHTML({
|
||||||
|
theme: config.ui.theme,
|
||||||
|
fontSize: config.preview.fontSize,
|
||||||
|
fontFamily: config.preview.fontFamily,
|
||||||
|
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,
|
||||||
|
sanitize: config.preview.sanitize,
|
||||||
|
customCSS: config.preview.customCSS,
|
||||||
|
allowCustomCSS: config.preview.allowCustomCSS,
|
||||||
|
storagePath: storage.path,
|
||||||
|
export: config.export,
|
||||||
|
RTL: config.editor.rtlEnabled /* && this.state.RTL */
|
||||||
|
})
|
||||||
|
} else if (fileType === 'pdf') {
|
||||||
|
return formatPDF({
|
||||||
|
theme: config.ui.theme,
|
||||||
|
fontSize: config.preview.fontSize,
|
||||||
|
fontFamily: config.preview.fontFamily,
|
||||||
|
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,
|
||||||
|
sanitize: config.preview.sanitize,
|
||||||
|
customCSS: config.preview.customCSS,
|
||||||
|
allowCustomCSS: config.preview.allowCustomCSS,
|
||||||
|
storagePath: storage.path,
|
||||||
|
export: config.export,
|
||||||
|
RTL: config.editor.rtlEnabled /* && this.state.RTL */
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
37
browser/main/lib/dataApi/getFilename.js
Normal file
37
browser/main/lib/dataApi/getFilename.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import filenamify from 'filenamify'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} note
|
||||||
|
* @param {String} fileType
|
||||||
|
* @param {String} directory
|
||||||
|
* @param {Object} deduplicator
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getFilename(note, fileType, directory, deduplicator) {
|
||||||
|
const basename = note.title
|
||||||
|
? filenamify(note.title, { replacement: '_' })
|
||||||
|
: i18n.__('Untitled')
|
||||||
|
|
||||||
|
if (deduplicator) {
|
||||||
|
if (deduplicator[basename]) {
|
||||||
|
const filename = path.join(
|
||||||
|
directory,
|
||||||
|
`${basename} (${deduplicator[basename]}).${fileType}`
|
||||||
|
)
|
||||||
|
|
||||||
|
++deduplicator[basename]
|
||||||
|
|
||||||
|
return filename
|
||||||
|
} else {
|
||||||
|
deduplicator[basename] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(directory, `${basename}.${fileType}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getFilename
|
||||||
@@ -15,11 +15,14 @@ const dataApi = {
|
|||||||
updateNote: require('./updateNote'),
|
updateNote: require('./updateNote'),
|
||||||
deleteNote: require('./deleteNote'),
|
deleteNote: require('./deleteNote'),
|
||||||
moveNote: require('./moveNote'),
|
moveNote: require('./moveNote'),
|
||||||
|
exportNoteAs: require('./exportNoteAs'),
|
||||||
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
migrateFromV5Storage: require('./migrateFromV5Storage'),
|
||||||
createSnippet: require('./createSnippet'),
|
createSnippet: require('./createSnippet'),
|
||||||
deleteSnippet: require('./deleteSnippet'),
|
deleteSnippet: require('./deleteSnippet'),
|
||||||
updateSnippet: require('./updateSnippet'),
|
updateSnippet: require('./updateSnippet'),
|
||||||
fetchSnippet: require('./fetchSnippet'),
|
fetchSnippet: require('./fetchSnippet'),
|
||||||
|
exportTag: require('./exportTag'),
|
||||||
|
getFilename: require('./getFilename'),
|
||||||
|
|
||||||
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
||||||
_resolveStorageData: require('./resolveStorageData'),
|
_resolveStorageData: require('./resolveStorageData'),
|
||||||
|
|||||||
184
browser/main/modals/PreferencesModal/ExportTab.js
Normal file
184
browser/main/modals/PreferencesModal/ExportTab.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ConfigTab.styl'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import store from 'browser/main/store'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const ipc = electron.ipcRenderer
|
||||||
|
|
||||||
|
class ExportTab extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
config: props.config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessage() {
|
||||||
|
_.debounce(() => {
|
||||||
|
this.setState({
|
||||||
|
ExportAlert: null
|
||||||
|
})
|
||||||
|
}, 2000)()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.handleSettingDone = () => {
|
||||||
|
this.setState({
|
||||||
|
ExportAlert: {
|
||||||
|
type: 'success',
|
||||||
|
message: i18n.__('Successfully applied!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.handleSettingError = err => {
|
||||||
|
this.setState({
|
||||||
|
ExportAlert: {
|
||||||
|
type: 'error',
|
||||||
|
message:
|
||||||
|
err.message != null ? err.message : i18n.__('An error occurred!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldExport = this.state.config.export
|
||||||
|
|
||||||
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
|
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
|
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveButtonClick(e) {
|
||||||
|
const newConfig = {
|
||||||
|
export: this.state.config.export
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: 'SET_UI',
|
||||||
|
config: newConfig
|
||||||
|
})
|
||||||
|
|
||||||
|
this.clearMessage()
|
||||||
|
this.props.haveToSave()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleExportChange(e) {
|
||||||
|
const { config } = this.state
|
||||||
|
|
||||||
|
config.export = {
|
||||||
|
metadata: this.refs.metadata.value,
|
||||||
|
variable: !_.isNil(this.refs.variable)
|
||||||
|
? this.refs.variable.value
|
||||||
|
: config.export.variable,
|
||||||
|
prefixAttachmentFolder: this.refs.prefixAttachmentFolder.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
config
|
||||||
|
})
|
||||||
|
|
||||||
|
if (_.isEqual(this.oldExport, config.export)) {
|
||||||
|
this.props.haveToSave()
|
||||||
|
} else {
|
||||||
|
this.props.haveToSave({
|
||||||
|
tab: 'Export',
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Unsaved Changes!')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { config, ExportAlert } = this.state
|
||||||
|
|
||||||
|
const ExportAlertElement =
|
||||||
|
ExportAlert != null ? (
|
||||||
|
<p className={`alert ${ExportAlert.type}`}>{ExportAlert.message}</p>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='group'>
|
||||||
|
<div styleName='group-header'>{i18n.__('Export')}</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Metadata')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<select
|
||||||
|
value={config.export.metadata}
|
||||||
|
onChange={e => this.handleExportChange(e)}
|
||||||
|
ref='metadata'
|
||||||
|
>
|
||||||
|
<option value='DONT_EXPORT'>{i18n.__(`Don't export`)}</option>
|
||||||
|
<option value='MERGE_HEADER'>
|
||||||
|
{i18n.__('Merge with the header')}
|
||||||
|
</option>
|
||||||
|
<option value='MERGE_VARIABLE'>
|
||||||
|
{i18n.__('Merge with a variable')}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{config.export.metadata === 'MERGE_VARIABLE' && (
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('Variable Name')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input
|
||||||
|
styleName='group-section-control-input'
|
||||||
|
onChange={e => this.handleExportChange(e)}
|
||||||
|
ref='variable'
|
||||||
|
value={config.export.variable}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
onChange={e => this.handleExportChange(e)}
|
||||||
|
checked={config.export.prefixAttachmentFolder}
|
||||||
|
ref='prefixAttachmentFolder'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
|
||||||
|
{i18n.__('Prefix attachment folder')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-control'>
|
||||||
|
<button
|
||||||
|
styleName='group-control-rightButton'
|
||||||
|
onClick={e => this.handleSaveButtonClick(e)}
|
||||||
|
>
|
||||||
|
{i18n.__('Save')}
|
||||||
|
</button>
|
||||||
|
{ExportAlertElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExportTab.propTypes = {
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
haveToSave: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ExportTab, styles)
|
||||||
@@ -16,25 +16,78 @@ class InfoTab extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
config: this.props.config
|
config: this.props.config,
|
||||||
|
subscriptionFormStatus: 'idle',
|
||||||
|
subscriptionFormErrorMessage: null,
|
||||||
|
subscriptionFormEmail: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { autoUpdateEnabled, amaEnabled } = ConfigManager.get()
|
||||||
|
|
||||||
|
this.setState({ config: { autoUpdateEnabled, amaEnabled } })
|
||||||
|
}
|
||||||
|
|
||||||
handleLinkClick(e) {
|
handleLinkClick(e) {
|
||||||
shell.openExternal(e.currentTarget.href)
|
shell.openExternal(e.currentTarget.href)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConfigChange(e) {
|
handleConfigChange(e) {
|
||||||
const newConfig = { amaEnabled: this.refs.amaEnabled.checked }
|
const newConfig = {
|
||||||
|
amaEnabled: this.refs.amaEnabled.checked,
|
||||||
|
autoUpdateEnabled: this.refs.autoUpdateEnabled.checked
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ config: newConfig })
|
this.setState({ config: newConfig })
|
||||||
|
return newConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubscriptionFormSubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormStatus: 'sending',
|
||||||
|
subscriptionFormErrorMessage: null
|
||||||
|
})
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
'https://boostmails.boostio.co/api/public/lists/5f434dccd05f3160b41c0d49/subscriptions',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ email: this.state.subscriptionFormEmail })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
if (response.status >= 400) {
|
||||||
|
return response.text().then(text => {
|
||||||
|
throw new Error(text)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormStatus: 'done'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormStatus: 'idle',
|
||||||
|
subscriptionFormErrorMessage: error.message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubscriptionFormEmailChange(e) {
|
||||||
|
this.setState({
|
||||||
|
subscriptionFormEmail: e.target.value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveButtonClick(e) {
|
handleSaveButtonClick(e) {
|
||||||
const newConfig = {
|
const newConfig = this.state.config
|
||||||
amaEnabled: this.state.config.amaEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newConfig.amaEnabled) {
|
if (!newConfig.amaEnabled) {
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
|
||||||
@@ -61,20 +114,17 @@ class InfoTab extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAutoUpdate() {
|
|
||||||
const newConfig = {
|
|
||||||
autoUpdateEnabled: !this.state.config.autoUpdateEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ config: newConfig })
|
|
||||||
ConfigManager.set(newConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
infoMessage() {
|
infoMessage() {
|
||||||
const { amaMessage } = this.state
|
const { amaMessage } = this.state
|
||||||
return amaMessage ? <p styleName='policy-confirm'>{amaMessage}</p> : null
|
return amaMessage ? <p styleName='policy-confirm'>{amaMessage}</p> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAutoUpdateChange() {
|
||||||
|
const { autoUpdateEnabled } = this.handleConfigChange()
|
||||||
|
|
||||||
|
ConfigManager.set({ autoUpdateEnabled })
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
@@ -134,6 +184,40 @@ class InfoTab extends React.Component {
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
<div styleName='group-header--sub'>Subscribe Update Notes</div>
|
||||||
|
{this.state.subscriptionFormStatus === 'done' ? (
|
||||||
|
<div>
|
||||||
|
<blockquote color={{ color: 'green' }}>
|
||||||
|
Thanks for the subscription!
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{this.state.subscriptionFormErrorMessage != null && (
|
||||||
|
<blockquote style={{ color: 'red' }}>
|
||||||
|
{this.state.subscriptionFormErrorMessage}
|
||||||
|
</blockquote>
|
||||||
|
)}
|
||||||
|
<form onSubmit={e => this.handleSubscriptionFormSubmit(e)}>
|
||||||
|
<input
|
||||||
|
styleName='subscription-email-input'
|
||||||
|
placeholder='E-mail'
|
||||||
|
type='email'
|
||||||
|
onChange={e => this.handleSubscriptionFormEmailChange(e)}
|
||||||
|
disabled={this.state.subscriptionFormStatus === 'sending'}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
styleName='subscription-submit-button'
|
||||||
|
type='submit'
|
||||||
|
disabled={this.state.subscriptionFormStatus === 'sending'}
|
||||||
|
>
|
||||||
|
Subscribe
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<hr />
|
||||||
|
|
||||||
<div styleName='group-header--sub'>{i18n.__('About')}</div>
|
<div styleName='group-header--sub'>{i18n.__('About')}</div>
|
||||||
|
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
@@ -181,7 +265,8 @@ class InfoTab extends React.Component {
|
|||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
onChange={this.toggleAutoUpdate.bind(this)}
|
ref='autoUpdateEnabled'
|
||||||
|
onChange={() => this.handleAutoUpdateChange()}
|
||||||
checked={this.state.config.autoUpdateEnabled}
|
checked={this.state.config.autoUpdateEnabled}
|
||||||
/>
|
/>
|
||||||
{i18n.__('Enable Auto Update')}
|
{i18n.__('Enable Auto Update')}
|
||||||
|
|||||||
@@ -33,6 +33,35 @@
|
|||||||
.separate-line
|
.separate-line
|
||||||
margin 40px 0
|
margin 40px 0
|
||||||
|
|
||||||
|
.subscription-email-input
|
||||||
|
height 35px
|
||||||
|
vertical-align middle
|
||||||
|
width 200px
|
||||||
|
font-size $tab--button-font-size
|
||||||
|
border solid 1px $border-color
|
||||||
|
border-radius 2px
|
||||||
|
padding 0 5px
|
||||||
|
margin-right 5px
|
||||||
|
outline none
|
||||||
|
&:disabled
|
||||||
|
background-color $ui-input--disabled-backgroundColor
|
||||||
|
|
||||||
|
.subscription-submit-button
|
||||||
|
margin-top 10px
|
||||||
|
height 35px
|
||||||
|
border-radius 2px
|
||||||
|
border none
|
||||||
|
background-color alpha(#1EC38B, 90%)
|
||||||
|
padding-left 20px
|
||||||
|
padding-right 20px
|
||||||
|
text-decoration none
|
||||||
|
color white
|
||||||
|
font-weight 600
|
||||||
|
font-size 16px
|
||||||
|
&:hover
|
||||||
|
background-color #1EC38B
|
||||||
|
transition 0.2s
|
||||||
|
|
||||||
.policy-submit
|
.policy-submit
|
||||||
margin-top 10px
|
margin-top 10px
|
||||||
height 35px
|
height 35px
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import UiTab from './UiTab'
|
|||||||
import InfoTab from './InfoTab'
|
import InfoTab from './InfoTab'
|
||||||
import Crowdfunding from './Crowdfunding'
|
import Crowdfunding from './Crowdfunding'
|
||||||
import StoragesTab from './StoragesTab'
|
import StoragesTab from './StoragesTab'
|
||||||
|
import ExportTab from './ExportTab'
|
||||||
import SnippetTab from './SnippetTab'
|
import SnippetTab from './SnippetTab'
|
||||||
import PluginsTab from './PluginsTab'
|
import PluginsTab from './PluginsTab'
|
||||||
import Blog from './Blog'
|
import Blog from './Blog'
|
||||||
@@ -24,7 +25,8 @@ class Preferences extends React.Component {
|
|||||||
currentTab: 'STORAGES',
|
currentTab: 'STORAGES',
|
||||||
UIAlert: '',
|
UIAlert: '',
|
||||||
HotkeyAlert: '',
|
HotkeyAlert: '',
|
||||||
BlogAlert: ''
|
BlogAlert: '',
|
||||||
|
ExportAlert: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +83,15 @@ class Preferences extends React.Component {
|
|||||||
haveToSave={alert => this.setState({ BlogAlert: alert })}
|
haveToSave={alert => this.setState({ BlogAlert: alert })}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
case 'EXPORT':
|
||||||
|
return (
|
||||||
|
<ExportTab
|
||||||
|
dispatch={dispatch}
|
||||||
|
config={config}
|
||||||
|
data={data}
|
||||||
|
haveToSave={alert => this.setState({ ExportAlert: alert })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'SNIPPET':
|
case 'SNIPPET':
|
||||||
return <SnippetTab dispatch={dispatch} config={config} data={data} />
|
return <SnippetTab dispatch={dispatch} config={config} data={data} />
|
||||||
case 'PLUGINS':
|
case 'PLUGINS':
|
||||||
@@ -131,6 +142,11 @@ class Preferences extends React.Component {
|
|||||||
{ target: 'INFO', label: i18n.__('About') },
|
{ target: 'INFO', label: i18n.__('About') },
|
||||||
{ target: 'CROWDFUNDING', label: i18n.__('Crowdfunding') },
|
{ target: 'CROWDFUNDING', label: i18n.__('Crowdfunding') },
|
||||||
{ target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert },
|
{ target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert },
|
||||||
|
{
|
||||||
|
target: 'EXPORT',
|
||||||
|
label: i18n.__('Export'),
|
||||||
|
Export: this.state.ExportAlert
|
||||||
|
},
|
||||||
{ target: 'SNIPPET', label: i18n.__('Snippets') },
|
{ target: 'SNIPPET', label: i18n.__('Snippets') },
|
||||||
{ target: 'PLUGINS', label: i18n.__('Plugins') }
|
{ target: 'PLUGINS', label: i18n.__('Plugins') }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ module.exports = function(grunt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChildProcess.exec(
|
ChildProcess.exec(
|
||||||
`codesign --verbose --deep --force --sign \"${OSX_COMMON_NAME}\" dist/Boostnote-darwin-x64/Boostnote.app`,
|
`codesign --verbose --deep --force --timestamp=none --sign \"${OSX_COMMON_NAME}\" dist/Boostnote-darwin-x64/Boostnote.app`,
|
||||||
function(err, stdout, stderr) {
|
function(err, stdout, stderr) {
|
||||||
grunt.log.writeln(stdout)
|
grunt.log.writeln(stdout)
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ if (!singleInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isUpdateReady = false
|
var isUpdateReady = false
|
||||||
|
let updateFound = false
|
||||||
|
|
||||||
var ghReleasesOpts = {
|
var ghReleasesOpts = {
|
||||||
repo: 'BoostIO/boost-releases',
|
repo: 'BoostIO/boost-releases',
|
||||||
@@ -36,25 +37,33 @@ const updater = new GhReleases(ghReleasesOpts)
|
|||||||
|
|
||||||
// Check for updates
|
// Check for updates
|
||||||
// `status` returns true if there is a new update available
|
// `status` returns true if there is a new update available
|
||||||
function checkUpdate() {
|
function checkUpdate(manualTriggered = false) {
|
||||||
if (!isPackaged) {
|
if (!isPackaged) {
|
||||||
// Prevents app from attempting to update when in dev mode.
|
// Prevents app from attempting to update when in dev mode.
|
||||||
console.log('Updates are disabled in Development mode, see main-app.js')
|
console.log('Updates are disabled in Development mode, see main-app.js')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (!electronConfig.get('autoUpdateEnabled', true)) return
|
|
||||||
if (process.platform === 'linux' || isUpdateReady) {
|
// End if auto updates disabled and it is an automatic check
|
||||||
|
if (!electronConfig.get('autoUpdateEnabled', true) && !manualTriggered) return
|
||||||
|
|
||||||
|
if (process.platform === 'linux' || isUpdateReady || updateFound) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
updater.check((err, status) => {
|
updater.check((err, status) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
var isLatest = err.message === 'There is no newer version.'
|
var isLatest = err.message === 'There is no newer version.'
|
||||||
if (!isLatest) console.error('Updater error! %s', err.message)
|
if (!isLatest) console.error('Updater error! %s', err.message)
|
||||||
|
mainWindow.webContents.send(
|
||||||
|
'update-not-found',
|
||||||
|
isLatest ? 'There is no newer version.' : 'Updater error'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (status) {
|
if (status) {
|
||||||
mainWindow.webContents.send('update-found', 'Update available!')
|
mainWindow.webContents.send('update-found', 'Update available!')
|
||||||
updater.download()
|
updateFound = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -63,6 +72,7 @@ updater.on('update-downloaded', info => {
|
|||||||
if (mainWindow != null) {
|
if (mainWindow != null) {
|
||||||
mainWindow.webContents.send('update-ready', 'Update available!')
|
mainWindow.webContents.send('update-ready', 'Update available!')
|
||||||
isUpdateReady = true
|
isUpdateReady = true
|
||||||
|
updateFound = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -77,6 +87,14 @@ ipc.on('update-app-confirm', function(event, msg) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipc.on('update-cancel', () => {
|
||||||
|
updateFound = false
|
||||||
|
})
|
||||||
|
|
||||||
|
ipc.on('update-download-confirm', () => {
|
||||||
|
updater.download()
|
||||||
|
})
|
||||||
|
|
||||||
app.on('window-all-closed', function() {
|
app.on('window-all-closed', function() {
|
||||||
app.quit()
|
app.quit()
|
||||||
})
|
})
|
||||||
@@ -113,7 +131,7 @@ app.on('ready', function() {
|
|||||||
if (isUpdateReady) {
|
if (isUpdateReady) {
|
||||||
mainWindow.webContents.send('update-ready', 'Update available!')
|
mainWindow.webContents.send('update-ready', 'Update available!')
|
||||||
} else {
|
} else {
|
||||||
checkUpdate()
|
checkUpdate(msg === 'manual')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 10 * 1000)
|
}, 10 * 1000)
|
||||||
|
|||||||
@@ -178,6 +178,18 @@ const file = {
|
|||||||
mainWindow.webContents.send('list:isMarkdownNote', 'print')
|
mainWindow.webContents.send('list:isMarkdownNote', 'print')
|
||||||
mainWindow.webContents.send('print')
|
mainWindow.webContents.send('print')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.send('update')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -472,9 +484,21 @@ const help = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const team = {
|
||||||
|
label: 'For Team',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'BoostHub',
|
||||||
|
click: async () => {
|
||||||
|
shell.openExternal('https://boosthub.io/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
process.platform === 'darwin'
|
process.platform === 'darwin'
|
||||||
? [boost, file, edit, view, window, help]
|
? [boost, file, edit, view, window, team, help]
|
||||||
: process.platform === 'win32'
|
: process.platform === 'win32'
|
||||||
? [boost, file, view, help]
|
? [boost, file, view, team, help]
|
||||||
: [file, view, help]
|
: [file, view, team, help]
|
||||||
|
|||||||
@@ -202,7 +202,6 @@
|
|||||||
"Create new folder": "Ordner erstellen",
|
"Create new folder": "Ordner erstellen",
|
||||||
"Folder name": "Ordnername",
|
"Folder name": "Ordnername",
|
||||||
"Create": "Erstellen",
|
"Create": "Erstellen",
|
||||||
"Untitled": "Neuer Ordner",
|
|
||||||
"Unlink Storage": "Speicherverknüpfung aufheben",
|
"Unlink Storage": "Speicherverknüpfung aufheben",
|
||||||
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "Die Verknüpfung des Speichers mit Boostnote wird entfernt. Es werden keine Daten gelöscht. Um die Daten dauerhaft zu löschen musst du den Ordner auf der Festplatte manuell entfernen.",
|
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "Die Verknüpfung des Speichers mit Boostnote wird entfernt. Es werden keine Daten gelöscht. Um die Daten dauerhaft zu löschen musst du den Ordner auf der Festplatte manuell entfernen.",
|
||||||
"Empty note": "Leere Notiz",
|
"Empty note": "Leere Notiz",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.16.0",
|
"version": "0.16.1",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
"react-transition-group": "^2.5.0",
|
"react-transition-group": "^2.5.0",
|
||||||
"redux": "^3.5.2",
|
"redux": "^3.5.2",
|
||||||
"sander": "^0.5.1",
|
"sander": "^0.5.1",
|
||||||
"sanitize-html": "^1.18.2",
|
"sanitize-html": "^2.3.2",
|
||||||
"striptags": "^2.2.1",
|
"striptags": "^2.2.1",
|
||||||
"turndown": "^4.0.2",
|
"turndown": "^4.0.2",
|
||||||
"turndown-plugin-gfm": "^1.0.2",
|
"turndown-plugin-gfm": "^1.0.2",
|
||||||
|
|||||||
@@ -702,14 +702,15 @@ it('should remove the all ":storage" and noteKey references', function() {
|
|||||||
' </p>\n' +
|
' </p>\n' +
|
||||||
' </body>\n' +
|
' </body>\n' +
|
||||||
'</html>'
|
'</html>'
|
||||||
const actual = systemUnderTest.removeStorageAndNoteReferences(
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
testInput,
|
testInput,
|
||||||
noteKey
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
)
|
)
|
||||||
expect(actual).toEqual(expectedOutput)
|
expect(actual).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should make sure that "removeStorageAndNoteReferences" works with markdown content as well', function() {
|
it('should make sure that "replaceStorageReferences" works with markdown content as well', function() {
|
||||||
const noteKey = 'noteKey'
|
const noteKey = 'noteKey'
|
||||||
const testInput =
|
const testInput =
|
||||||
'Test input' +
|
'Test input' +
|
||||||
@@ -736,9 +737,113 @@ it('should make sure that "removeStorageAndNoteReferences" works with markdown c
|
|||||||
systemUnderTest.DESTINATION_FOLDER +
|
systemUnderTest.DESTINATION_FOLDER +
|
||||||
path.posix.sep +
|
path.posix.sep +
|
||||||
'pdf.pdf)'
|
'pdf.pdf)'
|
||||||
const actual = systemUnderTest.removeStorageAndNoteReferences(
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
testInput,
|
testInput,
|
||||||
noteKey
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
|
)
|
||||||
|
expect(actual).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should replace the all ":storage" references', function() {
|
||||||
|
const storageFolder = systemUnderTest.DESTINATION_FOLDER
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const testInput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src=":storage' +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
noteKey +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
'0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href=":storage' +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
noteKey +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
'0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src=":storage' +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
noteKey +
|
||||||
|
mdurl.encode(path.sep) +
|
||||||
|
'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const expectedOutput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src="' +
|
||||||
|
storageFolder +
|
||||||
|
path.sep +
|
||||||
|
'0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href="' +
|
||||||
|
storageFolder +
|
||||||
|
path.sep +
|
||||||
|
'0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src="' +
|
||||||
|
storageFolder +
|
||||||
|
path.sep +
|
||||||
|
'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
|
testInput,
|
||||||
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
|
)
|
||||||
|
expect(actual).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make sure that "replaceStorageReferences" works with markdown content as well', function() {
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[pdf](' +
|
||||||
|
systemUnderTest.STORAGE_FOLDER_PLACEHOLDER +
|
||||||
|
path.posix.sep +
|
||||||
|
noteKey +
|
||||||
|
path.posix.sep +
|
||||||
|
'pdf.pdf)'
|
||||||
|
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[pdf](' +
|
||||||
|
systemUnderTest.DESTINATION_FOLDER +
|
||||||
|
path.posix.sep +
|
||||||
|
'pdf.pdf)'
|
||||||
|
const actual = systemUnderTest.replaceStorageReferences(
|
||||||
|
testInput,
|
||||||
|
noteKey,
|
||||||
|
systemUnderTest.DESTINATION_FOLDER
|
||||||
)
|
)
|
||||||
expect(actual).toEqual(expectedOutput)
|
expect(actual).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -52,12 +52,20 @@ test.serial('Export a folder', t => {
|
|||||||
}
|
}
|
||||||
input2.title = 'input2'
|
input2.title = 'input2'
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
export: {
|
||||||
|
metadata: 'DONT_EXPORT',
|
||||||
|
variable: 'boostnote',
|
||||||
|
prefixAttachmentFolder: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return createNote(storageKey, input1)
|
return createNote(storageKey, input1)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return createNote(storageKey, input2)
|
return createNote(storageKey, input2)
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return exportFolder(storageKey, folderKey, 'md', storagePath)
|
return exportFolder(storageKey, folderKey, 'md', storagePath, config)
|
||||||
})
|
})
|
||||||
.then(function assert() {
|
.then(function assert() {
|
||||||
let filePath = path.join(storagePath, 'input1.md')
|
let filePath = path.join(storagePath, 'input1.md')
|
||||||
|
|||||||
@@ -35,7 +35,16 @@ test.serial('Export a storage', t => {
|
|||||||
acc[folder.key] = folder.name
|
acc[folder.key] = folder.name
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
return exportStorage(storageKey, 'md', exportDir).then(() => {
|
|
||||||
|
const config = {
|
||||||
|
export: {
|
||||||
|
metadata: 'DONT_EXPORT',
|
||||||
|
variable: 'boostnote',
|
||||||
|
prefixAttachmentFolder: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportStorage(storageKey, 'md', exportDir, config).then(() => {
|
||||||
notes.forEach(note => {
|
notes.forEach(note => {
|
||||||
const noteDir = path.join(
|
const noteDir = path.join(
|
||||||
exportDir,
|
exportDir,
|
||||||
|
|||||||
@@ -98,11 +98,11 @@ exports[`Markdown.render() should renders checkboxes 1`] = `
|
|||||||
|
|
||||||
exports[`Markdown.render() should renders codeblock correctly 1`] = `
|
exports[`Markdown.render() should renders codeblock correctly 1`] = `
|
||||||
"<pre class=\\"code CodeMirror\\" data-line=\\"1\\">
|
"<pre class=\\"code CodeMirror\\" data-line=\\"1\\">
|
||||||
<span class=\\"filename\\">filename.js</span>
|
<span class=\\"filename\\">filename.js</span>
|
||||||
<span class=\\"lineNumber CodeMirror-gutters\\"><span class=\\"CodeMirror-linenumber\\">2</span></span>
|
<span class=\\"lineNumber CodeMirror-gutters\\"><span class=\\"CodeMirror-linenumber\\">2</span></span>
|
||||||
<code class=\\"js\\">var project = 'boostnote';
|
<code class=\\"js\\">var project = 'boostnote';
|
||||||
</code>
|
</code>
|
||||||
</pre>"
|
</pre>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Markdown.render() should renders definition lists correctly 1`] = `
|
exports[`Markdown.render() should renders definition lists correctly 1`] = `
|
||||||
|
|||||||
162
yarn.lock
162
yarn.lock
@@ -1658,7 +1658,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
|||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
supports-color "^2.0.0"
|
supports-color "^2.0.0"
|
||||||
|
|
||||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1:
|
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.2:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1940,6 +1940,11 @@ color@^3.0.0:
|
|||||||
color-convert "^1.9.1"
|
color-convert "^1.9.1"
|
||||||
color-string "^1.5.2"
|
color-string "^1.5.2"
|
||||||
|
|
||||||
|
colorette@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
|
||||||
|
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
|
||||||
|
|
||||||
colormin@^1.0.5:
|
colormin@^1.0.5:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133"
|
resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133"
|
||||||
@@ -2785,6 +2790,11 @@ deep-is@~0.1.3:
|
|||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||||
|
|
||||||
|
deepmerge@^4.2.2:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||||
|
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||||
|
|
||||||
default-require-extensions@^1.0.0:
|
default-require-extensions@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
|
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
|
||||||
@@ -2915,12 +2925,14 @@ dom-helpers@^3.3.1:
|
|||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
|
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
|
||||||
|
|
||||||
dom-serializer@0:
|
dom-serializer@^1.0.1:
|
||||||
version "0.1.0"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be"
|
||||||
|
integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "~1.1.1"
|
domelementtype "^2.0.1"
|
||||||
entities "~1.1.1"
|
domhandler "^4.0.0"
|
||||||
|
entities "^2.0.0"
|
||||||
|
|
||||||
dom-storage@^2.0.2:
|
dom-storage@^2.0.2:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
@@ -2934,13 +2946,10 @@ domain-browser@^1.1.1:
|
|||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||||
|
|
||||||
domelementtype@1, domelementtype@^1.3.0:
|
domelementtype@^2.0.1, domelementtype@^2.2.0:
|
||||||
version "1.3.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||||
|
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||||
domelementtype@~1.1.1:
|
|
||||||
version "1.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
|
|
||||||
|
|
||||||
domexception@^1.0.0, domexception@^1.0.1:
|
domexception@^1.0.0, domexception@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
@@ -2948,18 +2957,21 @@ domexception@^1.0.0, domexception@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
webidl-conversions "^4.0.2"
|
webidl-conversions "^4.0.2"
|
||||||
|
|
||||||
domhandler@^2.3.0:
|
domhandler@^4.0.0, domhandler@^4.2.0:
|
||||||
version "2.4.2"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059"
|
||||||
|
integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "1"
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
domutils@^1.5.1:
|
domutils@^2.5.2:
|
||||||
version "1.7.0"
|
version "2.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7"
|
||||||
|
integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA==
|
||||||
dependencies:
|
dependencies:
|
||||||
dom-serializer "0"
|
dom-serializer "^1.0.1"
|
||||||
domelementtype "1"
|
domelementtype "^2.2.0"
|
||||||
|
domhandler "^4.2.0"
|
||||||
|
|
||||||
dot-prop@^4.1.0:
|
dot-prop@^4.1.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
@@ -3177,7 +3189,12 @@ enhanced-resolve@~0.9.0:
|
|||||||
memory-fs "^0.2.0"
|
memory-fs "^0.2.0"
|
||||||
tapable "^0.1.8"
|
tapable "^0.1.8"
|
||||||
|
|
||||||
entities@^1.1.1, entities@~1.1.1:
|
entities@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
|
||||||
|
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
|
||||||
|
|
||||||
|
entities@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
|
||||||
|
|
||||||
@@ -3301,6 +3318,11 @@ escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^
|
|||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||||
|
|
||||||
|
escape-string-regexp@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||||
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
escaper@^2.5.3:
|
escaper@^2.5.3:
|
||||||
version "2.5.3"
|
version "2.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/escaper/-/escaper-2.5.3.tgz#8b8fe90ba364054151ab7eff18b4ce43b1e13ab5"
|
resolved "https://registry.yarnpkg.com/escaper/-/escaper-2.5.3.tgz#8b8fe90ba364054151ab7eff18b4ce43b1e13ab5"
|
||||||
@@ -4631,16 +4653,15 @@ html-minifier@^4.0.0:
|
|||||||
relateurl "^0.2.7"
|
relateurl "^0.2.7"
|
||||||
uglify-js "^3.5.1"
|
uglify-js "^3.5.1"
|
||||||
|
|
||||||
htmlparser2@^3.9.0:
|
htmlparser2@^6.0.0:
|
||||||
version "3.9.2"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||||
|
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "^1.3.0"
|
domelementtype "^2.0.1"
|
||||||
domhandler "^2.3.0"
|
domhandler "^4.0.0"
|
||||||
domutils "^1.5.1"
|
domutils "^2.5.2"
|
||||||
entities "^1.1.1"
|
entities "^2.0.0"
|
||||||
inherits "^2.0.1"
|
|
||||||
readable-stream "^2.0.2"
|
|
||||||
|
|
||||||
http-errors@1.6.2:
|
http-errors@1.6.2:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
@@ -5175,6 +5196,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isobject "^3.0.1"
|
isobject "^3.0.1"
|
||||||
|
|
||||||
|
is-plain-object@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
|
||||||
|
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
|
||||||
|
|
||||||
is-posix-bracket@^0.1.0:
|
is-posix-bracket@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
||||||
@@ -5935,6 +5961,11 @@ klaw@^1.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.9"
|
graceful-fs "^4.1.9"
|
||||||
|
|
||||||
|
klona@^2.0.3:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
|
||||||
|
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
|
||||||
|
|
||||||
last-line-stream@^1.0.0:
|
last-line-stream@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/last-line-stream/-/last-line-stream-1.0.0.tgz#d1b64d69f86ff24af2d04883a2ceee14520a5600"
|
resolved "https://registry.yarnpkg.com/last-line-stream/-/last-line-stream-1.0.0.tgz#d1b64d69f86ff24af2d04883a2ceee14520a5600"
|
||||||
@@ -6100,10 +6131,6 @@ lodash.difference@^4.3.0:
|
|||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
|
resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
|
||||||
|
|
||||||
lodash.escaperegexp@^4.1.2:
|
|
||||||
version "4.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
|
||||||
|
|
||||||
lodash.flatten@^4.2.0, lodash.flatten@^4.4.0:
|
lodash.flatten@^4.2.0, lodash.flatten@^4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||||
@@ -6125,10 +6152,6 @@ lodash.isequal@^4.5.0:
|
|||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
|
||||||
lodash.isplainobject@^4.0.6:
|
|
||||||
version "4.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
|
||||||
|
|
||||||
lodash.isstring@^4.0.1:
|
lodash.isstring@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
@@ -6141,11 +6164,6 @@ lodash.merge@^4.6.0:
|
|||||||
version "4.6.1"
|
version "4.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
|
||||||
|
|
||||||
lodash.mergewith@^4.6.0:
|
|
||||||
version "4.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
|
|
||||||
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
|
|
||||||
|
|
||||||
lodash.some@^4.5.1:
|
lodash.some@^4.5.1:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||||
@@ -6694,6 +6712,11 @@ nan@^2.9.2:
|
|||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||||
|
|
||||||
|
nanoid@^3.1.22:
|
||||||
|
version "3.1.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
|
||||||
|
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
|
||||||
|
|
||||||
nanomatch@^1.2.9:
|
nanomatch@^1.2.9:
|
||||||
version "1.2.9"
|
version "1.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2"
|
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2"
|
||||||
@@ -7226,6 +7249,11 @@ parse-ms@^1.0.0:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d"
|
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d"
|
||||||
|
|
||||||
|
parse-srcset@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
|
||||||
|
integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=
|
||||||
|
|
||||||
parse5@4.0.0:
|
parse5@4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
|
||||||
@@ -7643,13 +7671,14 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
|
|||||||
source-map "^0.5.6"
|
source-map "^0.5.6"
|
||||||
supports-color "^3.2.3"
|
supports-color "^3.2.3"
|
||||||
|
|
||||||
postcss@^6.0.14:
|
postcss@^8.0.2:
|
||||||
version "6.0.22"
|
version "8.2.14"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3"
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.14.tgz#dcf313eb8247b3ce8078d048c0e8262ca565ad2b"
|
||||||
|
integrity sha512-+jD0ZijcvyCqPQo/m/CW0UcARpdFylq04of+Q7RKX6f/Tu+dvpUI/9Sp81+i6/vJThnOBX09Quw0ZLOVwpzX3w==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^2.4.1"
|
colorette "^1.2.2"
|
||||||
|
nanoid "^3.1.22"
|
||||||
source-map "^0.6.1"
|
source-map "^0.6.1"
|
||||||
supports-color "^5.4.0"
|
|
||||||
|
|
||||||
prelude-ls@~1.1.2:
|
prelude-ls@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
@@ -8656,20 +8685,18 @@ sanitize-filename@^1.6.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
truncate-utf8-bytes "^1.0.0"
|
truncate-utf8-bytes "^1.0.0"
|
||||||
|
|
||||||
sanitize-html@^1.18.2:
|
sanitize-html@^2.3.2:
|
||||||
version "1.18.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.18.2.tgz#61877ba5a910327e42880a28803c2fbafa8e4642"
|
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.3.2.tgz#a1954aea877a096c408aca7b0c260bef6e4fc402"
|
||||||
|
integrity sha512-p7neuskvC8pSurUjdVmbWPXmc9A4+QpOXIL+4gwFC+av5h+lYCXFT8uEneqsFQg/wEA1IH+cKQA60AaQI6p3cg==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^2.3.0"
|
deepmerge "^4.2.2"
|
||||||
htmlparser2 "^3.9.0"
|
escape-string-regexp "^4.0.0"
|
||||||
lodash.clonedeep "^4.5.0"
|
htmlparser2 "^6.0.0"
|
||||||
lodash.escaperegexp "^4.1.2"
|
is-plain-object "^5.0.0"
|
||||||
lodash.isplainobject "^4.0.6"
|
klona "^2.0.3"
|
||||||
lodash.isstring "^4.0.1"
|
parse-srcset "^1.0.2"
|
||||||
lodash.mergewith "^4.6.0"
|
postcss "^8.0.2"
|
||||||
postcss "^6.0.14"
|
|
||||||
srcset "^1.0.0"
|
|
||||||
xtend "^4.0.0"
|
|
||||||
|
|
||||||
sax@0.5.x:
|
sax@0.5.x:
|
||||||
version "0.5.8"
|
version "0.5.8"
|
||||||
@@ -9075,13 +9102,6 @@ sprintf@^0.1.5:
|
|||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf"
|
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf"
|
||||||
|
|
||||||
srcset@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
|
|
||||||
dependencies:
|
|
||||||
array-uniq "^1.0.2"
|
|
||||||
number-is-nan "^1.0.0"
|
|
||||||
|
|
||||||
sshpk@^1.7.0:
|
sshpk@^1.7.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb"
|
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb"
|
||||||
@@ -9360,7 +9380,7 @@ supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-co
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^1.0.0"
|
has-flag "^1.0.0"
|
||||||
|
|
||||||
supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0:
|
supports-color@^5.0.0, supports-color@^5.3.0:
|
||||||
version "5.4.0"
|
version "5.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user