mirror of https://github.com/keeweb/keeweb.git
commit
e4c9f1d7c9
55
Gruntfile.js
55
Gruntfile.js
|
@ -3,6 +3,9 @@
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
path = require('path');
|
path = require('path');
|
||||||
|
|
||||||
|
/* jshint node:true */
|
||||||
|
/* jshint browser:false */
|
||||||
|
|
||||||
var StringReplacePlugin = require('string-replace-webpack-plugin');
|
var StringReplacePlugin = require('string-replace-webpack-plugin');
|
||||||
|
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
@ -55,8 +58,8 @@ module.exports = function(grunt) {
|
||||||
},
|
},
|
||||||
clean: {
|
clean: {
|
||||||
dist: ['dist', 'tmp'],
|
dist: ['dist', 'tmp'],
|
||||||
desktop_dist: ['dist/desktop'],
|
'desktop_dist': ['dist/desktop'],
|
||||||
desktop_tmp: ['tmp/desktop']
|
'desktop_tmp': ['tmp/desktop']
|
||||||
},
|
},
|
||||||
copy: {
|
copy: {
|
||||||
html: {
|
html: {
|
||||||
|
@ -76,6 +79,13 @@ module.exports = function(grunt) {
|
||||||
expand: true,
|
expand: true,
|
||||||
flatten: true
|
flatten: true
|
||||||
},
|
},
|
||||||
|
'desktop_app_content': {
|
||||||
|
cwd: 'electron/',
|
||||||
|
src: '**',
|
||||||
|
dest: 'tmp/desktop/app/',
|
||||||
|
expand: true,
|
||||||
|
nonull: true
|
||||||
|
},
|
||||||
'desktop_osx': {
|
'desktop_osx': {
|
||||||
src: 'tmp/desktop/KeeWeb.dmg',
|
src: 'tmp/desktop/KeeWeb.dmg',
|
||||||
dest: 'dist/desktop/KeeWeb.mac.dmg',
|
dest: 'dist/desktop/KeeWeb.mac.dmg',
|
||||||
|
@ -141,12 +151,16 @@ module.exports = function(grunt) {
|
||||||
'string-replace': {
|
'string-replace': {
|
||||||
manifest: {
|
manifest: {
|
||||||
options: {
|
options: {
|
||||||
replacements: [{
|
replacements: [
|
||||||
pattern: '# YYYY-MM-DD:v0.0.0',
|
{ pattern: '# YYYY-MM-DD:v0.0.0', replacement: '# ' + dt + ':v' + pkg.version },
|
||||||
replacement: '# ' + dt + ':v' + pkg.version
|
{ pattern: 'vElectron', replacement: electronVersion }
|
||||||
}]
|
]
|
||||||
},
|
},
|
||||||
files: { 'dist/manifest.appcache': 'app/manifest.appcache' }
|
files: { 'dist/manifest.appcache': 'app/manifest.appcache' }
|
||||||
|
},
|
||||||
|
'desktop_html': {
|
||||||
|
options: { replacements: [{ pattern: ' manifest="manifest.appcache"', replacement: '' }] },
|
||||||
|
files: { 'tmp/desktop/app/index.html': 'dist/index.html' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
webpack: {
|
webpack: {
|
||||||
|
@ -190,7 +204,8 @@ module.exports = function(grunt) {
|
||||||
}]})},
|
}]})},
|
||||||
{ test: /runtime\-info\.js$/, loader: StringReplacePlugin.replace({ replacements: [
|
{ test: /runtime\-info\.js$/, loader: StringReplacePlugin.replace({ replacements: [
|
||||||
{ pattern: /@@VERSION/g, replacement: function() { return pkg.version; } },
|
{ pattern: /@@VERSION/g, replacement: function() { return pkg.version; } },
|
||||||
{ pattern: /@@DATE/g, replacement: function() { return dt; } }
|
{ pattern: /@@DATE/g, replacement: function() { return dt; } },
|
||||||
|
{ pattern: /@@COMMIT/g, replacement: function() { return grunt.config.get('gitinfo.local.branch.current.shortSHA'); } }
|
||||||
]})},
|
]})},
|
||||||
{ test: /zepto(\.min)?\.js$/, loader: 'exports?Zepto; delete window.$; delete window.Zepto;' },
|
{ test: /zepto(\.min)?\.js$/, loader: 'exports?Zepto; delete window.$; delete window.Zepto;' },
|
||||||
{ test: /baron(\.min)?\.js$/, loader: 'exports?baron; delete window.baron;' },
|
{ test: /baron(\.min)?\.js$/, loader: 'exports?baron; delete window.baron;' },
|
||||||
|
@ -251,7 +266,7 @@ module.exports = function(grunt) {
|
||||||
electron: {
|
electron: {
|
||||||
options: {
|
options: {
|
||||||
name: 'KeeWeb',
|
name: 'KeeWeb',
|
||||||
dir: 'electron',
|
dir: 'tmp/desktop/app',
|
||||||
out: 'tmp/desktop',
|
out: 'tmp/desktop',
|
||||||
version: electronVersion,
|
version: electronVersion,
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
|
@ -326,15 +341,26 @@ module.exports = function(grunt) {
|
||||||
},
|
},
|
||||||
compress: {
|
compress: {
|
||||||
linux: {
|
linux: {
|
||||||
options: {
|
options: { archive: 'tmp/desktop/KeeWeb.linux.x64.zip' },
|
||||||
archive: 'tmp/desktop/KeeWeb.linux.x64.zip'
|
|
||||||
},
|
|
||||||
files: [{ cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true }]
|
files: [{ cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true }]
|
||||||
|
},
|
||||||
|
'desktop_update': {
|
||||||
|
options: { archive: 'dist/desktop/UpdateDesktop.zip' },
|
||||||
|
files: [{ cwd: 'tmp/desktop/app', src: '**', expand: true }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'validate-desktop-update': {
|
||||||
|
desktop: {
|
||||||
|
options: {
|
||||||
|
file: 'dist/desktop/UpdateDesktop.zip',
|
||||||
|
expected: ['main.js', 'app.js', 'index.html', 'package.json', 'node_modules/node-stream-zip/node_stream_zip.js']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
grunt.registerTask('default', [
|
grunt.registerTask('default', [
|
||||||
|
'gitinfo',
|
||||||
'bower-install-simple',
|
'bower-install-simple',
|
||||||
'clean',
|
'clean',
|
||||||
'jshint',
|
'jshint',
|
||||||
|
@ -347,13 +373,18 @@ module.exports = function(grunt) {
|
||||||
'postcss',
|
'postcss',
|
||||||
'inline',
|
'inline',
|
||||||
'htmlmin',
|
'htmlmin',
|
||||||
'string-replace'
|
'string-replace:manifest'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
grunt.registerTask('desktop', [
|
grunt.registerTask('desktop', [
|
||||||
|
'default',
|
||||||
'gitinfo',
|
'gitinfo',
|
||||||
'clean:desktop_tmp',
|
'clean:desktop_tmp',
|
||||||
'clean:desktop_dist',
|
'clean:desktop_dist',
|
||||||
|
'copy:desktop_app_content',
|
||||||
|
'string-replace:desktop_html',
|
||||||
|
'compress:desktop_update',
|
||||||
|
'validate-desktop-update',
|
||||||
'electron',
|
'electron',
|
||||||
'electron_builder',
|
'electron_builder',
|
||||||
'compress:linux',
|
'compress:linux',
|
||||||
|
|
19
README.md
19
README.md
|
@ -24,7 +24,6 @@ Reading and display is mostly complete; modification and sync is under construct
|
||||||
|
|
||||||
These major issues are in progress, or will be fixed in next releases, before v1.0:
|
These major issues are in progress, or will be fixed in next releases, before v1.0:
|
||||||
|
|
||||||
- auto-update is not implemented
|
|
||||||
- dropbox sync is one-way: changes are not loaded from dropbox, only saved
|
- dropbox sync is one-way: changes are not loaded from dropbox, only saved
|
||||||
|
|
||||||
# Self-hosting
|
# Self-hosting
|
||||||
|
@ -34,10 +33,20 @@ You can download the latest distribution files from [gh-pages](https://github.co
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
The app can be built with grunt: `grunt` (html file will be in `dist/`) or `grunt watch` (result will be in `tmp/`).
|
The app can be built with grunt: `grunt` (html file will be in `dist/`).
|
||||||
Electron app is built with `grunt electron` (works only under mac osx as it builds dmg; requires wine).
|
Desktop apps are built with `grunt desktop`. This works only in mac osx as it builds dmg; requires wine.
|
||||||
To run Electron app without building, install electron package (`npm install electron-prebuilt -g`) and start with `electron ./electron/`.
|
To run Electron app without building, install electron package (`npm install electron-prebuilt -g`) and start in this way:
|
||||||
For debug build: 1. run `grunt`, 2. run `grunt watch`, 3. open `tmp/index.html`.
|
```bash
|
||||||
|
$ cd electron
|
||||||
|
$ electron . --htmlpath=../tmp
|
||||||
|
```
|
||||||
|
|
||||||
|
For debug build:
|
||||||
|
|
||||||
|
1. run `grunt`
|
||||||
|
2. run `grunt watch`
|
||||||
|
3. open `tmp/index.html`
|
||||||
|
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
CACHE MANIFEST
|
CACHE MANIFEST
|
||||||
|
|
||||||
# YYYY-MM-DD:v0.0.0
|
# YYYY-MM-DD:v0.0.0 evElectron
|
||||||
|
|
||||||
CACHE:
|
CACHE:
|
||||||
index.html
|
index.html
|
||||||
|
|
|
@ -5,6 +5,7 @@ var AppModel = require('./models/app-model'),
|
||||||
KeyHandler = require('./comp/key-handler'),
|
KeyHandler = require('./comp/key-handler'),
|
||||||
Alerts = require('./comp/alerts'),
|
Alerts = require('./comp/alerts'),
|
||||||
DropboxLink = require('./comp/dropbox-link'),
|
DropboxLink = require('./comp/dropbox-link'),
|
||||||
|
Updater = require('./comp/updater'),
|
||||||
LastOpenFiles = require('./comp/last-open-files'),
|
LastOpenFiles = require('./comp/last-open-files'),
|
||||||
ThemeChanger = require('./util/theme-changer');
|
ThemeChanger = require('./util/theme-changer');
|
||||||
|
|
||||||
|
@ -47,6 +48,6 @@ $(function() {
|
||||||
} else {
|
} else {
|
||||||
appView.showOpenFile();
|
appView.showOpenFile();
|
||||||
}
|
}
|
||||||
|
Updater.init();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ var Backbone = require('backbone');
|
||||||
var Launcher;
|
var Launcher;
|
||||||
|
|
||||||
if (window.process && window.process.versions && window.process.versions.electron) {
|
if (window.process && window.process.versions && window.process.versions.electron) {
|
||||||
|
/* jshint node:true */
|
||||||
Launcher = {
|
Launcher = {
|
||||||
name: 'electron',
|
name: 'electron',
|
||||||
version: window.process.versions.electron,
|
version: window.process.versions.electron,
|
||||||
|
@ -18,6 +19,9 @@ if (window.process && window.process.versions && window.process.versions.electro
|
||||||
openDevTools: function() {
|
openDevTools: function() {
|
||||||
this.req('remote').getCurrentWindow().openDevTools();
|
this.req('remote').getCurrentWindow().openDevTools();
|
||||||
},
|
},
|
||||||
|
getAppVersion: function() {
|
||||||
|
return this.remReq('app').getVersion();
|
||||||
|
},
|
||||||
getSaveFileName: function(defaultPath, cb) {
|
getSaveFileName: function(defaultPath, cb) {
|
||||||
if (defaultPath) {
|
if (defaultPath) {
|
||||||
var homePath = this.remReq('app').getPath('userDesktop');
|
var homePath = this.remReq('app').getPath('userDesktop');
|
||||||
|
@ -32,6 +36,9 @@ if (window.process && window.process.versions && window.process.versions.electro
|
||||||
getUserDataPath: function(fileName) {
|
getUserDataPath: function(fileName) {
|
||||||
return this.req('path').join(this.remReq('app').getPath('userData'), fileName || '');
|
return this.req('path').join(this.remReq('app').getPath('userData'), fileName || '');
|
||||||
},
|
},
|
||||||
|
getTempPath: function(fileName) {
|
||||||
|
return this.req('path').join(this.remReq('app').getPath('temp'), fileName || '');
|
||||||
|
},
|
||||||
writeFile: function(path, data) {
|
writeFile: function(path, data) {
|
||||||
this.req('fs').writeFileSync(path, new window.Buffer(data));
|
this.req('fs').writeFileSync(path, new window.Buffer(data));
|
||||||
},
|
},
|
||||||
|
@ -42,9 +49,28 @@ if (window.process && window.process.versions && window.process.versions.electro
|
||||||
fileExists: function(path) {
|
fileExists: function(path) {
|
||||||
return this.req('fs').existsSync(path);
|
return this.req('fs').existsSync(path);
|
||||||
},
|
},
|
||||||
|
preventExit: function(e) {
|
||||||
|
e.returnValue = false;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
exit: function() {
|
exit: function() {
|
||||||
Launcher.exitRequested = true;
|
Launcher.exitRequested = true;
|
||||||
this.remReq('app').quit();
|
this.requestExit();
|
||||||
|
},
|
||||||
|
requestExit: function() {
|
||||||
|
var app = this.remReq('app');
|
||||||
|
if (this.restartPending) {
|
||||||
|
app.quitAndRestart();
|
||||||
|
} else {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requestRestart: function() {
|
||||||
|
this.restartPending = true;
|
||||||
|
this.requestExit();
|
||||||
|
},
|
||||||
|
cancelRestart: function() {
|
||||||
|
this.restartPending = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.launcherOpen = function(path) {
|
window.launcherOpen = function(path) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ var Launcher = require('../comp/launcher');
|
||||||
var RuntimeInfo = {
|
var RuntimeInfo = {
|
||||||
version: '@@VERSION',
|
version: '@@VERSION',
|
||||||
buildDate: '@@DATE',
|
buildDate: '@@DATE',
|
||||||
|
commit: '@@COMMIT',
|
||||||
userAgent: navigator.userAgent,
|
userAgent: navigator.userAgent,
|
||||||
launcher: Launcher ? Launcher.name + ' v' + Launcher.version : ''
|
launcher: Launcher ? Launcher.name + ' v' + Launcher.version : ''
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Launcher = require('./launcher');
|
||||||
|
|
||||||
|
var Transport = {
|
||||||
|
httpGet: function(config) {
|
||||||
|
var tmpFile;
|
||||||
|
var fs = Launcher.req('fs');
|
||||||
|
if (config.file) {
|
||||||
|
tmpFile = Launcher.getTempPath(config.file);
|
||||||
|
if (fs.existsSync(tmpFile)) {
|
||||||
|
try {
|
||||||
|
if (config.cache && fs.statSync(tmpFile).size > 0) {
|
||||||
|
console.log('File already downloaded ' + config.url);
|
||||||
|
return config.success(tmpFile);
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(tmpFile);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
fs.unlink(tmpFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var proto = config.url.split(':')[0];
|
||||||
|
console.log('GET ' + config.url);
|
||||||
|
var opts = Launcher.req('url').parse(config.url);
|
||||||
|
opts.headers = { 'User-Agent': navigator.userAgent };
|
||||||
|
Launcher.req(proto).get(opts, function(res) {
|
||||||
|
console.log('Response from ' + config.url + ': ' + res.statusCode);
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
if (config.file) {
|
||||||
|
var file = fs.createWriteStream(tmpFile);
|
||||||
|
res.pipe(file);
|
||||||
|
file.on('finish', function() {
|
||||||
|
file.close(function() { config.success(tmpFile); });
|
||||||
|
});
|
||||||
|
file.on('error', function(err) { config.error(err); });
|
||||||
|
} else {
|
||||||
|
var data = [];
|
||||||
|
res.on('data', function(chunk) { data.push(chunk); });
|
||||||
|
res.on('end', function() {
|
||||||
|
data = window.Buffer.concat(data);
|
||||||
|
if (config.utf8) {
|
||||||
|
data = data.toString('utf8');
|
||||||
|
}
|
||||||
|
config.success(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.error('HTTP status ' + res.statusCode);
|
||||||
|
}
|
||||||
|
}).on('error', function(e) {
|
||||||
|
console.error('Cannot GET ' + config.url, e);
|
||||||
|
if (tmpFile) {
|
||||||
|
fs.unlink(tmpFile);
|
||||||
|
}
|
||||||
|
config.error(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Transport;
|
|
@ -0,0 +1,198 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Backbone = require('backbone'),
|
||||||
|
RuntimeInfo = require('./runtime-info'),
|
||||||
|
Links = require('../const/links'),
|
||||||
|
Launcher = require('../comp/launcher'),
|
||||||
|
AppSettingsModel = require('../models/app-settings-model'),
|
||||||
|
UpdateModel = require('../models/update-model'),
|
||||||
|
Transport = require('../comp/transport');
|
||||||
|
|
||||||
|
var Updater = {
|
||||||
|
UpdateInterval: 1000*60*60*24,
|
||||||
|
MinUpdateTimeout: 500,
|
||||||
|
MinUpdateSize: 10000,
|
||||||
|
UpdateCheckFiles: ['index.html', 'app.js'],
|
||||||
|
nextCheckTimeout: null,
|
||||||
|
updateCheckDate: new Date(0),
|
||||||
|
|
||||||
|
enabledAutoUpdate: function() {
|
||||||
|
return Launcher && AppSettingsModel.instance.get('autoUpdate');
|
||||||
|
},
|
||||||
|
|
||||||
|
updateInProgress: function() {
|
||||||
|
return UpdateModel.instance.get('status') === 'checking' ||
|
||||||
|
['downloading', 'extracting'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
var willCheckNow = this.scheduleNextCheck();
|
||||||
|
if (!willCheckNow && this.enabledAutoUpdate()) {
|
||||||
|
this.check();
|
||||||
|
}
|
||||||
|
if (!Launcher && window.applicationCache) {
|
||||||
|
window.applicationCache.addEventListener('updateready', this.checkAppCacheUpdateReady.bind(this));
|
||||||
|
this.checkAppCacheUpdateReady();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduleNextCheck: function() {
|
||||||
|
if (this.nextCheckTimeout) {
|
||||||
|
clearTimeout(this.nextCheckTimeout);
|
||||||
|
this.nextCheckTimeout = null;
|
||||||
|
}
|
||||||
|
if (!this.enabledAutoUpdate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var timeDiff = this.MinUpdateTimeout;
|
||||||
|
var lastCheckDate = UpdateModel.instance.get('lastCheckDate');
|
||||||
|
if (lastCheckDate) {
|
||||||
|
timeDiff = Math.min(Math.max(this.UpdateInterval + (lastCheckDate - new Date()), this.MinUpdateTimeout), this.UpdateInterval);
|
||||||
|
}
|
||||||
|
this.nextCheckTimeout = setTimeout(this.check.bind(this), timeDiff);
|
||||||
|
console.log('Update check will happen in ' + Math.round(timeDiff / 1000) + 's');
|
||||||
|
return timeDiff === this.MinUpdateTimeout;
|
||||||
|
},
|
||||||
|
|
||||||
|
check: function(startedByUser) {
|
||||||
|
if (!Launcher || this.updateInProgress()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.checkManualDownload()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateModel.instance.set('status', 'checking');
|
||||||
|
var that = this;
|
||||||
|
if (!startedByUser) {
|
||||||
|
// additional protection from broken program logic, to ensure that auto-checks are not performed more than once an hour
|
||||||
|
var diffMs = new Date() - this.updateCheckDate;
|
||||||
|
if (isNaN(diffMs) || diffMs < 1000 * 60 * 60) {
|
||||||
|
console.error('Prevented update check; last check was performed at ' + this.updateCheckDate);
|
||||||
|
that.scheduleNextCheck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateCheckDate = new Date();
|
||||||
|
}
|
||||||
|
console.log('Checking for update...');
|
||||||
|
Transport.httpGet({
|
||||||
|
url: Links.WebApp + 'manifest.appcache',
|
||||||
|
utf8: true,
|
||||||
|
success: function(data) {
|
||||||
|
var dt = new Date();
|
||||||
|
var match = data.match(/#\s*(\d+\-\d+\-\d+):v([\d+\.\w]+)/);
|
||||||
|
console.log('Update check: ' + (match ? match[0] : 'unknown'));
|
||||||
|
if (!match) {
|
||||||
|
var errMsg = 'No version info found';
|
||||||
|
UpdateModel.instance.set({ status: 'error', lastCheckDate: dt, lastCheckError: errMsg });
|
||||||
|
UpdateModel.instance.save();
|
||||||
|
that.scheduleNextCheck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var prevLastVersion = UpdateModel.instance.get('lastVersion');
|
||||||
|
UpdateModel.instance.set({
|
||||||
|
status: 'ok',
|
||||||
|
lastCheckDate: dt,
|
||||||
|
lastSuccessCheckDate: dt,
|
||||||
|
lastVersionReleaseDate: new Date(match[1]),
|
||||||
|
lastVersion: match[2],
|
||||||
|
lastcheckError: null
|
||||||
|
});
|
||||||
|
UpdateModel.instance.save();
|
||||||
|
that.scheduleNextCheck();
|
||||||
|
if (prevLastVersion === UpdateModel.instance.get('lastVersion') &&
|
||||||
|
UpdateModel.instance.get('updateStatus') === 'ready') {
|
||||||
|
console.log('Waiting for the user to apply downloaded update');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
that.update(startedByUser);
|
||||||
|
},
|
||||||
|
error: function(e) {
|
||||||
|
console.error('Update check error', e);
|
||||||
|
UpdateModel.instance.set({
|
||||||
|
status: 'error',
|
||||||
|
lastCheckDate: new Date(),
|
||||||
|
lastCheckError: 'Error checking last version'
|
||||||
|
});
|
||||||
|
UpdateModel.instance.save();
|
||||||
|
that.scheduleNextCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
checkManualDownload: function() {
|
||||||
|
if (+Launcher.getAppVersion().split('.')[1] <= 2) {
|
||||||
|
UpdateModel.instance.set({ updateStatus: 'ready', updateManual: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function(startedByUser) {
|
||||||
|
var ver = UpdateModel.instance.get('lastVersion');
|
||||||
|
if (!Launcher || ver === RuntimeInfo.version) {
|
||||||
|
console.log('You are using the latest version');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateModel.instance.set({ updateStatus: 'downloading', updateError: null });
|
||||||
|
var that = this;
|
||||||
|
console.log('Downloading update', ver);
|
||||||
|
Transport.httpGet({
|
||||||
|
url: Links.UpdateDesktop.replace('{ver}', ver),
|
||||||
|
file: 'KeeWeb-' + ver + '.zip',
|
||||||
|
cache: !startedByUser,
|
||||||
|
success: function(filePath) {
|
||||||
|
UpdateModel.instance.set('updateStatus', 'extracting');
|
||||||
|
console.log('Extracting update file', that.UpdateCheckFiles, filePath);
|
||||||
|
that.extractAppUpdate(filePath, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error extracting update', err);
|
||||||
|
UpdateModel.instance.set({ updateStatus: 'error', updateError: 'Error extracting update' });
|
||||||
|
} else {
|
||||||
|
UpdateModel.instance.set({ updateStatus: 'ready', updateError: null });
|
||||||
|
if (!startedByUser) {
|
||||||
|
Backbone.trigger('update-app');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(e) {
|
||||||
|
console.error('Error downloading update', e);
|
||||||
|
UpdateModel.instance.set({ updateStatus: 'error', updateError: 'Error downloading update' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
extractAppUpdate: function(updateFile, cb) {
|
||||||
|
var expectedFiles = this.UpdateCheckFiles;
|
||||||
|
var appPath = Launcher.getUserDataPath();
|
||||||
|
var StreamZip = Launcher.req('node-stream-zip');
|
||||||
|
var zip = new StreamZip({ file: updateFile, storeEntries: true });
|
||||||
|
zip.on('error', cb);
|
||||||
|
zip.on('ready', function() {
|
||||||
|
var containsAll = expectedFiles.every(function(expFile) {
|
||||||
|
var entry = zip.entry(expFile);
|
||||||
|
return entry && entry.isFile;
|
||||||
|
});
|
||||||
|
if (!containsAll) {
|
||||||
|
return cb('Bad archive');
|
||||||
|
}
|
||||||
|
zip.extract(null, appPath, function(err) {
|
||||||
|
zip.close();
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
Launcher.req('fs').unlink(updateFile);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
checkAppCacheUpdateReady: function() {
|
||||||
|
if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
|
||||||
|
try { window.applicationCache.swapCache(); } catch (e) { }
|
||||||
|
UpdateModel.instance.set('updateStatus', 'ready');
|
||||||
|
Backbone.trigger('update-app');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Updater;
|
|
@ -4,7 +4,9 @@ var Links = {
|
||||||
Repo: 'https://github.com/antelle/keeweb',
|
Repo: 'https://github.com/antelle/keeweb',
|
||||||
Desktop: 'https://github.com/antelle/keeweb/releases/latest',
|
Desktop: 'https://github.com/antelle/keeweb/releases/latest',
|
||||||
WebApp: 'https://antelle.github.io/keeweb/',
|
WebApp: 'https://antelle.github.io/keeweb/',
|
||||||
License: 'https://github.com/antelle/keeweb/blob/master/MIT-LICENSE.txt'
|
License: 'https://github.com/antelle/keeweb/blob/master/MIT-LICENSE.txt',
|
||||||
|
UpdateDesktop: 'https://github.com/antelle/keeweb/releases/download/{ver}/UpdateDesktop.zip',
|
||||||
|
ReleaseNotes: 'https://github.com/antelle/keeweb/blob/master/release-notes.md#release-notes'
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Links;
|
module.exports = Links;
|
||||||
|
|
|
@ -10,7 +10,8 @@ var AppSettingsModel = Backbone.Model.extend({
|
||||||
theme: 'd',
|
theme: 'd',
|
||||||
expandGroups: true,
|
expandGroups: true,
|
||||||
listViewWidth: null,
|
listViewWidth: null,
|
||||||
menuViewWidth: null
|
menuViewWidth: null,
|
||||||
|
autoUpdate: true
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
|
@ -21,7 +22,10 @@ var AppSettingsModel = Backbone.Model.extend({
|
||||||
try {
|
try {
|
||||||
var data;
|
var data;
|
||||||
if (Launcher) {
|
if (Launcher) {
|
||||||
data = JSON.parse(Launcher.readFile(Launcher.getUserDataPath(FileName), 'utf8'));
|
var settingsFile = Launcher.getUserDataPath(FileName);
|
||||||
|
if (Launcher.fileExists(settingsFile)) {
|
||||||
|
data = JSON.parse(Launcher.readFile(settingsFile, 'utf8'));
|
||||||
|
}
|
||||||
} else if (typeof localStorage !== 'undefined' && localStorage.appSettings) {
|
} else if (typeof localStorage !== 'undefined' && localStorage.appSettings) {
|
||||||
data = JSON.parse(localStorage.appSettings);
|
data = JSON.parse(localStorage.appSettings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
|
||||||
|
var UpdateModel = Backbone.Model.extend({
|
||||||
|
defaults: {
|
||||||
|
lastSuccessCheckDate: null,
|
||||||
|
lastCheckDate: null,
|
||||||
|
lastVersion: null,
|
||||||
|
lastVersionReleaseDate: null,
|
||||||
|
lastCheckError: null,
|
||||||
|
status: null,
|
||||||
|
updateStatus: null,
|
||||||
|
updateError: null,
|
||||||
|
updateManual: false
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
if (localStorage.updateInfo) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(localStorage.updateInfo);
|
||||||
|
_.each(data, function(val, key) {
|
||||||
|
if (/Date$/.test(key)) {
|
||||||
|
data[key] = val ? new Date(val) : null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.set(data, { silent: true });
|
||||||
|
} catch (e) { /* failed to load model */ }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
save: function() {
|
||||||
|
var attr = _.clone(this.attributes);
|
||||||
|
Object.keys(attr).forEach(function(key) {
|
||||||
|
if (key.lastIndexOf('update', 0) === 0) {
|
||||||
|
delete attr[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
localStorage.updateInfo = JSON.stringify(attr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
UpdateModel.instance = new UpdateModel();
|
||||||
|
UpdateModel.instance.load();
|
||||||
|
|
||||||
|
module.exports = UpdateModel;
|
|
@ -58,6 +58,7 @@ var AppView = Backbone.View.extend({
|
||||||
this.listenTo(Backbone, 'toggle-details', this.toggleDetails);
|
this.listenTo(Backbone, 'toggle-details', this.toggleDetails);
|
||||||
this.listenTo(Backbone, 'edit-group', this.editGroup);
|
this.listenTo(Backbone, 'edit-group', this.editGroup);
|
||||||
this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile);
|
this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile);
|
||||||
|
this.listenTo(Backbone, 'update-app', this.updateApp);
|
||||||
|
|
||||||
window.onbeforeunload = this.beforeUnload.bind(this);
|
window.onbeforeunload = this.beforeUnload.bind(this);
|
||||||
window.onresize = this.windowResize.bind(this);
|
window.onresize = this.windowResize.bind(this);
|
||||||
|
@ -103,6 +104,12 @@ var AppView = Backbone.View.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateApp: function() {
|
||||||
|
if (!Launcher && !this.model.files.hasOpenFiles()) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
showEntries: function() {
|
showEntries: function() {
|
||||||
this.views.menu.show();
|
this.views.menu.show();
|
||||||
this.views.menuDrag.show();
|
this.views.menuDrag.show();
|
||||||
|
@ -189,16 +196,25 @@ var AppView = Backbone.View.extend({
|
||||||
beforeUnload: function(e) {
|
beforeUnload: function(e) {
|
||||||
if (this.model.files.hasUnsavedFiles()) {
|
if (this.model.files.hasUnsavedFiles()) {
|
||||||
if (Launcher && !Launcher.exitRequested) {
|
if (Launcher && !Launcher.exitRequested) {
|
||||||
Alerts.yesno({
|
if (!this.exitAlertShown) {
|
||||||
header: 'Unsaved changes!',
|
var that = this;
|
||||||
body: 'You have unsaved files, all changes will be lost.',
|
that.exitAlertShown = true;
|
||||||
buttons: [{result: 'yes', title: 'Exit and discard unsaved changes'}, {result: '', title: 'Don\'t exit'}],
|
Alerts.yesno({
|
||||||
success: function() {
|
header: 'Unsaved changes!',
|
||||||
Launcher.exit();
|
body: 'You have unsaved files, all changes will be lost.',
|
||||||
}
|
buttons: [{result: 'yes', title: 'Exit and discard unsaved changes'}, {result: '', title: 'Don\'t exit'}],
|
||||||
});
|
success: function () {
|
||||||
e.returnValue = false;
|
Launcher.exit();
|
||||||
return false;
|
},
|
||||||
|
cancel: function() {
|
||||||
|
Launcher.cancelRestart(false);
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
that.exitAlertShown = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Launcher.preventExit(e);
|
||||||
}
|
}
|
||||||
return 'You have unsaved files, all changes will be lost.';
|
return 'You have unsaved files, all changes will be lost.';
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
var Backbone = require('backbone'),
|
var Backbone = require('backbone'),
|
||||||
Keys = require('../const/keys'),
|
Keys = require('../const/keys'),
|
||||||
KeyHandler = require('../comp/key-handler'),
|
KeyHandler = require('../comp/key-handler'),
|
||||||
GeneratorView = require('./generator-view');
|
GeneratorView = require('./generator-view'),
|
||||||
|
UpdateModel = require('../models/update-model');
|
||||||
|
|
||||||
var FooterView = Backbone.View.extend({
|
var FooterView = Backbone.View.extend({
|
||||||
template: require('templates/footer.html'),
|
template: require('templates/footer.html'),
|
||||||
|
@ -28,10 +29,15 @@ var FooterView = Backbone.View.extend({
|
||||||
KeyHandler.onKey(Keys.DOM_VK_COMMA, this.toggleSettings, this, KeyHandler.SHORTCUT_ACTION);
|
KeyHandler.onKey(Keys.DOM_VK_COMMA, this.toggleSettings, this, KeyHandler.SHORTCUT_ACTION);
|
||||||
|
|
||||||
this.listenTo(this.model.files, 'update reset change', this.render);
|
this.listenTo(this.model.files, 'update reset change', this.render);
|
||||||
|
this.listenTo(Backbone, 'update-app', this.render);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
this.$el.html(this.template(this.model));
|
this.listenTo(Backbone, 'update-app', this.updateApp);
|
||||||
|
this.$el.html(this.template({
|
||||||
|
files: this.model.files,
|
||||||
|
updateAvailable: UpdateModel.instance.get('updateStatus') === 'ready'
|
||||||
|
}));
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,12 @@
|
||||||
|
|
||||||
var Backbone = require('backbone'),
|
var Backbone = require('backbone'),
|
||||||
Launcher = require('../../comp/launcher'),
|
Launcher = require('../../comp/launcher'),
|
||||||
AppSettingsModel = require('../../models/app-settings-model');
|
Updater = require('../../comp/updater'),
|
||||||
|
Format = require('../../util/format'),
|
||||||
|
AppSettingsModel = require('../../models/app-settings-model'),
|
||||||
|
UpdateModel = require('../../models/update-model'),
|
||||||
|
RuntimeInfo = require('../../comp/runtime-info'),
|
||||||
|
Links = require('../../const/links');
|
||||||
|
|
||||||
var SettingsGeneralView = Backbone.View.extend({
|
var SettingsGeneralView = Backbone.View.extend({
|
||||||
template: require('templates/settings/settings-general.html'),
|
template: require('templates/settings/settings-general.html'),
|
||||||
|
@ -10,6 +15,10 @@ var SettingsGeneralView = Backbone.View.extend({
|
||||||
events: {
|
events: {
|
||||||
'change .settings__general-theme': 'changeTheme',
|
'change .settings__general-theme': 'changeTheme',
|
||||||
'change .settings__general-expand': 'changeExpandGroups',
|
'change .settings__general-expand': 'changeExpandGroups',
|
||||||
|
'change .settings__general-auto-update': 'changeAutoUpdate',
|
||||||
|
'click .settings__general-update-btn': 'checkUpdate',
|
||||||
|
'click .settings__general-restart-btn': 'restartApp',
|
||||||
|
'click .settings__general-download-update-btn': 'downloadUpdate',
|
||||||
'click .settings__general-dev-tools-link': 'openDevTools'
|
'click .settings__general-dev-tools-link': 'openDevTools'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -19,20 +28,92 @@ var SettingsGeneralView = Backbone.View.extend({
|
||||||
wh: 'white'
|
wh: 'white'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
this.listenTo(UpdateModel.instance, 'change:status', this.render, this);
|
||||||
|
this.listenTo(UpdateModel.instance, 'change:updateStatus', this.render, this);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
this.renderTemplate({
|
this.renderTemplate({
|
||||||
themes: this.allThemes,
|
themes: this.allThemes,
|
||||||
activeTheme: AppSettingsModel.instance.get('theme'),
|
activeTheme: AppSettingsModel.instance.get('theme'),
|
||||||
expandGroups: AppSettingsModel.instance.get('expandGroups'),
|
expandGroups: AppSettingsModel.instance.get('expandGroups'),
|
||||||
devTools: Launcher && Launcher.devTools
|
devTools: Launcher && Launcher.devTools,
|
||||||
|
canAutoUpdate: !!Launcher,
|
||||||
|
autoUpdate: Updater.enabledAutoUpdate(),
|
||||||
|
updateInProgress: Updater.updateInProgress(),
|
||||||
|
updateInfo: this.getUpdateInfo(),
|
||||||
|
updateReady: UpdateModel.instance.get('updateStatus') === 'ready',
|
||||||
|
updateManual: UpdateModel.instance.get('updateManual'),
|
||||||
|
releaseNotesLink: Links.ReleaseNotes
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUpdateInfo: function() {
|
||||||
|
switch (UpdateModel.instance.get('status')) {
|
||||||
|
case 'checking':
|
||||||
|
return 'Checking for updates...';
|
||||||
|
case 'error':
|
||||||
|
var errMsg = 'Error checking for updates';
|
||||||
|
if (UpdateModel.instance.get('lastError')) {
|
||||||
|
errMsg += ': ' + UpdateModel.instance.get('lastError');
|
||||||
|
}
|
||||||
|
if (UpdateModel.instance.get('lastSuccessCheckDate')) {
|
||||||
|
errMsg += '. Last successful check was at ' + Format.dtStr(UpdateModel.instance.get('lastSuccessCheckDate')) +
|
||||||
|
': the latest version was ' + UpdateModel.instance.get('lastVersion');
|
||||||
|
}
|
||||||
|
return errMsg;
|
||||||
|
case 'ok':
|
||||||
|
var msg = 'Checked at ' + Format.dtStr(UpdateModel.instance.get('lastCheckDate')) + ': ';
|
||||||
|
if (RuntimeInfo.version === UpdateModel.instance.get('lastVersion')) {
|
||||||
|
msg += 'you are using the latest version';
|
||||||
|
} else {
|
||||||
|
msg += 'new version ' + UpdateModel.instance.get('lastVersion') + ' available, released at ' +
|
||||||
|
Format.dStr(UpdateModel.instance.get('lastVersionReleaseDate'));
|
||||||
|
}
|
||||||
|
switch (UpdateModel.instance.get('updateStatus')) {
|
||||||
|
case 'downloading':
|
||||||
|
return msg + '. Downloading update...';
|
||||||
|
case 'extracting':
|
||||||
|
return msg + '. Extracting update...';
|
||||||
|
case 'error':
|
||||||
|
return msg + '. There was an error downloading new version';
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
default:
|
||||||
|
return 'Never checked for updates';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
changeTheme: function(e) {
|
changeTheme: function(e) {
|
||||||
var theme = e.target.value;
|
var theme = e.target.value;
|
||||||
AppSettingsModel.instance.set('theme', theme);
|
AppSettingsModel.instance.set('theme', theme);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
changeAutoUpdate: function(e) {
|
||||||
|
var autoUpdate = e.target.checked;
|
||||||
|
AppSettingsModel.instance.set('autoUpdate', autoUpdate);
|
||||||
|
if (autoUpdate) {
|
||||||
|
Updater.scheduleNextCheck();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
checkUpdate: function() {
|
||||||
|
Updater.check(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
restartApp: function() {
|
||||||
|
if (Launcher) {
|
||||||
|
Launcher.requestRestart();
|
||||||
|
} else {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadUpdate: function() {
|
||||||
|
Launcher.openLink(Links.Desktop);
|
||||||
|
},
|
||||||
|
|
||||||
changeExpandGroups: function(e) {
|
changeExpandGroups: function(e) {
|
||||||
var expand = e.target.checked;
|
var expand = e.target.checked;
|
||||||
AppSettingsModel.instance.set('expandGroups', expand);
|
AppSettingsModel.instance.set('expandGroups', expand);
|
||||||
|
|
|
@ -8,7 +8,7 @@ var SettingsHelpView = Backbone.View.extend({
|
||||||
template: require('templates/settings/settings-help.html'),
|
template: require('templates/settings/settings-help.html'),
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var appInfo = 'KeeWeb v' + RuntimeInfo.version + ' (built at ' + RuntimeInfo.buildDate + ')\n' +
|
var appInfo = 'KeeWeb v' + RuntimeInfo.version + ' (' + RuntimeInfo.commit + ', ' + RuntimeInfo.buildDate + ')\n' +
|
||||||
'Environment: ' + (RuntimeInfo.launcher ? RuntimeInfo.launcher : 'web') + '\n' +
|
'Environment: ' + (RuntimeInfo.launcher ? RuntimeInfo.launcher : 'web') + '\n' +
|
||||||
'User-Agent: ' + RuntimeInfo.userAgent;
|
'User-Agent: ' + RuntimeInfo.userAgent;
|
||||||
this.renderTemplate({
|
this.renderTemplate({
|
||||||
|
|
|
@ -41,4 +41,9 @@
|
||||||
padding: $base-padding;
|
padding: $base-padding;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__update-icon {
|
||||||
|
@include th { color: action-color(); }
|
||||||
|
@include animation(shake 50s cubic-bezier(.36,.07,.19,.97) 0s infinite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,4 +71,12 @@
|
||||||
float: right;
|
float: right;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__general-update-buttons {
|
||||||
|
margin-top: $base-spacing;
|
||||||
|
}
|
||||||
|
&__general-update-btn {
|
||||||
|
width: 15em;
|
||||||
|
margin-right: $small-spacing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,5 @@ $all-colors: (
|
||||||
@each $col, $val in $all-colors {
|
@each $col, $val in $all-colors {
|
||||||
.#{$col}-color { color: #{$val}; }
|
.#{$col}-color { color: #{$val}; }
|
||||||
}
|
}
|
||||||
.muted-color {
|
.muted-color { @include th { color: muted-color(); }; }
|
||||||
@include th { color: muted-color(); };
|
.action-color { @include th { color: action-color(); }; }
|
||||||
}
|
|
||||||
|
|
|
@ -25,3 +25,11 @@
|
||||||
@include transform(rotateY(360deg));
|
@include transform(rotateY(360deg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include keyframes(shake) {
|
||||||
|
0%, 1%, 100% { @include transform(translate3d(0, 0, 0)); }
|
||||||
|
.1%, .9% { @include transform(translate3d(-1px, 0, 0)); }
|
||||||
|
.2%, .8% { @include transform(translate3d(2px, 0, 0)); }
|
||||||
|
.3%, .5%, .7% { @include transform(translate3d(-3px, 0, 0)); }
|
||||||
|
.4%, .6% { @include transform(translate3d(3px, 0, 0)); }
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,13 @@
|
||||||
<div class="footer__db footer__db--dimmed footer__db--expanded footer__db-open"><i class="fa fa-plus"></i> Open / New</div>
|
<div class="footer__db footer__db--dimmed footer__db--expanded footer__db-open"><i class="fa fa-plus"></i> Open / New</div>
|
||||||
<!--<div class="footer__btn footer__btn-view"><i class="fa fa-list-ul"></i></div>-->
|
<!--<div class="footer__btn footer__btn-view"><i class="fa fa-list-ul"></i></div>-->
|
||||||
<div class="footer__btn footer__btn-help"><i class="fa fa-question"></i></div>
|
<div class="footer__btn footer__btn-help"><i class="fa fa-question"></i></div>
|
||||||
<div class="footer__btn footer__btn-settings"><i class="fa fa-cog"></i></div>
|
<div class="footer__btn footer__btn-settings">
|
||||||
|
<% if (updateAvailable) { %>
|
||||||
|
<i class="fa fa-bell footer__update-icon"></i>
|
||||||
|
<% } else { %>
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
<div class="footer__btn footer__btn-generate"><i class="fa fa-bolt"></i></div>
|
<div class="footer__btn footer__btn-generate"><i class="fa fa-bolt"></i></div>
|
||||||
<div class="footer__btn footer__btn-lock"><i class="fa fa-lock"></i></div>
|
<div class="footer__btn footer__btn-lock"><i class="fa fa-lock"></i></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
<div>
|
<div>
|
||||||
<h1><i class="fa fa-cog"></i> General Settings</h1>
|
<h1><i class="fa fa-cog"></i> General Settings</h1>
|
||||||
|
<% if (updateReady && !canAutoUpdate) { %>
|
||||||
|
<h2 class="action-color">Update</h2>
|
||||||
|
<div>New app version was released and downloaded. <a href="<%= releaseNotesLink %>" target="_blank">View release notes</a></div>
|
||||||
|
<div class="settings__general-update-buttons">
|
||||||
|
<button class="settings__general-restart-btn">Reload to update</button>
|
||||||
|
</div>
|
||||||
|
<% } else if (updateManual) { %>
|
||||||
|
<h2 class="action-color">Update</h2>
|
||||||
|
<div>New version has been released. It will check for updates and install them automatically
|
||||||
|
but auto-upgrading from your version is impossible.</div>
|
||||||
|
<div class="settings__general-update-buttons">
|
||||||
|
<button class="settings__general-download-update-btn">Download update</button>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
<h2>Appearance</h2>
|
<h2>Appearance</h2>
|
||||||
<div>
|
<div>
|
||||||
<label for="settings__general-theme">Theme:</label>
|
<label for="settings__general-theme">Theme:</label>
|
||||||
|
@ -13,6 +27,25 @@
|
||||||
<input type="checkbox" class="settings__input input-base settings__general-expand" id="settings__general-expand" <%- expandGroups ? 'checked' : '' %> />
|
<input type="checkbox" class="settings__input input-base settings__general-expand" id="settings__general-expand" <%- expandGroups ? 'checked' : '' %> />
|
||||||
<label for="settings__general-expand">Show entries from all subgroups</label>
|
<label for="settings__general-expand">Show entries from all subgroups</label>
|
||||||
</div>
|
</div>
|
||||||
|
<% if (canAutoUpdate && !updateManual) { %>
|
||||||
|
<h2>Function</h2>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" class="settings__input settings__general-auto-update" id="settings__general-auto-update" <%- autoUpdate ? 'checked' : '' %> />
|
||||||
|
<label for="settings__general-auto-update">Automatic updates</label>
|
||||||
|
<div><%- updateInfo %></div>
|
||||||
|
<a href="<%= releaseNotesLink %>" target="_blank">View release notes</a>
|
||||||
|
</div>
|
||||||
|
<div class="settings__general-update-buttons">
|
||||||
|
<% if (updateInProgress) { %>
|
||||||
|
<button class="settings__general-update-btn btn-silent" disabled>Checking for updates</button>
|
||||||
|
<% } else { %>
|
||||||
|
<button class="settings__general-update-btn btn-silent">Check for updates</button>
|
||||||
|
<% } %>
|
||||||
|
<% if (updateReady) { %>
|
||||||
|
<button class="settings__general-restart-btn">Restart to update</button>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
<% if (devTools) { %>
|
<% if (devTools) { %>
|
||||||
<h2>Advanced</h2>
|
<h2>Advanced</h2>
|
||||||
<a class="settings__general-dev-tools-link">Show dev tools</a>
|
<a class="settings__general-dev-tools-link">Show dev tools</a>
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* jshint node:true */
|
||||||
|
/* jshint browser:false */
|
||||||
|
|
||||||
|
var app = require('app'),
|
||||||
|
BrowserWindow = require('browser-window'),
|
||||||
|
path = require('path'),
|
||||||
|
Menu = require('menu');
|
||||||
|
|
||||||
|
var mainWindow = null,
|
||||||
|
openFile = process.argv.filter(function(arg) { return /\.kdbx$/i.test(arg); })[0],
|
||||||
|
ready = false,
|
||||||
|
restartPending = false,
|
||||||
|
htmlPath = path.join(__dirname, 'index.html');
|
||||||
|
|
||||||
|
process.argv.forEach(function(arg) {
|
||||||
|
if (arg.lastIndexOf('--htmlpath=', 0) === 0) {
|
||||||
|
htmlPath = path.resolve(arg.replace('--htmlpath=', ''), 'index.html');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-closed', function() {
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
app.on('ready', function() {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
width: 1000, height: 700, 'min-width': 600, 'min-height': 300,
|
||||||
|
icon: path.join(__dirname, 'icon.png')
|
||||||
|
});
|
||||||
|
setMenu();
|
||||||
|
mainWindow.loadUrl('file://' + htmlPath);
|
||||||
|
mainWindow.webContents.on('dom-ready', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
mainWindow.show();
|
||||||
|
ready = true;
|
||||||
|
notifyOpenFile();
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
mainWindow.on('closed', function() {
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.on('open-file', function(e, path) {
|
||||||
|
e.preventDefault();
|
||||||
|
openFile = path;
|
||||||
|
notifyOpenFile();
|
||||||
|
});
|
||||||
|
app.on('quit', function() {
|
||||||
|
if (restartPending) {
|
||||||
|
require('child_process').exec(process.execPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.quitAndRestart = function() {
|
||||||
|
restartPending = true;
|
||||||
|
app.quit();
|
||||||
|
setTimeout(function() { restartPending = false; }, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
function setMenu() {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
var name = require('app').getName();
|
||||||
|
var template = [
|
||||||
|
{
|
||||||
|
label: name,
|
||||||
|
submenu: [
|
||||||
|
{ label: 'About ' + name, role: 'about' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Services', role: 'services', submenu: [] },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Hide ' + name, accelerator: 'Command+H', role: 'hide' },
|
||||||
|
{ label: 'Hide Others', accelerator: 'Command+Shift+H', role: 'hideothers' },
|
||||||
|
{ label: 'Show All', role: 'unhide' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Quit', accelerator: 'Command+Q', click: function() { app.quit(); } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
||||||
|
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
||||||
|
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
||||||
|
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
||||||
|
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectall' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var menu = Menu.buildFromTemplate(template);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyOpenFile() {
|
||||||
|
if (ready && openFile && mainWindow) {
|
||||||
|
openFile = openFile.replace(/"/g, '\\"').replace(/\\/g, '\\\\');
|
||||||
|
mainWindow.webContents.executeJavaScript('if (window.launcherOpen) { window.launcherOpen("' + openFile + '"); } ' +
|
||||||
|
' else { window.launcherOpenedFile="' + openFile + '"; }');
|
||||||
|
openFile = null;
|
||||||
|
}
|
||||||
|
}
|
104
electron/main.js
104
electron/main.js
|
@ -1,87 +1,43 @@
|
||||||
|
// KeeWeb launcher script
|
||||||
|
|
||||||
|
// This script is distributed with the app and is its entry point
|
||||||
|
// It checks whether the app is available in userData folder and if its version is higher than local, launches it
|
||||||
|
// This script is the only part which will be updated only with the app itself, auto-update will not change it
|
||||||
|
|
||||||
|
// (C) Antelle 2015, MIT license https://github.com/antelle/keeweb
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* jshint node:true */
|
/* jshint node:true */
|
||||||
/* jshint browser:false */
|
/* jshint browser:false */
|
||||||
|
|
||||||
var app = require('app'),
|
var app = require('app'),
|
||||||
BrowserWindow = require('browser-window'),
|
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
fs = require('fs'),
|
fs = require('fs');
|
||||||
Menu = require('menu');
|
|
||||||
|
|
||||||
var mainWindow = null,
|
var userDataDir = app.getPath('userData'),
|
||||||
openFile = process.argv.filter(function(arg) { return /\.kdbx$/i.test(arg); })[0],
|
appPathUserData = path.join(userDataDir, 'app.js'),
|
||||||
ready = false;
|
appPath = path.join(__dirname, 'app.js');
|
||||||
|
|
||||||
app.on('window-all-closed', function() { app.quit(); });
|
if (fs.existsSync(appPathUserData)) {
|
||||||
app.on('ready', function() {
|
var versionLocal = require('./package.json').version;
|
||||||
var htmlPath = path.join(app.getPath('userData'), 'index.html');
|
try {
|
||||||
|
var versionUserData = require(path.join(userDataDir, 'package.json')).version;
|
||||||
mainWindow = new BrowserWindow({
|
versionLocal = versionLocal.split('.');
|
||||||
show: false,
|
versionUserData = versionUserData.split('.');
|
||||||
width: 1000, height: 700, 'min-width': 600, 'min-height': 300,
|
for (var i = 0; i < versionLocal.length; i++) {
|
||||||
icon: path.join(__dirname, 'icon.png')
|
if (+versionUserData[i] > +versionLocal[i]) {
|
||||||
});
|
appPath = appPathUserData;
|
||||||
setMenu();
|
break;
|
||||||
if (fs.existsSync(htmlPath)) {
|
|
||||||
mainWindow.loadUrl('file://' + htmlPath);
|
|
||||||
} else {
|
|
||||||
mainWindow.loadUrl('https://antelle.github.io/keeweb/index.html');
|
|
||||||
}
|
|
||||||
mainWindow.webContents.on('dom-ready', function() {
|
|
||||||
mainWindow.show();
|
|
||||||
ready = true;
|
|
||||||
notifyOpenFile();
|
|
||||||
});
|
|
||||||
mainWindow.on('closed', function() { mainWindow = null; });
|
|
||||||
});
|
|
||||||
app.on('open-file', function(e, path) {
|
|
||||||
e.preventDefault();
|
|
||||||
openFile = path;
|
|
||||||
notifyOpenFile();
|
|
||||||
});
|
|
||||||
|
|
||||||
function setMenu() {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
var name = require('app').getName();
|
|
||||||
var template = [
|
|
||||||
{
|
|
||||||
label: name,
|
|
||||||
submenu: [
|
|
||||||
{ label: 'About ' + name, role: 'about' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Services', role: 'services', submenu: [] },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Hide ' + name, accelerator: 'Command+H', role: 'hide' },
|
|
||||||
{ label: 'Hide Others', accelerator: 'Command+Shift+H', role: 'hideothers' },
|
|
||||||
{ label: 'Show All', role: 'unhide' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Quit', accelerator: 'Command+Q', click: function() { app.quit(); } }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Edit',
|
|
||||||
submenu: [
|
|
||||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
|
||||||
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
|
||||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
|
||||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
|
||||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectall' }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
];
|
if (+versionUserData[i] < +versionLocal[i]) {
|
||||||
var menu = Menu.buildFromTemplate(template);
|
break;
|
||||||
Menu.setApplicationMenu(menu);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('Error reading user file version', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyOpenFile() {
|
require(appPath);
|
||||||
if (ready && openFile && mainWindow) {
|
|
||||||
openFile = openFile.replace(/"/g, '\\"').replace(/\\/g, '\\\\');
|
|
||||||
mainWindow.webContents.executeJavaScript('if (window.launcherOpen) { window.launcherOpen("' + openFile + '"); } ' +
|
|
||||||
' else { window.launcherOpenedFile="' + openFile + '"; }');
|
|
||||||
openFile = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "KeeWeb",
|
"name": "KeeWeb",
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"main": "main.js"
|
"description": "KeePass web app",
|
||||||
|
"main": "main.js",
|
||||||
|
"repository": "https://github.com/antelle/keeweb",
|
||||||
|
"author": "Antelle",
|
||||||
|
"license": "MIT",
|
||||||
|
"readme": "../README.md",
|
||||||
|
"dependencies": {
|
||||||
|
"node-stream-zip": "^1.2.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function (grunt) {
|
||||||
|
grunt.registerMultiTask('validate-desktop-update', 'Validates desktop update package', function () {
|
||||||
|
var path = require('path');
|
||||||
|
var done = this.async();
|
||||||
|
var StreamZip = require(path.resolve(__dirname, '../../electron/node_modules/node-stream-zip'));
|
||||||
|
var zip = new StreamZip({ file: this.options().file, storeEntries: true });
|
||||||
|
var expFiles = this.options().expected;
|
||||||
|
zip.on('error', function(err) {
|
||||||
|
grunt.warn(err);
|
||||||
|
});
|
||||||
|
zip.on('ready', function() {
|
||||||
|
var valid = true;
|
||||||
|
expFiles.forEach(function(entry) {
|
||||||
|
try {
|
||||||
|
if (!zip.entryDataSync(entry)) {
|
||||||
|
grunt.warn('Corrupted entry in desktop update archive: ' + entry);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
grunt.warn('Entry not found in desktop update archive: ' + entry);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (valid) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -32,12 +32,14 @@
|
||||||
"time-grunt": "^1.2.1",
|
"time-grunt": "^1.2.1",
|
||||||
"uglify-loader": "^1.2.0",
|
"uglify-loader": "^1.2.0",
|
||||||
"webpack": "^1.11.0",
|
"webpack": "^1.11.0",
|
||||||
"webpack-dev-server": "^1.10.1"
|
"webpack-dev-server": "^1.12.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "grunt",
|
"start": "grunt",
|
||||||
"test": "grunt test"
|
"test": "grunt test",
|
||||||
|
"postinstall": "npm install --prefix electron"
|
||||||
},
|
},
|
||||||
"author": "Antelle",
|
"author": "Antelle",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"readme": "README.md"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue