mirror of
https://github.com/keeweb/keeweb.git
synced 2024-06-28 07:50:55 +02:00
376 lines
11 KiB
JavaScript
376 lines
11 KiB
JavaScript
import kdbxweb from 'kdbxweb';
|
|
import { IconMap } from 'const/icon-map';
|
|
import { EntryModel } from 'models/entry-model';
|
|
import { MenuItemModel } from 'models/menu/menu-item-model';
|
|
import { IconUrlFormat } from 'util/formatting/icon-url-format';
|
|
import { GroupCollection } from 'collections/group-collection';
|
|
import { EntryCollection } from 'collections/entry-collection';
|
|
|
|
const KdbxIcons = kdbxweb.Consts.Icons;
|
|
|
|
const DefaultAutoTypeSequence = '{USERNAME}{TAB}{PASSWORD}{ENTER}';
|
|
|
|
class GroupModel extends MenuItemModel {
|
|
setGroup(group, file, parentGroup) {
|
|
const isRecycleBin = group.uuid.equals(file.db.meta.recycleBinUuid);
|
|
const id = file.subId(group.uuid.id);
|
|
this.set(
|
|
{
|
|
id,
|
|
uuid: group.uuid.id,
|
|
expanded: group.expanded,
|
|
visible: !isRecycleBin,
|
|
items: new GroupCollection(),
|
|
entries: new EntryCollection(),
|
|
filterValue: id,
|
|
enableSearching: group.enableSearching,
|
|
enableAutoType: group.enableAutoType,
|
|
autoTypeSeq: group.defaultAutoTypeSeq,
|
|
top: !parentGroup,
|
|
drag: !!parentGroup,
|
|
collapsible: !!parentGroup
|
|
},
|
|
{ silent: true }
|
|
);
|
|
this.group = group;
|
|
this.file = file;
|
|
this.parentGroup = parentGroup;
|
|
this._fillByGroup(true);
|
|
const items = this.items;
|
|
const entries = this.entries;
|
|
|
|
const itemsArray = group.groups.map(subGroup => {
|
|
let g = file.getGroup(file.subId(subGroup.uuid.id));
|
|
if (g) {
|
|
g.setGroup(subGroup, file, this);
|
|
} else {
|
|
g = GroupModel.fromGroup(subGroup, file, this);
|
|
}
|
|
return g;
|
|
}, this);
|
|
items.push(...itemsArray);
|
|
|
|
const entriesArray = group.entries.map(entry => {
|
|
let e = file.getEntry(file.subId(entry.uuid.id));
|
|
if (e) {
|
|
e.setEntry(entry, this, file);
|
|
} else {
|
|
e = EntryModel.fromEntry(entry, this, file);
|
|
}
|
|
return e;
|
|
}, this);
|
|
entries.push(...entriesArray);
|
|
}
|
|
|
|
_fillByGroup(silent) {
|
|
this.set(
|
|
{
|
|
title: this.parentGroup ? this.group.name : this.file.name,
|
|
iconId: this.group.icon,
|
|
icon: this._iconFromId(this.group.icon),
|
|
customIcon: this._buildCustomIcon(),
|
|
customIconId: this.group.customIcon ? this.group.customIcon.toString() : null,
|
|
expanded: this.group.expanded !== false
|
|
},
|
|
{ silent }
|
|
);
|
|
}
|
|
|
|
_iconFromId(id) {
|
|
if (id === KdbxIcons.Folder || id === KdbxIcons.FolderOpen) {
|
|
return undefined;
|
|
}
|
|
return IconMap[id];
|
|
}
|
|
|
|
_buildCustomIcon() {
|
|
this.customIcon = null;
|
|
if (this.group.customIcon) {
|
|
return IconUrlFormat.toDataUrl(this.file.db.meta.customIcons[this.group.customIcon]);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
_groupModified() {
|
|
if (this.isJustCreated) {
|
|
this.isJustCreated = false;
|
|
}
|
|
this.file.setModified();
|
|
this.group.times.update();
|
|
}
|
|
|
|
forEachGroup(callback, filter) {
|
|
let result = true;
|
|
this.items.forEach(group => {
|
|
if (group.matches(filter)) {
|
|
result =
|
|
callback(group) !== false && group.forEachGroup(callback, filter) !== false;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
forEachOwnEntry(filter, callback) {
|
|
this.entries.forEach(function(entry) {
|
|
if (entry.matches(filter)) {
|
|
callback(entry, this);
|
|
}
|
|
});
|
|
}
|
|
|
|
matches(filter) {
|
|
return (
|
|
((filter && filter.includeDisabled) ||
|
|
(this.group.enableSearching !== false &&
|
|
!this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup))) &&
|
|
(!filter || !filter.autoType || this.group.enableAutoType !== false)
|
|
);
|
|
}
|
|
|
|
getOwnSubGroups() {
|
|
return this.group.groups;
|
|
}
|
|
|
|
addEntry(entry) {
|
|
this.entries.push(entry);
|
|
}
|
|
|
|
addGroup(group) {
|
|
this.items.push(group);
|
|
}
|
|
|
|
setName(name) {
|
|
this._groupModified();
|
|
this.group.name = name;
|
|
this._fillByGroup();
|
|
}
|
|
|
|
setIcon(iconId) {
|
|
this._groupModified();
|
|
this.group.icon = iconId;
|
|
this.group.customIcon = undefined;
|
|
this._fillByGroup();
|
|
}
|
|
|
|
setCustomIcon(customIconId) {
|
|
this._groupModified();
|
|
this.group.customIcon = new kdbxweb.KdbxUuid(customIconId);
|
|
this._fillByGroup();
|
|
}
|
|
|
|
setExpanded(expanded) {
|
|
// this._groupModified(); // it's not good to mark the file as modified when a group is collapsed
|
|
this.group.expanded = expanded;
|
|
this.expanded = expanded;
|
|
}
|
|
|
|
setEnableSearching(enabled) {
|
|
this._groupModified();
|
|
let parentEnableSearching = true;
|
|
let parentGroup = this.parentGroup;
|
|
while (parentGroup) {
|
|
if (typeof parentGroup.enableSearching === 'boolean') {
|
|
parentEnableSearching = parentGroup.enableSearching;
|
|
break;
|
|
}
|
|
parentGroup = parentGroup.parentGroup;
|
|
}
|
|
if (enabled === parentEnableSearching) {
|
|
enabled = null;
|
|
}
|
|
this.group.enableSearching = enabled;
|
|
this.enableSearching = this.group.enableSearching;
|
|
}
|
|
|
|
getEffectiveEnableSearching() {
|
|
let grp = this;
|
|
while (grp) {
|
|
if (typeof grp.enableSearching === 'boolean') {
|
|
return grp.enableSearching;
|
|
}
|
|
grp = grp.parentGroup;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
setEnableAutoType(enabled) {
|
|
this._groupModified();
|
|
let parentEnableAutoType = true;
|
|
let parentGroup = this.parentGroup;
|
|
while (parentGroup) {
|
|
if (typeof parentGroup.enableAutoType === 'boolean') {
|
|
parentEnableAutoType = parentGroup.enableAutoType;
|
|
break;
|
|
}
|
|
parentGroup = parentGroup.parentGroup;
|
|
}
|
|
if (enabled === parentEnableAutoType) {
|
|
enabled = null;
|
|
}
|
|
this.group.enableAutoType = enabled;
|
|
this.enableAutoType = this.group.enableAutoType;
|
|
}
|
|
|
|
getEffectiveEnableAutoType() {
|
|
let grp = this;
|
|
while (grp) {
|
|
if (typeof grp.enableAutoType === 'boolean') {
|
|
return grp.enableAutoType;
|
|
}
|
|
grp = grp.parentGroup;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
setAutoTypeSeq(seq) {
|
|
this._groupModified();
|
|
this.group.defaultAutoTypeSeq = seq || undefined;
|
|
this.autoTypeSeq = this.group.defaultAutoTypeSeq;
|
|
}
|
|
|
|
getEffectiveAutoTypeSeq() {
|
|
let grp = this;
|
|
while (grp) {
|
|
if (grp.autoTypeSeq) {
|
|
return grp.autoTypeSeq;
|
|
}
|
|
grp = grp.parentGroup;
|
|
}
|
|
return DefaultAutoTypeSequence;
|
|
}
|
|
|
|
getParentEffectiveAutoTypeSeq() {
|
|
return this.parentGroup
|
|
? this.parentGroup.getEffectiveAutoTypeSeq()
|
|
: DefaultAutoTypeSequence;
|
|
}
|
|
|
|
isEntryTemplatesGroup() {
|
|
return this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup);
|
|
}
|
|
|
|
moveToTrash() {
|
|
this.file.setModified();
|
|
this.file.db.remove(this.group);
|
|
if (this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup)) {
|
|
this.file.db.meta.entryTemplatesGroup = undefined;
|
|
}
|
|
this.file.reload();
|
|
}
|
|
|
|
deleteFromTrash() {
|
|
this.file.db.move(this.group, null);
|
|
this.file.reload();
|
|
}
|
|
|
|
removeWithoutHistory() {
|
|
const ix = this.parentGroup.group.groups.indexOf(this.group);
|
|
if (ix >= 0) {
|
|
this.parentGroup.group.groups.splice(ix, 1);
|
|
}
|
|
this.file.reload();
|
|
}
|
|
|
|
moveHere(object) {
|
|
if (!object || object.id === this.id) {
|
|
return;
|
|
}
|
|
if (object.file === this.file) {
|
|
this.file.setModified();
|
|
if (object instanceof GroupModel) {
|
|
for (let parent = this; parent; parent = parent.parentGroup) {
|
|
if (object === parent) {
|
|
return;
|
|
}
|
|
}
|
|
if (this.group.groups.indexOf(object.group) >= 0) {
|
|
return;
|
|
}
|
|
this.file.db.move(object.group, this.group);
|
|
this.file.reload();
|
|
} else if (object instanceof EntryModel) {
|
|
if (this.group.entries.indexOf(object.entry) >= 0) {
|
|
return;
|
|
}
|
|
this.file.db.move(object.entry, this.group);
|
|
this.file.reload();
|
|
}
|
|
} else {
|
|
if (object instanceof EntryModel) {
|
|
this.file.setModified();
|
|
const detachedEntry = object.detach();
|
|
this.file.db.importEntry(detachedEntry, this.group, object.file.db);
|
|
this.file.reload();
|
|
} else {
|
|
// moving groups between files is not supported for now
|
|
}
|
|
}
|
|
}
|
|
|
|
moveToTop(object) {
|
|
if (
|
|
!object ||
|
|
object.id === this.id ||
|
|
object.file !== this.file ||
|
|
!(object instanceof GroupModel)
|
|
) {
|
|
return;
|
|
}
|
|
this.file.setModified();
|
|
for (let parent = this; parent; parent = parent.parentGroup) {
|
|
if (object === parent) {
|
|
return;
|
|
}
|
|
}
|
|
let atIndex = this.parentGroup.group.groups.indexOf(this.group);
|
|
const selfIndex = this.parentGroup.group.groups.indexOf(object.group);
|
|
if (selfIndex >= 0 && selfIndex < atIndex) {
|
|
atIndex--;
|
|
}
|
|
if (atIndex >= 0) {
|
|
this.file.db.move(object.group, this.parentGroup.group, atIndex);
|
|
}
|
|
this.file.reload();
|
|
}
|
|
|
|
static fromGroup(group, file, parentGroup) {
|
|
const model = new GroupModel();
|
|
model.setGroup(group, file, parentGroup);
|
|
return model;
|
|
}
|
|
|
|
static newGroup(group, file) {
|
|
const model = new GroupModel();
|
|
const grp = file.db.createGroup(group.group);
|
|
model.setGroup(grp, file, group);
|
|
model.group.times.update();
|
|
model.isJustCreated = true;
|
|
group.addGroup(model);
|
|
file.setModified();
|
|
file.reload();
|
|
return model;
|
|
}
|
|
}
|
|
|
|
GroupModel.defineModelProperties({
|
|
id: '',
|
|
uuid: '',
|
|
iconId: 0,
|
|
entries: null,
|
|
filterKey: 'group',
|
|
editable: true,
|
|
top: false,
|
|
drag: true,
|
|
drop: true,
|
|
enableSearching: true,
|
|
enableAutoType: null,
|
|
autoTypeSeq: null,
|
|
group: null,
|
|
file: null,
|
|
parentGroup: null,
|
|
customIconId: null,
|
|
isJustCreated: false
|
|
});
|
|
|
|
export { GroupModel };
|