mirror of https://github.com/keeweb/keeweb.git
HTML export
This commit is contained in:
parent
15b170db5d
commit
5703dceb0c
|
@ -0,0 +1,111 @@
|
|||
const Format = require('../util/format');
|
||||
const Locale = require('../util/locale');
|
||||
const Links = require('../const/links');
|
||||
const RuntimeInfo = require('./runtime-info');
|
||||
const kdbxweb = require('kdbxweb');
|
||||
|
||||
const Templates = {
|
||||
db: require('templates/export/db.hbs'),
|
||||
entry: require('templates/export/entry.hbs')
|
||||
};
|
||||
|
||||
const FieldMapping = [
|
||||
{ name: 'Password', locStr: 'password', protect: true },
|
||||
{ name: 'UserName', locStr: 'user' },
|
||||
{ name: 'URL', locStr: 'website' },
|
||||
{ name: 'Notes', locStr: 'notes' }
|
||||
];
|
||||
|
||||
const KnownFields = { 'Title': true };
|
||||
for (const { name } of FieldMapping) {
|
||||
KnownFields[name] = true;
|
||||
}
|
||||
|
||||
function walkGroup(db, group, parents) {
|
||||
parents = [...parents, group];
|
||||
if (
|
||||
group.uuid.equals(db.meta.recycleBinUuid) ||
|
||||
group.uuid.equals(db.meta.entryTemplatesGroup)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
const self = group.entries.map(entry => walkEntry(db, entry, parents)).join('\n');
|
||||
const children = group.groups.map(childGroup => walkGroup(db, childGroup, parents)).join('\n');
|
||||
return self + children;
|
||||
}
|
||||
|
||||
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);
|
||||
if (value) {
|
||||
fields.push({
|
||||
title: Format.capFirst(Locale[field.locStr]),
|
||||
value,
|
||||
protect: field.protect
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const fieldName of Object.keys(entry.fields)) {
|
||||
if (!KnownFields[fieldName]) {
|
||||
const value = entryField(entry, fieldName);
|
||||
if (value) {
|
||||
fields.push({
|
||||
title: fieldName,
|
||||
value,
|
||||
protect: entry.fields[fieldName].isProtected
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const title = entryField(entry, 'Title');
|
||||
let expires;
|
||||
if (entry.times.expires && entry.times.expiryTime) {
|
||||
expires = Format.dtStr(entry.times.expiryTime);
|
||||
}
|
||||
|
||||
const attachments = Object.entries(entry.binaries)
|
||||
.map(([name, data]) => {
|
||||
if (data && data.ref) {
|
||||
data = data.value;
|
||||
}
|
||||
if (data) {
|
||||
const base64 = kdbxweb.ByteUtils.bytesToBase64(data);
|
||||
data = 'data:application/octet-stream;base64,' + base64;
|
||||
}
|
||||
return { name, data };
|
||||
})
|
||||
.filter(att => att.name && att.data);
|
||||
|
||||
return Templates.entry({
|
||||
path,
|
||||
title,
|
||||
fields,
|
||||
tags: entry.tags.join(', '),
|
||||
created: Format.dtStr(entry.times.creationTime),
|
||||
modified: Format.dtStr(entry.times.lastModTime),
|
||||
expires,
|
||||
attachments
|
||||
});
|
||||
}
|
||||
|
||||
function entryField(entry, fieldName) {
|
||||
const value = entry.fields[fieldName];
|
||||
return (value && value.isProtected && value.getText()) || value || '';
|
||||
}
|
||||
|
||||
const KdbxToHtml = {
|
||||
convert(db, options) {
|
||||
const content = db.groups.map(group => walkGroup(db, group, [])).join('\n');
|
||||
return Templates.db({
|
||||
name: options.name,
|
||||
date: Format.dtStr(Date.now()),
|
||||
appLink: Links.Homepage,
|
||||
appVersion: RuntimeInfo.version,
|
||||
content
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = KdbxToHtml;
|
|
@ -1,4 +1,5 @@
|
|||
const Links = {
|
||||
Homepage: 'https://keeweb.info',
|
||||
Repo: 'https://github.com/keeweb/keeweb',
|
||||
Desktop: 'https://github.com/keeweb/keeweb/releases/latest',
|
||||
WebApp: 'https://app.keeweb.info',
|
||||
|
|
|
@ -434,6 +434,7 @@
|
|||
"setFileSync": "Sync",
|
||||
"setFileSyncVerb": "Sync",
|
||||
"setFileSaveToXml": "XML",
|
||||
"setFileSaveToHtml": "HTML",
|
||||
"setFileLastSync": "Last sync",
|
||||
"setFileLastSyncUnknown": "unknown",
|
||||
"setFileSyncInProgress": "sync in progress",
|
||||
|
@ -585,5 +586,11 @@
|
|||
"launcherFileFilter": "KeePass files",
|
||||
|
||||
"authPopupRequired": "Pop-ups are blocked",
|
||||
"authPopupRequiredBody": "Please allow pop-ups in your browser or try again."
|
||||
"authPopupRequiredBody": "Please allow pop-ups in your browser or try again.",
|
||||
|
||||
"exportFileInfo": "File information",
|
||||
"exportHtmlName": "Name",
|
||||
"exportHtmlDate": "Export date",
|
||||
"exportGenerator": "Software",
|
||||
"exportDescription": "This file is generated with {}."
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ const AppSettingsModel = Backbone.Model.extend({
|
|||
canImportXml: true,
|
||||
canRemoveLatest: true,
|
||||
canExportXml: true,
|
||||
canExportHtml: true,
|
||||
|
||||
dropbox: true,
|
||||
webdav: true,
|
||||
|
|
|
@ -3,6 +3,7 @@ const GroupCollection = require('../collections/group-collection');
|
|||
const GroupModel = require('./group-model');
|
||||
const IconUrl = require('../util/icon-url');
|
||||
const Logger = require('../util/logger');
|
||||
const KdbxToHtml = require('../comp/kdbx-to-html');
|
||||
const kdbxweb = require('kdbxweb');
|
||||
const demoFileData = require('demo.kdbx');
|
||||
|
||||
|
@ -420,6 +421,14 @@ const FileModel = Backbone.Model.extend({
|
|||
});
|
||||
},
|
||||
|
||||
getHtml(cb) {
|
||||
cb(
|
||||
KdbxToHtml.convert(this.db, {
|
||||
name: this.get('name')
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
getKeyFileHash() {
|
||||
const hash = this.db.credentials.keyFileHash;
|
||||
return hash ? kdbxweb.ByteUtils.bytesToBase64(hash.getBinary()) : null;
|
||||
|
|
|
@ -25,6 +25,7 @@ const SettingsFileView = Backbone.View.extend({
|
|||
'click .settings__file-button-close': 'closeFile',
|
||||
'click .settings__file-save-to-file': 'saveToFile',
|
||||
'click .settings__file-save-to-xml': 'saveToXml',
|
||||
'click .settings__file-save-to-html': 'saveToHtml',
|
||||
'click .settings__file-save-to-storage': 'saveToStorage',
|
||||
'change #settings__file-key-file': 'keyFileChange',
|
||||
'click #settings__file-file-select-link': 'triggerSelectFile',
|
||||
|
@ -107,7 +108,8 @@ const SettingsFileView = Backbone.View.extend({
|
|||
kdfParameters: this.kdfParametersToUi(this.model.get('kdfParameters')),
|
||||
storageProviders,
|
||||
canBackup,
|
||||
canExportXml: AppSettingsModel.instance.get('canExportXml')
|
||||
canExportXml: AppSettingsModel.instance.get('canExportXml'),
|
||||
canExportHtml: AppSettingsModel.instance.get('canExportHtml')
|
||||
});
|
||||
if (!this.model.get('created')) {
|
||||
this.$el
|
||||
|
@ -256,6 +258,13 @@ const SettingsFileView = Backbone.View.extend({
|
|||
});
|
||||
},
|
||||
|
||||
saveToHtml() {
|
||||
this.model.getHtml(html => {
|
||||
const blob = new Blob([html], { type: 'text/html' });
|
||||
FileSaver.saveAs(blob, this.model.get('name') + '.html');
|
||||
});
|
||||
},
|
||||
|
||||
saveToStorage(e) {
|
||||
if (this.model.get('syncing') || this.model.get('demo')) {
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>{{name}}</title>
|
||||
<link href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAF0lEQVRIx2NgGAWjYBSMglEwCkbBSAcACBAAAeaR9cIAAAAASUVORK5CYII=" rel="icon" type="image/x-icon" />
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, "BlinkMacSystemFont", "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
}
|
||||
td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
tr:nth-of-type(even) {
|
||||
background: #fafafa;
|
||||
}
|
||||
td:first-of-type {
|
||||
width: 30%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{name}}</h1>
|
||||
<h2>{{res 'exportFileInfo'}}</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{res 'exportHtmlName'}}</td>
|
||||
<td>{{name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{res 'exportHtmlDate'}}</td>
|
||||
<td>{{date}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{res 'exportGenerator'}}</td>
|
||||
<td>KeeWeb v{{appVersion}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>{{res 'exportEntries'}}</h2>
|
||||
<p>
|
||||
{{{content}}}
|
||||
</p>
|
||||
<p>
|
||||
{{#res 'exportDescription'}}<a href="{{appLink}}" rel="noreferrer noopener" target="_blank">KeeWeb</a>{{/res}}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
<h2>
|
||||
{{#if title}}{{title}}{{else}}({{Res 'noTitle'}}){{/if}}
|
||||
</h2>
|
||||
<table>
|
||||
{{#each fields as |field|}}
|
||||
<tr>
|
||||
<td>{{field.title}}</td>
|
||||
<td>
|
||||
{{#if field.protect}}
|
||||
<code>{{field.value}}</code>
|
||||
{{else}}
|
||||
{{field.value}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{#if tags}}
|
||||
<tr>
|
||||
<td>{{Res 'tags'}}</td>
|
||||
<td>{{tags}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
<tr>
|
||||
<td>{{Res 'group'}}</td>
|
||||
<td>{{path}}</td>
|
||||
</tr>
|
||||
{{#if attachments.length}}
|
||||
<tr>
|
||||
<td>{{res 'detAttachments'}}</td>
|
||||
<td>
|
||||
{{#each attachments as |attachment|}}
|
||||
<a href="{{{attachment.data}}}" download="{{attachment.name}}">{{attachment.name}}</a>
|
||||
{{~#unless @last}}, {{/unless}}
|
||||
{{/each}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
<tr>
|
||||
<td>{{res 'detCreated'}}</td>
|
||||
<td>{{created}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{res 'detUpdated'}}</td>
|
||||
<td>{{modified}}</td>
|
||||
</tr>
|
||||
{{#if expires}}
|
||||
<tr>
|
||||
<td>{{res 'detExpires'}}</td>
|
||||
<td>{{expires}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
</table>
|
|
@ -41,6 +41,11 @@
|
|||
<i class="fa fa-code"></i>{{res 'setFileSaveToXml'}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if canExportHtml}}
|
||||
<div class="settings__file-save-to settings__file-save-to-html">
|
||||
<i class="fa fa-html5"></i>{{res 'setFileSaveToHtml'}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if storage}}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
Release notes
|
||||
-------------
|
||||
##### v1.10 (TBD)
|
||||
`+` macOS Dark theme
|
||||
`+` pretty-printing exported XML files
|
||||
`+` HTML export
|
||||
`+` config option to disable xml export (canExportXml)
|
||||
`+` xml files can be now opened as regular files
|
||||
`+` macOS Dark theme
|
||||
`-` fix #1154: relative Destination header in WebDAV MOVE
|
||||
`-` fix #1129: webdav storage error on Unicode filenames
|
||||
`*` donation link changed
|
||||
|
@ -11,7 +13,6 @@ Release notes
|
|||
`*` dropped support for browsers without css variables
|
||||
`*` displaying websites as HTTPS if no scheme is provided
|
||||
`+` confirmation for deleting an entry on mobile
|
||||
`+` pretty-printing exported XML files
|
||||
|
||||
##### v1.9.3 (2019-09-07)
|
||||
`-` fixed group settings not being displayed
|
||||
|
|
Loading…
Reference in New Issue