mirror of https://github.com/keeweb/keeweb.git
fix #713: markdown notes
This commit is contained in:
parent
f7ef9a7b4a
commit
dbc73a73e2
|
@ -1,8 +1,9 @@
|
||||||
|
const kdbxweb = require('kdbxweb');
|
||||||
const Format = require('../util/format');
|
const Format = require('../util/format');
|
||||||
const Locale = require('../util/locale');
|
const Locale = require('../util/locale');
|
||||||
|
const MdToHtml = require('../util/md-to-html');
|
||||||
const Links = require('../const/links');
|
const Links = require('../const/links');
|
||||||
const RuntimeInfo = require('./runtime-info');
|
const RuntimeInfo = require('./runtime-info');
|
||||||
const kdbxweb = require('kdbxweb');
|
|
||||||
|
|
||||||
const Templates = {
|
const Templates = {
|
||||||
db: require('templates/export/db.hbs'),
|
db: require('templates/export/db.hbs'),
|
||||||
|
@ -13,7 +14,7 @@ const FieldMapping = [
|
||||||
{ name: 'UserName', locStr: 'user' },
|
{ name: 'UserName', locStr: 'user' },
|
||||||
{ name: 'Password', locStr: 'password', protect: true },
|
{ name: 'Password', locStr: 'password', protect: true },
|
||||||
{ name: 'URL', locStr: 'website' },
|
{ name: 'URL', locStr: 'website' },
|
||||||
{ name: 'Notes', locStr: 'notes' }
|
{ name: 'Notes', locStr: 'notes', markdown: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
const KnownFields = { 'Title': true };
|
const KnownFields = { 'Title': true };
|
||||||
|
@ -38,12 +39,21 @@ function walkEntry(db, entry, parents) {
|
||||||
const path = parents.map(group => group.name).join(' / ');
|
const path = parents.map(group => group.name).join(' / ');
|
||||||
const fields = [];
|
const fields = [];
|
||||||
for (const field of FieldMapping) {
|
for (const field of FieldMapping) {
|
||||||
const value = entryField(entry, field.name);
|
let value = entryField(entry, field.name);
|
||||||
if (value) {
|
if (value) {
|
||||||
|
let html = false;
|
||||||
|
if (field.markdown) {
|
||||||
|
const converted = MdToHtml.convert(value);
|
||||||
|
if (converted !== value) {
|
||||||
|
value = converted;
|
||||||
|
html = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
fields.push({
|
fields.push({
|
||||||
title: Format.capFirst(Locale[field.locStr]),
|
title: Format.capFirst(Locale[field.locStr]),
|
||||||
value,
|
value,
|
||||||
protect: field.protect
|
protect: field.protect,
|
||||||
|
html
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
const marked = require('marked');
|
||||||
|
const dompurify = require('dompurify');
|
||||||
|
|
||||||
|
const whiteSpaceRegex = /<\/?p>|<br>|\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 `<div class="markdown">${sanitized}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = MdToHtml;
|
|
@ -233,6 +233,7 @@ const DetailsView = Backbone.View.extend({
|
||||||
name: '$Notes',
|
name: '$Notes',
|
||||||
title: Format.capFirst(Locale.notes),
|
title: Format.capFirst(Locale.notes),
|
||||||
multiline: 'true',
|
multiline: 'true',
|
||||||
|
markdown: true,
|
||||||
value() {
|
value() {
|
||||||
return model.notes;
|
return model.notes;
|
||||||
}
|
}
|
||||||
|
@ -1016,6 +1017,13 @@ const DetailsView = Backbone.View.extend({
|
||||||
}
|
}
|
||||||
options.push({ value: 'det-add-new', icon: 'plus', text: Locale.detMenuAddNewField });
|
options.push({ value: 'det-add-new', icon: 'plus', text: Locale.detMenuAddNewField });
|
||||||
options.push({ value: 'det-clone', icon: 'clone', text: Locale.detClone });
|
options.push({ value: 'det-clone', icon: 'clone', text: Locale.detClone });
|
||||||
|
if (canCopy) {
|
||||||
|
options.push({
|
||||||
|
value: 'copy-to-clipboard',
|
||||||
|
icon: 'copy',
|
||||||
|
text: Locale.detCopyEntryToClipboard
|
||||||
|
});
|
||||||
|
}
|
||||||
if (AutoType.enabled) {
|
if (AutoType.enabled) {
|
||||||
options.push({ value: 'det-auto-type', icon: 'keyboard-o', text: Locale.detAutoType });
|
options.push({ value: 'det-auto-type', icon: 'keyboard-o', text: Locale.detAutoType });
|
||||||
}
|
}
|
||||||
|
@ -1039,6 +1047,9 @@ const DetailsView = Backbone.View.extend({
|
||||||
case 'det-auto-type':
|
case 'det-auto-type':
|
||||||
this.autoType();
|
this.autoType();
|
||||||
break;
|
break;
|
||||||
|
case 'copy-to-clipboard':
|
||||||
|
this.copyToClipboard();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
const Backbone = require('backbone');
|
const Backbone = require('backbone');
|
||||||
|
const kdbxweb = require('kdbxweb');
|
||||||
const FieldView = require('./field-view');
|
const FieldView = require('./field-view');
|
||||||
const GeneratorView = require('../generator-view');
|
const GeneratorView = require('../generator-view');
|
||||||
const KeyHandler = require('../../comp/key-handler');
|
const KeyHandler = require('../../comp/key-handler');
|
||||||
const Keys = require('../../const/keys');
|
const Keys = require('../../const/keys');
|
||||||
const PasswordGenerator = require('../../util/password-generator');
|
const PasswordGenerator = require('../../util/password-generator');
|
||||||
const FeatureDetector = require('../../util/feature-detector');
|
const FeatureDetector = require('../../util/feature-detector');
|
||||||
const kdbxweb = require('kdbxweb');
|
|
||||||
const Tip = require('../../util/tip');
|
const Tip = require('../../util/tip');
|
||||||
|
const MdToHtml = require('../../util/md-to-html');
|
||||||
|
|
||||||
const FieldViewText = FieldView.extend({
|
const FieldViewText = FieldView.extend({
|
||||||
renderValue(value) {
|
renderValue(value) {
|
||||||
|
if (this.model.markdown) {
|
||||||
|
if (value && value.isProtected) {
|
||||||
|
value = value.getText();
|
||||||
|
}
|
||||||
|
return MdToHtml.convert(value);
|
||||||
|
}
|
||||||
return value && value.isProtected
|
return value && value.isProtected
|
||||||
? PasswordGenerator.presentValueWithLineBreaks(value)
|
? PasswordGenerator.presentValueWithLineBreaks(value)
|
||||||
: _.escape(value || '').replace(/\n/g, '<br/>');
|
: _.escape(value || '').replace(/\n/g, '<br/>');
|
||||||
|
|
|
@ -385,6 +385,19 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 $base-padding-h;
|
padding: 0 $base-padding-h;
|
||||||
}
|
}
|
||||||
|
.markdown {
|
||||||
|
white-space: normal;
|
||||||
|
h6 { font-size: 1rem; }
|
||||||
|
h5 { font-size: modular-scale(1, 1rem, 1.05); }
|
||||||
|
h4 { font-size: modular-scale(2, 1rem, 1.05); }
|
||||||
|
h3 { font-size: modular-scale(3, 1rem, 1.05); }
|
||||||
|
h2 { font-size: modular-scale(4, 1rem, 1.05); }
|
||||||
|
h1 { font-size: $small-header-font-size; }
|
||||||
|
ul, ol { margin-bottom: 1em; }
|
||||||
|
ul { list-style-type: initial; }
|
||||||
|
ol { list-style-type: decimal; }
|
||||||
|
li { margin-left: 2em; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--no-select {
|
&--no-select {
|
||||||
|
|
|
@ -9,7 +9,11 @@
|
||||||
{{#if field.protect}}
|
{{#if field.protect}}
|
||||||
<code>{{field.value}}</code>
|
<code>{{field.value}}</code>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{field.value}}
|
{{#if field.html}}
|
||||||
|
{{{field.value}}}
|
||||||
|
{{else}}
|
||||||
|
{{field.value}}
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -3628,6 +3628,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
"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": {
|
"domutils": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
|
||||||
|
@ -7897,6 +7902,11 @@
|
||||||
"object-visit": "^1.0.0"
|
"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": {
|
"math-random": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"baron": "3.0.3",
|
"baron": "3.0.3",
|
||||||
"base64-loader": "1.0.0",
|
"base64-loader": "1.0.0",
|
||||||
"bourbon": "^6.0.0",
|
"bourbon": "^6.0.0",
|
||||||
|
"dompurify": "^2.0.0",
|
||||||
"electron": "^6.0.9",
|
"electron": "^6.0.9",
|
||||||
"eslint": "^6.4.0",
|
"eslint": "^6.4.0",
|
||||||
"eslint-config-prettier": "^6.3.0",
|
"eslint-config-prettier": "^6.3.0",
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
"jsqrcode": "github:antelle/jsqrcode#0.1.3",
|
"jsqrcode": "github:antelle/jsqrcode#0.1.3",
|
||||||
"kdbxweb": "1.4.2",
|
"kdbxweb": "1.4.2",
|
||||||
"load-grunt-tasks": "5.1.0",
|
"load-grunt-tasks": "5.1.0",
|
||||||
|
"marked": "^0.7.0",
|
||||||
"mini-css-extract-plugin": "^0.8.0",
|
"mini-css-extract-plugin": "^0.8.0",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.12.0",
|
||||||
"node-stream-zip": "1.8.2",
|
"node-stream-zip": "1.8.2",
|
||||||
|
|
|
@ -8,6 +8,7 @@ Release notes
|
||||||
`*` #502: increased the default value of encryption rounds
|
`*` #502: increased the default value of encryption rounds
|
||||||
`+` #348: configurable system-wide shortcuts
|
`+` #348: configurable system-wide shortcuts
|
||||||
`+` #743: copying entry fields to clipboard
|
`+` #743: copying entry fields to clipboard
|
||||||
|
`+` #713: markdown notes
|
||||||
`*` devtools are now opened with alt-cmd-I
|
`*` devtools are now opened with alt-cmd-I
|
||||||
`-` fix #764: multiple attachments display
|
`-` fix #764: multiple attachments display
|
||||||
`-` fix multi-line fields display in history
|
`-` fix multi-line fields display in history
|
||||||
|
|
|
@ -30,7 +30,8 @@ function config(grunt, mode = 'production') {
|
||||||
'pikaday',
|
'pikaday',
|
||||||
'jsqrcode',
|
'jsqrcode',
|
||||||
'argon2-wasm',
|
'argon2-wasm',
|
||||||
'argon2'
|
'argon2',
|
||||||
|
'marked'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
@ -64,6 +65,8 @@ function config(grunt, mode = 'production') {
|
||||||
baron: `baron/baron${devMode ? '.min' : ''}.js`,
|
baron: `baron/baron${devMode ? '.min' : ''}.js`,
|
||||||
qrcode: `jsqrcode/dist/qrcode${devMode ? '.min' : ''}.js`,
|
qrcode: `jsqrcode/dist/qrcode${devMode ? '.min' : ''}.js`,
|
||||||
argon2: 'argon2-browser/dist/argon2.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',
|
hbs: 'handlebars/runtime.js',
|
||||||
'argon2-wasm': 'argon2-browser/dist/argon2.wasm',
|
'argon2-wasm': 'argon2-browser/dist/argon2.wasm',
|
||||||
templates: path.join(__dirname, 'app/templates'),
|
templates: path.join(__dirname, 'app/templates'),
|
||||||
|
|
Loading…
Reference in New Issue