diff --git a/LICENSE b/LICENSE index 7472c9eb2..2d1ab1312 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,7 @@ GPL-3.0 Boostnote - an open source note-taking app made for programmers just like you. -Copyright (C) 2017 - 2018 BoostIO +Copyright (C) 2017 - 2019 BoostIO This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 486349934..6ad294ed2 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -888,10 +888,7 @@ export default class CodeEditor extends React.Component { handlePaste (editor, forceSmartPaste) { const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props - const isURL = str => { - const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ - return matcher.test(str) - } + const isURL = str => /(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str) const isInLinkTag = editor => { const startCursor = editor.getCursor('start') @@ -964,7 +961,7 @@ export default class CodeEditor extends React.Component { } else { const image = clipboard.readImage() if (!image.isEmpty()) { - attachmentManagement.handlePastNativeImage( + attachmentManagement.handlePasteNativeImage( this, storageKey, noteKey, @@ -1109,7 +1106,7 @@ export default class CodeEditor extends React.Component { iconv.encodingExists(_charset) ? _charset : 'utf-8' - resolve(iconv.decode(new Buffer(buff), charset).toString()) + resolve(iconv.decode(Buffer.from(buff), charset).toString()) } catch (e) { reject(e) } diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index e31548d0b..466be3fa7 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -20,10 +20,10 @@ class MarkdownEditor extends React.Component { this.supportMdSelectionBold = [16, 17, 186] this.state = { - status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'PREVIEW', + status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE', renderValue: props.value, keyPressed: new Set(), - isLocked: false + isLocked: props.isLocked } this.lockEditorCode = () => this.handleLockEditor() @@ -76,6 +76,7 @@ class MarkdownEditor extends React.Component { } handleContextMenu (e) { + if (this.state.isLocked) return const { config } = this.props if (config.editor.switchPreview === 'RIGHTCLICK') { const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW' diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 4c638dc94..a6819ce92 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -832,6 +832,81 @@ export default class MarkdownPreview extends React.Component { ) } ) + + const markdownPreviewIframe = document.querySelector('.MarkdownPreview') + const rect = markdownPreviewIframe.getBoundingClientRect() + const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img') + for (const img of imgList) { + img.onclick = () => { + const widthMagnification = document.body.clientWidth / img.width + const heightMagnification = document.body.clientHeight / img.height + const baseOnWidth = widthMagnification < heightMagnification + const magnification = baseOnWidth ? widthMagnification : heightMagnification + + const zoomImgWidth = img.width * magnification + const zoomImgHeight = img.height * magnification + const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2 + const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2 + const originalImgTop = img.y + rect.top + const originalImgLeft = img.x + rect.left + const originalImgRect = { + top: `${originalImgTop}px`, + left: `${originalImgLeft}px`, + width: `${img.width}px`, + height: `${img.height}px` + } + const zoomInImgRect = { + top: `${baseOnWidth ? zoomImgTop : 0}px`, + left: `${baseOnWidth ? 0 : zoomImgLeft}px`, + width: `${zoomImgWidth}px`, + height: `${zoomImgHeight}px` + } + const animationSpeed = 300 + + const zoomImg = document.createElement('img') + zoomImg.src = img.src + zoomImg.style = ` + position: absolute; + top: ${baseOnWidth ? zoomImgTop : 0}px; + left: ${baseOnWidth ? 0 : zoomImgLeft}px; + width: ${zoomImgWidth}; + height: ${zoomImgHeight}px; + ` + zoomImg.animate([ + originalImgRect, + zoomInImgRect + ], animationSpeed) + + const overlay = document.createElement('div') + overlay.style = ` + background-color: rgba(0,0,0,0.5); + cursor: zoom-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: ${document.body.clientHeight}px; + z-index: 100; + ` + overlay.onclick = () => { + zoomImg.style = ` + position: absolute; + top: ${originalImgTop}px; + left: ${originalImgLeft}px; + width: ${img.width}px; + height: ${img.height}px; + ` + const zoomOutImgAnimation = zoomImg.animate([ + zoomInImgRect, + originalImgRect + ], animationSpeed) + zoomOutImgAnimation.onfinish = () => overlay.remove() + } + + overlay.appendChild(zoomImg) + document.body.appendChild(overlay) + } + } } focus () { diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index da767a9f4..4921b531d 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -61,6 +61,8 @@ body // allow inline line breaks .katex white-space initial + .katex .katex-html + display inline-flex .katex .mfrac>.vlist>span:nth-child(2) top 0 !important .katex-error @@ -163,6 +165,7 @@ p white-space pre-line word-wrap break-word img + cursor zoom-in max-width 100% strong, b font-weight bold diff --git a/browser/lib/markdownTextHelper.js b/browser/lib/markdownTextHelper.js index 1501e2c70..1657efd9d 100644 --- a/browser/lib/markdownTextHelper.js +++ b/browser/lib/markdownTextHelper.js @@ -22,7 +22,7 @@ export function strip (input) { .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1') .replace(/>/g, '') .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '') - .replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1') + .replace(/^#{1,6}\s*/gm, '') .replace(/(`{3,})(.*?)\1/gm, '$2') .replace(/^-{3,}\s*$/g, '') .replace(/`(.+?)`/g, '$1') diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index ad7eb5909..3ed61eb74 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -42,9 +42,10 @@ class MarkdownNoteDetail extends React.Component { content: '', linesHighlighted: [] }, props.note), - isLockButtonShown: false, + isLockButtonShown: props.config.editor.type !== 'SPLIT', isLocked: false, - editorType: props.config.editor.type + editorType: props.config.editor.type, + switchPreview: props.config.editor.switchPreview } this.dispatchTimer = null @@ -65,6 +66,9 @@ class MarkdownNoteDetail extends React.Component { }) ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this)) ee.on('code:generate-toc', this.generateToc) + + // Focus content if using blur or double click + if (this.state.switchPreview === 'BLUR' || this.state.switchPreview === 'DBL_CLICK') this.focus() } componentWillReceiveProps (nextProps) { @@ -96,7 +100,12 @@ class MarkdownNoteDetail extends React.Component { handleUpdateContent () { const { note } = this.state note.content = this.refs.content.value - note.title = markdown.strip(striptags(findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField))) + + let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField) + title = striptags(title) + title = markdown.strip(title) + note.title = title + this.updateNote(note) } @@ -294,7 +303,7 @@ class MarkdownNoteDetail extends React.Component { handleToggleLockButton (event, noteStatus) { // first argument event is not used - if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') { + if (noteStatus === 'CODE') { this.setState({isLockButtonShown: true}) } else { this.setState({isLockButtonShown: false}) @@ -320,7 +329,8 @@ class MarkdownNoteDetail extends React.Component { } handleSwitchMode (type) { - this.setState({ editorType: type }, () => { + // If in split mode, hide the lock button + this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => { this.focus() const newConfig = Object.assign({}, this.props.config) newConfig.editor.type = type @@ -365,6 +375,7 @@ class MarkdownNoteDetail extends React.Component { noteKey={note.key} linesHighlighted={note.linesHighlighted} onChange={this.handleUpdateContent.bind(this)} + isLocked={this.state.isLocked} ignorePreviewPointerEvents={ignorePreviewPointerEvents} /> } else { diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index ca513c044..33a0adf08 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -656,14 +656,18 @@ class NoteList extends React.Component { }) ) .then((data) => { - data.forEach((item) => { - dispatch({ - type: 'DELETE_NOTE', - storageKey: item.storageKey, - noteKey: item.noteKey + const dispatchHandler = () => { + data.forEach((item) => { + dispatch({ + type: 'DELETE_NOTE', + storageKey: item.storageKey, + noteKey: item.noteKey + }) }) - }) + } + ee.once('list:next', dispatchHandler) }) + .then(() => ee.emit('list:next')) .catch((err) => { console.error('Cannot Delete note: ' + err) }) @@ -687,6 +691,7 @@ class NoteList extends React.Component { }) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE') }) + .then(() => ee.emit('list:next')) .catch((err) => { console.error('Notes could not go to trash: ' + err) }) diff --git a/browser/main/global.styl b/browser/main/global.styl index e04060c28..d864993dd 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -97,6 +97,7 @@ modalBackColor = white body[data-theme="dark"] + background-color $ui-dark-backgroundColor ::-webkit-scrollbar-thumb background-color rgba(0, 0, 0, 0.3) .ModalBase @@ -148,6 +149,7 @@ body[data-theme="dark"] z-index modalZIndex + 5 body[data-theme="solarized-dark"] + background-color $ui-solarized-dark-backgroundColor ::-webkit-scrollbar-thumb background-color rgba(0, 0, 0, 0.3) .ModalBase @@ -157,6 +159,7 @@ body[data-theme="solarized-dark"] color: $ui-solarized-dark-text-color body[data-theme="monokai"] + background-color $ui-monokai-backgroundColor ::-webkit-scrollbar-thumb background-color rgba(0, 0, 0, 0.3) .ModalBase @@ -166,6 +169,7 @@ body[data-theme="monokai"] color: $ui-monokai-text-color body[data-theme="dracula"] + background-color $ui-dracula-backgroundColor ::-webkit-scrollbar-thumb background-color rgba(0, 0, 0, 0.3) .ModalBase diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 059589d07..5558b3bd1 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -41,7 +41,7 @@ export const DEFAULT_CONFIG = { theme: 'base16-light', keyMap: 'sublime', fontSize: '14', - fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas', + fontFamily: win ? 'Consolas' : 'Monaco', indentType: 'space', indentSize: '2', enableRulers: false, @@ -211,7 +211,7 @@ function assignConfigValues (originalConfig, rcConfig) { function rewriteHotkey (config) { const keys = [...Object.keys(config.hotkey)] keys.forEach(key => { - config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command') + config.hotkey[key] = config.hotkey[key].replace(/Cmd\s/g, 'Command ') config.hotkey[key] = config.hotkey[key].replace(/Opt\s/g, 'Option ') }) return config diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 6a0315f7a..6fa3b51fa 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -6,6 +6,7 @@ const mdurl = require('mdurl') const fse = require('fs-extra') const escapeStringRegexp = require('escape-string-regexp') const sander = require('sander') +const url = require('url') import i18n from 'browser/lib/i18n' const STORAGE_FOLDER_PLACEHOLDER = ':storage' @@ -18,15 +19,23 @@ const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp( * @returns {Promise} Image element created */ function getImage (file) { - return new Promise((resolve) => { - const reader = new FileReader() - const img = new Image() - img.onload = () => resolve(img) - reader.onload = e => { - img.src = e.target.result - } - reader.readAsDataURL(file) - }) + if (_.isString(file)) { + return new Promise(resolve => { + const img = new Image() + img.onload = () => resolve(img) + img.src = file + }) + } else { + return new Promise(resolve => { + const reader = new FileReader() + const img = new Image() + img.onload = () => resolve(img) + reader.onload = e => { + img.src = e.target.result + } + reader.readAsDataURL(file) + }) + } } /** @@ -151,23 +160,28 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr try { const isBase64 = typeof sourceFilePath === 'object' && sourceFilePath.type === 'base64' - if (!fs.existsSync(sourceFilePath) && !isBase64) { + if (!isBase64 && !fs.existsSync(sourceFilePath)) { return reject('source file does not exist') } - const targetStorage = findStorage.findStorage(storageKey) + + const sourcePath = sourceFilePath.sourceFilePath || sourceFilePath + const sourceURL = url.parse(/^\w+:\/\//.test(sourcePath) ? sourcePath : 'file:///' + sourcePath) + let destinationName if (useRandomName) { - destinationName = `${uniqueSlug()}${path.extname(sourceFilePath.sourceFilePath || sourceFilePath)}` + destinationName = `${uniqueSlug()}${path.extname(sourceURL.pathname) || '.png'}` } else { - destinationName = path.basename(sourceFilePath.sourceFilePath || sourceFilePath) + destinationName = path.basename(sourceURL.pathname) } + + const targetStorage = findStorage.findStorage(storageKey) const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) createAttachmentDestinationFolder(targetStorage.path, noteKey) const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName)) if (isBase64) { const base64Data = sourceFilePath.data.replace(/^data:image\/\w+;base64,/, '') - const dataBuffer = new Buffer(base64Data, 'base64') + const dataBuffer = Buffer.from(base64Data, 'base64') outputFile.write(dataBuffer, () => { resolve(destinationName) }) @@ -231,11 +245,11 @@ function fixLocalURLS (renderedHTML, storagePath) { A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`. - `STORAGE_FOLDER_PLACEHOLDER` will match `:storage` - - `(?:(?:\\\/|%5C)[\\w.]+)+` will match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg` - - `(?:\\\/|%5C)[\\w.]+` will either match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564` or `/f939b2c3.jpg` + - `(?:(?:\\\/|%5C)[-.\\w]+)+` will match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg` + - `(?:\\\/|%5C)[-.\\w]+` will either match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564` or `/f939b2c3.jpg` - `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows. */ - return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[\\w.]+)+', 'g'), function (match) { + return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[-.\\w]+)+', '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)) }) @@ -261,22 +275,69 @@ function generateAttachmentMarkdown (fileName, path, showPreview) { * @param {Event} dropEvent DropEvent */ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { - const file = dropEvent.dataTransfer.files[0] - const filePath = file.path - const originalFileName = path.basename(filePath) - const fileType = file['type'] - const isImage = fileType.startsWith('image') let promise - if (isImage) { - promise = fixRotate(file).then(base64data => { - return copyAttachment({type: 'base64', data: base64data, sourceFilePath: filePath}, storageKey, noteKey) - }) + if (dropEvent.dataTransfer.files.length > 0) { + promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => { + if (file.type.startsWith('image')) { + if (file.type === 'image/gif' || file.type === 'image/svg+xml') { + return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({ + fileName, + title: path.basename(file.path), + isImage: true + })) + } else { + return fixRotate(file) + .then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey) + .then(fileName => ({ + fileName, + title: path.basename(file.path), + isImage: true + })) + ) + } + } else { + return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({ + fileName, + title: path.basename(file.path), + isImage: false + })) + } + })) } else { - promise = copyAttachment(filePath, storageKey, noteKey) + let imageURL = dropEvent.dataTransfer.getData('text/plain') + + if (!imageURL) { + const match = /]*[\s"']src="([^"]+)"/.exec(dropEvent.dataTransfer.getData('text/html')) + if (match) { + imageURL = match[1] + } + } + + if (!imageURL) { + return + } + + promise = Promise.all([getImage(imageURL) + .then(image => { + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + canvas.width = image.width + canvas.height = image.height + context.drawImage(image, 0, 0) + + return copyAttachment({type: 'base64', data: canvas.toDataURL(), sourceFilePath: imageURL}, storageKey, noteKey) + }) + .then(fileName => ({ + fileName, + title: imageURL, + isImage: true + }))]) } - promise.then((fileName) => { - const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), isImage) - codeEditor.insertAttachmentMd(imageMd) + + promise.then(files => { + const attachments = files.filter(file => !!file).map(file => generateAttachmentMarkdown(file.title, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, file.fileName), file.isImage)) + + codeEditor.insertAttachmentMd(attachments.join('\n')) }) } @@ -287,7 +348,7 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { * @param {String} noteKey Key of the current note * @param {DataTransferItem} dataTransferItem Part of the past-event */ -function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) { +function handlePasteImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) { if (!codeEditor) { throw new Error('codeEditor has to be given') } @@ -331,7 +392,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem * @param {String} noteKey Key of the current note * @param {NativeImage} image The native image */ -function handlePastNativeImage (codeEditor, storageKey, noteKey, image) { +function handlePasteNativeImage (codeEditor, storageKey, noteKey, image) { if (!codeEditor) { throw new Error('codeEditor has to be given') } @@ -429,7 +490,14 @@ function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) { * @returns {String} Input without the references */ function removeStorageAndNoteReferences (input, noteKey) { - return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER) + return input.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?("|])', 'g'), function (match) { + const temp = match + .replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), path.sep) + .replace(new RegExp(mdurl.encode(path.posix.sep), 'g'), path.sep) + .replace(new RegExp(escapeStringRegexp(path.win32.sep), 'g'), path.sep) + .replace(new RegExp(escapeStringRegexp(path.posix.sep), 'g'), path.sep) + return temp.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER) + }) } /** @@ -584,8 +652,8 @@ module.exports = { fixLocalURLS, generateAttachmentMarkdown, handleAttachmentDrop, - handlePastImageEvent, - handlePastNativeImage, + handlePasteImageEvent, + handlePasteNativeImage, getAttachmentsInMarkdownContent, getAbsolutePathsOfAttachmentsInContent, removeStorageAndNoteReferences, diff --git a/browser/main/lib/dataApi/init.js b/browser/main/lib/dataApi/init.js index 7f81e90bd..0dbcc1828 100644 --- a/browser/main/lib/dataApi/init.js +++ b/browser/main/lib/dataApi/init.js @@ -4,6 +4,7 @@ const resolveStorageData = require('./resolveStorageData') const resolveStorageNotes = require('./resolveStorageNotes') const consts = require('browser/lib/consts') const path = require('path') +const fs = require('fs') const CSON = require('@rokt33r/season') /** * @return {Object} all storages and notes @@ -19,11 +20,14 @@ const CSON = require('@rokt33r/season') * 2. legacy * 3. empty directory */ + function init () { const fetchStorages = function () { let rawStorages try { rawStorages = JSON.parse(window.localStorage.getItem('storages')) + // Remove storages who's location is inaccesible. + rawStorages = rawStorages.filter(storage => fs.existsSync(storage.path)) if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.') } catch (e) { console.warn('Failed to parse cached data from localStorage', e) @@ -36,6 +40,7 @@ function init () { const fetchNotes = function (storages) { const findNotesFromEachStorage = storages + .filter(storage => fs.existsSync(storage.path)) .map((storage) => { return resolveStorageNotes(storage) .then((notes) => { @@ -51,7 +56,11 @@ function init () { } }) if (unknownCount > 0) { - CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) + try { + CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) + } catch (e) { + console.log('Error writting boostnote.json: ' + e + ' from init.js') + } } return notes }) diff --git a/browser/main/modals/PreferencesModal/Crowdfunding.js b/browser/main/modals/PreferencesModal/Crowdfunding.js index f6389cd86..f94ee5caf 100644 --- a/browser/main/modals/PreferencesModal/Crowdfunding.js +++ b/browser/main/modals/PreferencesModal/Crowdfunding.js @@ -34,7 +34,7 @@ class Crowdfunding extends React.Component {

{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}


{i18n.__('### We believe Meritocracy')}

-

{i18n.__('We think developers who has skill and did great things must be rewarded properly.')}

+

{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}

{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}

{i18n.__('It sometimes looks like exploitation.')}

{i18n.__('We’ve realized IssueHunt could enhance sustainability of open-source ecosystem.')}

diff --git a/browser/main/modals/PreferencesModal/InfoTab.js b/browser/main/modals/PreferencesModal/InfoTab.js index d618fa221..dafabb021 100644 --- a/browser/main/modals/PreferencesModal/InfoTab.js +++ b/browser/main/modals/PreferencesModal/InfoTab.js @@ -134,7 +134,7 @@ class InfoTab extends React.Component { >{i18n.__('Development')}{i18n.__(' : Development configurations for Boostnote.')}
  • - {i18n.__('Copyright (C) 2017 - 2018 BoostIO')} + {i18n.__('Copyright (C) 2017 - 2019 BoostIO')}
  • {i18n.__('License: GPL v3')} diff --git a/docs/build.md b/docs/build.md index eaf946662..095f86283 100644 --- a/docs/build.md +++ b/docs/build.md @@ -31,6 +31,34 @@ $ yarn run dev > 1. When editing a constructor method of a component > 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.) +## Accessing code used in Pull Requests +Visit the page for the pull request and look at the end of the url for the PR number +
    +https://github.com/BoostIO/Boostnote/pull/2794
    +
    +In the following, replace \ with that number (no brackets). +For the above url, you would replace \ with 2794 + +_If you do not have a local copy of the master branch yet_ +``` +git clone https://github.com/BoostIO/Boostnote.git +cd Boostnote +git fetch origin pull//head: +git checkout +``` + +_If you already have the master branch_ +``` +git fetch origin pull//head: +git checkout +``` + +_To compile and run the code_ +``` +yarn +yarn run dev +``` + ## Deploy We use Grunt to automate deployment. diff --git a/extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js b/extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js index aa766f6ef..bc03e9205 100644 --- a/extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js +++ b/extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js @@ -44,9 +44,46 @@ ? match[2].replace('x', ' ') : (parseInt(match[3], 10) + 1) + match[4] replacements[i] = '\n' + indent + bullet + after + + if (bullet) incrementRemainingMarkdownListNumbers(cm, pos) } } - cm.replaceSelections(replacements) } + // Auto-updating Markdown list numbers when a new item is added to the + // middle of a list + function incrementRemainingMarkdownListNumbers(cm, pos) { + var startLine = pos.line, lookAhead = 0, skipCount = 0 + var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1] + + do { + lookAhead += 1 + var nextLineNumber = startLine + lookAhead + var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine) + + if (nextItem) { + var nextIndent = nextItem[1] + var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount) + var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber + + if (startIndent === nextIndent && !isNaN(nextNumber)) { + if (newNumber === nextNumber) itemNumber = nextNumber + 1 + if (newNumber > nextNumber) itemNumber = newNumber + 1 + cm.replaceRange( + nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]), + { + line: nextLineNumber, ch: 0 + }, { + line: nextLineNumber, ch: nextLine.length + }) + } else { + if (startIndent.length > nextIndent.length) return + // This doesn't run if the next line immediatley indents, as it is + // not clear of the users intention (new indented item or same level) + if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return + skipCount += 1 + } + } + } while (nextItem) + } }) diff --git a/lib/main-app.js b/lib/main-app.js index f25d07d23..6151c1f0d 100644 --- a/lib/main-app.js +++ b/lib/main-app.js @@ -3,7 +3,9 @@ const app = electron.app const Menu = electron.Menu const ipc = electron.ipcMain const GhReleases = require('electron-gh-releases') +const { isPackaged } = app // electron.crashReporter.start() + var ipcServer = null var mainWindow = null @@ -35,6 +37,10 @@ const updater = new GhReleases(ghReleasesOpts) // Check for updates // `status` returns true if there is a new update available function checkUpdate () { + if (!isPackaged) { // Prevents app from attempting to update when in dev mode. + console.log('Updates are disabled in Development mode, see main-app.js') + return true + } if (process.platform === 'linux' || isUpdateReady) { return true } @@ -94,12 +100,12 @@ app.on('ready', function () { // Check update every day setInterval(function () { - checkUpdate() + if (isPackaged) checkUpdate() }, 1000 * 60 * 60 * 24) // Check update after 10 secs to prevent file locking of Windows setTimeout(() => { - checkUpdate() + if (isPackaged) checkUpdate() ipc.on('update-check', function (event, msg) { if (isUpdateReady) { diff --git a/lib/main-menu.js b/lib/main-menu.js index 05921347c..dcd85217c 100644 --- a/lib/main-menu.js +++ b/lib/main-menu.js @@ -383,6 +383,27 @@ const help = { { label: 'Changelog', click () { shell.openExternal('https://github.com/BoostIO/boost-releases') } + }, + { + label: 'Cheatsheets', + submenu: [ + { + label: 'Markdown', + click () { shell.openExternal('https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet') } + }, + { + label: 'Latex', + click () { shell.openExternal('https://katex.org/docs/supported.html') } + }, + { + label: 'HTML', + click () { shell.openExternal('https://htmlcheatsheet.com/') } + }, + { + label: 'Boostnote', + click () { shell.openExternal('https://github.com/TobseF/boostnote-markdown-cheatsheet/blob/master/BOOSTNOTE_MARKDOWN_CHEAT_SHEET.md') } + } + ] } ] } diff --git a/package.json b/package.json index 95b4ecd79..a845c4381 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "boost", "productName": "Boostnote", - "version": "0.11.12", + "version": "0.11.15", "main": "index.js", "description": "Boostnote", "license": "GPL-3.0", @@ -31,7 +31,7 @@ "storage", "electron" ], - "author": "Dick Choi (https://github.com/Rokt33r)", + "author": "Junyoung Choi (https://github.com/Rokt33r)", "contributors": [ "Kazu Yokomizo (https://github.com/kazup01)", "dojineko (https://github.com/dojineko)", @@ -41,7 +41,8 @@ "Yoshihisa Mochihara (https://github.com/yosmoc)", "Mike Resoli (https://github.com/mikeres0)", "tjado (https://github.com/tejado)", - "Sota Sugiura (https://github.com/sota1235)" + "Sota Sugiura (https://github.com/sota1235)", + "Milo Todt (https://github.com/MiloTodt)" ], "bugs": { "url": "https://github.com/BoostIO/Boostnote/issues" diff --git a/readme.md b/readme.md index aba9b92a9..5657cb0ad 100644 --- a/readme.md +++ b/readme.md @@ -12,6 +12,7 @@

    ## Authors & Maintainers + - [Rokt33r](https://github.com/rokt33r) - [Sosuke](https://github.com/sosukesuzuki) - [Kazz](https://github.com/kazup01) @@ -23,7 +24,7 @@ Thank you to all the people who have contributed to Boostnote! ## Supporting Boostnote -Boostnote is an open source project. It's an independent project with its ongoing development made possible thanks to the support by our amazing backers. +Boostnote is an open source project. It's an independent project with its ongoing development made possible thanks to the support by our amazing backers. Issues on Boostnote can be funded by anyone and the money will be distributed to contributors and maintainers. If you use Boostnote please consider becoming a backer: @@ -41,7 +42,7 @@ Issues on Boostnote can be funded by anyone and the money will be distributed to * Website: https://boostnote.io * Newsletters: https://boostnote.io/#subscribe * [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote. -* Copyright (C) 2016 - 2018 BoostIO, Inc. +* Copyright (C) 2016 - 2019 BoostIO, Inc. #### License diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index a4cc80820..800159b86 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -38,7 +38,7 @@ it('should test that copyAttachment should throw an error if sourcePath dosen\'t fs.existsSync = jest.fn() fs.existsSync.mockReturnValue(false) - systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then(() => {}, error => { + return systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then(() => {}, error => { expect(error).toBe('source file does not exist') expect(fs.existsSync).toHaveBeenCalledWith('path') }) @@ -64,7 +64,7 @@ it('should test that copyAttachment works correctly assuming correct working of findStorage.findStorage.mockReturnValue(dummyStorage) uniqueSlug.mockReturnValue(dummyUniquePath) - systemUnderTest.copyAttachment(sourcePath, storageKey, noteKey).then( + return systemUnderTest.copyAttachment(sourcePath, storageKey, noteKey).then( function (newFileName) { expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) expect(fs.createReadStream).toHaveBeenCalledWith(sourcePath) @@ -83,7 +83,7 @@ it('should test that copyAttachment creates a new folder if the attachment folde const dummyReadStream = {} dummyReadStream.pipe = jest.fn() - dummyReadStream.on = jest.fn() + dummyReadStream.on = jest.fn((event, callback) => { callback() }) fs.createReadStream = jest.fn(() => dummyReadStream) fs.existsSync = jest.fn() fs.existsSync.mockReturnValueOnce(true) @@ -95,7 +95,7 @@ it('should test that copyAttachment creates a new folder if the attachment folde findStorage.findStorage.mockReturnValue(dummyStorage) uniqueSlug.mockReturnValue('dummyPath') - systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then( + return systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then( function () { expect(fs.existsSync).toHaveBeenCalledWith(attachmentFolderPath) expect(fs.mkdirSync).toHaveBeenCalledWith(attachmentFolderPath) @@ -109,7 +109,7 @@ it('should test that copyAttachment don\'t uses a random file name if not intend const dummyReadStream = {} dummyReadStream.pipe = jest.fn() - dummyReadStream.on = jest.fn() + dummyReadStream.on = jest.fn((event, callback) => { callback() }) fs.createReadStream = jest.fn(() => dummyReadStream) fs.existsSync = jest.fn() fs.existsSync.mockReturnValueOnce(true) @@ -120,14 +120,155 @@ it('should test that copyAttachment don\'t uses a random file name if not intend findStorage.findStorage.mockReturnValue(dummyStorage) uniqueSlug.mockReturnValue('dummyPath') - systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey', false).then( + return systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey', false).then( function (newFileName) { expect(newFileName).toBe('path') }) }) +it('should test that copyAttachment with url (with extension, without query)', function () { + const dummyStorage = {path: 'dummyStoragePath'} + + const dummyReadStream = { + pipe: jest.fn(), + on: jest.fn((event, callback) => { callback() }) + } + fs.createReadStream = jest.fn(() => dummyReadStream) + + const dummyWriteStream = { + write: jest.fn((data, callback) => { callback() }) + } + fs.createWriteStream = jest.fn(() => dummyWriteStream) + + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValueOnce(true) + fs.existsSync.mockReturnValueOnce(false) + fs.mkdirSync = jest.fn() + + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValue(dummyStorage) + uniqueSlug.mockReturnValue('dummyPath') + + const sourcePath = { + sourceFilePath: 'http://www.foo.bar/baz/qux.jpg', + type: 'base64', + data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl' + } + + return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then( + function (newFileName) { + expect(newFileName).toBe('dummyPath.jpg') + }) +}) + +it('should test that copyAttachment with url (with extension, with query)', function () { + const dummyStorage = {path: 'dummyStoragePath'} + + const dummyReadStream = { + pipe: jest.fn(), + on: jest.fn((event, callback) => { callback() }) + } + fs.createReadStream = jest.fn(() => dummyReadStream) + + const dummyWriteStream = { + write: jest.fn((data, callback) => { callback() }) + } + fs.createWriteStream = jest.fn(() => dummyWriteStream) + + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValueOnce(true) + fs.existsSync.mockReturnValueOnce(false) + fs.mkdirSync = jest.fn() + + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValue(dummyStorage) + uniqueSlug.mockReturnValue('dummyPath') + + const sourcePath = { + sourceFilePath: 'http://www.foo.bar/baz/qux.jpg?h=1080', + type: 'base64', + data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl' + } + + return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then( + function (newFileName) { + expect(newFileName).toBe('dummyPath.jpg') + }) +}) + +it('should test that copyAttachment with url (without extension, without query)', function () { + const dummyStorage = {path: 'dummyStoragePath'} + + const dummyReadStream = { + pipe: jest.fn(), + on: jest.fn((event, callback) => { callback() }) + } + fs.createReadStream = jest.fn(() => dummyReadStream) + + const dummyWriteStream = { + write: jest.fn((data, callback) => { callback() }) + } + fs.createWriteStream = jest.fn(() => dummyWriteStream) + + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValueOnce(true) + fs.existsSync.mockReturnValueOnce(false) + fs.mkdirSync = jest.fn() + + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValue(dummyStorage) + uniqueSlug.mockReturnValue('dummyPath') + + const sourcePath = { + sourceFilePath: 'http://www.foo.bar/baz/qux', + type: 'base64', + data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl' + } + + return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then( + function (newFileName) { + expect(newFileName).toBe('dummyPath.png') + }) +}) + +it('should test that copyAttachment with url (without extension, with query)', function () { + const dummyStorage = {path: 'dummyStoragePath'} + + const dummyReadStream = { + pipe: jest.fn(), + on: jest.fn((event, callback) => { callback() }) + } + fs.createReadStream = jest.fn(() => dummyReadStream) + + const dummyWriteStream = { + write: jest.fn((data, callback) => { callback() }) + } + fs.createWriteStream = jest.fn(() => dummyWriteStream) + + fs.existsSync = jest.fn() + fs.existsSync.mockReturnValueOnce(true) + fs.existsSync.mockReturnValueOnce(false) + fs.mkdirSync = jest.fn() + + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValue(dummyStorage) + uniqueSlug.mockReturnValue('dummyPath') + + const sourcePath = { + sourceFilePath: 'http://www.foo.bar/baz/qux?h=1080', + type: 'base64', + data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl' + } + + return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then( + function (newFileName) { + expect(newFileName).toBe('dummyPath.png') + }) +}) + it('should replace the all ":storage" path with the actual storage path', function () { const storageFolder = systemUnderTest.DESTINATION_FOLDER + const noteKey = '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' const testInput = '\n' + ' \n' + @@ -136,14 +277,18 @@ it('should replace the all ":storage" path with the actual storage path', functi ' \n' + '

    Headline

    \n' + '

    \n' + - ' dummyImage.png\n' + + ' dummyImage.png\n' + '

    \n' + '

    \n' + - ' dummyPDF.pdf\n' + + ' dummyPDF.pdf\n' + '

    \n' + '

    \n' + - ' dummyImage2.jpg\n' + + ' dummyImage2.jpg\n' + '

    \n' + + '
    \n' +
    +    '            \n' +
    +    '            \n' +
    +    '        
    \n' + ' \n' + '' const storagePath = '<>' @@ -155,14 +300,18 @@ it('should replace the all ":storage" path with the actual storage path', functi ' \n' + '

    Headline

    \n' + '

    \n' + - ' dummyImage.png\n' + + ' dummyImage.png\n' + '

    \n' + '

    \n' + - ' dummyPDF.pdf\n' + + ' dummyPDF.pdf\n' + '

    \n' + '

    \n' + - ' dummyImage2.jpg\n' + + ' dummyImage2.jpg\n' + '

    \n' + + '
    \n' +
    +    '            \n' +
    +    '            \n' +
    +    '        
    \n' + ' \n' + '' const actual = systemUnderTest.fixLocalURLS(testInput, storagePath) @@ -171,6 +320,7 @@ it('should replace the all ":storage" path with the actual storage path', functi it('should replace the ":storage" path with the actual storage path when they have different path separators', function () { const storageFolder = systemUnderTest.DESTINATION_FOLDER + const noteKey = '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' const testInput = '\n' + ' \n' + @@ -179,10 +329,10 @@ it('should replace the ":storage" path with the actual storage path when they ha ' \n' + '

    Headline

    \n' + '

    \n' + - ' dummyImage.png\n' + + ' dummyImage.png\n' + '

    \n' + '

    \n' + - ' dummyPDF.pdf\n' + + ' dummyPDF.pdf\n' + '

    \n' + ' \n' + '' @@ -195,10 +345,10 @@ it('should replace the ":storage" path with the actual storage path when they ha ' \n' + '

    Headline

    \n' + '

    \n' + - ' dummyImage.png\n' + + ' dummyImage.png\n' + '

    \n' + '

    \n' + - ' dummyPDF.pdf\n' + + ' dummyPDF.pdf\n' + '

    \n' + ' \n' + '' @@ -251,28 +401,17 @@ it('should test that getAttachmentsInMarkdownContent finds all attachments when it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () { const dummyStoragePath = 'dummyStoragePath' - const testInput = - '\n' + - ' \n' + - ' //header\n' + - ' \n' + - ' \n' + - '

    Headline

    \n' + - '

    \n' + - ' dummyImage.png\n' + - '

    \n' + - '

    \n' + - ' dummyPDF.pdf\n' + - '

    \n' + - '

    \n' + - ' dummyImage2.jpg\n' + - '

    \n' + - ' \n' + - '' + const noteKey = '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + const testInput = '"# Test\n' + + '\n' + + '![Screenshot1](:storage' + path.win32.sep + noteKey + path.win32.sep + '0.6r4zdgc22xp.png)\n' + + '![Screenshot2](:storage' + path.posix.sep + noteKey + path.posix.sep + '0.q2i4iw0fyx.pdf)\n' + + '![Screenshot3](:storage' + path.win32.sep + noteKey + path.posix.sep + 'd6c5ee92.jpg)"' + const actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath) - const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp.png', - dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx.pdf', - dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] + const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + noteKey + path.sep + '0.6r4zdgc22xp.png', + dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + noteKey + path.sep + '0.q2i4iw0fyx.pdf', + dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + noteKey + path.sep + 'd6c5ee92.jpg'] expect(actual).toEqual(expect.arrayContaining(expected)) }) @@ -287,13 +426,13 @@ it('should remove the all ":storage" and noteKey references', function () { ' \n' + '

    Headline

    \n' + '

    \n' + - ' dummyImage.png\n' + + ' dummyImage.png\n' + '

    \n' + '

    \n' + - ' dummyPDF.pdf\n' + + ' dummyPDF.pdf\n' + '

    \n' + '

    \n' + - ' dummyImage2.jpg\n' + + ' dummyImage2.jpg\n' + '

    \n' + ' \n' + '' @@ -323,8 +462,8 @@ it('should make sure that "removeStorageAndNoteReferences" works with markdown c 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})' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + noteKey + path.win32.sep + 'image.jpg](imageName}) \n' + + '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + noteKey + path.posix.sep + 'pdf.pdf](pdf})' const expectedOutput = 'Test input' + @@ -476,8 +615,8 @@ it('should test that moveAttachments returns a correct modified content version' const newNoteKey = 'newNoteKey' const testInput = 'Test input' + - '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNoteKey + path.sep + 'image.jpg](imageName}) \n' + - '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNoteKey + path.sep + 'pdf.pdf](pdf})' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + oldNoteKey + path.win32.sep + 'image.jpg](imageName}) \n' + + '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + oldNoteKey + path.posix.sep + 'pdf.pdf](pdf})' const expectedOutput = 'Test input' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + 'image.jpg](imageName}) \n' + @@ -492,8 +631,8 @@ it('should test that cloneAttachments modifies the content of the new note corre const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'} const testInput = 'Test input' + - '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' + - '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + oldNote.key + path.win32.sep + 'image.jpg](imageName}) \n' + + '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + oldNote.key + path.posix.sep + 'pdf.pdf](pdf})' newNote.content = testInput findStorage.findStorage = jest.fn() findStorage.findStorage.mockReturnValue({path: 'dummyStoragePath'}) @@ -516,8 +655,8 @@ it('should test that cloneAttachments finds all attachments and copies them to t const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'MARKDOWN_NOTE'} const testInput = 'Test input' + - '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' + - '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + oldNote.key + path.win32.sep + 'image.jpg](imageName}) \n' + + '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + oldNote.key + path.posix.sep + 'pdf.pdf](pdf})' oldNote.content = testInput newNote.content = testInput @@ -566,14 +705,22 @@ it('should test that isAttachmentLink works correctly', function () { expect(systemUnderTest.isAttachmentLink('text')).toBe(false) expect(systemUnderTest.isAttachmentLink('text [linkText](link)')).toBe(false) expect(systemUnderTest.isAttachmentLink('text ![linkText](link)')).toBe(false) - expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true) - expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf )')).toBe(true) - expect(systemUnderTest.isAttachmentLink('text [ linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true) - expect(systemUnderTest.isAttachmentLink('text ![linkText ](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf)')).toBe(true) - expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) - expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) - expect(systemUnderTest.isAttachmentLink('text [linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) - expect(systemUnderTest.isAttachmentLink('text ![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + 'noteKey' + path.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf)')).toBe(true) + expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf )')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text [ linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf)')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text ![linkText ](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf)')).toBe(true) + expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text [linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text ![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + 'noteKey' + path.win32.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf)')).toBe(true) + expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf )')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text [ linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf)')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text ![linkText ](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf)')).toBe(true) + expect(systemUnderTest.isAttachmentLink('[linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text [linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true) + expect(systemUnderTest.isAttachmentLink('text ![linkText](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + 'noteKey' + path.posix.sep + 'pdf.pdf) test')).toBe(true) }) it('should test that handleAttachmentLinkPaste copies the attachments to the new location', function () { @@ -581,7 +728,7 @@ it('should test that handleAttachmentLinkPaste copies the attachments to the new findStorage.findStorage = jest.fn(() => dummyStorage) const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf)' const storageKey = 'storageKey' const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') @@ -596,12 +743,53 @@ it('should test that handleAttachmentLinkPaste copies the attachments to the new }) }) -it('should test that handleAttachmentLinkPaste don\'t try to copy the file if it does not exist', function () { +it('should test that handleAttachmentLinkPaste copies the attachments to the new location - win32 path', function () { + const dummyStorage = {path: 'dummyStoragePath'} + findStorage.findStorage = jest.fn(() => dummyStorage) + const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' + const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf)' + const storageKey = 'storageKey' + const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') + + sander.exists = jest.fn(() => Promise.resolve(true)) + systemUnderTest.copyAttachment = jest.fn(() => Promise.resolve('dummyNewFileName')) + + return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText) + .then(() => { + expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) + expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePath) + expect(systemUnderTest.copyAttachment).toHaveBeenCalledWith(expectedSourceFilePath, storageKey, newNoteKey) + }) +}) + +it('should test that handleAttachmentLinkPaste don\'t try to copy the file if it does not exist - win32 path', function () { + const dummyStorage = {path: 'dummyStoragePath'} + findStorage.findStorage = jest.fn(() => dummyStorage) + const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' + const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf)' + const storageKey = 'storageKey' + const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') + + sander.exists = jest.fn(() => Promise.resolve(false)) + systemUnderTest.copyAttachment = jest.fn() + systemUnderTest.generateFileNotFoundMarkdown = jest.fn() + + return systemUnderTest.handleAttachmentLinkPaste(storageKey, newNoteKey, pasteText) + .then(() => { + expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) + expect(sander.exists).toHaveBeenCalledWith(expectedSourceFilePath) + expect(systemUnderTest.copyAttachment).not.toHaveBeenCalled() + }) +}) + +it('should test that handleAttachmentLinkPaste don\'t try to copy the file if it does not exist -- posix', function () { const dummyStorage = {path: 'dummyStoragePath'} findStorage.findStorage = jest.fn(() => dummyStorage) const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf)' const storageKey = 'storageKey' const expectedSourceFilePath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') @@ -622,8 +810,8 @@ it('should test that handleAttachmentLinkPaste copies multiple attachments if mu findStorage.findStorage = jest.fn(() => dummyStorage) const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ..' + - '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf) ..' + + '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'img.jpg)' const storageKey = 'storageKey' const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg') @@ -647,7 +835,7 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const dummyNewFileName = 'dummyNewFileName' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf)' const expectedText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileName + ')' const storageKey = 'storageKey' @@ -667,8 +855,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const dummyNewFileNameOne = 'dummyNewFileName' const dummyNewFileNameTwo = 'dummyNewFileNameTwo' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ' + - '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf) ' + + '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'img.jpg)' const expectedText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileNameOne + ') ' + '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyNewFileNameTwo + ')' const storageKey = 'storageKey' @@ -689,8 +877,8 @@ it('should test that handleAttachmentLinkPaste calls the copy method correct if findStorage.findStorage = jest.fn(() => dummyStorage) const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ..' + - '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf) ..' + + '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'img.jpg)' const storageKey = 'storageKey' const expectedSourceFilePathOne = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'pdf.pdf') const expectedSourceFilePathTwo = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, pastedNoteKey, 'img.jpg') @@ -716,7 +904,7 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past findStorage.findStorage = jest.fn(() => dummyStorage) const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' - const pasteText = 'text ![alt.png](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf)' + const pasteText = 'text ![alt.png](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf)' const storageKey = 'storageKey' const fileNotFoundMD = 'file not found' const expectedPastText = 'text ' + fileNotFoundMD @@ -735,8 +923,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past findStorage.findStorage = jest.fn(() => dummyStorage) const pastedNoteKey = 'b1e06f81-8266-49b9-b438-084003c2e723' const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) ' + - '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf) ' + + '![secondImage](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'img.jpg)' const storageKey = 'storageKey' const fileNotFoundMD = 'file not found' const expectedPastText = 'text ' + fileNotFoundMD + ' ' + fileNotFoundMD @@ -757,8 +945,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const dummyFoundFileName = 'dummyFileName' const fileNotFoundMD = 'file not found' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) .. ' + - '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'pdf.pdf) .. ' + + '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'img.jpg)' const storageKey = 'storageKey' const expectedPastText = 'text ' + fileNotFoundMD + ' .. ![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyFoundFileName + ')' @@ -781,8 +969,8 @@ it('should test that handleAttachmentLinkPaste returns the correct modified past const newNoteKey = 'abc234-8266-49b9-b438-084003c2e723' const dummyFoundFileName = 'dummyFileName' const fileNotFoundMD = 'file not found' - const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'pdf.pdf) .. ' + - '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + pastedNoteKey + path.sep + 'img.jpg)' + const pasteText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.posix.sep + pastedNoteKey + path.posix.sep + 'pdf.pdf) .. ' + + '![secondAttachment](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.win32.sep + pastedNoteKey + path.win32.sep + 'img.jpg)' const storageKey = 'storageKey' const expectedPastText = 'text ![alt](' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + dummyFoundFileName + ') .. ' + fileNotFoundMD diff --git a/tests/lib/markdown-text-helper-test.js b/tests/lib/markdown-text-helper-test.js index e4ad86bc2..38ee3136c 100644 --- a/tests/lib/markdown-text-helper-test.js +++ b/tests/lib/markdown-text-helper-test.js @@ -36,7 +36,8 @@ test(t => { ['`MY_TITLE`', 'MY_TITLE'], ['MY_TITLE', 'MY_TITLE'], // I have no idea for it... - ['```test', '`test'] + ['```test', '`test'], + ['# C# Features', 'C# Features'] ] testCases.forEach(testCase => { diff --git a/yarn.lock b/yarn.lock index 604880e57..c6fce7496 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4337,6 +4337,11 @@ 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.13.1: + version "9.13.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" + integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A== + highlight.js@^9.3.0: version "9.12.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" @@ -4652,6 +4657,11 @@ invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2: dependencies: loose-envify "^1.0.0" +invert-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-color/-/invert-color-2.0.0.tgz#894ab1f7494a6e45f5e74c2f99ec042cd67dd23e" + integrity sha512-9s6IATlhOAr0/0MPUpLdMpk81ixIu8IqwPwORssXBauFT/4ff/iyEOcojd0UYuPwkDbJvL1+blIZGhqVIaAm5Q== + invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" @@ -6316,9 +6326,10 @@ mousetrap-global-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd" -mousetrap@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.1.tgz#2a085f5c751294c75e7e81f6ec2545b29cbf42d9" +mousetrap@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.2.tgz#caadd9cf886db0986fb2fee59a82f6bd37527587" + integrity sha512-jDjhi7wlHwdO6q6DS7YRmSHcuI+RVxadBkLt3KHrhd3C2b+w5pKefg3oj5beTcHZyVFA9Aksf+yEE1y5jxUjVA== ms@2.0.0: version "2.0.0"