From 15bba03e1d681be32ac2256f8c225c5575624570 Mon Sep 17 00:00:00 2001 From: Robbie Antenesse Date: Fri, 10 May 2019 13:08:03 -0600 Subject: [PATCH] Add hotkeys --- index.html | 3 +- src/constants.js | 1 + src/js/displayToggles.js | 8 +++ src/js/hotkeys.js | 126 +++++++++++++++++++++++++++++++++++++++ src/js/render.js | 5 +- src/js/search.js | 12 ++++ src/js/setupListeners.js | 45 ++++---------- src/js/wordManagement.js | 28 ++++++++- 8 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 src/js/hotkeys.js diff --git a/index.html b/index.html index 7f3633e..1ea5648 100644 --- a/index.html +++ b/index.html @@ -168,8 +168,7 @@ diff --git a/src/constants.js b/src/constants.js index d1a7a58..50de91b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -56,6 +56,7 @@ export const DEFAULT_DICTIONARY = { export const DEFAULT_SETTINGS = { useIPAPronunciationField: true, + useHotkeys: true, }; export const DEFAULT_PAGE_SIZE = 50; diff --git a/src/js/displayToggles.js b/src/js/displayToggles.js index 13fe8d4..bf1d893 100644 --- a/src/js/displayToggles.js +++ b/src/js/displayToggles.js @@ -8,6 +8,14 @@ export function showSection(sectionName) { } } +export function hideDetailsPanel() { + document.getElementById('detailsPanel').style.display = 'none'; +} + +export function getIsDetailsPanelDisplayed() { + return document.getElementById('detailsPanel').style.display !== 'none'; +} + function showDescription() { const detailsPanel = document.getElementById('detailsPanel'); detailsPanel.style.display = 'block'; diff --git a/src/js/hotkeys.js b/src/js/hotkeys.js new file mode 100644 index 0000000..703ba5d --- /dev/null +++ b/src/js/hotkeys.js @@ -0,0 +1,126 @@ +import { confirmEditWord, submitWordForm } from "./wordManagement"; +import { showSection, getIsDetailsPanelDisplayed, hideDetailsPanel } from "./displayToggles"; +import { renderInfoModal, renderMaximizedTextbox } from "./render"; +import { showSearchModal, clearSearchText } from "./search"; +import { saveAndCloseSettingsModal, openSettingsModal } from "./settings"; +import { saveAndCloseEditModal, openEditModal } from "./dictionaryManagement"; + +export function enableHotKeys() { + document.addEventListener('keydown', hotKeyActions); +} + +export function disableHotKeys() { + document.removeEventListener('keydown', hotKeyActions); +} + +export function hotKeyActions(event) { + console.log(event); + + if (typeof event.key === 'undefined' || typeof event.ctrlKey === 'undefined' || typeof event.altKey === 'undefined') { + console.warn('Browser does not have required event properties for hotkeys.'); + disableHotKeys(); + return false; + } + + switch (event.key) { + case 'Escape': hideAllModals(); break; + case 'Return': + case 'Enter': { + if (event.ctrlKey) { + if (document.getElementById('settingsModal').style.display !== 'none') { + saveAndCloseSettingsModal(); + } else if (document.getElementById('editModal').style.display !== 'none') { + saveAndCloseEditModal(); + } else { + submitWord(); + } + } break; + } + case 'd': if (event.ctrlKey) {event.preventDefault(); toggleDetailsDisplay()} break; + case 'e': if (event.ctrlKey) {event.preventDefault(); openEditModal()} break; + case 'h': if (event.ctrlKey) {event.preventDefault(); showHelpModal();} break; + case 'm': if (event.ctrlKey) {event.preventDefault(); maximizeTextarea();} break; + case 's': { + if (event.ctrlKey) { + event.preventDefault(); + hideAllModals(); + if (event.shiftKey) { // This is a failsafe in case the 'S' case below doesn't work for certain browsers + openSettingsModal(); + } else { + showSearchModal(); + } + } + break; + } + case 'S': if (event.ctrlKey) { event.preventDefault(); hideAllModals(); openSettingsModal(); } break; + case 'x': if (event.ctrlKey) {event.preventDefault(); clearSearchText();} break; + } +} + +function hideAllModals() { + const permanentModals = ['#searchModal', '#settingsModal', '#editModal']; + const hideModals = document.querySelectorAll(permanentModals.join(',')), + removeModals = document.querySelectorAll('.modal:not(' + permanentModals.join('):not(') + ')'); + Array.from(hideModals).forEach(modal => modal.style.display = 'none'); + Array.from(removeModals).forEach(modal => modal.parentElement.removeChild(modal)); +} + +function toggleDetailsDisplay() { + const activeTab = document.querySelector('#detailsSection nav li.active'); + console.log(activeTab); + Array.from(document.querySelectorAll('#detailsSection nav li')).forEach(li => li.classList.remove('active')); + if (activeTab) { + switch(activeTab.innerText.trim().toLowerCase()) { + case 'description': { + document.querySelector('#detailsSection nav li:nth-child(2)').classList.add('active'); + showSection('details'); + break; + } + case 'details': { + document.querySelector('#detailsSection nav li:nth-child(3)').classList.add('active'); + showSection('stats'); + break; + } + case 'stats': { + hideDetailsPanel(); + break; + } + } + } else { + document.querySelector('#detailsSection nav li:nth-child(1)').classList.add('active'); + showSection('description'); + } +} + +function submitWord() { + const focused = document.activeElement; + if (focused && focused.id) { + const isSubmittableField = focused.id.includes('wordName') || focused.id.includes('wordDefinition') || focused.id.includes('wordDetails'); + if (isSubmittableField) { + if (focused.parentElement.parentElement.classList.contains('edit-form')) { + const wordId = parseInt(focused.parentElement.parentElement.id.replace('editForm_', '')); + confirmEditWord(wordId); + } else { + submitWordForm(); + } + } + } +} + +function showHelpModal() { + import('../markdown/help.md').then(html => { + renderInfoModal(html); + }); +} + +function maximizeTextarea() { + const focused = document.activeElement; + if (focused) { + const maximizeButton = focused.parentElement.querySelector('.maximize-button'); + console.log(maximizeButton); + console.log(maximizeButton.parentElement); + if (maximizeButton) { + renderMaximizedTextbox(maximizeButton); + } + } +} diff --git a/src/js/render.js b/src/js/render.js index 7c129d8..cb85906 100644 --- a/src/js/render.js +++ b/src/js/render.js @@ -277,7 +277,7 @@ export function renderEditForm(wordId = false) { } export function renderIPATable(ipaTableButton) { - ipaTableButton = typeof ipaTableButton.target === 'undefined' ? ipaTableButton : ipaTableButton.target; + ipaTableButton = typeof ipaTableButton.target === 'undefined' || ipaTableButton.target === '' ? ipaTableButton : ipaTableButton.target; const label = ipaTableButton.parentElement.innerText.replace(/(Field Help|IPA Chart)/g, '').trim(); const textBox = ipaTableButton.parentElement.querySelector('input'); import('./KeyboardFire/phondue/ipa-table.html').then(html => { @@ -301,7 +301,8 @@ export function renderIPATable(ipaTableButton) { } export function renderMaximizedTextbox(maximizeButton) { - maximizeButton = typeof maximizeButton.target === 'undefined' ? maximizeButton : maximizeButton.target; + maximizeButton = typeof maximizeButton.target === 'undefined' || maximizeButton.target === '' ? maximizeButton : maximizeButton.target; + console.log(maximizeButton.parentElement); const label = maximizeButton.parentElement.innerText.replace(/(\*|Maximize)/g, '').trim(); const textBox = maximizeButton.parentElement.querySelector('textarea'); const modalElement = document.createElement('section'); diff --git a/src/js/search.js b/src/js/search.js index 68c9b02..ea08930 100644 --- a/src/js/search.js +++ b/src/js/search.js @@ -1,5 +1,17 @@ import { cloneObject, getIndicesOf } from "../helpers"; import removeDiacritics from "./StackOverflow/removeDiacritics"; +import { renderWords } from "./render"; + +export function showSearchModal() { + document.getElementById('searchModal').style.display = 'block'; + document.getElementById('searchBox').focus(); +} + +export function clearSearchText() { + document.getElementById('searchBox').value = ''; + document.getElementById('openSearchModal').value = ''; + renderWords(); +} export function getSearchTerm() { return document.getElementById('searchBox').value; diff --git a/src/js/setupListeners.js b/src/js/setupListeners.js index 8c17c1e..b6ebe12 100644 --- a/src/js/setupListeners.js +++ b/src/js/setupListeners.js @@ -1,13 +1,13 @@ -import {showSection} from './displayToggles'; +import {showSection, hideDetailsPanel} from './displayToggles'; import { renderWords, renderEditForm, renderMaximizedTextbox, renderInfoModal, renderIPATable } from './render'; -import { validateWord, addWord, confirmEditWord, cancelEditWord, confirmDeleteWord } from './wordManagement'; -import { removeTags } from '../helpers'; -import { getNextId } from './utilities'; +import { confirmEditWord, cancelEditWord, confirmDeleteWord, submitWordForm } from './wordManagement'; import { openEditModal, saveEditModal, saveAndCloseEditModal } from './dictionaryManagement'; import { goToNextPage, goToPreviousPage, goToPage } from './pagination'; import { insertAtCursor, getInputSelection, setSelectionRange } from './StackOverflow/inputCursorManagement'; import { usePhondueDigraphs } from './KeyboardFire/phondue/ipaField'; import { openSettingsModal, saveSettingsModal, saveAndCloseSettingsModal } from './settings'; +import { enableHotKeys } from './hotkeys'; +import { showSearchModal, clearSearchText } from './search'; export default function setupListeners() { setupDetailsTabs(); @@ -16,6 +16,9 @@ export default function setupListeners() { setupWordForm(); setupMobileWordFormButton(); setupInfoButtons(); + if (window.settings.useHotkeys) { + enableHotKeys(); + } } function setupDetailsTabs() { @@ -33,7 +36,7 @@ function setupDetailsTabs() { const isActive = tab.classList.contains('active'); tabs.forEach(t => t.classList.remove('active')); if (isActive) { - document.getElementById('detailsPanel').style.display = 'none'; + hideDetailsPanel(); } else { tab.classList.add('active'); showSection(section); @@ -96,15 +99,8 @@ function setupSearchBar() { searchBox.addEventListener('input', event => { openSearchModal.value = event.target.value; }); - clearSearchButton.addEventListener('click', event => { - searchBox.value = ''; - openSearchModal.value = ''; - renderWords(); - }); - openSearchModal.addEventListener('click', () => { - document.getElementById('searchModal').style.display = 'block'; - searchBox.focus(); - }); + clearSearchButton.addEventListener('click', clearSearchText); + openSearchModal.addEventListener('click', showSearchModal); const toggleDetailsCheck = function() { if (searchExactWords.checked) { @@ -145,26 +141,7 @@ function setupWordForm() { event.preventDefault(); return false; }); - addWordButton.addEventListener('click', () => { - const name = document.getElementById('wordName').value, - pronunciation = document.getElementById('wordPronunciation').value, - partOfSpeech = document.getElementById('wordPartOfSpeech').value, - definition = document.getElementById('wordDefinition').value, - details = document.getElementById('wordDetails').value; - - const word = { - name: removeTags(name).trim(), - pronunciation: removeTags(pronunciation).trim(), - partOfSpeech: removeTags(partOfSpeech).trim(), - definition: removeTags(definition).trim(), - details: removeTags(details).trim(), - wordId: getNextId(), - }; - - if (validateWord(word)) { - addWord(word); - } - }); + addWordButton.addEventListener('click', submitWordForm); setupIPAFields(); setupMaximizeButtons(); diff --git a/src/js/wordManagement.js b/src/js/wordManagement.js index 700744c..d288199 100644 --- a/src/js/wordManagement.js +++ b/src/js/wordManagement.js @@ -1,5 +1,5 @@ import { renderWords } from "./render"; -import { wordExists, addMessage } from "./utilities"; +import { wordExists, addMessage, getNextId } from "./utilities"; import removeDiacritics from "./StackOverflow/removeDiacritics"; import { removeTags } from "../helpers"; import { saveDictionary } from "./dictionaryManagement"; @@ -46,6 +46,27 @@ export function sortWords(render) { } } +export function submitWordForm() { + const name = document.getElementById('wordName').value, + pronunciation = document.getElementById('wordPronunciation').value, + partOfSpeech = document.getElementById('wordPartOfSpeech').value, + definition = document.getElementById('wordDefinition').value, + details = document.getElementById('wordDetails').value; + + const word = { + name: removeTags(name).trim(), + pronunciation: removeTags(pronunciation).trim(), + partOfSpeech: removeTags(partOfSpeech).trim(), + definition: removeTags(definition).trim(), + details: removeTags(details).trim(), + wordId: getNextId(), + }; + + if (validateWord(word)) { + addWord(word); + } +} + export function addWord(word, render = true) { window.currentDictionary.words.push(word); addMessage('Word Created Successfully'); @@ -75,8 +96,9 @@ export function updateWord(word, wordId) { } } -export function confirmEditWord() { - const wordId = parseInt(this.id.replace('editWordButton_', '')); +export function confirmEditWord(id) { + const wordId = typeof id.target !== 'undefined' ? parseInt(this.id.replace('editWordButton_', '')) : id; + console.log(wordId); const name = document.getElementById('wordName_' + wordId).value, pronunciation = document.getElementById('wordPronunciation_' + wordId).value, partOfSpeech = document.getElementById('wordPartOfSpeech_' + wordId).value,