mirror of https://github.com/MISP/MISP
- Added keyboard navigation with arrows/pageUp/pageDown/enter for tag selection ( Issue #3001 )
- The color when hovering over a modal element is now grey, to differentiate from blue when choosing tags using keyboardpull/3012/head
parent
6e1528db1a
commit
3510f7b745
|
@ -18,7 +18,7 @@
|
|||
<div class="popover_choice_main" id ="popover_choice_main">
|
||||
<table style="width:100%;">
|
||||
<?php foreach ($options as $k => &$option): ?>
|
||||
<tr style="border-top:1px solid black;" class="templateChoiceButton" id="field_<?php echo h($k); ?>">
|
||||
<tr style="border-top:1px solid black;" class="templateChoiceButton shown" id="field_<?php echo h($k); ?>">
|
||||
<?php if (isset($attributeTag)): ?>
|
||||
<td style="padding-left:10px;padding-right:10px; text-align:center;width:100%;" onClick="quickSubmitAttributeTagForm('<?php echo h($object_id);?>', '<?php echo h($k); ?>');" title="<?php echo h($expanded[$k]);?>" role="button" tabindex="0" aria-label="Attach tag <?php echo h($option); ?>"><?php echo h($option); ?></td>
|
||||
<?php else: ?>
|
||||
|
@ -48,9 +48,13 @@
|
|||
var filterString = $("#filterField").val().toLowerCase();
|
||||
$.each(tags, function(index, value) {
|
||||
if (value.toLowerCase().indexOf(filterString) == -1) {
|
||||
$('#field_' + index).hide();
|
||||
let element = $('#field_' + index);
|
||||
element.hide();
|
||||
element.removeClass('shown');
|
||||
} else {
|
||||
$('#field_' + index).show();
|
||||
let element = $('#field_' + index);
|
||||
element.show();
|
||||
element.addClass('shown');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -60,3 +64,4 @@
|
|||
resizePopoverBody();
|
||||
});
|
||||
</script>
|
||||
<?php echo $this->Html->script('tag-selection-keyboard-navigation.js'); ?>
|
|
@ -1430,6 +1430,11 @@ a.proposal_link_red:hover {
|
|||
}
|
||||
|
||||
.templateChoiceButton:hover {
|
||||
background-color:#bbb !important;
|
||||
color:white;
|
||||
}
|
||||
|
||||
.selected-tag {
|
||||
background-color:#0088cc !important;
|
||||
color:white;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
/**
|
||||
* This classes deals with the front-end keyboard shortcuts and is included in every page.
|
||||
* This object deals with the front-end keyboard shortcuts and is included in every page.
|
||||
*/
|
||||
let keyboardShortcutsManager = {
|
||||
|
||||
NAVIGATION_KEYS: ["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Enter"],
|
||||
EVENTS: {
|
||||
"ArrowUp": "upArrowPressed",
|
||||
"ArrowDown": "downArrowPressed",
|
||||
"Enter": "enterPressed",
|
||||
"PageUp": "pageUpPressed",
|
||||
"PageDown": "pageDownPressed"
|
||||
},
|
||||
ESCAPED_TAG_NAMES: ["INPUT", "TEXTAREA", "SELECT"],
|
||||
shortcutKeys: new Map(),
|
||||
shortcutListToggled: false,
|
||||
escapedTagNames: ["INPUT", "TEXTAREA", "SELECT"],
|
||||
|
||||
/**
|
||||
* Fetches the keyboard shortcut config files and populates this.shortcutJSON.
|
||||
|
@ -59,14 +67,18 @@ let keyboardShortcutsManager = {
|
|||
|
||||
/**
|
||||
* Sets the event to listen to and the routine to call on keypress.
|
||||
* If it's a shortcut key, execute its code. If its a navigation
|
||||
* key, trigger an event depending on the key.
|
||||
*/
|
||||
setKeyboardListener() {
|
||||
window.onkeyup = (keyboardEvent) => {
|
||||
if(this.shortcutKeys.has(keyboardEvent.key)) {
|
||||
let activeElement = document.activeElement.tagName;
|
||||
if( !this.escapedTagNames.includes(activeElement)) {
|
||||
if( !this.ESCAPED_TAG_NAMES.includes(activeElement)) {
|
||||
eval(this.shortcutKeys.get(keyboardEvent.key).action);
|
||||
}
|
||||
} else if(this.NAVIGATION_KEYS.includes(keyboardEvent.key)) {
|
||||
window.dispatchEvent(new CustomEvent(this.EVENTS[keyboardEvent.key], {detail: keyboardEvent}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* This object deals with keyboard navigation when choosing a tag using the "Select Tag" modal.
|
||||
* It relies on events dispatched by "keyboard-shortcuts.js".
|
||||
*/
|
||||
let keyboardTagSelection = {
|
||||
TAG_CLASS: "#popover_choice_main .templateChoiceButton.shown",
|
||||
DISPLAYED_TAGS_CLASS: "shown",
|
||||
KEYS: ["ArrowUp", "ArrowDown", "Enter"],
|
||||
SELECTED_TAG_CLASS: "selected-tag",
|
||||
PAGE_OFFSET: 5,
|
||||
selectedElement: null,
|
||||
selectedElementIndex: -1,
|
||||
elements: $(this.TAG_CLASS),
|
||||
oldElementLength: 0,
|
||||
|
||||
/**
|
||||
* Sets the event to listen to and the routine to call on keypress, if not already done.
|
||||
*/
|
||||
init() {
|
||||
if(!window.tagSelectionPopupLoaded) {
|
||||
window.tagSelectionPopupLoaded = true;
|
||||
window.addEventListener('upArrowPressed', () => this.onUpArrowPress());
|
||||
window.addEventListener('downArrowPressed', () => this.onDownArrowPress());
|
||||
window.addEventListener('pageUpPressed', () => this.onPageUpPress());
|
||||
window.addEventListener('pageDownPressed', () => this.onPageDownPress());
|
||||
window.addEventListener('enterPressed', () => this.onEnterPress());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the previous tag in the list. If reached the beginning of the list,
|
||||
* select the last element.
|
||||
*/
|
||||
onUpArrowPress() {
|
||||
this.oldElementLength = this.elements.length;
|
||||
this.elements = $(this.TAG_CLASS);
|
||||
if(this.oldElementLength != this.elements.length) {
|
||||
if(!this.selectedElement || !this.selectedElement.hasClass(this.DISPLAYED_TAGS_CLASS)) {
|
||||
if(this.elements.length > 0) {
|
||||
this.selectPreviousTag();
|
||||
}
|
||||
} else {
|
||||
this.selectLastTag();
|
||||
}
|
||||
} else {
|
||||
this.selectPreviousTag();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the next tag in the list. If reached the end of the list,
|
||||
* select the first element.
|
||||
*/
|
||||
onDownArrowPress() {
|
||||
this.oldElementLength = this.elements.length;
|
||||
this.elements = $(this.TAG_CLASS);
|
||||
if(this.oldElementLength != this.elements.length) {
|
||||
if(!this.selectedElement || !this.selectedElement.hasClass(this.DISPLAYED_TAGS_CLASS)) {
|
||||
if(this.elements.length > 0) {
|
||||
this.selectNextTag();
|
||||
}
|
||||
} else {
|
||||
this.selectFirstTag();
|
||||
}
|
||||
} else {
|
||||
this.selectNextTag();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects a tag at several previous position (say, from index 15 to index 9).
|
||||
* Circular, so if we reach beginning of the list, goes at the end.
|
||||
*/
|
||||
onPageUpPress() {
|
||||
this.selectedElementIndex -= this.PAGE_OFFSET;
|
||||
this.onUpArrowPress();
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects a tag at several next position (say, from index 9 to index 15).
|
||||
* Circular, so if we reach beginning of the list, goes at the beginning.
|
||||
*/
|
||||
onPageDownPress() {
|
||||
this.selectedElementIndex += this.PAGE_OFFSET;
|
||||
this.onDownArrowPress();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clicks on the selected element's child if the selected element exists.
|
||||
*/
|
||||
onEnterPress() {
|
||||
if(this.selectedElement && document.contains(this.selectedElement[0])) {
|
||||
this.selectedElement.children().click();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the "selected" class from the old selected element (if it exists)
|
||||
* and sets the "selected" class on newSelectedElement. Also deal with scrolling
|
||||
* of the tag modal.
|
||||
* @param {Element} index The index of the selected tag in this.elements.
|
||||
*/
|
||||
updateSelectedElement(index) {
|
||||
if(this.selectedElement) {
|
||||
this.selectedElement.removeClass(this.SELECTED_TAG_CLASS);
|
||||
}
|
||||
this.selectedElement = $(this.elements[index]);
|
||||
this.selectedElementIndex = index;
|
||||
this.selectedElement.addClass(this.SELECTED_TAG_CLASS);
|
||||
let container = $('#popover_choice_main');
|
||||
container.animate({
|
||||
scrollTop: this.selectedElement.offset().top - $('#popover_choice_main').offset().top
|
||||
}, 100);
|
||||
},
|
||||
|
||||
/**
|
||||
* If there is at least a tag displayed, select the first of them.
|
||||
*/
|
||||
selectFirstTag() {
|
||||
if(this.elements.length > 0) {
|
||||
this.updateSelectedElement(0);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If there is at least a tag displayed, select the last of them.
|
||||
*/
|
||||
selectLastTag() {
|
||||
if(this.elements.length > 0) {
|
||||
this.updateSelectedElement(this.elements.length - 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the previous tag, or the last tag if we're at the top of the list.
|
||||
*/
|
||||
selectPreviousTag() {
|
||||
let newIndex;
|
||||
if(this.selectedElementIndex <= 0) {
|
||||
newIndex = this.elements.length - 1;
|
||||
} else {
|
||||
newIndex = this.selectedElementIndex - 1;
|
||||
}
|
||||
this.updateSelectedElement(newIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the next tag, or the first tag if we're at the bottom of the list.
|
||||
*/
|
||||
selectNextTag() {
|
||||
let newIndex;
|
||||
if(this.selectedElementIndex >= this.elements.length - 1) {
|
||||
newIndex = 0;
|
||||
} else {
|
||||
newIndex = this.selectedElementIndex + 1;
|
||||
}
|
||||
this.updateSelectedElement(newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Inits the keyboard tag selection's main routine.
|
||||
$(document).ready(() => keyboardTagSelection.init());
|
Loading…
Reference in New Issue