Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions browser/components/CodeEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export default class CodeEditor extends React.Component {
storageKey,
noteKey
} = this.props
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
if (this.props.deleteUnusedAttachments === true) {
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
}
}
this.pasteHandler = (editor, e) => {
e.preventDefault()
Expand Down Expand Up @@ -632,6 +634,9 @@ export default class CodeEditor extends React.Component {
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
}
}
if (prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments) {
this.editor.setOption('deleteUnusedAttachments', this.props.deleteUnusedAttachments)
}

if (needRefresh) {
this.editor.refresh()
Expand Down Expand Up @@ -1204,7 +1209,8 @@ CodeEditor.propTypes = {
autoDetect: PropTypes.bool,
spellCheck: PropTypes.bool,
enableMarkdownLint: PropTypes.bool,
customMarkdownLintConfig: PropTypes.string
customMarkdownLintConfig: PropTypes.string,
deleteUnusedAttachments: PropTypes.bool
}

CodeEditor.defaultProps = {
Expand All @@ -1219,5 +1225,6 @@ CodeEditor.defaultProps = {
spellCheck: false,
enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint,
customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig,
prettierConfig: DEFAULT_CONFIG.editor.prettierConfig
prettierConfig: DEFAULT_CONFIG.editor.prettierConfig,
deleteUnusedAttachments: DEFAULT_CONFIG.editor.deleteUnusedAttachments
}
1 change: 1 addition & 0 deletions browser/components/MarkdownEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ class MarkdownEditor extends React.Component {
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
prettierConfig={config.editor.prettierConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
/>
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview'
Expand Down
1 change: 1 addition & 0 deletions browser/components/MarkdownSplitEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class MarkdownSplitEditor extends React.Component {
switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
/>
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
<div styleName='slider-hitbox' />
Expand Down
17 changes: 16 additions & 1 deletion browser/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,24 @@ export function isMarkdownTitleURL (str) {
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
}

export function humanFileSize (bytes) {
const threshold = 1000
if (Math.abs(bytes) < threshold) {
return bytes + ' B'
}
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
var u = -1
do {
bytes /= threshold
++u
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
return bytes.toFixed(1) + ' ' + units[u]
}

export default {
lastFindInArray,
escapeHtmlCharacters,
isObjectEqual,
isMarkdownTitleURL
isMarkdownTitleURL,
humanFileSize
}
4 changes: 2 additions & 2 deletions browser/main/lib/ConfigManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export const DEFAULT_CONFIG = {
"tabWidth": 4,
"semi": false,
"singleQuote": true
}`

}`,
deleteUnusedAttachments: true
},
preview: {
fontSize: '14',
Expand Down
72 changes: 72 additions & 0 deletions browser/main/lib/dataApi/attachmentManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,76 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
}
}

/**
* @description Get all existing attachments related to a specific note
including their status (in use or not) and their path. Return null if there're no attachment related to note or specified parametters are invalid
* @param markdownContent markdownContent of the current note
* @param storageKey StorageKey of the current note
* @param noteKey NoteKey of the currentNote
* @return {Promise<Array<{path: String, isInUse: bool}>>} Promise returning the
list of attachments with their properties */
function getAttachmentsPathAndStatus (markdownContent, storageKey, noteKey) {
if (storageKey == null || noteKey == null || markdownContent == null) {
return null
}
const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
const attachmentsInNote = getAttachmentsInMarkdownContent(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)) {
return new Promise((resolve, reject) => {
fs.readdir(attachmentFolder, (err, files) => {
if (err) {
console.error('Error reading directory "' + attachmentFolder + '". Error:')
console.error(err)
reject(err)
return
}
const attachments = []
for (const file of files) {
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
attachments.push({ path: absolutePathOfFile, isInUse: false })
} else {
attachments.push({ path: absolutePathOfFile, isInUse: true })
}
}
resolve(attachments)
})
})
} else {
return null
}
}

/**
* @description Remove all specified attachment paths
* @param attachments attachment paths
* @return {Promise} Promise after all attachments are removed */
function removeAttachmentsByPaths (attachments) {
const promises = []
for (const attachment of attachments) {
const promise = new Promise((resolve, reject) => {
fs.unlink(attachment, (err) => {
if (err) {
console.error('Could not delete "%s"', attachment)
console.error(err)
reject(err)
return
}
resolve()
})
})
promises.push(promise)
}
return Promise.all(promises)
}

/**
* 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.
Expand Down Expand Up @@ -726,8 +796,10 @@ module.exports = {
getAbsolutePathsOfAttachmentsInContent,
importAttachments,
removeStorageAndNoteReferences,
removeAttachmentsByPaths,
deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote,
getAttachmentsPathAndStatus,
moveAttachments,
cloneAttachments,
isAttachmentLink,
Expand Down
71 changes: 70 additions & 1 deletion browser/main/modals/PreferencesModal/StoragesTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StoragesTab.styl'
import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import StorageItem from './StorageItem'
import i18n from 'browser/lib/i18n'
import { humanFileSize } from 'browser/lib/utils'
import fs from 'fs'

const electron = require('electron')
const { shell, remote } = electron
Expand Down Expand Up @@ -35,8 +38,29 @@ class StoragesTab extends React.Component {
name: 'Unnamed',
type: 'FILESYSTEM',
path: ''
}
},
attachments: []
}
this.loadAttachmentStorage()
}

loadAttachmentStorage () {
const promises = []
this.props.data.noteMap.map(note => {
const promise = attachmentManagement.getAttachmentsPathAndStatus(
note.content,
note.storage,
note.key
)
if (promise) promises.push(promise)
})

Promise.all(promises)
.then(data => {
const result = data.reduce((acc, curr) => acc.concat(curr), [])
this.setState({attachments: result})
})
.catch(console.error)
}

handleAddStorageButton (e) {
Expand All @@ -57,8 +81,39 @@ class StoragesTab extends React.Component {
e.preventDefault()
}

handleRemoveUnusedAttachments (attachments) {
attachmentManagement.removeAttachmentsByPaths(attachments)
.then(() => this.loadAttachmentStorage())
.catch(console.error)
}

renderList () {
const { data, boundingBox } = this.props
const { attachments } = this.state

const unusedAttachments = attachments.filter(attachment => !attachment.isInUse)
const inUseAttachments = attachments.filter(attachment => attachment.isInUse)

const totalUnusedAttachments = unusedAttachments.length
const totalInuseAttachments = inUseAttachments.length
const totalAttachments = totalUnusedAttachments + totalInuseAttachments

const totalUnusedAttachmentsSize = unusedAttachments
.reduce((acc, curr) => {
const stats = fs.statSync(curr.path)
const fileSizeInBytes = stats.size
return acc + fileSizeInBytes
}, 0)
const totalInuseAttachmentsSize = inUseAttachments
.reduce((acc, curr) => {
const stats = fs.statSync(curr.path)
const fileSizeInBytes = stats.size
return acc + fileSizeInBytes
}, 0)
const totalAttachmentsSize = totalUnusedAttachmentsSize + totalInuseAttachmentsSize

const unusedAttachmentPaths = unusedAttachments
.reduce((acc, curr) => acc.concat(curr.path), [])

if (!boundingBox) { return null }
const storageList = data.storageMap.map((storage) => {
Expand All @@ -82,6 +137,20 @@ class StoragesTab extends React.Component {
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
</button>
</div>
<div styleName='header'>{i18n.__('Attachment storage')}</div>
<p styleName='list-attachment-label'>
Unused attachments size: {humanFileSize(totalUnusedAttachmentsSize)} ({totalUnusedAttachments} items)
</p>
<p styleName='list-attachment-label'>
In use attachments size: {humanFileSize(totalInuseAttachmentsSize)} ({totalInuseAttachments} items)
</p>
<p styleName='list-attachment-label'>
Total attachments size: {humanFileSize(totalAttachmentsSize)} ({totalAttachments} items)
</p>
<button styleName='list-attachement-clear-button'
onClick={() => this.handleRemoveUnusedAttachments(unusedAttachmentPaths)}>
{i18n.__('Clear unused attachments')}
</button>
</div>
)
}
Expand Down
23 changes: 20 additions & 3 deletions browser/main/modals/PreferencesModal/StoragesTab.styl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@
colorDefaultButton()
font-size $tab--button-font-size
border-radius 2px
.list-attachment-label
margin-bottom 10px
color $ui-text-color
.list-attachement-clear-button
height 30px
border none
border-top-right-radius 2px
border-bottom-right-radius 2px
colorPrimaryButton()
vertical-align middle
padding 0 20px

.addStorage
margin-bottom 15px
Expand Down Expand Up @@ -154,8 +165,8 @@ body[data-theme="dark"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-dark-borderColor


.list-attachement-clear-button
colorDarkPrimaryButton()

body[data-theme="solarized-dark"]
.root
Expand Down Expand Up @@ -194,6 +205,8 @@ body[data-theme="solarized-dark"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-solarized-dark-borderColor
.list-attachement-clear-button
colorSolarizedDarkPrimaryButton()

body[data-theme="monokai"]
.root
Expand Down Expand Up @@ -232,6 +245,8 @@ body[data-theme="monokai"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-monokai-borderColor
.list-attachement-clear-button
colorMonokaiPrimaryButton()

body[data-theme="dracula"]
.root
Expand Down Expand Up @@ -269,4 +284,6 @@ body[data-theme="dracula"]
colorDarkPrimaryButton()
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-dracula-borderColor
border-color $ui-dracula-borderColor
.list-attachement-clear-button
colorDraculaPrimaryButton()
14 changes: 12 additions & 2 deletions browser/main/modals/PreferencesModal/UiTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ class UiTab extends React.Component {
enableSmartPaste: this.refs.enableSmartPaste.checked,
enableMarkdownLint: this.refs.enableMarkdownLint.checked,
customMarkdownLintConfig: this.customMarkdownLintConfigCM.getCodeMirror().getValue(),
prettierConfig: this.prettierConfigCM.getCodeMirror().getValue()

prettierConfig: this.prettierConfigCM.getCodeMirror().getValue(),
deleteUnusedAttachments: this.refs.deleteUnusedAttachments.checked
},
preview: {
fontSize: this.refs.previewFontSize.value,
Expand Down Expand Up @@ -618,6 +618,16 @@ class UiTab extends React.Component {
{i18n.__('Enable spellcheck - Experimental feature!! :)')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.deleteUnusedAttachments}
ref='deleteUnusedAttachments'
type='checkbox'
/>&nbsp;
{i18n.__('Delete attachments, that are not referenced in the text anymore')}
</label>
</div>

<div styleName='group-section'>
<div styleName='group-section-label'>
Expand Down
Loading