diff --git a/app/scripts/comp/kdbx-to-html.js b/app/scripts/comp/kdbx-to-html.js
index e24acdd5..e9bc119c 100644
--- a/app/scripts/comp/kdbx-to-html.js
+++ b/app/scripts/comp/kdbx-to-html.js
@@ -1,8 +1,9 @@
+const kdbxweb = require('kdbxweb');
const Format = require('../util/format');
const Locale = require('../util/locale');
+const MdToHtml = require('../util/md-to-html');
const Links = require('../const/links');
const RuntimeInfo = require('./runtime-info');
-const kdbxweb = require('kdbxweb');
const Templates = {
db: require('templates/export/db.hbs'),
@@ -13,7 +14,7 @@ const FieldMapping = [
{ name: 'UserName', locStr: 'user' },
{ name: 'Password', locStr: 'password', protect: true },
{ name: 'URL', locStr: 'website' },
- { name: 'Notes', locStr: 'notes' }
+ { name: 'Notes', locStr: 'notes', markdown: true }
];
const KnownFields = { 'Title': true };
@@ -38,12 +39,21 @@ function walkEntry(db, entry, parents) {
const path = parents.map(group => group.name).join(' / ');
const fields = [];
for (const field of FieldMapping) {
- const value = entryField(entry, field.name);
+ let value = entryField(entry, field.name);
if (value) {
+ let html = false;
+ if (field.markdown) {
+ const converted = MdToHtml.convert(value);
+ if (converted !== value) {
+ value = converted;
+ html = true;
+ }
+ }
fields.push({
title: Format.capFirst(Locale[field.locStr]),
value,
- protect: field.protect
+ protect: field.protect,
+ html
});
}
}
diff --git a/app/scripts/util/md-to-html.js b/app/scripts/util/md-to-html.js
new file mode 100644
index 00000000..bd7c73cc
--- /dev/null
+++ b/app/scripts/util/md-to-html.js
@@ -0,0 +1,23 @@
+const marked = require('marked');
+const dompurify = require('dompurify');
+
+const whiteSpaceRegex = /<\/?p>|
|\r|\n/g;
+
+const MdToHtml = {
+ convert(md) {
+ if (!md) {
+ return '';
+ }
+ const html = marked(md);
+ const htmlWithoutLineBreaks = html.replace(whiteSpaceRegex, '');
+ const mdWithoutLineBreaks = md.replace(whiteSpaceRegex, '');
+ if (htmlWithoutLineBreaks === mdWithoutLineBreaks) {
+ return md;
+ } else {
+ const sanitized = dompurify.sanitize(html);
+ return `
{{field.value}}
{{else}}
- {{field.value}}
+ {{#if field.html}}
+ {{{field.value}}}
+ {{else}}
+ {{field.value}}
+ {{/if}}
{{/if}}
diff --git a/package-lock.json b/package-lock.json
index a462149d..e73c8710 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3628,6 +3628,11 @@
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
},
+ "dompurify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.0.tgz",
+ "integrity": "sha512-i8LWSIMDpGmv7AbOcQOyy54L4TrRhjs6yrSessoNeYspsAtgaKiiGeBAG5959qLfhGvyndkHeyZWxx0dd4iDxw=="
+ },
"domutils": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
@@ -7897,6 +7902,11 @@
"object-visit": "^1.0.0"
}
},
+ "marked": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz",
+ "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg=="
+ },
"math-random": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
diff --git a/package.json b/package.json
index a88c7999..95fda725 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"baron": "3.0.3",
"base64-loader": "1.0.0",
"bourbon": "^6.0.0",
+ "dompurify": "^2.0.0",
"electron": "^6.0.9",
"eslint": "^6.4.0",
"eslint-config-prettier": "^6.3.0",
@@ -57,6 +58,7 @@
"jsqrcode": "github:antelle/jsqrcode#0.1.3",
"kdbxweb": "1.4.2",
"load-grunt-tasks": "5.1.0",
+ "marked": "^0.7.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0",
"node-stream-zip": "1.8.2",
diff --git a/release-notes.md b/release-notes.md
index 1202d443..d18ec410 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -8,6 +8,7 @@ Release notes
`*` #502: increased the default value of encryption rounds
`+` #348: configurable system-wide shortcuts
`+` #743: copying entry fields to clipboard
+`+` #713: markdown notes
`*` devtools are now opened with alt-cmd-I
`-` fix #764: multiple attachments display
`-` fix multi-line fields display in history
diff --git a/webpack.config.js b/webpack.config.js
index aeaaa0e1..59de7c08 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -30,7 +30,8 @@ function config(grunt, mode = 'production') {
'pikaday',
'jsqrcode',
'argon2-wasm',
- 'argon2'
+ 'argon2',
+ 'marked'
]
},
output: {
@@ -64,6 +65,8 @@ function config(grunt, mode = 'production') {
baron: `baron/baron${devMode ? '.min' : ''}.js`,
qrcode: `jsqrcode/dist/qrcode${devMode ? '.min' : ''}.js`,
argon2: 'argon2-browser/dist/argon2.js',
+ marked: devMode ? 'marked/lib/marked.js' : 'marked/dist/marked.min.js',
+ dompurify: `dompurify/dist/purify${devMode ? '.min' : ''}.js`,
hbs: 'handlebars/runtime.js',
'argon2-wasm': 'argon2-browser/dist/argon2.wasm',
templates: path.join(__dirname, 'app/templates'),