mirror of https://github.com/keeweb/keeweb
commit
604c1bd3d4
|
@ -333,14 +333,6 @@ module.exports = function(grunt) {
|
|||
}
|
||||
}
|
||||
},
|
||||
codesign: {
|
||||
dmg: {
|
||||
options: {
|
||||
identity: 'app'
|
||||
},
|
||||
src: [`dist/desktop/KeeWeb-${pkg.version}.mac.dmg`]
|
||||
}
|
||||
},
|
||||
compress: {
|
||||
options: {
|
||||
level: 6
|
||||
|
|
|
@ -15,7 +15,7 @@ function emitSet(target, value, prevValue) {
|
|||
emitter.emit('add', value, target);
|
||||
updates.added.push(value);
|
||||
}
|
||||
emitter.emit('change', updates, this);
|
||||
emitter.emit('change', updates, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ function emitRemoved(target, removed) {
|
|||
for (const item of removed) {
|
||||
emitter.emit('remove', item, target);
|
||||
}
|
||||
emitter.emit('change', { added: [], removed }, this);
|
||||
emitter.emit('change', { added: [], removed }, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,7 @@ class Collection {
|
|||
this[SymbolEvents].emit('remove', item, this);
|
||||
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
shift() {
|
||||
|
@ -137,6 +138,7 @@ class Collection {
|
|||
this[SymbolEvents].emit('remove', item, this);
|
||||
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
unshift(...items) {
|
||||
|
@ -197,7 +199,7 @@ class Collection {
|
|||
}
|
||||
|
||||
sort() {
|
||||
this[SymbolArray].sort(this.comparator);
|
||||
return this[SymbolArray].sort(this.comparator);
|
||||
}
|
||||
|
||||
fill() {
|
||||
|
|
|
@ -53,7 +53,15 @@ class SettingsShortcutsView extends View {
|
|||
shortcutClick(e) {
|
||||
const globalShortcutType = e.target.dataset.shortcut;
|
||||
|
||||
const shortcutEditor = $('<div/>').addClass('shortcut__editor');
|
||||
const existing = $(`.shortcut__editor[data-shortcut=${globalShortcutType}]`);
|
||||
if (existing.length) {
|
||||
existing.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcutEditor = $('<div/>')
|
||||
.addClass('shortcut__editor')
|
||||
.attr('data-shortcut', globalShortcutType);
|
||||
$('<div/>')
|
||||
.text(Locale.setShEdit)
|
||||
.appendTo(shortcutEditor);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "KeeWeb",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "KeeWeb",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.1",
|
||||
"description": "Free cross-platform password manager compatible with KeePass",
|
||||
"main": "main.js",
|
||||
"homepage": "https://keeweb.info",
|
||||
|
|
|
@ -47,7 +47,7 @@ module.exports = function(grunt) {
|
|||
'compress:linux-x64'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build-desktop-dist-darwin', ['appdmg', 'codesign:dmg']);
|
||||
grunt.registerTask('build-desktop-dist-darwin', ['appdmg']);
|
||||
|
||||
grunt.registerTask('build-desktop-dist-win32', [
|
||||
'nsis:win32-un-x64',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "keeweb",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "keeweb",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.1",
|
||||
"description": "Free cross-platform password manager compatible with KeePass",
|
||||
"main": "Gruntfile.js",
|
||||
"private": true,
|
||||
|
|
|
@ -3,6 +3,10 @@ Release notes
|
|||
##### v1.13.0 (TBD)
|
||||
`-` fix #1323: version in the About dialog
|
||||
|
||||
##### v1.12.1 (2019-10-27)
|
||||
`-` fix #1324: duplicated shortcut editor in settings
|
||||
`-` fix #1313: disabled code signing for macOS dmg
|
||||
|
||||
##### v1.12.0 (2019-10-26)
|
||||
`-` #1022: fuzzy search
|
||||
`+` #1108: setting for running in an iframe
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
import { expect } from 'chai';
|
||||
import { Model } from 'framework/model';
|
||||
import { Collection } from 'framework/collection';
|
||||
|
||||
describe('Collection', () => {
|
||||
class TestModel extends Model {
|
||||
constructor(id) {
|
||||
super();
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
TestModel.defineModelProperties({ id: 'bar' });
|
||||
|
||||
class TestCollection extends Collection {
|
||||
static model = TestModel;
|
||||
}
|
||||
|
||||
it('should create a collection without models', () => {
|
||||
const collection = new TestCollection();
|
||||
|
||||
expect(collection).to.be.ok;
|
||||
expect(collection.length).to.eql(0);
|
||||
});
|
||||
|
||||
it('should create a collection with models', () => {
|
||||
const collection = new TestCollection([new TestModel('1'), new TestModel('2')]);
|
||||
|
||||
expect(collection).to.be.ok;
|
||||
expect(collection.length).to.eql(2);
|
||||
expect(collection[0]).to.be.ok;
|
||||
expect(collection[0].id).to.eql('1');
|
||||
expect(collection[1]).to.be.ok;
|
||||
expect(collection[1].id).to.eql('2');
|
||||
expect(collection[2]).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should check types when adding new items', () => {
|
||||
const collection = new TestCollection();
|
||||
|
||||
expect(() => {
|
||||
collection.push({ id: 'bar' });
|
||||
}).to.throw();
|
||||
});
|
||||
|
||||
it('should serialize a collection to JSON', () => {
|
||||
const collection = new TestCollection([new TestModel('1'), new TestModel('2')]);
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":"1"},{"id":"2"}]');
|
||||
});
|
||||
|
||||
it('should add models with push', () => {
|
||||
const collection = new TestCollection();
|
||||
|
||||
const callsChange = [];
|
||||
const callsAdd = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('add', (...args) => callsAdd.push(args));
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
|
||||
const model1 = new TestModel(1);
|
||||
collection.push(model1);
|
||||
expect(callsChange).to.eql([[{ added: [model1], removed: [] }, collection]]);
|
||||
expect(callsAdd).to.eql([[model1, collection]]);
|
||||
expect(collection.length).to.eql(1);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
|
||||
callsChange.length = 0;
|
||||
callsAdd.length = 0;
|
||||
|
||||
const modelA = new TestModel('a');
|
||||
const modelB = new TestModel('b');
|
||||
collection.push(modelA, modelB);
|
||||
expect(callsChange).to.eql([[{ added: [modelA, modelB], removed: [] }, collection]]);
|
||||
expect(callsAdd).to.eql([[modelA, collection], [modelB, collection]]);
|
||||
expect(collection.length).to.eql(3);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":"a"},{"id":"b"}]');
|
||||
});
|
||||
|
||||
it('should add models with unshift', () => {
|
||||
const collection = new TestCollection([new TestModel(1)]);
|
||||
|
||||
const callsChange = [];
|
||||
const callsAdd = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('add', (...args) => callsAdd.push(args));
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
|
||||
|
||||
const model2 = new TestModel(2);
|
||||
collection.unshift(model2);
|
||||
expect(callsChange).to.eql([[{ added: [model2], removed: [] }, collection]]);
|
||||
expect(callsAdd).to.eql([[model2, collection]]);
|
||||
expect(collection.length).to.eql(2);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":2},{"id":1}]');
|
||||
callsChange.length = 0;
|
||||
callsAdd.length = 0;
|
||||
|
||||
const modelA = new TestModel('a');
|
||||
const modelB = new TestModel('b');
|
||||
collection.unshift(modelA, modelB);
|
||||
expect(callsChange).to.eql([[{ added: [modelA, modelB], removed: [] }, collection]]);
|
||||
expect(callsAdd).to.eql([[modelA, collection], [modelB, collection]]);
|
||||
expect(collection.length).to.eql(4);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":"a"},{"id":"b"},{"id":2},{"id":1}]');
|
||||
});
|
||||
|
||||
it('should remove models with pop', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const collection = new TestCollection([model1, model2]);
|
||||
|
||||
const callsChange = [];
|
||||
const callsRemove = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('remove', (...args) => callsRemove.push(args));
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
|
||||
|
||||
const popped1 = collection.pop();
|
||||
expect(popped1).to.eql(model2);
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model2] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model2, collection]]);
|
||||
expect(collection.length).to.eql(1);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
|
||||
callsChange.length = 0;
|
||||
callsRemove.length = 0;
|
||||
|
||||
const popped2 = collection.pop();
|
||||
expect(popped2).to.eql(model1);
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model1, collection]]);
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
callsChange.length = 0;
|
||||
callsRemove.length = 0;
|
||||
|
||||
const popped3 = collection.pop();
|
||||
expect(popped3).to.be.undefined;
|
||||
expect(callsChange).to.eql([]);
|
||||
expect(callsRemove).to.eql([]);
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
});
|
||||
|
||||
it('should remove models with shift', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const collection = new TestCollection([model1, model2]);
|
||||
|
||||
const callsChange = [];
|
||||
const callsRemove = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('remove', (...args) => callsRemove.push(args));
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
|
||||
|
||||
const shifted1 = collection.shift();
|
||||
expect(shifted1).to.eql(model1);
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model1, collection]]);
|
||||
expect(collection.length).to.eql(1);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":2}]');
|
||||
callsChange.length = 0;
|
||||
callsRemove.length = 0;
|
||||
|
||||
const shifted2 = collection.shift();
|
||||
expect(shifted2).to.eql(model2);
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model2] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model2, collection]]);
|
||||
expect(collection.length).to.eql(0);
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
callsChange.length = 0;
|
||||
callsRemove.length = 0;
|
||||
|
||||
const shifted3 = collection.shift();
|
||||
expect(shifted3).to.be.undefined;
|
||||
expect(callsChange).to.eql([]);
|
||||
expect(callsRemove).to.eql([]);
|
||||
expect(collection.length).to.eql(0);
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
});
|
||||
|
||||
it('should remove models by setting length', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const model3 = new TestModel(3);
|
||||
const collection = new TestCollection([model1, model2, model3]);
|
||||
|
||||
const callsChange = [];
|
||||
const callsRemove = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('remove', (...args) => callsRemove.push(args));
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2},{"id":3}]');
|
||||
|
||||
collection.length = 1;
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model2, model3] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model2, collection], [model3, collection]]);
|
||||
expect(collection.length).to.eql(1);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
|
||||
callsChange.length = 0;
|
||||
callsRemove.length = 0;
|
||||
|
||||
collection.length = 1;
|
||||
expect(callsChange).to.eql([]);
|
||||
expect(callsRemove).to.eql([]);
|
||||
expect(collection.length).to.eql(1);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
|
||||
|
||||
collection.length = 0;
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model1, collection]]);
|
||||
expect(collection.length).to.eql(0);
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
});
|
||||
|
||||
it('should set custom properties', () => {
|
||||
const collection = new TestCollection();
|
||||
|
||||
const calls = [];
|
||||
|
||||
collection.on('change', (...args) => calls.push(args));
|
||||
collection.on('add', (...args) => calls.push(args));
|
||||
collection.on('remove', (...args) => calls.push(args));
|
||||
|
||||
collection.prop = 'val';
|
||||
|
||||
expect(collection.prop).to.eql('val');
|
||||
|
||||
expect(collection.length).to.eql(0);
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
expect(calls).to.eql([]);
|
||||
});
|
||||
|
||||
it('should check types when setting items', () => {
|
||||
const collection = new TestCollection();
|
||||
|
||||
expect(() => {
|
||||
collection[0] = { id: 'bar' };
|
||||
}).to.throw();
|
||||
});
|
||||
|
||||
it('should set new items', () => {
|
||||
const collection = new TestCollection();
|
||||
|
||||
const callsChange = [];
|
||||
const callsAdd = [];
|
||||
const callsRemove = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('add', (...args) => callsAdd.push(args));
|
||||
collection.on('remove', (...args) => callsRemove.push(args));
|
||||
|
||||
const model = new TestModel(1);
|
||||
collection[0] = model;
|
||||
|
||||
expect(collection.length).to.eql(1);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1}]');
|
||||
expect(callsChange).to.eql([[{ added: [model], removed: [] }, collection]]);
|
||||
expect(callsAdd).to.eql([[model, collection]]);
|
||||
expect(callsRemove).to.eql([]);
|
||||
});
|
||||
|
||||
it('should set existing items', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const collection = new TestCollection([model1, model2]);
|
||||
|
||||
const callsChange = [];
|
||||
const callsAdd = [];
|
||||
const callsRemove = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('add', (...args) => callsAdd.push(args));
|
||||
collection.on('remove', (...args) => callsRemove.push(args));
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
|
||||
|
||||
const model3 = new TestModel(3);
|
||||
collection[1] = model3;
|
||||
|
||||
expect(collection.length).to.eql(2);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":3}]');
|
||||
expect(callsChange).to.eql([[{ added: [model3], removed: [model2] }, collection]]);
|
||||
expect(callsAdd).to.eql([[model3, collection]]);
|
||||
expect(callsRemove).to.eql([[model2, collection]]);
|
||||
});
|
||||
|
||||
it('should get items by id', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const collection = new TestCollection([model1, model2]);
|
||||
|
||||
expect(collection.get(1)).to.eql(model1);
|
||||
expect(collection.get(2)).to.eql(model2);
|
||||
expect(collection.get(3)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should remove items', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const collection = new TestCollection([model1, model2]);
|
||||
|
||||
const callsChange = [];
|
||||
const callsRemove = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('remove', (...args) => callsRemove.push(args));
|
||||
|
||||
collection.remove(1);
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":2}]');
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model1] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model1, collection]]);
|
||||
callsChange.length = 0;
|
||||
callsRemove.length = 0;
|
||||
|
||||
collection.remove(model2);
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[]');
|
||||
expect(callsChange).to.eql([[{ added: [], removed: [model2] }, collection]]);
|
||||
expect(callsRemove).to.eql([[model2, collection]]);
|
||||
});
|
||||
|
||||
it('should sort items by comparator', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const collection = new TestCollection([model1, model2]);
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
|
||||
|
||||
collection.comparator = (x, y) => x.id - y.id;
|
||||
collection.sort();
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2}]');
|
||||
|
||||
collection.comparator = (x, y) => y.id - x.id;
|
||||
collection.sort();
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":2},{"id":1}]');
|
||||
});
|
||||
|
||||
it('should splice items', () => {
|
||||
const model1 = new TestModel(1);
|
||||
const model2 = new TestModel(2);
|
||||
const model3 = new TestModel(3);
|
||||
const collection = new TestCollection([model1, model2, model3]);
|
||||
|
||||
const callsChange = [];
|
||||
const callsAdd = [];
|
||||
const callsRemove = [];
|
||||
|
||||
collection.on('change', (...args) => callsChange.push(args));
|
||||
collection.on('add', (...args) => callsAdd.push(args));
|
||||
collection.on('remove', (...args) => callsRemove.push(args));
|
||||
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":2},{"id":3}]');
|
||||
|
||||
const model4 = new TestModel(4);
|
||||
const model5 = new TestModel(5);
|
||||
const model6 = new TestModel(6);
|
||||
collection.splice(1, 1, model4, model5, model6);
|
||||
|
||||
expect(collection.length).to.eql(5);
|
||||
expect(JSON.stringify(collection)).to.eql('[{"id":1},{"id":4},{"id":5},{"id":6},{"id":3}]');
|
||||
expect(callsChange).to.eql([
|
||||
[{ added: [model4, model5, model6], removed: [model2] }, collection]
|
||||
]);
|
||||
expect(callsAdd).to.eql([[model4, collection], [model5, collection], [model6, collection]]);
|
||||
expect(callsRemove).to.eql([[model2, collection]]);
|
||||
});
|
||||
});
|
|
@ -11,6 +11,7 @@ const rootDir = path.join(__dirname, '..');
|
|||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
devtool: 'source-map',
|
||||
entry: path.resolve(__dirname, 'index.js'),
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
|
@ -22,5 +23,8 @@ module.exports = {
|
|||
...appConfig.resolve.alias,
|
||||
test: path.resolve(rootDir, 'test')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: appConfig.module.rules
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue