diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js
index e9e1dfbf..f16cc53c 100644
--- a/browser/components/CodeEditor.js
+++ b/browser/components/CodeEditor.js
@@ -5,23 +5,18 @@ import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
+import { options, TableEditor } from '@susisu/mte-kernel'
+import TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
import crypto from 'crypto'
import consts from 'browser/lib/consts'
import fs from 'fs'
const { ipcRenderer } = require('electron')
+import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
-const defaultEditorFontFamily = [
- 'Monaco',
- 'Menlo',
- 'Ubuntu Mono',
- 'Consolas',
- 'source-code-pro',
- 'monospace'
-]
const buildCMRulers = (rulers, enableRulers) =>
(enableRulers ? rulers.map(ruler => ({ column: ruler })) : [])
@@ -63,6 +58,8 @@ export default class CodeEditor extends React.Component {
}
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
+
+ this.formatTable = () => this.handleFormatTable()
}
handleSearch (msg) {
@@ -99,6 +96,10 @@ export default class CodeEditor extends React.Component {
})
}
+ handleFormatTable () {
+ this.tableEditor.formatAll(options({textWidthOptions: {}}))
+ }
+
componentDidMount () {
const { rulers, enableRulers } = this.props
const expandSnippet = this.expandSnippet.bind(this)
@@ -135,7 +136,12 @@ export default class CodeEditor extends React.Component {
dragDrop: false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
- autoCloseBrackets: true,
+ autoCloseBrackets: {
+ pairs: '()[]{}\'\'""$$**``',
+ triples: '```"""\'\'\'',
+ explode: '[]{}``$$',
+ override: true
+ },
extraKeys: {
Tab: function (cm) {
const cursor = cm.getCursor()
@@ -210,6 +216,8 @@ export default class CodeEditor extends React.Component {
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
CodeMirror.Vim.map('ZZ', ':q', 'normal')
this.setState({ isReady: true })
+ this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
+ eventEmitter.on('code:format-table', this.formatTable)
}
expandSnippet (line, cursor, cm, snippets) {
@@ -299,6 +307,8 @@ export default class CodeEditor extends React.Component {
this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)
+
+ eventEmitter.off('code:format-table', this.formatTable)
}
componentDidUpdate (prevProps, prevState) {
@@ -561,11 +571,8 @@ export default class CodeEditor extends React.Component {
}
render () {
- const { className, fontSize } = this.props
- let fontFamily = this.props.fontFamily
- fontFamily = _.isString(fontFamily) && fontFamily.length > 0
- ? [fontFamily].concat(defaultEditorFontFamily)
- : defaultEditorFontFamily
+ const {className, fontSize} = this.props
+ const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
const width = this.props.width
return (
{
+ let result = noteContent
+ if (this.props && this.props.storagePath && this.props.noteKey) {
+ const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
+ attachmentsAbsolutePaths.forEach((attachment) => {
+ exportTasks.push({
+ src: attachment,
+ dst: attachmentManagement.DESTINATION_FOLDER
+ })
+ })
+ result = attachmentManagement.removeStorageAndNoteReferences(noteContent, this.props.noteKey)
+ }
+ return result
+ })
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => {
- const {
- fontFamily,
- fontSize,
- codeBlockFontFamily,
- lineNumber,
- codeBlockTheme,
- scrollPastEnd,
- theme,
- allowCustomCSS,
- customCSS
- } = this.getStyleParams()
- const inlineStyles = buildStyle(
- fontFamily,
- fontSize,
- codeBlockFontFamily,
- lineNumber,
- scrollPastEnd,
- theme,
- allowCustomCSS,
- customCSS
- )
- let body = this.markdown.render(escapeHtmlCharacters(noteContent))
+ const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
+ const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
+ let body = this.markdown.render(escapeHtmlCharacters(noteContent, { detectCodeBlock: true }))
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent,
this.props.storagePath
)
- files.forEach(file => {
- file = file.replace('file://', '')
+
+ files.forEach((file) => {
+ if (global.process.platform === 'win32') {
+ file = file.replace('file:///', '')
+ } else {
+ file = file.replace('file://', '')
+ }
exportTasks.push({
src: file,
dst: 'css'
@@ -347,6 +367,21 @@ export default class MarkdownPreview extends React.Component {
}
}
+ getScrollBarStyle () {
+ const {
+ theme
+ } = this.props
+
+ switch (theme) {
+ case 'dark':
+ case 'solarized-dark':
+ case 'monokai':
+ return scrollBarDarkStyle
+ default:
+ return scrollBarStyle
+ }
+ }
+
componentDidMount () {
this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener(
@@ -358,6 +393,9 @@ export default class MarkdownPreview extends React.Component {
+
`
CSS_FILES.forEach(file => {
@@ -565,23 +603,9 @@ export default class MarkdownPreview extends React.Component {
let { value, codeBlockTheme } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
-
- const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g)
- if (codeBlocks !== null) {
- codeBlocks.forEach(codeBlock => {
- value = value.replace(
- codeBlock,
- htmlTextHelper.encodeEntities(codeBlock)
- )
- })
- }
- let renderedHTML = this.markdown.render(value)
- attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
- this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
- renderedHTML,
- storagePath
- )
-
+ const renderedHTML = this.markdown.render(value)
+ attachmentManagement.migrateAttachments(value, storagePath, noteKey)
+ this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll(
'input[type="checkbox"]'
@@ -683,6 +707,30 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = 'Sequence diagram parse error: ' + e.message
}
}
+ })
+
+ _.forEach(
+ this.refs.root.contentWindow.document.querySelectorAll('.chart'),
+ (el) => {
+ try {
+ const chartConfig = JSON.parse(el.innerHTML)
+ el.innerHTML = ''
+ var canvas = document.createElement('canvas')
+ el.appendChild(canvas)
+ /* eslint-disable no-new */
+ new Chart(canvas, chartConfig)
+ } catch (e) {
+ console.error(e)
+ el.className = 'chart-error'
+ el.innerHTML = 'chartjs diagram parse error: ' + e.message
+ }
+ }
+ )
+ _.forEach(
+ this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
+ (el) => {
+ mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme)
+ }
)
}
diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl
index cf94bb8e..03503231 100644
--- a/browser/components/markdown.styl
+++ b/browser/components/markdown.styl
@@ -68,7 +68,7 @@ body
padding 5px
margin -5px
border-radius 5px
- .flowchart-error, .sequence-error
+ .flowchart-error, .sequence-error .chart-error
background-color errorBackgroundColor
color errorTextColor
padding 5px
@@ -213,7 +213,7 @@ pre
margin 0 0 1em
display flex
line-height 1.4em
- &.flowchart, &.sequence
+ &.flowchart, &.sequence, &.chart
display flex
justify-content center
background-color white
diff --git a/browser/components/render/MermaidRender.js b/browser/components/render/MermaidRender.js
new file mode 100644
index 00000000..12dce327
--- /dev/null
+++ b/browser/components/render/MermaidRender.js
@@ -0,0 +1,39 @@
+import mermaidAPI from 'mermaid'
+
+// fixes bad styling in the mermaid dark theme
+const darkThemeStyling = `
+.loopText tspan {
+ fill: white;
+}`
+
+function getRandomInt (min, max) {
+ return Math.floor(Math.random() * (max - min)) + min
+}
+
+function getId () {
+ var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
+ var id = 'm-'
+ for (var i = 0; i < 7; i++) {
+ id += pool[getRandomInt(0, 16)]
+ }
+ return id
+}
+
+function render (element, content, theme) {
+ try {
+ let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai'
+ mermaidAPI.initialize({
+ theme: isDarkTheme ? 'dark' : 'default',
+ themeCSS: isDarkTheme ? darkThemeStyling : ''
+ })
+ mermaidAPI.render(getId(), content, (svgGraph) => {
+ element.innerHTML = svgGraph
+ })
+ } catch (e) {
+ console.error(e)
+ element.className = 'mermaid-error'
+ element.innerHTML = 'mermaid diagram parse error: ' + e.message
+ }
+}
+
+export default render
diff --git a/browser/lib/TextEditorInterface.js b/browser/lib/TextEditorInterface.js
new file mode 100644
index 00000000..53ae2337
--- /dev/null
+++ b/browser/lib/TextEditorInterface.js
@@ -0,0 +1,53 @@
+import { Point } from '@susisu/mte-kernel'
+
+export default class TextEditorInterface {
+ constructor (editor) {
+ this.editor = editor
+ }
+
+ getCursorPosition () {
+ const pos = this.editor.getCursor()
+ return new Point(pos.line, pos.ch)
+ }
+
+ setCursorPosition (pos) {
+ this.editor.setCursor({line: pos.row, ch: pos.column})
+ }
+
+ setSelectionRange (range) {
+ this.editor.setSelection({
+ anchor: {line: range.start.row, ch: range.start.column},
+ head: {line: range.end.row, ch: range.end.column}
+ })
+ }
+
+ getLastRow () {
+ return this.editor.lastLine()
+ }
+
+ acceptsTableEdit (row) {
+ return true
+ }
+
+ getLine (row) {
+ return this.editor.getLine(row)
+ }
+
+ insertLine (row, line) {
+ this.editor.replaceRange(line, {line: row, ch: 0})
+ }
+
+ deleteLine (row) {
+ this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length})
+ }
+
+ replaceLines (startRow, endRow, lines) {
+ endRow-- // because endRow is a first line after a table.
+ const endRowCh = this.editor.getLine(endRow).length
+ this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh})
+ }
+
+ transact (func) {
+ func()
+ }
+}
diff --git a/browser/lib/consts.js b/browser/lib/consts.js
index ec811007..84b962eb 100644
--- a/browser/lib/consts.js
+++ b/browser/lib/consts.js
@@ -36,7 +36,15 @@ const consts = {
'Violet Eggplant'
],
THEMES: ['default'].concat(themes),
- SNIPPET_FILE: snippetFile
+ SNIPPET_FILE: snippetFile,
+ DEFAULT_EDITOR_FONT_FAMILY: [
+ 'Monaco',
+ 'Menlo',
+ 'Ubuntu Mono',
+ 'Consolas',
+ 'source-code-pro',
+ 'monospace'
+ ]
}
module.exports = consts
diff --git a/browser/lib/markdown-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js
index beec9566..05e5e7be 100644
--- a/browser/lib/markdown-it-sanitize-html.js
+++ b/browser/lib/markdown-it-sanitize-html.js
@@ -1,6 +1,7 @@
'use strict'
import sanitizeHtml from 'sanitize-html'
+import { escapeHtmlCharacters } from './utils'
module.exports = function sanitizePlugin (md, options) {
options = options || {}
@@ -8,13 +9,26 @@ module.exports = function sanitizePlugin (md, options) {
md.core.ruler.after('linkify', 'sanitize_inline', state => {
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
if (state.tokens[tokenIdx].type === 'html_block') {
- state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
+ state.tokens[tokenIdx].content = sanitizeHtml(
+ state.tokens[tokenIdx].content,
+ options
+ )
+ }
+ if (state.tokens[tokenIdx].type === 'fence') {
+ // escapeHtmlCharacters has better performance
+ state.tokens[tokenIdx].content = escapeHtmlCharacters(
+ state.tokens[tokenIdx].content,
+ { skipSingleQuote: true }
+ )
}
if (state.tokens[tokenIdx].type === 'inline') {
const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
if (inlineTokens[childIdx].type === 'html_inline') {
- inlineTokens[childIdx].content = sanitizeHtml(inlineTokens[childIdx].content, options)
+ inlineTokens[childIdx].content = sanitizeHtml(
+ inlineTokens[childIdx].content,
+ options
+ )
}
}
}
diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js
index 4dafa4a3..49fd2f86 100644
--- a/browser/lib/markdown.js
+++ b/browser/lib/markdown.js
@@ -40,6 +40,12 @@ class Markdown {
if (langType === 'sequence') {
return `
${str}`
}
+ if (langType === 'chart') {
+ return `
${str}`
+ }
+ if (langType === 'mermaid') {
+ return `
${str}`
+ }
return '
' +
'' + fileName + '' +
createGutter(str, firstLineNumber) +
@@ -157,6 +163,22 @@ class Markdown {
}
})
+ // Ditaa support
+ this.md.use(require('markdown-it-plantuml'), {
+ openMarker: '@startditaa',
+ closeMarker: '@endditaa',
+ generateSource: function (umlCode) {
+ const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
+ // Currently PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
+ const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/png'
+ const s = unescape(encodeURIComponent(umlCode))
+ const zippedCode = deflate.encode64(
+ deflate.zip_deflate(`@startditaa\n${s}\n@endditaa`, 9)
+ )
+ return `${serverAddress}/${zippedCode}`
+ }
+ })
+
// Override task item
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
@@ -245,4 +267,3 @@ class Markdown {
}
export default Markdown
-
diff --git a/browser/lib/normalizeEditorFontFamily.js b/browser/lib/normalizeEditorFontFamily.js
new file mode 100644
index 00000000..a2a2ec31
--- /dev/null
+++ b/browser/lib/normalizeEditorFontFamily.js
@@ -0,0 +1,9 @@
+import consts from 'browser/lib/consts'
+import isString from 'lodash/isString'
+
+export default function normalizeEditorFontFamily (fontFamily) {
+ const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
+ return isString(fontFamily) && fontFamily.length > 0
+ ? [fontFamily].concat(defaultEditorFontFamily).join(', ')
+ : defaultEditorFontFamily.join(', ')
+}
diff --git a/browser/lib/search.js b/browser/lib/search.js
index b42fd389..cf5b3b1b 100644
--- a/browser/lib/search.js
+++ b/browser/lib/search.js
@@ -23,7 +23,9 @@ function findByWordOrTag (notes, block) {
return true
}
if (note.type === 'SNIPPET_NOTE') {
- return note.description.match(wordRegExp)
+ return note.description.match(wordRegExp) || note.snippets.some((snippet) => {
+ return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
+ })
} else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(wordRegExp)
}
diff --git a/browser/lib/utils.js b/browser/lib/utils.js
index 441cfbc7..1d15b722 100644
--- a/browser/lib/utils.js
+++ b/browser/lib/utils.js
@@ -6,52 +6,113 @@ export function lastFindInArray (array, callback) {
}
}
-export function escapeHtmlCharacters (text) {
- const matchHtmlRegExp = /["'&<>]/
- const str = '' + text
- const match = matchHtmlRegExp.exec(str)
+export function escapeHtmlCharacters (
+ html,
+ opt = { detectCodeBlock: false, skipSingleQuote: false }
+) {
+ const matchHtmlRegExp = /["'&<>]/g
+ const matchCodeBlockRegExp = /```/g
+ const escapes = ['"', '&', ''', '<', '>']
+ let match = null
+ const replaceAt = (str, index, replace) =>
+ str.substr(0, index) +
+ replace +
+ str.substr(index + replace.length - (replace.length - 1))
- if (!match) {
- return str
- }
-
- let escape
- let html = ''
- let index = 0
- let lastIndex = 0
-
- for (index = match.index; index < str.length; index++) {
- switch (str.charCodeAt(index)) {
- case 34: // "
- escape = '"'
- break
- case 38: // &
- escape = '&'
- break
- case 39: // '
- escape = '''
- break
- case 60: // <
- escape = '<'
- break
- case 62: // >
- escape = '>'
- break
- default:
+ while ((match = matchHtmlRegExp.exec(html)) !== null) {
+ const current = { char: match[0], index: match.index }
+ const codeBlockIndexs = []
+ let openCodeBlock = null
+ // if the detectCodeBlock option is activated then this function should skip
+ // characters that needed to be escape but located in code block
+ if (opt.detectCodeBlock) {
+ // The first type of code block is lines that start with 4 spaces
+ // Here we check for the \n character located before the character that
+ // needed to be escape. It means we check for the begining of the line that
+ // contain that character, then we check if there are 4 spaces next to the
+ // \n character (the line start with 4 spaces)
+ let previousLineEnd = current.index - 1
+ while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
+ previousLineEnd--
+ }
+ // 4 spaces means this character is in a code block
+ if (
+ html[previousLineEnd + 1] === ' ' &&
+ html[previousLineEnd + 2] === ' ' &&
+ html[previousLineEnd + 3] === ' ' &&
+ html[previousLineEnd + 4] === ' '
+ ) {
+ // skip the current character
continue
+ }
+ // The second type of code block is lines that wrapped in ```
+ // We will get the position of each ```
+ // then push it into an array
+ // then the array returned will be like this:
+ // [startCodeblock, endCodeBlock, startCodeBlock, endCodeBlock]
+ while ((openCodeBlock = matchCodeBlockRegExp.exec(html)) !== null) {
+ codeBlockIndexs.push(openCodeBlock.index)
+ }
+ let shouldSkipChar = false
+ // we loop through the array of positions
+ // we skip 2 element as the i index position is the position of ``` that
+ // open the codeblock and the i + 1 is the position of the ``` that close
+ // the code block
+ for (let i = 0; i < codeBlockIndexs.length; i += 2) {
+ // the i index position is the position of the ``` that open code block
+ // so we have to + 2 as that position is the position of the first ` in the ````
+ // but we need to make sure that the position current character is larger
+ // that the last ` in the ``` that open the code block so we have to take
+ // the position of the first ` and + 2
+ // the i + 1 index position is the closing ``` so the char must less than it
+ if (
+ current.index > codeBlockIndexs[i] + 2 &&
+ current.index < codeBlockIndexs[i + 1]
+ ) {
+ // skip it
+ shouldSkipChar = true
+ break
+ }
+ }
+ if (shouldSkipChar) {
+ // skip the current character
+ continue
+ }
}
-
- if (lastIndex !== index) {
- html += str.substring(lastIndex, index)
+ // otherwise, escape it !!!
+ if (current.char === '&') {
+ // when escaping character & we have to be becareful as the & could be a part
+ // of an escaped character like " will be came "
+ let nextStr = ''
+ let nextIndex = current.index
+ let escapedStr = false
+ // maximum length of an escaped string is 5. For example ('"')
+ // we take the next 5 character of the next string if it is one of the string:
+ // ['"', '&', ''', '<', '>'] then we will not escape the & character
+ // as it is a part of the escaped string and should not be escaped
+ while (nextStr.length <= 5) {
+ nextStr += html[nextIndex]
+ nextIndex++
+ if (escapes.indexOf(nextStr) !== -1) {
+ escapedStr = true
+ break
+ }
+ }
+ if (!escapedStr) {
+ // this & char is not a part of an escaped string
+ html = replaceAt(html, current.index, '&')
+ }
+ } else if (current.char === '"') {
+ html = replaceAt(html, current.index, '"')
+ } else if (current.char === "'" && !opt.skipSingleQuote) {
+ html = replaceAt(html, current.index, ''')
+ } else if (current.char === '<') {
+ html = replaceAt(html, current.index, '<')
+ } else if (current.char === '>') {
+ html = replaceAt(html, current.index, '>')
}
-
- lastIndex = index + 1
- html += escape
}
-
- return lastIndex !== index
- ? html + str.substring(lastIndex, index)
- : html
+ return html
}
export function isObjectEqual (a, b) {
diff --git a/browser/main/Detail/FullscreenButton.js b/browser/main/Detail/FullscreenButton.js
index 3d29c264..ee212603 100644
--- a/browser/main/Detail/FullscreenButton.js
+++ b/browser/main/Detail/FullscreenButton.js
@@ -4,12 +4,14 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n'
+const OSX = global.process.platform === 'darwin'
+const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
const FullscreenButton = ({
onClick
}) => (
)
diff --git a/browser/main/Detail/InfoPanel.styl b/browser/main/Detail/InfoPanel.styl
index ac91bbec..2a73ca7e 100644
--- a/browser/main/Detail/InfoPanel.styl
+++ b/browser/main/Detail/InfoPanel.styl
@@ -33,6 +33,7 @@
.control-infoButton-panel-trash
z-index 200
margin-top 0px
+ top 50px
right 0px
position absolute
padding 20px 25px 0 25px
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js
index c65f1425..652d1f53 100644
--- a/browser/main/Detail/SnippetNoteDetail.js
+++ b/browser/main/Detail/SnippetNoteDetail.js
@@ -32,7 +32,7 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
const electron = require('electron')
const { remote } = electron
-const { Menu, MenuItem, dialog } = remote
+const { dialog } = remote
class SnippetNoteDetail extends React.Component {
constructor (props) {
@@ -441,7 +441,7 @@ class SnippetNoteDetail extends React.Component {
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
- if (isSuper) {
+ if (isSuper && !e.shiftKey) {
e.preventDefault()
this.addSnippet()
}
@@ -451,14 +451,14 @@ class SnippetNoteDetail extends React.Component {
}
handleModeButtonClick (e, index) {
- const menu = new Menu()
+ const templetes = []
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
- menu.append(new MenuItem({
+ templetes.push({
label: mode.name,
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
- }))
+ })
})
- menu.popup(remote.getCurrentWindow())
+ context.popup(templetes)
}
handleIndentTypeButtonClick (e) {
diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js
index d14c7a8c..e251dd42 100644
--- a/browser/main/Detail/TagSelect.js
+++ b/browser/main/Detail/TagSelect.js
@@ -5,6 +5,7 @@ import styles from './TagSelect.styl'
import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
+import ee from 'browser/main/lib/eventEmitter'
class TagSelect extends React.Component {
constructor (props) {
@@ -13,16 +14,26 @@ class TagSelect extends React.Component {
this.state = {
newTag: ''
}
+ this.addtagHandler = this.handleAddTag.bind(this)
}
componentDidMount () {
this.value = this.props.value
+ ee.on('editor:add-tag', this.addtagHandler)
}
componentDidUpdate () {
this.value = this.props.value
}
+ componentWillUnmount () {
+ ee.off('editor:add-tag', this.addtagHandler)
+ }
+
+ handleAddTag () {
+ this.refs.newTag.focus()
+ }
+
handleNewTagInputKeyDown (e) {
switch (e.keyCode) {
case 9:
diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js
index 2c451085..b6b6ef14 100644
--- a/browser/main/Detail/index.js
+++ b/browser/main/Detail/index.js
@@ -9,6 +9,7 @@ import ee from 'browser/main/lib/eventEmitter'
import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n'
import debounceRender from 'react-debounce-render'
+import searchFromNotes from 'browser/lib/search'
const OSX = global.process.platform === 'darwin'
@@ -35,11 +36,38 @@ class Detail extends React.Component {
}
render () {
- const { location, data, config } = this.props
+ const { location, data, params, config } = this.props
let note = null
+
if (location.query.key != null) {
const noteKey = location.query.key
- note = data.noteMap.get(noteKey)
+ const allNotes = data.noteMap.map(note => note)
+ const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
+ let displayedNotes = allNotes
+
+ if (location.pathname.match(/\/searched/)) {
+ const searchStr = params.searchword
+ displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
+ : searchFromNotes(allNotes, searchStr)
+ }
+
+ if (location.pathname.match(/\/tags/)) {
+ const listOfTags = params.tagname.split(' ')
+ displayedNotes = data.noteMap.map(note => note).filter(note =>
+ listOfTags.every(tag => note.tags.includes(tag))
+ )
+ }
+
+ if (location.pathname.match(/\/trashed/)) {
+ displayedNotes = trashedNotes
+ } else {
+ displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
+ }
+
+ const noteKeys = displayedNotes.map(note => note.key)
+ if (noteKeys.includes(noteKey)) {
+ note = data.noteMap.get(noteKey)
+ }
}
if (note == null) {
diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js
index 3626130d..eeb16a5f 100644
--- a/browser/main/NoteList/index.js
+++ b/browser/main/NoteList/index.js
@@ -21,9 +21,10 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
+import context from 'browser/lib/context'
const { remote } = require('electron')
-const { Menu, MenuItem, dialog } = remote
+const { dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
function sortByCreatedAt (a, b) {
@@ -491,55 +492,51 @@ class NoteList extends React.Component {
const updateLabel = i18n.__('Update Blog')
const openBlogLabel = i18n.__('Open Blog')
- const menu = new Menu()
+ const templates = []
if (location.pathname.match(/\/trash/)) {
- menu.append(new MenuItem({
+ templates.push({
label: restoreNote,
click: this.restoreNote
- }))
- menu.append(new MenuItem({
+ }, {
label: deleteLabel,
click: this.deleteNote
- }))
+ })
} else {
if (!location.pathname.match(/\/starred/)) {
- menu.append(new MenuItem({
+ templates.push({
label: pinLabel,
click: this.pinToTop
- }))
+ })
}
- menu.append(new MenuItem({
+ templates.push({
label: deleteLabel,
click: this.deleteNote
- }))
- menu.append(new MenuItem({
+ }, {
label: cloneNote,
click: this.cloneNote.bind(this)
- }))
- menu.append(new MenuItem({
+ }, {
label: copyNoteLink,
click: this.copyNoteLink(note)
- }))
+ })
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
- menu.append(new MenuItem({
+ templates.push({
label: updateLabel,
click: this.publishMarkdown.bind(this)
- }))
- menu.append(new MenuItem({
+ }, {
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
- }))
+ })
} else {
- menu.append(new MenuItem({
+ templates.push({
label: publishLabel,
click: this.publishMarkdown.bind(this)
- }))
+ })
}
}
}
- menu.popup()
+ context.popup(templates)
}
updateSelectedNotes (updateFunc, cleanSelection = true) {
diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js
index 93e9157f..d72f0a8f 100644
--- a/browser/main/SideNav/StorageItem.js
+++ b/browser/main/SideNav/StorageItem.js
@@ -11,9 +11,10 @@ import StorageItemChild from 'browser/components/StorageItem'
import _ from 'lodash'
import { SortableElement } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
+import context from 'browser/lib/context'
const { remote } = require('electron')
-const { Menu, dialog } = remote
+const { dialog } = remote
const escapeStringRegexp = require('escape-string-regexp')
const path = require('path')
@@ -21,13 +22,15 @@ class StorageItem extends React.Component {
constructor (props) {
super(props)
+ const { storage } = this.props
+
this.state = {
- isOpen: true
+ isOpen: !!storage.isOpen
}
}
handleHeaderContextMenu (e) {
- const menu = Menu.buildFromTemplate([
+ context.popup([
{
label: i18n.__('Add Folder'),
click: (e) => this.handleAddFolderButtonClick(e)
@@ -40,8 +43,6 @@ class StorageItem extends React.Component {
click: (e) => this.handleUnlinkStorageClick(e)
}
])
-
- menu.popup()
}
handleUnlinkStorageClick (e) {
@@ -68,8 +69,18 @@ class StorageItem extends React.Component {
}
handleToggleButtonClick (e) {
+ const { storage, dispatch } = this.props
+ const isOpen = !this.state.isOpen
+ dataApi.toggleStorage(storage.key, isOpen)
+ .then((storage) => {
+ dispatch({
+ type: 'EXPAND_STORAGE',
+ storage,
+ isOpen
+ })
+ })
this.setState({
- isOpen: !this.state.isOpen
+ isOpen: isOpen
})
}
@@ -94,7 +105,7 @@ class StorageItem extends React.Component {
}
handleFolderButtonContextMenu (e, folder) {
- const menu = Menu.buildFromTemplate([
+ context.popup([
{
label: i18n.__('Rename Folder'),
click: (e) => this.handleRenameFolderClick(e, folder)
@@ -123,8 +134,6 @@ class StorageItem extends React.Component {
click: (e) => this.handleFolderDeleteClick(e, folder)
}
])
-
- menu.popup()
}
handleRenameFolderClick (e, folder) {
diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js
index 67adf700..c4fa417b 100644
--- a/browser/main/SideNav/index.js
+++ b/browser/main/SideNav/index.js
@@ -1,8 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
-const { remote } = require('electron')
-const { Menu } = remote
import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl'
import { openModal } from 'browser/main/lib/modal'
@@ -19,6 +17,7 @@ import ListButton from './ListButton'
import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
+import context from 'browser/lib/context'
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
@@ -254,10 +253,9 @@ class SideNav extends React.Component {
handleFilterButtonContextMenu (event) {
const { data } = this.props
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
- const menu = Menu.buildFromTemplate([
+ context.popup([
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
])
- menu.popup()
}
render () {
diff --git a/browser/main/StatusBar/index.js b/browser/main/StatusBar/index.js
index e5f5ae1a..8b48e3d3 100644
--- a/browser/main/StatusBar/index.js
+++ b/browser/main/StatusBar/index.js
@@ -4,10 +4,11 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './StatusBar.styl'
import ZoomManager from 'browser/main/lib/ZoomManager'
import i18n from 'browser/lib/i18n'
+import context from 'browser/lib/context'
const electron = require('electron')
const { remote, ipcRenderer } = electron
-const { Menu, MenuItem, dialog } = remote
+const { dialog } = remote
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
@@ -26,16 +27,16 @@ class StatusBar extends React.Component {
}
handleZoomButtonClick (e) {
- const menu = new Menu()
+ const templates = []
zoomOptions.forEach((zoom) => {
- menu.append(new MenuItem({
+ templates.push({
label: Math.floor(zoom * 100) + '%',
click: () => this.handleZoomMenuItemClick(zoom)
- }))
+ })
})
- menu.popup(remote.getCurrentWindow())
+ context.popup(templates)
}
handleZoomMenuItemClick (zoomFactor) {
diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js
index ae4d9664..a5687ecb 100644
--- a/browser/main/TopBar/index.js
+++ b/browser/main/TopBar/index.js
@@ -156,8 +156,7 @@ class TopBar extends React.Component {
if (this.state.isSearching) {
el.blur()
} else {
- el.focus()
- el.setSelectionRange(0, el.value.length)
+ el.select()
}
}
diff --git a/browser/main/global.styl b/browser/main/global.styl
index 7025163f..8f3216ef 100644
--- a/browser/main/global.styl
+++ b/browser/main/global.styl
@@ -15,6 +15,12 @@ body
font-weight 200
-webkit-font-smoothing antialiased
+::-webkit-scrollbar
+ width 12px
+
+::-webkit-scrollbar-thumb
+ background-color rgba(0, 0, 0, 0.15)
+
button, input, select, textarea
font-family DEFAULT_FONTS
@@ -85,9 +91,11 @@ modalBackColor = white
absolute top left bottom right
background-color modalBackColor
z-index modalZIndex + 1
-
+
body[data-theme="dark"]
+ ::-webkit-scrollbar-thumb
+ background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-dark-backgroundColor
@@ -128,6 +136,8 @@ body[data-theme="dark"]
z-index modalZIndex + 5
body[data-theme="solarized-dark"]
+ ::-webkit-scrollbar-thumb
+ background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-solarized-dark-backgroundColor
@@ -135,9 +145,10 @@ body[data-theme="solarized-dark"]
color: $ui-solarized-dark-text-color
body[data-theme="monokai"]
+ ::-webkit-scrollbar-thumb
+ background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-monokai-backgroundColor
.sortableItemHelper
color: $ui-monokai-text-color
-
diff --git a/browser/main/lib/dataApi/addStorage.js b/browser/main/lib/dataApi/addStorage.js
index 630c0bd3..bfd6698a 100644
--- a/browser/main/lib/dataApi/addStorage.js
+++ b/browser/main/lib/dataApi/addStorage.js
@@ -37,7 +37,8 @@ function addStorage (input) {
key,
name: input.name,
type: input.type,
- path: input.path
+ path: input.path,
+ isOpen: false
}
return Promise.resolve(newStorage)
@@ -48,7 +49,8 @@ function addStorage (input) {
key: newStorage.key,
type: newStorage.type,
name: newStorage.name,
- path: newStorage.path
+ path: newStorage.path,
+ isOpen: false
})
localStorage.setItem('storages', JSON.stringify(rawStorages))
diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js
index a4c420bd..d1e0ab62 100644
--- a/browser/main/lib/dataApi/attachmentManagement.js
+++ b/browser/main/lib/dataApi/attachmentManagement.js
@@ -10,6 +10,7 @@ import i18n from 'browser/lib/i18n'
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments'
+const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
/**
* @description
@@ -76,14 +77,14 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
/**
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
- * @param renderedHTML HTML of the current note
+ * @param markdownContent of the current note
* @param storagePath Storage path of the current note
* @param noteKey Key of the current note
*/
-function migrateAttachments (renderedHTML, storagePath, noteKey) {
- if (sander.existsSync(path.join(storagePath, 'images'))) {
- const attachments = getAttachmentsInContent(renderedHTML) || []
- if (attachments !== []) {
+function migrateAttachments (markdownContent, storagePath, noteKey) {
+ if (noteKey !== undefined && sander.existsSync(path.join(storagePath, 'images'))) {
+ const attachments = getAttachmentsInMarkdownContent(markdownContent) || []
+ if (attachments.length) {
createAttachmentDestinationFolder(storagePath, noteKey)
}
for (const attachment of attachments) {
@@ -106,7 +107,10 @@ function migrateAttachments (renderedHTML, storagePath, noteKey) {
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/
function fixLocalURLS (renderedHTML, storagePath) {
- return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
+ return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', 'g'), function (match) {
+ var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
+ return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
+ })
}
/**
@@ -186,13 +190,13 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
}
/**
- * @description Returns all attachment paths of the given markdown
- * @param {String} markdownContent content in which the attachment paths should be found
- * @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
- */
-function getAttachmentsInContent (markdownContent) {
- const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
- const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
+* @description Returns all attachment paths of the given markdown
+* @param {String} markdownContent content in which the attachment paths should be found
+* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
+*/
+function getAttachmentsInMarkdownContent (markdownContent) {
+ const preparedInput = markdownContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
+ const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + ')' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + ')' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
return preparedInput.match(regexp)
}
@@ -203,7 +207,7 @@ function getAttachmentsInContent (markdownContent) {
* @returns {String[]} Absolute paths of the referenced attachments
*/
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
- const temp = getAttachmentsInContent(markdownContent) || []
+ const temp = getAttachmentsInMarkdownContent(markdownContent) || []
const result = []
for (const relativePath of temp) {
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
@@ -239,7 +243,8 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
*/
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
if (noteContent) {
- return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
+ const preparedInput = noteContent.replace(new RegExp('[' + PATH_SEPARATORS + ']', 'g'), path.sep)
+ return preparedInput.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
}
return noteContent
}
@@ -277,7 +282,7 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
}
const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
- const attachmentsInNote = getAttachmentsInContent(markdownContent)
+ const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
const attachmentsInNoteOnlyFileNames = []
if (attachmentsInNote) {
for (let i = 0; i < attachmentsInNote.length; i++) {
@@ -348,7 +353,7 @@ function generateFileNotFoundMarkdown () {
*/
function isAttachmentLink (text) {
if (text) {
- return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + escapeStringRegexp(path.sep) + '.*\\).*', 'gi')) != null
+ return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + '[' + PATH_SEPARATORS + ']' + '.*\\).*', 'gi')) != null
}
return false
}
@@ -364,7 +369,7 @@ function isAttachmentLink (text) {
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
if (storageKey != null && noteKey != null && linkText != null) {
const storagePath = findStorage.findStorage(storageKey).path
- const attachments = getAttachmentsInContent(linkText) || []
+ const attachments = getAttachmentsInMarkdownContent(linkText) || []
const replaceInstructions = []
const copies = []
for (const attachment of attachments) {
@@ -373,13 +378,13 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
sander.exists(absPathOfAttachment)
.then((fileExists) => {
if (!fileExists) {
- const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
+ const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
return Promise.resolve()
}
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
.then((fileName) => {
- const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
+ const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + PATH_SEPARATORS + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
replaceInstructions.push({
regexp: replaceLinkRegExp,
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
@@ -408,7 +413,7 @@ module.exports = {
generateAttachmentMarkdown,
handleAttachmentDrop,
handlePastImageEvent,
- getAttachmentsInContent,
+ getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences,
deleteAttachmentFolder,
diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js
index 7c57e016..4e2f0061 100644
--- a/browser/main/lib/dataApi/index.js
+++ b/browser/main/lib/dataApi/index.js
@@ -1,5 +1,6 @@
const dataApi = {
init: require('./init'),
+ toggleStorage: require('./toggleStorage'),
addStorage: require('./addStorage'),
renameStorage: require('./renameStorage'),
removeStorage: require('./removeStorage'),
diff --git a/browser/main/lib/dataApi/resolveStorageData.js b/browser/main/lib/dataApi/resolveStorageData.js
index af040c5d..681a102e 100644
--- a/browser/main/lib/dataApi/resolveStorageData.js
+++ b/browser/main/lib/dataApi/resolveStorageData.js
@@ -8,7 +8,8 @@ function resolveStorageData (storageCache) {
key: storageCache.key,
name: storageCache.name,
type: storageCache.type,
- path: storageCache.path
+ path: storageCache.path,
+ isOpen: storageCache.isOpen
}
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
diff --git a/browser/main/lib/dataApi/toggleStorage.js b/browser/main/lib/dataApi/toggleStorage.js
new file mode 100644
index 00000000..dbb625c3
--- /dev/null
+++ b/browser/main/lib/dataApi/toggleStorage.js
@@ -0,0 +1,28 @@
+const _ = require('lodash')
+const resolveStorageData = require('./resolveStorageData')
+
+/**
+ * @param {String} key
+ * @param {Boolean} isOpen
+ * @return {Object} Storage meta data
+ */
+function toggleStorage (key, isOpen) {
+ let cachedStorageList
+ try {
+ cachedStorageList = JSON.parse(localStorage.getItem('storages'))
+ if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
+ } catch (err) {
+ console.log('error got')
+ console.error(err)
+ return Promise.reject(err)
+ }
+ const targetStorage = _.find(cachedStorageList, {key: key})
+ if (targetStorage == null) return Promise.reject('Storage')
+
+ targetStorage.isOpen = isOpen
+ localStorage.setItem('storages', JSON.stringify(cachedStorageList))
+
+ return resolveStorageData(targetStorage)
+}
+
+module.exports = toggleStorage
diff --git a/browser/main/modals/NewNoteModal.js b/browser/main/modals/NewNoteModal.js
index 185004e7..b748587c 100644
--- a/browser/main/modals/NewNoteModal.js
+++ b/browser/main/modals/NewNoteModal.js
@@ -12,8 +12,7 @@ class NewNoteModal extends React.Component {
constructor (props) {
super(props)
- this.state = {
- }
+ this.state = {}
}
componentDidMount () {
@@ -35,19 +34,20 @@ class NewNoteModal extends React.Component {
title: '',
content: ''
})
- .then((note) => {
+ .then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
+
hashHistory.push({
pathname: location.pathname,
- query: {key: noteHash}
+ query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
- this.props.close()
+ setTimeout(this.props.close, 200)
})
}
@@ -69,13 +69,15 @@ class NewNoteModal extends React.Component {
folder: folder,
title: '',
description: '',
- snippets: [{
- name: '',
- mode: 'text',
- content: ''
- }]
+ snippets: [
+ {
+ name: '',
+ mode: 'text',
+ content: ''
+ }
+ ]
})
- .then((note) => {
+ .then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
@@ -83,11 +85,11 @@ class NewNoteModal extends React.Component {
})
hashHistory.push({
pathname: location.pathname,
- query: {key: noteHash}
+ query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
- this.props.close()
+ setTimeout(this.props.close, 200)
})
}
@@ -106,49 +108,65 @@ class NewNoteModal extends React.Component {
render () {
return (
- this.handleKeyDown(e)}
+ onKeyDown={e => this.handleKeyDown(e)}
>
-
this.handleCloseButtonClick(e)} />
+ this.handleCloseButtonClick(e)}
+ />
-
- {i18n.__('Tab to switch format')}
+
+ {i18n.__('Tab to switch format')}
+
)
}
}
-NewNoteModal.propTypes = {
-}
+NewNoteModal.propTypes = {}
export default CSSModules(NewNoteModal, styles)
diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js
index f0e93dec..4ce5dc34 100644
--- a/browser/main/modals/PreferencesModal/SnippetEditor.js
+++ b/browser/main/modals/PreferencesModal/SnippetEditor.js
@@ -27,7 +27,12 @@ class SnippetEditor extends React.Component {
dragDrop: false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
- autoCloseBrackets: true,
+ autoCloseBrackets: {
+ pairs: '()[]{}\'\'""$$**``',
+ triples: '```"""\'\'\'',
+ explode: '[]{}``$$',
+ override: true
+ },
mode: 'null'
})
this.cm.setSize('100%', '100%')
diff --git a/browser/main/modals/PreferencesModal/SnippetList.js b/browser/main/modals/PreferencesModal/SnippetList.js
index 3cf28cf6..3e892f97 100644
--- a/browser/main/modals/PreferencesModal/SnippetList.js
+++ b/browser/main/modals/PreferencesModal/SnippetList.js
@@ -4,8 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi'
import i18n from 'browser/lib/i18n'
import eventEmitter from 'browser/main/lib/eventEmitter'
-const { remote } = require('electron')
-const { Menu, MenuItem } = remote
+import context from 'browser/lib/context'
class SnippetList extends React.Component {
constructor (props) {
@@ -21,18 +20,17 @@ class SnippetList extends React.Component {
}
reloadSnippetList () {
- dataApi.fetchSnippet().then(snippets => this.setState({snippets}))
+ dataApi.fetchSnippet().then(snippets => {
+ this.setState({snippets})
+ this.props.onSnippetSelect(snippets[0])
+ })
}
handleSnippetContextMenu (snippet) {
- const menu = new Menu()
- menu.append(new MenuItem({
+ context.popup([{
label: i18n.__('Delete snippet'),
- click: () => {
- this.deleteSnippet(snippet)
- }
- }))
- menu.popup()
+ click: () => this.deleteSnippet(snippet)
+ }])
}
deleteSnippet (snippet) {
@@ -43,7 +41,7 @@ class SnippetList extends React.Component {
}
handleSnippetClick (snippet) {
- this.props.onSnippetClick(snippet)
+ this.props.onSnippetSelect(snippet)
}
createSnippet () {
@@ -55,6 +53,16 @@ class SnippetList extends React.Component {
}).catch(err => { throw err })
}
+ defineSnippetStyleName (snippet) {
+ const { currentSnippet } = this.props
+ if (currentSnippet == null) return
+ if (currentSnippet.id === snippet.id) {
+ return 'snippet-item-selected'
+ } else {
+ return 'snippet-item'
+ }
+ }
+
render () {
const { snippets } = this.state
return (
@@ -70,7 +78,7 @@ class SnippetList extends React.Component {
{
snippets.map((snippet) => (
this.handleSnippetContextMenu(snippet)}
onClick={() => this.handleSnippetClick(snippet)}>
diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js
index 67e9ace6..e35ecd69 100644
--- a/browser/main/modals/PreferencesModal/SnippetTab.js
+++ b/browser/main/modals/PreferencesModal/SnippetTab.js
@@ -25,7 +25,7 @@ class SnippetTab extends React.Component {
}, 500)
}
- handleSnippetClick (snippet) {
+ handleSnippetSelect (snippet) {
const { currentSnippet } = this.state
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
@@ -66,8 +66,9 @@ class SnippetTab extends React.Component {
{i18n.__('Snippets')}
+ onSnippetSelect={this.handleSnippetSelect.bind(this)}
+ onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
+ currentSnippet={currentSnippet} />
{i18n.__('Snippet name')}
diff --git a/browser/main/modals/PreferencesModal/SnippetTab.styl b/browser/main/modals/PreferencesModal/SnippetTab.styl
index 118c56ed..02307b64 100644
--- a/browser/main/modals/PreferencesModal/SnippetTab.styl
+++ b/browser/main/modals/PreferencesModal/SnippetTab.styl
@@ -122,6 +122,10 @@
&:hover
background darken(#f5f5f5, 5)
+ .snippet-item-selected
+ @extend .snippet-list .snippet-item
+ background darken(#f5f5f5, 5)
+
.snippet-detail
width 70%
height calc(100% - 200px)
@@ -142,6 +146,8 @@ body[data-theme="default"], body[data-theme="white"]
background $ui-borderColor
&:hover
background darken($ui-backgroundColor, 5)
+ .snippet-item-selected
+ background darken($ui-backgroundColor, 5)
body[data-theme="dark"]
.snippets
@@ -152,8 +158,12 @@ body[data-theme="dark"]
background $ui-dark-borderColor
&:hover
background darken($ui-dark-backgroundColor, 5)
+ .snippet-item-selected
+ background darken($ui-dark-backgroundColor, 5)
.snippet-detail
color white
+ .group-control-button
+ colorDarkPrimaryButton()
body[data-theme="solarized-dark"]
.snippets
@@ -164,8 +174,12 @@ body[data-theme="solarized-dark"]
background $ui-solarized-dark-borderColor
&:hover
background darken($ui-solarized-dark-backgroundColor, 5)
+ .snippet-item-selected
+ background darken($ui-solarized-dark-backgroundColor, 5)
.snippet-detail
color white
+ .group-control-button
+ colorSolarizedDarkPrimaryButton()
body[data-theme="monokai"]
.snippets
@@ -176,5 +190,9 @@ body[data-theme="monokai"]
background $ui-monokai-borderColor
&:hover
background darken($ui-monokai-backgroundColor, 5)
+ .snippet-item-selected
+ background darken($ui-monokai-backgroundColor, 5)
.snippet-detail
color white
+ .group-control-button
+ colorMonokaiPrimaryButton()
diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js
index ce149f65..aa3568e7 100644
--- a/browser/main/modals/PreferencesModal/UiTab.js
+++ b/browser/main/modals/PreferencesModal/UiTab.js
@@ -11,6 +11,7 @@ import 'codemirror-mode-elixir'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
import { getLanguages } from 'browser/lib/Languages'
+import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const OSX = global.process.platform === 'darwin'
@@ -164,7 +165,7 @@ class UiTab extends React.Component {
const { config, codemirrorTheme } = this.state
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
- const customCSS = config.preview.customCSS
+ const fontFamily = normalizeEditorFontFamily(config.editor.fontFamily)
return (
@@ -262,8 +263,16 @@ class UiTab extends React.Component {
})
}
-
-
(this.codeMirrorInstance = e)} value={codemirrorSampleCode} options={{ lineNumbers: true, readOnly: true, mode: 'javascript', theme: codemirrorTheme }} />
+
+ (this.codeMirrorInstance = e)}
+ value={codemirrorSampleCode}
+ options={{
+ lineNumbers: true,
+ readOnly: true,
+ mode: 'javascript',
+ theme: codemirrorTheme
+ }} />
@@ -596,7 +605,19 @@ class UiTab extends React.Component {
type='checkbox'
/>
{i18n.__('Allow custom CSS for preview')}
-
this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} />
+
+ this.handleUIChange(e)}
+ ref={e => (this.customCSSCM = e)}
+ value={config.preview.customCSS}
+ options={{
+ lineNumbers: true,
+ mode: 'css',
+ theme: codemirrorTheme
+ }} />
+
diff --git a/browser/main/store.js b/browser/main/store.js
index 7ea6decb..a1b6b791 100644
--- a/browser/main/store.js
+++ b/browser/main/store.js
@@ -360,6 +360,12 @@ function data (state = defaultDataMap(), action) {
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
+ case 'EXPAND_STORAGE':
+ state = Object.assign({}, state)
+ state.storageMap = new Map(state.storageMap)
+ action.storage.isOpen = action.isOpen
+ state.storageMap.set(action.storage.key, action.storage)
+ return state
}
return state
}
diff --git a/docs/jp/build.md b/docs/jp/build.md
index 4d0fab33..b8d2e414 100644
--- a/docs/jp/build.md
+++ b/docs/jp/build.md
@@ -1,9 +1,16 @@
# Build
+## 環境
+* npm: 4.x
+* node: 7.x
+
+`npm v5.x` だと `$ grunt pre-build` が失敗するので、 `npm v4.x` を使用してください。
+
## 開発
Webpack HRMを使います。
-次の命令から私達がしておいた設定を使うことができます。
+Boostnoteの最上位ディレクトリにて以下のコマンドを実行して、
+デフォルトの設定の開発環境を起動させます。
依存するパッケージをインストールします。
@@ -27,15 +34,15 @@ $ yarn run dev-start
> ### 注意
> 時々、直接リフレッシュをする必要があります。
-> 1. コンポネントのコンストラクター関数を編集する場合
-> 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポネントごとに書きなおされまが、この作業はコンストラクターで行われます。)
+> 1. コンポーネントのコンストラクタ関数を編集する場合
+> 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポーネントごとに書きなおされますが、この作業はコンストラクタで行われます。)
## 配布
Gruntを使います。
-実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeの仮定が含まれるので、使っては行けないです。
+実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeを実行するタスクが含まれるので、使用しないでください。
-それで、実行ファイルを作るスクリプトを用意しておきました。
+代わりに、実行ファイルを作るスクリプトを用意しておきました。
このビルドはnpm v5.3.0では動かないのでv5.2.0で動かす必要があります。
diff --git a/lib/main-menu.js b/lib/main-menu.js
index 9345bd67..cda964c5 100644
--- a/lib/main-menu.js
+++ b/lib/main-menu.js
@@ -136,6 +136,15 @@ const file = {
{
type: 'separator'
},
+ {
+ label: 'Format Table',
+ click () {
+ mainWindow.webContents.send('code:format-table')
+ }
+ },
+ {
+ type: 'separator'
+ },
{
label: 'Print',
accelerator: 'CommandOrControl+P',
@@ -209,6 +218,16 @@ const edit = {
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
+ },
+ {
+ type: 'separator'
+ },
+ {
+ label: 'Add Tag',
+ accelerator: 'CommandOrControl+Shift+T',
+ click () {
+ mainWindow.webContents.send('editor:add-tag')
+ }
}
]
}
@@ -235,14 +254,14 @@ const view = {
},
{
label: 'Next Note',
- accelerator: 'Control+J',
+ accelerator: 'CommandOrControl+]',
click () {
mainWindow.webContents.send('list:next')
}
},
{
label: 'Previous Note',
- accelerator: 'Control+K',
+ accelerator: 'CommandOrControl+[',
click () {
mainWindow.webContents.send('list:prior')
}
@@ -267,6 +286,19 @@ const view = {
mainWindow.setFullScreen(!mainWindow.isFullScreen())
}
},
+ {
+ type: 'separator'
+ },
+ {
+ label: 'Toggle Side Bar',
+ accelerator: 'CommandOrControl+B',
+ click () {
+ mainWindow.webContents.send('editor:fullscreen')
+ }
+ },
+ {
+ type: 'separator'
+ },
{
role: 'zoomin',
accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+='
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 8150228b..00000000
--- a/package-lock.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "name": "boost",
- "version": "0.10.0",
- "lockfileVersion": 1,
- "requires": true,
- "dependencies": {
- "debug": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "i18n-2": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/i18n-2/-/i18n-2-0.7.2.tgz",
- "integrity": "sha512-Rdh6vfpNhL7q61cNf27x7QGULTi1TcGLVdFb5OJ6dOiJo+EkOTqEg0+3xgyeEMgYhopUBsh2IiSkFkjM+EhEmA==",
- "requires": {
- "debug": "3.1.0",
- "sprintf": "0.1.5"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- },
- "sprintf": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/sprintf/-/sprintf-0.1.5.tgz",
- "integrity": "sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8="
- }
- }
-}
diff --git a/package.json b/package.json
index 28f1239b..fbbb025f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "boost",
"productName": "Boostnote",
- "version": "0.11.6",
+ "version": "0.11.8",
"main": "index.js",
"description": "Boostnote",
"license": "GPL-3.0",
@@ -51,9 +51,11 @@
"dependencies": {
"@rokt33r/markdown-it-math": "^4.0.1",
"@rokt33r/season": "^5.3.0",
+ "@susisu/mte-kernel": "^2.0.0",
"aws-sdk": "^2.48.0",
"aws-sdk-mobile-analytics": "^0.9.2",
- "codemirror": "^5.37.0",
+ "chart.js": "^2.7.2",
+ "codemirror": "^5.39.0",
"codemirror-mode-elixir": "^1.1.1",
"electron-config": "^0.2.1",
"electron-gh-releases": "^2.0.2",
@@ -71,7 +73,7 @@
"lodash": "^4.11.1",
"lodash-move": "^1.1.1",
"markdown-it": "^6.0.1",
- "markdown-it-admonition": "https://github.com/johannbre/markdown-it-admonition.git",
+ "markdown-it-admonition": "^1.0.4",
"markdown-it-emoji": "^1.1.1",
"markdown-it-footnote": "^3.0.0",
"markdown-it-imsize": "^2.0.1",
@@ -81,6 +83,7 @@
"markdown-it-plantuml": "^1.1.0",
"markdown-it-smartarrows": "^1.0.1",
"mdurl": "^1.0.1",
+ "mermaid": "^8.0.0-rc.8",
"moment": "^2.10.3",
"mousetrap": "^1.6.1",
"mousetrap-global-bind": "^1.1.0",
diff --git a/readme.md b/readme.md
index 40866e46..c6674902 100644
--- a/readme.md
+++ b/readme.md
@@ -1,4 +1,4 @@
-:mega: We've launched [Boostnote Bounty Program](http://bit.ly/2I5Tpik).
+:mega: The Boostnote team launches [IssueHunt](https://issuehunt.io/) for sustainable open-source ecosystem.

@@ -19,13 +19,15 @@ Thank you to all the people who already contributed to Boostnote!

## Supporting Boostnote
-Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/BoostIO/Boostnote/blob/master/Backers.md). If you'd like to join them, please consider:
-- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
+Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers.
+
+Any issues on Boostnote can be funded by anyone and that money will be distributed to contributors and maintainers. If you'd like to join them, please consider:
+- [Become a backer on IssueHunt](https://issuehunt.io/repos/53266139).
## Community
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
- [Twitter](https://twitter.com/boostnoteapp)
-- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzcwNDU3NDU3ODI0LTU1ZDgwZDNiZTNmN2RhOTY4OTM5ODY0ODUzMTRiNmQ0ZDMzZDRiYzg2YmQ5ZDYzZTQxYjMxYzBlNTM4NjcyYjM)
+- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzkxOTk4ODkyNzc0LThkNmMzY2VlZjVhYTNiYjE5YjQyZGVjNTJlYTY1OGMyZTFjNGU5YTUyYjUzOWZhYTU4OTVlNDYyNDFjYWMzNDM)
- [Blog](https://boostlog.io/tags/boostnote)
- [Reddit](https://www.reddit.com/r/Boostnote/)
diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js
index 4ce031a7..a4cc8082 100644
--- a/tests/dataApi/attachmentManagement.test.js
+++ b/tests/dataApi/attachmentManagement.test.js
@@ -169,6 +169,43 @@ it('should replace the all ":storage" path with the actual storage path', functi
expect(actual).toEqual(expectedOutput)
})
+it('should replace the ":storage" path with the actual storage path when they have different path separators', function () {
+ const storageFolder = systemUnderTest.DESTINATION_FOLDER
+ const testInput =
+ '\n' +
+ ' \n' +
+ ' //header\n' +
+ ' \n' +
+ ' \n' +
+ '
Headline
\n' +
+ '
\n' +
+ '
\n' +
+ '
\n' +
+ '
\n' +
+ ' dummyPDF.pdf\n' +
+ '
\n' +
+ ' \n' +
+ ''
+ const storagePath = '<
>'
+ const expectedOutput =
+ '\n' +
+ ' \n' +
+ ' //header\n' +
+ ' \n' +
+ ' \n' +
+ ' Headline
\n' +
+ ' \n' +
+ '
\n' +
+ '
\n' +
+ ' \n' +
+ ' dummyPDF.pdf\n' +
+ '
\n' +
+ ' \n' +
+ ''
+ const actual = systemUnderTest.fixLocalURLS(testInput, storagePath)
+ expect(actual).toEqual(expectedOutput)
+})
+
it('should test that generateAttachmentMarkdown works correct both with previews and without', function () {
const fileName = 'fileName'
const path = 'path'
@@ -180,27 +217,35 @@ it('should test that generateAttachmentMarkdown works correct both with previews
expect(actual).toEqual(expected)
})
-it('should test that getAttachmentsInContent finds all attachments', function () {
- const testInput =
- '\n' +
- ' \n' +
- ' //header\n' +
- ' \n' +
- ' \n' +
- ' Headline
\n' +
- ' \n' +
- '
\n' +
- '
\n' +
- ' \n' +
- ' dummyPDF.pdf\n' +
- '
\n' +
- ' \n' +
- '
\n' +
- '
\n' +
- ' \n' +
- ''
- const actual = systemUnderTest.getAttachmentsInContent(testInput)
- const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp.png', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx.pdf', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
+it('should test that migrateAttachments work when they have different path separators', function () {
+ sander.existsSync = jest.fn(() => true)
+ const dummyStoragePath = 'dummyStoragePath'
+ const imagesPath = path.join(dummyStoragePath, 'images')
+ const attachmentsPath = path.join(dummyStoragePath, 'attachments')
+ const noteKey = 'noteKey'
+ const testInput = '"# Test\n' +
+ '\n' +
+ '\n' +
+ '"'
+
+ systemUnderTest.migrateAttachments(testInput, dummyStoragePath, noteKey)
+
+ expect(sander.existsSync.mock.calls[0][0]).toBe(imagesPath)
+ expect(sander.existsSync.mock.calls[1][0]).toBe(path.join(imagesPath, '0.3b88d0dc.png'))
+ expect(sander.existsSync.mock.calls[2][0]).toBe(path.join(attachmentsPath, '0.3b88d0dc.png'))
+ expect(sander.existsSync.mock.calls[3][0]).toBe(path.join(imagesPath, '0.2cb8875c.pdf'))
+ expect(sander.existsSync.mock.calls[4][0]).toBe(path.join(attachmentsPath, '0.2cb8875c.pdf'))
+})
+
+it('should test that getAttachmentsInMarkdownContent finds all attachments when they have different path separators', function () {
+ const testInput = '"# Test\n' +
+ '\n' +
+ '\n' +
+ '\n' +
+ '"'
+
+ const actual = systemUnderTest.getAttachmentsInMarkdownContent(testInput)
+ const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.3b88d0dc.png', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '2cb8875c.pdf', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'bbf49b02.jpg']
expect(actual).toEqual(expect.arrayContaining(expected))
})
@@ -274,6 +319,21 @@ it('should remove the all ":storage" and noteKey references', function () {
expect(actual).toEqual(expectedOutput)
})
+it('should make sure that "removeStorageAndNoteReferences" works with markdown content as well', function () {
+ const noteKey = 'noteKey'
+ const testInput =
+ 'Test input' +
+ ' \n' +
+ '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'pdf.pdf](pdf})'
+
+ const expectedOutput =
+ 'Test input' +
+ ' \n' +
+ '[' + systemUnderTest.DESTINATION_FOLDER + path.sep + 'pdf.pdf](pdf})'
+ const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey)
+ expect(actual).toEqual(expectedOutput)
+})
+
it('should delete the correct attachment folder if a note is deleted', function () {
const dummyStorage = {path: 'dummyStoragePath'}
const storageKey = 'storageKey'
diff --git a/tests/dataApi/toggleStorage-test.js b/tests/dataApi/toggleStorage-test.js
new file mode 100644
index 00000000..5169a4f4
--- /dev/null
+++ b/tests/dataApi/toggleStorage-test.js
@@ -0,0 +1,38 @@
+const test = require('ava')
+const toggleStorage = require('browser/main/lib/dataApi/toggleStorage')
+
+global.document = require('jsdom').jsdom('')
+global.window = document.defaultView
+global.navigator = window.navigator
+
+const Storage = require('dom-storage')
+const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
+const path = require('path')
+const _ = require('lodash')
+const TestDummy = require('../fixtures/TestDummy')
+const sander = require('sander')
+const os = require('os')
+
+const storagePath = path.join(os.tmpdir(), 'test/toggle-storage')
+
+test.beforeEach((t) => {
+ t.context.storage = TestDummy.dummyStorage(storagePath)
+ localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
+})
+
+test.serial('Toggle a storage location', (t) => {
+ const storageKey = t.context.storage.cache.key
+ return Promise.resolve()
+ .then(function doTest () {
+ return toggleStorage(storageKey, true)
+ })
+ .then(function assert (data) {
+ const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
+ t.true(_.find(cachedStorageList, {key: storageKey}).isOpen === true)
+ })
+})
+
+test.after(function after () {
+ localStorage.clear()
+ sander.rimrafSync(storagePath)
+})
diff --git a/tests/lib/escapeHtmlCharacters-test.js b/tests/lib/escapeHtmlCharacters-test.js
new file mode 100644
index 00000000..672ef917
--- /dev/null
+++ b/tests/lib/escapeHtmlCharacters-test.js
@@ -0,0 +1,73 @@
+const { escapeHtmlCharacters } = require('browser/lib/utils')
+const test = require('ava')
+
+test('escapeHtmlCharacters should return the original string if nothing needed to escape', t => {
+ const input = 'Nothing to be escaped'
+ const expected = 'Nothing to be escaped'
+ const actual = escapeHtmlCharacters(input)
+ t.is(actual, expected)
+})
+
+test('escapeHtmlCharacters should skip code block if that option is enabled', t => {
+ const input = `
+`
+ const expected = `
+<escapeMe>`
+ const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
+ t.is(actual, expected)
+})
+
+test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', t => {
+ const input = '4 spaces &'
+ const expected = '4 spaces &'
+ const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
+ t.is(actual, expected)
+})
+
+test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', t => {
+ const input = `
+`
+ const expected = ` <no escape>
+<escapeMe>`
+ const actual = escapeHtmlCharacters(input)
+ t.is(actual, expected)
+})
+
+test("escapeHtmlCharacters should NOT escape & character if it's a part of an escaped character", t => {
+ const input = 'Do not escape & or " but do escape &'
+ const expected = 'Do not escape & or " but do escape &'
+ const actual = escapeHtmlCharacters(input)
+ t.is(actual, expected)
+})
+
+test('escapeHtmlCharacters should skip char if in code block', t => {
+ const input = `
+\`\`\`
+
+\`\`\`
+dasdasd
+dasdasdasd
+\`\`\`
+
+\`\`\`
+`
+ const expected = `
+\`\`\`
+
+\`\`\`
+das<das>dasd
+dasdasdasd
+\`\`\`
+
+\`\`\`
+`
+ const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
+ t.is(actual, expected)
+})
+
+test('escapeHtmlCharacters should return the correct result', t => {
+ const input = '& < > " \''
+ const expected = '& < > " ''
+ const actual = escapeHtmlCharacters(input)
+ t.is(actual, expected)
+})
diff --git a/tests/lib/normalize-editor-font-family-test.js b/tests/lib/normalize-editor-font-family-test.js
new file mode 100644
index 00000000..aacd03ac
--- /dev/null
+++ b/tests/lib/normalize-editor-font-family-test.js
@@ -0,0 +1,16 @@
+/**
+ * @fileoverview Unit test for browser/lib/normalizeEditorFontFamily
+ */
+import test from 'ava'
+import normalizeEditorFontFamily from '../../browser/lib/normalizeEditorFontFamily'
+import consts from '../../browser/lib/consts'
+const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
+
+test('normalizeEditorFontFamily() should return default font family (string[])', t => {
+ t.is(normalizeEditorFontFamily(), defaultEditorFontFamily.join(', '))
+})
+
+test('normalizeEditorFontFamily(["hoge", "huga"]) should return default font family connected with arg.', t => {
+ const arg = 'font1, font2'
+ t.is(normalizeEditorFontFamily(arg), `${arg}, ${defaultEditorFontFamily.join(', ')}`)
+})
diff --git a/yarn.lock b/yarn.lock
index 7bf19d2b..f77252ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -79,6 +79,12 @@
fs-plus "2.x"
optimist "~0.4.0"
+"@susisu/mte-kernel@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@susisu/mte-kernel/-/mte-kernel-2.0.0.tgz#da3354d6a07ea27f36ec91d9fccf6bfa9010d00e"
+ dependencies:
+ meaw "^2.0.0"
+
"@types/node@^8.0.24":
version "8.10.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3"
@@ -1574,6 +1580,26 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+chart.js@^2.7.2:
+ version "2.7.2"
+ resolved "http://registry.npm.taobao.org/chart.js/download/chart.js-2.7.2.tgz#3c9fde4dc5b95608211bdefeda7e5d33dffa5714"
+ dependencies:
+ chartjs-color "^2.1.0"
+ moment "^2.10.2"
+
+chartjs-color-string@^0.5.0:
+ version "0.5.0"
+ resolved "http://registry.npm.taobao.org/chartjs-color-string/download/chartjs-color-string-0.5.0.tgz#8d3752d8581d86687c35bfe2cb80ac5213ceb8c1"
+ dependencies:
+ color-name "^1.0.0"
+
+chartjs-color@^2.1.0:
+ version "2.2.0"
+ resolved "http://registry.npm.taobao.org/chartjs-color/download/chartjs-color-2.2.0.tgz#84a2fb755787ed85c39dd6dd8c7b1d88429baeae"
+ dependencies:
+ chartjs-color-string "^0.5.0"
+ color-convert "^0.5.3"
+
chokidar@^1.0.0, chokidar@^1.4.2:
version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
@@ -1727,10 +1753,14 @@ codemirror-mode-elixir@^1.1.1:
dependencies:
codemirror "^5.20.2"
-codemirror@^5.18.2, codemirror@^5.20.2, codemirror@^5.37.0:
+codemirror@^5.18.2, codemirror@^5.20.2:
version "5.38.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.38.0.tgz#26a9551446e51dbdde36aabe60f72469724fd332"
+codemirror@^5.39.0:
+ version "5.39.0"
+ resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.39.0.tgz#4654f7d2f7e525e04a62e72d9482348ccb37dce5"
+
coffee-script@^1.10.0:
version "1.12.7"
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
@@ -1746,6 +1776,10 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
+color-convert@^0.5.3:
+ version "0.5.3"
+ resolved "http://registry.npm.taobao.org/color-convert/download/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd"
+
color-convert@^1.3.0, color-convert@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
@@ -1802,6 +1836,10 @@ combined-stream@~0.0.4, combined-stream@~0.0.5:
dependencies:
delayed-stream "0.0.5"
+commander@2:
+ version "2.16.0"
+ resolved "http://registry.npm.taobao.org/commander/download/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
+
commander@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
@@ -2142,12 +2180,250 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"
+d3-array@1, d3-array@1.2.1, d3-array@^1.2.0:
+ version "1.2.1"
+ resolved "http://registry.npm.taobao.org/d3-array/download/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc"
+
+d3-axis@1.0.8:
+ version "1.0.8"
+ resolved "http://registry.npm.taobao.org/d3-axis/download/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa"
+
+d3-brush@1.0.4:
+ version "1.0.4"
+ resolved "http://registry.npm.taobao.org/d3-brush/download/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4"
+ dependencies:
+ d3-dispatch "1"
+ d3-drag "1"
+ d3-interpolate "1"
+ d3-selection "1"
+ d3-transition "1"
+
+d3-chord@1.0.4:
+ version "1.0.4"
+ resolved "http://registry.npm.taobao.org/d3-chord/download/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c"
+ dependencies:
+ d3-array "1"
+ d3-path "1"
+
+d3-collection@1, d3-collection@1.0.4:
+ version "1.0.4"
+ resolved "http://registry.npm.taobao.org/d3-collection/download/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2"
+
+d3-color@1:
+ version "1.2.0"
+ resolved "http://registry.npm.taobao.org/d3-color/download/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a"
+
+d3-color@1.0.3:
+ version "1.0.3"
+ resolved "http://registry.npm.taobao.org/d3-color/download/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"
+
+d3-dispatch@1, d3-dispatch@1.0.3:
+ version "1.0.3"
+ resolved "http://registry.npm.taobao.org/d3-dispatch/download/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8"
+
+d3-drag@1, d3-drag@1.2.1:
+ version "1.2.1"
+ resolved "http://registry.npm.taobao.org/d3-drag/download/d3-drag-1.2.1.tgz#df8dd4c502fb490fc7462046a8ad98a5c479282d"
+ dependencies:
+ d3-dispatch "1"
+ d3-selection "1"
+
+d3-dsv@1, d3-dsv@1.0.8:
+ version "1.0.8"
+ resolved "http://registry.npm.taobao.org/d3-dsv/download/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae"
+ dependencies:
+ commander "2"
+ iconv-lite "0.4"
+ rw "1"
+
+d3-ease@1, d3-ease@1.0.3:
+ version "1.0.3"
+ resolved "http://registry.npm.taobao.org/d3-ease/download/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e"
+
+d3-force@1.1.0:
+ version "1.1.0"
+ resolved "http://registry.npm.taobao.org/d3-force/download/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3"
+ dependencies:
+ d3-collection "1"
+ d3-dispatch "1"
+ d3-quadtree "1"
+ d3-timer "1"
+
+d3-format@1:
+ version "1.3.0"
+ resolved "http://registry.npm.taobao.org/d3-format/download/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11"
+
+d3-format@1.2.2:
+ version "1.2.2"
+ resolved "http://registry.npm.taobao.org/d3-format/download/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a"
+
+d3-geo@1.9.1:
+ version "1.9.1"
+ resolved "http://registry.npm.taobao.org/d3-geo/download/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"
+ dependencies:
+ d3-array "1"
+
+d3-hierarchy@1.1.5:
+ version "1.1.5"
+ resolved "http://registry.npm.taobao.org/d3-hierarchy/download/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26"
+
+d3-interpolate@1:
+ version "1.2.0"
+ resolved "http://registry.npm.taobao.org/d3-interpolate/download/d3-interpolate-1.2.0.tgz#40d81bd8e959ff021c5ea7545bc79b8d22331c41"
+ dependencies:
+ d3-color "1"
+
+d3-interpolate@1.1.6:
+ version "1.1.6"
+ resolved "http://registry.npm.taobao.org/d3-interpolate/download/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6"
+ dependencies:
+ d3-color "1"
+
+d3-path@1, d3-path@1.0.5:
+ version "1.0.5"
+ resolved "http://registry.npm.taobao.org/d3-path/download/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764"
+
+d3-polygon@1.0.3:
+ version "1.0.3"
+ resolved "http://registry.npm.taobao.org/d3-polygon/download/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62"
+
+d3-quadtree@1, d3-quadtree@1.0.3:
+ version "1.0.3"
+ resolved "http://registry.npm.taobao.org/d3-quadtree/download/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438"
+
+d3-queue@3.0.7:
+ version "3.0.7"
+ resolved "http://registry.npm.taobao.org/d3-queue/download/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618"
+
+d3-random@1.1.0:
+ version "1.1.0"
+ resolved "http://registry.npm.taobao.org/d3-random/download/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3"
+
+d3-request@1.0.6:
+ version "1.0.6"
+ resolved "http://registry.npm.taobao.org/d3-request/download/d3-request-1.0.6.tgz#a1044a9ef4ec28c824171c9379fae6d79474b19f"
+ dependencies:
+ d3-collection "1"
+ d3-dispatch "1"
+ d3-dsv "1"
+ xmlhttprequest "1"
+
+d3-scale@1.0.7:
+ version "1.0.7"
+ resolved "http://registry.npm.taobao.org/d3-scale/download/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d"
+ dependencies:
+ d3-array "^1.2.0"
+ d3-collection "1"
+ d3-color "1"
+ d3-format "1"
+ d3-interpolate "1"
+ d3-time "1"
+ d3-time-format "2"
+
+d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0:
+ version "1.3.0"
+ resolved "http://registry.npm.taobao.org/d3-selection/download/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d"
+
+d3-shape@1.2.0:
+ version "1.2.0"
+ resolved "http://registry.npm.taobao.org/d3-shape/download/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
+ dependencies:
+ d3-path "1"
+
+d3-time-format@2, d3-time-format@2.1.1:
+ version "2.1.1"
+ resolved "http://registry.npm.taobao.org/d3-time-format/download/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31"
+ dependencies:
+ d3-time "1"
+
+d3-time@1, d3-time@1.0.8:
+ version "1.0.8"
+ resolved "http://registry.npm.taobao.org/d3-time/download/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84"
+
+d3-timer@1, d3-timer@1.0.7:
+ version "1.0.7"
+ resolved "http://registry.npm.taobao.org/d3-timer/download/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531"
+
+d3-transition@1, d3-transition@1.1.1:
+ version "1.1.1"
+ resolved "http://registry.npm.taobao.org/d3-transition/download/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039"
+ dependencies:
+ d3-color "1"
+ d3-dispatch "1"
+ d3-ease "1"
+ d3-interpolate "1"
+ d3-selection "^1.1.0"
+ d3-timer "1"
+
+d3-voronoi@1.1.2:
+ version "1.1.2"
+ resolved "http://registry.npm.taobao.org/d3-voronoi/download/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c"
+
+d3-zoom@1.7.1:
+ version "1.7.1"
+ resolved "http://registry.npm.taobao.org/d3-zoom/download/d3-zoom-1.7.1.tgz#02f43b3c3e2db54f364582d7e4a236ccc5506b63"
+ dependencies:
+ d3-dispatch "1"
+ d3-drag "1"
+ d3-interpolate "1"
+ d3-selection "1"
+ d3-transition "1"
+
+d3@^4.13.0:
+ version "4.13.0"
+ resolved "http://registry.npm.taobao.org/d3/download/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d"
+ dependencies:
+ d3-array "1.2.1"
+ d3-axis "1.0.8"
+ d3-brush "1.0.4"
+ d3-chord "1.0.4"
+ d3-collection "1.0.4"
+ d3-color "1.0.3"
+ d3-dispatch "1.0.3"
+ d3-drag "1.2.1"
+ d3-dsv "1.0.8"
+ d3-ease "1.0.3"
+ d3-force "1.1.0"
+ d3-format "1.2.2"
+ d3-geo "1.9.1"
+ d3-hierarchy "1.1.5"
+ d3-interpolate "1.1.6"
+ d3-path "1.0.5"
+ d3-polygon "1.0.3"
+ d3-quadtree "1.0.3"
+ d3-queue "3.0.7"
+ d3-random "1.1.0"
+ d3-request "1.0.6"
+ d3-scale "1.0.7"
+ d3-selection "1.3.0"
+ d3-shape "1.2.0"
+ d3-time "1.0.8"
+ d3-time-format "2.1.1"
+ d3-timer "1.0.7"
+ d3-transition "1.1.1"
+ d3-voronoi "1.1.2"
+ d3-zoom "1.7.1"
+
d@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
dependencies:
es5-ext "^0.10.9"
+dagre-d3-renderer@^0.5.8:
+ version "0.5.8"
+ resolved "http://registry.npm.taobao.org/dagre-d3-renderer/download/dagre-d3-renderer-0.5.8.tgz#aa071bb71d3c4d67426925906f3f6ddead49c1a3"
+ dependencies:
+ dagre-layout "^0.8.8"
+ lodash "^4.17.5"
+
+dagre-layout@^0.8.8:
+ version "0.8.8"
+ resolved "http://registry.npm.taobao.org/dagre-layout/download/dagre-layout-0.8.8.tgz#9b6792f24229f402441c14162c1049e3f261f6d9"
+ dependencies:
+ graphlibrary "^2.2.0"
+ lodash "^4.17.5"
+
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -3681,6 +3957,12 @@ graceful-fs@~1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364"
+graphlibrary@^2.2.0:
+ version "2.2.0"
+ resolved "http://registry.npm.taobao.org/graphlibrary/download/graphlibrary-2.2.0.tgz#017a14899775228dec4497a39babfdd6bf56eac6"
+ dependencies:
+ lodash "^4.17.5"
+
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@@ -3870,6 +4152,10 @@ hawk@~2.3.0:
hoek "2.x.x"
sntp "1.x.x"
+he@^1.1.1:
+ version "1.1.1"
+ resolved "http://registry.npm.taobao.org/he/download/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
+
highlight.js@^9.3.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
@@ -4033,16 +4319,16 @@ i18n-2@^0.7.2:
debug "^3.1.0"
sprintf "^0.1.5"
-iconv-lite@0.4.19:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
-
-iconv-lite@^0.4.19, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
+iconv-lite@0.4, iconv-lite@^0.4.19, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
safer-buffer ">= 2.1.2 < 3"
+iconv-lite@0.4.19:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+
iconv-lite@~0.2.11:
version "0.2.11"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.2.11.tgz#1ce60a3a57864a292d1321ff4609ca4bb965adc8"
@@ -5294,7 +5580,7 @@ lodash@^3.5.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-lodash@^4.0.0, lodash@^4.0.1, lodash@^4.11.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
+lodash@^4.0.0, lodash@^4.0.1, lodash@^4.11.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@@ -5364,9 +5650,9 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
-"markdown-it-admonition@https://github.com/johannbre/markdown-it-admonition.git":
- version "1.0.2"
- resolved "https://github.com/johannbre/markdown-it-admonition.git#e0c0fcd59e9119d6d60ed209aa3d0f1177ec0166"
+markdown-it-admonition@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/markdown-it-admonition/-/markdown-it-admonition-1.0.4.tgz#d7bbc7eb1fe6168fc8cc304de7a9d8c993acb2f5"
markdown-it-emoji@^1.1.1:
version "1.4.0"
@@ -5466,6 +5752,10 @@ mdurl@^1.0.1, mdurl@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
+meaw@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/meaw/-/meaw-2.0.0.tgz#7c3467efee5618cb865661dfaa38d6948dc23f7a"
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -5523,6 +5813,19 @@ merge@^1.1.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
+mermaid@^8.0.0-rc.8:
+ version "8.0.0-rc.8"
+ resolved "http://registry.npm.taobao.org/mermaid/download/mermaid-8.0.0-rc.8.tgz#74ed54d0d46e9ee71c4db2730b2d83d516a21e72"
+ dependencies:
+ d3 "^4.13.0"
+ dagre-d3-renderer "^0.5.8"
+ dagre-layout "^0.8.8"
+ graphlibrary "^2.2.0"
+ he "^1.1.1"
+ lodash "^4.17.5"
+ moment "^2.21.0"
+ scope-css "^1.0.5"
+
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -5692,6 +5995,10 @@ mock-require@^3.0.1:
get-caller-file "^1.0.2"
normalize-path "^2.1.1"
+moment@^2.10.2, moment@^2.21.0:
+ version "2.22.2"
+ resolved "http://registry.npm.taobao.org/moment/download/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
+
moment@^2.10.3:
version "2.22.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad"
@@ -7341,6 +7648,10 @@ run-series@^1.1.1:
version "1.1.8"
resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36"
+rw@1:
+ version "1.3.3"
+ resolved "http://registry.npm.taobao.org/rw/download/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
+
rx-lite@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
@@ -7418,6 +7729,10 @@ sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+scope-css@^1.0.5:
+ version "1.1.0"
+ resolved "http://registry.npm.taobao.org/scope-css/download/scope-css-1.1.0.tgz#74eff45461bc9d3f3b29ed575b798cd722fa1256"
+
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -8818,6 +9133,10 @@ xmldom@0.1.x:
version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
+xmlhttprequest@1:
+ version "1.8.0"
+ resolved "http://registry.npm.taobao.org/xmlhttprequest/download/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
+
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"