diff --git a/CliClient/tests/urlUtils.js b/CliClient/tests/urlUtils.js
index 9a893a3238..06fcbe0a92 100644
--- a/CliClient/tests/urlUtils.js
+++ b/CliClient/tests/urlUtils.js
@@ -35,7 +35,10 @@ describe('urlUtils', function() {
it('should detect resource URLs', asyncTest(async (done) => {
const testCases = [
[':/1234abcd1234abcd1234abcd1234abcd', { itemId: '1234abcd1234abcd1234abcd1234abcd', hash: '' }],
+ [':/1234abcd1234abcd1234abcd1234abcd "some text"', { itemId: '1234abcd1234abcd1234abcd1234abcd', hash: '' }],
[':/1234abcd1234abcd1234abcd1234abcd#hash', { itemId: '1234abcd1234abcd1234abcd1234abcd', hash: 'hash' }],
+ [':/1234abcd1234abcd1234abcd1234abcd#Книги-из-номер', { itemId: '1234abcd1234abcd1234abcd1234abcd', hash: 'Книги-из-номер' }],
+ [':/1234abcd1234abcd1234abcd1234abcd#hash "some text"', { itemId: '1234abcd1234abcd1234abcd1234abcd', hash: 'hash' }],
['joplin://1234abcd1234abcd1234abcd1234abcd', { itemId: '1234abcd1234abcd1234abcd1234abcd', hash: '' }],
['joplin://1234abcd1234abcd1234abcd1234abcd#hash', { itemId: '1234abcd1234abcd1234abcd1234abcd', hash: 'hash' }],
[':/1234abcd1234abcd1234abcd1234abc', null],
@@ -49,10 +52,33 @@ describe('urlUtils', function() {
if (!expected) {
expect(!u).toBe(true);
} else {
- expect(u.itemId).toBe(expected.itemId);
- expect(u.hash).toBe(expected.hash);
+ if (!u) {
+ expect(!!u).toBe(true);
+ } else {
+ expect(u.itemId).toBe(expected.itemId);
+ expect(u.hash).toBe(expected.hash);
+ }
}
}
}));
+ it('should extract resource URLs', asyncTest(async (done) => {
+ const testCases = [
+ ['Bla [](:/11111111111111111111111111111111) bla [](:/22222222222222222222222222222222) bla', ['11111111111111111111111111111111', '22222222222222222222222222222222']],
+ ['Bla [](:/11111111111111111111111111111111 "Some title") bla [](:/22222222222222222222222222222222 "something else") bla', ['11111111111111111111111111111111', '22222222222222222222222222222222']],
+ ['Bla
bla [](:/22222222222222222222222222222222) bla', ['fcca2938a96a22570e8eae2565bc6b0b', '22222222222222222222222222222222']],
+ ['Bla
bla Some note link blu [](:/22222222222222222222222222222222) bla', ['fcca2938a96a22570e8eae2565bc6b0b', '33333333333333333333333333333333', '22222222222222222222222222222222']],
+ ['nothing here', []],
+ ['', []],
+ ];
+
+ for (const t of testCases) {
+ const result = urlUtils.extractResourceUrls(t[0]);
+ const expected = t[1];
+
+ const itemIds = result.map(r => r.itemId);
+ expect(itemIds.sort().join(',')).toBe(expected.sort().join(','));
+ }
+ }));
+
});
diff --git a/ReactNativeClient/lib/models/Note.js b/ReactNativeClient/lib/models/Note.js
index 9fa68829db..d39ded0f4f 100644
--- a/ReactNativeClient/lib/models/Note.js
+++ b/ReactNativeClient/lib/models/Note.js
@@ -10,6 +10,7 @@ const { time } = require('lib/time-utils.js');
const { _ } = require('lib/locale.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const lodash = require('lodash');
+const urlUtils = require('lib/urlUtils.js');
class Note extends BaseItem {
static tableName() {
@@ -114,27 +115,9 @@ class Note extends BaseItem {
static linkedItemIds(body) {
if (!body || body.length <= 32) return [];
- // For example: ![](:/fcca2938a96a22570e8eae2565bc6b0b)
- let matches = body.match(/\(:\/[a-zA-Z0-9]{32}\)/g);
- if (!matches) matches = [];
- matches = matches.map(m => m.substr(3, 32));
-
- // For example: ![](:/fcca2938a96a22570e8eae2565bc6b0b "Some title")
- let matches2 = body.match(/\(:\/[a-zA-Z0-9]{32}\s(.*?)\)/g);
- if (!matches2) matches2 = [];
- matches2 = matches2.map(m => m.substr(3, 32));
- matches = matches.concat(matches2);
-
- // For example:
- const imgRegex = /
/gi;
- const imgMatches = [];
- while (true) {
- const m = imgRegex.exec(body);
- if (!m) break;
- imgMatches.push(m[1]);
- }
-
- return ArrayUtils.unique(matches.concat(imgMatches));
+ const links = urlUtils.extractResourceUrls(body);
+ const itemIds = links.map(l => l.itemId);
+ return ArrayUtils.unique(itemIds);
}
static async linkedItems(body) {
diff --git a/ReactNativeClient/lib/urlUtils.js b/ReactNativeClient/lib/urlUtils.js
index 44771e6f5f..43a0855149 100644
--- a/ReactNativeClient/lib/urlUtils.js
+++ b/ReactNativeClient/lib/urlUtils.js
@@ -40,27 +40,53 @@ urlUtils.prependBaseUrl = function(url, baseUrl) {
}
};
+const resourceRegex = /^(joplin:\/\/|:\/)([0-9a-zA-Z]{32})(|#[^\s]*)(|\s".*?")$/;
+
urlUtils.isResourceUrl = function(url) {
- return !!url.match(/^(joplin:\/\/|:\/)[0-9a-zA-Z]{32}(|#.*)$/);
+ return !!url.match(resourceRegex);
};
urlUtils.parseResourceUrl = function(url) {
if (!urlUtils.isResourceUrl(url)) return null;
- const filename = url.split('/').pop();
- const splitted = filename.split('#');
+ const match = url.match(resourceRegex);
- const output = {
- itemId: '',
- hash: '',
- };
-
- if (splitted.length) output.itemId = splitted[0];
+ const itemId = match[2];
+ let hash = match[3].trim();
// In general we want the hash to be decoded so that non-alphabetical languages
// appear as-is without being encoded with %.
// Fixes https://github.com/laurent22/joplin/issues/1870
- if (splitted.length >= 2) output.hash = urlDecode(splitted[1]);
+ if (hash) hash = urlDecode(hash.substr(1)); // Remove the first #
+
+ return {
+ itemId: itemId,
+ hash: hash,
+ };
+};
+
+urlUtils.extractResourceUrls = function(text) {
+ const markdownLinksRE = /\]\((.*?)\)/g;
+ const output = [];
+ let result = null;
+
+ while ((result = markdownLinksRE.exec(text)) !== null) {
+ const resourceUrlInfo = urlUtils.parseResourceUrl(result[1]);
+ if (resourceUrlInfo) output.push(resourceUrlInfo);
+ }
+
+ const htmlRegexes = [
+ /
/gi,
+ //gi,
+ ];
+
+ for (const htmlRegex of htmlRegexes) {
+ while (true) {
+ const m = htmlRegex.exec(text);
+ if (!m) break;
+ output.push({ itemId: m[1], hash: '' });
+ }
+ }
return output;
};