Compare commits

...

10 Commits

15 changed files with 457 additions and 121 deletions

View File

@ -143,30 +143,37 @@
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<form>
<label>Use IPA Auto-Fill
<input id="settingsUseIPA" type="checkbox" checked><br />
<small>Check this to use character combinations to input International Phonetic Alphabet characters into
Pronunciation fields.</small>
</label>
<label>Theme
<select disabled>
<option selected value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="royal">Royal</option>
</select>
</label>
<form class="split two">
<div>
<label>Use IPA Auto-Fill
<input id="settingsUseIPA" type="checkbox" checked><br />
<small>Check this to use character combinations to input International Phonetic Alphabet characters into
Pronunciation fields.</small>
</label>
<label>Use Hotkeys
<input id="settingsUseHotkeys" type="checkbox" checked><br />
<small>Check this to enable keyboard combinations to perform different helpful actions.</small>
</label>
<label>Theme
<select disabled>
<option selected value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="royal">Royal</option>
</select>
</label>
</div>
<div id="accountSettings"></div>
</form>
</section>
<footer>
<a class="button" id="settingsSave">Save</a>
<a class="button" id="settingsSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without
Saving</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section>
@ -203,21 +210,24 @@
<div class="split three">
<div>
<label>Consonants<br>
<input id="editConsonants" class="ipa-field"><br>
<small>(Space separated list)</small><br>
<input id="editConsonants" class="ipa-field" placeholder="p b m n t ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Vowels<br>
<input id="editVowels" class="ipa-field"><br>
<small>(Space separated list)</small><br>
<input id="editVowels" class="ipa-field" placeholder="æ u e ɪ ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Polyphthongs&nbsp;/ Blends<br>
<input id="editBlends" class="ipa-field"><br>
<small>(Space separated list)</small><br>
<input id="editBlends" class="ipa-field" placeholder="ai ou ue ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
@ -282,21 +292,24 @@
<div class="split two">
<div>
<p>
<a class="button">Import JSON</a><br>
<small>Import a previously-exported <code>JSON</code> file.</small>
<label class="button">Import JSON <input type="file" id="importDictionaryFile" accept="application/json, .dict"><br>
<small>Import a previously-exported <code>JSON</code> file.</small>
</label>
</p>
<p>
<a class="button">Import Words</a><br>
<small>Import a CSV file of words. (Download an <a>example file with the correct formatting</a>.)</small>
<label class="button">Import Words <input type="file" id="importWordsCSV" accept="text/csv, .csv"><br>
<small>Import a CSV file of words.</small>
</label>
<a class="small button" download="Lexiconga_import-template.csv" href="data:text/csv;charset=utf-8,%22word%22,%22pronunciation%22,%22part of speech%22,%22definition%22,%22explanation%22%0A">Download an example file with the correct formatting</a>
</p>
</div>
<div>
<p>
<a class="button">Export JSON</a><br>
<a class="button" id="exportDictionaryButton">Export JSON</a><br>
<small>Export your work as a <code>JSON</code> file to re-import later.</small>
</p>
<p>
<a class="button">Export Words</a><br>
<a class="button" id="exportWordsButton">Export Words</a><br>
<small>Export a CSV file of your words.</small>
</p>
</div>

View File

@ -21,6 +21,7 @@
},
"dependencies": {
"marked": "^0.6.2",
"normalize.css": "^8.0.1"
"normalize.css": "^8.0.1",
"papaparse": "^4.6.3"
}
}

View File

@ -37,8 +37,8 @@ export const DEFAULT_DICTIONARY = {
name: '',
pronunciation: '',
partOfSpeech: '',
simpleDefinition: '',
longDefinition: '',
definition: '',
details: '',
wordId: 0
}, */
],
@ -56,6 +56,7 @@ export const DEFAULT_DICTIONARY = {
export const DEFAULT_SETTINGS = {
useIPAPronunciationField: true,
useHotkeys: true,
};
export const DEFAULT_PAGE_SIZE = 50;

View File

@ -4,6 +4,24 @@ export function cloneObject(object) {
return JSON.parse(JSON.stringify(object));
}
export function download(data, filename, type) {
var file = new Blob([data], { type });
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
export function getIndicesOf(searchStr, findIn, caseSensitive) {
// https://stackoverflow.com/a/3410557
const searchStrLen = searchStr.length;
@ -52,5 +70,5 @@ export function removeTags(html) {
}
export function slugify(string) {
return removeDiacritics(string).replace(/[!a-zA-Z0-9-_]/g, '-');
return removeDiacritics(string).replace(/[^a-zA-Z0-9-_]/g, '-');
}

View File

@ -1,7 +1,8 @@
import { renderDictionaryDetails, renderPartsOfSpeech } from "./render";
import { removeTags, cloneObject, getTimestampInSeconds } from "../helpers";
import { renderDictionaryDetails, renderPartsOfSpeech, renderAll } from "./render";
import { removeTags, cloneObject, getTimestampInSeconds, download, slugify } from "../helpers";
import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY, MIGRATE_VERSION } from "../constants";
import { addMessage } from "./utilities";
import { addMessage, getNextId } from "./utilities";
import { addWord } from "./wordManagement";
export function updateDictionary () {
@ -19,9 +20,9 @@ export function openEditModal() {
document.getElementById('editDescription').value = description;
document.getElementById('editPartsOfSpeech').value = partsOfSpeech.join(',');
document.getElementById('editConsonants').value = consonants.join(',');
document.getElementById('editVowels').value = vowels.join(',');
document.getElementById('editBlends').value = blends.join(',');
document.getElementById('editConsonants').value = consonants.join(' ');
document.getElementById('editVowels').value = vowels.join(' ');
document.getElementById('editBlends').value = blends.join(' ');
document.getElementById('editOnset').value = phonotactics.onset.join(',');
document.getElementById('editNucleus').value = phonotactics.nucleus.join(',');
document.getElementById('editCoda').value = phonotactics.coda.join(',');
@ -46,9 +47,9 @@ export function saveEditModal() {
window.currentDictionary.description = removeTags(document.getElementById('editDescription').value.trim());
window.currentDictionary.partsOfSpeech = document.getElementById('editPartsOfSpeech').value.split(',').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.consonants = document.getElementById('editConsonants').value.split(',').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.vowels = document.getElementById('editVowels').value.split(',').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.blends = document.getElementById('editBlends').value.split(',').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.consonants = document.getElementById('editConsonants').value.split(' ').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.vowels = document.getElementById('editVowels').value.split(' ').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.blends = document.getElementById('editBlends').value.split(' ').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.phonotactics.onset = document.getElementById('editOnset').value.split(',').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.phonotactics.nucleus = document.getElementById('editNucleus').value.split(',').map(val => val.trim()).filter(val => val !== '');
window.currentDictionary.details.phonology.phonotactics.coda = document.getElementById('editCoda').value.split(',').map(val => val.trim()).filter(val => val !== '');
@ -93,13 +94,128 @@ export function clearDictionary() {
window.currentDictionary = cloneObject(DEFAULT_DICTIONARY);
}
export function importDictionary() {
const importDictionaryField = document.getElementById('importDictionaryFile');
if (importDictionaryField.files.length === 1) {
if (confirm('Importing a dicitonary file will overwrite and replace your current dictionary!\nDo you want to continue?')) {
addMessage('Importing Dictionary...');
const fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
const textFromFileLoaded = fileLoadedEvent.target.result;
const importedDictionary = JSON.parse(textFromFileLoaded);
if (importedDictionary && importedDictionary.hasOwnProperty('words')) {
window.currentDictionary = importedDictionary;
saveDictionary();
renderAll();
importDictionaryField.value = '';
document.getElementById('editModal').style.display = 'none';
addMessage('Dictionary Imported Successfully');
} else {
addMessage('Dictionary could not be imported', 10000);
}
};
fileReader.readAsText(importDictionaryField.files[0], "UTF-8");
}
}
}
export function importWords() {
const importWordsField = document.getElementById('importWordsCSV');
if (importWordsField.files.length === 1) {
if (confirm('Importing a CSV file with words will add all of the words in the file to your dictionary regardless of duplication!\nDo you want to continue?')) {
addMessage('Importing words...');
import('papaparse').then(papa => {
let wordsImported = 0;
papa.parse(importWordsField.files[0], {
header: true,
encoding: "utf-8",
step: results => {
if (results.errors.length > 0) {
results.errors.forEach(err => {
addMessage('Error Importing Word: ' + err);
console.error('Error Importing Word: ', err)
});
} else {
const row = results.data[0];
addWord({
name: removeTags(row.word).trim(),
pronunciation: removeTags(row.pronunciation).trim(),
partOfSpeech: removeTags(row['part of speech']).trim(),
definition: removeTags(row.definition).trim(),
details: removeTags(row.explanation).trim(),
wordId: getNextId(),
}, false, false);
wordsImported++;
}
},
complete: () => {
saveDictionary();
renderAll();
importWordsField.value = '';
document.getElementById('editModal').style.display = 'none';
addMessage(`Done Importing ${wordsImported} Words`);
},
error: err => {
addMessage('Error Importing Words: ' + err);
console.error('Error Importing Words: ', err);
},
skipEmptyLines: true,
});
});
}
}
}
export function exportDictionary() {
addMessage('Exporting JSON...');
setTimeout(() => {
const file = JSON.stringify(window.currentDictionary),
{ name, specification } = window.currentDictionary;
const fileName = slugify(name + '_' + specification) + '.json';
download(file, fileName, 'application/json;charset=utf-8');
}, 1);
}
export function exportWords() {
addMessage('Exporting Words...');
setTimeout(() => {
import('papaparse').then(papa => {
const { name, specification } = window.currentDictionary;
const fileName = slugify(name + '_' + specification) + '_words.csv';
const words = window.currentDictionary.words.map(word => {
return {
word: word.name,
pronunciation: word.pronunciation,
'part of speech': word.partOfSpeech,
definition: word.definition,
explanation: word.details,
}
});
const csv = papa.unparse(words, { quotes: true });
download(csv, fileName, 'text/csv;charset=utf-8');
});
}, 1);
}
export function migrateDictionary() {
let migrated = false;
if (!window.currentDictionary.hasOwnProperty('version')) {
const fixStupidOldNonsense = string => string.replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#92;/g, '\\').replace(/<br>/g, '\n');
window.currentDictionary.description = fixStupidOldNonsense(window.currentDictionary.description);
window.currentDictionary.words = window.currentDictionary.words.map(word => {
word.longDefinition = fixStupidOldNonsense(word.longDefinition);
word.definition = word.simpleDefinition;
delete word.simpleDefinition;
word.details = fixStupidOldNonsense(word.longDefinition);
delete word.longDefinition;
return word;
});
window.currentDictionary = Object.assign({}, DEFAULT_DICTIONARY, window.currentDictionary);

View File

@ -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';

128
src/js/hotkeys.js Normal file
View File

@ -0,0 +1,128 @@
import { confirmEditWord, submitWordForm } from "./wordManagement";
import { showSection, getIsDetailsPanelDisplayed, hideDetailsPanel } from "./displayToggles";
import { renderInfoModal, renderMaximizedTextbox } from "./render";
import { showSearchModal, clearSearchText } from "./search";
import { saveAndCloseSettingsModal, openSettingsModal, saveSettings } from "./settings";
import { saveAndCloseEditModal, openEditModal } from "./dictionaryManagement";
import { addMessage } from "./utilities";
export function enableHotKeys() {
document.addEventListener('keydown', hotKeyActions);
}
export function disableHotKeys() {
document.removeEventListener('keydown', hotKeyActions);
}
export function hotKeyActions(event) {
if (typeof event.key === 'undefined' || typeof event.ctrlKey === 'undefined' || typeof event.altKey === 'undefined') {
addMessage('Hotkeys disabled');
console.warn('Browser does not have required event properties for hotkeys.');
window.settings.useHotkeys = false;
saveSettings();
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(); hideAllModals(); openEditModal();} break;
case 'h': if (event.ctrlKey) {event.preventDefault(); hideAllModals(); 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);
}
}
}

View File

@ -154,7 +154,7 @@ export function renderWords() {
// words.slice(pageStart, pageEnd).forEach(originalWord => {
words.forEach(originalWord => {
let detailsMarkdown = removeTags(originalWord.longDefinition);
let detailsMarkdown = removeTags(originalWord.details);
const references = detailsMarkdown.match(/\{\{.+?\}\}/g);
if (references && Array.isArray(references)) {
new Set(references).forEach(reference => {
@ -170,8 +170,8 @@ export function renderWords() {
name: removeTags(originalWord.name),
pronunciation: removeTags(originalWord.pronunciation),
partOfSpeech: removeTags(originalWord.partOfSpeech),
simpleDefinition: removeTags(originalWord.simpleDefinition),
longDefinition: detailsMarkdown,
definition: removeTags(originalWord.definition),
details: detailsMarkdown,
wordId: originalWord.wordId,
});
wordsHTML += `<article class="entry" id="${word.wordId}">
@ -186,9 +186,9 @@ export function renderWords() {
</div>
</header>
<dl>
<dt class="definition">${word.simpleDefinition}</dt>
<dt class="definition">${word.definition}</dt>
<dd class="details">
${md(word.longDefinition)}
${md(word.details)}
</dd>
</dl>
</article>`;
@ -261,10 +261,10 @@ export function renderEditForm(wordId = false) {
</select>
</label>
<label>Definition<span class="red">*</span><br>
<input id="wordDefinition_${wordId}" value="${word.simpleDefinition}" placeholder="Equivalent words">
<input id="wordDefinition_${wordId}" value="${word.definition}" placeholder="Equivalent words">
</label>
<label>Details<span class="red">*</span><a class="label-button maximize-button">Maximize</a><br>
<textarea id="wordDetails_${wordId}" placeholder="Markdown formatting allowed">${word.longDefinition}</textarea>
<textarea id="wordDetails_${wordId}" placeholder="Markdown formatting allowed">${word.details}</textarea>
</label>
<div id="wordErrorMessage_${wordId}"></div>
<a class="button edit-save-changes" id="editWordButton_${wordId}">Save Changes</a>
@ -277,8 +277,8 @@ export function renderEditForm(wordId = false) {
}
export function renderIPATable(ipaTableButton) {
ipaTableButton = typeof ipaTableButton.target === 'undefined' ? ipaTableButton : ipaTableButton.target;
// const label = ipaTableButton.parentElement.innerText.replace(/(\*|Maximize)/g, '').trim();
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 => {
const modalElement = document.createElement('section');
@ -286,7 +286,7 @@ export function renderIPATable(ipaTableButton) {
modalElement.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<header><label>Pronunciation <input value="${textBox.value}" class="ipa-field"></label></header>
<header><label>${label} <input value="${textBox.value}" class="ipa-field"></label></header>
<section>
${html}
</section>
@ -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');

View File

@ -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;
@ -44,18 +56,18 @@ export function getMatchingSearchWords() {
searchTerm = filters.caseSensitive ? searchTerm : searchTerm.toLowerCase();
let name = filters.ignoreDiacritics ? removeDiacritics(word.name) : word.name;
name = filters.caseSensitive ? name : name.toLowerCase();
let simpleDefinition = filters.ignoreDiacritics ? removeDiacritics(word.simpleDefinition) : word.simpleDefinition;
simpleDefinition = filters.caseSensitive ? simpleDefinition : simpleDefinition.toLowerCase();
let longDefinition = filters.ignoreDiacritics ? removeDiacritics(word.longDefinition) : word.longDefinition;
longDefinition = filters.caseSensitive ? longDefinition : longDefinition.toLowerCase();
let definition = filters.ignoreDiacritics ? removeDiacritics(word.definition) : word.definition;
definition = filters.caseSensitive ? definition : definition.toLowerCase();
let details = filters.ignoreDiacritics ? removeDiacritics(word.details) : word.details;
details = filters.caseSensitive ? details : details.toLowerCase();
const isInName = filters.name && (filters.exact
? searchTerm == name
: new RegExp(searchTerm, 'g').test(name));
const isInDefinition = filters.definition && (filters.exact
? searchTerm == simpleDefinition
: new RegExp(searchTerm, 'g').test(simpleDefinition));
const isInDetails = filters.details && new RegExp(searchTerm, 'g').test(longDefinition);
? searchTerm == definition
: new RegExp(searchTerm, 'g').test(definition));
const isInDetails = filters.details && new RegExp(searchTerm, 'g').test(details);
return searchTerm === '' || isInName || isInDefinition || isInDetails;
});
return matchingWords;
@ -82,21 +94,21 @@ export function highlightSearchTerm(word) {
});
}
if (filters.definition) {
const simpleDefinitionMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.simpleDefinition), filters.caseSensitive);
simpleDefinitionMatches.forEach((wordIndex, i) => {
const definitionMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.definition), filters.caseSensitive);
definitionMatches.forEach((wordIndex, i) => {
wordIndex += '<mark></mark>'.length * i;
markedUpWord.simpleDefinition = markedUpWord.simpleDefinition.substring(0, wordIndex)
+ '<mark>' + markedUpWord.simpleDefinition.substr(wordIndex, searchTermLength) + '</mark>'
+ markedUpWord.simpleDefinition.substr(wordIndex + searchTermLength);
markedUpWord.definition = markedUpWord.definition.substring(0, wordIndex)
+ '<mark>' + markedUpWord.definition.substr(wordIndex, searchTermLength) + '</mark>'
+ markedUpWord.definition.substr(wordIndex + searchTermLength);
});
}
if (filters.details) {
const longDefinitionMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.longDefinition), filters.caseSensitive);
longDefinitionMatches.forEach((wordIndex, i) => {
const detailsMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.details), filters.caseSensitive);
detailsMatches.forEach((wordIndex, i) => {
wordIndex += '<mark></mark>'.length * i;
markedUpWord.longDefinition = markedUpWord.longDefinition.substring(0, wordIndex)
+ '<mark>' + markedUpWord.longDefinition.substr(wordIndex, searchTermLength) + '</mark>'
+ markedUpWord.longDefinition.substr(wordIndex + searchTermLength);
markedUpWord.details = markedUpWord.details.substring(0, wordIndex)
+ '<mark>' + markedUpWord.details.substr(wordIndex, searchTermLength) + '</mark>'
+ markedUpWord.details.substr(wordIndex + searchTermLength);
});
}
} else {
@ -105,10 +117,10 @@ export function highlightSearchTerm(word) {
markedUpWord.name = markedUpWord.name.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
}
if (filters.definition) {
markedUpWord.simpleDefinition = markedUpWord.simpleDefinition.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
markedUpWord.definition = markedUpWord.definition.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
}
if (filters.details) {
markedUpWord.longDefinition = markedUpWord.longDefinition.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
markedUpWord.details = markedUpWord.details.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
}
}
return markedUpWord;

View File

@ -3,6 +3,7 @@ import { cloneObject } from "../helpers";
import { usePhondueDigraphs } from "./KeyboardFire/phondue/ipaField";
import { renderWords } from "./render";
import { addMessage } from "./utilities";
import { enableHotKeys, disableHotKeys } from "./hotkeys";
export function loadSettings() {
const storedSettings = window.localStorage.getItem(SETTINGS_KEY);
@ -16,17 +17,20 @@ export function saveSettings() {
}
export function openSettingsModal() {
const { useIPAPronunciationField } = window.settings;
const { useIPAPronunciationField, useHotkeys } = window.settings;
document.getElementById('settingsUseIPA').checked = useIPAPronunciationField;
document.getElementById('settingsUseHotkeys').checked = useHotkeys;
document.getElementById('settingsModal').style.display = '';
}
export function saveSettingsModal() {
window.settings.useIPAPronunciationField = document.getElementById('settingsUseIPA').checked;
window.settings.useHotkeys = document.getElementById('settingsUseHotkeys').checked;
saveSettings();
toggleHotkeysEnabled();
toggleIPAPronunciationFields();
}
@ -35,6 +39,13 @@ export function saveAndCloseSettingsModal() {
document.getElementById('settingsModal').style.display = 'none';
}
export function toggleHotkeysEnabled() {
disableHotKeys();
if (window.settings.useHotkeys) {
enableHotKeys();
}
}
export function toggleIPAPronunciationFields() {
const ipaButtons = document.querySelectorAll('.ipa-table-button, .ipa-field-help-button'),
ipaFields = document.querySelectorAll('.ipa-field');

View File

@ -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 { openEditModal, saveEditModal, saveAndCloseEditModal } from './dictionaryManagement';
import { confirmEditWord, cancelEditWord, confirmDeleteWord, submitWordForm } from './wordManagement';
import { openEditModal, saveEditModal, saveAndCloseEditModal, exportDictionary, exportWords, importDictionary, importWords } 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);
@ -79,6 +82,10 @@ function setupEditFormInteractions() {
function setupEditFormButtons() {
document.getElementById('editSave').addEventListener('click', () => saveEditModal());
document.getElementById('editSaveAndClose').addEventListener('click', () => saveAndCloseEditModal());
document.getElementById('importDictionaryFile').addEventListener('change', importDictionary);
document.getElementById('importWordsCSV').addEventListener('change', importWords);
document.getElementById('exportDictionaryButton').addEventListener('click', exportDictionary);
document.getElementById('exportWordsButton').addEventListener('click', exportWords);
setupMaximizeButtons();
}
@ -96,15 +103,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 +145,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(),
simpleDefinition: removeTags(definition).trim(),
longDefinition: removeTags(details).trim(),
wordId: getNextId(),
};
if (validateWord(word)) {
addWord(word);
}
});
addWordButton.addEventListener('click', submitWordForm);
setupIPAFields();
setupMaximizeButtons();
@ -219,8 +200,8 @@ export function setupSettingsModal() {
}
export function setupWordEditFormButtons() {
const saveChangesButtons = document.getElementsByClassName('edit-save-changes');
const cancelChangesButtons = document.getElementsByClassName('edit-cancel');
const saveChangesButtons = document.getElementsByClassName('edit-save-changes'),
cancelChangesButtons = document.getElementsByClassName('edit-cancel');
Array.from(saveChangesButtons).forEach(button => {
button.removeEventListener('click', confirmEditWord);
button.addEventListener('click', confirmEditWord);

View File

@ -123,8 +123,8 @@ export function generateRandomWords(numberOfWords) {
name: word,
pronunciation: '/' + word + '/',
partOfSpeech: Math.random() > 0.5 ? 'Noun' : 'Verb',
simpleDefinition: word,
longDefinition: word + (index > 0 ? '\n\nRef: {{' + words[index - 1] + '}}' : ''),
definition: word,
details: word + (index > 0 ? '\n\nRef: {{' + words[index - 1] + '}}' : ''),
wordId: getNextId(),
}, false);
});

View File

@ -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";
@ -12,7 +12,7 @@ export function validateWord(word, wordId = false) {
if (word.name === '') {
errorMessage += '<p class="bold red">Word field must not be blank.</p>';
}
if (word.simpleDefinition === '' && word.longDefinition === '') {
if (word.definition === '' && word.details === '') {
errorMessage += '<p class="bold red">You must enter Definition or Details.</p>';
}
@ -32,7 +32,7 @@ export function validateWord(word, wordId = false) {
export function sortWords(render) {
const { sortByDefinition } = window.currentDictionary.settings;
const sortBy = sortByDefinition ? 'simpleDefinition' : 'name';
const sortBy = sortByDefinition ? 'definition' : 'name';
window.currentDictionary.words.sort((wordA, wordB) => {
if (removeDiacritics(wordA[sortBy]).toLowerCase() === removeDiacritics(wordB[sortBy]).toLowerCase()) return 0;
@ -46,9 +46,43 @@ export function sortWords(render) {
}
}
export function addWord(word, render = true) {
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);
clearWordForm();
}
}
export function clearWordForm() {
document.getElementById('wordName').value = '';
document.getElementById('wordPronunciation').value = '';
document.getElementById('wordPartOfSpeech').value = '';
document.getElementById('wordDefinition').value = '';
document.getElementById('wordDetails').value = '';
document.getElementById('wordName').focus();
}
export function addWord(word, render = true, message = true) {
window.currentDictionary.words.push(word);
addMessage('Word Created Successfully');
if (message) {
addMessage(`<a href="#${word.wordId}">${word.name}</a> Created Successfully`, 10000);
}
sortWords(render);
}
@ -75,8 +109,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,
@ -87,8 +122,8 @@ export function confirmEditWord() {
name: removeTags(name).trim(),
pronunciation: removeTags(pronunciation).trim(),
partOfSpeech: removeTags(partOfSpeech).trim(),
simpleDefinition: removeTags(definition).trim(),
longDefinition: removeTags(details).trim(),
definition: removeTags(definition).trim(),
details: removeTags(details).trim(),
wordId,
};

View File

@ -82,6 +82,12 @@
}
}
#settingsModal {
.modal-content section {
padding: ($general-padding * 2) $general-padding $general-padding;
}
}
#mobileWordFormShow {
display: none;
}

View File

@ -3524,6 +3524,11 @@ pako@~1.0.5:
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
papaparse@^4.6.3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-4.6.3.tgz#742e5eaaa97fa6c7e1358d2934d8f18f44aee781"
integrity sha512-LRq7BrHC2kHPBYSD50aKuw/B/dGcg29omyJbKWY3KsYUZU69RKwaBHu13jGmCYBtOc4odsLCrFyk6imfyNubJQ==
parcel-bundler@^1.12.3:
version "1.12.3"
resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.3.tgz#2bbf70bfa2d06097f071653285040bd125684d09"