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

Allow user to view attachments and clear unused attachments

This commit is contained in:
Nguyễn Việt Hưng
2019-08-27 22:24:36 +12:00
committed by Junyoung Choi
parent cf324d93fe
commit c7d33fbd83
3 changed files with 168 additions and 4 deletions

View File

@@ -624,6 +624,52 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
} }
} }
function getAttachments (markdownContent, storageKey, noteKey) {
if (storageKey == null || noteKey == null || markdownContent == null) {
return
}
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 attachmentsNotInNotePaths = []
const attachmentsInNotePaths = []
const allAttachments = []
for (const file of files) {
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
attachmentsNotInNotePaths.push(absolutePathOfFile)
} else {
attachmentsInNotePaths.push(absolutePathOfFile)
}
allAttachments.push(absolutePathOfFile)
}
resolve({
allAttachments,
attachmentsNotInNotePaths,
attachmentsInNotePaths
})
})
})
} else {
return null
}
}
/** /**
* Clones the attachments of a given note. * 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. * 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.
@@ -728,6 +774,7 @@ module.exports = {
removeStorageAndNoteReferences, removeStorageAndNoteReferences,
deleteAttachmentFolder, deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote, deleteAttachmentsNotPresentInNote,
getAttachments,
moveAttachments, moveAttachments,
cloneAttachments, cloneAttachments,
isAttachmentLink, isAttachmentLink,

View File

@@ -3,8 +3,10 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './StoragesTab.styl' import styles from './StoragesTab.styl'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import StorageItem from './StorageItem' import StorageItem from './StorageItem'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import fs from 'fs'
const electron = require('electron') const electron = require('electron')
const { shell, remote } = electron const { shell, remote } = electron
@@ -25,6 +27,20 @@ function browseFolder () {
}) })
} }
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]
}
class StoragesTab extends React.Component { class StoragesTab extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -35,8 +51,26 @@ class StoragesTab extends React.Component {
name: 'Unnamed', name: 'Unnamed',
type: 'FILESYSTEM', type: 'FILESYSTEM',
path: '' path: ''
} },
attachments: []
} }
this.loadAttachmentStorage()
}
loadAttachmentStorage () {
const promises = []
this.props.data.noteMap.map(note => {
const promise = attachmentManagement.getAttachments(
note.content,
note.storage,
note.key
)
if (promise) promises.push(promise)
})
Promise.all(promises)
.then(data => this.setState({attachments: data}))
.catch(console.error)
} }
handleAddStorageButton (e) { handleAddStorageButton (e) {
@@ -57,8 +91,61 @@ class StoragesTab extends React.Component {
e.preventDefault() e.preventDefault()
} }
removeAllAttachments (attachments) {
const promises = []
for (const attachment of attachments) {
for (const file of attachment) {
const promise = new Promise((resolve, reject) => {
fs.unlink(file, (err) => {
if (err) {
console.error('Could not delete "%s"', file)
console.error(err)
reject(err)
return
}
resolve()
})
})
promises.push(promise)
}
}
Promise.all(promises)
.then(() => this.loadAttachmentStorage())
.catch(console.error)
}
renderList () { renderList () {
const { data, boundingBox } = this.props const { data, boundingBox } = this.props
const { attachments } = this.state
const totalUnusedAttachments = attachments
.reduce((acc, curr) => acc + curr.attachmentsNotInNotePaths.length, 0)
const totalInuseAttachments = attachments
.reduce((acc, curr) => acc + curr.attachmentsInNotePaths.length, 0)
const totalAttachments = totalUnusedAttachments + totalInuseAttachments
const unusedAttachments = attachments.reduce((acc, curr) => {
acc.push(curr.attachmentsNotInNotePaths)
return acc
}, [])
const totalUnusedAttachmentsSize = attachments
.reduce((acc, curr) => {
return acc + curr.attachmentsNotInNotePaths.reduce((racc, rcurr) => {
const stats = fs.statSync(rcurr)
const fileSizeInBytes = stats.size
return racc + fileSizeInBytes
}, 0)
}, 0)
const totalInuseAttachmentsSize = attachments
.reduce((acc, curr) => {
return acc + curr.attachmentsInNotePaths.reduce((racc, rcurr) => {
const stats = fs.statSync(rcurr)
const fileSizeInBytes = stats.size
return racc + fileSizeInBytes
}, 0)
}, 0)
const totalAttachmentsSize = totalUnusedAttachmentsSize + totalInuseAttachmentsSize
if (!boundingBox) { return null } if (!boundingBox) { return null }
const storageList = data.storageMap.map((storage) => { const storageList = data.storageMap.map((storage) => {
@@ -82,6 +169,19 @@ class StoragesTab extends React.Component {
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')} <i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
</button> </button>
</div> </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.removeAllAttachments(unusedAttachments)}>
{i18n.__('Clear unused attachments')}
</button>
</div> </div>
) )
} }

View File

@@ -33,6 +33,17 @@
colorDefaultButton() colorDefaultButton()
font-size $tab--button-font-size font-size $tab--button-font-size
border-radius 2px 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 .addStorage
margin-bottom 15px margin-bottom 15px
@@ -154,8 +165,8 @@ body[data-theme="dark"]
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
.list-attachement-clear-button
colorDarkPrimaryButton()
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
@@ -194,6 +205,8 @@ body[data-theme="solarized-dark"]
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
.list-attachement-clear-button
colorSolarizedDarkPrimaryButton()
body[data-theme="monokai"] body[data-theme="monokai"]
.root .root
@@ -232,6 +245,8 @@ body[data-theme="monokai"]
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-monokai-borderColor border-color $ui-monokai-borderColor
.list-attachement-clear-button
colorMonokaiPrimaryButton()
body[data-theme="dracula"] body[data-theme="dracula"]
.root .root
@@ -269,4 +284,6 @@ body[data-theme="dracula"]
colorDarkPrimaryButton() colorDarkPrimaryButton()
.addStorage-body-control-cancelButton .addStorage-body-control-cancelButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-dracula-borderColor border-color $ui-dracula-borderColor
.list-attachement-clear-button
colorDraculaPrimaryButton()