From 897f53b13eb7ade7c99f8748cff3d5ff3fc8858b Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 21 Nov 2018 00:36:23 +0000 Subject: [PATCH] All: Resolves #846: Set resource path to correct relative path so that for example images show up in Markdown viewers --- CliClient/tests/pathUtils.js | 1 + ReactNativeClient/lib/path-utils.js | 9 +++ .../lib/services/InteropService.js | 56 ++++++++++++------- .../services/InteropService_Exporter_Base.js | 8 +++ .../services/InteropService_Exporter_Md.js | 32 +++++++++-- 5 files changed, 80 insertions(+), 26 deletions(-) diff --git a/CliClient/tests/pathUtils.js b/CliClient/tests/pathUtils.js index 91378c404..9114bf1fe 100644 --- a/CliClient/tests/pathUtils.js +++ b/CliClient/tests/pathUtils.js @@ -21,6 +21,7 @@ describe('pathUtils', function() { ['con', '___'], ['no space at the end ', 'no space at the end'], ['nor dots...', 'nor dots'], + [' no space before either', 'no space before either'], ['thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong', 'thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong'], ]; diff --git a/ReactNativeClient/lib/path-utils.js b/ReactNativeClient/lib/path-utils.js index e9d67c124..7bc0646f7 100644 --- a/ReactNativeClient/lib/path-utils.js +++ b/ReactNativeClient/lib/path-utils.js @@ -86,6 +86,15 @@ function friendlySafeFilename(e, maxLength = null) { } } + while (output.length) { + const c = output[0]; + if (c === ' ') { + output = output.substr(1, output.length - 1); + } else { + break; + } + } + if (!output) return _('Untitled'); return output.substr(0, maxLength); diff --git a/ReactNativeClient/lib/services/InteropService.js b/ReactNativeClient/lib/services/InteropService.js index c67f0b947..6e6080cda 100644 --- a/ReactNativeClient/lib/services/InteropService.js +++ b/ReactNativeClient/lib/services/InteropService.js @@ -236,32 +236,46 @@ class InteropService { const exporter = this.newModule_('exporter', exportFormat); await exporter.init(exportPath); - for (let i = 0; i < itemsToExport.length; i++) { - const itemType = itemsToExport[i].type; - const ItemClass = BaseItem.getClassByItemType(itemType); - const itemOrId = itemsToExport[i].itemOrId; - const item = typeof itemOrId === 'object' ? itemOrId : await ItemClass.load(itemOrId); + const typeOrder = [BaseModel.TYPE_FOLDER, BaseModel.TYPE_RESOURCE, BaseModel.TYPE_NOTE, BaseModel.TYPE_TAG, BaseModel.TYPE_NOTE_TAG]; + const context = { + resourcePaths: {}, + }; - if (!item) { - if (itemType === BaseModel.TYPE_RESOURCE) { - result.warnings.push(sprintf('A resource that does not exist is referenced in a note. The resource was skipped. Resource ID: %s', itemOrId)); - } else { - result.warnings.push(sprintf('Cannot find item with type "%s" and ID %s. Item was skipped.', ItemClass.tableName(), JSON.stringify(itemOrId))); - } - continue; - } + for (let typeOrderIndex = 0; typeOrderIndex < typeOrder.length; typeOrderIndex++) { + const type = typeOrder[typeOrderIndex]; - if (item.encryption_applied || item.encryption_blob_encrypted) throw new Error(_('This item is currently encrypted: %s "%s". Please wait for all items to be decrypted and try again.', BaseModel.modelTypeToName(itemType), item.title ? item.title : item.id)); + for (let i = 0; i < itemsToExport.length; i++) { + const itemType = itemsToExport[i].type; - try { - if (itemType == BaseModel.TYPE_RESOURCE) { - const resourcePath = Resource.fullPath(item); - await exporter.processResource(item, resourcePath); + if (itemType !== type) continue; + + const ItemClass = BaseItem.getClassByItemType(itemType); + const itemOrId = itemsToExport[i].itemOrId; + const item = typeof itemOrId === 'object' ? itemOrId : await ItemClass.load(itemOrId); + + if (!item) { + if (itemType === BaseModel.TYPE_RESOURCE) { + result.warnings.push(sprintf('A resource that does not exist is referenced in a note. The resource was skipped. Resource ID: %s', itemOrId)); + } else { + result.warnings.push(sprintf('Cannot find item with type "%s" and ID %s. Item was skipped.', ItemClass.tableName(), JSON.stringify(itemOrId))); + } + continue; } - await exporter.processItem(ItemClass, item); - } catch (error) { - result.warnings.push(error.message); + if (item.encryption_applied || item.encryption_blob_encrypted) throw new Error(_('This item is currently encrypted: %s "%s". Please wait for all items to be decrypted and try again.', BaseModel.modelTypeToName(itemType), item.title ? item.title : item.id)); + + try { + if (itemType == BaseModel.TYPE_RESOURCE) { + const resourcePath = Resource.fullPath(item); + context.resourcePaths[item.id] = resourcePath; + exporter.updateContext(context); + await exporter.processResource(item, resourcePath); + } + + await exporter.processItem(ItemClass, item); + } catch (error) { + result.warnings.push(error.message); + } } } diff --git a/ReactNativeClient/lib/services/InteropService_Exporter_Base.js b/ReactNativeClient/lib/services/InteropService_Exporter_Base.js index 14b1e147b..7dec80b8c 100644 --- a/ReactNativeClient/lib/services/InteropService_Exporter_Base.js +++ b/ReactNativeClient/lib/services/InteropService_Exporter_Base.js @@ -13,6 +13,14 @@ class InteropService_Exporter_Base { return this.metadata_; } + updateContext(context) { + this.context_ = context; + } + + context() { + return this.context_; + } + async temporaryDirectory_(createIt) { const md5 = require('md5'); const tempDir = require('os').tmpdir() + '/' + md5(Math.random() + Date.now()); diff --git a/ReactNativeClient/lib/services/InteropService_Exporter_Md.js b/ReactNativeClient/lib/services/InteropService_Exporter_Md.js index 13d22162b..611026781 100644 --- a/ReactNativeClient/lib/services/InteropService_Exporter_Md.js +++ b/ReactNativeClient/lib/services/InteropService_Exporter_Md.js @@ -1,5 +1,5 @@ const InteropService_Exporter_Base = require('lib/services/InteropService_Exporter_Base'); -const { basename, filename, friendlySafeFilename } = require('lib/path-utils.js'); +const { basename, filename, friendlySafeFilename, rtrimSlashes } = require('lib/path-utils.js'); const BaseModel = require('lib/BaseModel'); const Folder = require('lib/models/Folder'); const Note = require('lib/models/Note'); @@ -16,12 +16,16 @@ class InteropService_Exporter_Md extends InteropService_Exporter_Base { await shim.fsDriver().mkdir(this.resourceDir_); } - async makeDirPath_(item) { + async makeDirPath_(item, pathPart = null) { let output = ''; while (true) { if (item.type_ === BaseModel.TYPE_FOLDER) { - output = friendlySafeFilename(item.title, null, true) + '/' + output; - output = await shim.fsDriver().findUniqueFilename(output); + if (pathPart) { + output = pathPart + '/' + output; + } else { + output = friendlySafeFilename(item.title, null, true) + '/' + output; + output = await shim.fsDriver().findUniqueFilename(output); + } } if (!item.parent_id) return output; item = await Folder.load(item.parent_id); @@ -29,6 +33,22 @@ class InteropService_Exporter_Md extends InteropService_Exporter_Base { return output; } + async replaceResourceIdsByRelativePaths_(item) { + const linkedResourceIds = await Note.linkedResourceIds(item.body); + const relativePath = rtrimSlashes(await this.makeDirPath_(item, '..')); + const resourcePaths = this.context() && this.context().resourcePaths ? this.context().resourcePaths : {}; + + let newBody = item.body; + + for (let i = 0; i < linkedResourceIds.length; i++) { + const id = linkedResourceIds[i]; + const resourcePath = relativePath + '/_resources/' + basename(resourcePaths[id]); + newBody = newBody.replace(new RegExp(':/' + id, 'g'), resourcePath); + } + + return newBody; + } + async processItem(ItemClass, item) { if ([BaseModel.TYPE_NOTE, BaseModel.TYPE_FOLDER].indexOf(item.type_) < 0) return; @@ -42,7 +62,9 @@ class InteropService_Exporter_Md extends InteropService_Exporter_Base { if (item.type_ === BaseModel.TYPE_NOTE) { let noteFilePath = dirPath + '/' + friendlySafeFilename(item.title, null, true) + '.md'; noteFilePath = await shim.fsDriver().findUniqueFilename(noteFilePath); - const noteContent = await Note.serializeForEdit(item); + const noteBody = await this.replaceResourceIdsByRelativePaths_(item); + const modNote = Object.assign({}, item, { body: noteBody }); + const noteContent = await Note.serializeForEdit(modNote); await shim.fsDriver().writeFile(noteFilePath, noteContent, 'utf-8'); } }