mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
All: Resolves #8684: Apply correct size to images imported from ENEX files
This commit is contained in:
parent
a14674aaa8
commit
dcd3def942
File diff suppressed because one or more lines are too long
@ -0,0 +1,17 @@
|
||||
![](:/RESOURCE_ID_1)
|
||||
|
||||
#### Last Transfer
|
||||
|
||||
<img src=":/RESOURCE_ID_2" width="65" height="65" alt="bank.svg"/>
|
||||
|
||||
##### **Next Day Bank Deposit / USD**
|
||||
|
||||
###### **March 5, 2023 04:28AM**
|
||||
|
||||
* * *
|
||||
|
||||
Processing
|
||||
**Confirmation**: ILbwHO5Z06p7meW
|
||||
|
||||
![](https://joplinapp.org/images/logo-text.svg)
|
||||
<img src="https://joplinapp.org/images/logo-text.svg" width="100" height="50"/>
|
@ -1,7 +1,7 @@
|
||||
import { NoteEntity, ResourceEntity, TagEntity } from './services/database/types';
|
||||
import shim from './shim';
|
||||
|
||||
const fs = require('fs-extra');
|
||||
import { readFile, stat } from 'fs/promises';
|
||||
const os = require('os');
|
||||
const { filename } = require('./path-utils');
|
||||
import { setupDatabaseAndSynchronizer, switchClient, expectNotThrow, supportDir, expectThrow } from './testing/test-utils';
|
||||
@ -13,6 +13,16 @@ import Resource from './models/Resource';
|
||||
|
||||
const enexSampleBaseDir = `${supportDir}/../enex_to_md`;
|
||||
|
||||
const importEnexFile = async (filename: string) => {
|
||||
const filePath = `${enexSampleBaseDir}/${filename}`;
|
||||
await importEnex('', filePath);
|
||||
};
|
||||
|
||||
const readExpectedFile = async (filename: string) => {
|
||||
const filePath = `${enexSampleBaseDir}/${filename}`;
|
||||
return readFile(filePath, 'utf8');
|
||||
};
|
||||
|
||||
describe('import-enex-md-gen', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -65,8 +75,7 @@ describe('import-enex-md-gen', () => {
|
||||
});
|
||||
|
||||
it('should import ENEX metadata', async () => {
|
||||
const filePath = `${enexSampleBaseDir}/sample-enex.xml`;
|
||||
await importEnex('', filePath);
|
||||
await importEnexFile('sample-enex.xml');
|
||||
|
||||
const note: NoteEntity = (await Note.all())[0];
|
||||
expect(note.title).toBe('Test Note for Export');
|
||||
@ -87,37 +96,33 @@ describe('import-enex-md-gen', () => {
|
||||
|
||||
const resource: ResourceEntity = (await Resource.all())[0];
|
||||
expect(resource.id).toBe('3d0f4d01abc02cf8c4dc1c796df8c4b2');
|
||||
const stat = await fs.stat(Resource.fullPath(resource));
|
||||
expect(stat.size).toBe(277);
|
||||
const s = await stat(Resource.fullPath(resource));
|
||||
expect(s.size).toBe(277);
|
||||
});
|
||||
|
||||
it('should handle invalid dates', async () => {
|
||||
const filePath = `${enexSampleBaseDir}/invalid_date.enex`;
|
||||
await importEnex('', filePath);
|
||||
await importEnexFile('invalid_date.enex');
|
||||
const note: NoteEntity = (await Note.all())[0];
|
||||
expect(note.created_time).toBe(1521822724000); // 20180323T163204Z
|
||||
expect(note.updated_time).toBe(1521822724000); // Because this date was invalid, it is set to the created time instead
|
||||
});
|
||||
|
||||
it('should handle empty resources', async () => {
|
||||
const filePath = `${enexSampleBaseDir}/empty_resource.enex`;
|
||||
await expectNotThrow(() => importEnex('', filePath));
|
||||
await expectNotThrow(() => importEnexFile('empty_resource.enex'));
|
||||
const all = await Resource.all();
|
||||
expect(all.length).toBe(1);
|
||||
expect(all[0].size).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle tasks', async () => {
|
||||
const filePath = `${enexSampleBaseDir}/tasks.enex`;
|
||||
await importEnex('', filePath);
|
||||
await importEnexFile('tasks.enex');
|
||||
const expectedMd = await shim.fsDriver().readFile(`${enexSampleBaseDir}/tasks.md`);
|
||||
const note: NoteEntity = (await Note.all())[0];
|
||||
expect(note.body).toEqual(expectedMd);
|
||||
});
|
||||
|
||||
it('should handle empty note content', async () => {
|
||||
const filePath = `${enexSampleBaseDir}/empty_content.enex`;
|
||||
await expectNotThrow(() => importEnex('', filePath));
|
||||
await importEnexFile('empty_content.enex');
|
||||
const all = await Note.all();
|
||||
expect(all.length).toBe(1);
|
||||
expect(all[0].title).toBe('China and the case for stimulus.');
|
||||
@ -131,8 +136,7 @@ describe('import-enex-md-gen', () => {
|
||||
// type "application/octet-stream", which can later cause problems to
|
||||
// open the file.
|
||||
// https://discourse.joplinapp.org/t/importing-a-note-with-a-zip-file/12123?u=laurent
|
||||
const filePath = `${enexSampleBaseDir}/WithInvalidMime.enex`;
|
||||
await importEnex('', filePath);
|
||||
await importEnexFile('WithInvalidMime.enex');
|
||||
const all = await Resource.all();
|
||||
expect(all.length).toBe(1);
|
||||
expect(all[0].mime).toBe('application/zip');
|
||||
@ -154,8 +158,26 @@ describe('import-enex-md-gen', () => {
|
||||
});
|
||||
|
||||
it('should throw an error and stop if the outer XML is invalid', async () => {
|
||||
const filePath = `${enexSampleBaseDir}/invalid_html.enex`;
|
||||
await expectThrow(async () => importEnex('', filePath));
|
||||
await expectThrow(async () => importEnexFile('invalid_html.enex'));
|
||||
});
|
||||
|
||||
it('should import images with sizes', async () => {
|
||||
await importEnexFile('images_with_and_without_size.enex');
|
||||
let expected = await readExpectedFile('images_with_and_without_size.md');
|
||||
|
||||
const note: NoteEntity = (await Note.all())[0];
|
||||
|
||||
const all: ResourceEntity[] = await Resource.all();
|
||||
|
||||
expect(all.length).toBe(2);
|
||||
|
||||
const svgResource = all.find(r => r.mime === 'image/svg+xml');
|
||||
const pngResource = all.find(r => r.mime === 'image/png');
|
||||
|
||||
expected = expected.replace(/RESOURCE_ID_1/, pngResource.id);
|
||||
expected = expected.replace(/RESOURCE_ID_2/, svgResource.id);
|
||||
|
||||
expect(note.body).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import markdownUtils from './markdownUtils';
|
||||
import { ResourceEntity } from './services/database/types';
|
||||
import { htmlentities } from '@joplin/utils/html';
|
||||
const stringPadding = require('string-padding');
|
||||
const stringToStream = require('string-to-stream');
|
||||
const resourceUtils = require('./resourceUtils.js');
|
||||
@ -368,26 +369,49 @@ function tagAttributeToMdText(attr: string): string {
|
||||
return attr;
|
||||
}
|
||||
|
||||
function addResourceTag(lines: string[], resource: ResourceEntity, alt = ''): string[] {
|
||||
// Note: refactor to use Resource.markdownTag
|
||||
|
||||
if (!alt) alt = resource.title;
|
||||
if (!alt) alt = resource.filename;
|
||||
if (!alt) alt = '';
|
||||
interface AddResourceOptions {
|
||||
alt?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
alt = tagAttributeToMdText(alt);
|
||||
if (resourceUtils.isImageMimeType(resource.mime)) {
|
||||
lines.push('![');
|
||||
lines.push(alt);
|
||||
lines.push(`](:/${resource.id})`);
|
||||
const addResourceTag = (lines: string[], src: string, mime: string, options: AddResourceOptions): string[] => {
|
||||
const alt = options.alt ? tagAttributeToMdText(options.alt) : '';
|
||||
|
||||
if (resourceUtils.isImageMimeType(mime)) {
|
||||
if (!!options.width || !!options.height) {
|
||||
const attrs: Record<string, string> = { src };
|
||||
if (options.width) attrs.width = options.width.toString();
|
||||
if (options.height) attrs.height = options.height.toString();
|
||||
if (alt) attrs.alt = alt;
|
||||
|
||||
const attrsHtml: string[] = [];
|
||||
for (const [key, value] of Object.entries(attrs)) {
|
||||
attrsHtml.push(`${key}="${htmlentities(value)}"`);
|
||||
}
|
||||
|
||||
lines.push(`<img ${attrsHtml.join(' ')}/>`);
|
||||
} else {
|
||||
lines.push('![');
|
||||
lines.push(alt);
|
||||
lines.push(`](${markdownUtils.escapeLinkUrl(src)})`);
|
||||
}
|
||||
} else {
|
||||
lines.push('[');
|
||||
lines.push(alt);
|
||||
lines.push(`](:/${resource.id})`);
|
||||
lines.push(`](${markdownUtils.escapeLinkUrl(src)})`);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
|
||||
const altFromResource = (resource: ResourceEntity): string => {
|
||||
let alt = '';
|
||||
if (!alt) alt = resource.title;
|
||||
if (!alt) alt = resource.filename;
|
||||
return alt;
|
||||
};
|
||||
|
||||
function isBlockTag(n: string) {
|
||||
return ['div', 'p', 'dl', 'dd', 'dt', 'center', 'address'].indexOf(n) >= 0;
|
||||
@ -806,12 +830,14 @@ function enexXmlToMdArray(stream: any, resources: ResourceEntity[], tasks: Extra
|
||||
} else if (n === 'q') {
|
||||
section.lines.push('"');
|
||||
} else if (n === 'img') {
|
||||
// Many (most?) img tags don't have no source associated,
|
||||
// especially when they were imported from HTML
|
||||
if (nodeAttributes.src) {
|
||||
// Many (most?) img tags don't have no source associated, especially when they were imported from HTML
|
||||
let s = '![';
|
||||
if (nodeAttributes.alt) s += tagAttributeToMdText(nodeAttributes.alt);
|
||||
s += `](${markdownUtils.escapeLinkUrl(nodeAttributes.src)})`;
|
||||
section.lines.push(s);
|
||||
section.lines = addResourceTag(section.lines, nodeAttributes.src, 'image/png', {
|
||||
width: nodeAttributes.width ? Number(nodeAttributes.width) : 0,
|
||||
height: nodeAttributes.height ? Number(nodeAttributes.height) : 0,
|
||||
alt: nodeAttributes.alt ? nodeAttributes.alt : '',
|
||||
});
|
||||
}
|
||||
} else if (isAnchor(n)) {
|
||||
state.anchorAttributes.push(nodeAttributes);
|
||||
@ -928,7 +954,11 @@ function enexXmlToMdArray(stream: any, resources: ResourceEntity[], tasks: Extra
|
||||
// means it's an attachement. It will be appended along with the
|
||||
// other remaining resources at the bottom of the markdown text.
|
||||
if (resource && !!resource.id) {
|
||||
section.lines = addResourceTag(section.lines, resource, nodeAttributes.alt);
|
||||
section.lines = addResourceTag(section.lines, `:/${resource.id}`, resource.mime, {
|
||||
alt: nodeAttributes.alt ? nodeAttributes.alt : altFromResource(resource),
|
||||
width: nodeAttributes.width ? Number(nodeAttributes.width) : 0,
|
||||
height: nodeAttributes.height ? Number(nodeAttributes.height) : 0,
|
||||
});
|
||||
}
|
||||
} else if (n === 'span') {
|
||||
if (isSpanWithStyle(nodeAttributes)) {
|
||||
@ -1411,7 +1441,9 @@ async function enexXmlToMd(xmlString: string, resources: ResourceEntity[], tasks
|
||||
const r = result.resources[i];
|
||||
if (firstAttachment) mdLines.push(NEWLINE);
|
||||
mdLines.push(NEWLINE);
|
||||
mdLines = addResourceTag(mdLines, r, r.filename);
|
||||
mdLines = addResourceTag(mdLines, `:/${r.id}`, r.mime, {
|
||||
alt: altFromResource(r),
|
||||
});
|
||||
firstAttachment = false;
|
||||
}
|
||||
|
||||
@ -1422,4 +1454,4 @@ async function enexXmlToMd(xmlString: string, resources: ResourceEntity[], tasks
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
export { enexXmlToMd, processMdArrayNewLines, NEWLINE, addResourceTag, cssValue };
|
||||
export { enexXmlToMd, processMdArrayNewLines, NEWLINE, cssValue };
|
||||
|
Loading…
Reference in New Issue
Block a user