1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-16 11:15:12 +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 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName' 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 eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite' import iconv from 'iconv-lite'
import crypto from 'crypto' import crypto from 'crypto'
@@ -48,6 +50,8 @@ export default class CodeEditor extends React.Component {
} }
this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null this.searchState = null
this.formatTable = () => this.handleFormatTable()
} }
handleSearch (msg) { handleSearch (msg) {
@@ -81,6 +85,10 @@ export default class CodeEditor extends React.Component {
}) })
} }
handleFormatTable () {
this.tableEditor.formatAll(options({textWidthOptions: {}}))
}
componentDidMount () { componentDidMount () {
const { rulers, enableRulers } = this.props const { rulers, enableRulers } = this.props
const expandSnippet = this.expandSnippet.bind(this) const expandSnippet = this.expandSnippet.bind(this)
@@ -113,7 +121,12 @@ export default class CodeEditor extends React.Component {
dragDrop: false, dragDrop: false,
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: true, autoCloseBrackets: {
pairs: '()[]{}\'\'""$$**``',
triples: '```"""\'\'\'',
explode: '[]{}``$$',
override: true
},
extraKeys: { extraKeys: {
Tab: function (cm) { Tab: function (cm) {
const cursor = cm.getCursor() 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('wq', 'wq', this.quitEditor)
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
CodeMirror.Vim.map('ZZ', ':q', 'normal') 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) { expandSnippet (line, cursor, cm, snippets) {
@@ -264,6 +280,8 @@ export default class CodeEditor extends React.Component {
this.editor.off('scroll', this.scrollHandler) this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler) editorTheme.removeEventListener('load', this.loadStyleHandler)
eventEmitter.off('code:format-table', this.formatTable)
} }
componentDidUpdate (prevProps, prevState) { componentDidUpdate (prevProps, prevState) {

View File

@@ -7,7 +7,9 @@ import 'codemirror-mode-elixir'
import consts from 'browser/lib/consts' import consts from 'browser/lib/consts'
import Raphael from 'raphael' import Raphael from 'raphael'
import flowchart from 'flowchart' import flowchart from 'flowchart'
import mermaidRender from './render/MermaidRender'
import SequenceDiagram from 'js-sequence-diagrams' import SequenceDiagram from 'js-sequence-diagrams'
import Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper' import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName' 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 { shell } = require('electron')
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -211,7 +232,20 @@ export default class MarkdownPreview extends React.Component {
} }
handleSaveAsMd () { 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 () { 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 {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) 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 files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
files.forEach((file) => { files.forEach((file) => {
file = file.replace('file://', '') if (global.process.platform === 'win32') {
file = file.replace('file:///', '')
} else {
file = file.replace('file://', '')
}
exportTasks.push({ exportTasks.push({
src: file, src: file,
dst: 'css' 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 () { componentDidMount () {
this.refs.root.setAttribute('sandbox', 'allow-scripts') this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler) 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> <style id='style'></style>
<link rel="stylesheet" id="codeTheme"> <link rel="stylesheet" id="codeTheme">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
${this.getScrollBarStyle()}
</style>
` `
CSS_FILES.forEach((file) => { CSS_FILES.forEach((file) => {
@@ -405,15 +462,8 @@ export default class MarkdownPreview extends React.Component {
let { value, codeBlockTheme } = this.props let { value, codeBlockTheme } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme) this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
const renderedHTML = this.markdown.render(value)
const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g) attachmentManagement.migrateAttachments(value, storagePath, noteKey)
if (codeBlocks !== null) {
codeBlocks.forEach((codeBlock) => {
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
})
}
let renderedHTML = this.markdown.render(value)
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => { _.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 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 () { focus () {

View File

@@ -68,7 +68,7 @@ body
padding 5px padding 5px
margin -5px margin -5px
border-radius 5px border-radius 5px
.flowchart-error, .sequence-error .flowchart-error, .sequence-error .chart-error
background-color errorBackgroundColor background-color errorBackgroundColor
color errorTextColor color errorTextColor
padding 5px padding 5px
@@ -213,7 +213,7 @@ pre
margin 0 0 1em margin 0 0 1em
display flex display flex
line-height 1.4em line-height 1.4em
&.flowchart, &.sequence &.flowchart, &.sequence, &.chart
display flex display flex
justify-content center justify-content center
background-color white 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' 'use strict'
import sanitizeHtml from 'sanitize-html' import sanitizeHtml from 'sanitize-html'
import { escapeHtmlCharacters } from './utils'
module.exports = function sanitizePlugin (md, options) { module.exports = function sanitizePlugin (md, options) {
options = options || {} options = options || {}
@@ -8,13 +9,26 @@ module.exports = function sanitizePlugin (md, options) {
md.core.ruler.after('linkify', 'sanitize_inline', state => { md.core.ruler.after('linkify', 'sanitize_inline', state => {
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) { for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
if (state.tokens[tokenIdx].type === 'html_block') { 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') { if (state.tokens[tokenIdx].type === 'inline') {
const inlineTokens = state.tokens[tokenIdx].children const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) { for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
if (inlineTokens[childIdx].type === 'html_inline') { 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') { if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>` 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">' + return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' + '<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) + 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 // Override task item
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) { this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token let content, terminate, i, l, token
@@ -245,4 +267,3 @@ class Markdown {
} }
export default Markdown export default Markdown

View File

@@ -23,7 +23,9 @@ function findByWordOrTag (notes, block) {
return true return true
} }
if (note.type === 'SNIPPET_NOTE') { 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') { } else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(wordRegExp) return note.content.match(wordRegExp)
} }

View File

@@ -6,52 +6,113 @@ export function lastFindInArray (array, callback) {
} }
} }
export function escapeHtmlCharacters (text) { export function escapeHtmlCharacters (
const matchHtmlRegExp = /["'&<>]/ html,
const str = '' + text opt = { detectCodeBlock: false, skipSingleQuote: false }
const match = matchHtmlRegExp.exec(str) ) {
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) { while ((match = matchHtmlRegExp.exec(html)) !== null) {
return str const current = { char: match[0], index: match.index }
} const codeBlockIndexs = []
let openCodeBlock = null
let escape // if the detectCodeBlock option is activated then this function should skip
let html = '' // characters that needed to be escape but located in code block
let index = 0 if (opt.detectCodeBlock) {
let lastIndex = 0 // 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
for (index = match.index; index < str.length; index++) { // needed to be escape. It means we check for the begining of the line that
switch (str.charCodeAt(index)) { // contain that character, then we check if there are 4 spaces next to the
case 34: // " // \n character (the line start with 4 spaces)
escape = '&quot;' let previousLineEnd = current.index - 1
break while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
case 38: // & previousLineEnd--
escape = '&amp;' }
break // 4 spaces means this character is in a code block
case 39: // ' if (
escape = '&#39;' html[previousLineEnd + 1] === ' ' &&
break html[previousLineEnd + 2] === ' ' &&
case 60: // < html[previousLineEnd + 3] === ' ' &&
escape = '&lt;' html[previousLineEnd + 4] === ' '
break ) {
case 62: // > // skip the current character
escape = '&gt;'
break
default:
continue 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
}
} }
// otherwise, escape it !!!
if (lastIndex !== index) { if (current.char === '&') {
html += str.substring(lastIndex, index) // 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 html
return lastIndex !== index
? html + str.substring(lastIndex, index)
: html
} }
export function isObjectEqual (a, b) { export function isObjectEqual (a, b) {

View File

@@ -4,12 +4,14 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl' import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
const FullscreenButton = ({ const FullscreenButton = ({
onClick onClick
}) => ( }) => (
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}> <button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span> <span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
</button> </button>
) )

View File

@@ -33,6 +33,7 @@
.control-infoButton-panel-trash .control-infoButton-panel-trash
z-index 200 z-index 200
margin-top 0px margin-top 0px
top 50px
right 0px right 0px
position absolute position absolute
padding 20px 25px 0 25px padding 20px 25px 0 25px

View File

@@ -441,7 +441,7 @@ class SnippetNoteDetail extends React.Component {
const isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
? e.metaKey ? e.metaKey
: e.ctrlKey : e.ctrlKey
if (isSuper) { if (isSuper && !e.shiftKey) {
e.preventDefault() e.preventDefault()
this.addSnippet() this.addSnippet()
} }

View File

@@ -5,6 +5,7 @@ import styles from './TagSelect.styl'
import _ from 'lodash' import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter'
class TagSelect extends React.Component { class TagSelect extends React.Component {
constructor (props) { constructor (props) {
@@ -13,16 +14,26 @@ class TagSelect extends React.Component {
this.state = { this.state = {
newTag: '' newTag: ''
} }
this.addtagHandler = this.handleAddTag.bind(this)
} }
componentDidMount () { componentDidMount () {
this.value = this.props.value this.value = this.props.value
ee.on('editor:add-tag', this.addtagHandler)
} }
componentDidUpdate () { componentDidUpdate () {
this.value = this.props.value this.value = this.props.value
} }
componentWillUnmount () {
ee.off('editor:add-tag', this.addtagHandler)
}
handleAddTag () {
this.refs.newTag.focus()
}
handleNewTagInputKeyDown (e) { handleNewTagInputKeyDown (e) {
switch (e.keyCode) { switch (e.keyCode) {
case 9: case 9:

View File

@@ -9,6 +9,7 @@ import ee from 'browser/main/lib/eventEmitter'
import StatusBar from '../StatusBar' import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import debounceRender from 'react-debounce-render' import debounceRender from 'react-debounce-render'
import searchFromNotes from 'browser/lib/search'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -35,11 +36,38 @@ class Detail extends React.Component {
} }
render () { render () {
const { location, data, config } = this.props const { location, data, params, config } = this.props
let note = null let note = null
if (location.query.key != null) { if (location.query.key != null) {
const noteKey = location.query.key 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) { if (note == null) {

View File

@@ -156,8 +156,7 @@ class TopBar extends React.Component {
if (this.state.isSearching) { if (this.state.isSearching) {
el.blur() el.blur()
} else { } else {
el.focus() el.select()
el.setSelectionRange(0, el.value.length)
} }
} }

View File

@@ -15,6 +15,12 @@ body
font-weight 200 font-weight 200
-webkit-font-smoothing antialiased -webkit-font-smoothing antialiased
::-webkit-scrollbar
width 12px
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.15)
button, input, select, textarea button, input, select, textarea
font-family DEFAULT_FONTS font-family DEFAULT_FONTS
@@ -85,9 +91,11 @@ modalBackColor = white
absolute top left bottom right absolute top left bottom right
background-color modalBackColor background-color modalBackColor
z-index modalZIndex + 1 z-index modalZIndex + 1
body[data-theme="dark"] body[data-theme="dark"]
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
.modalBack .modalBack
background-color $ui-dark-backgroundColor background-color $ui-dark-backgroundColor
@@ -128,6 +136,8 @@ body[data-theme="dark"]
z-index modalZIndex + 5 z-index modalZIndex + 5
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
.modalBack .modalBack
background-color $ui-solarized-dark-backgroundColor background-color $ui-solarized-dark-backgroundColor
@@ -135,9 +145,10 @@ body[data-theme="solarized-dark"]
color: $ui-solarized-dark-text-color color: $ui-solarized-dark-text-color
body[data-theme="monokai"] body[data-theme="monokai"]
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
.modalBack .modalBack
background-color $ui-monokai-backgroundColor background-color $ui-monokai-backgroundColor
.sortableItemHelper .sortableItemHelper
color: $ui-monokai-text-color color: $ui-monokai-text-color

View File

@@ -10,6 +10,7 @@ import i18n from 'browser/lib/i18n'
const STORAGE_FOLDER_PLACEHOLDER = ':storage' const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments' const DESTINATION_FOLDER = 'attachments'
const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
/** /**
* @description * @description
@@ -76,14 +77,14 @@ function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
/** /**
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/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 storagePath Storage path of the current note
* @param noteKey Key of the current note * @param noteKey Key of the current note
*/ */
function migrateAttachments (renderedHTML, storagePath, noteKey) { function migrateAttachments (markdownContent, storagePath, noteKey) {
if (sander.existsSync(path.join(storagePath, 'images'))) { if (noteKey !== undefined && sander.existsSync(path.join(storagePath, 'images'))) {
const attachments = getAttachmentsInContent(renderedHTML) || [] const attachments = getAttachmentsInMarkdownContent(markdownContent) || []
if (attachments !== []) { if (attachments.length) {
createAttachmentDestinationFolder(storagePath, noteKey) createAttachmentDestinationFolder(storagePath, noteKey)
} }
for (const attachment of attachments) { 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. * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/ */
function fixLocalURLS (renderedHTML, storagePath) { 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 * @description Returns all attachment paths of the given markdown
* @param {String} markdownContent content in which the attachment paths should be found * @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 * @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
*/ */
function getAttachmentsInContent (markdownContent) { function getAttachmentsInMarkdownContent (markdownContent) {
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) 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') 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) return preparedInput.match(regexp)
} }
@@ -203,7 +207,7 @@ function getAttachmentsInContent (markdownContent) {
* @returns {String[]} Absolute paths of the referenced attachments * @returns {String[]} Absolute paths of the referenced attachments
*/ */
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) { function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
const temp = getAttachmentsInContent(markdownContent) || [] const temp = getAttachmentsInMarkdownContent(markdownContent) || []
const result = [] const result = []
for (const relativePath of temp) { for (const relativePath of temp) {
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))) 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) { function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
if (noteContent) { 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 return noteContent
} }
@@ -277,7 +282,7 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
} }
const targetStorage = findStorage.findStorage(storageKey) const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
const attachmentsInNote = getAttachmentsInContent(markdownContent) const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
const attachmentsInNoteOnlyFileNames = [] const attachmentsInNoteOnlyFileNames = []
if (attachmentsInNote) { if (attachmentsInNote) {
for (let i = 0; i < attachmentsInNote.length; i++) { for (let i = 0; i < attachmentsInNote.length; i++) {
@@ -348,7 +353,7 @@ function generateFileNotFoundMarkdown () {
*/ */
function isAttachmentLink (text) { function isAttachmentLink (text) {
if (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 return false
} }
@@ -364,7 +369,7 @@ function isAttachmentLink (text) {
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) { function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
if (storageKey != null && noteKey != null && linkText != null) { if (storageKey != null && noteKey != null && linkText != null) {
const storagePath = findStorage.findStorage(storageKey).path const storagePath = findStorage.findStorage(storageKey).path
const attachments = getAttachmentsInContent(linkText) || [] const attachments = getAttachmentsInMarkdownContent(linkText) || []
const replaceInstructions = [] const replaceInstructions = []
const copies = [] const copies = []
for (const attachment of attachments) { for (const attachment of attachments) {
@@ -373,13 +378,13 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
sander.exists(absPathOfAttachment) sander.exists(absPathOfAttachment)
.then((fileExists) => { .then((fileExists) => {
if (!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()}) replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
return Promise.resolve() return Promise.resolve()
} }
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey) return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
.then((fileName) => { .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({ replaceInstructions.push({
regexp: replaceLinkRegExp, regexp: replaceLinkRegExp,
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')' replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
@@ -408,7 +413,7 @@ module.exports = {
generateAttachmentMarkdown, generateAttachmentMarkdown,
handleAttachmentDrop, handleAttachmentDrop,
handlePastImageEvent, handlePastImageEvent,
getAttachmentsInContent, getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences, removeStorageAndNoteReferences,
deleteAttachmentFolder, deleteAttachmentFolder,

View File

@@ -12,8 +12,7 @@ class NewNoteModal extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {}
}
} }
componentDidMount () { componentDidMount () {
@@ -35,19 +34,20 @@ class NewNoteModal extends React.Component {
title: '', title: '',
content: '' content: ''
}) })
.then((note) => { .then(note => {
const noteHash = note.key const noteHash = note.key
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note: note
}) })
hashHistory.push({ hashHistory.push({
pathname: location.pathname, pathname: location.pathname,
query: {key: noteHash} query: { key: noteHash }
}) })
ee.emit('list:jump', noteHash) ee.emit('list:jump', noteHash)
ee.emit('detail:focus') ee.emit('detail:focus')
this.props.close() setTimeout(this.props.close, 200)
}) })
} }
@@ -69,13 +69,15 @@ class NewNoteModal extends React.Component {
folder: folder, folder: folder,
title: '', title: '',
description: '', description: '',
snippets: [{ snippets: [
name: '', {
mode: 'text', name: '',
content: '' mode: 'text',
}] content: ''
}
]
}) })
.then((note) => { .then(note => {
const noteHash = note.key const noteHash = note.key
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
@@ -83,11 +85,11 @@ class NewNoteModal extends React.Component {
}) })
hashHistory.push({ hashHistory.push({
pathname: location.pathname, pathname: location.pathname,
query: {key: noteHash} query: { key: noteHash }
}) })
ee.emit('list:jump', noteHash) ee.emit('list:jump', noteHash)
ee.emit('detail:focus') ee.emit('detail:focus')
this.props.close() setTimeout(this.props.close, 200)
}) })
} }
@@ -106,49 +108,65 @@ class NewNoteModal extends React.Component {
render () { render () {
return ( return (
<div styleName='root' <div
styleName='root'
tabIndex='-1' tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)} onKeyDown={e => this.handleKeyDown(e)}
> >
<div styleName='header'> <div styleName='header'>
<div styleName='title'>{i18n.__('Make a note')}</div> <div styleName='title'>{i18n.__('Make a note')}</div>
</div> </div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} /> <ModalEscButton
handleEscButtonClick={e => this.handleCloseButtonClick(e)}
/>
<div styleName='control'> <div styleName='control'>
<button styleName='control-button' <button
onClick={(e) => this.handleMarkdownNoteButtonClick(e)} styleName='control-button'
onKeyDown={(e) => this.handleMarkdownNoteButtonKeyDown(e)} onClick={e => this.handleMarkdownNoteButtonClick(e)}
onKeyDown={e => this.handleMarkdownNoteButtonKeyDown(e)}
ref='markdownButton' ref='markdownButton'
> >
<i styleName='control-button-icon' <i styleName='control-button-icon' className='fa fa-file-text-o' />
className='fa fa-file-text-o' <br />
/><br /> <span styleName='control-button-label'>
<span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br /> {i18n.__('Markdown Note')}
<span styleName='control-button-description'>{i18n.__('This format is for creating text documents. Checklists, code blocks and Latex blocks are available.')}</span> </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>
<button styleName='control-button' <button
onClick={(e) => this.handleSnippetNoteButtonClick(e)} styleName='control-button'
onKeyDown={(e) => this.handleSnippetNoteButtonKeyDown(e)} onClick={e => this.handleSnippetNoteButtonClick(e)}
onKeyDown={e => this.handleSnippetNoteButtonKeyDown(e)}
ref='snippetButton' ref='snippetButton'
> >
<i styleName='control-button-icon' <i styleName='control-button-icon' className='fa fa-code' /><br />
className='fa fa-code' <span styleName='control-button-label'>
/><br /> {i18n.__('Snippet Note')}
<span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br /> </span>
<span styleName='control-button-description'>{i18n.__('This format is for creating code snippets. Multiple snippets can be grouped into a single note.')} <br />
<span styleName='control-button-description'>
{i18n.__(
'This format is for creating code snippets. Multiple snippets can be grouped into a single note.'
)}
</span> </span>
</button> </button>
</div> </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> </div>
) )
} }
} }
NewNoteModal.propTypes = { NewNoteModal.propTypes = {}
}
export default CSSModules(NewNoteModal, styles) export default CSSModules(NewNoteModal, styles)

View File

@@ -27,7 +27,12 @@ class SnippetEditor extends React.Component {
dragDrop: false, dragDrop: false,
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: true, autoCloseBrackets: {
pairs: '()[]{}\'\'""$$**``',
triples: '```"""\'\'\'',
explode: '[]{}``$$',
override: true
},
mode: 'null' mode: 'null'
}) })
this.cm.setSize('100%', '100%') this.cm.setSize('100%', '100%')

View File

@@ -1,9 +1,16 @@
# Build # Build
## 環境
* npm: 4.x
* node: 7.x
`npm v5.x` だと `$ grunt pre-build` が失敗するので、 `npm v4.x` を使用してください。
## 開発 ## 開発
Webpack HRMを使います。 Webpack HRMを使います。
次の命令から私達がしておいた設定を使うことができます。 Boostnoteの最上位ディレクトリにて以下のコマンドを実行して、
デフォルトの設定の開発環境を起動させます。
依存するパッケージをインストールします。 依存するパッケージをインストールします。
@@ -27,15 +34,15 @@ $ yarn run dev-start
> ### 注意 > ### 注意
> 時々、直接リフレッシュをする必要があります。 > 時々、直接リフレッシュをする必要があります。
> 1. コンポネントのコンストラクタ関数を編集する場合 > 1. コンポネントのコンストラクタ関数を編集する場合
> 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポネントごとに書きなおされまが、この作業はコンストラクタで行われます。) > 2. 新しいCSSクラスを追加する場合(1.の理由と同じ: CSSクラス名はコンポネントごとに書きなおされまが、この作業はコンストラクタで行われます。)
## 配布 ## 配布
Gruntを使います。 Gruntを使います。
実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeの仮定が含まれるので、使っては行けないです 実際の配布は`grunt`で実行できます。しかし、これにはCodesignとAuthenticodeを実行するタスクが含まれるので、使用しないでください
それで、実行ファイルを作るスクリプトを用意しておきました。 代わりに、実行ファイルを作るスクリプトを用意しておきました。
このビルドはnpm v5.3.0では動かないのでv5.2.0で動かす必要があります。 このビルドはnpm v5.3.0では動かないのでv5.2.0で動かす必要があります。

View File

@@ -136,6 +136,15 @@ const file = {
{ {
type: 'separator' type: 'separator'
}, },
{
label: 'Format Table',
click () {
mainWindow.webContents.send('code:format-table')
}
},
{
type: 'separator'
},
{ {
label: 'Print', label: 'Print',
accelerator: 'CommandOrControl+P', accelerator: 'CommandOrControl+P',
@@ -209,6 +218,16 @@ const edit = {
label: 'Select All', label: 'Select All',
accelerator: 'Command+A', accelerator: 'Command+A',
selector: 'selectAll:' 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', label: 'Next Note',
accelerator: 'Control+J', accelerator: 'CommandOrControl+]',
click () { click () {
mainWindow.webContents.send('list:next') mainWindow.webContents.send('list:next')
} }
}, },
{ {
label: 'Previous Note', label: 'Previous Note',
accelerator: 'Control+K', accelerator: 'CommandOrControl+[',
click () { click () {
mainWindow.webContents.send('list:prior') mainWindow.webContents.send('list:prior')
} }
@@ -267,6 +286,19 @@ const view = {
mainWindow.setFullScreen(!mainWindow.isFullScreen()) mainWindow.setFullScreen(!mainWindow.isFullScreen())
} }
}, },
{
type: 'separator'
},
{
label: 'Toggle Side Bar',
accelerator: 'CommandOrControl+B',
click () {
mainWindow.webContents.send('editor:fullscreen')
}
},
{
type: 'separator'
},
{ {
role: 'zoomin', role: 'zoomin',
accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+=' 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", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.11.7", "version": "0.11.8",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -51,8 +51,10 @@
"dependencies": { "dependencies": {
"@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/markdown-it-math": "^4.0.1",
"@rokt33r/season": "^5.3.0", "@rokt33r/season": "^5.3.0",
"@susisu/mte-kernel": "^2.0.0",
"aws-sdk": "^2.48.0", "aws-sdk": "^2.48.0",
"aws-sdk-mobile-analytics": "^0.9.2", "aws-sdk-mobile-analytics": "^0.9.2",
"chart.js": "^2.7.2",
"codemirror": "^5.39.0", "codemirror": "^5.39.0",
"codemirror-mode-elixir": "^1.1.1", "codemirror-mode-elixir": "^1.1.1",
"electron-config": "^0.2.1", "electron-config": "^0.2.1",
@@ -71,7 +73,7 @@
"lodash": "^4.11.1", "lodash": "^4.11.1",
"lodash-move": "^1.1.1", "lodash-move": "^1.1.1",
"markdown-it": "^6.0.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-emoji": "^1.1.1",
"markdown-it-footnote": "^3.0.0", "markdown-it-footnote": "^3.0.0",
"markdown-it-imsize": "^2.0.1", "markdown-it-imsize": "^2.0.1",
@@ -81,6 +83,7 @@
"markdown-it-plantuml": "^1.1.0", "markdown-it-plantuml": "^1.1.0",
"markdown-it-smartarrows": "^1.0.1", "markdown-it-smartarrows": "^1.0.1",
"mdurl": "^1.0.1", "mdurl": "^1.0.1",
"mermaid": "^8.0.0-rc.8",
"moment": "^2.10.3", "moment": "^2.10.3",
"mousetrap": "^1.6.1", "mousetrap": "^1.6.1",
"mousetrap-global-bind": "^1.1.0", "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) ![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> <a href="https://github.com/BoostIO/Boostnote/graphs/contributors"><img src="https://opencollective.com/boostnoteio/contributors.svg?width=890" /></a>
## Supporting 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: 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.
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
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 ## Community
- [Facebook Group](https://www.facebook.com/groups/boostnote/) - [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) 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 () { it('should test that generateAttachmentMarkdown works correct both with previews and without', function () {
const fileName = 'fileName' const fileName = 'fileName'
const path = 'path' const path = 'path'
@@ -180,27 +217,35 @@ it('should test that generateAttachmentMarkdown works correct both with previews
expect(actual).toEqual(expected) expect(actual).toEqual(expected)
}) })
it('should test that getAttachmentsInContent finds all attachments', function () { it('should test that migrateAttachments work when they have different path separators', function () {
const testInput = sander.existsSync = jest.fn(() => true)
'<html>\n' + const dummyStoragePath = 'dummyStoragePath'
' <head>\n' + const imagesPath = path.join(dummyStoragePath, 'images')
' //header\n' + const attachmentsPath = path.join(dummyStoragePath, 'attachments')
' </head>\n' + const noteKey = 'noteKey'
' <body data-theme="default">\n' + const testInput = '"# Test\n' +
' <h2 data-line="0" id="Headline">Headline</h2>\n' + '\n' +
' <p data-line="2">\n' + '![Screenshot1](:storage' + path.win32.sep + '0.3b88d0dc.png)\n' +
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' + '![Screenshot2](:storage' + path.posix.sep + '0.2cb8875c.pdf)"'
' </p>\n' +
' <p data-line="4">\n' + systemUnderTest.migrateAttachments(testInput, dummyStoragePath, noteKey)
' <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' + expect(sander.existsSync.mock.calls[0][0]).toBe(imagesPath)
' <p data-line="6">\n' + expect(sander.existsSync.mock.calls[1][0]).toBe(path.join(imagesPath, '0.3b88d0dc.png'))
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' + expect(sander.existsSync.mock.calls[2][0]).toBe(path.join(attachmentsPath, '0.3b88d0dc.png'))
' </p>\n' + expect(sander.existsSync.mock.calls[3][0]).toBe(path.join(imagesPath, '0.2cb8875c.pdf'))
' </body>\n' + expect(sander.existsSync.mock.calls[4][0]).toBe(path.join(attachmentsPath, '0.2cb8875c.pdf'))
'</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 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)) expect(actual).toEqual(expect.arrayContaining(expected))
}) })
@@ -274,6 +319,21 @@ it('should remove the all ":storage" and noteKey references', function () {
expect(actual).toEqual(expectedOutput) 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 () { it('should delete the correct attachment folder if a note is deleted', function () {
const dummyStorage = {path: 'dummyStoragePath'} const dummyStorage = {path: 'dummyStoragePath'}
const storageKey = 'storageKey' 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" fs-plus "2.x"
optimist "~0.4.0" 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": "@types/node@^8.0.24":
version "8.10.17" version "8.10.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3" 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" escape-string-regexp "^1.0.5"
supports-color "^5.3.0" 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: chokidar@^1.0.0, chokidar@^1.4.2:
version "1.7.0" version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" 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" map-visit "^1.0.0"
object-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: color-convert@^1.3.0, color-convert@^1.9.0:
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" 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: dependencies:
delayed-stream "0.0.5" 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: commander@2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
@@ -2146,12 +2180,250 @@ currently-unhandled@^0.4.1:
dependencies: dependencies:
array-find-index "^1.0.1" 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: d@1:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
dependencies: dependencies:
es5-ext "^0.10.9" 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: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 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" version "1.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" 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: growly@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@@ -3874,6 +4152,10 @@ hawk@~2.3.0:
hoek "2.x.x" hoek "2.x.x"
sntp "1.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: highlight.js@^9.3.0:
version "9.12.0" version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" 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" debug "^3.1.0"
sprintf "^0.1.5" sprintf "^0.1.5"
iconv-lite@0.4.19: iconv-lite@0.4, iconv-lite@^0.4.19, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
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:
version "0.4.23" version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies: dependencies:
safer-buffer ">= 2.1.2 < 3" 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: iconv-lite@~0.2.11:
version "0.2.11" version "0.2.11"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.2.11.tgz#1ce60a3a57864a292d1321ff4609ca4bb965adc8" 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" version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" 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" version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@@ -5368,9 +5650,9 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" object-visit "^1.0.0"
"markdown-it-admonition@https://github.com/johannbre/markdown-it-admonition.git": markdown-it-admonition@^1.0.4:
version "1.0.2" version "1.0.4"
resolved "https://github.com/johannbre/markdown-it-admonition.git#e0c0fcd59e9119d6d60ed209aa3d0f1177ec0166" resolved "https://registry.yarnpkg.com/markdown-it-admonition/-/markdown-it-admonition-1.0.4.tgz#d7bbc7eb1fe6168fc8cc304de7a9d8c993acb2f5"
markdown-it-emoji@^1.1.1: markdown-it-emoji@^1.1.1:
version "1.4.0" version "1.4.0"
@@ -5470,6 +5752,10 @@ mdurl@^1.0.1, mdurl@~1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" 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: media-typer@0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 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" version "1.2.0"
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" 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: methods@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 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" get-caller-file "^1.0.2"
normalize-path "^2.1.1" 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: moment@^2.10.3:
version "2.22.1" version "2.22.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" 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" version "1.1.8"
resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36" 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: rx-lite@^3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" 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" version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 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: semver-diff@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" 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" version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" 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: xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"