1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-23 23:33:01 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Laurent Cozic
c0ceefab8a Restrict auto-detection of links and option to toggle linkify 2020-12-09 00:58:36 +00:00
23 changed files with 261 additions and 309 deletions

View File

@@ -910,9 +910,6 @@ packages/lib/errorUtils.js.map
packages/lib/eventManager.d.ts
packages/lib/eventManager.js
packages/lib/eventManager.js.map
packages/lib/fs-driver-base.d.ts
packages/lib/fs-driver-base.js
packages/lib/fs-driver-base.js.map
packages/lib/fs-driver-node.d.ts
packages/lib/fs-driver-node.js
packages/lib/fs-driver-node.js.map

3
.gitignore vendored
View File

@@ -899,9 +899,6 @@ packages/lib/errorUtils.js.map
packages/lib/eventManager.d.ts
packages/lib/eventManager.js
packages/lib/eventManager.js.map
packages/lib/fs-driver-base.d.ts
packages/lib/fs-driver-base.js
packages/lib/fs-driver-base.js.map
packages/lib/fs-driver-node.d.ts
packages/lib/fs-driver-node.js
packages/lib/fs-driver-node.js.map

View File

@@ -54,5 +54,4 @@ module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: [`${__dirname}/jest.setup.js`],
slowTestThreshold: 20,
};

View File

@@ -1,2 +1,2 @@
test data/
data/
export/

View File

@@ -1,83 +1,97 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const shim_1 = require("@joplin/lib/shim");
const os = require('os');
const { filename } = require('@joplin/lib/path-utils');
const HtmlToMd = require('@joplin/lib/HtmlToMd');
describe('HtmlToMd', function () {
// beforeEach(async (done) => {
// await setupDatabaseAndSynchronizer(1);
// await switchClient(1);
// done();
// });
it('should convert from Html to Markdown', (() => __awaiter(this, void 0, void 0, function* () {
const basePath = `${__dirname}/html_to_md`;
const files = yield shim_1.default.fsDriver().readDirStats(basePath);
const htmlToMd = new HtmlToMd();
for (let i = 0; i < files.length; i++) {
const htmlFilename = files[i].path;
if (htmlFilename.indexOf('.html') < 0)
continue;
const htmlPath = `${basePath}/${htmlFilename}`;
const mdPath = `${basePath}/${filename(htmlFilename)}.md`;
if (htmlFilename !== 'anchor_same_title_and_url.html')
continue;
// if (htmlFilename.indexOf('image_preserve_size') !== 0) continue;
const htmlToMdOptions = {};
if (htmlFilename === 'anchor_local.html') {
// Normally the list of anchor names in the document are retrieved from the HTML code
// This is straightforward when the document is still in DOM format, as with the clipper,
// but otherwise it would need to be somehow parsed out from the HTML. Here we just
// hard code the anchors that we know are in the file.
htmlToMdOptions.anchorNames = ['first', 'second', 'fourth'];
}
if (htmlFilename.indexOf('image_preserve_size') === 0) {
htmlToMdOptions.preserveImageTagsWithSize = true;
}
const html = yield shim_1.default.fsDriver().readFile(htmlPath);
let expectedMd = yield shim_1.default.fsDriver().readFile(mdPath);
let actualMd = yield htmlToMd.parse(`<div>${html}</div>`, htmlToMdOptions);
if (os.EOL === '\r\n') {
expectedMd = expectedMd.replace(/\r\n/g, '\n');
actualMd = actualMd.replace(/\r\n/g, '\n');
}
if (actualMd !== expectedMd) {
const result = [];
result.push('');
result.push(`Error converting file: ${htmlFilename}`);
result.push('--------------------------------- Got:');
result.push(actualMd.split('\n').map(l => `"${l}"`).join('\n'));
result.push('--------------------------------- Expected:');
result.push(expectedMd.split('\n').map(l => `"${l}"`).join('\n'));
result.push('--------------------------------------------');
result.push('');
console.info(result.join('\n'));
// console.info('');
// console.info(`Error converting file: ${htmlFilename}`);
// console.info('--------------------------------- Got:');
// console.info(actualMd);
// console.info('--------------------------------- Raw:');
// console.info(actualMd.split('\n'));
// console.info('--------------------------------- Expected:');
// console.info(expectedMd.split('\n'));
// console.info('--------------------------------------------');
// console.info('');
expect(false).toBe(true);
// return;
}
else {
expect(true).toBe(true);
}
}
})));
});
//# sourceMappingURL=HtmlToMd.js.map
/* eslint-disable no-unused-vars */
const os = require('os');
const time = require('@joplin/lib/time').default;
const { filename } = require('@joplin/lib/path-utils');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const BaseModel = require('@joplin/lib/BaseModel').default;
const shim = require('@joplin/lib/shim').default;
const HtmlToMd = require('@joplin/lib/HtmlToMd');
const { enexXmlToMd } = require('@joplin/lib/import-enex-md-gen.js');
describe('HtmlToMd', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should convert from Html to Markdown', (async () => {
const basePath = `${__dirname}/html_to_md`;
const files = await shim.fsDriver().readDirStats(basePath);
const htmlToMd = new HtmlToMd();
for (let i = 0; i < files.length; i++) {
const htmlFilename = files[i].path;
if (htmlFilename.indexOf('.html') < 0) continue;
const htmlPath = `${basePath}/${htmlFilename}`;
const mdPath = `${basePath}/${filename(htmlFilename)}.md`;
// if (htmlFilename !== 'code_3.html') continue;
// if (htmlFilename.indexOf('image_preserve_size') !== 0) continue;
const htmlToMdOptions = {};
if (htmlFilename === 'anchor_local.html') {
// Normally the list of anchor names in the document are retrieved from the HTML code
// This is straightforward when the document is still in DOM format, as with the clipper,
// but otherwise it would need to be somehow parsed out from the HTML. Here we just
// hard code the anchors that we know are in the file.
htmlToMdOptions.anchorNames = ['first', 'second', 'fourth'];
}
if (htmlFilename.indexOf('image_preserve_size') === 0) {
htmlToMdOptions.preserveImageTagsWithSize = true;
}
const html = await shim.fsDriver().readFile(htmlPath);
let expectedMd = await shim.fsDriver().readFile(mdPath);
let actualMd = await htmlToMd.parse(`<div>${html}</div>`, htmlToMdOptions);
if (os.EOL === '\r\n') {
expectedMd = expectedMd.replace(/\r\n/g, '\n');
actualMd = actualMd.replace(/\r\n/g, '\n');
}
if (actualMd !== expectedMd) {
const result = [];
result.push('');
result.push(`Error converting file: ${htmlFilename}`);
result.push('--------------------------------- Got:');
result.push(actualMd.split('\n').map(l => `"${l}"`).join('\n'));
result.push('--------------------------------- Expected:');
result.push(expectedMd.split('\n').map(l => `"${l}"`).join('\n'));
result.push('--------------------------------------------');
result.push('');
console.info(result.join('\n'));
// console.info('');
// console.info(`Error converting file: ${htmlFilename}`);
// console.info('--------------------------------- Got:');
// console.info(actualMd);
// console.info('--------------------------------- Raw:');
// console.info(actualMd.split('\n'));
// console.info('--------------------------------- Expected:');
// console.info(expectedMd.split('\n'));
// console.info('--------------------------------------------');
// console.info('');
expect(false).toBe(true);
// return;
} else {
expect(true).toBe(true);
}
}
}));
});

View File

@@ -1,87 +0,0 @@
import shim from '@joplin/lib/shim';
const os = require('os');
const { filename } = require('@joplin/lib/path-utils');
const HtmlToMd = require('@joplin/lib/HtmlToMd');
describe('HtmlToMd', function() {
// beforeEach(async (done) => {
// await setupDatabaseAndSynchronizer(1);
// await switchClient(1);
// done();
// });
it('should convert from Html to Markdown', (async () => {
const basePath = `${__dirname}/html_to_md`;
const files = await shim.fsDriver().readDirStats(basePath);
const htmlToMd = new HtmlToMd();
for (let i = 0; i < files.length; i++) {
const htmlFilename = files[i].path;
if (htmlFilename.indexOf('.html') < 0) continue;
const htmlPath = `${basePath}/${htmlFilename}`;
const mdPath = `${basePath}/${filename(htmlFilename)}.md`;
if (htmlFilename !== 'anchor_same_title_and_url.html') continue;
// if (htmlFilename.indexOf('image_preserve_size') !== 0) continue;
const htmlToMdOptions:any = {};
if (htmlFilename === 'anchor_local.html') {
// Normally the list of anchor names in the document are retrieved from the HTML code
// This is straightforward when the document is still in DOM format, as with the clipper,
// but otherwise it would need to be somehow parsed out from the HTML. Here we just
// hard code the anchors that we know are in the file.
htmlToMdOptions.anchorNames = ['first', 'second', 'fourth'];
}
if (htmlFilename.indexOf('image_preserve_size') === 0) {
htmlToMdOptions.preserveImageTagsWithSize = true;
}
const html = await shim.fsDriver().readFile(htmlPath);
let expectedMd = await shim.fsDriver().readFile(mdPath);
let actualMd = await htmlToMd.parse(`<div>${html}</div>`, htmlToMdOptions);
if (os.EOL === '\r\n') {
expectedMd = expectedMd.replace(/\r\n/g, '\n');
actualMd = actualMd.replace(/\r\n/g, '\n');
}
if (actualMd !== expectedMd) {
const result = [];
result.push('');
result.push(`Error converting file: ${htmlFilename}`);
result.push('--------------------------------- Got:');
result.push(actualMd.split('\n').map(l => `"${l}"`).join('\n'));
result.push('--------------------------------- Expected:');
result.push(expectedMd.split('\n').map(l => `"${l}"`).join('\n'));
result.push('--------------------------------------------');
result.push('');
console.info(result.join('\n'));
// console.info('');
// console.info(`Error converting file: ${htmlFilename}`);
// console.info('--------------------------------- Got:');
// console.info(actualMd);
// console.info('--------------------------------- Raw:');
// console.info(actualMd.split('\n'));
// console.info('--------------------------------- Expected:');
// console.info(expectedMd.split('\n'));
// console.info('--------------------------------------------');
// console.info('');
expect(false).toBe(true);
// return;
} else {
expect(true).toBe(true);
}
}
}));
});

View File

@@ -1 +0,0 @@
<a href="https://example.com"/>https://example.com</a>

View File

@@ -1 +0,0 @@
https://example.com

View File

@@ -1,7 +1,6 @@
import Setting from '@joplin/lib/models/Setting';
import BaseModel from '@joplin/lib/BaseModel';
import shim from '@joplin/lib/shim';
import markdownUtils from '@joplin/lib/markdownUtils';
const { sortedIds, createNTestNotes, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
@@ -218,8 +217,6 @@ describe('models_Note', function() {
const t1 = r1.updated_time;
const t2 = r2.updated_time;
const resourceDirE = markdownUtils.escapeLinkUrl(resourceDir);
const testCases = [
[
false,
@@ -244,17 +241,17 @@ describe('models_Note', function() {
[
true,
`![](:/${r1.id})`,
`![](file://${resourceDirE}/${r1.id}.jpg?t=${t1})`,
`![](file://${resourceDir}/${r1.id}.jpg?t=${t1})`,
],
[
true,
`![](:/${r1.id}) ![](:/${r1.id}) ![](:/${r2.id})`,
`![](file://${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDirE}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDirE}/${r2.id}.jpg?t=${t2})`,
`![](file://${resourceDir}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDir}/${r1.id}.jpg?t=${t1}) ![](file://${resourceDir}/${r2.id}.jpg?t=${t2})`,
],
[
true,
`![](:/${r3.id})`,
`![](file://${resourceDirE}/${r3.id}.pdf)`,
`![](file://${resourceDir}/${r3.id}.pdf)`,
],
];

View File

@@ -2,7 +2,8 @@ import InteropService from '@joplin/lib/services/interop/InteropService';
import { CustomExportContext, CustomImportContext, Module, ModuleType } from '@joplin/lib/services/interop/types';
import shim from '@joplin/lib/shim';
const { fileContentEqual, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync, exportDir } = require('./test-utils.js');
const { fileContentEqual, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const Tag = require('@joplin/lib/models/Tag.js');
@@ -10,6 +11,10 @@ const Resource = require('@joplin/lib/models/Resource.js');
const fs = require('fs-extra');
const ArrayUtils = require('@joplin/lib/ArrayUtils');
function exportDir() {
return `${__dirname}/export`;
}
async function recreateExportDir() {
const dir = exportDir();
await fs.remove(dir);

View File

@@ -2,7 +2,7 @@
const fs = require('fs-extra');
const { setupDatabaseAndSynchronizer, switchClient, exportDir } = require('./test-utils.js');
const { setupDatabaseAndSynchronizer, switchClient } = require('./test-utils.js');
const InteropService_Exporter_Md = require('@joplin/lib/services/interop/InteropService_Exporter_Md').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
const Folder = require('@joplin/lib/models/Folder.js');
@@ -10,27 +10,29 @@ const Resource = require('@joplin/lib/models/Resource.js');
const Note = require('@joplin/lib/models/Note.js');
const shim = require('@joplin/lib/shim').default;
const exportDir = `${__dirname}/export`;
describe('services_InteropService_Exporter_Md', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
await fs.remove(exportDir());
await fs.mkdirp(exportDir());
await fs.remove(exportDir);
await fs.mkdirp(exportDir);
done();
});
it('should create resources directory', (async () => {
const service = new InteropService_Exporter_Md();
await service.init(exportDir());
await service.init(exportDir);
expect(await shim.fsDriver().exists(`${exportDir()}/_resources/`)).toBe(true);
expect(await shim.fsDriver().exists(`${exportDir}/_resources/`)).toBe(true);
}));
it('should create note paths and add them to context', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -72,7 +74,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should handle duplicate note names', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -99,7 +101,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should not override existing files', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -116,7 +118,7 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Folder.modelType(), folder1);
// Create a file with the path of note1 before processing note1
await shim.fsDriver().writeFile(`${exportDir()}/folder1/note1.md`, 'Note content', 'utf-8');
await shim.fsDriver().writeFile(`${exportDir}/folder1/note1.md`, 'Note content', 'utf-8');
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
@@ -126,7 +128,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should save resource files in _resource directory', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -157,13 +159,13 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processResource(resource1, Resource.fullPath(resource1));
await exporter.processResource(resource2, Resource.fullPath(resource2));
expect(await shim.fsDriver().exists(`${exportDir()}/_resources/${Resource.filename(resource1)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
expect(await shim.fsDriver().exists(`${exportDir()}/_resources/${Resource.filename(resource2)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
expect(await shim.fsDriver().exists(`${exportDir}/_resources/${Resource.filename(resource1)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
expect(await shim.fsDriver().exists(`${exportDir}/_resources/${Resource.filename(resource2)}`)).toBe(true, 'Resource file should be copied to _resources directory.');
}));
it('should create folders in fs', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -187,14 +189,14 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.prepareForProcessingItemType(BaseModel.TYPE_NOTE, itemsToExport);
await exporter.processItem(Note.modelType(), note2);
expect(await shim.fsDriver().exists(`${exportDir()}/folder1`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/folder1/folder2`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/folder1/folder3`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/folder1`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/folder1/folder2`)).toBe(true, 'Folder should be created in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/folder1/folder3`)).toBe(true, 'Folder should be created in filesystem.');
}));
it('should save notes in fs', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -224,14 +226,14 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note2);
await exporter.processItem(Note.modelType(), note3);
expect(await shim.fsDriver().exists(`${exportDir()}/${exporter.context().notePaths[note1.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/${exporter.context().notePaths[note2.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir()}/${exporter.context().notePaths[note3.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note1.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note2.id]}`)).toBe(true, 'File should be saved in filesystem.');
expect(await shim.fsDriver().exists(`${exportDir}/${exporter.context().notePaths[note3.id]}`)).toBe(true, 'File should be saved in filesystem.');
}));
it('should replace resource ids with relative paths', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -267,8 +269,8 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
const note1_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note2.id]}`);
const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
expect(note1_body).toContain('](../_resources/resource1.jpg)', 'Resource id should be replaced with a relative path.');
expect(note2_body).toContain('](../../_resources/resource2.jpg)', 'Resource id should be replaced with a relative path.');
@@ -276,7 +278,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should replace note ids with relative paths', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -316,9 +318,9 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note2);
await exporter.processItem(Note.modelType(), note3);
const note1_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note2.id]}`);
const note3_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note3.id]}`);
const note1_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note1.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
const note3_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note3.id]}`);
expect(note1_body).toContain('](../folder3/note3.md)', 'Note id should be replaced with a relative path.');
expect(note2_body).toContain('](../../folder3/note3.md)', 'Resource id should be replaced with a relative path.');
@@ -328,7 +330,7 @@ describe('services_InteropService_Exporter_Md', function() {
it('should url encode relative note links', (async () => {
const exporter = new InteropService_Exporter_Md();
await exporter.init(exportDir());
await exporter.init(exportDir);
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
@@ -349,7 +351,7 @@ describe('services_InteropService_Exporter_Md', function() {
await exporter.processItem(Note.modelType(), note1);
await exporter.processItem(Note.modelType(), note2);
const note2_body = await shim.fsDriver().readFile(`${exportDir()}/${exporter.context().notePaths[note2.id]}`);
const note2_body = await shim.fsDriver().readFile(`${exportDir}/${exporter.context().notePaths[note2.id]}`);
expect(note2_body).toContain('[link](../folder%20with%20space1/note1%20name%20with%20space.md)', 'Whitespace in URL should be encoded');
}));
});

View File

@@ -0,0 +1,86 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.localNotesFoldersSameAsRemote = exports.remoteResources = exports.remoteNotesFoldersResources = exports.remoteNotesAndFolders = exports.allNotesFolders = void 0;
const BaseModel_1 = require("@joplin/lib/BaseModel");
const { fileApi } = require('./test-utils.js');
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const BaseItem = require('@joplin/lib/models/BaseItem.js');
function allNotesFolders() {
return __awaiter(this, void 0, void 0, function* () {
const folders = yield Folder.all();
const notes = yield Note.all();
return folders.concat(notes);
});
}
exports.allNotesFolders = allNotesFolders;
function remoteItemsByTypes(types) {
return __awaiter(this, void 0, void 0, function* () {
const list = yield fileApi().list('', { includeDirs: false, syncItemsOnly: true });
if (list.has_more)
throw new Error('Not implemented!!!');
const files = list.items;
const output = [];
for (const file of files) {
const remoteContent = yield fileApi().get(file.path);
const content = yield BaseItem.unserialize(remoteContent);
if (types.indexOf(content.type_) < 0)
continue;
output.push(content);
}
return output;
});
}
function remoteNotesAndFolders() {
return __awaiter(this, void 0, void 0, function* () {
return remoteItemsByTypes([BaseModel_1.default.TYPE_NOTE, BaseModel_1.default.TYPE_FOLDER]);
});
}
exports.remoteNotesAndFolders = remoteNotesAndFolders;
function remoteNotesFoldersResources() {
return __awaiter(this, void 0, void 0, function* () {
return remoteItemsByTypes([BaseModel_1.default.TYPE_NOTE, BaseModel_1.default.TYPE_FOLDER, BaseModel_1.default.TYPE_RESOURCE]);
});
}
exports.remoteNotesFoldersResources = remoteNotesFoldersResources;
function remoteResources() {
return __awaiter(this, void 0, void 0, function* () {
return remoteItemsByTypes([BaseModel_1.default.TYPE_RESOURCE]);
});
}
exports.remoteResources = remoteResources;
function localNotesFoldersSameAsRemote(locals, expect) {
return __awaiter(this, void 0, void 0, function* () {
let error = null;
try {
const nf = yield remoteNotesAndFolders();
expect(locals.length).toBe(nf.length);
for (let i = 0; i < locals.length; i++) {
const dbItem = locals[i];
const path = BaseItem.systemPath(dbItem);
const remote = yield fileApi().stat(path);
expect(!!remote).toBe(true);
if (!remote)
continue;
let remoteContent = yield fileApi().get(path);
remoteContent = dbItem.type_ == BaseModel_1.default.TYPE_NOTE ? yield Note.unserialize(remoteContent) : yield Folder.unserialize(remoteContent);
expect(remoteContent.title).toBe(dbItem.title);
}
}
catch (e) {
error = e;
}
expect(error).toBe(null);
});
}
exports.localNotesFoldersSameAsRemote = localNotesFoldersSameAsRemote;
//# sourceMappingURL=test-utils-synchronizer.js.map

View File

@@ -100,11 +100,7 @@ FileApiDriverLocal.fsDriver_ = fsDriver;
const logDir = `${__dirname}/../tests/logs`;
const baseTempDir = `${__dirname}/../tests/tmp/${suiteName_}`;
// We add a space in the data directory path as that will help uncover
// various space-in-path issues.
const dataDir = `${__dirname}/test data/${suiteName_}`;
const dataDir = `${__dirname}/data/${suiteName_}`;
fs.mkdirpSync(logDir, 0o755);
fs.mkdirpSync(baseTempDir, 0o755);
fs.mkdirpSync(dataDir);
@@ -306,11 +302,6 @@ async function setupDatabase(id: number = null, options: any = null) {
await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy);
}
function exportDir(id: number = null) {
if (id === null) id = currentClient_;
return `${dataDir}/export`;
}
function resourceDirName(id: number = null) {
if (id === null) id = currentClient_;
return `resources-${id}`;
@@ -792,4 +783,4 @@ class TestApp extends BaseApplication {
}
}
module.exports = { exportDir, newPluginService, newPluginScript, synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
module.exports = { newPluginService, newPluginScript, synchronizerStart, afterEachCleanUp, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };

View File

@@ -1,5 +1,5 @@
const RNFS = require('react-native-fs');
const FsDriverBase = require('@joplin/lib/fs-driver-base').default;
const FsDriverBase = require('@joplin/lib/fs-driver-base');
const RNFetchBlob = require('rn-fetch-blob').default;
class FsDriverRN extends FsDriverBase {

View File

@@ -1,48 +1,15 @@
import time from './time';
import Setting from './models/Setting';
import { filename, fileExtension } from './path-utils';
const { filename, fileExtension } = require('./path-utils');
const time = require('./time').default;
const Setting = require('./models/Setting').default;
const md5 = require('md5');
export interface Stat {
birthtime: number;
mtime: number;
isDirectory(): boolean;
path: string;
size: number;
}
export interface ReadDirStatsOptions {
recursive: boolean;
}
export default class FsDriverBase {
public async stat(_path: string): Promise<Stat> {
throw new Error('Not implemented');
}
public async readDirStats(_path: string, _options: ReadDirStatsOptions = null): Promise<Stat[]> {
throw new Error('Not implemented');
}
public async exists(_path: string): Promise<boolean> {
throw new Error('Not implemented');
}
public async remove(_path: string): Promise<void> {
throw new Error('Not implemented');
}
public async isDirectory(path: string) {
class FsDriverBase {
async isDirectory(path) {
const stat = await this.stat(path);
return !stat ? false : stat.isDirectory();
}
public async writeFile(_path: string, _content: string, _encoding: string = 'base64'): Promise<void> {
throw new Error('Not implemented');
}
protected async readDirStatsHandleRecursion_(basePath: string, stat: Stat, output: Stat[], options: ReadDirStatsOptions): Promise<Stat[]> {
async readDirStatsHandleRecursion_(basePath, stat, output, options) {
if (options.recursive && stat.isDirectory()) {
const subPath = `${basePath}/${stat.path}`;
const subStats = await this.readDirStats(subPath, options);
@@ -56,7 +23,7 @@ export default class FsDriverBase {
return output;
}
public async findUniqueFilename(name: string, reservedNames: string[] = null): Promise<string> {
async findUniqueFilename(name, reservedNames = null) {
if (reservedNames === null) {
reservedNames = [];
}
@@ -80,7 +47,7 @@ export default class FsDriverBase {
}
}
public async removeAllThatStartWith(dirPath: string, filenameStart: string) {
async removeAllThatStartWith(dirPath, filenameStart) {
if (!filenameStart || !dirPath) throw new Error('dirPath and filenameStart cannot be empty');
const stats = await this.readDirStats(dirPath);
@@ -92,7 +59,7 @@ export default class FsDriverBase {
}
}
public async waitTillExists(path: string, timeout: number = 10000) {
async waitTillExists(path, timeout = 10000) {
const startTime = Date.now();
while (true) {
@@ -105,7 +72,7 @@ export default class FsDriverBase {
// TODO: move out of here and make it part of joplin-renderer
// or assign to option using .bind(fsDriver())
public async cacheCssToFile(cssStrings: string[]) {
async cacheCssToFile(cssStrings) {
const cssString = Array.isArray(cssStrings) ? cssStrings.join('\n') : cssStrings;
const cssFilePath = `${Setting.value('tempDir')}/${md5(escape(cssString))}.css`;
if (!(await this.exists(cssFilePath))) {
@@ -117,5 +84,6 @@ export default class FsDriverBase {
mime: 'text/css',
};
}
}
module.exports = FsDriverBase;

View File

@@ -1,8 +1,8 @@
import { resolve as nodeResolve } from 'path';
import FsDriverBase, { Stat } from './fs-driver-base';
import time from './time';
const fs = require('fs-extra');
const time = require('./time').default;
const FsDriverBase = require('./fs-driver-base');
export default class FsDriverNode extends FsDriverBase {
@@ -125,7 +125,7 @@ export default class FsDriverNode extends FsDriverBase {
throw this.fsErrorToJsError_(error);
}
let output: Stat[] = [];
let output = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const stat = await this.stat(`${path}/${item}`);

View File

@@ -171,7 +171,7 @@ class Note extends BaseItem {
useAbsolutePaths: false,
}, options);
let pathsToTry = [];
const pathsToTry = [];
if (options.useAbsolutePaths) {
pathsToTry.push(`file://${Setting.value('resourceDir')}`);
pathsToTry.push(`file://${shim.pathRelativeToCwd(Setting.value('resourceDir'))}`);
@@ -179,21 +179,6 @@ class Note extends BaseItem {
pathsToTry.push(Resource.baseRelativeDirectoryPath());
}
// We support both the escaped and unescaped versions because both
// of those paths are valid:
//
// [](file://C:/I like spaces in paths/abcdefg.jpg)
// [](file://C:/I%20like%20spaces%20in%20paths/abcdefg.jpg)
//
// https://discourse.joplinapp.org/t/12986/4
const temp = [];
for (const p of pathsToTry) {
temp.push(p);
temp.push(markdownUtils.escapeLinkUrl(p));
}
pathsToTry = temp;
this.logger().debug('replaceResourceExternalToInternalLinks', 'options:', options, 'pathsToTry:', pathsToTry);
for (const basePath of pathsToTry) {

View File

@@ -590,6 +590,8 @@ class Setting extends BaseModel {
'markdown.plugin.softbreaks': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.typographer': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
'markdown.plugin.linkify': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Linkify')}${wysiwygYes}` },
'markdown.plugin.katex': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
'markdown.plugin.fountain': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
'markdown.plugin.mermaid': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Mermaid diagrams support')}${wysiwygYes}` },

View File

@@ -1,17 +1,11 @@
import { _ } from '../../locale';
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
import InteropService_Exporter_Raw from './InteropService_Exporter_Raw';
import shim from '../../shim';
const InteropService_Exporter_Base = require('./InteropService_Exporter_Base').default;
const InteropService_Exporter_Raw = require('./InteropService_Exporter_Raw').default;
const fs = require('fs-extra');
const shim = require('../../shim').default;
export default class InteropService_Exporter_Jex extends InteropService_Exporter_Base {
private tempDir_: string;
private destPath_: string;
private rawExporter_: InteropService_Exporter_Raw;
public async init(destPath: string) {
async init(destPath: string) {
if (await shim.fsDriver().isDirectory(destPath)) throw new Error(`Path is a directory: ${destPath}`);
this.tempDir_ = await this.temporaryDirectory_(false);
@@ -20,15 +14,15 @@ export default class InteropService_Exporter_Jex extends InteropService_Exporter
await this.rawExporter_.init(this.tempDir_);
}
public async processItem(itemType: number, item: any) {
async processItem(itemType: number, item: any) {
return this.rawExporter_.processItem(itemType, item);
}
public async processResource(resource: any, filePath: string) {
async processResource(resource: any, filePath: string) {
return this.rawExporter_.processResource(resource, filePath);
}
public async close() {
async close() {
const stats = await shim.fsDriver().readDirStats(this.tempDir_, { recursive: true });
const filePaths = stats.filter((a: any) => !a.isDirectory()).map((a: any) => a.path);

View File

@@ -378,7 +378,7 @@ export default class MdToHtml {
const markdownIt = new MarkdownIt({
breaks: !this.pluginEnabled('softbreaks'),
typographer: this.pluginEnabled('typographer'),
linkify: true,
linkify: this.pluginEnabled('linkify'),
html: true,
highlight: (str: string, lang: string) => {
let outputCodeHtml = '';
@@ -471,7 +471,7 @@ export default class MdToHtml {
}
}
setupLinkify(markdownIt);
if (this.pluginEnabled('linkify')) setupLinkify(markdownIt);
const renderedBody = markdownIt.render(body, context);

View File

@@ -26,4 +26,10 @@ module.exports = function(markdownIt) {
return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true;
};
markdownIt.linkify.set({
'fuzzyLink': false,
'fuzzyIP': false,
'fuzzyEmail': false,
});
};

View File

@@ -227,9 +227,6 @@ rules.inlineLink = {
replacement: function (content, node, options) {
var href = filterLinkHref(node.getAttribute('href'))
console.info('RRRRRRRR', href);
if (!href) {
return getNamedAnchorFromLink(node, options) + filterLinkContent(content)
} else {

View File

@@ -146,6 +146,7 @@ Note that the functionality added by these plugins is not part of the CommonMark
|--------|--------|-------------|---------|------------|
| Soft breaks | See [breaks](https://markdown-it.github.io/#md3=%7B%22source%22%3A%22This%20is%20line1%5CnThis%20is%20line2%5Cn%5CnThis%20is%20a%20line%20with%202%20trailing%20spaces%20%20%5CnNext%20line%5Cn%5CnClick%20the%20%60breaks%60%20checkbox%20above%20to%20see%20the%20difference.%5CnJoplin%27s%20default%20is%20hard%20breaks%20%28checked%20%60breaks%60%20checkbox%29.%22%2C%22defaults%22%3A%7B%22html%22%3Afalse%2C%22xhtmlOut%22%3Afalse%2C%22breaks%22%3Afalse%2C%22langPrefix%22%3A%22language-%22%2C%22linkify%22%3Afalse%2C%22typographer%22%3Afalse%2C%22_highlight%22%3Afalse%2C%22_strict%22%3Afalse%2C%22_view%22%3A%22html%22%7D%7D) markdown-it demo| Joplin uses hard breaks by default, which means that a line break is rendered as `<br>`. Enable soft breaks for traditional markdown line-break behaviour. | no | [View](https://joplinapp.org/images/md_plugins/softbreaks_plugin.jpg)
| Typographer | See [typographer](https://markdown-it.github.io/#md3=%7B%22source%22%3A%22%23%20Typographic%20replacements%5Cn%5Cn%28c%29%20%28C%29%20%28r%29%20%28R%29%20%28tm%29%20%28TM%29%20%28p%29%20%28P%29%20%2B-%5Cn%5Cntest..%20test...%20test.....%20test%3F.....%20test!....%5Cn%5Cn!!!!!!%20%3F%3F%3F%3F%20%2C%2C%20%20--%20---%5Cn%5Cn%5C%22Smartypants%2C%20double%20quotes%5C%22%20and%20%27single%20quotes%27%5Cn%22%2C%22defaults%22%3A%7B%22html%22%3Afalse%2C%22xhtmlOut%22%3Afalse%2C%22breaks%22%3Afalse%2C%22langPrefix%22%3A%22language-%22%2C%22linkify%22%3Atrue%2C%22typographer%22%3Atrue%2C%22_highlight%22%3Atrue%2C%22_strict%22%3Afalse%2C%22_view%22%3A%22html%22%7D%7D) markdown-it demo | Does typographic replacements, (c) -&gt; © and so on | no | [View](https://joplinapp.org/images/md_plugins/typographer_plugin.jpg) |
| Linkify | See [linkify](https://markdown-it.github.io/#md3=%7B%22source%22%3A%22Use%20the%20Linkify%20checkbox%20to%20switch%20link-detection%20on%20and%20off.%5Cn%5Cn%2A%2AThese%20links%20are%20auto-detected%3A%2A%2A%5Cn%5Cnhttps%3A%2F%2Fexample.com%5Cn%5Cnexample.com%5Cn%5Cntest%40example.com%5Cn%5Cn%2A%2AThese%20are%20always%20links%3A%2A%2A%5Cn%5Cn%5Blink%5D%28https%3A%2F%2Fjoplinapp.org%29%5Cn%5Cn%3Chttps%3A%2F%2Fexample.com%3E%22%2C%22defaults%22%3A%7B%22html%22%3Afalse%2C%22xhtmlOut%22%3Afalse%2C%22breaks%22%3Afalse%2C%22langPrefix%22%3A%22language-%22%2C%22linkify%22%3Atrue%2C%22typographer%22%3Atrue%2C%22_highlight%22%3Atrue%2C%22_strict%22%3Afalse%2C%22_view%22%3A%22html%22%7D%7D) markdown-it demo | Auto-detects URLs and convert them to clickable links | yes | |
| [Katex](https://katex.org) | `$$math expr$$` or `$math$` | [See above](#math-notation) | yes | [View](https://joplinapp.org/images/md_plugins/katex_plugin.jpg) |
| [Mark](https://github.com/markdown-it/markdown-it-mark) | `==marked==` | Transforms into `<mark>marked</mark>` (highlighted) | yes | [View](https://joplinapp.org/images/md_plugins/mark_plugin.jpg) |
| [Footnote](https://github.com/markdown-it/markdown-it-footnote) | `Simples inline footnote ^[I'm inline!]` | See [plugin page](https://github.com/markdown-it/markdown-it-footnote) for full description | yes | [View](https://joplinapp.org/images/md_plugins/footnote_plugin.jpg) |