mirror of https://github.com/laurent22/joplin.git
All: Resolves #846: Set resource path to correct relative path so that for example images show up in Markdown viewers
parent
45cd8b7e3c
commit
897f53b13e
|
@ -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'],
|
||||
];
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue