1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

Merge remote-tracking branch 'BoostIO/master'

This commit is contained in:
Unknown
2018-08-12 17:53:03 +01:00
27 changed files with 1021 additions and 202 deletions

View File

@@ -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) {

View File

@@ -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 {
<style id='style'></style>
<link rel="stylesheet" id="codeTheme">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
${this.getScrollBarStyle()}
</style>
`
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 () {

View File

@@ -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

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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
)
}
}
}

View File

@@ -40,6 +40,12 @@ class Markdown {
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
if (langType === 'chart') {
return `<pre class="chart">${str}</pre>`
}
if (langType === 'mermaid') {
return `<pre class="mermaid">${str}</pre>`
}
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
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

View File

@@ -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)
}

View File

@@ -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 = ['&quot;', '&amp;', '&#39;', '&lt;', '&gt;']
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 = '&quot;'
break
case 38: // &
escape = '&amp;'
break
case 39: // '
escape = '&#39;'
break
case 60: // <
escape = '&lt;'
break
case 62: // >
escape = '&gt;'
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 &quot; will be came &amp;quot;
let nextStr = ''
let nextIndex = current.index
let escapedStr = false
// maximum length of an escaped string is 5. For example ('&quot;')
// we take the next 5 character of the next string if it is one of the string:
// ['&quot;', '&amp;', '&#39;', '&lt;', '&gt;'] 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, '&amp;')
}
} else if (current.char === '"') {
html = replaceAt(html, current.index, '&quot;')
} else if (current.char === "'" && !opt.skipSingleQuote) {
html = replaceAt(html, current.index, '&#39;')
} else if (current.char === '<') {
html = replaceAt(html, current.index, '&lt;')
} else if (current.char === '>') {
html = replaceAt(html, current.index, '&gt;')
}
lastIndex = index + 1
html += escape
}
return lastIndex !== index
? html + str.substring(lastIndex, index)
: html
return html
}
export function isObjectEqual (a, b) {

View File

@@ -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
}) => (
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
</button>
)

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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:

View File

@@ -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) {

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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 (
<div styleName='root'
<div
styleName='root'
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyDown={e => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>{i18n.__('Make a note')}</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<ModalEscButton
handleEscButtonClick={e => this.handleCloseButtonClick(e)}
/>
<div styleName='control'>
<button styleName='control-button'
onClick={(e) => this.handleMarkdownNoteButtonClick(e)}
onKeyDown={(e) => this.handleMarkdownNoteButtonKeyDown(e)}
<button
styleName='control-button'
onClick={e => this.handleMarkdownNoteButtonClick(e)}
onKeyDown={e => this.handleMarkdownNoteButtonKeyDown(e)}
ref='markdownButton'
>
<i styleName='control-button-icon'
className='fa fa-file-text-o'
/><br />
<span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br />
<span styleName='control-button-description'>{i18n.__('This format is for creating text documents. Checklists, code blocks and Latex blocks are available.')}</span>
<i styleName='control-button-icon' className='fa fa-file-text-o' />
<br />
<span styleName='control-button-label'>
{i18n.__('Markdown Note')}
</span>
<br />
<span styleName='control-button-description'>
{i18n.__(
'This format is for creating text documents. Checklists, code blocks and Latex blocks are available.'
)}
</span>
</button>
<button styleName='control-button'
onClick={(e) => this.handleSnippetNoteButtonClick(e)}
onKeyDown={(e) => this.handleSnippetNoteButtonKeyDown(e)}
<button
styleName='control-button'
onClick={e => this.handleSnippetNoteButtonClick(e)}
onKeyDown={e => this.handleSnippetNoteButtonKeyDown(e)}
ref='snippetButton'
>
<i styleName='control-button-icon'
className='fa fa-code'
/><br />
<span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br />
<span styleName='control-button-description'>{i18n.__('This format is for creating code snippets. Multiple snippets can be grouped into a single note.')}
<i styleName='control-button-icon' className='fa fa-code' /><br />
<span styleName='control-button-label'>
{i18n.__('Snippet Note')}
</span>
<br />
<span styleName='control-button-description'>
{i18n.__(
'This format is for creating code snippets. Multiple snippets can be grouped into a single note.'
)}
</span>
</button>
</div>
<div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
<div styleName='description'>
<i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}
</div>
</div>
)
}
}
NewNoteModal.propTypes = {
}
NewNoteModal.propTypes = {}
export default CSSModules(NewNoteModal, styles)

View File

@@ -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%')

View File

@@ -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で動かす必要があります。

View File

@@ -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+='

35
package-lock.json generated
View File

@@ -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="
}
}
}

View File

@@ -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",

View File

@@ -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.
![Boostnote app screenshot](./resources/repository/top.png)
@@ -19,8 +19,10 @@ Thank you to all the people who already contributed to Boostnote!
<a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a>
## 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/)

View File

@@ -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 =
'<html>\n' +
' <head>\n' +
' //header\n' +
' </head>\n' +
' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' +
' <img src=":storage' + mdurl.encode(path.win32.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' +
' <p data-line="4">\n' +
' <a href=":storage' + mdurl.encode(path.posix.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' +
' </body>\n' +
'</html>'
const storagePath = '<<dummyStoragePath>>'
const expectedOutput =
'<html>\n' +
' <head>\n' +
' //header\n' +
' </head>\n' +
' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' +
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' +
' <p data-line="4">\n' +
' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' +
' </body>\n' +
'</html>'
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 =
'<html>\n' +
' <head>\n' +
' //header\n' +
' </head>\n' +
' <body data-theme="default">\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
' <p data-line="2">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
' </p>\n' +
' <p data-line="4">\n' +
' <a href=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
' </p>\n' +
' <p data-line="6">\n' +
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
' </p>\n' +
' </body>\n' +
'</html>'
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' +
'![Screenshot1](:storage' + path.win32.sep + '0.3b88d0dc.png)\n' +
'![Screenshot2](:storage' + path.posix.sep + '0.2cb8875c.pdf)"'
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' +
'![Screenshot1](:storage' + path.win32.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.win32.sep + '0.3b88d0dc.png)\n' +
'![Screenshot2](:storage' + path.posix.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.posix.sep + '2cb8875c.pdf)\n' +
'![Screenshot3](:storage' + path.win32.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.posix.sep + 'bbf49b02.jpg)"'
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' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + noteKey + path.sep + 'pdf.pdf](pdf})'
const expectedOutput =
'Test input' +
'![' + systemUnderTest.DESTINATION_FOLDER + path.sep + 'image.jpg](imageName}) \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'

View File

@@ -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 = ` <no escape>
<escapeMe>`
const expected = ` <no escape>
&lt;escapeMe&gt;`
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 &amp;'
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 = ` <no escape>
<escapeMe>`
const expected = ` &lt;no escape&gt;
&lt;escapeMe&gt;`
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 &amp; or &quot; but do escape &'
const expected = 'Do not escape &amp; or &quot; but do escape &amp;'
const actual = escapeHtmlCharacters(input)
t.is(actual, expected)
})
test('escapeHtmlCharacters should skip char if in code block', t => {
const input = `
\`\`\`
<dontescapeme>
\`\`\`
das<das>dasd
dasdasdasd
\`\`\`
<dontescapeme>
\`\`\`
`
const expected = `
\`\`\`
<dontescapeme>
\`\`\`
das&lt;das&gt;dasd
dasdasdasd
\`\`\`
<dontescapeme>
\`\`\`
`
const actual = escapeHtmlCharacters(input, { detectCodeBlock: true })
t.is(actual, expected)
})
test('escapeHtmlCharacters should return the correct result', t => {
const input = '& < > " \''
const expected = '&amp; &lt; &gt; &quot; &#39;'
const actual = escapeHtmlCharacters(input)
t.is(actual, expected)
})

333
yarn.lock
View File

@@ -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"