mirror of https://github.com/keeweb/keeweb.git
commit
e4c9f1d7c9
55
Gruntfile.js
55
Gruntfile.js
|
@ -3,6 +3,9 @@
|
|||
var fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
/* jshint node:true */
|
||||
/* jshint browser:false */
|
||||
|
||||
var StringReplacePlugin = require('string-replace-webpack-plugin');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
@ -55,8 +58,8 @@ module.exports = function(grunt) {
|
|||
},
|
||||
clean: {
|
||||
dist: ['dist', 'tmp'],
|
||||
desktop_dist: ['dist/desktop'],
|
||||
desktop_tmp: ['tmp/desktop']
|
||||
'desktop_dist': ['dist/desktop'],
|
||||
'desktop_tmp': ['tmp/desktop']
|
||||
},
|
||||
copy: {
|
||||
html: {
|
||||
|
@ -76,6 +79,13 @@ module.exports = function(grunt) {
|
|||
expand: true,
|
||||
flatten: true
|
||||
},
|
||||
'desktop_app_content': {
|
||||
cwd: 'electron/',
|
||||
src: '**',
|
||||
dest: 'tmp/desktop/app/',
|
||||
expand: true,
|
||||
nonull: true
|
||||
},
|
||||
'desktop_osx': {
|
||||
src: 'tmp/desktop/KeeWeb.dmg',
|
||||
dest: 'dist/desktop/KeeWeb.mac.dmg',
|
||||
|
@ -141,12 +151,16 @@ module.exports = function(grunt) {
|
|||
'string-replace': {
|
||||
manifest: {
|
||||
options: {
|
||||
replacements: [{
|
||||
pattern: '# YYYY-MM-DD:v0.0.0',
|
||||
replacement: '# ' + dt + ':v' + pkg.version
|
||||
}]
|
||||
replacements: [
|
||||
{ pattern: '# YYYY-MM-DD:v0.0.0', replacement: '# ' + dt + ':v' + pkg.version },
|
||||
{ pattern: 'vElectron', replacement: electronVersion }
|
||||
]
|
||||
},
|
||||
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: {
|
||||
|
@ -190,7 +204,8 @@ module.exports = function(grunt) {
|
|||
}]})},
|
||||
{ test: /runtime\-info\.js$/, loader: StringReplacePlugin.replace({ replacements: [
|
||||
{ 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: /baron(\.min)?\.js$/, loader: 'exports?baron; delete window.baron;' },
|
||||
|
@ -251,7 +266,7 @@ module.exports = function(grunt) {
|
|||
electron: {
|
||||
options: {
|
||||
name: 'KeeWeb',
|
||||
dir: 'electron',
|
||||
dir: 'tmp/desktop/app',
|
||||
out: 'tmp/desktop',
|
||||
version: electronVersion,
|
||||
overwrite: true,
|
||||
|
@ -326,15 +341,26 @@ module.exports = function(grunt) {
|
|||
},
|
||||
compress: {
|
||||
linux: {
|
||||
options: {
|
||||
archive: 'tmp/desktop/KeeWeb.linux.x64.zip'
|
||||
},
|
||||
options: { archive: 'tmp/desktop/KeeWeb.linux.x64.zip' },
|
||||
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', [
|
||||
'gitinfo',
|
||||
'bower-install-simple',
|
||||
'clean',
|
||||
'jshint',
|
||||
|
@ -347,13 +373,18 @@ module.exports = function(grunt) {
|
|||
'postcss',
|
||||
'inline',
|
||||
'htmlmin',
|
||||
'string-replace'
|
||||
'string-replace:manifest'
|
||||
]);
|
||||
|
||||
grunt.registerTask('desktop', [
|
||||
'default',
|
||||
'gitinfo',
|
||||
'clean:desktop_tmp',
|
||||
'clean:desktop_dist',
|
||||
'copy:desktop_app_content',
|
||||
'string-replace:desktop_html',
|
||||
'compress:desktop_update',
|
||||
'validate-desktop-update',
|
||||
'electron',
|
||||
'electron_builder',
|
||||
'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:
|
||||
|
||||
- auto-update is not implemented
|
||||
- dropbox sync is one-way: changes are not loaded from dropbox, only saved
|
||||
|
||||
# Self-hosting
|
||||
|
@ -34,10 +33,20 @@ You can download the latest distribution files from [gh-pages](https://github.co
|
|||
|
||||
# Building
|
||||
|
||||
The app can be built with grunt: `grunt` (html file will be in `dist/`) or `grunt watch` (result will be in `tmp/`).
|
||||
Electron app is built with `grunt electron` (works only under 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/`.
|
||||
For debug build: 1. run `grunt`, 2. run `grunt watch`, 3. open `tmp/index.html`.
|
||||
The app can be built with grunt: `grunt` (html file will be in `dist/`).
|
||||
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 in this way:
|
||||
```bash
|
||||
$ cd electron
|
||||
$ electron . --htmlpath=../tmp
|
||||
```
|
||||
|
||||
For debug build:
|
||||
|
||||
1. run `grunt`
|
||||
2. run `grunt watch`
|
||||
3. open `tmp/index.html`
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
CACHE MANIFEST
|
||||
|
||||
# YYYY-MM-DD:v0.0.0
|
||||
# YYYY-MM-DD:v0.0.0 evElectron
|
||||
|
||||
CACHE:
|
||||
index.html
|
||||
|
|
|
@ -5,6 +5,7 @@ var AppModel = require('./models/app-model'),
|
|||
KeyHandler = require('./comp/key-handler'),
|
||||
Alerts = require('./comp/alerts'),
|
||||
DropboxLink = require('./comp/dropbox-link'),
|
||||
Updater = require('./comp/updater'),
|
||||
LastOpenFiles = require('./comp/last-open-files'),
|
||||
ThemeChanger = require('./util/theme-changer');
|
||||
|
||||
|
@ -47,6 +48,6 @@ $(function() {
|
|||
} else {
|
||||
appView.showOpenFile();
|
||||
}
|
||||
Updater.init();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ var Backbone = require('backbone');
|
|||
var Launcher;
|
||||
|
||||
if (window.process && window.process.versions && window.process.versions.electron) {
|
||||
/* jshint node:true */
|
||||
Launcher = {
|
||||
name: 'electron',
|
||||
version: window.process.versions.electron,
|
||||
|
@ -18,6 +19,9 @@ if (window.process && window.process.versions && window.process.versions.electro
|
|||
openDevTools: function() {
|
||||
this.req('remote').getCurrentWindow().openDevTools();
|
||||
},
|
||||
getAppVersion: function() {
|
||||
return this.remReq('app').getVersion();
|
||||
},
|
||||
getSaveFileName: function(defaultPath, cb) {
|
||||
if (defaultPath) {
|
||||
var homePath = this.remReq('app').getPath('userDesktop');
|
||||
|
@ -32,6 +36,9 @@ if (window.process && window.process.versions && window.process.versions.electro
|
|||
getUserDataPath: function(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) {
|
||||
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) {
|
||||
return this.req('fs').existsSync(path);
|
||||
},
|
||||
preventExit: function(e) {
|
||||
e.returnValue = false;
|
||||
return false;
|
||||
},
|
||||
exit: function() {
|
||||
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) {
|
||||
|
|
|
@ -5,6 +5,7 @@ var Launcher = require('../comp/launcher');
|
|||
var RuntimeInfo = {
|
||||
version: '@@VERSION',
|
||||
buildDate: '@@DATE',
|
||||
commit: '@@COMMIT',
|
||||
userAgent: navigator.userAgent,
|
||||
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',
|
||||
Desktop: 'https://github.com/antelle/keeweb/releases/latest',
|
||||
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;
|
||||
|
|
|
@ -10,7 +10,8 @@ var AppSettingsModel = Backbone.Model.extend({
|
|||
theme: 'd',
|
||||
expandGroups: true,
|
||||
listViewWidth: null,
|
||||
menuViewWidth: null
|
||||
menuViewWidth: null,
|
||||
autoUpdate: true
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
@ -21,7 +22,10 @@ var AppSettingsModel = Backbone.Model.extend({
|
|||
try {
|
||||
var data;
|
||||
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) {
|
||||
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, 'edit-group', this.editGroup);
|
||||
this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile);
|
||||
this.listenTo(Backbone, 'update-app', this.updateApp);
|
||||
|
||||
window.onbeforeunload = this.beforeUnload.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() {
|
||||
this.views.menu.show();
|
||||
this.views.menuDrag.show();
|
||||
|
@ -189,16 +196,25 @@ var AppView = Backbone.View.extend({
|
|||
beforeUnload: function(e) {
|
||||
if (this.model.files.hasUnsavedFiles()) {
|
||||
if (Launcher && !Launcher.exitRequested) {
|
||||
Alerts.yesno({
|
||||
header: 'Unsaved changes!',
|
||||
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() {
|
||||
Launcher.exit();
|
||||
}
|
||||
});
|
||||
e.returnValue = false;
|
||||
return false;
|
||||
if (!this.exitAlertShown) {
|
||||
var that = this;
|
||||
that.exitAlertShown = true;
|
||||
Alerts.yesno({
|
||||
header: 'Unsaved changes!',
|
||||
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 () {
|
||||
Launcher.exit();
|
||||
},
|
||||
cancel: function() {
|
||||
Launcher.cancelRestart(false);
|
||||
},
|
||||
complete: function () {
|
||||
that.exitAlertShown = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return Launcher.preventExit(e);
|
||||
}
|
||||
return 'You have unsaved files, all changes will be lost.';
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
var Backbone = require('backbone'),
|
||||
Keys = require('../const/keys'),
|
||||
KeyHandler = require('../comp/key-handler'),
|
||||
GeneratorView = require('./generator-view');
|
||||
GeneratorView = require('./generator-view'),
|
||||
UpdateModel = require('../models/update-model');
|
||||
|
||||
var FooterView = Backbone.View.extend({
|
||||
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);
|
||||
|
||||
this.listenTo(this.model.files, 'update reset change', this.render);
|
||||
this.listenTo(Backbone, 'update-app', this.render);
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
|
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
var Backbone = require('backbone'),
|
||||
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({
|
||||
template: require('templates/settings/settings-general.html'),
|
||||
|
@ -10,6 +15,10 @@ var SettingsGeneralView = Backbone.View.extend({
|
|||
events: {
|
||||
'change .settings__general-theme': 'changeTheme',
|
||||
'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'
|
||||
},
|
||||
|
||||
|
@ -19,20 +28,92 @@ var SettingsGeneralView = Backbone.View.extend({
|
|||
wh: 'white'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.listenTo(UpdateModel.instance, 'change:status', this.render, this);
|
||||
this.listenTo(UpdateModel.instance, 'change:updateStatus', this.render, this);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.renderTemplate({
|
||||
themes: this.allThemes,
|
||||
activeTheme: AppSettingsModel.instance.get('theme'),
|
||||
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) {
|
||||
var theme = e.target.value;
|
||||
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) {
|
||||
var expand = e.target.checked;
|
||||
AppSettingsModel.instance.set('expandGroups', expand);
|
||||
|
|
|
@ -8,7 +8,7 @@ var SettingsHelpView = Backbone.View.extend({
|
|||
template: require('templates/settings/settings-help.html'),
|
||||
|
||||
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' +
|
||||
'User-Agent: ' + RuntimeInfo.userAgent;
|
||||
this.renderTemplate({
|
||||
|
|
|
@ -41,4 +41,9 @@
|
|||
padding: $base-padding;
|
||||
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;
|
||||
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 {
|
||||
.#{$col}-color { color: #{$val}; }
|
||||
}
|
||||
.muted-color {
|
||||
@include th { color: muted-color(); };
|
||||
}
|
||||
.muted-color { @include th { color: muted-color(); }; }
|
||||
.action-color { @include th { color: action-color(); }; }
|
||||
|
|
|
@ -25,3 +25,11 @@
|
|||
@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__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-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-lock"><i class="fa fa-lock"></i></div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
<div>
|
||||
<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>
|
||||
<div>
|
||||
<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' : '' %> />
|
||||
<label for="settings__general-expand">Show entries from all subgroups</label>
|
||||
</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) { %>
|
||||
<h2>Advanced</h2>
|
||||
<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';
|
||||
|
||||
/* jshint node:true */
|
||||
/* jshint browser:false */
|
||||
|
||||
var app = require('app'),
|
||||
BrowserWindow = require('browser-window'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
Menu = require('menu');
|
||||
fs = require('fs');
|
||||
|
||||
var mainWindow = null,
|
||||
openFile = process.argv.filter(function(arg) { return /\.kdbx$/i.test(arg); })[0],
|
||||
ready = false;
|
||||
var userDataDir = app.getPath('userData'),
|
||||
appPathUserData = path.join(userDataDir, 'app.js'),
|
||||
appPath = path.join(__dirname, 'app.js');
|
||||
|
||||
app.on('window-all-closed', function() { app.quit(); });
|
||||
app.on('ready', function() {
|
||||
var htmlPath = path.join(app.getPath('userData'), 'index.html');
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
show: false,
|
||||
width: 1000, height: 700, 'min-width': 600, 'min-height': 300,
|
||||
icon: path.join(__dirname, 'icon.png')
|
||||
});
|
||||
setMenu();
|
||||
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 (fs.existsSync(appPathUserData)) {
|
||||
var versionLocal = require('./package.json').version;
|
||||
try {
|
||||
var versionUserData = require(path.join(userDataDir, 'package.json')).version;
|
||||
versionLocal = versionLocal.split('.');
|
||||
versionUserData = versionUserData.split('.');
|
||||
for (var i = 0; i < versionLocal.length; i++) {
|
||||
if (+versionUserData[i] > +versionLocal[i]) {
|
||||
appPath = appPathUserData;
|
||||
break;
|
||||
}
|
||||
];
|
||||
var menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
if (+versionUserData[i] < +versionLocal[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Error reading user file version', e);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
require(appPath);
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
{
|
||||
"name": "KeeWeb",
|
||||
"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",
|
||||
"uglify-loader": "^1.2.0",
|
||||
"webpack": "^1.11.0",
|
||||
"webpack-dev-server": "^1.10.1"
|
||||
"webpack-dev-server": "^1.12.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "grunt",
|
||||
"test": "grunt test"
|
||||
"test": "grunt test",
|
||||
"postinstall": "npm install --prefix electron"
|
||||
},
|
||||
"author": "Antelle",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"readme": "README.md"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue