diff --git a/.eslintignore b/.eslintignore
index d7e33cda05..5fc0872a64 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -145,6 +145,7 @@ ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
+ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/media.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
diff --git a/.gitignore b/.gitignore
index cbe02a27c3..6eb16d439a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,6 +136,7 @@ ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
+ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/media.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
diff --git a/ReactNativeClient/lib/joplin-renderer/HtmlToHtml.js b/ReactNativeClient/lib/joplin-renderer/HtmlToHtml.js
index 803b27b9f8..b5436a52a6 100644
--- a/ReactNativeClient/lib/joplin-renderer/HtmlToHtml.js
+++ b/ReactNativeClient/lib/joplin-renderer/HtmlToHtml.js
@@ -58,7 +58,7 @@ class HtmlToHtml {
html = htmlUtils.processImageTags(html, data => {
if (!data.src) return null;
- const r = utils.imageReplacement(this.ResourceModel_, data.src, options.resources, this.resourceBaseUrl_);
+ const r = utils.resourceReplacement(this.ResourceModel_, data.src, options.resources, this.resourceBaseUrl_);
if (!r) return null;
if (typeof r === 'string') {
diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml.js b/ReactNativeClient/lib/joplin-renderer/MdToHtml.js
index 3690fb5e35..42fde1f22c 100644
--- a/ReactNativeClient/lib/joplin-renderer/MdToHtml.js
+++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml.js
@@ -17,6 +17,7 @@ const rules = {
code_inline: require('./MdToHtml/rules/code_inline'),
fountain: require('./MdToHtml/rules/fountain'),
mermaid: require('./MdToHtml/rules/mermaid').default,
+ media: require('./MdToHtml/rules/media').default,
};
const setupLinkify = require('./MdToHtml/setupLinkify');
diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
index d3e41a9a22..ea21e01d9b 100644
--- a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
+++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
@@ -3,7 +3,7 @@ const htmlUtils = require('../../htmlUtils.js');
const utils = require('../../utils');
function renderImageHtml(before, src, after, ruleOptions) {
- const r = utils.imageReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl);
+ const r = utils.resourceReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl);
if (typeof r === 'string') return r;
if (r) return `
`;
return `[Image: ${src}]`;
diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/image.js b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/image.js
index dadac3f6ff..cfb50b7682 100644
--- a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/image.js
+++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/image.js
@@ -14,9 +14,9 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
if (!Resource.isResourceUrl(src) || ruleOptions.plainResourceRendering) return defaultRender(tokens, idx, options, env, self);
- const r = utils.imageReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl);
+ const r = utils.resourceReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl);
if (typeof r === 'string') return r;
- if (r) return `
`;
+ if (r && r.type === 'image') return `
`;
return defaultRender(tokens, idx, options, env, self);
};
diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/media.ts b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/media.ts
new file mode 100644
index 0000000000..6144635401
--- /dev/null
+++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/media.ts
@@ -0,0 +1,30 @@
+const utils = require('../../utils');
+
+// @ts-ignore: Keep the function signature as-is despite unusued arguments
+function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) {
+ const defaultRender = markdownIt.renderer.rules.link_open;
+
+ markdownIt.renderer.rules.link_open = (tokens: { [x: string]: any; }, idx: string | number, options: any, env: any, self: any) => {
+ const Resource = ruleOptions.ResourceModel;
+
+ const token = tokens[idx];
+ const src = utils.getAttr(token.attrs, 'href');
+
+ if (!Resource.isResourceUrl(src) || ruleOptions.plainResourceRendering) return defaultRender(tokens, idx, options, env, self);
+
+ const r = utils.resourceReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl);
+ if (typeof r === 'string') return r;
+ if (r && r.type === 'audio') return ``;
+ if (r && r.type === 'video') return ``;
+
+ console.log(context);
+
+ return defaultRender(tokens, idx, options, env, self);
+ };
+}
+
+export default function(context:any,ruleOptions:any) {
+ return function(md:any, mdOptions:any) {
+ installRule(md, mdOptions, ruleOptions, context);
+ };
+}
diff --git a/ReactNativeClient/lib/joplin-renderer/utils.js b/ReactNativeClient/lib/joplin-renderer/utils.js
index 60a027c0ef..2d04995cf5 100644
--- a/ReactNativeClient/lib/joplin-renderer/utils.js
+++ b/ReactNativeClient/lib/joplin-renderer/utils.js
@@ -122,7 +122,8 @@ utils.resourceStatus = function(ResourceModel, resourceInfo) {
return resourceStatus;
};
-utils.imageReplacement = function(ResourceModel, src, resources, resourceBaseUrl) {
+utils.resourceReplacement = function(ResourceModel, src, resources, resourceBaseUrl) {
+ if (!ResourceModel) return null;
if (!ResourceModel || !resources) return null;
if (!ResourceModel.isResourceUrl(src)) return null;
@@ -138,13 +139,15 @@ utils.imageReplacement = function(ResourceModel, src, resources, resourceBaseUrl
}
const mime = resource.mime ? resource.mime.toLowerCase() : '';
- if (ResourceModel.isSupportedImageMimeType(mime)) {
+ const type = ResourceModel.mimeTypeToMediaType(mime);
+ if (type != 'unknown') {
let newSrc = `./${ResourceModel.filename(resource)}`;
if (resourceBaseUrl) newSrc = resourceBaseUrl + newSrc;
newSrc += `?t=${resource.updated_time}`;
return {
'data-resource-id': resource.id,
src: newSrc,
+ type: type,
};
}
diff --git a/ReactNativeClient/lib/models/Resource.js b/ReactNativeClient/lib/models/Resource.js
index bf268f9155..e266c8fd94 100644
--- a/ReactNativeClient/lib/models/Resource.js
+++ b/ReactNativeClient/lib/models/Resource.js
@@ -26,9 +26,16 @@ class Resource extends BaseItem {
return this.encryptionService_;
}
- static isSupportedImageMimeType(type) {
- const imageMimeTypes = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp'];
- return imageMimeTypes.indexOf(type.toLowerCase()) >= 0;
+ static mimeTypeToMediaType(type) {
+ if (type.startsWith('image/')) {
+ return 'image';
+ } else if (type.startsWith('audio/')) {
+ return 'audio';
+ } else if (type.startsWith('video/')) {
+ return 'video';
+ } else {
+ return 'unknown';
+ }
}
static fetchStatuses(resourceIds) {
@@ -205,7 +212,7 @@ class Resource extends BaseItem {
let tagAlt = resource.alt ? resource.alt : resource.title;
if (!tagAlt) tagAlt = '';
const lines = [];
- if (Resource.isSupportedImageMimeType(resource.mime)) {
+ if (Resource.mimeTypeToMediaType(resource.mime) === 'image') {
lines.push('`);
diff --git a/ReactNativeClient/lib/models/Setting.js b/ReactNativeClient/lib/models/Setting.js
index 28f75736eb..1864bc6ff6 100644
--- a/ReactNativeClient/lib/models/Setting.js
+++ b/ReactNativeClient/lib/models/Setting.js
@@ -477,6 +477,7 @@ class Setting extends BaseModel {
'markdown.plugin.emoji': { value: false, type: Setting.TYPE_BOOL, section: 'plugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable markdown emoji')}${wysiwygNo}` },
'markdown.plugin.insert': { value: false, type: Setting.TYPE_BOOL, section: 'plugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ++insert++ syntax')}${wysiwygNo}` },
'markdown.plugin.multitable': { value: false, type: Setting.TYPE_BOOL, section: 'plugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable multimarkdown table extension')}${wysiwygNo}` },
+ 'markdown.plugin.media': { value: true, type: Setting.TYPE_BOOL, section: 'plugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable media players (audio and video)')}${wysiwygNo}` },
// Tray icon (called AppIndicator) doesn't work in Ubuntu
// http://www.webupd8.org/2017/04/fix-appindicator-not-working-for.html