keeweb/app/scripts/views/list-search-view.js

434 lines
14 KiB
JavaScript

import { View } from 'framework/views/view';
import { Events } from 'framework/events';
import { Shortcuts } from 'comp/app/shortcuts';
import { KeyHandler } from 'comp/browser/key-handler';
import { Keys } from 'const/keys';
import { Comparators } from 'util/data/comparators';
import { Features } from 'util/features';
import { StringFormat } from 'util/formatting/string-format';
import { Locale } from 'util/locale';
import { DropdownView } from 'views/dropdown-view';
import template from 'templates/list-search.hbs';
class ListSearchView extends View {
parent = '.list__header';
template = template;
events = {
'keydown .list__search-field': 'inputKeyDown',
'keypress .list__search-field': 'inputKeyPress',
'input .list__search-field': 'inputChange',
'focus .list__search-field': 'inputFocus',
'click .list__search-btn-new': 'createOptionsClick',
'click .list__search-btn-sort': 'sortOptionsClick',
'click .list__search-icon-search': 'advancedSearchClick',
'click .list__search-btn-menu': 'toggleMenu',
'change .list__search-adv input[type=checkbox]': 'toggleAdvCheck'
};
inputEl = null;
sortOptions = null;
sortIcons = null;
createOptions = null;
advancedSearchEnabled = false;
advancedSearch = null;
constructor(model) {
super(model);
this.sortOptions = [
{
value: 'title',
icon: 'sort-alpha-asc',
loc: () =>
StringFormat.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchAZ)
},
{
value: '-title',
icon: 'sort-alpha-desc',
loc: () =>
StringFormat.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchZA)
},
{
value: 'website',
icon: 'sort-alpha-asc',
loc: () =>
StringFormat.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchAZ)
},
{
value: '-website',
icon: 'sort-alpha-desc',
loc: () =>
StringFormat.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchZA)
},
{
value: 'user',
icon: 'sort-alpha-asc',
loc: () => StringFormat.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchAZ)
},
{
value: '-user',
icon: 'sort-alpha-desc',
loc: () => StringFormat.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchZA)
},
{
value: 'created',
icon: 'sort-numeric-asc',
loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchON)
},
{
value: '-created',
icon: 'sort-numeric-desc',
loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO)
},
{
value: 'updated',
icon: 'sort-numeric-asc',
loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON)
},
{
value: '-updated',
icon: 'sort-numeric-desc',
loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO)
},
{
value: '-attachments',
icon: 'sort-amount-desc',
loc: () => Locale.searchAttachments
},
{ value: '-rank', icon: 'sort-amount-desc', loc: () => Locale.searchRank }
];
this.sortIcons = {};
this.sortOptions.forEach((opt) => {
this.sortIcons[opt.value] = opt.icon;
});
this.advancedSearch = {
user: true,
other: true,
url: true,
protect: false,
notes: true,
pass: false,
cs: false,
regex: false,
history: false,
title: true
};
if (this.model.advancedSearch) {
this.advancedSearch = { ...this.model.advancedSearch };
}
this.setLocale();
this.onKey(Keys.DOM_VK_F, this.findKeyPress, KeyHandler.SHORTCUT_ACTION);
this.onKey(Keys.DOM_VK_N, this.newKeyPress, KeyHandler.SHORTCUT_OPT);
this.onKey(Keys.DOM_VK_DOWN, this.downKeyPress);
this.onKey(Keys.DOM_VK_UP, this.upKeyPress);
this.listenTo(this, 'show', this.viewShown);
this.listenTo(this, 'hide', this.viewHidden);
this.listenTo(Events, 'filter', this.filterChanged);
this.listenTo(Events, 'set-locale', this.setLocale);
this.listenTo(Events, 'page-blur', this.pageBlur);
this.listenTo(this.model.files, 'change', this.fileListUpdated);
this.once('remove', () => {
this.removeKeypressHandler();
});
}
setLocale() {
this.sortOptions.forEach((opt) => {
opt.text = opt.loc();
});
this.createOptions = [
{
value: 'entry',
icon: 'key',
text: StringFormat.capFirst(Locale.entry),
hint: Features.isMobile
? null
: `(${Locale.searchShiftClickOr} ${Shortcuts.altShortcutSymbol(true)})`
},
{ value: 'group', icon: 'folder', text: StringFormat.capFirst(Locale.group) }
];
if (this.el) {
this.render();
}
}
pageBlur() {
this.inputEl.blur();
}
removeKeypressHandler() {}
viewShown() {
const keypressHandler = (e) => this.documentKeyPress(e);
Events.on('keypress', keypressHandler);
this.removeKeypressHandler = () => Events.off('keypress', keypressHandler);
}
viewHidden() {
this.removeKeypressHandler();
}
render() {
let searchVal;
if (this.inputEl) {
searchVal = this.inputEl.val();
}
super.render({
adv: this.advancedSearch,
advEnabled: this.advancedSearchEnabled,
canCreate: this.model.canCreateEntries()
});
this.inputEl = this.$el.find('.list__search-field');
if (searchVal) {
this.inputEl.val(searchVal);
}
}
inputKeyDown(e) {
switch (e.which) {
case Keys.DOM_VK_UP:
case Keys.DOM_VK_DOWN:
break;
case Keys.DOM_VK_RETURN:
e.target.blur();
break;
case Keys.DOM_VK_ESCAPE:
if (this.inputEl.val()) {
this.inputEl.val('');
this.inputChange();
}
e.target.blur();
break;
default:
return;
}
e.preventDefault();
}
inputKeyPress(e) {
e.stopPropagation();
}
inputChange() {
Events.emit('add-filter', { text: this.inputEl.val() });
}
inputFocus(e) {
$(e.target).select();
}
documentKeyPress(e) {
if (this.hidden) {
return;
}
const code = e.charCode;
if (!code) {
return;
}
this.hideSearchOptions();
this.inputEl.val(String.fromCharCode(code)).focus();
this.inputEl[0].setSelectionRange(1, 1);
this.inputChange();
e.preventDefault();
}
findKeyPress(e) {
if (!this.hidden) {
e.preventDefault();
this.hideSearchOptions();
this.inputEl.select().focus();
}
}
newKeyPress(e) {
if (!this.hidden) {
e.preventDefault();
this.hideSearchOptions();
this.emit('create-entry');
}
}
downKeyPress(e) {
e.preventDefault();
this.hideSearchOptions();
this.emit('select-next');
}
upKeyPress(e) {
e.preventDefault();
this.hideSearchOptions();
this.emit('select-prev');
}
filterChanged(filter) {
this.hideSearchOptions();
if (filter.filter.text !== this.inputEl.val()) {
this.inputEl.val(filter.text || '');
}
const sortIconCls = this.sortIcons[filter.sort] || 'sort';
this.$el.find('.list__search-btn-sort>i').attr('class', 'fa fa-' + sortIconCls);
let adv = !!filter.filter.advanced;
if (this.model.advancedSearch) {
adv = filter.filter.advanced !== this.model.advancedSearch;
}
if (this.advancedSearchEnabled !== adv) {
this.advancedSearchEnabled = adv;
this.$el.find('.list__search-adv').toggleClass('hide', !this.advancedSearchEnabled);
}
}
createOptionsClick(e) {
e.stopImmediatePropagation();
if (e.shiftKey) {
this.hideSearchOptions();
this.emit('create-entry');
return;
}
this.toggleCreateOptions();
}
sortOptionsClick(e) {
this.toggleSortOptions();
e.stopImmediatePropagation();
}
advancedSearchClick() {
this.advancedSearchEnabled = !this.advancedSearchEnabled;
this.$el.find('.list__search-adv').toggleClass('hide', !this.advancedSearchEnabled);
let advanced = false;
if (this.advancedSearchEnabled) {
advanced = this.advancedSearch;
} else if (this.model.advancedSearch) {
advanced = this.model.advancedSearch;
}
Events.emit('add-filter', { advanced });
}
toggleMenu() {
Events.emit('toggle-menu');
}
toggleAdvCheck(e) {
const setting = $(e.target).data('id');
this.advancedSearch[setting] = e.target.checked;
Events.emit('add-filter', { advanced: this.advancedSearch });
}
hideSearchOptions() {
if (this.views.searchDropdown) {
this.views.searchDropdown.remove();
this.views.searchDropdown = null;
this.$el
.find('.list__search-btn-sort,.list__search-btn-new')
.removeClass('sel--active');
}
}
toggleSortOptions() {
if (this.views.searchDropdown && this.views.searchDropdown.isSort) {
this.hideSearchOptions();
return;
}
this.hideSearchOptions();
this.$el.find('.list__search-btn-sort').addClass('sel--active');
const view = new DropdownView();
view.isSort = true;
this.listenTo(view, 'cancel', this.hideSearchOptions);
this.listenTo(view, 'select', this.sortDropdownSelect);
this.sortOptions.forEach(function (opt) {
opt.active = this.model.sort === opt.value;
}, this);
view.render({
position: {
top: this.$el.find('.list__search-btn-sort')[0].getBoundingClientRect().bottom,
right: this.$el[0].getBoundingClientRect().right + 1
},
options: this.sortOptions
});
this.views.searchDropdown = view;
}
toggleCreateOptions() {
if (this.views.searchDropdown && this.views.searchDropdown.isCreate) {
this.hideSearchOptions();
return;
}
this.hideSearchOptions();
this.$el.find('.list__search-btn-new').addClass('sel--active');
const view = new DropdownView();
view.isCreate = true;
this.listenTo(view, 'cancel', this.hideSearchOptions);
this.listenTo(view, 'select', this.createDropdownSelect);
view.render({
position: {
top: this.$el.find('.list__search-btn-new')[0].getBoundingClientRect().bottom,
right: this.$el[0].getBoundingClientRect().right + 1
},
options: this.createOptions.concat(this.getCreateEntryTemplateOptions())
});
this.views.searchDropdown = view;
}
getCreateEntryTemplateOptions() {
const entryTemplates = this.model.getEntryTemplates();
const hasMultipleFiles = this.model.files.length > 1;
this.entryTemplates = {};
const options = [];
entryTemplates.forEach((tmpl) => {
const id = 'tmpl:' + tmpl.entry.id;
options.push({
value: id,
icon: tmpl.entry.icon,
text: hasMultipleFiles
? tmpl.file.name + ' / ' + tmpl.entry.title
: tmpl.entry.title
});
this.entryTemplates[id] = tmpl;
});
options.sort(Comparators.stringComparator('text', true));
options.push({
value: 'tmpl',
icon: 'sticky-note-o',
text: StringFormat.capFirst(Locale.template)
});
return options;
}
sortDropdownSelect(e) {
this.hideSearchOptions();
Events.emit('set-sort', e.item);
}
createDropdownSelect(e) {
this.hideSearchOptions();
switch (e.item) {
case 'entry':
this.emit('create-entry');
break;
case 'group':
this.emit('create-group');
break;
case 'tmpl':
this.emit('create-template');
break;
default:
if (this.entryTemplates[e.item]) {
this.emit('create-entry', { template: this.entryTemplates[e.item] });
}
}
}
addArrow(str) {
return str.replace('{}', '→');
}
fileListUpdated() {
this.render();
}
}
export { ListSearchView };