diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index a4d2278e..1b546ed1 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -5,6 +5,8 @@ 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' @@ -48,6 +50,8 @@ export default class CodeEditor extends React.Component { } this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchState = null + + this.formatTable = () => this.handleFormatTable() } handleSearch (msg) { @@ -81,6 +85,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) @@ -113,7 +121,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() @@ -182,6 +195,9 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor) CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) CodeMirror.Vim.map('ZZ', ':q', 'normal') + + this.tableEditor = new TableEditor(new TextEditorInterface(this.editor)) + eventEmitter.on('code:format-table', this.formatTable) } expandSnippet (line, cursor, cm, snippets) { @@ -264,6 +280,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) { diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 124bd8c3..5f0afc07 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -7,7 +7,9 @@ import 'codemirror-mode-elixir' import consts from 'browser/lib/consts' import Raphael from 'raphael' import flowchart from 'flowchart' +import mermaidRender from './render/MermaidRender' import SequenceDiagram from 'js-sequence-diagrams' +import Chart from 'chart.js' import eventEmitter from 'browser/main/lib/eventEmitter' import htmlTextHelper from 'browser/lib/htmlTextHelper' import convertModeName from 'browser/lib/convertModeName' @@ -131,6 +133,25 @@ body p { ` } +const scrollBarStyle = ` +::-webkit-scrollbar { + width: 12px; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.15); +} +` +const scrollBarDarkStyle = ` +::-webkit-scrollbar { + width: 12px; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.3); +} +` + const { shell } = require('electron') const OSX = global.process.platform === 'darwin' @@ -211,7 +232,20 @@ export default class MarkdownPreview extends React.Component { } handleSaveAsMd () { - this.exportAsDocument('md') + this.exportAsDocument('md', (noteContent, exportTasks) => { + 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 () { @@ -219,13 +253,18 @@ export default class MarkdownPreview extends React.Component { 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)) + 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://', '') + if (global.process.platform === 'win32') { + file = file.replace('file:///', '') + } else { + file = file.replace('file://', '') + } + exportTasks.push({ src: file, dst: 'css' @@ -295,6 +334,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('contextmenu', this.contextMenuHandler) @@ -303,6 +357,9 @@ export default class MarkdownPreview extends React.Component { + ` CSS_FILES.forEach((file) => { @@ -405,15 +462,8 @@ 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) + 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"]'), (el) => { @@ -496,6 +546,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) + } + ) } focus () { 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/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/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 75be4798..652d1f53 100644
--- a/browser/main/Detail/SnippetNoteDetail.js
+++ b/browser/main/Detail/SnippetNoteDetail.js
@@ -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()
}
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/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/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/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)}
>
{i18n.__('Make a note')}
- 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/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 a1d17e57..fbbb025f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "boost",
"productName": "Boostnote",
- "version": "0.11.7",
+ "version": "0.11.8",
"main": "index.js",
"description": "Boostnote",
"license": "GPL-3.0",
@@ -51,8 +51,10 @@
"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",
+ "chart.js": "^2.7.2",
"codemirror": "^5.39.0",
"codemirror-mode-elixir": "^1.1.1",
"electron-config": "^0.2.1",
@@ -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 6c68efaf..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,8 +19,10 @@ 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/)
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/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/yarn.lock b/yarn.lock
index 42f51ab9..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"
@@ -1750,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"
@@ -1806,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"
@@ -2146,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"
@@ -3685,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"
@@ -3874,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"
@@ -4037,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"
@@ -5298,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"
@@ -5368,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"
@@ -5470,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"
@@ -5527,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"
@@ -5696,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"
@@ -7345,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"
@@ -7422,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"
@@ -8822,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"