mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
327 lines
14 KiB
JavaScript
327 lines
14 KiB
JavaScript
const uniqueSlug = require('unique-slug')
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const findStorage = require('browser/lib/findStorage')
|
|
const mdurl = require('mdurl')
|
|
const fse = require('fs-extra')
|
|
const escapeStringRegexp = require('escape-string-regexp')
|
|
const sander = require('sander')
|
|
|
|
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
|
const DESTINATION_FOLDER = 'attachments'
|
|
|
|
/**
|
|
* @description
|
|
* Copies a copy of an attachment to the storage folder specified by the given key and return the generated attachment name.
|
|
* Renames the file to match a unique file name.
|
|
*
|
|
* @param {String} sourceFilePath The source path of the attachment to be copied
|
|
* @param {String} storageKey Storage key of the destination storage
|
|
* @param {String} noteKey Key of the current note. Will be used as subfolder in :storage
|
|
* @param {boolean} useRandomName determines whether a random filename for the new file is used. If false the source file name is used
|
|
* @return {Promise<String>} name (inclusive extension) of the generated file
|
|
*/
|
|
function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = true) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!sourceFilePath) {
|
|
reject('sourceFilePath has to be given')
|
|
}
|
|
|
|
if (!storageKey) {
|
|
reject('storageKey has to be given')
|
|
}
|
|
|
|
if (!noteKey) {
|
|
reject('noteKey has to be given')
|
|
}
|
|
|
|
try {
|
|
if (!fs.existsSync(sourceFilePath)) {
|
|
reject('source file does not exist')
|
|
}
|
|
|
|
const targetStorage = findStorage.findStorage(storageKey)
|
|
|
|
const inputFileStream = fs.createReadStream(sourceFilePath)
|
|
let destinationName
|
|
if (useRandomName) {
|
|
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
|
} else {
|
|
destinationName = path.basename(sourceFilePath)
|
|
}
|
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
|
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
|
inputFileStream.pipe(outputFile)
|
|
inputFileStream.on('end', () => {
|
|
resolve(destinationName)
|
|
})
|
|
} catch (e) {
|
|
return reject(e)
|
|
}
|
|
})
|
|
}
|
|
|
|
function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
|
|
let destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER)
|
|
if (!fs.existsSync(destinationDir)) {
|
|
fs.mkdirSync(destinationDir)
|
|
}
|
|
destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER, noteKey)
|
|
if (!fs.existsSync(destinationDir)) {
|
|
fs.mkdirSync(destinationDir)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description Fixes the URLs embedded in the generated HTML so that they again refer actual local files.
|
|
* @param {String} renderedHTML HTML in that the links should be fixed
|
|
* @param {String} storagePath Path of the current storage
|
|
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
|
|
*/
|
|
function fixLocalURLS (renderedHTML, storagePath) {
|
|
return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
|
|
}
|
|
|
|
/**
|
|
* @description Generates the markdown code for a given attachment
|
|
* @param {String} fileName Name of the attachment
|
|
* @param {String} path Path of the attachment
|
|
* @param {Boolean} showPreview Indicator whether the generated markdown should show a preview of the image. Note that at the moment only previews for images are supported
|
|
* @returns {String} Generated markdown code
|
|
*/
|
|
function generateAttachmentMarkdown (fileName, path, showPreview) {
|
|
return `${showPreview ? '!' : ''}[${fileName}](${path})`
|
|
}
|
|
|
|
/**
|
|
* @description Handles the drop-event of a file. Includes the necessary markdown code and copies the file to the corresponding storage folder.
|
|
* The method calls {CodeEditor#insertAttachmentMd()} to include the generated markdown at the needed place!
|
|
* @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
|
|
* @param {String} storageKey Key of the current storage
|
|
* @param {String} noteKey Key of the current note
|
|
* @param {Event} dropEvent DropEvent
|
|
*/
|
|
function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
|
|
const file = dropEvent.dataTransfer.files[0]
|
|
const filePath = file.path
|
|
const originalFileName = path.basename(filePath)
|
|
const fileType = file['type']
|
|
|
|
copyAttachment(filePath, storageKey, noteKey).then((fileName) => {
|
|
const showPreview = fileType.startsWith('image')
|
|
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview)
|
|
codeEditor.insertAttachmentMd(imageMd)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code
|
|
* @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
|
|
* @param {String} storageKey Key of the current storage
|
|
* @param {String} noteKey Key of the current note
|
|
* @param {DataTransferItem} dataTransferItem Part of the past-event
|
|
*/
|
|
function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) {
|
|
if (!codeEditor) {
|
|
throw new Error('codeEditor has to be given')
|
|
}
|
|
if (!storageKey) {
|
|
throw new Error('storageKey has to be given')
|
|
}
|
|
|
|
if (!noteKey) {
|
|
throw new Error('noteKey has to be given')
|
|
}
|
|
if (!dataTransferItem) {
|
|
throw new Error('dataTransferItem has to be given')
|
|
}
|
|
|
|
const blob = dataTransferItem.getAsFile()
|
|
const reader = new FileReader()
|
|
let base64data
|
|
const targetStorage = findStorage.findStorage(storageKey)
|
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
|
|
|
const imageName = `${uniqueSlug()}.png`
|
|
const imagePath = path.join(destinationDir, imageName)
|
|
|
|
reader.onloadend = function () {
|
|
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
|
base64data += base64data.replace('+', ' ')
|
|
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
|
fs.writeFileSync(imagePath, binaryData, 'binary')
|
|
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
|
|
codeEditor.insertAttachmentMd(imageMd)
|
|
}
|
|
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 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)
|
|
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)
|
|
}
|
|
|
|
/**
|
|
* @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) {
|
|
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
|
|
}
|
|
|
|
/**
|
|
* @description Moves the attachments of the current note to the new location.
|
|
* Returns a modified version of the given content so that the links to the attachments point to the new note key.
|
|
* @param {String} oldPath Source of the note to be moved
|
|
* @param {String} newPath Destination of the note to be moved
|
|
* @param {String} noteKey Old note key
|
|
* @param {String} newNoteKey New note key
|
|
* @param {String} noteContent Content of the note to be moved
|
|
* @returns {String} Modified version of noteContent in which the paths of the attachments are fixed
|
|
*/
|
|
function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
|
|
const src = path.join(oldPath, DESTINATION_FOLDER, noteKey)
|
|
const dest = path.join(newPath, DESTINATION_FOLDER, newNoteKey)
|
|
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) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
|
|
}
|
|
return noteContent
|
|
}
|
|
|
|
/**
|
|
* @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)
|
|
}
|
|
|
|
/**
|
|
* @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.rimrafSync(noteAttachmentPath)
|
|
}
|
|
|
|
/**
|
|
* @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..")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 oldNote Note that is being cloned
|
|
* @param newNote Clone of the note
|
|
*/
|
|
function cloneAttachments (oldNote, newNote) {
|
|
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)
|
|
}
|
|
|
|
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')
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
copyAttachment,
|
|
fixLocalURLS,
|
|
generateAttachmentMarkdown,
|
|
handleAttachmentDrop,
|
|
handlePastImageEvent,
|
|
getAttachmentsInContent,
|
|
getAbsolutePathsOfAttachmentsInContent,
|
|
removeStorageAndNoteReferences,
|
|
deleteAttachmentFolder,
|
|
deleteAttachmentsNotPresentInNote,
|
|
moveAttachments,
|
|
cloneAttachments,
|
|
STORAGE_FOLDER_PLACEHOLDER,
|
|
DESTINATION_FOLDER
|
|
}
|