BookStack/resources/js/components/entity-selector.js
Dan Brown b649738718
Made book-sort changes based on screen reader testing
- Removed having sort items in tabbing order since they have no action.
- Updated "show other books" list to add upon single selection since it
  was not clear how these were added (double press) without then seeing
the add button, and even then the add button would be after the scroll
list.
2023-01-27 17:06:39 +00:00

178 lines
5.1 KiB
JavaScript

import {onChildEvent} from "../services/dom";
import {Component} from "./component";
/**
* Entity Selector
*/
export class EntitySelector extends Component {
setup() {
this.elem = this.$el;
this.entityTypes = this.$opts.entityTypes || 'page,book,chapter';
this.entityPermission = this.$opts.entityPermission || 'view';
this.input = this.$refs.input;
this.searchInput = this.$refs.search;
this.loading = this.$refs.loading;
this.resultsContainer = this.$refs.results;
this.search = '';
this.lastClick = 0;
this.selectedItemData = null;
this.setupListeners();
this.showLoading();
this.initialLoad();
}
setupListeners() {
this.elem.addEventListener('click', this.onClick.bind(this));
let lastSearch = 0;
this.searchInput.addEventListener('input', event => {
lastSearch = Date.now();
this.showLoading();
setTimeout(() => {
if (Date.now() - lastSearch < 199) return;
this.searchEntities(this.searchInput.value);
}, 200);
});
this.searchInput.addEventListener('keydown', event => {
if (event.keyCode === 13) event.preventDefault();
});
// Keyboard navigation
onChildEvent(this.$el, '[data-entity-type]', 'keydown', (e, el) => {
if (e.ctrlKey && e.code === 'Enter') {
const form = this.$el.closest('form');
if (form) {
form.submit();
e.preventDefault();
return;
}
}
if (e.code === 'ArrowDown') {
this.focusAdjacent(true);
}
if (e.code === 'ArrowUp') {
this.focusAdjacent(false);
}
});
this.searchInput.addEventListener('keydown', e => {
if (e.code === 'ArrowDown') {
this.focusAdjacent(true);
}
})
}
focusAdjacent(forward = true) {
const items = Array.from(this.resultsContainer.querySelectorAll('[data-entity-type]'));
const selectedIndex = items.indexOf(document.activeElement);
const newItem = items[selectedIndex+ (forward ? 1 : -1)] || items[0];
if (newItem) {
newItem.focus();
}
}
reset() {
this.searchInput.value = '';
this.showLoading();
this.initialLoad();
}
focusSearch() {
this.searchInput.focus();
}
showLoading() {
this.loading.style.display = 'block';
this.resultsContainer.style.display = 'none';
}
hideLoading() {
this.loading.style.display = 'none';
this.resultsContainer.style.display = 'block';
}
initialLoad() {
window.$http.get(this.searchUrl()).then(resp => {
this.resultsContainer.innerHTML = resp.data;
this.hideLoading();
})
}
searchUrl() {
return `/search/entity-selector?types=${encodeURIComponent(this.entityTypes)}&permission=${encodeURIComponent(this.entityPermission)}`;
}
searchEntities(searchTerm) {
this.input.value = '';
const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`;
window.$http.get(url).then(resp => {
this.resultsContainer.innerHTML = resp.data;
this.hideLoading();
});
}
isDoubleClick() {
const now = Date.now();
const answer = now - this.lastClick < 300;
this.lastClick = now;
return answer;
}
onClick(event) {
const listItem = event.target.closest('[data-entity-type]');
if (listItem) {
event.preventDefault();
event.stopPropagation();
this.selectItem(listItem);
}
}
selectItem(item) {
const isDblClick = this.isDoubleClick();
const type = item.getAttribute('data-entity-type');
const id = item.getAttribute('data-entity-id');
const isSelected = (!item.classList.contains('selected') || isDblClick);
this.unselectAll();
this.input.value = isSelected ? `${type}:${id}` : '';
const link = item.getAttribute('href');
const name = item.querySelector('.entity-list-item-name').textContent;
const data = {id: Number(id), name: name, link: link};
if (isSelected) {
item.classList.add('selected');
this.selectedItemData = data;
} else {
window.$events.emit('entity-select-change', null)
}
if (!isDblClick && !isSelected) return;
if (isDblClick) {
this.confirmSelection(data);
}
if (isSelected) {
window.$events.emit('entity-select-change', data)
}
}
confirmSelection(data) {
window.$events.emit('entity-select-confirm', data);
}
unselectAll() {
const selected = this.elem.querySelectorAll('.selected');
for (const selectedElem of selected) {
selectedElem.classList.remove('selected', 'primary-background');
}
this.selectedItemData = null;
}
}