From 8ae7d96cc7756f9acf85adc597b1fbedd25e35f6 Mon Sep 17 00:00:00 2001 From: Yu-Hung Ou Date: Tue, 27 Feb 2018 23:59:29 +1100 Subject: [PATCH 01/51] updated Travis CI node version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c68d1063..36542f3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - 6 + - 7 script: - npm run lint && npm run test - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi' From a76aed2d4efd31f99e49230717808479280544c0 Mon Sep 17 00:00:00 2001 From: ehhc Date: Sat, 21 Apr 2018 14:49:43 +0200 Subject: [PATCH 02/51] Fixes #1822 --- browser/main/lib/dataApi/attachmentManagement.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 6d4d7406..d1e57b75 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -39,7 +39,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr const targetStorage = findStorage.findStorage(storageKey) - const inputFile = fs.createReadStream(sourceFilePath) + const inputFileStream = fs.createReadStream(sourceFilePath) let destinationName if (useRandomName) { destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}` @@ -49,8 +49,10 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) createAttachmentDestinationFolder(targetStorage.path, noteKey) const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName)) - inputFile.pipe(outputFile) - resolve(destinationName) + inputFileStream.pipe(outputFile) + inputFileStream.on('end', () => { + resolve(destinationName) + }) } catch (e) { return reject(e) } @@ -146,7 +148,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem base64data = reader.result.replace(/^data:image\/png;base64,/, '') base64data += base64data.replace('+', ' ') const binaryData = new Buffer(base64data, 'base64').toString('binary') - fs.writeFile(imagePath, binaryData, 'binary') + fs.writeFileSync(imagePath, binaryData, 'binary') let imageMd = generateAttachmentMarkdown(imageName, imagePath, true) codeEditor.insertAttachmentMd(imageMd) } From 16794b9d78b8b30fc664b166bb9d56a4a76d7987 Mon Sep 17 00:00:00 2001 From: ehhc Date: Sat, 21 Apr 2018 16:32:27 +0200 Subject: [PATCH 03/51] Fixes #1822 -> fix for the broken tests --- tests/dataApi/attachmentManagement.test.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index 7e97cf80..1f40cd60 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -48,11 +48,13 @@ it('should test that copyAttachment works correctly assuming correct working of const noteKey = 'noteKey' const dummyUniquePath = 'dummyPath' const dummyStorage = {path: 'dummyStoragePath'} + const dummyReadStream = {} + dummyReadStream.pipe = jest.fn() + dummyReadStream.on = jest.fn((event, callback) => { callback() }) fs.existsSync = jest.fn() fs.existsSync.mockReturnValue(true) - fs.createReadStream = jest.fn() - fs.createReadStream.mockReturnValue({pipe: jest.fn()}) + fs.createReadStream = jest.fn(() => dummyReadStream) fs.createWriteStream = jest.fn() findStorage.findStorage = jest.fn() @@ -75,7 +77,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde const noteKey = 'noteKey' const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER) const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey) + const dummyReadStream = {} + dummyReadStream.pipe = jest.fn() + dummyReadStream.on = jest.fn() + fs.createReadStream = jest.fn(() => dummyReadStream) fs.existsSync = jest.fn() fs.existsSync.mockReturnValueOnce(true) fs.existsSync.mockReturnValueOnce(false) @@ -97,7 +103,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde it('should test that copyAttachment don\'t uses a random file name if not intended ', function () { const dummyStorage = {path: 'dummyStoragePath'} + const dummyReadStream = {} + dummyReadStream.pipe = jest.fn() + dummyReadStream.on = jest.fn() + fs.createReadStream = jest.fn(() => dummyReadStream) fs.existsSync = jest.fn() fs.existsSync.mockReturnValueOnce(true) fs.existsSync.mockReturnValueOnce(false) From 744bcba599c1c796bf66f971e80731140e5df766 Mon Sep 17 00:00:00 2001 From: Chen Shenghan Date: Wed, 25 Apr 2018 03:31:36 +1200 Subject: [PATCH 04/51] Fix #1780: Use black text with white background for printing and export in all theme --- browser/components/MarkdownPreview.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 33e0fa8f..d1e6dba5 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -29,7 +29,7 @@ const CSS_FILES = [ `${appPath}/node_modules/codemirror/lib/codemirror.css` ] -function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) { +function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) { return ` @font-face { font-family: 'Lato'; @@ -101,6 +101,13 @@ h2 { body p { white-space: normal; } + +@media print { + body[data-theme="${theme}"] { + color: #000; + background-color: #fff; + } +} ` } @@ -206,9 +213,9 @@ export default class MarkdownPreview extends React.Component { handleSaveAsHtml () { this.exportAsDocument('html', (noteContent, exportTasks) => { - const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams() + const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams() - const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber) + const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) const body = this.markdown.render(escapeHtmlCharacters(noteContent)) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] @@ -340,7 +347,7 @@ export default class MarkdownPreview extends React.Component { } getStyleParams () { - const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props + const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme } = this.props let { fontFamily, codeBlockFontFamily } = this.props fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0 ? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily) @@ -349,14 +356,14 @@ export default class MarkdownPreview extends React.Component { ? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily) : defaultCodeBlockFontFamily - return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} + return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} } applyStyle () { - const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams() + const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams() this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme) - this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) + this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) } GetCodeThemeLink (theme) { From c9c28eda1b0fac100679f973a772edae31f351f2 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Tue, 24 Apr 2018 23:40:59 +0200 Subject: [PATCH 05/51] support internal note link --- browser/components/MarkdownPreview.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 33e0fa8f..c70b4c71 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -532,21 +532,36 @@ export default class MarkdownPreview extends React.Component { return } - const noteHash = e.target.href.split('/').pop() + const linkHash = href.split('/').pop() + + const regexNoteInternalLink = /main.html#(.+)/ + if (regexNoteInternalLink.test(linkHash)) { + const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1]) + const targetElement = this.refs.root.contentWindow.document.getElementById(targetId) + + if (targetElement != null) { + this.getWindow().scrollTo(0, targetElement.offsetTop) + } + return + } + // this will match the new uuid v4 hash and the old hash // e.g. // :note:1c211eb7dcb463de6490 and // :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/ - if (regexIsNoteLink.test(noteHash)) { - eventEmitter.emit('list:jump', noteHash.replace(':note:', '')) + if (regexIsNoteLink.test(linkHash)) { + eventEmitter.emit('list:jump', linkHash.replace(':note:', '')) + return } + // this will match the old link format storage.key-note.key // e.g. // 877f99c3268608328037-1c211eb7dcb463de6490 const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/ - if (regexIsLegacyNoteLink.test(noteHash)) { - eventEmitter.emit('list:jump', noteHash.split('-')[1]) + if (regexIsLegacyNoteLink.test(linkHash)) { + eventEmitter.emit('list:jump', linkHash.split('-')[1]) + return } } From 01b1c49738a6c99d81189e652bb3dd964c0ba063 Mon Sep 17 00:00:00 2001 From: cJack1913 <328330255@qq.com> Date: Wed, 25 Apr 2018 17:23:09 +0800 Subject: [PATCH 06/51] Fix font color in code block(markdown preview) --- browser/components/markdown.styl | 1 - 1 file changed, 1 deletion(-) diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index cc6d7d92..e22aad9a 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -199,7 +199,6 @@ ol &>li>ul, &>li>ol margin 0 code - color #CC305F padding 0.2em 0.4em background-color #f7f7f7 border-radius 3px From 32e22dd5070a9f7bed72f78bddf408f3afd74b83 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Thu, 26 Apr 2018 23:46:39 +0200 Subject: [PATCH 07/51] update tagNoteMap when delete / restore the notebook deleted note should not belong to tagNoteMap --- browser/main/store.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/browser/main/store.js b/browser/main/store.js index f078ad20..8ca00e7f 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -88,9 +88,27 @@ function data (state = defaultDataMap(), action) { if (note.isTrashed) { state.trashedSet.add(uniqueKey) state.starredSet.delete(uniqueKey) + + note.tags.forEach(tag => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList != null) { + tagNoteList = new Set(tagNoteList) + tagNoteList.delete(uniqueKey) + state.tagNoteMap.set(tag, tagNoteList) + } + }) } else { state.trashedSet.delete(uniqueKey) + note.tags.forEach(tag => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList != null) { + tagNoteList = new Set(tagNoteList) + tagNoteList.add(uniqueKey) + state.tagNoteMap.set(tag, tagNoteList) + } + }) + if (note.isStarred) { state.starredSet.add(uniqueKey) } From 2831b0bd2af09da58221fadcf668fcd0c51f8ca6 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Thu, 26 Apr 2018 23:52:05 +0200 Subject: [PATCH 08/51] Only showing the tags that size > 0 No meaning to show the tag that has empty List The tag which is only used in the note(s) in the Trash, tags.size becomes zero. In order to support this case, filtering tagList is needed --- browser/main/SideNav/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index 6b53478e..0159293a 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -148,7 +148,9 @@ class SideNav extends React.Component { const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap) let tagList = _.sortBy(data.tagNoteMap.map( (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) }) - ), ['name']) + ), ['name']).filter( + tag => tag.size > 0 + ) if (config.sortTagsBy === 'COUNTER') { tagList = _.sortBy(tagList, item => (0 - item.size)) } From 92be3f32d6b8a50b74f4ec88e7e1f4147203c58a Mon Sep 17 00:00:00 2001 From: William Grant Date: Fri, 27 Apr 2018 08:10:50 +0200 Subject: [PATCH 09/51] added monokai theme --- browser/components/NoteItem.styl | 73 +++++++++++++++++++ browser/components/NoteItemSimple.styl | 58 +++++++++++++++ browser/components/RealtimeNotification.styl | 11 +++ browser/components/SideNavFilter.styl | 44 ++++++++++- browser/components/StorageItem.styl | 19 +++++ browser/components/TodoListPercentage.styl | 10 +++ browser/components/markdown.styl | 27 +++++++ browser/main/Detail/Detail.styl | 7 ++ browser/main/Detail/FolderSelect.styl | 19 +++++ browser/main/Detail/InfoPanel.styl | 40 ++++++++++ browser/main/Detail/MarkdownNoteDetail.styl | 5 ++ browser/main/Detail/NoteDetailInfo.styl | 4 + browser/main/Detail/SnippetNoteDetail.styl | 19 ++++- browser/main/Detail/TagSelect.styl | 18 ++++- browser/main/Detail/ToggleModeButton.styl | 7 ++ browser/main/Main.js | 3 +- browser/main/NewNoteButton/NewNoteButton.styl | 6 +- browser/main/NoteList/NoteList.styl | 26 ++++++- browser/main/SideNav/SideNav.styl | 5 ++ browser/main/StatusBar/StatusBar.styl | 11 +++ browser/main/TopBar/TopBar.styl | 22 ++++++ browser/main/global.styl | 6 ++ browser/main/lib/ConfigManager.js | 2 + browser/main/modals/CreateFolderModal.styl | 26 +++++++ browser/main/modals/NewNoteModal.styl | 16 ++++ .../modals/PreferencesModal/ConfigTab.styl | 30 ++++++++ .../modals/PreferencesModal/Crowdfunding.styl | 8 +- .../modals/PreferencesModal/FolderItem.styl | 23 ++++++ .../main/modals/PreferencesModal/InfoTab.styl | 7 ++ .../PreferencesModal/PreferencesModal.styl | 23 ++++++ .../modals/PreferencesModal/StoragesTab.styl | 37 ++++++++++ browser/main/modals/PreferencesModal/UiTab.js | 1 + browser/styles/index.styl | 36 +++++++++ 33 files changed, 642 insertions(+), 7 deletions(-) diff --git a/browser/components/NoteItem.styl b/browser/components/NoteItem.styl index 4067a6cd..b2e256e7 100644 --- a/browser/components/NoteItem.styl +++ b/browser/components/NoteItem.styl @@ -321,3 +321,76 @@ body[data-theme="solarized-dark"] .item-bottom-tagList-empty color $ui-inactive-text-color vertical-align middle + +body[data-theme="monokai"] + .root + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + + .item + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + &:hover + transition 0.15s + // background-color alpha($ui-monokai-noteList-backgroundColor, 20%) + color $ui-monokai-text-color + .item-title + .item-title-icon + .item-bottom-time + transition 0.15s + color $ui-monokai-text-color + .item-bottom-tagList-item + transition 0.15s + background-color alpha($ui-monokai-noteList-backgroundColor, 20%) + color $ui-monokai-text-color + &:active + transition 0.15s + background-color $ui-monokai-noteList-backgroundColor + color $ui-monokai-text-color + .item-title + .item-title-icon + .item-bottom-time + transition 0.15s + color $ui-monokai-text-color + .item-bottom-tagList-item + transition 0.15s + background-color alpha($ui-monokai-noteList-backgroundColor, 10%) + color $ui-monokai-text-color + + .item-wrapper + border-color alpha($ui-monokai-button--active-backgroundColor, 60%) + + .item--active + border-color $ui-monokai-borderColor + background-color $ui-monokai-button-backgroundColor + .item-wrapper + border-color transparent + .item-title + .item-title-icon + .item-bottom-time + color $ui-monokai-text-color + .item-bottom-tagList-item + background-color alpha(white, 10%) + color $ui-monokai-text-color + &:hover + // background-color alpha($ui-monokai-button--active-backgroundColor, 60%) + color #c0392b + .item-bottom-tagList-item + background-color alpha(#fff, 20%) + + .item-title + color $ui-inactive-text-color + + .item-title-icon + color $ui-inactive-text-color + + .item-title-empty + color $ui-inactive-text-color + + .item-bottom-tagList-item + background-color alpha($ui-dark-button--active-backgroundColor, 40%) + color $ui-inactive-text-color + + .item-bottom-tagList-empty + color $ui-inactive-text-color + vertical-align middle diff --git a/browser/components/NoteItemSimple.styl b/browser/components/NoteItemSimple.styl index 3097b82c..91c28c2a 100644 --- a/browser/components/NoteItemSimple.styl +++ b/browser/components/NoteItemSimple.styl @@ -212,3 +212,61 @@ body[data-theme="solarized-dark"] .item-simple-right-storageName padding-left 4px opacity 0.4 + +body[data-theme="monokai"] + .root + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + + .item-simple + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + &:hover + transition 0.15s + // background-color alpha($ui-dark-button--active-backgroundColor, 20%) + color $ui-monokai-text-color + .item-simple-title + .item-simple-title-icon + .item-simple-bottom-time + transition 0.15s + color $ui-monokai-text-color + .item-simple-bottom-tagList-item + transition 0.15s + background-color alpha(#fff, 20%) + color $ui-monokai-text-color + &:active + transition 0.15s + background-color $ui-monokai-button--active-backgroundColor + color $ui-monokai-text-color + .item-simple-title + .item-simple-title-icon + .item-simple-bottom-time + transition 0.15s + color $ui-monokai-text-color + .item-simple-bottom-tagList-item + transition 0.15s + background-color alpha(white, 10%) + color $ui-monokai-text-color + + .item-simple--active + border-color $ui-monokai-borderColor + background-color $ui-monokai-button--active-backgroundColor + .item-simple-wrapper + border-color transparent + .item-simple-title + .item-simple-title-icon + .item-simple-bottom-time + color $ui-monokai-text-color + .item-simple-bottom-tagList-item + background-color alpha(white, 10%) + color $ui-monokai-text-color + &:hover + // background-color alpha($ui-dark-button--active-backgroundColor, 60%) + color #c0392b + .item-simple-bottom-tagList-item + background-color alpha(#fff, 20%) +.item-simple-right + float right + .item-simple-right-storageName + padding-left 4px + opacity 0.4 diff --git a/browser/components/RealtimeNotification.styl b/browser/components/RealtimeNotification.styl index 0f77acbb..0365d8c9 100644 --- a/browser/components/RealtimeNotification.styl +++ b/browser/components/RealtimeNotification.styl @@ -41,3 +41,14 @@ body[data-theme="solarized-dark"] background-color $ui-solarized-dark-button-backgroundColor &:hover color #5CB85C + +body[data-theme="monokai"] + .notification-area + background-color none + + .notification-link + color $ui-monokai-text-color + border none + background-color $ui-monokai-button-backgroundColor + &:hover + color #5CB85C \ No newline at end of file diff --git a/browser/components/SideNavFilter.styl b/browser/components/SideNavFilter.styl index 8a9a350d..c1b378b8 100644 --- a/browser/components/SideNavFilter.styl +++ b/browser/components/SideNavFilter.styl @@ -222,4 +222,46 @@ body[data-theme="solarized-dark"] background-color $ui-solarized-dark-button-backgroundColor color $ui-solarized-dark-text-color .menu-button-label - color $ui-solarized-dark-text-color \ No newline at end of file + color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .menu-button + &:active + background-color $ui-monokai-noteList-backgroundColor + color $ui-monokai-text-color + &:hover + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + + .menu-button--active + color $ui-monokai-text-color + background-color $ui-monokai-button-backgroundColor + .menu-button-label + color $ui-monokai-text-color + &:hover + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + .menu-button-label + color $ui-monokai-text-color + + .menu-button-star--active + color $ui-monokai-text-color + background-color $ui-monokai-button-backgroundColor + .menu-button-label + color $ui-monokai-text-color + &:hover + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + .menu-button-label + color $ui-monokai-text-color + + .menu-button-trash--active + color $ui-monokai-text-color + background-color $ui-monokai-button-backgroundColor + .menu-button-label + color $ui-monokai-text-color + &:hover + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + .menu-button-label + color $ui-monokai-text-color \ No newline at end of file diff --git a/browser/components/StorageItem.styl b/browser/components/StorageItem.styl index 842f8d66..ece97008 100644 --- a/browser/components/StorageItem.styl +++ b/browser/components/StorageItem.styl @@ -138,3 +138,22 @@ body[data-theme="solarized-dark"] &:hover color $ui-solarized-dark-text-color background-color $ui-solarized-dark-button-backgroundColor + +body[data-theme="monokai"] + .folderList-item + &:hover + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + &:active + color $ui-monokai-text-color + background-color $ui-monokai-button-backgroundColor + + .folderList-item--active + @extend .folderList-item + color $ui-monokai-text-color + background-color $ui-monokai-button-backgroundColor + &:active + background-color $ui-monokai-button-backgroundColor + &:hover + color $ui-monokai-text-color + background-color $ui-monokai-button-backgroundColor \ No newline at end of file diff --git a/browser/components/TodoListPercentage.styl b/browser/components/TodoListPercentage.styl index 329663f9..6116cd58 100644 --- a/browser/components/TodoListPercentage.styl +++ b/browser/components/TodoListPercentage.styl @@ -47,5 +47,15 @@ body[data-theme="solarized-dark"] .progressBar background-color: #2aa198 + .percentageText + color #fdf6e3 + +body[data-theme="monokai"] + .percentageBar + background-color #f92672 + + .progressBar + background-color: #373831 + .percentageText color #fdf6e3 \ No newline at end of file diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index cc6d7d92..373b321e 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -371,3 +371,30 @@ body[data-theme="solarized-dark"] border-color themeSolarizedDarkTableBorder &:last-child border-right solid 1px themeSolarizedDarkTableBorder + +themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor +themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%) +themeMonokaiTableHead = themeMonokaiTableEven +themeMonokaiTableBorder = themeDarkBorder + +body[data-theme="monokai"] + color $ui-monokai-text-color + border-color themeDarkBorder + background-color $ui-monokai-noteDetail-backgroundColor + table + thead + tr + background-color themeMonokaiTableHead + th + border-color themeMonokaiTableBorder + &:last-child + border-right solid 1px themeMonokaiTableBorder + tbody + tr:nth-child(2n + 1) + background-color themeMonokaiTableOdd + tr:nth-child(2n) + background-color themeMonokaiTableEven + td + border-color themeMonokaiTableBorder + &:last-child + border-right solid 1px themeMonokaiTableBorder \ No newline at end of file diff --git a/browser/main/Detail/Detail.styl b/browser/main/Detail/Detail.styl index d4c4100c..49a634f3 100644 --- a/browser/main/Detail/Detail.styl +++ b/browser/main/Detail/Detail.styl @@ -30,3 +30,10 @@ body[data-theme="solarized-dark"] border-left 1px solid $ui-solarized-dark-borderColor .empty-message color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .root + background-color $ui-monokai-noteDetail-backgroundColor + border-left 1px solid $ui-monokai-borderColor + .empty-message + color $ui-monokai-text-color diff --git a/browser/main/Detail/FolderSelect.styl b/browser/main/Detail/FolderSelect.styl index 31930fe6..e898faa8 100644 --- a/browser/main/Detail/FolderSelect.styl +++ b/browser/main/Detail/FolderSelect.styl @@ -133,3 +133,22 @@ body[data-theme="dark"] color $ui-dark-button--active-color .search-optionList-item-name-surfix color $ui-dark-inactive-text-color + +body[data-theme="monokai"] + .search-optionList + color white + border-color $ui-monokai-borderColor + background-color $ui-monokai-button-backgroundColor + + .search-optionList-item + &:hover + background-color lighten($ui-monokai-button--hover-backgroundColor, 15%) + + .search-optionList-item--active + background-color $ui-monokai-button--active-backgroundColor + color $ui-monokai-button--active-color + &:hover + background-color $ui-monokai-button--active-backgroundColor + color $ui-monokai-button--active-color + .search-optionList-item-name-surfix + color $ui-monokai-inactive-text-color diff --git a/browser/main/Detail/InfoPanel.styl b/browser/main/Detail/InfoPanel.styl index d90dea49..480441bd 100644 --- a/browser/main/Detail/InfoPanel.styl +++ b/browser/main/Detail/InfoPanel.styl @@ -215,3 +215,43 @@ body[data-theme="solarized-dark"] color $ui-dark-inactive-text-color &:hover color $ui-solarized-ark-text-color + +body[data-theme="monokai"] + .control-infoButton-panel + background-color $ui-monokai-noteList-backgroundColor + + .control-infoButton-panel-trash + background-color $ui-monokai-noteList-backgroundColor + + .modification-date + color $ui-monokai-text-color + + .modification-date-desc + color $ui-inactive-text-color + + .infoPanel-defaul-count + color $ui-monokai-text-color + + .infoPanel-sub-count + color $ui-inactive-text-color + + .infoPanel-default + color $ui-monokai-text-color + + .infoPanel-sub + color $ui-inactive-text-color + + .infoPanel-noteLink + background-color alpha($ui-monokai-borderColor, 20%) + color $ui-monokai-text-color + + [id=export-wrap] + button + color $ui-dark-inactive-text-color + &:hover + background-color alpha($ui-monokai-borderColor, 20%) + color $ui-monokai-text-color + p + color $ui-dark-inactive-text-color + &:hover + color $ui-monokai-text-color diff --git a/browser/main/Detail/MarkdownNoteDetail.styl b/browser/main/Detail/MarkdownNoteDetail.styl index ad20f0f2..b27dc80e 100644 --- a/browser/main/Detail/MarkdownNoteDetail.styl +++ b/browser/main/Detail/MarkdownNoteDetail.styl @@ -71,3 +71,8 @@ body[data-theme="solarized-dark"] .root border-left 1px solid $ui-solarized-dark-borderColor background-color $ui-solarized-dark-noteDetail-backgroundColor + +body[data-theme="monokai"] + .root + border-left 1px solid $ui-monokai-borderColor + background-color $ui-monokai-noteDetail-backgroundColor diff --git a/browser/main/Detail/NoteDetailInfo.styl b/browser/main/Detail/NoteDetailInfo.styl index bc3c9462..8d454203 100644 --- a/browser/main/Detail/NoteDetailInfo.styl +++ b/browser/main/Detail/NoteDetailInfo.styl @@ -98,3 +98,7 @@ body[data-theme="solarized-dark"] border-color $ui-solarized-dark-borderColor background-color $ui-solarized-dark-noteDetail-backgroundColor +body[data-theme="monokai"] + .info + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteDetail-backgroundColor \ No newline at end of file diff --git a/browser/main/Detail/SnippetNoteDetail.styl b/browser/main/Detail/SnippetNoteDetail.styl index 789d5186..f8ca48cc 100644 --- a/browser/main/Detail/SnippetNoteDetail.styl +++ b/browser/main/Detail/SnippetNoteDetail.styl @@ -152,4 +152,21 @@ body[data-theme="solarized-dark"] .tabList background-color $ui-solarized-dark-noteDetail-backgroundColor - color $ui-solarized-dark-text-color \ No newline at end of file + color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .root + border-left 1px solid $ui-monokai-borderColor + background-color $ui-monokai-noteDetail-backgroundColor + + .body + background-color $ui-monokai-noteDetail-backgroundColor + + .body .description textarea + background-color $ui-monokai-noteDetail-backgroundColor + color $ui-monokai-text-color + border 1px solid $ui-monokai-borderColor + + .tabList + background-color $ui-monokai-noteDetail-backgroundColor + color $ui-monokai-text-color \ No newline at end of file diff --git a/browser/main/Detail/TagSelect.styl b/browser/main/Detail/TagSelect.styl index 18d4d2e0..3de22a52 100644 --- a/browser/main/Detail/TagSelect.styl +++ b/browser/main/Detail/TagSelect.styl @@ -81,4 +81,20 @@ body[data-theme="solarized-dark"] .newTag border-color none background-color transparent - color $ui-solarized-dark-text-color \ No newline at end of file + color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .tag + background-color $ui-monokai-tag-backgroundColor + + .tag-removeButton + border-color $ui-button--focus-borderColor + background-color transparent + + .tag-label + color $ui-monokai-text-color + + .newTag + border-color none + background-color transparent + color $ui-monokai-text-color diff --git a/browser/main/Detail/ToggleModeButton.styl b/browser/main/Detail/ToggleModeButton.styl index 185a780c..2e7ab5fa 100644 --- a/browser/main/Detail/ToggleModeButton.styl +++ b/browser/main/Detail/ToggleModeButton.styl @@ -56,3 +56,10 @@ body[data-theme="solarized-dark"] .active background-color #1EC38B box-shadow 2px 0px 7px #222222 + +body[data-theme="monokai"] + .control-toggleModeButton + background-color #272822 + .active + background-color #1EC38B + box-shadow 2px 0px 7px #222222 diff --git a/browser/main/Main.js b/browser/main/Main.js index 14a56225..a35aa862 100644 --- a/browser/main/Main.js +++ b/browser/main/Main.js @@ -143,7 +143,8 @@ class Main extends React.Component { const supportedThemes = [ 'dark', 'white', - 'solarized-dark' + 'solarized-dark', + 'monokai' ] if (supportedThemes.indexOf(config.ui.theme) !== -1) { diff --git a/browser/main/NewNoteButton/NewNoteButton.styl b/browser/main/NewNoteButton/NewNoteButton.styl index 81ff7e8d..e8e4b5f0 100644 --- a/browser/main/NewNoteButton/NewNoteButton.styl +++ b/browser/main/NewNoteButton/NewNoteButton.styl @@ -74,4 +74,8 @@ body[data-theme="dark"] body[data-theme="solarized-dark"] .root, .root--expanded - background-color $ui-solarized-dark-noteList-backgroundColor \ No newline at end of file + background-color $ui-solarized-dark-noteList-backgroundColor + +body[data-theme="monokai"] + .root, .root--expanded + background-color $ui-monokai-noteList-backgroundColor diff --git a/browser/main/NoteList/NoteList.styl b/browser/main/NoteList/NoteList.styl index 312f5143..ea261208 100644 --- a/browser/main/NoteList/NoteList.styl +++ b/browser/main/NoteList/NoteList.styl @@ -113,4 +113,28 @@ body[data-theme="solarized-dark"] .control-button--active color $ui-solarized-dark-text-color &:active - color $ui-solarized-dark-text-color \ No newline at end of file + color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .root + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + + .control + background-color $ui-monokai-noteList-backgroundColor + border-color $ui-monokai-borderColor + + .control-sortBy-select + &:hover + transition 0.2s + color $ui-monokai-text-color + + .control-button + color $ui-monokai-inactive-text-color + &:hover + color $ui-monokai-text-color + + .control-button--active + color $ui-monokai-text-color + &:active + color $ui-monokai-text-color diff --git a/browser/main/SideNav/SideNav.styl b/browser/main/SideNav/SideNav.styl index 666ae0cd..ecab70d0 100644 --- a/browser/main/SideNav/SideNav.styl +++ b/browser/main/SideNav/SideNav.styl @@ -117,3 +117,8 @@ body[data-theme="solarized-dark"] .root, .root--folded background-color $ui-solarized-dark-backgroundColor border-right 1px solid $ui-solarized-dark-borderColor + +body[data-theme="monokai"] + .root, .root--folded + background-color $ui-monokai-backgroundColor + border-right 1px solid $ui-monokai-borderColor diff --git a/browser/main/StatusBar/StatusBar.styl b/browser/main/StatusBar/StatusBar.styl index 9f189fec..52cc4b02 100644 --- a/browser/main/StatusBar/StatusBar.styl +++ b/browser/main/StatusBar/StatusBar.styl @@ -69,3 +69,14 @@ body[data-theme="dark"] navDarkButtonColor() border-color $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor + +body[data-theme="monokai"] + navButtonColor() + .zoom + border-color $ui-dark-borderColor + color $ui-monokai-text-color + &:hover + transition 0.15s + color $ui-monokai-active-color + &:active + color $ui-monokai-active-color diff --git a/browser/main/TopBar/TopBar.styl b/browser/main/TopBar/TopBar.styl index 0956571f..7654f66f 100644 --- a/browser/main/TopBar/TopBar.styl +++ b/browser/main/TopBar/TopBar.styl @@ -234,3 +234,25 @@ body[data-theme="solarized-dark"] input background-color $ui-solarized-dark-noteList-backgroundColor color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .root, .root--expanded + background-color $ui-monokai-noteList-backgroundColor + + .control + border-color $ui-monokai-borderColor + .control-search + background-color $ui-monokai-noteList-backgroundColor + + .control-search-icon + absolute top bottom left + line-height 32px + width 35px + color $ui-monokai-inactive-text-color + background-color $ui-monokai-noteList-backgroundColor + + .control-search-input + background-color $ui-monokai-noteList-backgroundColor + input + background-color $ui-monokai-noteList-backgroundColor + color $ui-monokai-text-color diff --git a/browser/main/global.styl b/browser/main/global.styl index 613c7611..7025163f 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -134,4 +134,10 @@ body[data-theme="solarized-dark"] .sortableItemHelper color: $ui-solarized-dark-text-color +body[data-theme="monokai"] + .ModalBase + .modalBack + background-color $ui-monokai-backgroundColor + .sortableItemHelper + color: $ui-monokai-text-color diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 3e1a2162..ee8a57c7 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -135,6 +135,8 @@ function set (updates) { document.body.setAttribute('data-theme', 'white') } else if (newConfig.ui.theme === 'solarized-dark') { document.body.setAttribute('data-theme', 'solarized-dark') + } else if (newConfig.ui.theme === 'monokai') { + document.body.setAttribute('data-theme', 'monokai') } else { document.body.setAttribute('data-theme', 'default') } diff --git a/browser/main/modals/CreateFolderModal.styl b/browser/main/modals/CreateFolderModal.styl index 45f2e852..1b96e123 100644 --- a/browser/main/modals/CreateFolderModal.styl +++ b/browser/main/modals/CreateFolderModal.styl @@ -102,3 +102,29 @@ body[data-theme="solarized-dark"] .control-confirmButton colorSolarizedDarkPrimaryButton() + +body[data-theme="monokai"] + .root + modalMonokai() + width 500px + height 270px + overflow hidden + position relative + + .header + background-color transparent + border-color $ui-dark-borderColor + color $ui-monokai-text-color + + .control-folder-label + color $ui-monokai-text-color + + .control-folder-input + border 1px solid $ui-input--create-folder-modal + color white + + .description + color $ui-inactive-text-color + + .control-confirmButton + colorMonokaiPrimaryButton() diff --git a/browser/main/modals/NewNoteModal.styl b/browser/main/modals/NewNoteModal.styl index 748ab88c..db14133f 100644 --- a/browser/main/modals/NewNoteModal.styl +++ b/browser/main/modals/NewNoteModal.styl @@ -81,3 +81,19 @@ body[data-theme="solarized-dark"] .description color $ui-solarized-dark-text-color +body[data-theme="monokai"] + .root + background-color transparent + + .header + color $ui-monokai-text-color + + .control-button + border-color $ui-monokai-borderColor + color $ui-monokai-text-color + background-color transparent + &:focus + colorDarkPrimaryButton() + + .description + color $ui-monokai-text-color diff --git a/browser/main/modals/PreferencesModal/ConfigTab.styl b/browser/main/modals/PreferencesModal/ConfigTab.styl index f6f7ace9..0e5f81fb 100644 --- a/browser/main/modals/PreferencesModal/ConfigTab.styl +++ b/browser/main/modals/PreferencesModal/ConfigTab.styl @@ -133,6 +133,11 @@ colorSolarizedDarkControl() background-color $ui-solarized-dark-button-backgroundColor color $ui-solarized-dark-text-color +colorMonokaiControl() + border none + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + body[data-theme="dark"] .root @@ -189,4 +194,29 @@ body[data-theme="solarized-dark"] select, .group-section-control-input colorSolarizedDarkControl() +body[data-theme="monokai"] + .root + color $ui-monokai-text-color + .group-header + color $ui-monokai-text-color + border-color $ui-monokai-borderColor + + .group-header2 + color $ui-monokai-text-color + + .group-section-control-input + border-color $ui-monokai-borderColor + + .group-control + border-color $ui-monokai-borderColor + .group-control-leftButton + colorDarkDefaultButton() + border-color $ui-monokai-borderColor + .group-control-rightButton + colorMonokaiPrimaryButton() + .group-hint + colorMonokaiControl() + .group-section-control + select, .group-section-control-input + colorMonokaiControl() diff --git a/browser/main/modals/PreferencesModal/Crowdfunding.styl b/browser/main/modals/PreferencesModal/Crowdfunding.styl index 930c33f0..3d4af539 100644 --- a/browser/main/modals/PreferencesModal/Crowdfunding.styl +++ b/browser/main/modals/PreferencesModal/Crowdfunding.styl @@ -33,4 +33,10 @@ body[data-theme="solarized-dark"] .root color $ui-solarized-dark-text-color p - color $ui-solarized-dark-text-color \ No newline at end of file + color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .root + color $ui-monokai-text-color + p + color $ui-monokai-text-color diff --git a/browser/main/modals/PreferencesModal/FolderItem.styl b/browser/main/modals/PreferencesModal/FolderItem.styl index acc4cbfb..8bcf2b02 100644 --- a/browser/main/modals/PreferencesModal/FolderItem.styl +++ b/browser/main/modals/PreferencesModal/FolderItem.styl @@ -126,3 +126,26 @@ body[data-theme="solarized-dark"] .folderItem-right-dangerButton colorSolarizedDarkPrimaryButton() + +body[data-theme="monokai"] + .folderItem + &:hover + background-color $ui-monokai-button-backgroundColor + + .folderItem-left-danger + color $danger-color + + .folderItem-left-key + color $ui-dark-inactive-text-color + + .folderItem-left-colorButton + colorMonokaiPrimaryButton() + + .folderItem-right-button + colorMonokaiPrimaryButton() + + .folderItem-right-confirmButton + colorMonokaiPrimaryButton() + + .folderItem-right-dangerButton + colorMonokaiPrimaryButton() diff --git a/browser/main/modals/PreferencesModal/InfoTab.styl b/browser/main/modals/PreferencesModal/InfoTab.styl index cc04a10f..491fc4d4 100644 --- a/browser/main/modals/PreferencesModal/InfoTab.styl +++ b/browser/main/modals/PreferencesModal/InfoTab.styl @@ -68,3 +68,10 @@ body[data-theme="solarized-dark"] .list a color $ui-solarized-dark-active-color + +body[data-theme="monokai"] + .root + color $ui-monokai-text-color +.list + a + color $ui-monokai-active-color diff --git a/browser/main/modals/PreferencesModal/PreferencesModal.styl b/browser/main/modals/PreferencesModal/PreferencesModal.styl index 57b5dbad..d21f6c28 100644 --- a/browser/main/modals/PreferencesModal/PreferencesModal.styl +++ b/browser/main/modals/PreferencesModal/PreferencesModal.styl @@ -116,3 +116,26 @@ body[data-theme="solarized-dark"] &:hover color white +body[data-theme="monokai"] + .root + background-color transparent + .top-bar + background-color transparent + border-color $ui-monokai-borderColor + p + color $ui-monokai-text-color + .nav + background-color transparent + border-color $ui-monokai-borderColor + .nav-button + background-color transparent + color $ui-monokai-text-color + &:hover + color $ui-monokai-text-color + + .nav-button--active + @extend .nav-button + color $ui-monokai-button--active-color + background-color $ui-monokai-button--active-backgroundColor + &:hover + color white diff --git a/browser/main/modals/PreferencesModal/StoragesTab.styl b/browser/main/modals/PreferencesModal/StoragesTab.styl index 230f0aed..9804d7e7 100644 --- a/browser/main/modals/PreferencesModal/StoragesTab.styl +++ b/browser/main/modals/PreferencesModal/StoragesTab.styl @@ -199,3 +199,40 @@ body[data-theme="solarized-dark"] colorDarkDefaultButton() border-color $ui-solarized-dark-borderColor +body[data-theme="monokai"] + .root + color $ui-monokai-text-color + + .folderList-item + border-bottom $ui-monokai-borderColor + + .folderList-empty + color $ui-monokai-text-color + + .list-empty + color $ui-monokai-text-color + .list-control-addStorageButton + border-color $ui-monokai-button-backgroundColor + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + + .addStorage-header + color $ui-monokai-text-color + border-color $ui-monokai-borderColor + + .addStorage-body-section-name-input + border-color $$ui-monokai-borderColor + + .addStorage-body-section-type-description + color $ui-monokai-text-color + + .addStorage-body-section-path-button + colorPrimaryButton() + .addStorage-body-control + border-color $ui-monokai-borderColor + + .addStorage-body-control-createButton + colorDarkPrimaryButton() + .addStorage-body-control-cancelButton + colorDarkDefaultButton() + border-color $ui-monokai-borderColor diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index a092129a..d7f0cc65 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -170,6 +170,7 @@ class UiTab extends React.Component { + diff --git a/browser/styles/index.styl b/browser/styles/index.styl index 6fb208b1..7d32e77a 100644 --- a/browser/styles/index.styl +++ b/browser/styles/index.styl @@ -118,6 +118,16 @@ colorSolarizedDarkPrimaryButton() &:active:hover background-color $dark-primary-button-background--active +colorMonokaiPrimaryButton() + color $ui-monokai-text-color + background-color $ui-monokai-button-backgroundColor + border none + &:hover + background-color $dark-primary-button-background--hover + &:active + &:active:hover + background-color $dark-primary-button-background--active + // Danger button(Brand color) $danger-button-background = #c9302c @@ -348,3 +358,29 @@ modalSolarizedDark() background-color $ui-solarized-dark-backgroundColor overflow hidden border-radius $modal-border-radius + +/******* Monokai theme ********/ +$ui-monokai-backgroundColor = #272822 +$ui-monokai-noteList-backgroundColor = #272822 +$ui-monokai-noteDetail-backgroundColor = #272822 + +$ui-monokai-text-color = #f8f8f2 +$ui-monokai-active-color = #f92672 + +$ui-monokai-borderColor = #373831 + +$ui-monokai-tag-backgroundColor = #f92672 + +$ui-monokai-button-backgroundColor = #373831 +$ui-monokai-button--active-color = white +$ui-monokai-button--active-backgroundColor = #f92672 +$ui-monokai-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%) +$ui-monokai-button--focus-borderColor = lighten(#369DCD, 25%) + +modalmonokai() + position relative + z-index $modal-z-index + width 100% + background-color $ui-monokai-backgroundColor + overflow hidden + border-radius $modal-border-radius \ No newline at end of file From bf3f5a5971d5705da141b69d672e41b577387eaa Mon Sep 17 00:00:00 2001 From: ehhc Date: Fri, 27 Apr 2018 08:51:33 +0200 Subject: [PATCH 10/51] Fixes #1827 -> include attachments in HTML exports of notes --- browser/components/MarkdownEditor.js | 1 + browser/components/MarkdownPreview.js | 10 +- browser/components/MarkdownSplitEditor.js | 1 + .../main/lib/dataApi/attachmentManagement.js | 40 ++++++++ browser/main/lib/dataApi/exportNote.js | 17 +--- tests/dataApi/attachmentManagement.test.js | 95 +++++++++++++++++++ 6 files changed, 147 insertions(+), 17 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 313c6f90..2c98f18e 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -294,6 +294,7 @@ class MarkdownEditor extends React.Component { onCheckboxClick={(e) => this.handleCheckboxClick(e)} showCopyNotification={config.ui.showCopyNotification} storagePath={storage.path} + noteKey={noteKey} /> ) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index aa920975..d1d5a702 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -211,8 +211,9 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber) - const body = this.markdown.render(escapeHtmlCharacters(noteContent)) + let body = this.markdown.render(escapeHtmlCharacters(noteContent)) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] + const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) files.forEach((file) => { file = file.replace('file://', '') @@ -221,6 +222,13 @@ export default class MarkdownPreview extends React.Component { dst: 'css' }) }) + attachmentsAbsolutePaths.forEach((attachment) => { + exportTasks.push({ + src: attachment, + dst: attachmentManagement.DESTINATION_FOLDER + }) + }) + body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey) let styles = '' files.forEach((file) => { diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index d82d4da3..27505a5a 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -139,6 +139,7 @@ class MarkdownSplitEditor extends React.Component { onScroll={this.handleScroll.bind(this)} showCopyNotification={config.ui.showCopyNotification} storagePath={storage.path} + noteKey={noteKey} /> ) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 6d4d7406..e696f4ff 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -3,6 +3,7 @@ const fs = require('fs') const path = require('path') const findStorage = require('browser/lib/findStorage') const mdurl = require('mdurl') +const escapeStringRegexp = require('escape-string-regexp') const STORAGE_FOLDER_PLACEHOLDER = ':storage' const DESTINATION_FOLDER = 'attachments' @@ -153,12 +154,51 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem reader.readAsDataURL(blob) } +/** + * @description Returns all attachment paths of the given markdown + * @param {String} markdownContent content in which the attachment paths should be found + * @returns {String[]} Array of the relativ paths (starting with :storage) of the attachments of the given markdown + */ +function getAttachmentsInContent (markdownContent) { + let preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) + let 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) +} + +/** + * @description Returns an array of the absolute paths of the attachments referenced in the given markdown code + * @param {String} markdownContent content in which the attachment paths should be found + * @param {String} storagePath path of the current storage + * @returns {String[]} Absolute paths of the referenced attachments + */ +function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) { + let temp = getAttachmentsInContent(markdownContent) + let result = [] + for (let relativePath of temp) { + result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))) + } + return result +} + +/** + * @description Deletes all :storage and noteKey references from the given input. + * @param input Input in which the references should be deleted + * @param noteKey Key of the current note + * @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) +} + module.exports = { copyAttachment, fixLocalURLS, generateAttachmentMarkdown, handleAttachmentDrop, handlePastImageEvent, + getAttachmentsInContent, + getAbsolutePathsOfAttachmentsInContent, + removeStorageAndNoteReferences, STORAGE_FOLDER_PLACEHOLDER, DESTINATION_FOLDER } diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js index 71f7d017..ffd30b8f 100755 --- a/browser/main/lib/dataApi/exportNote.js +++ b/browser/main/lib/dataApi/exportNote.js @@ -1,6 +1,5 @@ import copyFile from 'browser/main/lib/dataApi/copyFile' import { findStorage } from 'browser/lib/findStorage' -import filenamify from 'filenamify' const fs = require('fs') const path = require('path') @@ -29,21 +28,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) { throw new Error('Storage path is not found') } - let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => { - dstFilename = filenamify(dstFilename, {replacement: '_'}) - if (!path.extname(dstFilename)) { - dstFilename += path.extname(srcFilename) - } - - const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename) - - exportTasks.push({ - src: path.join(IMAGES_FOLDER_NAME, srcFilename), - dst: dstRelativePath - }) - - return `![${dstFilename}](${dstRelativePath})` - }) + let exportedData = noteContent if (outputFormatter) { exportedData = outputFormatter(exportedData, exportTasks) diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index c90e1961..78f77405 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -166,3 +166,98 @@ it('should test that generateAttachmentMarkdown works correct both with previews actual = systemUnderTest.generateAttachmentMarkdown(fileName, path, false) expect(actual).toEqual(expected) }) + +it('should test that getAttachmentsInContent finds all attachments', function () { + let 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' + + '' + let actual = systemUnderTest.getAttachmentsInContent(testInput) + let expected = [':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.6r4zdgc22xp', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.q2i4iw0fyx', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\d6c5ee92.jpg'] + expect(actual).toEqual(expect.arrayContaining(expected)) +}) + +it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () { + let dummyStoragePath = 'dummyStoragePath' + let 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' + + '' + let actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath) + let expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', + dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', + dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] + expect(actual).toEqual(expect.arrayContaining(expected)) +}) + +it('should remove the all ":storage" and noteKey references', function () { + let storageFolder = systemUnderTest.DESTINATION_FOLDER + let noteKey = 'noteKey' + let 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' + + '' + let storagePath = '<>' + let expectedOutput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

Headline

\n' + + '

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

\n' + + '

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

\n' + + '

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

\n' + + ' \n' + + '' + let actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) + expect(actual).toEqual(expectedOutput) +}) From 239edb06055f867e74a9cb71e32a02db6e0e46e7 Mon Sep 17 00:00:00 2001 From: William Grant Date: Fri, 27 Apr 2018 09:01:41 +0200 Subject: [PATCH 11/51] tweaked styling --- browser/components/NoteItem.styl | 2 +- browser/main/Detail/FolderSelect.styl | 7 +++++++ browser/main/Detail/TagSelect.styl | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/browser/components/NoteItem.styl b/browser/components/NoteItem.styl index b2e256e7..017ef6d0 100644 --- a/browser/components/NoteItem.styl +++ b/browser/components/NoteItem.styl @@ -358,7 +358,7 @@ body[data-theme="monokai"] color $ui-monokai-text-color .item-wrapper - border-color alpha($ui-monokai-button--active-backgroundColor, 60%) + border-color alpha($ui-monokai-button-backgroundColor, 60%) .item--active border-color $ui-monokai-borderColor diff --git a/browser/main/Detail/FolderSelect.styl b/browser/main/Detail/FolderSelect.styl index e898faa8..cfdc2734 100644 --- a/browser/main/Detail/FolderSelect.styl +++ b/browser/main/Detail/FolderSelect.styl @@ -135,6 +135,13 @@ body[data-theme="dark"] color $ui-dark-inactive-text-color body[data-theme="monokai"] + .root + color $ui-dark-text-color + &:hover + color white + background-color $ui-monokai-button--hover-backgroundColor + border-color $ui-monokai-borderColor + .search-optionList color white border-color $ui-monokai-borderColor diff --git a/browser/main/Detail/TagSelect.styl b/browser/main/Detail/TagSelect.styl index 3de22a52..0ff4c6a3 100644 --- a/browser/main/Detail/TagSelect.styl +++ b/browser/main/Detail/TagSelect.styl @@ -85,7 +85,7 @@ body[data-theme="solarized-dark"] body[data-theme="monokai"] .tag - background-color $ui-monokai-tag-backgroundColor + background-color $ui-monokai-button-backgroundColor .tag-removeButton border-color $ui-button--focus-borderColor From 99e706bcd2276af00d62f6b641c4508079bd4bd6 Mon Sep 17 00:00:00 2001 From: ehhc Date: Fri, 27 Apr 2018 09:05:47 +0200 Subject: [PATCH 12/51] Enable yarn on travis + fix broken test --- .eslintrc | 3 +++ .travis.yml | 1 + .../TagListItem.snapshot.test.js.snap | 4 +--- tests/dataApi/attachmentManagement.test.js | 14 +++++++------- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.eslintrc b/.eslintrc index a460b507..1709c9d8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,5 +19,8 @@ "FileReader": true, "localStorage": true, "fetch": true + }, + "env": { + "jest": true } } diff --git a/.travis.yml b/.travis.yml index c68d1063..ed22d242 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ node_js: - 6 script: - npm run lint && npm run test + - yarn jest - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi' after_success: - openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv diff --git a/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap index 0521ca0e..80293ab4 100644 --- a/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap +++ b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap @@ -17,9 +17,7 @@ exports[`TagListItem renders correctly 1`] = ` # Test - - + /> diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index 78f77405..e13b2a55 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -142,13 +142,13 @@ 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' + '' @@ -187,7 +187,7 @@ it('should test that getAttachmentsInContent finds all attachments', function () ' \n' + '' let actual = systemUnderTest.getAttachmentsInContent(testInput) - let expected = [':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.6r4zdgc22xp', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.q2i4iw0fyx', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\d6c5ee92.jpg'] + let expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] expect(actual).toEqual(expect.arrayContaining(expected)) }) @@ -248,13 +248,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' + '' From e9de8f42e5da38f9793742f2b36218625bc6033f Mon Sep 17 00:00:00 2001 From: ehhc Date: Fri, 27 Apr 2018 09:16:30 +0200 Subject: [PATCH 13/51] Fix eslinter --- .../main/lib/dataApi/attachmentManagement.js | 18 ++++++------- browser/main/lib/dataApi/exportNote.js | 4 --- tests/dataApi/attachmentManagement.test.js | 25 +++++++++---------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index e696f4ff..c2e7f6d6 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -105,8 +105,8 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { const fileType = file['type'] copyAttachment(filePath, storageKey, noteKey).then((fileName) => { - let showPreview = fileType.startsWith('image') - let imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) + const showPreview = fileType.startsWith('image') + const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) codeEditor.insertAttachmentMd(imageMd) }) } @@ -140,7 +140,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) createAttachmentDestinationFolder(targetStorage.path, noteKey) - let imageName = `${uniqueSlug()}.png` + const imageName = `${uniqueSlug()}.png` const imagePath = path.join(destinationDir, imageName) reader.onloadend = function () { @@ -148,7 +148,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem base64data += base64data.replace('+', ' ') const binaryData = new Buffer(base64data, 'base64').toString('binary') fs.writeFile(imagePath, binaryData, 'binary') - let imageMd = generateAttachmentMarkdown(imageName, imagePath, true) + const imageMd = generateAttachmentMarkdown(imageName, imagePath, true) codeEditor.insertAttachmentMd(imageMd) } reader.readAsDataURL(blob) @@ -160,8 +160,8 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem * @returns {String[]} Array of the relativ paths (starting with :storage) of the attachments of the given markdown */ function getAttachmentsInContent (markdownContent) { - let preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) - let 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 preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) + const regexp = new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + '([a-zA-Z0-9]|-)+' + escapeStringRegexp(path.sep) + '[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?', 'g') return preparedInput.match(regexp) } @@ -172,9 +172,9 @@ function getAttachmentsInContent (markdownContent) { * @returns {String[]} Absolute paths of the referenced attachments */ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) { - let temp = getAttachmentsInContent(markdownContent) - let result = [] - for (let relativePath of temp) { + const temp = getAttachmentsInContent(markdownContent) + const result = [] + for (const relativePath of temp) { result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))) } return result diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js index ffd30b8f..e4fec5f4 100755 --- a/browser/main/lib/dataApi/exportNote.js +++ b/browser/main/lib/dataApi/exportNote.js @@ -4,10 +4,6 @@ import { findStorage } from 'browser/lib/findStorage' const fs = require('fs') const path = require('path') -const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi -// TODO: ehhc: check this -> attachmentManagement -const IMAGES_FOLDER_NAME = 'images' - /** * Export note together with images * diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index e13b2a55..d58a8eb8 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -168,7 +168,7 @@ it('should test that generateAttachmentMarkdown works correct both with previews }) it('should test that getAttachmentsInContent finds all attachments', function () { - let testInput = + const testInput = '\n' + ' \n' + ' //header\n' + @@ -186,14 +186,14 @@ it('should test that getAttachmentsInContent finds all attachments', function () '

\n' + ' \n' + '' - let actual = systemUnderTest.getAttachmentsInContent(testInput) - let expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] + const actual = systemUnderTest.getAttachmentsInContent(testInput) + const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] expect(actual).toEqual(expect.arrayContaining(expected)) }) it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () { - let dummyStoragePath = 'dummyStoragePath' - let testInput = + const dummyStoragePath = 'dummyStoragePath' + const testInput = '\n' + ' \n' + ' //header\n' + @@ -211,17 +211,17 @@ it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute '

\n' + ' \n' + '' - let actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath) - let expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', + 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', dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] expect(actual).toEqual(expect.arrayContaining(expected)) }) it('should remove the all ":storage" and noteKey references', function () { - let storageFolder = systemUnderTest.DESTINATION_FOLDER - let noteKey = 'noteKey' - let testInput = + const storageFolder = systemUnderTest.DESTINATION_FOLDER + const noteKey = 'noteKey' + const testInput = '\n' + ' \n' + ' //header\n' + @@ -239,8 +239,7 @@ it('should remove the all ":storage" and noteKey references', function () { '

\n' + ' \n' + '' - let storagePath = '<>' - let expectedOutput = + const expectedOutput = '\n' + ' \n' + ' //header\n' + @@ -258,6 +257,6 @@ it('should remove the all ":storage" and noteKey references', function () { '

\n' + ' \n' + '' - let actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) + const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) expect(actual).toEqual(expectedOutput) }) From dc60be404aeedf18aec932803a09791e47e83248 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Mon, 30 Apr 2018 22:30:41 +0200 Subject: [PATCH 14/51] select next note after tranfer note(s) to another folder --- browser/main/NoteList/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index e8c09f65..07e98670 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -460,7 +460,7 @@ class NoteList extends React.Component { const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) const noteData = JSON.stringify(selectedNotes) e.dataTransfer.setData('note', noteData) - this.setState({ selectedNoteKeys: [] }) + this.selectNextNote() } handleNoteContextMenu (e, uniqueKey) { From 2d0f7589eaa1c6ecc47624fcb67ac30a6589c887 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Mon, 30 Apr 2018 22:34:15 +0200 Subject: [PATCH 15/51] select proper notes for dragging When the dragged target note is included in the selected notes, keeps the current selected notes, otherwise, replace the selected notes by the dragged target --- browser/main/NoteList/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 07e98670..876de0c0 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -455,7 +455,14 @@ class NoteList extends React.Component { } handleDragStart (e, note) { - const { selectedNoteKeys } = this.state + let { selectedNoteKeys } = this.state + const noteKey = getNoteKey(note) + + if (!selectedNoteKeys.includes(noteKey)) { + selectedNoteKeys = [] + selectedNoteKeys.push(noteKey) + } + const notes = this.notes.map((note) => Object.assign({}, note)) const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) const noteData = JSON.stringify(selectedNotes) From ab038b1f311103dc1407f5b14e87e758e3cf6bc2 Mon Sep 17 00:00:00 2001 From: ozone <802146+o-3@users.noreply.github.com> Date: Fri, 4 May 2018 00:44:22 -0400 Subject: [PATCH 16/51] Add configuration to render line breaks as
or not --- browser/components/MarkdownEditor.js | 1 + browser/components/MarkdownPreview.js | 12 ++++++++---- browser/components/MarkdownSplitEditor.js | 1 + browser/lib/markdown.js | 2 +- browser/main/lib/ConfigManager.js | 1 + browser/main/modals/PreferencesModal/UiTab.js | 11 +++++++++++ tests/fixtures/markdowns.js | 5 ++++- tests/lib/markdown-test.js | 9 +++++++++ tests/lib/snapshots/markdown-test.js.md | 14 ++++++++++++++ tests/lib/snapshots/markdown-test.js.snap | Bin 1665 -> 3060 bytes 10 files changed, 50 insertions(+), 6 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 2c98f18e..2bd5d951 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -283,6 +283,7 @@ class MarkdownEditor extends React.Component { indentSize={editorIndentSize} scrollPastEnd={config.preview.scrollPastEnd} smartQuotes={config.preview.smartQuotes} + breaks={config.preview.breaks} sanitize={config.preview.sanitize} ref='preview' onContextMenu={(e) => this.handleContextMenu(e)} diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 058dce19..9d457238 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -145,10 +145,11 @@ export default class MarkdownPreview extends React.Component { } initMarkdown () { - const { smartQuotes, sanitize } = this.props + const { smartQuotes, sanitize, breaks } = this.props this.markdown = new Markdown({ typographer: smartQuotes, - sanitize + sanitize, + breaks }) } @@ -340,7 +341,9 @@ export default class MarkdownPreview extends React.Component { componentDidUpdate (prevProps) { if (prevProps.value !== this.props.value) this.rewriteIframe() - if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) { + if (prevProps.smartQuotes !== this.props.smartQuotes || + prevProps.sanitize !== this.props.sanitize || + prevProps.breaks !== this.props.breaks) { this.initMarkdown() this.rewriteIframe() } @@ -599,5 +602,6 @@ MarkdownPreview.propTypes = { value: PropTypes.string, showCopyNotification: PropTypes.bool, storagePath: PropTypes.string, - smartQuotes: PropTypes.bool + smartQuotes: PropTypes.bool, + breaks: PropTypes.bool } diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 27505a5a..2bee5c24 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -131,6 +131,7 @@ class MarkdownSplitEditor extends React.Component { lineNumber={config.preview.lineNumber} scrollPastEnd={config.preview.scrollPastEnd} smartQuotes={config.preview.smartQuotes} + breaks={config.preview.breaks} sanitize={config.preview.sanitize} ref='preview' tabInde='0' diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 1ef488a7..e5923790 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -25,7 +25,7 @@ class Markdown { linkify: true, html: true, xhtmlOut: true, - breaks: true, + breaks: config.preview.breaks, highlight: function (str, lang) { const delimiter = ':' const langInfo = lang.split(delimiter) diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index ee8a57c7..166e7181 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -55,6 +55,7 @@ export const DEFAULT_CONFIG = { latexBlockClose: '$$', scrollPastEnd: false, smartQuotes: true, + breaks: true, sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE' }, blog: { diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index a607f548..42f1503c 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -96,6 +96,7 @@ class UiTab extends React.Component { latexBlockClose: this.refs.previewLatexBlockClose.value, scrollPastEnd: this.refs.previewScrollPastEnd.checked, smartQuotes: this.refs.previewSmartQuotes.checked, + breaks: this.refs.previewBreaks.checked, sanitize: this.refs.previewSanitize.value } } @@ -475,6 +476,16 @@ class UiTab extends React.Component { Enable smart quotes +
+ +
diff --git a/tests/fixtures/markdowns.js b/tests/fixtures/markdowns.js index 8db35485..69e335e0 100644 --- a/tests/fixtures/markdowns.js +++ b/tests/fixtures/markdowns.js @@ -48,10 +48,13 @@ const checkboxes = ` const smartQuotes = 'This is a "QUOTE".' +const breaks = 'This is the first line.\nThis is the second line.' + export default { basic, codeblock, katex, checkboxes, - smartQuotes + smartQuotes, + breaks } diff --git a/tests/lib/markdown-test.js b/tests/lib/markdown-test.js index b2a81fdf..73b68799 100644 --- a/tests/lib/markdown-test.js +++ b/tests/lib/markdown-test.js @@ -34,3 +34,12 @@ test('Markdown.render() should text with quotes correctly', t => { const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes) t.snapshot(renderedNonSmartQuotes) }) + +test('Markdown.render() should render line breaks correctly', t => { + const renderedBreaks = md.render(markdownFixtures.breaks) + t.snapshot(renderedBreaks) + + const newmd = new Markdown({ breaks: false }) + const renderedNonBreaks = newmd.render(markdownFixtures.breaks) + t.snapshot(renderedNonBreaks) +}) diff --git a/tests/lib/snapshots/markdown-test.js.md b/tests/lib/snapshots/markdown-test.js.md index d4f0469e..0c3e317b 100644 --- a/tests/lib/snapshots/markdown-test.js.md +++ b/tests/lib/snapshots/markdown-test.js.md @@ -73,4 +73,18 @@ Generated by [AVA](https://ava.li). > Snapshot 2 `

This is a "QUOTE".

␊ + + +## Markdown.render() should render line breaks correctly + +> Snapshot 1 + + `

This is the first line.
␊ + This is the second line.

␊ ` + +> Snapshot 2 + + `

This is the first line.␊ + This is the second line.

␊ + ` \ No newline at end of file diff --git a/tests/lib/snapshots/markdown-test.js.snap b/tests/lib/snapshots/markdown-test.js.snap index 71ff221d9f61036d05dda745e42bdb820e08eda5..1dc64bba756effea535593a01693be463c95a26a 100644 GIT binary patch literal 3060 zcmZ8jTT@e46b^z71V%0aBm@YAt0=c{Q6LCGAPE;y10<1)2DwC#Yc7ZoV@KPW&gesZ zw6=CSbz0kL`_N7woN;{6b{u@rGVM&A4&y^RZJ*k|(C=fNo~X{Z*80}o=bY@l*0(p> zMr?(yMc;D3zZ5JC*XiOk=Dgu$`>-i>@!y9JW?+Y!D$-4eiJIMidQf*CTwZq${P^Q* z6@F%`2S-P2Aa9_oUIw1rdVB-y&-ZRRLW3Y-Sa1z)WEgG=HVI2lFvY$Rzf^DxW928| z1h$k!v$^y5C1ftE4DoHCl|K{VLdvx=0w3Ls(P{(uikgEgND_Ny`!2UB*g!~utw-T} z3FSvyHeS91S~JEOAQ0f<^CW?9GQ#cLf#VxzV1oTMDh1&~z`pZvscX5C!Sfw6?6Nun zaul`-t7(O6fT`d<&hdF*0yZZUev!`tk8Z(5g!7|57{8?=nN5iB8^JlQW;*I|b<1pq z1o$$QRe_C5c348j+*x(D2aIy*?=prEjwD&LGx{v^pmjyX%15S#N(@`~Fa%t9Eg<2` ztGfF96Np?5FkiC$F@mmcsmXH?q=#B8ruudrqQLJ&EXk6jE1wylU4qk%z$vQ?r0hfD zr=UWC3$q9b`EbC~``!b>9LtDY$|)G-NQs<=b;3$5E6zAX!1?#hciY@!l;QK+9)4Hb zFEe8ZN{)(8(7jK5`pEhO7@$%=RfxipWV6rSY=sI?d0^*gxw zyg985vIYQ4s%tc|jsn(qzfirKn-b(B1U7ien_z8B0bl;2y#O&=QdgRvV#|%!PUi*N zSxBG)W)#Zx7D=>tf(A%G3pzT$z`_*yo+eG7GQy7QAm!|50~%?|-iMH1Wh zeKK$ep%+%qeVX~Qz&cw5k_7Y0KyYdeuPsk)E98_BHZ98o0pUT$5jIqOr22icAj8?3BjKi&r2oz&y}xa z@Y(wjkd@d?!<1Jvz^)aB~aoid4bJi{Ywa^}pPM7x1Cts!9sp=wh+0JhlK8)&^Vg=o!2j zaCTULvi|>1R2sM@`Q+iYBh~)g0&T@#*FQKuz|ytAxc6Q0DaG+-QR%h3zb0&O0)AJw zt{fT}a&(y}4S?HKp`52aN(4B>URi{DS%xlNw;dFxHq0a0o6|rI1AZz6I47aBd7)8Q zyaaeEm*7Z;q=rCQTg&RuD(5|uMtY{%OA}AYk)>fYfkHEIswnEs7a+)`} z;!2o6DNG*KH8!3#Uup=;1m#uCMbxk$iClCQtSOge6Yw&m;FbqV*%8WwR}c>+@axyy zB*0gP6fw8+?FHqclIf?PPWjsV!B4yw>h>DYQH&~ol(9Oev?v1ovsHz_dWZZxg#8I`2Qh`6s}!fhUNPb!`D$kZgy)Tu)3@%Yo%QL&P?#%K7&hf!wEX(qOD**-zdSj|z(*^r(YELW%;Au~9X# z6dCU^9to}}#D?wO))W{}k_2bZgfHree+1ehJBwkKkrsUX^jKab`VMy^ON8c?<=c sJEDuj5iorNuXDfw(>2DrGG?=~fq)AZdh)HYd%_4tl>>WV>w1mmKX*}&Ul>7wp3L7 z^a8XXq#h7$#f@VH;)Fn4xNtyJLP7{3q@Lger*cB@X6$u#x0|1mmJ^m@&%Sx@dw=uZ zo4l)O+JoA6eNTLP^ZRp`3!nUK#(yn7-lNIq+BQw|UikRJlUI6gymtHAs~6`Ud8tQH z-|o}2$G)umTDf`U{MT>1x$E|&+1)*g`qQJDw)kh?@7u+#FFyMA)K}Xd{%wDcqF&#j zX=g5-ib7|{jc=C!{^6mEuRqnRs6VEEyLM_?ueMF;?s-7l*?W0g*)5eePZQ5!J~0F{CK-!Fzy#5)l!^pH8FhErbPLw@vRiD0DJC#e9(8JlOSnQJRVoZ0 z^IDbF1EYU7-#$``y; zan(592pk$LC`%{7(KwVjh8>jdpgq_R$5FBuZCR^CHKt{LCLGQnUehG12^J~?jxd{g z#DD-qx^AjTQ+ZIS!P<+&=d<`EfTHsocX963C@BA ztI1O~Q$kyvZ9Os1kcQ1LhupY~3nvy3j3y0bw0oh($6;j9z?gwDX58>ul_*t`j%a`q zI+FBzk$8bujDyewJlP1~Xsza$2$^TtM{aXp>OUUPFoeA%+L-~SDpi*w^Nw;aj3o6w z!3A~TC{1w3rOTwX^e1#H-9_oH>;fOG*9E{qn~I#*FpK`N!_USey7fG%%HB5 zH;9gGa9jzf2=Ub(@-m6c4D5J_gDrtM!7A94E1Sqhd0Pi;FZjKQ>av~`kD97?AO z^ZhuGjV}#&X6V@0{_nEn+*KK>C{XI!_+5k#-K zI}g1ofJvoW%~fs@idnL>Gs;*VjbRk>7BFKWmYzf%pe+niEKp*)Rczv>A7xo8vIrQ< zn9CWnNF0GGXm6JLz2o4eWClrati#)<``UEZuplc&l}4e58y$A3y>2wPUI^FsU`sB5 zf%rIpqKtsRSsmWga>^#W%RaLzuwsf9rZj5#*(%CbOWBHNE0L}e)F=)W6{pld#>kfu zO1GG`FICoLwbx)Z6Ug*~0|*?h4KoRdg6ha9vVVbHz}ha3Ij3A1!fl4xcHa zeaJpjJP(H}6PL+a4ThP7r_1q3!srSJBEpM^{+V6_LSCC?{fl2v2vf4v^o@S z$l-6u(Y0%|a;#x-eqgASn7QyF2?G&)R3-rhzUy>wQ(S!3nbeKSBS+3EKG6ex3MCYu^2uh6o(h z<{+qt1o6Q}8iX)?L?Z;J9hW#Yo2?`l%;qnnsKYfBoeUI#RI^LoT_L8z?Z^WPd8!Tf zjtgMV{&LuQ0nWpf)1~k)RD>|Pb>YM3P98fkbL+xY_}?(>kSwkvxo0V6Vq6I)%S8Sk Lf>1$nsS*GH)B7ad From 83a9e5489677af66005a630e16a3da72e93dabc6 Mon Sep 17 00:00:00 2001 From: ozone <802146+o-3@users.noreply.github.com> Date: Fri, 4 May 2018 01:22:55 -0400 Subject: [PATCH 17/51] Fix snapshot --- tests/lib/snapshots/markdown-test.js.md | 28 +++++++++++----------- tests/lib/snapshots/markdown-test.js.snap | Bin 3060 -> 1724 bytes 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/lib/snapshots/markdown-test.js.md b/tests/lib/snapshots/markdown-test.js.md index 0c3e317b..ffc3d699 100644 --- a/tests/lib/snapshots/markdown-test.js.md +++ b/tests/lib/snapshots/markdown-test.js.md @@ -4,6 +4,20 @@ The actual snapshot is saved in `markdown-test.js.snap`. Generated by [AVA](https://ava.li). +## Markdown.render() should render line breaks correctly + +> Snapshot 1 + + `

This is the first line.
␊ + This is the second line.

␊ + ` + +> Snapshot 2 + + `

This is the first line.␊ + This is the second line.

␊ + ` + ## Markdown.render() should renders KaTeX correctly > Snapshot 1 @@ -73,18 +87,4 @@ Generated by [AVA](https://ava.li). > Snapshot 2 `

This is a "QUOTE".

␊ - - -## Markdown.render() should render line breaks correctly - -> Snapshot 1 - - `

This is the first line.
␊ - This is the second line.

␊ ` - -> Snapshot 2 - - `

This is the first line.␊ - This is the second line.

␊ - ` \ No newline at end of file diff --git a/tests/lib/snapshots/markdown-test.js.snap b/tests/lib/snapshots/markdown-test.js.snap index 1dc64bba756effea535593a01693be463c95a26a..fc310cfd28f22b2e7d20fa14c94bd5f9b806b0fd 100644 GIT binary patch delta 1717 zcmV;m21@z#7rYIS8-LzrqAWHj5g&^P00000000y1S4)T-MHuc~#Dpkl)EsgvTUJ5S z)AQKP&L%UB`^cKajfv)ol1Oz=?RM>SclB0P?_{E4@DT(bL4<(f18*Kff+tbDh~PmC z2qGdvPI}T)Jc<5Q-P1cWnSE%YCkLjx=6`(utLm?RE*XY#tAFv$=53d+eEZD#(xo5m z7`Ezr3oE*~B*ESo*-JjKeu3x$E+!rssy#3l6 zv-b{Z>JN7s#^Udrf88Rle){3p$3EY3`!73&H1&h6hH>hRVpSjPrZF2xO8f1+hu=PQ$_au628O6ChgtOEyi8L=Qq{-5^7+H zt$;?PZk9|$8+G$I2|N}OOEPPUF(D%+iRoliBpJ%7_ttE`1au$Bo(@nM1LJg^xFq-z6w^#V1>?e3=Xtt zL;7`dY-iB~9lGGlx>+fWnMiQ2Zu(NjVwY{VTCGBxB{FeI0sOW}=#qv#S6q7R(41Hf z+Xqaf;atWWgx5^~!$H7W^GOg0j}sC(H9NI{i6`kYm2C@04GSE^ z*-r?)SQ#brgt$c&EVg;uZ%H5)_J;O}6k7pU|TS39h+_Z_rz zdv=R9sXZOAq+uOyM;?t9w5L-LXcDU$0|#Xsbbmh%IzyapcfLM?KVLE$ROKmX$3Ta|gRT0)&DAUqONwtvDx?%VBEUQYZGc?U2Sj*Jv zJZB-Q3CUTs;B;hac1GwqXE$D$M@iddm_u!R#-*1?NJfW-a@x%>6XIA{G_v-cLi;DWRQT&;M>ROE6?BI9N)LIO2!A^VI~?QahQPdN1#H?^O=P{gtpTQ!dVjcN8WQg|76r+xDdSn3sE;cIDal^ z8XgBI=Zu?9O+fZK+(aUO|=1#}c>-c^86AOT*-LwtsVoAV)}9B9KcAB06+apb*FCmEtbBZmWHh&rp3n!364PZ7$UK>4~}%GD$W)O)Qh zfCu8hNfe3Or?(7Ovb5wtYJWS{)Quhut(b|z^tsEb6iTHE@cQ#J)+L`AiU%ITa$$V+ zQ%BFJ`J2PND=qjzb|UqxpinB^fyv4^{f&R)ufMnV`#!7heV0b2>VK^Ns*!beko*4* z`Y(3S`mf$`;$x#T`*j?VSTyG#srw`e!ABa!aQn!13_*K7@tQ6>oer3tUsl+oUK{f- LnAZa9c@qEtkRLub literal 3060 zcmZ8jTT@e46b^z71V%0aBm@YAt0=c{Q6LCGAPE;y10<1)2DwC#Yc7ZoV@KPW&gesZ zw6=CSbz0kL`_N7woN;{6b{u@rGVM&A4&y^RZJ*k|(C=fNo~X{Z*80}o=bY@l*0(p> zMr?(yMc;D3zZ5JC*XiOk=Dgu$`>-i>@!y9JW?+Y!D$-4eiJIMidQf*CTwZq${P^Q* z6@F%`2S-P2Aa9_oUIw1rdVB-y&-ZRRLW3Y-Sa1z)WEgG=HVI2lFvY$Rzf^DxW928| z1h$k!v$^y5C1ftE4DoHCl|K{VLdvx=0w3Ls(P{(uikgEgND_Ny`!2UB*g!~utw-T} z3FSvyHeS91S~JEOAQ0f<^CW?9GQ#cLf#VxzV1oTMDh1&~z`pZvscX5C!Sfw6?6Nun zaul`-t7(O6fT`d<&hdF*0yZZUev!`tk8Z(5g!7|57{8?=nN5iB8^JlQW;*I|b<1pq z1o$$QRe_C5c348j+*x(D2aIy*?=prEjwD&LGx{v^pmjyX%15S#N(@`~Fa%t9Eg<2` ztGfF96Np?5FkiC$F@mmcsmXH?q=#B8ruudrqQLJ&EXk6jE1wylU4qk%z$vQ?r0hfD zr=UWC3$q9b`EbC~``!b>9LtDY$|)G-NQs<=b;3$5E6zAX!1?#hciY@!l;QK+9)4Hb zFEe8ZN{)(8(7jK5`pEhO7@$%=RfxipWV6rSY=sI?d0^*gxw zyg985vIYQ4s%tc|jsn(qzfirKn-b(B1U7ien_z8B0bl;2y#O&=QdgRvV#|%!PUi*N zSxBG)W)#Zx7D=>tf(A%G3pzT$z`_*yo+eG7GQy7QAm!|50~%?|-iMH1Wh zeKK$ep%+%qeVX~Qz&cw5k_7Y0KyYdeuPsk)E98_BHZ98o0pUT$5jIqOr22icAj8?3BjKi&r2oz&y}xa z@Y(wjkd@d?!<1Jvz^)aB~aoid4bJi{Ywa^}pPM7x1Cts!9sp=wh+0JhlK8)&^Vg=o!2j zaCTULvi|>1R2sM@`Q+iYBh~)g0&T@#*FQKuz|ytAxc6Q0DaG+-QR%h3zb0&O0)AJw zt{fT}a&(y}4S?HKp`52aN(4B>URi{DS%xlNw;dFxHq0a0o6|rI1AZz6I47aBd7)8Q zyaaeEm*7Z;q=rCQTg&RuD(5|uMtY{%OA}AYk)>fYfkHEIswnEs7a+)`} z;!2o6DNG*KH8!3#Uup=;1m#uCMbxk$iClCQtSOge6Yw&m;FbqV*%8WwR}c>+@axyy zB*0gP6fw8+?FHqclIf?PPWjsV!B4yw>h>DYQH&~ol(9Oev?v1ovsHz_dWZZxg#8I`2Qh`6s}!fhUNPb!`D$kZgy)Tu)3@%Yo%QL&P?#%K7&hf!wEX(qOD**-zdSj|z(*^r(YELW%;Au~9X# z6dCU^9to}}#D?wO))W{}k_2bZgfHree+1ehJBwkKkrsUX^jKab`VMy^ON8c?<=c sJEDuj5iorNuXDfw(>2DrGG?=~fq)AZdh)HYd%_4tl>>WV>w1mmKX*} Date: Wed, 9 May 2018 10:00:07 +0900 Subject: [PATCH 18/51] Add bounty program --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 928bedc9..116223f3 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -:mega: We've launched a blogging platform with markdown called **[Boostlog](https://boostlog.io/)**. +:mega: We've launched [Boostnote Bounty Program](http://bit.ly/2I5Tpik). ![Boostnote app screenshot](./resources/repository/top.png) From d58ea70a9570995f823fbcc8c8301da0e2e8546b Mon Sep 17 00:00:00 2001 From: yosmoc Date: Thu, 10 May 2018 06:22:27 +0200 Subject: [PATCH 19/51] fix moving note across different storage bug should update storageNoteMap here. --- browser/main/store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/store.js b/browser/main/store.js index 8ca00e7f..27796d30 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -256,7 +256,7 @@ function data (state = defaultDataMap(), action) { let noteSet = state.storageNoteMap.get(note.storage) noteSet = new Set(noteSet) noteSet.add(uniqueKey) - state.folderNoteMap.set(folderKey, noteSet) + state.storageNoteMap.set(folderKey, noteSet) } // Update foldermap if folder changed or post created From ffc3fb770c4cf9796410fe5dcc73bf558edc431c Mon Sep 17 00:00:00 2001 From: ehhc Date: Thu, 10 May 2018 20:27:47 +0200 Subject: [PATCH 20/51] Deleting of attachments -> fixes #1828 and fixes #740 --- browser/components/CodeEditor.js | 3 ++ .../main/lib/dataApi/attachmentManagement.js | 46 +++++++++++++++- tests/dataApi/attachmentManagement.test.js | 52 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index dfe072ef..8acf6362 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -50,6 +50,9 @@ export default class CodeEditor extends React.Component { el = el.parentNode } this.props.onBlur != null && this.props.onBlur(e) + + const {storageKey, noteKey} = this.props + attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey) } this.pasteHandler = (editor, e) => this.handlePaste(editor, e) this.loadStyleHandler = (e) => { diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index c2e7f6d6..efacd47c 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -157,7 +157,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem /** * @description Returns all attachment paths of the given markdown * @param {String} markdownContent content in which the attachment paths should be found - * @returns {String[]} Array of the relativ 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) { const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) @@ -190,6 +190,49 @@ 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) } +/** + * @description Deletes all attachments stored in the attachment folder of the give not that are not referenced in the markdownContent + * @param markdownContent Content of the note. All unreferenced notes will be deleted + * @param storageKey StorageKey of the current note. Is used to determine the belonging attachment folder. + * @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder. + */ +function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) { + const targetStorage = findStorage.findStorage(storageKey) + const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) + const attachmentsInNote = getAttachmentsInContent(markdownContent) + const attachmentsInNoteOnlyFileNames = [] + if (attachmentsInNote) { + for (let i = 0; i < attachmentsInNote.length; i++) { + attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), '')) + } + } + + if (fs.existsSync(attachmentFolder)) { + fs.readdir(attachmentFolder, (err, files) => { + if (err) { + console.error("Error reading directory '" + attachmentFolder + "'. Error:") + console.error(err) + return + } + files.forEach(file => { + if (!attachmentsInNoteOnlyFileNames.includes(file)) { + const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file) + fs.unlink(absolutePathOfFile, (err) => { + if (err) { + console.error("Could not delete '%s'", absolutePathOfFile) + console.error(err) + return + } + console.info("File '" + absolutePathOfFile + "' deleted because it was not included in the content of the note") + }) + } + }) + }) + } else { + console.info("Attachment folder ('" + attachmentFolder + "') did not exist..") + } +} + module.exports = { copyAttachment, fixLocalURLS, @@ -199,6 +242,7 @@ module.exports = { getAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent, removeStorageAndNoteReferences, + deleteAttachmentsNotPresentInNote, STORAGE_FOLDER_PLACEHOLDER, DESTINATION_FOLDER } diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index d58a8eb8..7efe4f21 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -260,3 +260,55 @@ it('should remove the all ":storage" and noteKey references', function () { const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) expect(actual).toEqual(expectedOutput) }) + +it('should test that deleteAttachmentsNotPresentInNote deletes all unreferenced attachments ', function () { + const dummyStorage = {path: 'dummyStoragePath'} + const noteKey = 'noteKey' + const storageKey = 'storageKey' + const markdownContent = '' + const dummyFilesInFolder = ['file1.txt', 'file2.pdf', 'file3.jpg'] + const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey) + + findStorage.findStorage = jest.fn(() => dummyStorage) + fs.existsSync = jest.fn(() => true) + fs.readdir = jest.fn((paht, callback) => callback(undefined, dummyFilesInFolder)) + fs.unlink = jest.fn() + + systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey) + expect(fs.existsSync).toHaveBeenLastCalledWith(attachmentFolderPath) + expect(fs.readdir).toHaveBeenCalledTimes(1) + expect(fs.readdir.mock.calls[0][0]).toBe(attachmentFolderPath) + + expect(fs.unlink).toHaveBeenCalledTimes(dummyFilesInFolder.length) + const fsUnlinkCallArguments = [] + for (let i = 0; i < dummyFilesInFolder.length; i++) { + fsUnlinkCallArguments.push(fs.unlink.mock.calls[i][0]) + } + + dummyFilesInFolder.forEach(function (file) { + expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, file))).toBe(true) + }) +}) + +it('should test that deleteAttachmentsNotPresentInNote does not delete referenced attachments', function () { + const dummyStorage = {path: 'dummyStoragePath'} + const noteKey = 'noteKey' + const storageKey = 'storageKey' + const dummyFilesInFolder = ['file1.txt', 'file2.pdf', 'file3.jpg'] + const markdownContent = systemUnderTest.generateAttachmentMarkdown('fileLabel', path.join(systemUnderTest.STORAGE_FOLDER_PLACEHOLDER, noteKey, dummyFilesInFolder[0]), false) + const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey) + + findStorage.findStorage = jest.fn(() => dummyStorage) + fs.existsSync = jest.fn(() => true) + fs.readdir = jest.fn((paht, callback) => callback(undefined, dummyFilesInFolder)) + fs.unlink = jest.fn() + + systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey) + + expect(fs.unlink).toHaveBeenCalledTimes(dummyFilesInFolder.length - 1) + const fsUnlinkCallArguments = [] + for (let i = 0; i < dummyFilesInFolder.length - 1; i++) { + fsUnlinkCallArguments.push(fs.unlink.mock.calls[i][0]) + } + expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, dummyFilesInFolder[0]))).toBe(false) +}) From 73ba8b8b13534174494a03e8243033206a32297b Mon Sep 17 00:00:00 2001 From: ehhc Date: Thu, 10 May 2018 21:36:58 +0200 Subject: [PATCH 21/51] Deleting a note should also delete the attachments -> fixes #1900 --- browser/main/lib/dataApi/attachmentManagement.js | 13 +++++++++++++ browser/main/lib/dataApi/deleteNote.js | 5 +++++ tests/dataApi/attachmentManagement.test.js | 14 ++++++++++++++ tests/dataApi/deleteNote-test.js | 10 ++++++++++ 4 files changed, 42 insertions(+) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index c2e7f6d6..ace33464 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -4,6 +4,7 @@ const path = require('path') const findStorage = require('browser/lib/findStorage') const mdurl = require('mdurl') const escapeStringRegexp = require('escape-string-regexp') +const sander = require('sander') const STORAGE_FOLDER_PLACEHOLDER = ':storage' const DESTINATION_FOLDER = 'attachments' @@ -190,6 +191,17 @@ 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) } +/** + * @description Deletes the attachment folder specified by the given storageKey and noteKey + * @param storageKey Key of the storage of the note to be deleted + * @param noteKey Key of the note to be deleted + */ +function deleteAttachmentFolder (storageKey, noteKey) { + const storagePath = findStorage.findStorage(storageKey) + const noteAttachmentPath = path.join(storagePath.path, DESTINATION_FOLDER, noteKey) + sander.rimraf(noteAttachmentPath) +} + module.exports = { copyAttachment, fixLocalURLS, @@ -199,6 +211,7 @@ module.exports = { getAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent, removeStorageAndNoteReferences, + deleteAttachmentFolder, STORAGE_FOLDER_PLACEHOLDER, DESTINATION_FOLDER } diff --git a/browser/main/lib/dataApi/deleteNote.js b/browser/main/lib/dataApi/deleteNote.js index 49498a30..46ec2b55 100644 --- a/browser/main/lib/dataApi/deleteNote.js +++ b/browser/main/lib/dataApi/deleteNote.js @@ -1,6 +1,7 @@ const resolveStorageData = require('./resolveStorageData') const path = require('path') const sander = require('sander') +const attachmentManagement = require('./attachmentManagement') const { findStorage } = require('browser/lib/findStorage') function deleteNote (storageKey, noteKey) { @@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) { storageKey } }) + .then(function deleteAttachments (storageInfo) { + attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey) + return storageInfo + }) } module.exports = deleteNote diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index d58a8eb8..148f6958 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -7,6 +7,7 @@ const findStorage = require('browser/lib/findStorage') jest.mock('unique-slug') const uniqueSlug = require('unique-slug') const mdurl = require('mdurl') +const sander = require('sander') const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement') @@ -260,3 +261,16 @@ it('should remove the all ":storage" and noteKey references', function () { const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) expect(actual).toEqual(expectedOutput) }) + +it('should delete the correct attachment folder if a note is deleted', function () { + const dummyStorage = {path: 'dummyStoragePath'} + const storageKey = 'storageKey' + const noteKey = 'noteKey' + findStorage.findStorage = jest.fn(() => dummyStorage) + sander.rimraf = jest.fn() + + const expectedPathToBeDeleted = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey) + systemUnderTest.deleteAttachmentFolder(storageKey, noteKey) + expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) + expect(sander.rimraf).toHaveBeenCalledWith(expectedPathToBeDeleted) +}) diff --git a/tests/dataApi/deleteNote-test.js b/tests/dataApi/deleteNote-test.js index 611022de..ed37854d 100644 --- a/tests/dataApi/deleteNote-test.js +++ b/tests/dataApi/deleteNote-test.js @@ -14,6 +14,8 @@ const sander = require('sander') const os = require('os') const CSON = require('@rokt33r/season') const faker = require('faker') +const fs = require('fs') +const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement') const storagePath = path.join(os.tmpdir(), 'test/delete-note') @@ -42,6 +44,10 @@ test.serial('Delete a note', (t) => { return Promise.resolve() .then(function doTest () { return createNote(storageKey, input1) + .then(function createAttachmentFolder (data) { + fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.noteKey)) + return data + }) .then(function (data) { return deleteNote(storageKey, data.key) }) @@ -54,6 +60,10 @@ test.serial('Delete a note', (t) => { t.is(err.code, 'ENOENT') } }) + .then(function assertAttachmentFolderDeleted (data) { + const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.noteKey) + t.assert(fs.existsSync(attachmentFolderPath) === false, 'Attachment folder was not deleted') + }) }) test.after(function after () { From ff59af6b51e6f79d885d1392b8cfead4fedc48a5 Mon Sep 17 00:00:00 2001 From: ehhc Date: Thu, 10 May 2018 21:42:23 +0200 Subject: [PATCH 22/51] Fix for the broken test --- tests/dataApi/deleteNote-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/dataApi/deleteNote-test.js b/tests/dataApi/deleteNote-test.js index ed37854d..c9a33145 100644 --- a/tests/dataApi/deleteNote-test.js +++ b/tests/dataApi/deleteNote-test.js @@ -45,7 +45,7 @@ test.serial('Delete a note', (t) => { .then(function doTest () { return createNote(storageKey, input1) .then(function createAttachmentFolder (data) { - fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.noteKey)) + fs.mkdirSync(path.join(storagePath.path, attachmentManagement.DESTINATION_FOLDER, data.noteKey)) return data }) .then(function (data) { @@ -54,14 +54,14 @@ test.serial('Delete a note', (t) => { }) .then(function assert (data) { try { - CSON.readFileSync(path.join(storagePath, 'notes', data.noteKey + '.cson')) + CSON.readFileSync(path.join(storagePath.path, 'notes', data.noteKey + '.cson')) t.fail('note cson must be deleted.') } catch (err) { t.is(err.code, 'ENOENT') } }) .then(function assertAttachmentFolderDeleted (data) { - const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.noteKey) + const attachmentFolderPath = path.join(storagePath.path, attachmentManagement.DESTINATION_FOLDER, data.noteKey) t.assert(fs.existsSync(attachmentFolderPath) === false, 'Attachment folder was not deleted') }) }) From 03fd1e29e37a84dacaea3bce8cdad7cda55599a4 Mon Sep 17 00:00:00 2001 From: ehhc Date: Thu, 10 May 2018 22:30:13 +0200 Subject: [PATCH 23/51] Fix for the broken test --- browser/main/lib/dataApi/attachmentManagement.js | 2 +- tests/dataApi/deleteNote-test.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index ace33464..a1030aee 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -199,7 +199,7 @@ function removeStorageAndNoteReferences (input, noteKey) { function deleteAttachmentFolder (storageKey, noteKey) { const storagePath = findStorage.findStorage(storageKey) const noteAttachmentPath = path.join(storagePath.path, DESTINATION_FOLDER, noteKey) - sander.rimraf(noteAttachmentPath) + sander.rimrafSync(noteAttachmentPath) } module.exports = { diff --git a/tests/dataApi/deleteNote-test.js b/tests/dataApi/deleteNote-test.js index c9a33145..9c809dcf 100644 --- a/tests/dataApi/deleteNote-test.js +++ b/tests/dataApi/deleteNote-test.js @@ -45,7 +45,8 @@ test.serial('Delete a note', (t) => { .then(function doTest () { return createNote(storageKey, input1) .then(function createAttachmentFolder (data) { - fs.mkdirSync(path.join(storagePath.path, attachmentManagement.DESTINATION_FOLDER, data.noteKey)) + fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER)) + fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.key)) return data }) .then(function (data) { @@ -54,15 +55,16 @@ test.serial('Delete a note', (t) => { }) .then(function assert (data) { try { - CSON.readFileSync(path.join(storagePath.path, 'notes', data.noteKey + '.cson')) + CSON.readFileSync(path.join(storagePath, 'notes', data.noteKey + '.cson')) t.fail('note cson must be deleted.') } catch (err) { t.is(err.code, 'ENOENT') + return data } }) .then(function assertAttachmentFolderDeleted (data) { - const attachmentFolderPath = path.join(storagePath.path, attachmentManagement.DESTINATION_FOLDER, data.noteKey) - t.assert(fs.existsSync(attachmentFolderPath) === false, 'Attachment folder was not deleted') + const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.noteKey) + t.is(fs.existsSync(attachmentFolderPath), false) }) }) From e9218d10889cd89b4dc13a7ea772d84a032d1bef Mon Sep 17 00:00:00 2001 From: ehhc Date: Thu, 10 May 2018 22:39:00 +0200 Subject: [PATCH 24/51] Fix for the broken test --- tests/dataApi/attachmentManagement.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index 148f6958..734aa3b9 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -267,10 +267,10 @@ it('should delete the correct attachment folder if a note is deleted', function const storageKey = 'storageKey' const noteKey = 'noteKey' findStorage.findStorage = jest.fn(() => dummyStorage) - sander.rimraf = jest.fn() + sander.rimrafSync = jest.fn() const expectedPathToBeDeleted = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey) systemUnderTest.deleteAttachmentFolder(storageKey, noteKey) expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) - expect(sander.rimraf).toHaveBeenCalledWith(expectedPathToBeDeleted) + expect(sander.rimrafSync).toHaveBeenCalledWith(expectedPathToBeDeleted) }) From 26d7f4923df468b9d0736c0efc630a9a52853d96 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Sat, 12 May 2018 23:21:02 +0200 Subject: [PATCH 25/51] support multiplex mode embedded javascript uses combined several modes. Multiplex mode renders ejs and other formats which uses several modes. --- lib/main.html | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main.html b/lib/main.html index 5e5d13c3..15e2bbeb 100644 --- a/lib/main.html +++ b/lib/main.html @@ -77,6 +77,7 @@ + From 67bba043ed587798bea1279ea1c3c2c93883c3c9 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Sat, 12 May 2018 23:23:12 +0200 Subject: [PATCH 26/51] MarkdownPreview also needs to convert the mode name for specific modes --- browser/components/MarkdownPreview.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 058dce19..935f97e3 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -123,6 +123,20 @@ if (!OSX) { } const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] +function pass (name) { + switch (name) { + case 'ejs': + return 'Embedded Javascript' + case 'html_ruby': + return 'Embedded Ruby' + case 'objectivec': + return 'Objective C' + case 'text': + return 'Plain Text' + default: + return name + } +} export default class MarkdownPreview extends React.Component { constructor (props) { super(props) @@ -430,7 +444,7 @@ export default class MarkdownPreview extends React.Component { : 'default' _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => { - let syntax = CodeMirror.findModeByName(el.className) + let syntax = CodeMirror.findModeByName(pass(el.className)) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') CodeMirror.requireMode(syntax.mode, () => { const content = htmlTextHelper.decodeEntities(el.innerHTML) From 7e1596de3090969c5e09f4bab669944643e45912 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Sat, 12 May 2018 23:58:15 +0200 Subject: [PATCH 27/51] refactor code duplication --- browser/components/CodeEditor.js | 18 ++---------------- browser/components/MarkdownPreview.js | 18 ++---------------- browser/lib/convertModeName.js | 14 ++++++++++++++ browser/main/Detail/SnippetNoteDetail.js | 18 ++---------------- 4 files changed, 20 insertions(+), 48 deletions(-) create mode 100644 browser/lib/convertModeName.js diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index dfe072ef..7feef2e8 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -4,6 +4,7 @@ import _ from 'lodash' import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' +import convertModeName from 'browser/lib/convertModeName' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' @@ -15,21 +16,6 @@ const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', ' const buildCMRulers = (rulers, enableRulers) => enableRulers ? rulers.map(ruler => ({ column: ruler })) : [] -function pass (name) { - switch (name) { - case 'ejs': - return 'Embedded Javascript' - case 'html_ruby': - return 'Embedded Ruby' - case 'objectivec': - return 'Objective C' - case 'text': - return 'Plain Text' - default: - return name - } -} - export default class CodeEditor extends React.Component { constructor (props) { super(props) @@ -229,7 +215,7 @@ export default class CodeEditor extends React.Component { } setMode (mode) { - let syntax = CodeMirror.findModeByName(pass(mode)) + let syntax = CodeMirror.findModeByName(convertModeName(mode)) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') this.editor.setOption('mode', syntax.mime) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 935f97e3..6646f749 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -10,6 +10,7 @@ import flowchart from 'flowchart' import SequenceDiagram from 'js-sequence-diagrams' import eventEmitter from 'browser/main/lib/eventEmitter' import htmlTextHelper from 'browser/lib/htmlTextHelper' +import convertModeName from 'browser/lib/convertModeName' import copy from 'copy-to-clipboard' import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' @@ -122,21 +123,6 @@ if (!OSX) { defaultFontFamily.unshift('meiryo') } const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] - -function pass (name) { - switch (name) { - case 'ejs': - return 'Embedded Javascript' - case 'html_ruby': - return 'Embedded Ruby' - case 'objectivec': - return 'Objective C' - case 'text': - return 'Plain Text' - default: - return name - } -} export default class MarkdownPreview extends React.Component { constructor (props) { super(props) @@ -444,7 +430,7 @@ export default class MarkdownPreview extends React.Component { : 'default' _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => { - let syntax = CodeMirror.findModeByName(pass(el.className)) + let syntax = CodeMirror.findModeByName(convertModeName(el.className)) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') CodeMirror.requireMode(syntax.mode, () => { const content = htmlTextHelper.decodeEntities(el.innerHTML) diff --git a/browser/lib/convertModeName.js b/browser/lib/convertModeName.js new file mode 100644 index 00000000..b0431059 --- /dev/null +++ b/browser/lib/convertModeName.js @@ -0,0 +1,14 @@ +export default function convertModeName (name) { + switch (name) { + case 'ejs': + return 'Embedded Javascript' + case 'html_ruby': + return 'Embedded Ruby' + case 'objectivec': + return 'Objective C' + case 'text': + return 'Plain Text' + default: + return name + } +} diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 411027d5..4f9903e9 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -18,6 +18,7 @@ import context from 'browser/lib/context' import ConfigManager from 'browser/main/lib/ConfigManager' import _ from 'lodash' import {findNoteTitle} from 'browser/lib/findNoteTitle' +import convertModeName from 'browser/lib/convertModeName' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import TrashButton from './TrashButton' import RestoreButton from './RestoreButton' @@ -29,21 +30,6 @@ import { formatDate } from 'browser/lib/date-formatter' import i18n from 'browser/lib/i18n' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' -function pass (name) { - switch (name) { - case 'ejs': - return 'Embedded Javascript' - case 'html_ruby': - return 'Embedded Ruby' - case 'objectivec': - return 'Objective C' - case 'text': - return 'Plain Text' - default: - return name - } -} - const electron = require('electron') const { remote } = electron const { Menu, MenuItem, dialog } = remote @@ -677,7 +663,7 @@ class SnippetNoteDetail extends React.Component { const viewList = note.snippets.map((snippet, index) => { const isActive = this.state.snippetIndex === index - let syntax = CodeMirror.findModeByName(pass(snippet.mode)) + let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode)) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') return
Date: Mon, 14 May 2018 21:04:18 +0200 Subject: [PATCH 28/51] fixed NoteItemSimple styling for the dark themes --- browser/components/NoteItemSimple.styl | 47 +++++++++++++++++--------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/browser/components/NoteItemSimple.styl b/browser/components/NoteItemSimple.styl index 91c28c2a..661751bc 100644 --- a/browser/components/NoteItemSimple.styl +++ b/browser/components/NoteItemSimple.styl @@ -104,6 +104,7 @@ body[data-theme="dark"] background-color alpha($ui-dark-button--active-backgroundColor, 20%) color $ui-dark-text-color .item-simple-title + .item-simple-title-empty .item-simple-title-icon .item-simple-bottom-time transition 0.15s @@ -117,6 +118,7 @@ body[data-theme="dark"] background-color $ui-dark-button--active-backgroundColor color $ui-dark-text-color .item-simple-title + .item-simple-title-empty .item-simple-title-icon .item-simple-bottom-time transition 0.15s @@ -165,9 +167,10 @@ body[data-theme="solarized-dark"] background-color $ui-solarized-dark-noteList-backgroundColor &:hover transition 0.15s - // background-color alpha($ui-dark-button--active-backgroundColor, 20%) + background-color alpha($ui-dark-button--active-backgroundColor, 60%) color $ui-solarized-dark-text-color .item-simple-title + .item-simple-title-empty .item-simple-title-icon .item-simple-bottom-time transition 0.15s @@ -178,9 +181,10 @@ body[data-theme="solarized-dark"] color $ui-solarized-dark-text-color &:active transition 0.15s - background-color $ui-solarized-dark-button--active-backgroundColor - color $ui-solarized-dark-text-color + // background-color $ui-solarized-dark-button--active-backgroundColor + color $ui-dark-text-color .item-simple-title + .item-simple-title-empty .item-simple-title-icon .item-simple-bottom-time transition 0.15s @@ -192,11 +196,13 @@ body[data-theme="solarized-dark"] .item-simple--active border-color $ui-solarized-dark-borderColor - background-color $ui-solarized-dark-button--active-backgroundColor + background-color $ui-solarized-dark-tag-backgroundColor .item-simple-wrapper border-color transparent .item-simple-title + .item-simple-title-empty .item-simple-title-icon + color $ui-dark-text-color .item-simple-bottom-time color $ui-solarized-dark-text-color .item-simple-bottom-tagList-item @@ -207,11 +213,14 @@ body[data-theme="solarized-dark"] color #c0392b .item-simple-bottom-tagList-item background-color alpha(#fff, 20%) -.item-simple-right - float right - .item-simple-right-storageName - padding-left 4px - opacity 0.4 + .item-simple-title + color $ui-dark-text-color + border-bottom $ui-dark-borderColor + .item-simple-right + float right + .item-simple-right-storageName + padding-left 4px + opacity 0.4 body[data-theme="monokai"] .root @@ -223,13 +232,14 @@ body[data-theme="monokai"] background-color $ui-monokai-noteList-backgroundColor &:hover transition 0.15s - // background-color alpha($ui-dark-button--active-backgroundColor, 20%) + background-color alpha($ui-monokai-button-backgroundColor, 60%) color $ui-monokai-text-color .item-simple-title + .item-simple-title-empty .item-simple-title-icon .item-simple-bottom-time transition 0.15s - color $ui-monokai-text-color + color $ui-solarized-dark-text-color .item-simple-bottom-tagList-item transition 0.15s background-color alpha(#fff, 20%) @@ -239,6 +249,7 @@ body[data-theme="monokai"] background-color $ui-monokai-button--active-backgroundColor color $ui-monokai-text-color .item-simple-title + .item-simple-title-empty .item-simple-title-icon .item-simple-bottom-time transition 0.15s @@ -254,6 +265,7 @@ body[data-theme="monokai"] .item-simple-wrapper border-color transparent .item-simple-title + .item-simple-title-empty .item-simple-title-icon .item-simple-bottom-time color $ui-monokai-text-color @@ -265,8 +277,11 @@ body[data-theme="monokai"] color #c0392b .item-simple-bottom-tagList-item background-color alpha(#fff, 20%) -.item-simple-right - float right - .item-simple-right-storageName - padding-left 4px - opacity 0.4 + .item-simple-title + color $ui-dark-text-color + border-bottom $ui-dark-borderColor + .item-simple-right + float right + .item-simple-right-storageName + padding-left 4px + opacity 0.4 From a7946805ae2802afe3470a6e3b430a30fe98c77a Mon Sep 17 00:00:00 2001 From: yosmoc Date: Mon, 14 May 2018 22:35:38 +0200 Subject: [PATCH 29/51] configuable plantUML server address --- browser/lib/markdown.js | 4 +++- browser/main/modals/PreferencesModal/UiTab.js | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 1ef488a7..5848aeea 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -145,11 +145,13 @@ class Markdown { const deflate = require('markdown-it-plantuml/lib/deflate') this.md.use(require('markdown-it-plantuml'), '', { generateSource: function (umlCode) { + const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url + const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg' const s = unescape(encodeURIComponent(umlCode)) const zippedCode = deflate.encode64( deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9) ) - return `http://www.plantuml.com/plantuml/svg/${zippedCode}` + return `${serverAddress}/${zippedCode}` } }) diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index a607f548..1bbcb18d 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -94,6 +94,7 @@ class UiTab extends React.Component { latexInlineClose: this.refs.previewLatexInlineClose.value, latexBlockOpen: this.refs.previewLatexBlockOpen.value, latexBlockClose: this.refs.previewLatexBlockClose.value, + plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value, scrollPastEnd: this.refs.previewScrollPastEnd.checked, smartQuotes: this.refs.previewSmartQuotes.checked, sanitize: this.refs.previewSanitize.value @@ -544,6 +545,19 @@ class UiTab extends React.Component { />
+
+
+ {i18n.__('PlantUML Server')} +
+
+ this.handleUIChange(e)} + type='text' + /> +
+
+
+
{i18n.__('Toggle editor mode')}
+
+ this.handleHotkeyChange(e)} + ref='toggleMode' + value={config.hotkey.toggleMode} + type='text' + /> +
+
From a6af5de3e1044b7fedddce26fe00b0a5b6bcb21d Mon Sep 17 00:00:00 2001 From: bimlas Date: Fri, 18 May 2018 15:18:28 +0200 Subject: [PATCH 44/51] Refactor tag removing methods (duplicated code) --- browser/main/Detail/TagSelect.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index ce0b4f79..d14c7a8c 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -44,16 +44,9 @@ class TagSelect extends React.Component { } removeLastTag () { - let { value } = this.props - - value = _.isArray(value) - ? value.slice() - : [] - value.pop() - value = _.uniq(value) - - this.value = value - this.props.onChange() + this.removeTagByCallback((value) => { + value.pop() + }) } reset () { @@ -96,12 +89,18 @@ class TagSelect extends React.Component { } handleTagRemoveButtonClick (tag) { + this.removeTagByCallback((value, tag) => { + value.splice(value.indexOf(tag), 1) + }, tag) + } + + removeTagByCallback (callback, tag = null) { let { value } = this.props value = _.isArray(value) ? value.slice() : [] - value.splice(value.indexOf(tag), 1) + callback(value, tag) value = _.uniq(value) this.value = value From 0ca4e6ca4f3688537e82c76a82934aa193e03a58 Mon Sep 17 00:00:00 2001 From: Junyoung Choi Date: Sat, 19 May 2018 16:45:03 +0900 Subject: [PATCH 45/51] v0.11.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3548431..7139094f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "boost", "productName": "Boostnote", - "version": "0.11.4", + "version": "0.11.5", "main": "index.js", "description": "Boostnote", "license": "GPL-3.0", From 86a6311f75b115eeeba41c65b1f267b7cdec5a0a Mon Sep 17 00:00:00 2001 From: ehhc Date: Sat, 19 May 2018 20:38:10 +0200 Subject: [PATCH 46/51] deleting a folder should also delete the attachments -> fixes #1903 --- browser/main/lib/dataApi/deleteFolder.js | 7 ++---- tests/dataApi/deleteFolder-test.js | 31 ++++++++++++++++++++++++ tests/dataApi/exportFolder-test.js | 6 +++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/browser/main/lib/dataApi/deleteFolder.js b/browser/main/lib/dataApi/deleteFolder.js index 908677e1..0c7486f5 100644 --- a/browser/main/lib/dataApi/deleteFolder.js +++ b/browser/main/lib/dataApi/deleteFolder.js @@ -5,6 +5,7 @@ const resolveStorageNotes = require('./resolveStorageNotes') const CSON = require('@rokt33r/season') const sander = require('sander') const { findStorage } = require('browser/lib/findStorage') +const deleteSingleNote = require('./deleteNote') /** * @param {String} storageKey @@ -49,11 +50,7 @@ function deleteFolder (storageKey, folderKey) { const deleteAllNotes = targetNotes .map(function deleteNote (note) { - const notePath = path.join(storage.path, 'notes', note.key + '.cson') - return sander.unlink(notePath) - .catch(function (err) { - console.warn('Failed to delete', notePath, err) - }) + return deleteSingleNote(storageKey, note.key) }) return Promise.all(deleteAllNotes) .then(() => storage) diff --git a/tests/dataApi/deleteFolder-test.js b/tests/dataApi/deleteFolder-test.js index 1d9c7646..ef819247 100644 --- a/tests/dataApi/deleteFolder-test.js +++ b/tests/dataApi/deleteFolder-test.js @@ -1,5 +1,9 @@ const test = require('ava') const deleteFolder = require('browser/main/lib/dataApi/deleteFolder') +const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement') +const createNote = require('browser/main/lib/dataApi/createNote') +const fs = require('fs') +const faker = require('faker') global.document = require('jsdom').jsdom('') global.window = document.defaultView @@ -24,8 +28,32 @@ test.beforeEach((t) => { test.serial('Delete a folder', (t) => { const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key + let noteKey = undefined + + const input1 = { + type: 'SNIPPET_NOTE', + description: faker.lorem.lines(), + snippets: [{ + name: faker.system.fileName(), + mode: 'text', + content: faker.lorem.lines() + }], + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input1.title = input1.description.split('\n').shift() return Promise.resolve() + .then(function prepare () { + return createNote(storageKey, input1) + .then(function createAttachmentFolder (data) { + fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER)) + fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.key)) + noteKey = data.key + + return data + }) + }) .then(function doTest () { return deleteFolder(storageKey, folderKey) }) @@ -36,6 +64,9 @@ test.serial('Delete a folder', (t) => { t.true(_.find(jsonData.folders, {key: folderKey}) == null) const notePaths = sander.readdirSync(data.storage.path, 'notes') t.is(notePaths.length, t.context.storage.notes.filter((note) => note.folder !== folderKey).length) + + const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, noteKey) + t.false(fs.existsSync(attachmentFolderPath)) }) }) diff --git a/tests/dataApi/exportFolder-test.js b/tests/dataApi/exportFolder-test.js index ee6fb898..fb4aaa7b 100644 --- a/tests/dataApi/exportFolder-test.js +++ b/tests/dataApi/exportFolder-test.js @@ -13,6 +13,7 @@ const TestDummy = require('../fixtures/TestDummy') const os = require('os') const faker = require('faker') const fs = require('fs') +const sander = require('sander') const storagePath = path.join(os.tmpdir(), 'test/export-note') @@ -60,3 +61,8 @@ test.serial('Export a folder', (t) => { t.false(fs.existsSync(filePath)) }) }) + +test.after.always(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) +}) From 199f2202e047caf623cc7ebb02af153df05002ec Mon Sep 17 00:00:00 2001 From: ehhc Date: Sat, 19 May 2018 20:43:48 +0200 Subject: [PATCH 47/51] deleting a folder should also delete the attachments -> fixe eslint issue --- tests/dataApi/deleteFolder-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dataApi/deleteFolder-test.js b/tests/dataApi/deleteFolder-test.js index ef819247..af901896 100644 --- a/tests/dataApi/deleteFolder-test.js +++ b/tests/dataApi/deleteFolder-test.js @@ -28,7 +28,7 @@ test.beforeEach((t) => { test.serial('Delete a folder', (t) => { const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key - let noteKey = undefined + let noteKey const input1 = { type: 'SNIPPET_NOTE', From f76224bd17c10c43feca02c8d76027d27551ebbf Mon Sep 17 00:00:00 2001 From: ehhc Date: Sun, 20 May 2018 15:18:49 +0200 Subject: [PATCH 48/51] Cloning of a note should also clone its attachments -> fixes #1904 --- browser/main/NoteList/index.js | 5 ++ .../main/lib/dataApi/attachmentManagement.js | 37 ++++++++++++- tests/dataApi/attachmentManagement.test.js | 55 +++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 876de0c0..be885f8e 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -7,6 +7,7 @@ import moment from 'moment' import _ from 'lodash' import ee from 'browser/main/lib/eventEmitter' import dataApi from 'browser/main/lib/dataApi' +import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import ConfigManager from 'browser/main/lib/ConfigManager' import NoteItem from 'browser/components/NoteItem' import NoteItemSimple from 'browser/components/NoteItemSimple' @@ -662,6 +663,10 @@ class NoteList extends React.Component { title: firstNote.title + ' ' + i18n.__('copy'), content: firstNote.content }) + .then((note) => { + attachmentManagement.cloneAttachments(storage.key, firstNote, note) + return note + }) .then((note) => { dispatch({ type: 'UPDATE_NOTE', diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index b78ff8a9..e9604d30 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -200,8 +200,19 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) { if (fse.existsSync(src)) { fse.moveSync(src, dest) } + return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey) +} + +/** + * Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one + * @param noteContent content that should be modified + * @param oldNoteKey note key to be replaced + * @param newNoteKey note key serving as a replacement + * @returns {String} modified note content + */ +function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) { if (noteContent) { - return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey)) + return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey)) } return noteContent } @@ -270,6 +281,29 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey } } +/** + * Clones the attachments of a given note. + * Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination. + * @param storageKey Key of the current storage + * @param oldNote Note that is being cloned + * @param newNote Clone of the note + */ +function cloneAttachments (storageKey, oldNote, newNote) { + const storage = findStorage.findStorage(storageKey) + const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, storage.path) || [] + + const destinationFolder = path.join(storage.path, DESTINATION_FOLDER, newNote.key) + if (!sander.existsSync(destinationFolder)) { + sander.mkdirSync(destinationFolder) + } + + for (const attachment of attachmentsPaths) { + const destination = path.join(storage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment)) + sander.copyFileSync(attachment).to(destination) + } + newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key) +} + module.exports = { copyAttachment, fixLocalURLS, @@ -282,6 +316,7 @@ module.exports = { deleteAttachmentFolder, deleteAttachmentsNotPresentInNote, moveAttachments, + cloneAttachments, STORAGE_FOLDER_PLACEHOLDER, DESTINATION_FOLDER } diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index a3c88cbb..4175846a 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -8,6 +8,7 @@ jest.mock('unique-slug') const uniqueSlug = require('unique-slug') const mdurl = require('mdurl') const fse = require('fs-extra') +jest.mock('sander') const sander = require('sander') const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement') @@ -393,3 +394,57 @@ it('should test that moveAttachments returns a correct modified content version' const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput) expect(actualContent).toBe(expectedOutput) }) + +it('should test that cloneAttachments modifies the content of the new note correctly', function () { + const storageKey = 'storageKey' + const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent'} + const newNote = {key: 'newNoteKey', content: 'oldNoteContent'} + 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})' + newNote.content = testInput + + const expectedOutput = + 'Test input' + + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'image.jpg](imageName}) \n' + + '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'pdf.pdf](pdf})' + systemUnderTest.cloneAttachments(storageKey, oldNote, newNote) + + expect(newNote.content).toBe(expectedOutput) +}) + +it('should test that cloneAttachments finds all attachments and copies them to the new location', function () { + const storageKey = 'storageKey' + const storagePath = 'storagePath' + const dummyStorage = {path: storagePath} + const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent'} + const newNote = {key: 'newNoteKey', content: 'oldNoteContent'} + 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})' + oldNote.content = testInput + newNote.content = testInput + + const copyFileSyncResp = {to: jest.fn()} + sander.copyFileSync = jest.fn() + sander.copyFileSync.mockReturnValue(copyFileSyncResp) + findStorage.findStorage = jest.fn(() => dummyStorage) + + const pathAttachmentOneFrom = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg') + const pathAttachmentOneTo = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg') + + const pathAttachmentTwoFrom = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf') + const pathAttachmentTwoTo = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf') + + systemUnderTest.cloneAttachments(storageKey, oldNote, newNote) + + expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) + expect(sander.copyFileSync).toHaveBeenCalledTimes(2) + expect(copyFileSyncResp.to).toHaveBeenCalledTimes(2) + expect(sander.copyFileSync.mock.calls[0][0]).toBe(pathAttachmentOneFrom) + expect(copyFileSyncResp.to.mock.calls[0][0]).toBe(pathAttachmentOneTo) + expect(sander.copyFileSync.mock.calls[1][0]).toBe(pathAttachmentTwoFrom) + expect(copyFileSyncResp.to.mock.calls[1][0]).toBe(pathAttachmentTwoTo) +}) From cd6233a3d7cfa2a03aede8b23bcc630c70fb2560 Mon Sep 17 00:00:00 2001 From: ehhc Date: Sun, 20 May 2018 15:49:15 +0200 Subject: [PATCH 49/51] Cloning of a note should also clone its attachments -> works if the notes are in different storages now --- browser/main/NoteList/index.js | 2 +- .../main/lib/dataApi/attachmentManagement.js | 12 +++---- tests/dataApi/attachmentManagement.test.js | 35 ++++++++++--------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index be885f8e..d6b7f846 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -664,7 +664,7 @@ class NoteList extends React.Component { content: firstNote.content }) .then((note) => { - attachmentManagement.cloneAttachments(storage.key, firstNote, note) + attachmentManagement.cloneAttachments(firstNote, note) return note }) .then((note) => { diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index e9604d30..eb73bd95 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -284,21 +284,21 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey /** * Clones the attachments of a given note. * Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination. - * @param storageKey Key of the current storage * @param oldNote Note that is being cloned * @param newNote Clone of the note */ -function cloneAttachments (storageKey, oldNote, newNote) { - const storage = findStorage.findStorage(storageKey) - const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, storage.path) || [] +function cloneAttachments (oldNote, newNote) { + const oldStorage = findStorage.findStorage(oldNote.storage) + const newStorage = findStorage.findStorage(newNote.storage) + const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || [] - const destinationFolder = path.join(storage.path, DESTINATION_FOLDER, newNote.key) + const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key) if (!sander.existsSync(destinationFolder)) { sander.mkdirSync(destinationFolder) } for (const attachment of attachmentsPaths) { - const destination = path.join(storage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment)) + const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment)) sander.copyFileSync(attachment).to(destination) } newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key) diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index 4175846a..c101030b 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -396,9 +396,8 @@ it('should test that moveAttachments returns a correct modified content version' }) it('should test that cloneAttachments modifies the content of the new note correctly', function () { - const storageKey = 'storageKey' - const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent'} - const newNote = {key: 'newNoteKey', content: 'oldNoteContent'} + const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKey'} + const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey'} const testInput = 'Test input' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' + @@ -409,17 +408,18 @@ it('should test that cloneAttachments modifies the content of the new note corre 'Test input' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'image.jpg](imageName}) \n' + '[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'pdf.pdf](pdf})' - systemUnderTest.cloneAttachments(storageKey, oldNote, newNote) + systemUnderTest.cloneAttachments(oldNote, newNote) expect(newNote.content).toBe(expectedOutput) }) it('should test that cloneAttachments finds all attachments and copies them to the new location', function () { - const storageKey = 'storageKey' - const storagePath = 'storagePath' - const dummyStorage = {path: storagePath} - const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent'} - const newNote = {key: 'newNoteKey', content: 'oldNoteContent'} + const storagePathOld = 'storagePathOld' + const storagePathNew = 'storagePathNew' + const dummyStorageOld = {path: storagePathOld} + const dummyStorageNew = {path: storagePathNew} + const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote'} + const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote'} const testInput = 'Test input' + '![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' + @@ -430,17 +430,20 @@ it('should test that cloneAttachments finds all attachments and copies them to t const copyFileSyncResp = {to: jest.fn()} sander.copyFileSync = jest.fn() sander.copyFileSync.mockReturnValue(copyFileSyncResp) - findStorage.findStorage = jest.fn(() => dummyStorage) + findStorage.findStorage = jest.fn() + findStorage.findStorage.mockReturnValueOnce(dummyStorageOld) + findStorage.findStorage.mockReturnValue(dummyStorageNew) - const pathAttachmentOneFrom = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg') - const pathAttachmentOneTo = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg') + const pathAttachmentOneFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg') + const pathAttachmentOneTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg') - const pathAttachmentTwoFrom = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf') - const pathAttachmentTwoTo = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf') + const pathAttachmentTwoFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf') + const pathAttachmentTwoTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf') - systemUnderTest.cloneAttachments(storageKey, oldNote, newNote) + systemUnderTest.cloneAttachments(oldNote, newNote) - expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey) + expect(findStorage.findStorage).toHaveBeenCalledWith(oldNote.storage) + expect(findStorage.findStorage).toHaveBeenCalledWith(newNote.storage) expect(sander.copyFileSync).toHaveBeenCalledTimes(2) expect(copyFileSyncResp.to).toHaveBeenCalledTimes(2) expect(sander.copyFileSync.mock.calls[0][0]).toBe(pathAttachmentOneFrom) From bfcf349ffefb54ec4e4e96dc19a6e85cb70b4b13 Mon Sep 17 00:00:00 2001 From: ehhc Date: Wed, 23 May 2018 19:24:03 +0200 Subject: [PATCH 50/51] Attachment management should only become active on cloning notes if the note was of type MARKDOWN_NOTE -> No Stacktraces otherwise! --- .../main/lib/dataApi/attachmentManagement.js | 26 +++++++++++-------- tests/dataApi/attachmentManagement.test.js | 24 ++++++++++++++--- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index eb73bd95..893e03d1 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -288,20 +288,24 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey * @param newNote Clone of the note */ function cloneAttachments (oldNote, newNote) { - const oldStorage = findStorage.findStorage(oldNote.storage) - const newStorage = findStorage.findStorage(newNote.storage) - const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || [] + if (newNote.type === 'MARKDOWN_NOTE') { + const oldStorage = findStorage.findStorage(oldNote.storage) + const newStorage = findStorage.findStorage(newNote.storage) + const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || [] - const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key) - if (!sander.existsSync(destinationFolder)) { - sander.mkdirSync(destinationFolder) - } + const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key) + if (!sander.existsSync(destinationFolder)) { + sander.mkdirSync(destinationFolder) + } - for (const attachment of attachmentsPaths) { - const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment)) - sander.copyFileSync(attachment).to(destination) + for (const attachment of attachmentsPaths) { + const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment)) + sander.copyFileSync(attachment).to(destination) + } + newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key) + } else { + console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs') } - newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key) } module.exports = { diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index c101030b..b61d8bf9 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -396,8 +396,8 @@ it('should test that moveAttachments returns a correct modified content version' }) it('should test that cloneAttachments modifies the content of the new note correctly', function () { - const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKey'} - const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey'} + const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'} + 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' + @@ -418,8 +418,8 @@ it('should test that cloneAttachments finds all attachments and copies them to t const storagePathNew = 'storagePathNew' const dummyStorageOld = {path: storagePathOld} const dummyStorageNew = {path: storagePathNew} - const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote'} - const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote'} + const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'MARKDOWN_NOTE'} + 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' + @@ -451,3 +451,19 @@ it('should test that cloneAttachments finds all attachments and copies them to t expect(sander.copyFileSync.mock.calls[1][0]).toBe(pathAttachmentTwoFrom) expect(copyFileSyncResp.to.mock.calls[1][0]).toBe(pathAttachmentTwoTo) }) + +it('should test that cloneAttachments finds all attachments and copies them to the new location', function () { + const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'SOMETHING_ELSE'} + const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'SOMETHING_ELSE'} + const testInput = 'Test input' + oldNote.content = testInput + newNote.content = testInput + + sander.copyFileSync = jest.fn() + findStorage.findStorage = jest.fn() + + systemUnderTest.cloneAttachments(oldNote, newNote) + + expect(findStorage.findStorage).not.toHaveBeenCalled() + expect(sander.copyFileSync).not.toHaveBeenCalled() +}) From a5938422658f04997385dc43219fa9c15760e3b3 Mon Sep 17 00:00:00 2001 From: yosmoc Date: Wed, 23 May 2018 22:49:52 +0200 Subject: [PATCH 51/51] standardrize sidebar tooltips - show in same place - same height --- browser/components/SideNavFilter.styl | 5 ++--- browser/components/StorageItem.styl | 4 ++-- browser/main/SideNav/StorageItem.styl | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/browser/components/SideNavFilter.styl b/browser/components/SideNavFilter.styl index c1b378b8..c9dbd861 100644 --- a/browser/components/SideNavFilter.styl +++ b/browser/components/SideNavFilter.styl @@ -18,7 +18,7 @@ .iconWrap width 20px text-align center - + .counters float right color $ui-inactive-text-color @@ -68,10 +68,9 @@ .menu-button-label position fixed display inline-block - height 32px + height 36px left 44px padding 0 10px - margin-top -8px margin-left 0 overflow ellipsis z-index 10 diff --git a/browser/components/StorageItem.styl b/browser/components/StorageItem.styl index ece97008..0a1b4525 100644 --- a/browser/components/StorageItem.styl +++ b/browser/components/StorageItem.styl @@ -58,8 +58,8 @@ opacity 0 border-top-right-radius 2px border-bottom-right-radius 2px - height 26px - line-height 26px + height 34px + line-height 32px .folderList-item:hover, .folderList-item--active:hover .folderList-item-tooltip diff --git a/browser/main/SideNav/StorageItem.styl b/browser/main/SideNav/StorageItem.styl index 619498ec..a06ecb11 100644 --- a/browser/main/SideNav/StorageItem.styl +++ b/browser/main/SideNav/StorageItem.styl @@ -44,7 +44,7 @@ height 36px padding-left 25px padding-right 15px - line-height 22px + line-height 36px cursor pointer font-size 14px border none @@ -147,7 +147,7 @@ body[data-theme="dark"] background-color $ui-dark-button--active-backgroundColor &:active color $ui-dark-text-color - background-color $ui-dark-button--active-backgroundColor + background-color $ui-dark-button--active-backgroundColor .header--active .header-addFolderButton @@ -180,7 +180,7 @@ body[data-theme="dark"] &:active, &:active:hover color $ui-dark-text-color background-color $ui-dark-button--active-backgroundColor - +