mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-12-02 05:43:40 +08:00
b649738718
- 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.
178 lines
5.1 KiB
JavaScript
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;
|
|
}
|
|
|
|
} |