From fbb0ac58927324f6771808a2be6441cbc8a0da32 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 15 Jul 2019 20:43:28 +0000 Subject: [PATCH] Clipper: Refactored image rules to re-use more code --- CliClient/tests/htmlUtils.js | 15 +++++++ .../lib/MdToHtml/rules/html_image.js | 21 ++-------- ReactNativeClient/lib/MdToHtml/rules/image.js | 21 ++-------- ReactNativeClient/lib/MdToHtml/utils.js | 26 +++++++++++- ReactNativeClient/lib/htmlUtils.js | 41 +++++++++++++------ 5 files changed, 76 insertions(+), 48 deletions(-) diff --git a/CliClient/tests/htmlUtils.js b/CliClient/tests/htmlUtils.js index 0181025f3..d4350d3ac 100644 --- a/CliClient/tests/htmlUtils.js +++ b/CliClient/tests/htmlUtils.js @@ -30,4 +30,19 @@ describe('htmlUtils', function() { done(); }); + it('should encode attributes', async (done) => { + const testCases = [ + [{ a: 'one', b: 'two' }, 'a="one" b="two"'], + [{ a: 'one&two' }, 'a="one&two"'], + ]; + + for (let i = 0; i < testCases.length; i++) { + const attrs = testCases[i][0]; + const expected = testCases[i][1]; + expect(htmlUtils.attributesHtml(attrs)).toBe(expected); + } + + done(); + }); + }); \ No newline at end of file diff --git a/ReactNativeClient/lib/MdToHtml/rules/html_image.js b/ReactNativeClient/lib/MdToHtml/rules/html_image.js index da8dfd1fa..5607af77b 100644 --- a/ReactNativeClient/lib/MdToHtml/rules/html_image.js +++ b/ReactNativeClient/lib/MdToHtml/rules/html_image.js @@ -1,26 +1,13 @@ const Entities = require('html-entities').AllHtmlEntities; const htmlentities = (new Entities()).encode; const Resource = require('lib/models/Resource.js'); +const htmlUtils = require('lib/htmlUtils.js'); const utils = require('../utils'); function renderImageHtml(before, src, after, ruleOptions) { - const resourceId = Resource.urlToId(src); - const result = ruleOptions.resources[resourceId]; - const resource = result ? result.item : null; - const resourceStatus = utils.resourceStatus(result); - - if (resourceStatus !== 'ready') { - const icon = utils.resourceStatusImage(resourceStatus); - return '
' + '' + '
'; - } - - const mime = resource.mime ? resource.mime.toLowerCase() : ''; - if (Resource.isSupportedImageMimeType(mime)) { - let newSrc = './' + Resource.filename(resource); - if (ruleOptions.resourceBaseUrl) newSrc = ruleOptions.resourceBaseUrl + newSrc; - return ''; - } - + const r = utils.imageReplacement(src, ruleOptions.resources, ruleOptions.resourceBaseUrl); + if (typeof r === 'string') return r; + if (r) return ''; return '[Image: ' + htmlentities(resource.title) + ' (' + htmlentities(mime) + ')]'; } diff --git a/ReactNativeClient/lib/MdToHtml/rules/image.js b/ReactNativeClient/lib/MdToHtml/rules/image.js index dfbbf69ba..c4b90b89c 100644 --- a/ReactNativeClient/lib/MdToHtml/rules/image.js +++ b/ReactNativeClient/lib/MdToHtml/rules/image.js @@ -2,6 +2,7 @@ const Entities = require('html-entities').AllHtmlEntities; const htmlentities = (new Entities()).encode; const Resource = require('lib/models/Resource.js'); const utils = require('../utils'); +const htmlUtils = require('lib/htmlUtils.js'); function installRule(markdownIt, mdOptions, ruleOptions) { const defaultRender = markdownIt.renderer.rules.image; @@ -13,23 +14,9 @@ function installRule(markdownIt, mdOptions, ruleOptions) { if (!Resource.isResourceUrl(src)) return defaultRender(tokens, idx, options, env, self); - const resourceId = Resource.urlToId(src); - const result = ruleOptions.resources[resourceId]; - const resource = result ? result.item : null; - const resourceStatus = utils.resourceStatus(result); - - if (resourceStatus !== 'ready') { - const icon = utils.resourceStatusImage(resourceStatus); - return '
' + '' + '
'; - } - - const mime = resource.mime ? resource.mime.toLowerCase() : ''; - if (Resource.isSupportedImageMimeType(mime)) { - let realSrc = './' + Resource.filename(resource); - if (ruleOptions.resourceBaseUrl) realSrc = ruleOptions.resourceBaseUrl + realSrc; - let output = ''; - return output; - } + const r = utils.imageReplacement(src, ruleOptions.resources, ruleOptions.resourceBaseUrl); + if (typeof r === 'string') return r; + if (r) return ''; return defaultRender(tokens, idx, options, env, self); }; diff --git a/ReactNativeClient/lib/MdToHtml/utils.js b/ReactNativeClient/lib/MdToHtml/utils.js index d830ba6bb..0febfc1a6 100644 --- a/ReactNativeClient/lib/MdToHtml/utils.js +++ b/ReactNativeClient/lib/MdToHtml/utils.js @@ -68,7 +68,7 @@ utils.resourceStatusFile = function(state) { throw new Error('Unknown state: ' + state); } -utils.resourceStatus = function(resourceInfo, localState) { +utils.resourceStatus = function(resourceInfo) { let resourceStatus = 'ready'; if (resourceInfo) { @@ -91,4 +91,28 @@ utils.resourceStatus = function(resourceInfo, localState) { return resourceStatus; } +utils.imageReplacement = function(src, resources, resourceBaseUrl) { + const resourceId = Resource.urlToId(src); + const result = resources[resourceId]; + const resource = result ? result.item : null; + const resourceStatus = utils.resourceStatus(result); + + if (resourceStatus !== 'ready') { + const icon = utils.resourceStatusImage(resourceStatus); + return '
' + '' + '
'; + } + + const mime = resource.mime ? resource.mime.toLowerCase() : ''; + if (Resource.isSupportedImageMimeType(mime)) { + let newSrc = './' + Resource.filename(resource); + if (resourceBaseUrl) newSrc = resourceBaseUrl + newSrc; + return { + 'data-resource-id': resource.id, + 'src': newSrc, + }; + } + + return null; +} + module.exports = utils; \ No newline at end of file diff --git a/ReactNativeClient/lib/htmlUtils.js b/ReactNativeClient/lib/htmlUtils.js index 77414eafe..31538319d 100644 --- a/ReactNativeClient/lib/htmlUtils.js +++ b/ReactNativeClient/lib/htmlUtils.js @@ -1,15 +1,17 @@ const jsdom = require("jsdom"); const { JSDOM } = jsdom; const urlUtils = require('lib/urlUtils.js'); +const Entities = require('html-entities').AllHtmlEntities; +const htmlentities = (new Entities()).encode; -function headAndBodyHtml_(doc) { - const output = []; - if (doc.head) output.push(doc.head.innerHTML); - if (doc.body) output.push(doc.body.innerHTML); - return output.join('\n'); -} +class HtmlUtils { -const htmlUtils = { + headAndBodyHtml(doc) { + const output = []; + if (doc.head) output.push(doc.head.innerHTML); + if (doc.body) output.push(doc.body.innerHTML); + return output.join('\n'); + } extractImageUrls(html) { if (!html) return []; @@ -25,7 +27,7 @@ const htmlUtils = { } return output; - }, + } replaceImageUrls(html, callback) { if (!html) return ''; @@ -42,8 +44,8 @@ const htmlUtils = { // This function returns the head and body but without the and // tag, which for our purpose are not needed and can cause issues when present. - return headAndBodyHtml_(doc); - }, + return this.headAndBodyHtml(doc); + } prependBaseUrl(html, baseUrl) { const dom = new JSDOM(html); @@ -57,9 +59,22 @@ const htmlUtils = { anchor.setAttribute('href', newHref); } - return headAndBodyHtml_(doc); - }, + return this.headAndBodyHtml(doc); + } -}; + attributesHtml(attr) { + const output = []; + + for (const n in attr) { + if (!attr.hasOwnProperty(n)) continue; + output.push(n + '="' + htmlentities(attr[n]) + '"'); + } + + return output.join(' '); + } + +} + +const htmlUtils = new HtmlUtils(); module.exports = htmlUtils; \ No newline at end of file