From b4adfcb9df1ba2e92a31169f618ed6e0f72207e5 Mon Sep 17 00:00:00 2001 From: Antelle Date: Fri, 30 Oct 2015 22:39:24 +0300 Subject: [PATCH 01/85] update dropbox dep --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 78573b26..94af6b26 100644 --- a/bower.json +++ b/bower.json @@ -26,7 +26,7 @@ "backbone": "~1.2.3", "baron": "~0.7.11", "bourbon": "~4.2.5", - "dropbox": "antelle/dropbox-js#0.10.3", + "dropbox": "antelle/dropbox-js#0.10.4", "font-awesome": "~4.4.0", "install": "~1.0.4", "kdbxweb": "~0.1.10", From be491aee5273e22db046acce24f17d31a2b39f20 Mon Sep 17 00:00:00 2001 From: Antelle Date: Sat, 31 Oct 2015 09:15:17 +0300 Subject: [PATCH 02/85] contrast text color on buttons --- app/scripts/app.js | 7 ++++++- app/styles/base/_buttons.scss | 4 ++-- app/styles/base/_colors.scss | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/scripts/app.js b/app/scripts/app.js index d013e834..dc79afd4 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -14,6 +14,12 @@ $(function() { return; } KeyHandler.init(); + + var appModel = new AppModel(); + if (appModel.settings.get('theme')) { + document.body.setAttribute('class', 'th-' + appModel.settings.get('theme')); + } + if (['https:', 'file:', 'app:'].indexOf(location.protocol) < 0) { Alerts.error({ header: 'Not Secure!', icon: 'user-secret', esc: false, enter: false, click: false, body: 'You have loaded this app with insecure connection. ' + @@ -31,7 +37,6 @@ $(function() { } function showApp() { - var appModel = new AppModel(); new AppView({ model: appModel }).render().showOpenFile(appModel.settings.get('lastOpenFile')); } }); diff --git a/app/styles/base/_buttons.scss b/app/styles/base/_buttons.scss index 1f93a135..4365a515 100644 --- a/app/styles/base/_buttons.scss +++ b/app/styles/base/_buttons.scss @@ -20,11 +20,12 @@ @include th { border: 1px solid action-color(); background-color: action-color(); - color: text-color(); + color: text-contrast-color(action-color()); &.btn-error, &.btn-silent { border-color: base-border-color(); background-color: transparent; + color: text-contrast-color(background-color()); } &:hover { @@ -38,7 +39,6 @@ border-color: action-background-color-focus(); background-color: rgba(action-background-color-focus(), .1); } - color: text-color(); } &:active { diff --git a/app/styles/base/_colors.scss b/app/styles/base/_colors.scss index 71e95939..b0bb17b4 100644 --- a/app/styles/base/_colors.scss +++ b/app/styles/base/_colors.scss @@ -20,6 +20,7 @@ $violet: #d946db; @function muted-color-border() { @return mix(medium-color(), background-color(), 15%); } @function text-selection-bg-color() { @return rgba(action-color(), .3); } @function text-selection-bg-color-error() { @return rgba(error-color(), .8); } +@function text-contrast-color($bg) { @if (lightness($bg) >= lightness(background-color())) { @return text-color(); } @else { @return background-color(); } } // Borders, shadows From 27e3b6c2471880b4b4e3dc39c252f6fdf490e45b Mon Sep 17 00:00:00 2001 From: Antelle Date: Sat, 31 Oct 2015 09:18:24 +0300 Subject: [PATCH 03/85] better theme changing --- app/scripts/app.js | 6 +++--- app/scripts/util/theme-changer.js | 14 ++++++++++++++ app/scripts/views/app-view.js | 11 +++-------- 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 app/scripts/util/theme-changer.js diff --git a/app/scripts/app.js b/app/scripts/app.js index dc79afd4..1eb82e64 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -4,7 +4,8 @@ var AppModel = require('./models/app-model'), AppView = require('./views/app-view'), KeyHandler = require('./comp/key-handler'), Alerts = require('./comp/alerts'), - DropboxLink = require('./comp/dropbox-link'); + DropboxLink = require('./comp/dropbox-link'), + ThemeChanger = require('./util/theme-changer'); $(function() { require('./mixins/view'); @@ -17,9 +18,8 @@ $(function() { var appModel = new AppModel(); if (appModel.settings.get('theme')) { - document.body.setAttribute('class', 'th-' + appModel.settings.get('theme')); + ThemeChanger.setTheme(appModel.settings.get('theme')); } - if (['https:', 'file:', 'app:'].indexOf(location.protocol) < 0) { Alerts.error({ header: 'Not Secure!', icon: 'user-secret', esc: false, enter: false, click: false, body: 'You have loaded this app with insecure connection. ' + diff --git a/app/scripts/util/theme-changer.js b/app/scripts/util/theme-changer.js new file mode 100644 index 00000000..36fb5804 --- /dev/null +++ b/app/scripts/util/theme-changer.js @@ -0,0 +1,14 @@ +'use strict'; + +var ThemeChanger = { + setTheme: function(theme) { + _.forEach(document.body.classList, function(cls) { + if (/^th\-/.test(cls)) { + document.body.classList.remove(cls); + } + }); + document.body.classList.add('th-' + theme); + } +}; + +module.exports = ThemeChanger; diff --git a/app/scripts/views/app-view.js b/app/scripts/views/app-view.js index 85357a95..8bd56bc0 100644 --- a/app/scripts/views/app-view.js +++ b/app/scripts/views/app-view.js @@ -11,7 +11,8 @@ var Backbone = require('backbone'), Alerts = require('../comp/alerts'), Keys = require('../const/keys'), KeyHandler = require('../comp/key-handler'), - Launcher = require('../comp/launcher'); + Launcher = require('../comp/launcher'), + ThemeChanger = require('../util/theme-changer'); var AppView = Backbone.View.extend({ el: 'body', @@ -275,13 +276,7 @@ var AppView = Backbone.View.extend({ }, setTheme: function() { - var theme = this.model.settings.get('theme'); - _.forEach(document.body.classList, function(cls) { - if (/^th\-/.test(cls)) { - document.body.classList.remove(cls); - } - }); - document.body.classList.add('th-' + theme); + ThemeChanger.setTheme(this.model.settings.get('theme')); }, extLinkClick: function(e) { From 7c6a943e84c038d8cd929b56f0eb6fd3d3e2ff7f Mon Sep 17 00:00:00 2001 From: Antelle Date: Sat, 31 Oct 2015 09:24:24 +0300 Subject: [PATCH 04/85] todo --- TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index a971d18d..9c1027a7 100644 --- a/TODO.md +++ b/TODO.md @@ -10,10 +10,11 @@ - [ ] switch view - [ ] optional auto-update - [ ] lock without closing +- [ ] close files - [ ] merge - [ ] show sync date - [ ] dropbox keyfiles -- [ ] save to localstorage +- [ ] offline and local storage - [ ] generation templates - [ ] advanced search - [ ] mobile From 161b31f16329a5f8795a5cecc73728dba575a354 Mon Sep 17 00:00:00 2001 From: Antelle Date: Sat, 31 Oct 2015 15:23:12 +0300 Subject: [PATCH 05/85] todo --- TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 9c1027a7..07eff91e 100644 --- a/TODO.md +++ b/TODO.md @@ -10,6 +10,7 @@ - [ ] switch view - [ ] optional auto-update - [ ] lock without closing +- [ ] secure fields - [ ] close files - [ ] merge - [ ] show sync date @@ -20,8 +21,8 @@ - [ ] mobile - [ ] file type associations - [ ] auto-type -- [ ] secure fields - [ ] audit +- [ ] native encryption for electron - [ ] entry templates - [ ] custom icons - [ ] plugins From 9bd66cfc1aff0eae3b2af98d5ef7526f42ed92ae Mon Sep 17 00:00:00 2001 From: Antelle Date: Sat, 31 Oct 2015 15:23:36 +0300 Subject: [PATCH 06/85] todo --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 07eff91e..147a15c8 100644 --- a/TODO.md +++ b/TODO.md @@ -18,7 +18,7 @@ - [ ] offline and local storage - [ ] generation templates - [ ] advanced search -- [ ] mobile +- [ ] mobile apps - [ ] file type associations - [ ] auto-type - [ ] audit From b1620be716901e62a9e02327658d4be3914b1ded Mon Sep 17 00:00:00 2001 From: Antelle Date: Sat, 31 Oct 2015 16:49:53 +0300 Subject: [PATCH 07/85] todo --- TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 147a15c8..6b5fcd0c 100644 --- a/TODO.md +++ b/TODO.md @@ -16,11 +16,12 @@ - [ ] show sync date - [ ] dropbox keyfiles - [ ] offline and local storage +- [ ] trim history by rules - [ ] generation templates - [ ] advanced search - [ ] mobile apps - [ ] file type associations -- [ ] auto-type +- [ ] auto-type for desktop - [ ] audit - [ ] native encryption for electron - [ ] entry templates From 4f4c984620625f8d1d025bb1fbf360141d504d9a Mon Sep 17 00:00:00 2001 From: Antelle Date: Sat, 31 Oct 2015 22:09:32 +0300 Subject: [PATCH 08/85] add/edit groups --- TODO.md | 6 +- app/scripts/mixins/view.js | 4 + app/scripts/models/app-model.js | 15 ++- app/scripts/models/file-model.js | 1 + app/scripts/models/group-model.js | 90 +++++++++++++++-- app/scripts/views/app-view.js | 35 ++++++- app/scripts/views/details/details-view.js | 6 +- app/scripts/views/grp-view.js | 98 +++++++++++++++++++ ...tails-icon-view.js => icon-select-view.js} | 10 +- app/scripts/views/list-view.js | 6 ++ app/scripts/views/menu/menu-item-view.js | 28 ++++-- app/styles/areas/_app.scss | 11 +++ app/styles/areas/_details.scss | 19 ---- app/styles/areas/_grp.scss | 38 +++++++ app/styles/areas/_menu.scss | 21 ++++ app/styles/areas/_settings.scss | 7 -- app/styles/base/_forms.scss | 9 ++ app/styles/common/_icon-select.scss | 18 ++++ app/styles/main.scss | 2 + app/templates/app.html | 1 + app/templates/details/details-icon.html | 5 - app/templates/grp.html | 19 ++++ app/templates/icon-select.html | 5 + app/templates/menu/menu-item.html | 5 +- app/templates/settings/settings-file.html | 18 ++-- app/templates/settings/settings-general.html | 2 +- app/templates/settings/settings-help.html | 2 +- 27 files changed, 408 insertions(+), 73 deletions(-) create mode 100644 app/scripts/views/grp-view.js rename app/scripts/views/{details/details-icon-view.js => icon-select-view.js} (64%) create mode 100644 app/styles/areas/_grp.scss create mode 100644 app/styles/common/_icon-select.scss delete mode 100644 app/templates/details/details-icon.html create mode 100644 app/templates/grp.html create mode 100644 app/templates/icon-select.html diff --git a/TODO.md b/TODO.md index 6b5fcd0c..b0a1a413 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,4 @@ -# MVP - -- [ ] add/edit groups - -# FUTURE +# TODO - [ ] trash: groups/empty/untrash - [ ] move groups/entries diff --git a/app/scripts/mixins/view.js b/app/scripts/mixins/view.js index 73d95682..c849de32 100644 --- a/app/scripts/mixins/view.js +++ b/app/scripts/mixins/view.js @@ -22,6 +22,10 @@ _.extend(Backbone.View.prototype, { return this; }, + isHidden: function() { + return this._hidden; + }, + afterPaint: function(callback) { this.requestAnimationFrame(function() { this.requestAnimationFrame(callback); diff --git a/app/scripts/models/app-model.js b/app/scripts/models/app-model.js index e18c2a8c..ffee3d56 100644 --- a/app/scripts/models/app-model.js +++ b/app/scripts/models/app-model.js @@ -4,6 +4,7 @@ var Backbone = require('backbone'), AppSettingsModel = require('./app-settings-model'), MenuModel = require('./menu/menu-model'), EntryModel = require('./entry-model'), + GroupModel = require('./group-model'), FileCollection = require('../collections/file-collection'), EntryCollection = require('../collections/entry-collection'); @@ -139,7 +140,7 @@ var AppModel = Backbone.Model.extend({ return filter; }, - createNewEntry: function() { + getFirstSelectedGroup: function() { var selGroupId = this.filter.group; var file, group; if (selGroupId) { @@ -155,7 +156,17 @@ var AppModel = Backbone.Model.extend({ file = this.files.first(); group = file.get('groups').first(); } - return EntryModel.newEntry(group, file); + return { group: group, file: file }; + }, + + createNewEntry: function() { + var sel = this.getFirstSelectedGroup(); + return EntryModel.newEntry(sel.group, sel.file); + }, + + createNewGroup: function() { + var sel = this.getFirstSelectedGroup(); + return GroupModel.newGroup(sel.group, sel.file); } }); diff --git a/app/scripts/models/file-model.js b/app/scripts/models/file-model.js index 7dbe6a88..592ee3a5 100644 --- a/app/scripts/models/file-model.js +++ b/app/scripts/models/file-model.js @@ -245,6 +245,7 @@ var FileModel = Backbone.Model.extend({ this.db.meta.name = name; this.db.meta.nameChanged = new Date(); this.set('name', name); + this.get('groups').first().setName(name); this.setModified(); }, diff --git a/app/scripts/models/group-model.js b/app/scripts/models/group-model.js index 8a2e0ed9..b14cc18d 100644 --- a/app/scripts/models/group-model.js +++ b/app/scripts/models/group-model.js @@ -10,7 +10,9 @@ var GroupModel = MenuItemModel.extend({ defaults: _.extend({}, MenuItemModel.prototype.defaults, { iconId: 0, entries: null, - filterKey: 'group' + filterKey: 'group', + editable: true, + top: false }), initialize: function() { @@ -23,9 +25,6 @@ var GroupModel = MenuItemModel.extend({ var isRecycleBin = file.db.meta.recycleBinUuid && file.db.meta.recycleBinUuid.id === group.uuid.id; this.set({ id: group.uuid.id, - title: group.name, - iconId: group.icon, - icon: this._iconFromId(group.icon), expanded: true, visible: !isRecycleBin, items: new GroupCollection(), @@ -33,16 +32,25 @@ var GroupModel = MenuItemModel.extend({ }, { silent: true }); this.group = group; this.file = file; + this._fillByGroup(true); var items = this.get('items'), entries = this.get('entries'); group.groups.forEach(function(subGroup) { - items.add(GroupModel.fromGroup(subGroup, file)); - }); + items.add(GroupModel.fromGroup(subGroup, file, this)); + }, this); group.entries.forEach(function(entry) { entries.add(EntryModel.fromEntry(entry, this, file)); }, this); }, + _fillByGroup: function(silent) { + this.set({ + title: this.group.name, + iconId: this.group.icon, + icon: this._iconFromId(this.group.icon) + }, { silent: silent }); + }, + _iconFromId: function(id) { if (id === KdbxIcons.Folder || id === KdbxIcons.FolderOpen) { return undefined; @@ -50,6 +58,14 @@ var GroupModel = MenuItemModel.extend({ return IconMap[id]; }, + _groupModified: function() { + this.file.setModified(); + if (this.isJustCreated) { + this.isJustCreated = false; + } + this.group.times.update(); + }, + forEachGroup: function(callback, includeDisabled) { var result = true; this.get('items').forEach(function(group) { @@ -74,12 +90,72 @@ var GroupModel = MenuItemModel.extend({ addEntry: function(entry) { this.get('entries').add(entry); + }, + + removeGroup: function(group) { + this.get('items').remove(group); + }, + + addGroup: function(group) { + this.get('items').add(group); + this.trigger('insert', group); + }, + + setName: function(name) { + this._groupModified(); + this.group.name = name; + this._fillByGroup(); + }, + + setIcon: function(iconId) { + this._groupModified(); + this.group.icon = iconId; + this._fillByGroup(); + }, + + moveToTrash: function() { + this.file.setModified(); + this.file.db.remove(this.group, this.parentGroup.group); + this.parentGroup.removeGroup(this); + var trashGroup = this.file.getTrashGroup(); + if (trashGroup) { + //trashGroup.addGroup(this); // TODO: groups in trash are currently not displayed + this.parentGroup = trashGroup; + this.deleted = true; + } + this.trigger('delete'); + }, + + removeWithoutHistory: function() { + var ix = this.parentGroup.group.groups.indexOf(this.group); + if (ix >= 0) { + this.parentGroup.group.groups.splice(ix, 1); + } + this.parentGroup.removeGroup(this); } }); -GroupModel.fromGroup = function(group, file) { +GroupModel.fromGroup = function(group, file, parentGroup) { var model = new GroupModel(); model.setFromGroup(group, file); + if (parentGroup) { + model.parentGroup = parentGroup; + } else { + model.set({ top: true }, { silent: true }); + } + return model; +}; + +GroupModel.newGroup = function(group, file) { + var model = new GroupModel(); + var grp = file.db.createGroup(group.group); + model.setFromGroup(grp, file); + model.group.times.update(); + model.parentGroup = group; + model.unsaved = true; + model.isJustCreated = true; + group.addGroup(model); + file.setModified(); return model; }; diff --git a/app/scripts/views/app-view.js b/app/scripts/views/app-view.js index 8bd56bc0..3056c9c4 100644 --- a/app/scripts/views/app-view.js +++ b/app/scripts/views/app-view.js @@ -6,6 +6,7 @@ var Backbone = require('backbone'), FooterView = require('../views/footer-view'), ListView = require('../views/list-view'), DetailsView = require('../views/details/details-view'), + GrpView = require('../views/grp-view'), OpenView = require('../views/open-view'), SettingsView = require('../views/settings/settings-view'), Alerts = require('../comp/alerts'), @@ -37,6 +38,7 @@ var AppView = Backbone.View.extend({ this.views.listDrag = new DragView('x'); this.views.details = new DetailsView(); this.views.details.appModel = this.model; + this.views.grp = new GrpView(); this.views.menu.listenDrag(this.views.menuDrag); this.views.list.listenDrag(this.views.listDrag); @@ -44,6 +46,7 @@ var AppView = Backbone.View.extend({ this.listenTo(this.model.settings, 'change:theme', this.setTheme); this.listenTo(this.model.files, 'update reset', this.fileListUpdated); + this.listenTo(Backbone, 'select-all', this.selectAll); this.listenTo(Backbone, 'menu-select', this.menuSelect); this.listenTo(Backbone, 'lock-workspace', this.lockWorkspace); this.listenTo(Backbone, 'show-file', this.showFileSettings); @@ -53,6 +56,7 @@ var AppView = Backbone.View.extend({ this.listenTo(Backbone, 'toggle-settings', this.toggleSettings); this.listenTo(Backbone, 'toggle-menu', this.toggleMenu); this.listenTo(Backbone, 'toggle-details', this.toggleDetails); + this.listenTo(Backbone, 'edit-group', this.editGroup); this.listenTo(Backbone, 'launcher-open-file', this.launcherOpenFile); window.onbeforeunload = this.beforeUnload.bind(this); @@ -71,6 +75,7 @@ var AppView = Backbone.View.extend({ this.views.list.setElement(this.$el.find('.app__list')).render(); this.views.listDrag.setElement(this.$el.find('.app__list-drag')).render(); this.views.details.setElement(this.$el.find('.app__details')).render(); + this.views.grp.setElement(this.$el.find('.app__grp')).render().hide(); return this; }, @@ -80,6 +85,7 @@ var AppView = Backbone.View.extend({ this.views.list.hide(); this.views.listDrag.hide(); this.views.details.hide(); + this.views.grp.hide(); this.views.footer.toggle(this.model.files.hasOpenFiles()); this.hideSettings(); this.hideOpenFile(); @@ -103,6 +109,7 @@ var AppView = Backbone.View.extend({ this.views.list.show(); this.views.listDrag.show(); this.views.details.show(); + this.views.grp.hide(); this.views.footer.show(); this.hideOpenFile(); this.hideSettings(); @@ -130,6 +137,7 @@ var AppView = Backbone.View.extend({ this.views.list.hide(); this.views.listDrag.hide(); this.views.details.hide(); + this.views.grp.hide(); this.hideOpenFile(); this.views.settings = new SettingsView(); this.views.settings.setElement(this.$el.find('.app__body')).render(); @@ -140,6 +148,13 @@ var AppView = Backbone.View.extend({ this.views.menu.switchVisibility(false); }, + showEditGroup: function() { + this.views.list.hide(); + this.views.listDrag.hide(); + this.views.details.hide(); + this.views.grp.show(); + }, + fileListUpdated: function() { if (this.model.files.hasOpenFiles()) { this.showEntries(); @@ -193,8 +208,15 @@ var AppView = Backbone.View.extend({ } }, - menuSelect: function(item) { - this.model.menu.select(item); + selectAll: function() { + this.menuSelect({ item: this.model.menu.allItemsSection.get('items').first() }); + }, + + menuSelect: function(opt) { + this.model.menu.select(opt); + if (!this.views.grp.isHidden()) { + this.showEntries(); + } }, lockWorkspace: function() { @@ -261,6 +283,15 @@ var AppView = Backbone.View.extend({ this.views.menu.switchVisibility(false); }, + editGroup: function(group) { + if (group && this.views.grp.isHidden()) { + this.showEditGroup(); + this.views.grp.showGroup(group); + } else { + this.showEntries(); + } + }, + contextmenu: function(e) { if (['input', 'textarea'].indexOf(e.target.tagName.toLowerCase()) < 0) { e.preventDefault(); diff --git a/app/scripts/views/details/details-view.js b/app/scripts/views/details/details-view.js index 9087958d..1322ef9f 100644 --- a/app/scripts/views/details/details-view.js +++ b/app/scripts/views/details/details-view.js @@ -9,7 +9,7 @@ var Backbone = require('backbone'), FieldViewReadOnly = require('../fields/field-view-read-only'), FieldViewHistory = require('../fields/field-view-history'), FieldViewCustom = require('../fields/field-view-custom'), - DetailsIconView = require('./details-icon-view'), + IconSelectView = require('../icon-select-view'), DetailsHistoryView = require('./details-history-view'), DetailsAttachmentView = require('./details-attachment-view'), Keys = require('../../const/keys'), @@ -175,12 +175,12 @@ var DetailsView = Backbone.View.extend({ }, toggleIcons: function() { - if (this.views.sub && this.views.sub instanceof DetailsIconView) { + if (this.views.sub && this.views.sub instanceof IconSelectView) { this.render(); return; } this.removeSubView(); - var subView = new DetailsIconView({ el: this.scroller, model: this.model }); + var subView = new IconSelectView({ el: this.scroller, model: this.model }); this.listenTo(subView, 'select', this.iconSelected); subView.render(); this.pageResized(); diff --git a/app/scripts/views/grp-view.js b/app/scripts/views/grp-view.js new file mode 100644 index 00000000..b54a7e56 --- /dev/null +++ b/app/scripts/views/grp-view.js @@ -0,0 +1,98 @@ +'use strict'; + +var Backbone = require('backbone'), + Scrollable = require('../mixins/scrollable'), + IconSelectView = require('./icon-select-view'), + baron = require('baron'); + +var GrpView = Backbone.View.extend({ + template: require('templates/grp.html'), + + events: { + 'click .grp__icon': 'showIconsSelect', + 'click .grp__buttons-trash': 'moveToTrash', + 'blur #grp__field-title': 'titleBlur' + }, + + initialize: function() { + this.views = {}; + }, + + render: function() { + this.removeSubView(); + if (this.model) { + this.$el.html(this.template({ + title: this.model.get('title'), + icon: this.model.get('icon') || 'folder', + readonly: this.model.get('top') + })); + } + this.scroll = baron({ + root: this.$el.find('.details__body')[0], + scroller: this.$el.find('.scroller')[0], + bar: this.$el.find('.scroller__bar')[0], + $: Backbone.$ + }); + this.scroller = this.$el.find('.scroller'); + this.scrollerBar = this.$el.find('.scroller__bar'); + this.scrollerBarWrapper = this.$el.find('.scroller__bar-wrapper'); + this.pageResized(); + return this; + }, + + removeSubView: function() { + if (this.views.sub) { + this.views.sub.remove(); + delete this.views.sub; + } + }, + + showGroup: function(group) { + this.model = group; + this.render(); + }, + + titleBlur: function(e) { + var title = $.trim(e.target.value); + if (title) { + if (!this.model.get('top') && e.target.value !== this.model.get('title')) { + this.model.setName(e.target.value); + } + } else { + if (this.model.isJustCreated) { + this.model.removeWithoutHistory(); + Backbone.trigger('edit-group'); + } else { + this.render(); + } + } + }, + + showIconsSelect: function() { + if (this.views.sub) { + this.removeSubView(); + } else { + var subView = new IconSelectView({el: this.$el.find('.grp__icons'), model: {iconId: this.model.get('iconId')}}); + this.listenTo(subView, 'select', this.iconSelected); + subView.render(); + this.views.sub = subView; + } + this.pageResized(); + }, + + iconSelected: function(iconId) { + if (iconId !== this.model.get('iconId')) { + this.model.setIcon(iconId); + } + this.render(); + }, + + moveToTrash: function() { + this.model.moveToTrash(); + Backbone.trigger('select-all'); + } +}); + +_.extend(GrpView.prototype, Scrollable); + +module.exports = GrpView; diff --git a/app/scripts/views/details/details-icon-view.js b/app/scripts/views/icon-select-view.js similarity index 64% rename from app/scripts/views/details/details-icon-view.js rename to app/scripts/views/icon-select-view.js index 30734029..37e4c09e 100644 --- a/app/scripts/views/details/details-icon-view.js +++ b/app/scripts/views/icon-select-view.js @@ -1,13 +1,13 @@ 'use strict'; var Backbone = require('backbone'), - IconMap = require('../../const/icon-map'); + IconMap = require('../const/icon-map'); -var DetailsIconView = Backbone.View.extend({ - template: require('templates/details/details-icon.html'), +var IconSelectView = Backbone.View.extend({ + template: require('templates/icon-select.html'), events: { - 'click .details__icons-icon': 'iconClick' + 'click .icon-select__icon': 'iconClick' }, render: function() { @@ -26,4 +26,4 @@ var DetailsIconView = Backbone.View.extend({ } }); -module.exports = DetailsIconView; +module.exports = IconSelectView; diff --git a/app/scripts/views/list-view.js b/app/scripts/views/list-view.js index fc4c283a..0176a965 100644 --- a/app/scripts/views/list-view.js +++ b/app/scripts/views/list-view.js @@ -31,6 +31,7 @@ var ListView = Backbone.View.extend({ this.listenTo(this.views.search, 'select-prev', this.selectPrev); this.listenTo(this.views.search, 'select-next', this.selectNext); this.listenTo(this.views.search, 'create-entry', this.createEntry); + this.listenTo(this.views.search, 'create-group', this.createGroup); this.listenTo(this, 'show', this.viewShown); this.listenTo(this, 'hide', this.viewHidden); this.listenTo(Backbone, 'filter', this.filterChanged); @@ -105,6 +106,11 @@ var ListView = Backbone.View.extend({ this.selectItem(newEntry); }, + createGroup: function() { + var newGroup = this.model.createNewGroup(); + Backbone.trigger('edit-group', newGroup); + }, + selectItem: function(item) { this.items.setActive(item); Backbone.trigger('select-entry', item); diff --git a/app/scripts/views/menu/menu-item-view.js b/app/scripts/views/menu/menu-item-view.js index 02eba770..abcb793e 100644 --- a/app/scripts/views/menu/menu-item-view.js +++ b/app/scripts/views/menu/menu-item-view.js @@ -13,7 +13,8 @@ var MenuItemView = Backbone.View.extend({ 'mouseout': 'mouseout', 'click .menu__item-option': 'selectOption', 'click': 'selectItem', - 'dblclick': 'expandItem' + 'dblclick': 'expandItem', + 'click .menu__item-edit': 'editItem' }, iconEl: null, @@ -22,9 +23,12 @@ var MenuItemView = Backbone.View.extend({ initialize: function () { this.itemViews = []; this.listenTo(this.model, 'change:title', this.changeTitle); + this.listenTo(this.model, 'change:icon', this.changeIcon); this.listenTo(this.model, 'change:active', this.changeActive); this.listenTo(this.model, 'change:expanded', this.changeExpanded); this.listenTo(this.model, 'change:cls', this.changeCls); + this.listenTo(this.model, 'delete', this.remove); + this.listenTo(this.model, 'insert', this.insertItem); var shortcut = this.model.get('shortcut'); if (shortcut) { KeyHandler.onKey(shortcut, this.selectItem, this, KeyHandler.SHORTCUT_OPT); @@ -42,15 +46,16 @@ var MenuItemView = Backbone.View.extend({ if (items && this.model.get('expanded')) { items.forEach(function (item) { if (item.get('visible')) { - var itemView = new MenuItemView({el: this.$el, model: item}); - itemView.listenTo(itemView, 'select', this.itemSelected); - itemView.render(); - this.itemViews.push(itemView); + this.insertItem(item); } }, this); } }, + insertItem: function(item) { + this.itemViews.push(new MenuItemView({el: this.$el, model: item}).render()); + }, + remove : function() { this.removeInnerViews(); var shortcut = this.model.get('shortcut'); @@ -69,7 +74,11 @@ var MenuItemView = Backbone.View.extend({ }, changeTitle: function(model, title) { - this.$el.find('.menu__item-title').text(title); + this.$el.find('.menu__item-title').first().text(title); + }, + + changeIcon: function(model, icon) { + this.iconEl[0].className = 'menu__item-icon fa ' + (icon ? 'fa-' + icon : 'menu__item-icon--no-icon'); }, changeActive: function(model, active) { @@ -129,6 +138,13 @@ var MenuItemView = Backbone.View.extend({ this.model.toggleExpanded(); } e.stopPropagation(); + }, + + editItem: function(e) { + if (this.model.get('active') && this.model.get('editable')) { + e.stopPropagation(); + Backbone.trigger('edit-group', this.model); + } } }); diff --git a/app/styles/areas/_app.scss b/app/styles/areas/_app.scss index 166c3081..425d416b 100644 --- a/app/styles/areas/_app.scss +++ b/app/styles/areas/_app.scss @@ -66,6 +66,17 @@ } } + &__grp { + @include flex(1); + @include display(flex); + overflow: hidden; + padding: $base-spacing; + position: relative; + @include mobile { + padding: $base-padding; + } + } + &__footer { @include flex(0 0 auto); @include th { border-top: light-border(); } diff --git a/app/styles/areas/_details.scss b/app/styles/areas/_details.scss index 3e2430fa..2f4c9e28 100644 --- a/app/styles/areas/_details.scss +++ b/app/styles/areas/_details.scss @@ -365,25 +365,6 @@ } } - &__icons { - @include display(flex); - @include align-items(flex-start); - @include flex-direction(row); - @include justify-content(flex-start); - @include flex-wrap(wrap); - @include user-select(none); - &-icon { - @include area-selectable(bottom); - width: 26px; - text-align: center; - font-size: 20px; - padding: 10px; - &.details__icons-icon--active { - @include area-selected(bottom); - } - } - } - &__history { @include flex(1 0 auto); @include display(flex); diff --git a/app/styles/areas/_grp.scss b/app/styles/areas/_grp.scss new file mode 100644 index 00000000..ecfc93b5 --- /dev/null +++ b/app/styles/areas/_grp.scss @@ -0,0 +1,38 @@ +.grp { + @include flex(1); + @include display(flex); + @include align-items(stretch); + @include flex-direction(column); + @include justify-content(flex-start); + @include scrollbar-on-hover; + width: 100%; + user-select: none; + + >.scroller { + @include flex(1); + @include display(flex); + @include align-items(stretch); + @include flex-direction(column); + @include justify-content(flex-start); + overflow-x: hidden; + padding-top: 3px; + } + + &__icon { + display: block; + font-size: $large-header-font-size; + padding: $base-padding-px; + @include align-self(flex-start); + @include area-selectable(); + } + + &__buttons { + @include display(flex); + @include flex-direction(row); + margin-top: $base-padding-v; + + &-trash { + @include icon-btn($error:true); + } + } +} diff --git a/app/styles/areas/_menu.scss b/app/styles/areas/_menu.scss index b453443e..81735bd2 100644 --- a/app/styles/areas/_menu.scss +++ b/app/styles/areas/_menu.scss @@ -44,6 +44,7 @@ &__item { text-overflow: ellipsis; overflow: hidden; + position: relative; @include display(flex); @include align-items(stretch); @include flex-direction(column); @@ -121,6 +122,26 @@ } } + &-edit { + display: none; + opacity: 0; + position: absolute; + right: .8em; + top: .6em; + cursor: pointer; + transition: opacity $base-duration $base-timing, color $base-duration $base-timing; + @include th { + color: muted-color(); + &:hover { color: medium-color(); } + } + .menu__item--active>.menu__item-body>& { + display: block; + } + .menu__item--active>.menu__item-body:hover>& { + opacity: .5; + } + } + .fa { margin-right: $base-padding-h / 2; } diff --git a/app/styles/areas/_settings.scss b/app/styles/areas/_settings.scss index 703fc2d9..ea15d689 100644 --- a/app/styles/areas/_settings.scss +++ b/app/styles/areas/_settings.scss @@ -53,13 +53,6 @@ } } - &__select, &__input, &__pre, &__file-master-pass-label { - width: 60%; - @include tablet { - width: calc(100% - 20px); - } - } - &__select, &__input { height: 2em; } diff --git a/app/styles/base/_forms.scss b/app/styles/base/_forms.scss index 92167662..45a40edf 100644 --- a/app/styles/base/_forms.scss +++ b/app/styles/base/_forms.scss @@ -243,3 +243,12 @@ $thumb-size: 14px; @include th { background: text-color(); } } } + + + +.input-base { + width: 60%; + @include tablet { + width: calc(100% - 20px); + } +} diff --git a/app/styles/common/_icon-select.scss b/app/styles/common/_icon-select.scss new file mode 100644 index 00000000..fdb1c666 --- /dev/null +++ b/app/styles/common/_icon-select.scss @@ -0,0 +1,18 @@ +&.icon-select { + @include display(flex); + @include align-items(flex-start); + @include flex-direction(row); + @include justify-content(flex-start); + @include flex-wrap(wrap); + @include user-select(none); + &__icon { + @include area-selectable(bottom); + width: 26px; + text-align: center; + font-size: 20px; + padding: 10px; + &.icon-select__icon--active { + @include area-selected(bottom); + } + } +} diff --git a/app/styles/main.scss b/app/styles/main.scss index 63550d73..b89e655f 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -16,12 +16,14 @@ @import "common/empty"; @import "common/fx"; @import "common/help-tip"; +@import "common/icon-select"; @import "common/modal"; @import "common/scroll"; @import "areas/app"; @import "areas/details"; @import "areas/footer"; +@import "areas/grp"; @import "areas/generator"; @import "areas/list"; @import "areas/menu"; diff --git a/app/templates/app.html b/app/templates/app.html index 4f7dc131..229a28d2 100644 --- a/app/templates/app.html +++ b/app/templates/app.html @@ -5,6 +5,7 @@
+
diff --git a/app/templates/details/details-icon.html b/app/templates/details/details-icon.html deleted file mode 100644 index cfc0ea22..00000000 --- a/app/templates/details/details-icon.html +++ /dev/null @@ -1,5 +0,0 @@ -
- <% icons.forEach(function(icon, ix) { %> - - <% }); %> -
\ No newline at end of file diff --git a/app/templates/grp.html b/app/templates/grp.html new file mode 100644 index 00000000..dada3705 --- /dev/null +++ b/app/templates/grp.html @@ -0,0 +1,19 @@ +
+
+

Group

+
+ + /> +
+ + +
+
+
+ <% if (!readonly) { %> +
+ +
+ <% } %> +
diff --git a/app/templates/icon-select.html b/app/templates/icon-select.html new file mode 100644 index 00000000..9e93294d --- /dev/null +++ b/app/templates/icon-select.html @@ -0,0 +1,5 @@ +
+ <% icons.forEach(function(icon, ix) { %> + + <% }); %> +
diff --git a/app/templates/menu/menu-item.html b/app/templates/menu/menu-item.html index d0cee37d..d671782e 100644 --- a/app/templates/menu/menu-item.html +++ b/app/templates/menu/menu-item.html @@ -14,5 +14,8 @@ <% }); %> <% } %> - + <% if (typeof editable !== 'undefined') { %> + + <% } %> + diff --git a/app/templates/settings/settings-file.html b/app/templates/settings/settings-file.html index a52dc2e4..09817942 100644 --- a/app/templates/settings/settings-file.html +++ b/app/templates/settings/settings-file.html @@ -24,34 +24,34 @@

Settings

-