Merge branch 'new-features-wave-1'

This commit is contained in:
Robbie Antenesse 2019-07-14 23:28:40 -06:00
commit 8c9ae4b100
43 changed files with 1878 additions and 1374 deletions

View File

@ -1,7 +1,7 @@
{
"plugins": {
"autoprefixer": {
"browsers": [
"overrideBrowserslist": [
">1%",
"last 4 versions",
"Firefox ESR",

View File

@ -13,7 +13,7 @@
<meta property="og:title" content="Lexiconga (OFFLINE)">
<meta property="og:description" content="The quick and easy (offline) dictionary builder for constructed languages.">
<meta property="og:image" content="processedImages/logo.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:image:alt" content="Lexiconga logo">
@ -26,13 +26,13 @@
<meta name="apple-mobile-web-app-title" content="Lexiconga">
<link rel="apple-touch-icon" href="processedImages/icon-152.png">
<link rel="stylesheet" href="src/main.scss" />
<link rel="stylesheet" href="src/main.scss">
<script>window.isOffline = true;</script>
<script src="src/index.js"></script>
</head>
<body id="defaultTheme">
<header id="top">
<a href="/" title="Lexiconga"><svg id="title" alt="Lexiconga Logo"viewBox="0 0 249.78 55.087">
<a href="/" title="Lexiconga"><svg id="title" alt="Lexiconga Logo" viewBox="0 0 249.78 55.087">
<g transform="translate(-107.53 -155.84)">
<g id="lexi">
<path d="m144.03 159.39-11.339 22.409h-21.62l11.339-22.409z" />
@ -81,6 +81,9 @@
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
<label>Translations
<input type="checkbox" id="searchOrthography">
</label>
</div>
</div>
<div class="split">
@ -241,9 +244,11 @@
<section id="editDescriptionTab">
<label>Name<br>
<input id="editName" maxlength="50">
<small>Won't update if left blank.</small>
</label>
<label>Specification<br>
<input id="editSpecification" maxlength="50">
<small>Won't update if left blank.</small>
</label>
<label>Description<a class="label-button maximize-button">Maximize</a><br>
<textarea id="editDescription"></textarea>
@ -254,8 +259,12 @@
<label>Parts of Speech <small>(Comma Separated List)</small><br>
<input id="editPartsOfSpeech" maxlength="2500" placeholder="Noun,Adjective,Verb">
</label>
<label>Alphabetical Order <small>(Comma Separated List. Include every letter!)</small><br>
<input id="editAlphabeticalOrder" disabled value="English Alphabet">
<label>Alphabetical Order <small>(Space Separated List)</small><br>
<input id="editAlphabeticalOrder" placeholder="a A b B c C d D ...">
<a class="label-help-button" onclick="alert('Include every letter and case! Any letters used in your words that are not specified will be sorted in the default order below your alphabetically custom-sorted words.\n\nLexiconga can only sort by single characters and will sort by the words AS ENTERED, not using orthographic translation.')">
Field Info
</a>&nbsp;
<small>Leave blank for default (case-insensitive ASCII/Unicode sorting)</small>
</label>
<h3>Phonology</h3>
<div class="split three">
@ -284,6 +293,9 @@
</label>
</div>
</div>
<label>Notes <small>(Markdown-enabled)</small><br>
<textarea id="editPhonologyNotes"></textarea>
</label>
<h3>Phonotactics</h3>
<div class="split three">
<div>
@ -305,10 +317,17 @@
</label>
</div>
</div>
<label>Exceptions <small>(Markdown-enabled)</small><br>
<textarea id="editExceptions"></textarea>
<label>Notes <small>(Markdown-enabled)</small><br>
<textarea id="editPhonotacticsNotes"></textarea>
</label>
<h3>Orthography</h3>
<label>Translations <small>(One translation per line)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editTranslations" placeholder="ai=I
AA=ay
ou=ow"></textarea>
<small>Use format: <code>sequence=replacement</code></small><br>
<small>Translations occur in the order specified here, so try to avoid double translations!</small>
</label>
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editOrthography"></textarea>
</label>
@ -345,6 +364,9 @@
<option value="grape">Grape</option>
</select>
</label>
<label>Custom Styling <small>(CSS Only)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editCustomCSS" placeholder=".orthographic-translation {font-family: serif;}"></textarea>
</label>
</section>
<section id="editActionsTab" style="display:none;">

View File

@ -11,7 +11,7 @@
"watch-js": "parcel watch template-index.html offline.html template-view.html template-passwordreset.html --no-hmr --public-url /lexiconga/",
"watch-php": "cpx \"src/php/**/{*,.*}\" dist -v -w",
"bundle": "npm run process-images && npm run bundle-js && npm run copy-files && npm run copy-php",
"bundle-js": "parcel build template-index.html offline.html template-view.html template-passwordreset.html --no-source-maps",
"bundle-js": "parcel build template-index.html offline.html template-view.html template-passwordreset.html --no-source-maps --experimental-scope-hoisting",
"copy-files": "cpx \"node_modules/upup/dist/*.min.js\" dist -v",
"copy-php": "cpx \"src/php/**/{*,.*}\" dist",
"process-images": "node dev/resize-images.js",
@ -21,16 +21,16 @@
"clear-cache": "rimraf .cache/{*,.*}"
},
"devDependencies": {
"autoprefixer": "^9.5.1",
"concurrently": "^4.1.0",
"autoprefixer": "^9.6.1",
"concurrently": "^4.1.1",
"cpx": "^1.5.0",
"parcel-bundler": "^1.12.3",
"rimraf": "^2.6.3",
"sass": "^1.19.0",
"sass": "^1.22.4",
"sharp": "^0.22.1"
},
"dependencies": {
"marked": "^0.6.2",
"marked": "^0.6.3",
"normalize.css": "^8.0.1",
"papaparse": "^4.6.3",
"upup": "^1.1.0"

View File

@ -1,36 +1,32 @@
import { getTimestampInSeconds } from "./helpers";
export const MIGRATE_VERSION = '2.0.0';
export const MIGRATE_VERSION = '2.1.0';
export const DEFAULT_DICTIONARY = {
name: 'New',
specification: 'Dictionary',
description: 'A new dictionary.',
partsOfSpeech: ['Noun', 'Adjective', 'Verb'],
partsOfSpeech: ['Noun', 'Adjective', 'Verb', 'Adverb', 'Preposition', 'Pronoun', 'Conjunction'],
alphabeticalOrder: [],
details: {
phonology: {
consonants: [],
vowels: [],
blends: [],
phonotactics: {
onset: [],
nucleus: [],
coda: [],
exceptions: '',
},
notes: '',
},
phonotactics: {
onset: [],
nucleus: [],
coda: [],
notes: '',
},
orthography: {
translations: [],
notes: '',
},
grammar: {
notes: '',
},
// custom: [
// // {
// // name: 'Example Tab',
// // content: `This is an _example_ tab to show how **tabs** work with [Markdown](${ MARKDOWN_LINK })!`,
// // }
// ],
},
words: [
/* {
@ -47,6 +43,7 @@ export const DEFAULT_DICTIONARY = {
caseSensitive: false,
sortByDefinition: false,
theme: 'default',
customCSS: '',
isPublic: false,
},
lastUpdated: getTimestampInSeconds(),

View File

@ -42,6 +42,7 @@ export function renderLoginForm() {
<label>Allow Emails
<input type="checkbox" id="createNewAllowEmails">
</label>
<p>Creating an account is <em>not</em> required to use Lexiconga's core features. Click "Help" in the site footer to learn what accounts provide.</p>
<section id="createAccountErrorMessages"></section>
<button id="createAccountSubmit" class="button">Create Account</button>
</div>

View File

@ -3,6 +3,7 @@ import { setCookie } from "../StackOverflow/cookie";
import { changeDictionary, createNewDictionary } from "./dictionaryManagement";
import { addMessage } from "../utilities";
import { renderForgotPasswordForm } from "./passwordReset";
import { setupMaximizeButtons } from "../setupListeners/buttons";
export function setupLoginModal(modal) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
@ -73,4 +74,5 @@ export function setupMakePublic() {
document.execCommand('copy');
addMessage('Copied public link to clipboard', 3000);
});
setupMaximizeButtons();
}

View File

@ -80,7 +80,9 @@ export function uploadWholeDictionary(asNew = false) {
dictionary,
}, remoteId => {
window.currentDictionary.externalID = remoteId;
document.getElementById('publicLink').value = document.domain + window.location.pathname + remoteId.toString();
if (document.getElementById('publicLink')) {
document.getElementById('publicLink').value = getPublicLink();
}
saveDictionary(false);
addMessage('Dictionary Uploaded Successfully');
renderChangeDictionaryOptions();

View File

@ -1,9 +1,11 @@
import papa from 'papaparse';
import { renderDictionaryDetails, renderPartsOfSpeech, renderAll, renderTheme } from "./render";
import { renderDictionaryDetails, renderPartsOfSpeech } from "./render/details";
import { renderAll, renderTheme, renderCustomCSS } from "./render";
import { removeTags, cloneObject, getTimestampInSeconds, download, slugify } from "../helpers";
import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY, MIGRATE_VERSION } from "../constants";
import { addMessage, getNextId, hasToken } from "./utilities";
import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY } from "../constants";
import { addMessage, getNextId, hasToken, objectValuesAreDifferent } from "./utilities";
import { addWord, sortWords } from "./wordManagement";
import { migrateDictionary } from './migration';
export function updateDictionary () {
@ -11,24 +13,28 @@ export function updateDictionary () {
}
export function openEditModal() {
const { name, specification, description, partsOfSpeech } = window.currentDictionary;
const { consonants, vowels, blends, phonotactics } = window.currentDictionary.details.phonology;
const { orthography, grammar } = window.currentDictionary.details;
const { allowDuplicates, caseSensitive, sortByDefinition, theme, isPublic } = window.currentDictionary.settings;
const { name, specification, description, partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
const { phonology, phonotactics, orthography, grammar } = window.currentDictionary.details;
const { consonants, vowels, blends } = phonology;
const { allowDuplicates, caseSensitive, sortByDefinition, theme, customCSS, isPublic } = window.currentDictionary.settings;
document.getElementById('editName').value = name;
document.getElementById('editSpecification').value = specification;
document.getElementById('editDescription').value = description;
document.getElementById('editPartsOfSpeech').value = partsOfSpeech.join(',');
document.getElementById('editAlphabeticalOrder').value = alphabeticalOrder.join(' ');
document.getElementById('editConsonants').value = consonants.join(' ');
document.getElementById('editVowels').value = vowels.join(' ');
document.getElementById('editBlends').value = blends.join(' ');
document.getElementById('editPhonologyNotes').value = phonology.notes;
document.getElementById('editOnset').value = phonotactics.onset.join(',');
document.getElementById('editNucleus').value = phonotactics.nucleus.join(',');
document.getElementById('editCoda').value = phonotactics.coda.join(',');
document.getElementById('editExceptions').value = phonotactics.exceptions;
document.getElementById('editPhonotacticsNotes').value = phonotactics.notes;
document.getElementById('editTranslations').value = orthography.translations.join('\n');
document.getElementById('editOrthography').value = orthography.notes;
document.getElementById('editGrammar').value = grammar.notes;
@ -37,6 +43,7 @@ export function openEditModal() {
if (allowDuplicates) document.getElementById('editCaseSensitive').disabled = true;
document.getElementById('editSortByDefinition').checked = sortByDefinition;
document.getElementById('editTheme').value = theme;
document.getElementById('editCustomCSS').value = customCSS;
if (hasToken()) {
document.getElementById('editIsPublic').checked = isPublic;
}
@ -45,51 +52,66 @@ export function openEditModal() {
}
export function saveEditModal() {
window.currentDictionary.name = removeTags(document.getElementById('editName').value.trim());
window.currentDictionary.specification = removeTags(document.getElementById('editSpecification').value.trim());
window.currentDictionary.description = removeTags(document.getElementById('editDescription').value.trim());
window.currentDictionary.partsOfSpeech = document.getElementById('editPartsOfSpeech').value.split(',').map(val => val.trim()).filter(val => val !== '');
const updatedDictionary = cloneObject(window.currentDictionary);
delete updatedDictionary.words;
updatedDictionary.name = removeTags(document.getElementById('editName').value.trim());
if (updatedDictionary.name.length < 1) {
updatedDictionary.name = window.currentDictionary.name;
}
updatedDictionary.specification = removeTags(document.getElementById('editSpecification').value.trim());
if (updatedDictionary.specification.length < 1) {
updatedDictionary.specification = window.currentDictionary.specification;
}
updatedDictionary.description = removeTags(document.getElementById('editDescription').value.trim());
updatedDictionary.partsOfSpeech = document.getElementById('editPartsOfSpeech').value.split(',').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.alphabeticalOrder = document.getElementById('editAlphabeticalOrder').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 !== '');
window.currentDictionary.details.phonology.phonotactics.exceptions = removeTags(document.getElementById('editExceptions').value.trim());
updatedDictionary.details.phonology.consonants = document.getElementById('editConsonants').value.split(' ').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.details.phonology.vowels = document.getElementById('editVowels').value.split(' ').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.details.phonology.blends = document.getElementById('editBlends').value.split(' ').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.details.phonology.notes = removeTags(document.getElementById('editPhonologyNotes').value.trim());
window.currentDictionary.details.orthography.notes = removeTags(document.getElementById('editOrthography').value.trim());
window.currentDictionary.details.grammar.notes = removeTags(document.getElementById('editGrammar').value.trim());
updatedDictionary.details.phonotactics.onset = document.getElementById('editOnset').value.split(',').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.details.phonotactics.nucleus = document.getElementById('editNucleus').value.split(',').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.details.phonotactics.coda = document.getElementById('editCoda').value.split(',').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.details.phonotactics.notes = removeTags(document.getElementById('editPhonotacticsNotes').value.trim());
window.currentDictionary.settings.allowDuplicates = !document.getElementById('editPreventDuplicates').checked;
window.currentDictionary.settings.caseSensitive = document.getElementById('editCaseSensitive').checked;
const needsReSort = window.currentDictionary.settings.sortByDefinition !== document.getElementById('editSortByDefinition').checked;
window.currentDictionary.settings.sortByDefinition = document.getElementById('editSortByDefinition').checked;
window.currentDictionary.settings.theme = document.getElementById('editTheme').value;
updatedDictionary.details.orthography.translations = document.getElementById('editTranslations').value.split('\n').map(val => val.trim()).filter(val => val !== '');
updatedDictionary.details.orthography.notes = removeTags(document.getElementById('editOrthography').value.trim());
updatedDictionary.details.grammar.notes = removeTags(document.getElementById('editGrammar').value.trim());
updatedDictionary.settings.allowDuplicates = !document.getElementById('editPreventDuplicates').checked;
updatedDictionary.settings.caseSensitive = document.getElementById('editCaseSensitive').checked;
updatedDictionary.settings.sortByDefinition = document.getElementById('editSortByDefinition').checked;
updatedDictionary.settings.theme = document.getElementById('editTheme').value;
updatedDictionary.settings.customCSS = removeTags(document.getElementById('editCustomCSS').value.trim());
let needsWordRender = false;
if (hasToken()) {
needsWordRender = window.currentDictionary.settings.isPublic !== document.getElementById('editIsPublic').checked;
window.currentDictionary.settings.isPublic = document.getElementById('editIsPublic').checked;
updatedDictionary.settings.isPublic = document.getElementById('editIsPublic').checked;
} else {
window.currentDictionary.settings.isPublic = false;
updatedDictionary.settings.isPublic = false;
}
addMessage('Saved ' + window.currentDictionary.specification + ' Successfully');
saveDictionary();
renderTheme();
renderDictionaryDetails();
renderPartsOfSpeech();
if (needsReSort || needsWordRender) {
if (objectValuesAreDifferent(updatedDictionary, window.currentDictionary)) {
window.currentDictionary = Object.assign(window.currentDictionary, updatedDictionary);
renderTheme();
renderCustomCSS();
renderDictionaryDetails();
renderPartsOfSpeech();
sortWords(true);
}
if (hasToken()) {
import('./account/index.js').then(account => {
account.uploadDetailsDirect();
account.updateChangeDictionaryOption();
})
addMessage('Saved ' + window.currentDictionary.specification + ' Successfully');
saveDictionary();
if (hasToken()) {
import('./account/index.js').then(account => {
account.uploadDetailsDirect();
account.updateChangeDictionaryOption();
})
}
} else {
addMessage('No changes made to Dictionary Settings.');
}
}
@ -285,39 +307,3 @@ export function exportWords() {
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);
const timestamp = getTimestampInSeconds();
window.currentDictionary.words = window.currentDictionary.words.map(word => {
word.definition = word.simpleDefinition;
delete word.simpleDefinition;
word.details = fixStupidOldNonsense(word.longDefinition);
delete word.longDefinition;
word.lastUpdated = timestamp;
word.createdOn = timestamp;
return word;
});
window.currentDictionary = Object.assign({}, DEFAULT_DICTIONARY, window.currentDictionary);
window.currentDictionary.partsOfSpeech = window.currentDictionary.settings.partsOfSpeech.split(',').map(val => val.trim()).filter(val => val !== '');
delete window.currentDictionary.settings.partsOfSpeech;
delete window.currentDictionary.nextWordId;
window.currentDictionary.settings.sortByDefinition = window.currentDictionary.settings.sortByEquivalent;
delete window.currentDictionary.settings.sortByEquivalent;
window.currentDictionary.settings.theme = 'default';
delete window.currentDictionary.settings.isComplete;
migrated = true;
} else if (window.currentDictionary.version !== MIGRATE_VERSION) {
switch (window.currentDictionary.version) {
default: console.error('Unknown version'); break;
}
}
if (migrated) {
saveDictionary();
}
}

View File

@ -1,4 +1,4 @@
import { renderDescription, renderDetails, renderStats } from './render';
import { renderDescription, renderDetails, renderStats } from './render/details';
export function showSection(sectionName) {
switch (sectionName) {

View File

@ -1,6 +1,6 @@
import { confirmEditWord, submitWordForm } from "./wordManagement";
import { showSection, hideDetailsPanel } from "./displayToggles";
import { renderInfoModal, renderMaximizedTextbox } from "./render";
import { renderInfoModal, renderMaximizedTextbox } from "./render/modals";
import { showSearchModal, clearSearchText } from "./search";
import { saveAndCloseSettingsModal, openSettingsModal, saveSettings } from "./settings";
import { saveAndCloseEditModal, openEditModal } from "./dictionaryManagement";
@ -56,7 +56,8 @@ export function hotKeyActions(event) {
break;
}
case 'S': if (event.ctrlKey) {event.preventDefault(); hideAllModals(); openSettingsModal();} break;
case 'x': if (event.ctrlKey) {event.preventDefault(); clearSearchText();} break;
case 'Delete':
case 'Backspace': if (event.ctrlKey) {event.preventDefault(); clearSearchText();} break;
}
}

View File

@ -1,4 +1,5 @@
import { LOCAL_STORAGE_KEY } from "../constants";
import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY, MIGRATE_VERSION } from "../constants";
import { saveDictionary } from "./dictionaryManagement";
export default function migrate() {
if (window.location.pathname === '/') {
@ -74,4 +75,53 @@ function checkForReceived() {
delete window.dictionaryImportedFromHTTP;
}
}
}
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);
const timestamp = getTimestampInSeconds();
window.currentDictionary.words = window.currentDictionary.words.map(word => {
word.definition = word.simpleDefinition;
delete word.simpleDefinition;
word.details = fixStupidOldNonsense(word.longDefinition);
delete word.longDefinition;
word.lastUpdated = timestamp;
word.createdOn = timestamp;
return word;
});
window.currentDictionary = Object.assign({}, DEFAULT_DICTIONARY, window.currentDictionary);
window.currentDictionary.partsOfSpeech = window.currentDictionary.settings.partsOfSpeech.split(',').map(val => val.trim()).filter(val => val !== '');
delete window.currentDictionary.settings.partsOfSpeech;
delete window.currentDictionary.nextWordId;
window.currentDictionary.settings.sortByDefinition = window.currentDictionary.settings.sortByEquivalent;
delete window.currentDictionary.settings.sortByEquivalent;
window.currentDictionary.settings.theme = 'default';
delete window.currentDictionary.settings.isComplete;
migrated = true;
} else if (window.currentDictionary.version !== MIGRATE_VERSION) {
switch (window.currentDictionary.version) {
default: console.error('Unknown version'); break;
case '2.0.0': {
window.currentDictionary.details.phonology.notes = '';
window.currentDictionary.details.phonotactics = Object.assign({}, window.currentDictionary.details.phonology.phonotactics);
delete window.currentDictionary.details.phonology.phonotactics;
window.currentDictionary.details.phonotactics.notes = window.currentDictionary.details.phonotactics.exceptions;
delete window.currentDictionary.details.phonotactics.exceptions;
window.currentDictionary.details.orthography.translations = [];
window.currentDictionary.settings.customCSS = '';
window.currentDictionary = Object.assign({}, DEFAULT_DICTIONARY, window.currentDictionary);
window.currentDictionary.version = MIGRATE_VERSION;
migrated = true;
// break; By skipping the break, all migrations can happen in sequence.
}
}
}
if (migrated) {
saveDictionary();
}
}

View File

@ -1,5 +1,5 @@
import { DEFAULT_PAGE_SIZE } from '../constants';
import { renderWords } from "./render";
import { renderWords } from "./render/words";
export function getPaginationData(words) {
const numWords = words.length;

View File

@ -1,369 +0,0 @@
import md from 'marked';
import { removeTags, slugify } from '../helpers';
import { getWordsStats, getHomonymnNumber, hasToken } from './utilities';
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from './search';
import { showSection } from './displayToggles';
import {
setupSearchFilters,
setupWordOptionButtons,
setupPagination,
setupWordOptionSelections,
setupWordEditFormButtons,
setupMaximizeModal,
setupInfoModal,
setupIPATable,
setupIPAFields
} from './setupListeners';
import { getPaginationData } from './pagination';
import { getOpenEditForms, parseReferences } from './wordManagement';
import { renderAd } from './ads';
import ipaTableFile from './KeyboardFire/phondue/ipa-table.html';
import { getPublicLink } from './account/utilities';
export function renderAll() {
renderTheme();
renderDictionaryDetails();
renderPartsOfSpeech();
renderWords();
}
export function renderTheme() {
const { theme } = window.currentDictionary.settings;
document.body.id = theme + 'Theme';
}
export function renderDictionaryDetails() {
renderName();
const tabs = document.querySelectorAll('#detailsSection nav li');
const shownTab = Array.from(tabs).find(tab => tab.classList.contains('active'));
if (shownTab) {
const tabName = shownTab.innerText.toLowerCase();
showSection(tabName);
}
}
export function renderName() {
const dictionaryName = removeTags(window.currentDictionary.name) + ' ' + removeTags(window.currentDictionary.specification);
const name = document.getElementById('dictionaryName');
name.innerHTML = dictionaryName;
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
const shareLinkElement = document.getElementById('dictionaryShare');
if (isPublic && !shareLinkElement) {
const shareLink = document.createElement('a');
shareLink.id = 'dictionaryShare';
shareLink.classList.add('button');
shareLink.style.float = 'right';
shareLink.href = getPublicLink();
shareLink.target = '_blank';
shareLink.title = 'Public Link to Dictionary';
shareLink.innerHTML = '&#10150;';
name.parentElement.insertBefore(shareLink, name);
} else if (isPublic && shareLinkElement) {
shareLinkElement.href = getPublicLink();
} else if (!isPublic && shareLinkElement) {
shareLinkElement.parentElement.removeChild(shareLinkElement);
}
}
export function renderDescription() {
const descriptionHTML = md(removeTags(window.currentDictionary.description));
document.getElementById('detailsPanel').innerHTML = '<div class="content">' + descriptionHTML + '</div>';
}
export function renderDetails() {
const { partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
const { phonology, orthography, grammar } = window.currentDictionary.details;
const partsOfSpeechHTML = `<p><strong>Parts of Speech:</strong> ${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</p>`;
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order:</strong> ${
(alphabeticalOrder.length > 0 ? alphabeticalOrder : ['English Alphabet']).map(letter => `<span class="tag">${letter}</span>`).join(' ')
}</p>`;
const generalHTML = `<h3>General</h3>${partsOfSpeechHTML}${alphabeticalOrderHTML}`;
const { consonants, vowels, blends, phonotactics } = phonology
const consonantHTML = `<p><strong>Consonants:</strong> ${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const vowelHTML = `<p><strong>Vowels:</strong> ${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs&nbsp;/&nbsp;Blends:</strong> ${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
const phonologyHTML = `<h3>Phonology</h3>
<div class="split two">
<div>${consonantHTML}</div>
<div>${vowelHTML}</div>
</div>
${blendHTML}`;
const { onset, nucleus, coda, exceptions } = phonotactics;
const onsetHTML = `<p><strong>Onset:</strong> ${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const nucleusHTML = `<p><strong>Nucleus:</strong> ${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const codaHTML = `<p><strong>Coda:</strong> ${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const exceptionsHTML = exceptions.trim().length > 0 ? '<p><strong>Exceptions:</strong></p><div>' + md(removeTags(exceptions)) + '</div>' : '';
const phonotacticsHTML = `<h3>Phonotactics</h3>
<div class="split three">
<div>${onsetHTML}</div>
<div>${nucleusHTML}</div>
<div>${codaHTML}</div>
</div>
${exceptionsHTML}`;
const orthographyHTML = '<h3>Orthography</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(orthography.notes)) + '</div>';
const grammarHTML = '<h3>Grammar</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(grammar.notes)) + '</div>';
detailsPanel.innerHTML = generalHTML + phonologyHTML + phonotacticsHTML + orthographyHTML + grammarHTML;
}
export function renderStats() {
const wordStats = getWordsStats();
const numberOfWordsHTML = `<p><strong>Number of Words</strong><br>${wordStats.numberOfWords.map(stat => `<span><span class="tag">${stat.name}</span><span class="tag">${stat.value}</span></span>`).join(' ')}</p>`;
const wordLengthHTML = `<p><strong>Word Length</strong><br><span><span class="tag">Shortest</span><span class="tag">${wordStats.wordLength.shortest}</span></span>
<span><span class="tag">Longest</span><span class="tag">${wordStats.wordLength.longest}</span></span>
<span><span class="tag">Average</span><span class="tag">${wordStats.wordLength.average}</span></span></p>`;
const letterDistributionHTML = `<p><strong>Letter Distribution</strong><br>${wordStats.letterDistribution.map(stat => `<span title="${stat.number} ${stat.letter}'s total"><span class="tag">${stat.letter}</span><span class="tag">${stat.percentage.toFixed(2)}</span></span>`).join(' ')}</p>`;
const totalLettersHTML = `<p><strong>${wordStats.totalLetters} Total Letters</strong></p>`;
detailsPanel.innerHTML = numberOfWordsHTML + wordLengthHTML + letterDistributionHTML + totalLettersHTML;
}
export function renderPartsOfSpeech(onlyOptions = false) {
let optionsHTML = '<option value=""></option>',
searchHTML = '<label>Unclassified <input type="checkbox" checked id="searchPartOfSpeech__None"></label>';
window.currentDictionary.partsOfSpeech.forEach(partOfSpeech => {
partOfSpeech = removeTags(partOfSpeech);
optionsHTML += `<option value="${partOfSpeech}">${partOfSpeech}</option>`;
searchHTML += `<label>${partOfSpeech} <input type="checkbox" checked id="searchPartOfSpeech_${slugify(partOfSpeech)}"></label>`;
});
searchHTML += `<a class="small button" id="checkAllFilters">Check All</a> <a class="small button" id="uncheckAllFilters">Uncheck All</a>`;
Array.from(document.getElementsByClassName('part-of-speech-select')).forEach(select => {
const selectedValue = select.value;
select.innerHTML = optionsHTML;
select.value = selectedValue;
});
if (!onlyOptions) {
document.getElementById('searchPartsOfSpeech').innerHTML = searchHTML;
}
setupSearchFilters();
}
export function renderWords() {
let wordsHTML = '';
let openEditForms = getOpenEditForms();
let words = false;
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
if (window.currentDictionary.words.length === 0) {
wordsHTML = `<article class="entry">
<header>
<h4 class="word">No Words Created</h4>
</header>
<dl>
<dt class="definition">Use the Word Form to create words or click the Help button below!</dt>
</dl>
</article>`;
} else {
words = getMatchingSearchWords();
if (words.length === 0) {
wordsHTML = `<article class="entry">
<header>
<h4 class="word">No Search Results</h4>
</header>
<dl>
<dt class="definition">Edit your search or filter to show words.</dt>
</dl>
</article>`;
}
if (openEditForms.length > 0) {
// Clone the dom nodes
openEditForms.forEach((wordFormId, index) => {
openEditForms[index] = document.getElementById(wordFormId.toString()).cloneNode(true);
});
}
// const { pageStart, pageEnd } = getPaginationData(words);
// words.slice(pageStart, pageEnd).forEach(originalWord => {
words.forEach((originalWord, displayIndex) => {
const word = highlightSearchTerm({
name: removeTags(originalWord.name),
pronunciation: removeTags(originalWord.pronunciation),
partOfSpeech: removeTags(originalWord.partOfSpeech),
definition: removeTags(originalWord.definition),
details: parseReferences(removeTags(originalWord.details)),
wordId: originalWord.wordId,
});
const homonymnNumber = getHomonymnNumber(originalWord);
const shareLink = window.currentDictionary.hasOwnProperty('externalID') ? getPublicLink() + '/' + word.wordId : '';
wordsHTML += renderAd(displayIndex);
wordsHTML += `<article class="entry" id="${word.wordId}">
<header>
<h4 class="word">${word.name}${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
<span class="pronunciation">${word.pronunciation}</span>
<span class="part-of-speech">${word.partOfSpeech}</span>
${isPublic ? `<a class="small button share-link" href="${shareLink}" target="_blank" title="Public Link to Word">&#10150;</a>` : ''}
<span class="small button word-option-button">Options</span>
<div class="word-option-list" style="display:none;">
<div class="word-option" id="edit_${word.wordId}">Edit</div>
<div class="word-option" id="delete_${word.wordId}">Delete</div>
</div>
</header>
<dl>
<dt class="definition">${word.definition}</dt>
<dd class="details">
${md(word.details)}
</dd>
</dl>
</article>`;
});
}
document.getElementById('entries').innerHTML = wordsHTML;
if (openEditForms.length > 0) {
// Clone the dom nodes
openEditForms.forEach(editForm => {
const entryElement = document.getElementById(editForm.id);
entryElement.parentNode.replaceChild(editForm, entryElement);
});
setupWordEditFormButtons();
}
setupWordOptionButtons();
setupWordOptionSelections();
// Show Search Results
const searchTerm = getSearchTerm();
const filters = getSearchFilters();
let resultsText = searchTerm !== '' || !filters.allPartsOfSpeechChecked ? (words ? words.length : 0).toString() + ' Results' : '';
resultsText += !filters.allPartsOfSpeechChecked ? ' (Filtered)' : '';
document.getElementById('searchResults').innerHTML = resultsText;
// renderPagination(words);
}
export function renderPagination(filteredWords) {
const paginationData = getPaginationData(filteredWords);
if (paginationData.pages > 0) {
let paginationHTML = (paginationData.currentPage > 0 ? '<span class="button prev-button">&laquo; Previous</span>' : '')
+ '<select class="page-selector">';
for (let i = 0; i < paginationData.pages; i++) {
paginationHTML += `<option value="${i}"${paginationData.currentPage === i ? ' selected' : ''}>Page ${i + 1}</option>`;
}
paginationHTML += '</select>'
+ (paginationData.currentPage < paginationData.pages - 1 ? '<span class="button next-button">Next &raquo;</span>' : '');
Array.from(document.getElementsByClassName('pagination')).forEach(pagination => {
pagination.innerHTML = paginationHTML;
});
setupPagination();
}
}
export function renderEditForm(wordId = false) {
wordId = typeof wordId.target === 'undefined' ? wordId : parseInt(this.id.replace('edit_', ''));
const word = window.currentDictionary.words.find(w => w.wordId === wordId);
if (word) {
const ipaPronunciationField = `<input id="wordPronunciation_${wordId}" class="ipa-field" maxlength="200" value="${word.pronunciation}"><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>`;
const plainPronunciationField = `<input id="wordPronunciation_${wordId}" maxlength="200" value="${word.pronunciation}">`;
const editForm = `<form id="editForm_${wordId}" class="edit-form">
<label>Word<span class="red">*</span><br>
<input id="wordName_${wordId}" maxlength="200" value="${word.name}">
</label>
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
${window.settings.useIPAPronunciationField ? ipaPronunciationField : plainPronunciationField}
</label>
<label>Part of Speech<br>
<select id="wordPartOfSpeech_${wordId}" class="part-of-speech-select">
<option value="${word.partOfSpeech}" selected>${word.partOfSpeech}</option>
</select>
</label>
<label>Definition<span class="red">*</span><br>
<input id="wordDefinition_${wordId}" maxlength="2500" 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.details}</textarea>
</label>
<div id="wordErrorMessage_${wordId}"></div>
<a class="button edit-save-changes" id="editWordButton_${wordId}">Save Changes</a>
<a class="button edit-cancel">Cancel Edit</a>
</form>`;
document.getElementById(wordId.toString()).innerHTML = editForm;
setupWordEditFormButtons();
renderPartsOfSpeech(true);
}
}
export function renderIPAHelp() {
import('./KeyboardFire/phondue/usage.html').then(html => {
renderInfoModal(html);
});
}
export function renderIPATable(ipaTableButton) {
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');
const modalElement = document.createElement('section');
modalElement.classList.add('modal', 'ipa-table-modal');
modalElement.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<header><label>${label} <input value="${textBox.value}" class="ipa-field"></label></header>
<section>
${ipaTableFile}
</section>
<footer><a class="button done-button">Done</a></footer>
</div>`;
document.body.appendChild(modalElement);
setupIPAFields();
setupIPATable(modalElement, textBox);
}
export function renderMaximizedTextbox(maximizeButton) {
maximizeButton = typeof maximizeButton.target === 'undefined' || maximizeButton.target === '' ? maximizeButton : maximizeButton.target;
const label = maximizeButton.parentElement.innerText.replace(/(\*|Maximize)/g, '').trim();
const textBox = maximizeButton.parentElement.querySelector('textarea');
const modalElement = document.createElement('section');
modalElement.classList.add('modal', 'maximize-modal');
modalElement.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<header><h3>${label}</h3></header>
<section>
<textarea>${textBox.value}</textarea>
</section>
<footer><a class="button done-button">Done</a></footer>
</div>`;
document.body.appendChild(modalElement);
setupMaximizeModal(modalElement, textBox);
}
export function renderInfoModal(content) {
const modalElement = document.createElement('section');
modalElement.classList.add('modal', 'info-modal');
modalElement.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<section class="info-modal">
<div class="content">
${content}
</div>
</section>
</div>`;
document.body.appendChild(modalElement);
setupInfoModal(modalElement);
}

141
src/js/render/details.js Normal file
View File

@ -0,0 +1,141 @@
import md from 'marked';
import { removeTags, slugify } from '../../helpers';
import { getWordsStats, hasToken } from '../utilities';
import { showSection } from '../displayToggles';
import { setupSearchFilters } from '../setupListeners/search';
import { parseReferences } from '../wordManagement';
import { getPublicLink } from '../account/utilities';
export function renderDictionaryDetails() {
renderName();
const tabs = document.querySelectorAll('#detailsSection nav li');
const shownTab = Array.from(tabs).find(tab => tab.classList.contains('active'));
if (shownTab) {
const tabName = shownTab.innerText.toLowerCase();
showSection(tabName);
}
}
export function renderName() {
const dictionaryName = removeTags(window.currentDictionary.name) + ' ' + removeTags(window.currentDictionary.specification);
const name = document.getElementById('dictionaryName');
name.innerHTML = dictionaryName;
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
const shareLinkElement = document.getElementById('dictionaryShare');
if (isPublic && !shareLinkElement) {
const shareLink = document.createElement('a');
shareLink.id = 'dictionaryShare';
shareLink.classList.add('button');
shareLink.style.float = 'right';
shareLink.href = getPublicLink();
shareLink.target = '_blank';
shareLink.title = 'Public Link to Dictionary';
shareLink.innerHTML = '&#10150;';
name.parentElement.insertBefore(shareLink, name);
} else if (isPublic && shareLinkElement) {
shareLinkElement.href = getPublicLink();
} else if (!isPublic && shareLinkElement) {
shareLinkElement.parentElement.removeChild(shareLinkElement);
}
}
export function renderDescription() {
const descriptionHTML = md(parseReferences(removeTags(window.currentDictionary.description)));
document.getElementById('detailsPanel').innerHTML = '<div class="content">' + descriptionHTML + '</div>';
}
export function renderDetails() {
const { partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
const { phonology, phonotactics, orthography, grammar } = window.currentDictionary.details;
const partsOfSpeechHTML = `<p><strong>Parts of Speech</strong><br>${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</div>`;
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order</strong><br>${
(alphabeticalOrder.length > 0 ? alphabeticalOrder : ['English Alphabet']).map(letter => `<span class="tag">${letter}</span>`).join(' ')
}</div>`;
const generalHTML = `<h3>General</h3>${partsOfSpeechHTML}${alphabeticalOrderHTML}`;
const { consonants, vowels, blends } = phonology
const consonantHTML = `<p><strong>Consonants</strong><br>${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const vowelHTML = `<p><strong>Vowels</strong><br>${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs&nbsp;/&nbsp;Blends</strong><br>${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
const phonologyNotesHTML = phonology.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(removeTags(phonology.notes)) + '</div>' : '';
const phonologyHTML = `<h3>Phonology</h3>
<div class="split two">
<div>${consonantHTML}</div>
<div>${vowelHTML}</div>
</div>
${blendHTML}
${phonologyNotesHTML}`;
const { onset, nucleus, coda } = phonotactics;
const onsetHTML = `<p><strong>Onset</strong><br>${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const nucleusHTML = `<p><strong>Nucleus</strong><br>${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const codaHTML = `<p><strong>Coda</strong><br>${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const phonotacticsNotesHTML = phonotactics.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(removeTags(phonotactics.notes)) + '</div>' : '';
const phonotacticsHTML = onset.length + nucleus.length + coda.length + phonotacticsNotesHTML.length > 0
? `<h3>Phonotactics</h3>
${onset.length > 0 || nucleus.length > 0 || coda.length > 0
? `<div class="split three">
<div>${onsetHTML}</div>
<div>${nucleusHTML}</div>
<div>${codaHTML}</div>
</div>` : ''}
${phonotacticsNotesHTML}`
: '';
const { translations } = orthography;
const translationsHTML = translations.length > 0 ? `<p><strong>Translations</strong><br>${translations.map(translation => {
translation = translation.split('=').map(value => value.trim());
if (translation.length > 1 && translation[0] !== '' && translation[1] !== '') {
return `<span><span class="tag">${translation[0]}</span><span class="tag orthographic-translation">${translation[1]}</span></span>`;
}
return false;
}).filter(html => html !== false).join(' ')}</p>` : '';
const orthographyNotesHTML = orthography.notes.trim().length > 0 ? '<p><strong>Notes</strong><br>' + md(removeTags(orthography.notes)) + '</div>' : '';
const orthographyHTML = translations.length + orthographyNotesHTML.length > 0
? `<h3>Orthography</h3>
${translationsHTML}
${orthographyNotesHTML}`
: '';
const grammarHTML = grammar.notes.trim().length > 0 ? '<h3>Grammar</h3><div>'
+ (grammar.notes.trim().length > 0 ? md(removeTags(grammar.notes)) : '')
+ '</div>' : '';
detailsPanel.innerHTML = generalHTML + phonologyHTML + phonotacticsHTML + orthographyHTML + grammarHTML;
}
export function renderStats() {
const wordStats = getWordsStats();
const numberOfWordsHTML = `<p><strong>Number of Words</strong><br>${wordStats.numberOfWords.map(stat => `<span><span class="tag">${stat.name}</span><span class="tag">${stat.value}</span></span>`).join(' ')}</p>`;
const wordLengthHTML = `<p><strong>Word Length</strong><br><span><span class="tag">Shortest</span><span class="tag">${wordStats.wordLength.shortest}</span></span>
<span><span class="tag">Longest</span><span class="tag">${wordStats.wordLength.longest}</span></span>
<span><span class="tag">Average</span><span class="tag">${wordStats.wordLength.average}</span></span></p>`;
const letterDistributionHTML = `<p><strong>Letter Distribution</strong><br>${wordStats.letterDistribution.map(stat => `<span title="${stat.number} ${stat.letter}'s total"><span class="tag">${stat.letter}</span><span class="tag">${stat.percentage.toFixed(2)}</span></span>`).join(' ')}</p>`;
const totalLettersHTML = `<p><strong>${wordStats.totalLetters} Total Letters</strong></p>`;
detailsPanel.innerHTML = numberOfWordsHTML + wordLengthHTML + letterDistributionHTML + totalLettersHTML;
}
export function renderPartsOfSpeech(onlyOptions = false) {
let optionsHTML = '<option value=""></option>',
searchHTML = '<label>Unclassified <input type="checkbox" checked id="searchPartOfSpeech__None"></label>';
window.currentDictionary.partsOfSpeech.forEach(partOfSpeech => {
partOfSpeech = removeTags(partOfSpeech);
optionsHTML += `<option value="${partOfSpeech}">${partOfSpeech}</option>`;
searchHTML += `<label>${partOfSpeech} <input type="checkbox" checked id="searchPartOfSpeech_${slugify(partOfSpeech)}"></label>`;
});
searchHTML += `<a class="small button" id="checkAllFilters">Check All</a> <a class="small button" id="uncheckAllFilters">Uncheck All</a>`;
Array.from(document.getElementsByClassName('part-of-speech-select')).forEach(select => {
const selectedValue = select.value;
select.innerHTML = optionsHTML;
select.value = selectedValue;
});
if (!onlyOptions) {
document.getElementById('searchPartsOfSpeech').innerHTML = searchHTML;
}
setupSearchFilters();
}

29
src/js/render/index.js Normal file
View File

@ -0,0 +1,29 @@
import { renderDictionaryDetails, renderPartsOfSpeech } from './details';
import { renderWords } from './words';
export function renderAll() {
renderTheme();
renderCustomCSS();
renderDictionaryDetails();
renderPartsOfSpeech();
renderWords();
}
export function renderTheme() {
const { theme } = window.currentDictionary.settings;
document.body.id = theme + 'Theme';
}
export function renderCustomCSS() {
const { customCSS } = window.currentDictionary.settings;
const stylingId = 'customCSS';
const stylingElement = document.getElementById(stylingId);
if (!stylingElement) {
const styling = document.createElement('style');
styling.id = stylingId;
styling.innerHTML = customCSS;
document.body.appendChild(styling);
} else {
stylingElement.innerHTML = customCSS;
}
}

74
src/js/render/modals.js Normal file
View File

@ -0,0 +1,74 @@
import { setupIPAFields } from '../setupListeners';
import {
setupMaximizeModal,
setupInfoModal,
setupIPATable,
} from '../setupListeners/modals';
import ipaTableFile from '../KeyboardFire/phondue/ipa-table.html';
export function renderIPAHelp() {
import('../KeyboardFire/phondue/usage.html').then(html => {
renderInfoModal(html);
});
}
export function renderIPATable(ipaTableButton) {
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');
const modalElement = document.createElement('section');
modalElement.classList.add('modal', 'ipa-table-modal');
modalElement.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<header><label>${label} <input value="${textBox.value}" class="ipa-field"></label></header>
<section>
${ipaTableFile}
</section>
<footer><a class="button done-button">Done</a></footer>
</div>`;
document.body.appendChild(modalElement);
setupIPAFields();
setupIPATable(modalElement, textBox);
}
export function renderMaximizedTextbox(maximizeButton) {
maximizeButton = typeof maximizeButton.target === 'undefined' || maximizeButton.target === '' ? maximizeButton : maximizeButton.target;
const label = maximizeButton.parentElement.innerText.replace(/(\*|Maximize)/g, '').trim();
const textBox = maximizeButton.parentElement.querySelector('textarea');
const modalElement = document.createElement('section');
modalElement.classList.add('modal', 'maximize-modal');
modalElement.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<header><h3>${label}</h3></header>
<section>
<textarea>${textBox.value}</textarea>
</section>
<footer><a class="button done-button">Done</a></footer>
</div>`;
document.body.appendChild(modalElement);
setupMaximizeModal(modalElement, textBox);
}
export function renderInfoModal(content) {
const modalElement = document.createElement('section');
modalElement.classList.add('modal', 'info-modal');
modalElement.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<section class="info-modal">
<div class="content">
${content}
</div>
</section>
</div>`;
document.body.appendChild(modalElement);
setupInfoModal(modalElement);
}

172
src/js/render/words.js Normal file
View File

@ -0,0 +1,172 @@
import md from 'marked';
import { removeTags } from '../../helpers';
import { getHomonymnNumber, hasToken } from '../utilities';
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from '../search';
import {
setupWordOptionButtons,
setupPagination,
setupWordOptionSelections,
setupWordEditFormButtons,
} from '../setupListeners/words';
import { getPaginationData } from '../pagination';
import { getOpenEditForms, translateOrthography, parseReferences } from '../wordManagement';
import { renderAd } from '../ads';
import { getPublicLink } from '../account/utilities';
import { renderPartsOfSpeech } from './details';
export function renderWords() {
let wordsHTML = '';
let openEditForms = getOpenEditForms();
let words = false;
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
if (window.currentDictionary.words.length === 0) {
wordsHTML = `<article class="entry">
<header>
<h4 class="word">No Words Created</h4>
</header>
<dl>
<dt class="definition">Use the Word Form to create words or click the Help button below!</dt>
</dl>
</article>`;
} else {
words = getMatchingSearchWords();
if (words.length === 0) {
wordsHTML = `<article class="entry">
<header>
<h4 class="word">No Search Results</h4>
</header>
<dl>
<dt class="definition">Edit your search or filter to show words.</dt>
</dl>
</article>`;
}
if (openEditForms.length > 0) {
// Clone the dom nodes
openEditForms.forEach((wordFormId, index) => {
openEditForms[index] = document.getElementById(wordFormId.toString()).cloneNode(true);
});
}
// const { pageStart, pageEnd } = getPaginationData(words);
// words.slice(pageStart, pageEnd).forEach(originalWord => {
words.forEach((originalWord, displayIndex) => {
const word = highlightSearchTerm({
name: removeTags(originalWord.name),
pronunciation: removeTags(originalWord.pronunciation),
partOfSpeech: removeTags(originalWord.partOfSpeech),
definition: removeTags(originalWord.definition),
details: parseReferences(removeTags(originalWord.details)),
wordId: originalWord.wordId,
});
const homonymnNumber = getHomonymnNumber(originalWord);
const shareLink = window.currentDictionary.hasOwnProperty('externalID') ? getPublicLink() + '/' + word.wordId : '';
wordsHTML += renderAd(displayIndex);
let wordNameDisplay = translateOrthography(word.name);
wordsHTML += `<article class="entry" id="${word.wordId}">
<header>
<h4 class="word"><span class="orthographic-translation">${wordNameDisplay}</span>${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
<span class="pronunciation">${word.pronunciation}</span>
<span class="part-of-speech">${word.partOfSpeech}</span>
${isPublic ? `<a class="small button share-link" href="${shareLink}" target="_blank" title="Public Link to Word">&#10150;</a>` : ''}
<span class="small button word-option-button">Options</span>
<div class="word-option-list" style="display:none;">
<div class="word-option" id="edit_${word.wordId}">Edit</div>
<div class="word-option" id="delete_${word.wordId}">Delete</div>
</div>
</header>
<dl>
<dt class="definition">${word.definition}</dt>
<dd class="details">
${md(word.details)}
</dd>
</dl>
</article>`;
});
}
document.getElementById('entries').innerHTML = wordsHTML;
if (openEditForms.length > 0) {
// Clone the dom nodes
openEditForms.forEach(editForm => {
const entryElement = document.getElementById(editForm.id);
entryElement.parentNode.replaceChild(editForm, entryElement);
});
setupWordEditFormButtons();
}
setupWordOptionButtons();
setupWordOptionSelections();
// Show Search Results
const searchTerm = getSearchTerm();
const filters = getSearchFilters();
let resultsText = searchTerm !== '' || !filters.allPartsOfSpeechChecked ? (words ? words.length : 0).toString() + ' Results' : '';
resultsText += !filters.allPartsOfSpeechChecked ? ' (Filtered)' : '';
document.getElementById('searchResults').innerHTML = resultsText;
// renderPagination(words);
}
export function renderPagination(filteredWords) {
const paginationData = getPaginationData(filteredWords);
if (paginationData.pages > 0) {
let paginationHTML = (paginationData.currentPage > 0 ? '<span class="button prev-button">&laquo; Previous</span>' : '')
+ '<select class="page-selector">';
for (let i = 0; i < paginationData.pages; i++) {
paginationHTML += `<option value="${i}"${paginationData.currentPage === i ? ' selected' : ''}>Page ${i + 1}</option>`;
}
paginationHTML += '</select>'
+ (paginationData.currentPage < paginationData.pages - 1 ? '<span class="button next-button">Next &raquo;</span>' : '');
Array.from(document.getElementsByClassName('pagination')).forEach(pagination => {
pagination.innerHTML = paginationHTML;
});
setupPagination();
}
}
export function renderEditForm(wordId = false) {
wordId = typeof wordId.target === 'undefined' ? wordId : parseInt(this.id.replace('edit_', ''));
const word = window.currentDictionary.words.find(w => w.wordId === wordId);
if (word) {
const ipaPronunciationField = `<input id="wordPronunciation_${wordId}" class="ipa-field" maxlength="200" value="${word.pronunciation}"><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>`;
const plainPronunciationField = `<input id="wordPronunciation_${wordId}" maxlength="200" value="${word.pronunciation}">`;
const editForm = `<form id="editForm_${wordId}" class="edit-form">
<label>Word<span class="red">*</span><br>
<input id="wordName_${wordId}" maxlength="200" value="${word.name}">
</label>
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
${window.settings.useIPAPronunciationField ? ipaPronunciationField : plainPronunciationField}
</label>
<label>Part of Speech<br>
<select id="wordPartOfSpeech_${wordId}" class="part-of-speech-select">
<option value="${word.partOfSpeech}" selected>${word.partOfSpeech}</option>
</select>
</label>
<label>Definition<span class="red">*</span><br>
<input id="wordDefinition_${wordId}" maxlength="2500" 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.details}</textarea>
</label>
<div id="wordErrorMessage_${wordId}"></div>
<a class="button edit-save-changes" id="editWordButton_${wordId}">Save Changes</a>
<a class="button edit-cancel">Cancel Edit</a>
</form>`;
document.getElementById(wordId.toString()).innerHTML = editForm;
setupWordEditFormButtons();
renderPartsOfSpeech(true);
}
}

View File

@ -1,6 +1,7 @@
import { cloneObject, getIndicesOf } from "../helpers";
import removeDiacritics from "./StackOverflow/removeDiacritics";
import { renderWords } from "./render";
import { renderWords } from "./render/words";
import { translateOrthography, parseReferences } from "./wordManagement";
export function showSearchModal() {
document.getElementById('searchModal').style.display = 'block';
@ -22,6 +23,7 @@ export function getSearchFilters() {
caseSensitive: document.getElementById('searchCaseSensitive').checked,
ignoreDiacritics: document.getElementById('searchIgnoreDiacritics').checked,
exact: document.getElementById('searchExactWords').checked,
orthography: document.getElementById('searchOrthography').checked,
name: document.getElementById('searchIncludeName').checked,
definition: document.getElementById('searchIncludeDefinition').checked,
details: document.getElementById('searchIncludeDetails').checked,
@ -53,11 +55,13 @@ export function getMatchingSearchWords() {
}).filter(word => {
searchTerm = filters.ignoreDiacritics ? removeDiacritics(searchTerm) : searchTerm;
searchTerm = filters.caseSensitive ? searchTerm : searchTerm.toLowerCase();
let name = filters.ignoreDiacritics ? removeDiacritics(word.name) : word.name;
let name = filters.orthography ? translateOrthography(word.name) : word.name;
name = filters.ignoreDiacritics ? removeDiacritics(name) : name;
name = filters.caseSensitive ? name : name.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;
let details = filters.orthography ? parseReferences(word.details) : word.details;
details = filters.ignoreDiacritics ? removeDiacritics(details) : details;
details = filters.caseSensitive ? details : details.toLowerCase();
const isInName = filters.name && (filters.exact
@ -80,6 +84,10 @@ export function highlightSearchTerm(word) {
if (searchTerm) {
const filters = getSearchFilters();
const markedUpWord = cloneObject(word);
if (filters.orthography) {
markedUpWord.name = translateOrthography(markedUpWord.name);
markedUpWord.details = parseReferences(markedUpWord.details);
}
if (filters.ignoreDiacritics) {
const searchTermLength = searchTerm.length;
searchTerm = removeDiacritics(searchTerm);

View File

@ -1,8 +1,8 @@
import { SETTINGS_KEY, DEFAULT_SETTINGS } from "../constants";
import { cloneObject, removeTags } from "../helpers";
import { usePhondueDigraphs } from "./KeyboardFire/phondue/ipaField";
import { renderWords } from "./render";
import { addMessage, hasToken } from "./utilities";
import { renderWords } from "./render/words";
import { addMessage, hasToken, objectValuesAreDifferent } from "./utilities";
import { enableHotKeys, disableHotKeys } from "./hotkeys";
export function loadSettings() {
@ -27,9 +27,10 @@ export function openSettingsModal() {
}
export function saveSettingsModal() {
window.settings.useIPAPronunciationField = document.getElementById('settingsUseIPA').checked;
window.settings.useHotkeys = document.getElementById('settingsUseHotkeys').checked;
window.settings.defaultTheme = document.getElementById('settingsDefaultTheme').value;
const updatedSettings = cloneObject(window.settings);
updatedSettings.useIPAPronunciationField = document.getElementById('settingsUseIPA').checked;
updatedSettings.useHotkeys = document.getElementById('settingsUseHotkeys').checked;
updatedSettings.defaultTheme = document.getElementById('settingsDefaultTheme').value;
if (hasToken()) {
import('./account/index.js').then(account => {
@ -40,19 +41,30 @@ export function saveSettingsModal() {
email = window.account.email;
emailField.value = email;
}
window.account.email = email;
window.account.publicName = removeTags(publicName.value).trim();
window.account.allowEmails = document.getElementById('accountSettingsAllowEmails').checked;
const updatedAccount = cloneObject(window.account);
updatedAccount.email = email;
updatedAccount.publicName = removeTags(publicName.value).trim();
updatedAccount.allowEmails = document.getElementById('accountSettingsAllowEmails').checked;
const newPassword = document.getElementById('accountSettingsNewPassword').value;
account.editAccount(Object.assign({ newPassword }, window.account));
if (objectValuesAreDifferent(updatedAccount, window.account)) {
window.account = Object.assign(window.account, updatedAccount);
account.editAccount(Object.assign({ newPassword }, window.account));
} else {
addMessage('No changes made to Account.');
}
});
}
saveSettings();
toggleHotkeysEnabled();
toggleIPAPronunciationFields();
if (objectValuesAreDifferent(updatedSettings, window.settings)) {
window.settings = Object.assign(window.settings, updatedSettings);
saveSettings();
toggleHotkeysEnabled();
toggleIPAPronunciationFields();
} else {
addMessage('No changes made to Settings.');
}
}
export function saveAndCloseSettingsModal() {

View File

@ -1,391 +0,0 @@
import {showSection, hideDetailsPanel} from './displayToggles';
import { renderWords, renderEditForm, renderMaximizedTextbox, renderInfoModal, renderIPATable, renderIPAHelp } from './render';
import { confirmEditWord, cancelEditWord, confirmDeleteWord, submitWordForm } from './wordManagement';
import { openEditModal, saveEditModal, saveAndCloseEditModal, exportDictionary, exportWords, importDictionary, importWords, confirmDeleteDictionary } 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, checkAllPartsOfSpeechFilters, uncheckAllPartsOfSpeechFilters } from './search';
import helpFile from '../markdown/help.md';
import termsFile from '../markdown/terms.md';
import privacyFile from '../markdown/privacy.md';
import { dismiss, isDismissed } from './announcements';
import { fadeOutElement } from './utilities';
export default function setupListeners() {
setupAnnouncements();
setupDetailsTabs();
setupHeaderButtons();
setupWordForm();
setupMobileWordFormButton();
setupInfoButtons();
if (window.settings.useHotkeys) {
enableHotKeys();
}
}
export function setupHeaderButtons() {
setupSearchBar();
setupSettingsModal();
document.getElementById('loginCreateAccountButton').addEventListener('click', () => {
import('./account/index.js').then(account => {
account.showLoginForm();
});
});
}
function setupAnnouncements() {
const announcements = document.querySelectorAll('.announcement');
Array.from(announcements).forEach(announcement => {
if (announcement.id && isDismissed(announcement.id)) {
fadeOutElement(announcement);
} else {
announcement.querySelector('.close-button').addEventListener('click', () => dismiss(announcement));
}
});
}
function setupDetailsTabs() {
const tabs = document.querySelectorAll('#detailsSection nav li');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const section = tab.innerText.toLowerCase();
if (section === 'edit') {
openEditModal();
// import('../test.js').then(function (test) {
// // Render page
// test.aaa();
// });
} else {
const isActive = tab.classList.contains('active');
tabs.forEach(t => t.classList.remove('active'));
if (isActive) {
hideDetailsPanel();
} else {
tab.classList.add('active');
showSection(section);
}
}
});
});
setupEditFormTabs();
setupEditFormInteractions();
setupEditFormButtons();
}
function setupEditFormTabs() {
const tabs = document.querySelectorAll('#editModal nav li');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => {
t.classList.remove('active');
document.getElementById('edit' + t.innerText + 'Tab').style.display = 'none';
});
tab.classList.add('active');
document.getElementById('edit' + tab.innerText + 'Tab').style.display = '';
});
});
}
function setupEditFormInteractions() {
const preventDuplicatesBox = document.getElementById('editPreventDuplicates');
preventDuplicatesBox.addEventListener('change', () => {
const caseSensitiveBox = document.getElementById('editCaseSensitive');
if (preventDuplicatesBox.checked) {
caseSensitiveBox.disabled = false;
} else {
caseSensitiveBox.disabled = true;
caseSensitiveBox.checked = false;
}
});
}
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);
document.getElementById('deleteDictionaryButton').addEventListener('click', confirmDeleteDictionary);
setupMaximizeButtons();
}
function setupSearchBar() {
const searchBox = document.getElementById('searchBox'),
clearSearchButton = document.getElementById('clearSearchButton'),
openSearchModal = document.getElementById('openSearchModal'),
searchIgnoreDiacritics = document.getElementById('searchIgnoreDiacritics'),
searchExactWords = document.getElementById('searchExactWords'),
searchIncludeDetails = document.getElementById('searchIncludeDetails');
searchBox.addEventListener('change', () => {
renderWords();
});
searchBox.addEventListener('input', event => {
openSearchModal.value = event.target.value;
});
clearSearchButton.addEventListener('click', clearSearchText);
openSearchModal.addEventListener('click', showSearchModal);
const toggleDetailsCheck = function() {
if (searchExactWords.checked) {
searchIncludeDetails.checked = false;
searchIncludeDetails.disabled = true;
} else {
searchIncludeDetails.disabled = false;
searchIncludeDetails.checked = true;
}
}
searchIgnoreDiacritics.addEventListener('change', () => {
if (searchIgnoreDiacritics.checked) {
searchExactWords.checked = false;
searchExactWords.disabled = true;
} else {
searchExactWords.disabled = false;
}
toggleDetailsCheck();
});
searchExactWords.addEventListener('change', () => toggleDetailsCheck());
}
export function setupSearchFilters() {
const searchFilters = document.querySelectorAll('#searchOptions input[type="checkbox"]'),
searchBox = document.getElementById('searchBox');
Array.from(searchFilters).concat([searchBox]).forEach(filter => {
filter.removeEventListener('change', renderWords);
filter.addEventListener('change', renderWords);
});
document.getElementById('checkAllFilters').removeEventListener('click', checkAllPartsOfSpeechFilters);
document.getElementById('checkAllFilters').addEventListener('click', checkAllPartsOfSpeechFilters);
document.getElementById('uncheckAllFilters').removeEventListener('click', uncheckAllPartsOfSpeechFilters);
document.getElementById('uncheckAllFilters').addEventListener('click', uncheckAllPartsOfSpeechFilters);
}
function setupWordForm() {
const wordForm = document.getElementById('wordForm'),
addWordButton = document.getElementById('addWordButton');
wordForm.addEventListener('submit', event => {
// Allow semantic form and prevent it from getting submitted
event.preventDefault();
return false;
});
addWordButton.addEventListener('click', submitWordForm);
setupIPAFields();
setupMaximizeButtons();
}
export function setupWordOptionButtons() {
const wordOptionButtons = document.getElementsByClassName('word-option-button');
const showWordOptions = function() {
this.parentElement.querySelector('.word-option-list').style.display = '';
}
const hideWordOptions = function(e) {
if (!e.target.classList.contains('word-option-button')) {
const allWordOptions = document.querySelectorAll('.word-option-list');
Array.from(allWordOptions).forEach(wordOptionList => {
wordOptionList.style.display = 'none';
});
}
}
Array.from(wordOptionButtons).forEach(button => {
button.removeEventListener('click', showWordOptions);
button.addEventListener('click', showWordOptions);
});
document.removeEventListener('click', hideWordOptions);
document.addEventListener('click', hideWordOptions);
}
export function setupWordOptionSelections() {
const wordOptions = document.getElementsByClassName('word-option');
Array.from(wordOptions).forEach(option => {
switch (option.innerText) {
case 'Edit': {
option.removeEventListener('click', renderEditForm);
option.addEventListener('click', renderEditForm);
break;
}
case 'Delete': {
option.removeEventListener('click', confirmDeleteWord);
option.addEventListener('click', confirmDeleteWord);
break;
}
}
});
}
export function setupSettingsModal() {
document.getElementById('settingsButton').addEventListener('click', openSettingsModal);
document.getElementById('settingsSave').addEventListener('click', saveSettingsModal);
document.getElementById('settingsSaveAndClose').addEventListener('click', saveAndCloseSettingsModal);
}
export function setupWordEditFormButtons() {
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);
});
Array.from(cancelChangesButtons).forEach(button => {
button.removeEventListener('click', cancelEditWord);
button.addEventListener('click', cancelEditWord);
});
setupIPAFields();
setupMaximizeButtons();
}
export function setupMobileWordFormButton() {
const mobileButton = document.getElementById('mobileWordFormShow'),
wordForm = document.getElementById('wordForm');
mobileButton.addEventListener('click', () => {
if (mobileButton.innerText === '+') {
wordForm.style.display = 'block';
mobileButton.innerHTML = '&times;&#xFE0E;';
} else {
wordForm.style.display = '';
mobileButton.innerHTML = '+';
}
});
}
export function setupPagination() {
const nextButtons = document.getElementsByClassName('next-button'),
prevButtons = document.getElementsByClassName('prev-button'),
pageSelectors = document.getElementsByClassName('page-selector');
Array.from(nextButtons).forEach(nextButton => {
nextButton.removeEventListener('click', goToNextPage);
nextButton.addEventListener('click', goToNextPage);
});
Array.from(prevButtons).forEach(prevButton => {
prevButton.removeEventListener('click', goToPreviousPage);
prevButton.addEventListener('click', goToPreviousPage);
});
Array.from(pageSelectors).forEach(pageSelector => {
pageSelector.removeEventListener('change', goToPage);
pageSelector.addEventListener('change', goToPage);
});
}
export function setupIPAFields() {
if (window.settings.useIPAPronunciationField) {
const ipaFields = document.getElementsByClassName('ipa-field');
Array.from(ipaFields).forEach(field => {
field.removeEventListener('keypress', usePhondueDigraphs);
field.addEventListener('keypress', usePhondueDigraphs);
});
}
setupIPAButtons();
}
export function setupIPAButtons() {
const ipaTableButtons = document.getElementsByClassName('ipa-table-button'),
ipaFieldHelpButtons = document.getElementsByClassName('ipa-field-help-button');
Array.from(ipaTableButtons).forEach(button => {
button.removeEventListener('click', renderIPATable);
button.addEventListener('click', renderIPATable);
});
Array.from(ipaFieldHelpButtons).forEach(button => {
button.removeEventListener('click', renderIPAHelp);
button.addEventListener('click', renderIPAHelp);
});
}
export function setupIPATable(modal, textBox) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
headerTextBox = modal.querySelector('header input'),
ipaButtons = modal.querySelectorAll('.td-btn button');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
textBox.focus();
const endOfTextbox = textBox.value.length;
setSelectionRange(textBox, endOfTextbox, endOfTextbox);
modal.parentElement.removeChild(modal);
});
});
headerTextBox.addEventListener('change', () => {
textBox.value = headerTextBox.value;
});
Array.from(ipaButtons).forEach(button => {
button.addEventListener('click', () => {
insertAtCursor(headerTextBox, button.innerText);
textBox.value = headerTextBox.value;
});
});
setTimeout(() => {
headerTextBox.focus();
const endOfTextbox = headerTextBox.value.length;
setSelectionRange(headerTextBox, endOfTextbox, endOfTextbox);
}, 1);
}
export function setupMaximizeButtons() {
const maximizeButtons = document.getElementsByClassName('maximize-button');
Array.from(maximizeButtons).forEach(button => {
button.removeEventListener('click', renderMaximizedTextbox);
button.addEventListener('click', renderMaximizedTextbox);
});
}
export function setupMaximizeModal(modal, textBox) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
maximizedTextBox = modal.querySelector('textarea');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
const selection = getInputSelection(maximizedTextBox);
textBox.focus();
setSelectionRange(textBox, selection.start, selection.end);
modal.parentElement.removeChild(modal);
});
});
maximizedTextBox.addEventListener('change', () => {
textBox.value = maximizedTextBox.value;
})
setTimeout(() => {
const selection = getInputSelection(textBox);
maximizedTextBox.focus();
setSelectionRange(maximizedTextBox, selection.start, selection.end);
}, 1);
}
export function setupInfoButtons() {
document.getElementById('helpInfoButton').addEventListener('click', () => {
renderInfoModal(helpFile);
});
document.getElementById('termsInfoButton').addEventListener('click', () => {
renderInfoModal(termsFile);
});
document.getElementById('privacyInfoButton').addEventListener('click', () => {
renderInfoModal(privacyFile);
});
}
export function setupInfoModal(modal) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
modal.parentElement.removeChild(modal);
});
});
}

View File

@ -0,0 +1,52 @@
import { renderMaximizedTextbox, renderInfoModal, renderIPATable, renderIPAHelp } from '../render/modals';
import helpFile from '../../markdown/help.md';
import termsFile from '../../markdown/terms.md';
import privacyFile from '../../markdown/privacy.md';
import { setupSearchBar } from './search';
import { setupSettingsModal } from './modals';
export function setupHeaderButtons() {
setupSearchBar();
setupSettingsModal();
document.getElementById('loginCreateAccountButton').addEventListener('click', () => {
import('../account/index.js').then(account => {
account.showLoginForm();
});
});
}
export function setupIPAButtons() {
const ipaTableButtons = document.getElementsByClassName('ipa-table-button'),
ipaFieldHelpButtons = document.getElementsByClassName('ipa-field-help-button');
Array.from(ipaTableButtons).forEach(button => {
button.removeEventListener('click', renderIPATable);
button.addEventListener('click', renderIPATable);
});
Array.from(ipaFieldHelpButtons).forEach(button => {
button.removeEventListener('click', renderIPAHelp);
button.addEventListener('click', renderIPAHelp);
});
}
export function setupMaximizeButtons() {
const maximizeButtons = document.getElementsByClassName('maximize-button');
Array.from(maximizeButtons).forEach(button => {
button.removeEventListener('click', renderMaximizedTextbox);
button.addEventListener('click', renderMaximizedTextbox);
});
}
export function setupInfoButtons() {
document.getElementById('helpInfoButton').addEventListener('click', () => {
renderInfoModal(helpFile);
});
document.getElementById('termsInfoButton').addEventListener('click', () => {
renderInfoModal(termsFile);
});
document.getElementById('privacyInfoButton').addEventListener('click', () => {
renderInfoModal(privacyFile);
});
}

View File

@ -0,0 +1,66 @@
import { showSection, hideDetailsPanel } from '../displayToggles';
import { openEditModal, saveEditModal, saveAndCloseEditModal, exportDictionary, exportWords, importDictionary, importWords, confirmDeleteDictionary } from '../dictionaryManagement';
import { setupMaximizeButtons } from './buttons';
export function setupDetailsTabs() {
const tabs = document.querySelectorAll('#detailsSection nav li');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const section = tab.innerText.toLowerCase();
if (section === 'edit') {
openEditModal();
} else {
const isActive = tab.classList.contains('active');
tabs.forEach(t => t.classList.remove('active'));
if (isActive) {
hideDetailsPanel();
} else {
tab.classList.add('active');
showSection(section);
}
}
});
});
setupEditFormTabs();
setupEditFormInteractions();
setupEditFormButtons();
}
function setupEditFormTabs() {
const tabs = document.querySelectorAll('#editModal nav li');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => {
t.classList.remove('active');
document.getElementById('edit' + t.innerText + 'Tab').style.display = 'none';
});
tab.classList.add('active');
document.getElementById('edit' + tab.innerText + 'Tab').style.display = '';
});
});
}
function setupEditFormInteractions() {
const preventDuplicatesBox = document.getElementById('editPreventDuplicates');
preventDuplicatesBox.addEventListener('change', () => {
const caseSensitiveBox = document.getElementById('editCaseSensitive');
if (preventDuplicatesBox.checked) {
caseSensitiveBox.disabled = false;
} else {
caseSensitiveBox.disabled = true;
caseSensitiveBox.checked = false;
}
});
}
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);
document.getElementById('deleteDictionaryButton').addEventListener('click', confirmDeleteDictionary);
setupMaximizeButtons();
}

View File

@ -0,0 +1,42 @@
import { usePhondueDigraphs } from '../KeyboardFire/phondue/ipaField';
import { enableHotKeys } from '../hotkeys';
import { dismiss, isDismissed } from '../announcements';
import { fadeOutElement } from '../utilities';
import { setupDetailsTabs } from './details';
import { setupWordForm, setupMobileWordFormButton } from './words';
import { setupIPAButtons, setupHeaderButtons, setupInfoButtons } from './buttons';
export default function setupListeners() {
setupAnnouncements();
setupDetailsTabs();
setupHeaderButtons();
setupWordForm();
setupMobileWordFormButton();
setupInfoButtons();
if (window.settings.useHotkeys) {
enableHotKeys();
}
}
function setupAnnouncements() {
const announcements = document.querySelectorAll('.announcement');
Array.from(announcements).forEach(announcement => {
if (announcement.id && isDismissed(announcement.id)) {
fadeOutElement(announcement);
} else {
announcement.querySelector('.close-button').addEventListener('click', () => dismiss(announcement));
}
});
}
export function setupIPAFields() {
if (window.settings.useIPAPronunciationField) {
const ipaFields = document.getElementsByClassName('ipa-field');
Array.from(ipaFields).forEach(field => {
field.removeEventListener('keypress', usePhondueDigraphs);
field.addEventListener('keypress', usePhondueDigraphs);
});
}
setupIPAButtons();
}

View File

@ -0,0 +1,71 @@
import { insertAtCursor, getInputSelection, setSelectionRange } from '../StackOverflow/inputCursorManagement';
import { openSettingsModal, saveSettingsModal, saveAndCloseSettingsModal } from '../settings';
export function setupSettingsModal() {
document.getElementById('settingsButton').addEventListener('click', openSettingsModal);
document.getElementById('settingsSave').addEventListener('click', saveSettingsModal);
document.getElementById('settingsSaveAndClose').addEventListener('click', saveAndCloseSettingsModal);
}
export function setupIPATable(modal, textBox) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
headerTextBox = modal.querySelector('header input'),
ipaButtons = modal.querySelectorAll('.td-btn button');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
textBox.focus();
const endOfTextbox = textBox.value.length;
setSelectionRange(textBox, endOfTextbox, endOfTextbox);
modal.parentElement.removeChild(modal);
});
});
headerTextBox.addEventListener('change', () => {
textBox.value = headerTextBox.value;
});
Array.from(ipaButtons).forEach(button => {
button.addEventListener('click', () => {
insertAtCursor(headerTextBox, button.innerText);
textBox.value = headerTextBox.value;
});
});
setTimeout(() => {
headerTextBox.focus();
const endOfTextbox = headerTextBox.value.length;
setSelectionRange(headerTextBox, endOfTextbox, endOfTextbox);
}, 1);
}
export function setupMaximizeModal(modal, textBox) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
maximizedTextBox = modal.querySelector('textarea');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
const selection = getInputSelection(maximizedTextBox);
textBox.focus();
setSelectionRange(textBox, selection.start, selection.end);
modal.parentElement.removeChild(modal);
});
});
maximizedTextBox.addEventListener('change', () => {
textBox.value = maximizedTextBox.value;
})
setTimeout(() => {
const selection = getInputSelection(textBox);
maximizedTextBox.focus();
setSelectionRange(maximizedTextBox, selection.start, selection.end);
}, 1);
}
export function setupInfoModal(modal) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
modal.parentElement.removeChild(modal);
});
});
}

View File

@ -0,0 +1,54 @@
import { renderWords } from '../render/words';
import { showSearchModal, clearSearchText, checkAllPartsOfSpeechFilters, uncheckAllPartsOfSpeechFilters } from '../search';
export function setupSearchBar() {
const searchBox = document.getElementById('searchBox'),
clearSearchButton = document.getElementById('clearSearchButton'),
openSearchModal = document.getElementById('openSearchModal'),
searchIgnoreDiacritics = document.getElementById('searchIgnoreDiacritics'),
searchExactWords = document.getElementById('searchExactWords'),
searchIncludeDetails = document.getElementById('searchIncludeDetails');
searchBox.addEventListener('change', () => {
renderWords();
});
searchBox.addEventListener('input', event => {
openSearchModal.value = event.target.value;
});
clearSearchButton.addEventListener('click', clearSearchText);
openSearchModal.addEventListener('click', showSearchModal);
const toggleDetailsCheck = function() {
if (searchExactWords.checked) {
searchIncludeDetails.checked = false;
searchIncludeDetails.disabled = true;
} else {
searchIncludeDetails.disabled = false;
searchIncludeDetails.checked = true;
}
}
searchIgnoreDiacritics.addEventListener('change', () => {
if (searchIgnoreDiacritics.checked) {
searchExactWords.checked = false;
searchExactWords.disabled = true;
} else {
searchExactWords.disabled = false;
}
toggleDetailsCheck();
});
searchExactWords.addEventListener('change', () => toggleDetailsCheck());
}
export function setupSearchFilters() {
const searchFilters = document.querySelectorAll('#searchOptions input[type="checkbox"]'),
searchBox = document.getElementById('searchBox');
Array.from(searchFilters).concat([searchBox]).forEach(filter => {
filter.removeEventListener('change', renderWords);
filter.addEventListener('change', renderWords);
});
document.getElementById('checkAllFilters').removeEventListener('click', checkAllPartsOfSpeechFilters);
document.getElementById('checkAllFilters').addEventListener('click', checkAllPartsOfSpeechFilters);
document.getElementById('uncheckAllFilters').removeEventListener('click', uncheckAllPartsOfSpeechFilters);
document.getElementById('uncheckAllFilters').addEventListener('click', uncheckAllPartsOfSpeechFilters);
}

View File

@ -0,0 +1,112 @@
import { renderEditForm } from '../render/words';
import { confirmEditWord, cancelEditWord, confirmDeleteWord, submitWordForm } from '../wordManagement';
import { goToNextPage, goToPreviousPage, goToPage } from '../pagination';
import { setupMaximizeButtons } from './buttons';
import { setupIPAFields } from '.';
export function setupWordForm() {
const wordForm = document.getElementById('wordForm'),
addWordButton = document.getElementById('addWordButton');
wordForm.addEventListener('submit', event => {
// Allow semantic form and prevent it from getting submitted
event.preventDefault();
return false;
});
addWordButton.addEventListener('click', submitWordForm);
setupIPAFields();
setupMaximizeButtons();
}
export function setupWordOptionButtons() {
const wordOptionButtons = document.getElementsByClassName('word-option-button');
const showWordOptions = function() {
this.parentElement.querySelector('.word-option-list').style.display = '';
}
const hideWordOptions = function(e) {
if (!e.target.classList.contains('word-option-button')) {
const allWordOptions = document.querySelectorAll('.word-option-list');
Array.from(allWordOptions).forEach(wordOptionList => {
wordOptionList.style.display = 'none';
});
}
}
Array.from(wordOptionButtons).forEach(button => {
button.removeEventListener('click', showWordOptions);
button.addEventListener('click', showWordOptions);
});
document.removeEventListener('click', hideWordOptions);
document.addEventListener('click', hideWordOptions);
}
export function setupWordOptionSelections() {
const wordOptions = document.getElementsByClassName('word-option');
Array.from(wordOptions).forEach(option => {
switch (option.innerText) {
case 'Edit': {
option.removeEventListener('click', renderEditForm);
option.addEventListener('click', renderEditForm);
break;
}
case 'Delete': {
option.removeEventListener('click', confirmDeleteWord);
option.addEventListener('click', confirmDeleteWord);
break;
}
}
});
}
export function setupWordEditFormButtons() {
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);
});
Array.from(cancelChangesButtons).forEach(button => {
button.removeEventListener('click', cancelEditWord);
button.addEventListener('click', cancelEditWord);
});
setupIPAFields();
setupMaximizeButtons();
}
export function setupMobileWordFormButton() {
const mobileButton = document.getElementById('mobileWordFormShow'),
wordForm = document.getElementById('wordForm');
mobileButton.addEventListener('click', () => {
if (mobileButton.innerText === '+') {
wordForm.style.display = 'block';
mobileButton.innerHTML = '&times;&#xFE0E;';
} else {
wordForm.style.display = '';
mobileButton.innerHTML = '+';
}
});
}
export function setupPagination() {
const nextButtons = document.getElementsByClassName('next-button'),
prevButtons = document.getElementsByClassName('prev-button'),
pageSelectors = document.getElementsByClassName('page-selector');
Array.from(nextButtons).forEach(nextButton => {
nextButton.removeEventListener('click', goToNextPage);
nextButton.addEventListener('click', goToNextPage);
});
Array.from(prevButtons).forEach(prevButton => {
prevButton.removeEventListener('click', goToPreviousPage);
prevButton.addEventListener('click', goToPreviousPage);
});
Array.from(pageSelectors).forEach(pageSelector => {
pageSelector.removeEventListener('change', goToPage);
pageSelector.addEventListener('change', goToPage);
});
}

View File

@ -176,3 +176,19 @@ export function hideAllModals() {
export function hasToken() {
return window.isOffline !== true && getCookie('token') !== '';
}
export function objectValuesAreDifferent(newObject, oldObject) {
let valuesAreDifferent = false;
for (let property in newObject) {
if (!oldObject.hasOwnProperty(property) || JSON.stringify(newObject[property]) !== JSON.stringify(oldObject[property])) {
valuesAreDifferent = true;
}
if (typeof newObject[property] === 'object' && !Array.isArray(newObject[property])) {
valuesAreDifferent = objectValuesAreDifferent(newObject[property], oldObject[property]);
}
if (valuesAreDifferent) break;
}
return valuesAreDifferent;
}

View File

@ -1,18 +1,17 @@
import md from 'marked';
import { removeTags, slugify } from '../../helpers';
import { getWordsStats, getHomonymnNumber } from './utilities';
import { getHomonymnNumber } from './utilities';
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from './search';
import { showSection } from './displayToggles';
import { setupSearchFilters, setupInfoModal } from './setupListeners';
import { parseReferences } from './wordManagement';
import { renderAd } from '../ads';
import { sortWords } from './wordManagement';
import { setupInfoModal } from '../setupListeners/modals';
import { setupSearchFilters } from '../setupListeners/search';
export function renderAll() {
renderTheme();
renderCustomCSS();
renderDictionaryDetails();
renderPartsOfSpeech();
sortWords();
renderWords();
}
@ -21,15 +20,23 @@ export function renderTheme() {
document.body.id = theme + 'Theme';
}
export function renderCustomCSS() {
const { customCSS } = window.currentDictionary.settings;
const stylingId = 'customCSS';
const stylingElement = document.getElementById(stylingId);
if (!stylingElement) {
const styling = document.createElement('style');
styling.id = stylingId;
styling.innerHTML = customCSS;
document.body.appendChild(styling);
} else {
stylingElement.innerHTML = customCSS;
}
}
export function renderDictionaryDetails() {
renderName();
const tabs = document.querySelectorAll('#detailsSection nav li');
const shownTab = Array.from(tabs).find(tab => tab.classList.contains('active'));
if (shownTab) {
const tabName = shownTab.innerText.toLowerCase();
showSection(tabName);
}
showSection('description');
}
export function renderName() {
@ -41,52 +48,72 @@ export function renderName() {
}
export function renderDescription() {
const descriptionHTML = md(removeTags(window.currentDictionary.description));
const descriptionHTML = md(window.currentDictionary.description);
document.getElementById('detailsPanel').innerHTML = '<div class="content">' + descriptionHTML + '</div>';
}
export function renderDetails() {
const { partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
const { phonology, orthography, grammar } = window.currentDictionary.details;
const partsOfSpeechHTML = `<p><strong>Parts of Speech:</strong> ${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</p>`;
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order:</strong> ${
const { phonology, phonotactics, orthography, grammar } = window.currentDictionary.details;
const partsOfSpeechHTML = `<p><strong>Parts of Speech</strong><br>${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</div>`;
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order</strong><br>${
(alphabeticalOrder.length > 0 ? alphabeticalOrder : ['English Alphabet']).map(letter => `<span class="tag">${letter}</span>`).join(' ')
}</p>`;
}</div>`;
const generalHTML = `<h3>General</h3>${partsOfSpeechHTML}${alphabeticalOrderHTML}`;
const { consonants, vowels, blends, phonotactics } = phonology
const consonantHTML = `<p><strong>Consonants:</strong> ${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const vowelHTML = `<p><strong>Vowels:</strong> ${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs&nbsp;/&nbsp;Blends:</strong> ${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
const { consonants, vowels, blends } = phonology
const consonantHTML = `<p><strong>Consonants</strong><br>${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const vowelHTML = `<p><strong>Vowels</strong><br>${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs&nbsp;/&nbsp;Blends</strong><br>${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
const phonologyNotesHTML = phonology.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(removeTags(phonology.notes)) + '</div>' : '';
const phonologyHTML = `<h3>Phonology</h3>
<div class="split two">
<div>${consonantHTML}</div>
<div>${vowelHTML}</div>
</div>
${blendHTML}`;
${blendHTML}
${phonologyNotesHTML}`;
const { onset, nucleus, coda, exceptions } = phonotactics;
const onsetHTML = `<p><strong>Onset:</strong> ${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const nucleusHTML = `<p><strong>Nucleus:</strong> ${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const codaHTML = `<p><strong>Coda:</strong> ${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const exceptionsHTML = exceptions.trim().length > 0 ? '<p><strong>Exceptions:</strong></p><div>' + md(removeTags(exceptions)) + '</div>' : '';
const phonotacticsHTML = `<h3>Phonotactics</h3>
<div class="split three">
<div>${onsetHTML}</div>
<div>${nucleusHTML}</div>
<div>${codaHTML}</div>
</div>
${exceptionsHTML}`;
const { onset, nucleus, coda } = phonotactics;
const onsetHTML = `<p><strong>Onset</strong><br>${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const nucleusHTML = `<p><strong>Nucleus</strong><br>${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const codaHTML = `<p><strong>Coda</strong><br>${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
const phonotacticsNotesHTML = phonotactics.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(removeTags(phonotactics.notes)) + '</div>' : '';
const phonotacticsHTML = onset.length + nucleus.length + coda.length + phonotacticsNotesHTML.length > 0
? `<h3>Phonotactics</h3>
${onset.length > 0 || nucleus.length > 0 || coda.length > 0
? `<div class="split three">
<div>${onsetHTML}</div>
<div>${nucleusHTML}</div>
<div>${codaHTML}</div>
</div>` : ''}
${phonotacticsNotesHTML}`
: '';
const orthographyHTML = '<h3>Orthography</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(orthography.notes)) + '</div>';
const grammarHTML = '<h3>Grammar</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(grammar.notes)) + '</div>';
const { translations } = orthography;
const translationsHTML = translations.length > 0 ? `<p><strong>Translations</strong><br>${translations.map(translation => {
translation = translation.split('=').map(value => value.trim());
if (translation.length > 1 && translation[0] !== '' && translation[1] !== '') {
return `<span><span class="tag">${translation[0]}</span><span class="tag orthographic-translation">${translation[1]}</span></span>`;
}
return false;
}).filter(html => html !== false).join(' ')}</p>` : '';
const orthographyNotesHTML = orthography.notes.trim().length > 0 ? '<p><strong>Notes</strong><br>' + md(removeTags(orthography.notes)) + '</div>' : '';
const orthographyHTML = translations.length + orthographyNotesHTML.length > 0
? `<h3>Orthography</h3>
${translationsHTML}
${orthographyNotesHTML}`
: '';
const grammarHTML = grammar.notes.trim().length > 0 ? '<h3>Grammar</h3><div>'
+ (grammar.notes.trim().length > 0 ? md(removeTags(grammar.notes)) : '')
+ '</div>' : '';
detailsPanel.innerHTML = generalHTML + phonologyHTML + phonotacticsHTML + orthographyHTML + grammarHTML;
}
export function renderStats() {
const wordStats = getWordsStats();
const { wordStats } = window.currentDictionary;
const numberOfWordsHTML = `<p><strong>Number of Words</strong><br>${wordStats.numberOfWords.map(stat => `<span><span class="tag">${stat.name}</span><span class="tag">${stat.value}</span></span>`).join(' ')}</p>`;
const wordLengthHTML = `<p><strong>Word Length</strong><br><span><span class="tag">Shortest</span><span class="tag">${wordStats.wordLength.shortest}</span></span>
<span><span class="tag">Longest</span><span class="tag">${wordStats.wordLength.longest}</span></span>
@ -152,7 +179,7 @@ export function renderWords() {
pronunciation: removeTags(originalWord.pronunciation),
partOfSpeech: removeTags(originalWord.partOfSpeech),
definition: removeTags(originalWord.definition),
details: parseReferences(removeTags(originalWord.details)),
details: originalWord.details,
wordId: originalWord.wordId,
});
@ -163,7 +190,7 @@ export function renderWords() {
wordsHTML += `<article class="entry" id="${word.wordId}">
<header>
<h4 class="word">${word.name}${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
<h4 class="word"><span class="orthographic-translation">${word.name}</span>${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
<span class="pronunciation">${word.pronunciation}</span>
<span class="part-of-speech">${word.partOfSpeech}</span>
<a href="${shareLink}" target="_blank" class="small button word-option-button" title="Link to Word">&#10150;</a>

View File

@ -22,6 +22,7 @@ export function getSearchFilters() {
caseSensitive: document.getElementById('searchCaseSensitive').checked,
ignoreDiacritics: document.getElementById('searchIgnoreDiacritics').checked,
exact: document.getElementById('searchExactWords').checked,
orthography: document.getElementById('searchOrthography').checked,
name: document.getElementById('searchIncludeName').checked,
definition: document.getElementById('searchIncludeDefinition').checked,
details: document.getElementById('searchIncludeDetails').checked,
@ -53,11 +54,13 @@ export function getMatchingSearchWords() {
}).filter(word => {
searchTerm = filters.ignoreDiacritics ? removeDiacritics(searchTerm) : searchTerm;
searchTerm = filters.caseSensitive ? searchTerm : searchTerm.toLowerCase();
let name = filters.ignoreDiacritics ? removeDiacritics(word.name) : word.name;
let name = filters.orthography ? translateOrthography(word.name) : word.name;
name = filters.ignoreDiacritics ? removeDiacritics(name) : name;
name = filters.caseSensitive ? name : name.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;
let details = filters.orthography ? parseReferences(word.details) : word.details;
details = filters.ignoreDiacritics ? removeDiacritics(details) : details;
details = filters.caseSensitive ? details : details.toLowerCase();
const isInName = filters.name && (filters.exact
@ -80,6 +83,10 @@ export function highlightSearchTerm(word) {
if (searchTerm) {
const filters = getSearchFilters();
const markedUpWord = cloneObject(word);
if (filters.orthography) {
markedUpWord.name = translateOrthography(markedUpWord.name);
markedUpWord.details = parseReferences(markedUpWord.details);
}
if (filters.ignoreDiacritics) {
const searchTermLength = searchTerm.length;
searchTerm = removeDiacritics(searchTerm);

View File

@ -1,93 +1,3 @@
export function getWordsStats() {
const {words, partsOfSpeech} = window.currentDictionary;
const {caseSensitive} = window.currentDictionary.settings;
const wordStats = {
numberOfWords: [
{
name: 'Total',
value: words.length,
},
],
wordLength: {
shortest: 0,
longest: 0,
average: 0,
},
letterDistribution: [
/* {
letter: '',
number: 0,
percentage: 0.00,
} */
],
totalLetters: 0,
};
partsOfSpeech.forEach(partOfSpeech => {
const wordsWithPartOfSpeech = words.filter(word => word.partOfSpeech === partOfSpeech);
wordStats.numberOfWords.push({
name: partOfSpeech,
value: wordsWithPartOfSpeech.length,
});
});
wordStats.numberOfWords.push({
name: 'Unclassified',
value: words.filter(word => !partsOfSpeech.includes(word.partOfSpeech)).length,
});
let totalLetters = 0;
const numberOfLetters = {};
words.forEach(word => {
const shortestWord = wordStats.wordLength.shortest;
const longestWord = wordStats.wordLength.longest;
const wordLetters = word.name.split('');
const lettersInWord = wordLetters.length;
totalLetters += lettersInWord;
if (shortestWord === 0 || lettersInWord < shortestWord) {
wordStats.wordLength.shortest = lettersInWord;
}
if (longestWord === 0 || lettersInWord > longestWord) {
wordStats.wordLength.longest = lettersInWord;
}
wordLetters.forEach(letter => {
const letterToUse = caseSensitive ? letter : letter.toLowerCase();
if (!numberOfLetters.hasOwnProperty(letterToUse)) {
numberOfLetters[letterToUse] = 1;
} else {
numberOfLetters[letterToUse]++;
}
});
});
wordStats.totalLetters = totalLetters;
wordStats.wordLength.average = words.length > 0 ? Math.round(totalLetters / words.length) : 0;
for (const letter in numberOfLetters) {
if (numberOfLetters.hasOwnProperty(letter)) {
const number = numberOfLetters[letter];
wordStats.letterDistribution.push({
letter,
number,
percentage: number / totalLetters,
});
}
}
wordStats.letterDistribution.sort((a, b) => {
if (a.percentage === b.percentage) return 0;
return (a.percentage > b.percentage) ? -1 : 1;
});
return wordStats;
}
export function getHomonymnIndexes(word) {
const { currentDictionary } = window;
const { caseSensitive } = currentDictionary.settings;

View File

@ -1,4 +1,4 @@
import { renderWords } from "./render";
import { renderWords } from "./render/words";
import { wordExists, addMessage, getNextId, hasToken, getHomonymnIndexes } from "./utilities";
import removeDiacritics from "./StackOverflow/removeDiacritics";
import { removeTags, getTimestampInSeconds } from "../helpers";
@ -32,12 +32,68 @@ export function validateWord(word, wordId = false) {
export function sortWords(render) {
const { sortByDefinition } = window.currentDictionary.settings;
const { alphabeticalOrder } = window.currentDictionary;
const sortBy = sortByDefinition ? 'definition' : 'name';
window.currentDictionary.words.sort((wordA, wordB) => {
if (removeDiacritics(wordA[sortBy]).toLowerCase() === removeDiacritics(wordB[sortBy]).toLowerCase()) return 0;
return removeDiacritics(wordA[sortBy]).toLowerCase() > removeDiacritics(wordB[sortBy]).toLowerCase() ? 1 : -1;
// Sort normally first
return wordA[sortBy].localeCompare(wordB[sortBy], 'en', { sensitivity: 'base' }); // This is the smart way to do the below!
// if (removeDiacritics(wordA[sortBy]).toLowerCase() === removeDiacritics(wordB[sortBy]).toLowerCase()) return 0;
// return removeDiacritics(wordA[sortBy]).toLowerCase() > removeDiacritics(wordB[sortBy]).toLowerCase() ? 1 : -1;
});
if (alphabeticalOrder.length > 0) {
// If there's an alphabetical order specified, sort by that after! Any letters not in the alphabet will be unsorted, keeping them in ASCII order
const ordering = {}; // map for efficient lookup of sortIndex
for (let i = 0; i < alphabeticalOrder.length; i++) {
ordering[alphabeticalOrder[i]] = i + 1; // Add 1 to prevent 0 from resolving to false
}
window.currentDictionary.words.sort((wordA, wordB) => {
// console.log(alphabeticalOrder, ordering);
// console.log('comparing:', wordA[sortBy], wordB[sortBy]);
if (wordA[sortBy] === wordB[sortBy]) return 0;
const aLetters = wordA[sortBy].split('');
const bLetters = wordB[sortBy].split('');
for (let i = 0; i < aLetters.length; i++) {
const a = aLetters[i];
const b = bLetters[i];
// console.log('comparing letters', a, b);
if (!b) {
// console.log('no b, ', wordA[sortBy], 'is longer than', wordB[sortBy]);
return 1;
}
if (!ordering[a] && !ordering[b]) {
// console.log('a and b not in dictionary:', a, b, 'continuing to the next letter');
continue;
}
if (!ordering[a]) {
// console.log('a is not in dictionary:', a, 'moving back:', wordA[sortBy]);
return 1;
}
if (!ordering[b]) {
// console.log('b is not in dictionary:', b, 'moving forward:', wordA[sortBy]);
return -1;
}
if (ordering[a] === ordering[b]) {
// console.log('letters are the same order:', a, b);
if (aLetters.length < bLetters.length && i === aLetters.length - 1) {
// console.log(a, 'is shorter than', b);
return -1;
}
// console.log(a, 'is the same as', b, 'continuing to the next letter');
continue;
}
// console.log('comparing order:', a, b, 'result:', ordering[a] - ordering[b]);
return ordering[a] - ordering[b];
}
// console.log('all of the letters were dumb, no sort');
return 0;
});
}
saveDictionary(false);
@ -46,6 +102,16 @@ export function sortWords(render) {
}
}
export function translateOrthography(word) {
window.currentDictionary.details.orthography.translations.forEach(translation => {
translation = translation.split('=').map(value => value.trim());
if (translation.length > 1 && translation[0] !== '' && translation[1] !== '') {
word = word.replace(new RegExp(translation[0], 'g'), translation[1]);
}
});
return word;
}
export function parseReferences(detailsMarkdown) {
const references = detailsMarkdown.match(/\{\{.+?\}\}/g);
if (references && Array.isArray(references)) {
@ -80,8 +146,8 @@ export function parseReferences(detailsMarkdown) {
if (homonymn < 1 && homonymnIndexes.length > 0) {
homonymn = 1;
}
const homonymnSubHTML = homonymn > 0 ? '<sub>' + homonymn.toString() + '</sub>' : '';
const wordMarkdownLink = `[${wordToFind}${homonymnSubHTML}](#${existingWordId})`;
const homonymnSubHTML = homonymnIndexes.length > 1 && homonymn - 1 >= 0 ? '<sub>' + homonymn.toString() + '</sub>' : '';
const wordMarkdownLink = `<span class="word-reference">[<span class="orthographic-translation">${translateOrthography(wordToFind)}</span>${homonymnSubHTML}](#${existingWordId})</span>`;
detailsMarkdown = detailsMarkdown.replace(new RegExp(reference, 'g'), wordMarkdownLink);
}
});

View File

@ -18,20 +18,20 @@
* [Creating An Account](#creating-an-account)
* [Logging In](#logging-in)
* [Differences](#differences)
* [Settings](#settings)
* [Settings](#settings-1)
* [Public Dictionaries](#public-dictionaries)
* [Forgot Your Password?](#forgot-your-password)
* [Lockout](#lockout)
* [Problems or Requests](#problems-or-requests)
* [Update Log](#update-log)
* [Open Source](#open-source)
* [Thanks](#thanks-)
* [Thanks](#thanks)
## What is Lexiconga?
Lexiconga is a tool intended to help you build constructed language (conlang) dictionaries/lexicons.
Lexiconga is a tool built to help you build constructed language (conlang) dictionaries/lexicons quickly and easily.
You can enter words and definitions, and they will appear nicely formatted and in alphabetical order by name under your dictionary's title and details. You can also set your dicitonary to display your words by definition if you prefer that view. If the default parts of speech are not adequate for your conlang, you can change them to whatever you might need. You can also enter a description and full set of language rules that you can toggle on and off below the dictionary's title!
You can enter words and definitions, and they will appear nicely formatted and in alphabetical order by name under your dictionary's title and details. You can also set your dicitonary to sort your words by definition if you prefer that view or even specify a fully custom alphabetical order. If the default parts of speech are not adequate for your conlang, you can change them to whatever you might need. You can also enter a description and full set of language rules that you can toggle on and off below the dictionary's title!
Lexiconga accepts Unicode characters so you can utilize whatever typable characters you might need and [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) for formatting long text entries, and if you want to share or even just make a backup of your dictionary, you can export it to a single convenient file that can be easily re-imported. Your dictionary is saved to your browser's [localStorage](https://www.w3schools.com/html/html5_webstorage.asp) every time you make a change, which means as long as you use the same browser and don't deliberately delete it by clearing your cache, your dictionary will always be there when you come back.
@ -89,6 +89,7 @@ You can refine your search by clicking the "Toggle Options" button and using the
- **Case-Sensitive:** When checked, Lexiconga finds entries matching the letter case in the entered text. When unchecked, it will find any case as long as the letters match.
- **Ignore Diacritics/Accents:** When checked, Lexiconga will ignore accented letters and diacritics and identify them as their equivalent unaccented letter and vice-versa, in case you want to find a word with a diacritic without entering the diacritic in the search box. When unchecked, it will only find diacritics and accented letters if they are specifically entered in the search box.
- **Exact Words:** When checked, the search term will find entries with _exact matches_ in only the Word or Definition field. If Word or Definition has _any_ text aside from exactly what was entered in the search bar, it will not be displayed.
- **Translation:** When checked, Lexiconga will translate all words and references by any specified orthographic translations and compare your search term with that instead of the words as entered.
- **Include in Search**
- **Word**: When checked, Lexiconga searches your dictionary's "Word" entries for the entered text. When unchecked, it ignores it.
- **Definition**: When checked, Lexiconga searches your dictionary's "Definition/Equivalent Word(s)" entries for the entered text. When unchecked, it ignores it.
@ -124,17 +125,19 @@ Clicking the "Edit" button under your dictionary's name will display a window wi
#### Details
- **Parts of Speech:** The parts of speech available in the dropdown box on word forms. Separate each individual part of speech with a comma.
- **Alphabetical Order:** This feature has not been implemented yet.
- **Alphabetical Order:** The order that your words will be sorted by. Include every letter and different capitalization used in your dictionary to sort your words in whatever order you want—any letters in your words that are not sorted here are sorted by the default ASCII/Unicode order (i.e. English Alphabetical) _after_ any custom-sorted words. Lexiconga can only sort by single characters (rather than sets of characters) and will sort the words _as entered_, not using orthographic translations. Separate each character with a _space_.
- **Phonology**
- **Consonants:** The IPA characters representing the consonants present in your language. Uses the IPA Auto-Fill feature unless it is turned off. Separate each consonant with a _space_ so they will be displayed correctly under the Details section of your dictionary.
- **Vowels:** The IPA characters representing the vowels present in your language. Uses the IPA Auto-Fill feature unless it is turned off. Separate each vowel with a _space_ so they will be displayed correctly under the Details section of your dictionary.
- **Polyphthongs/Blends:** The IPA characters representing the polyphthongs or blends present in your language. Uses the IPA Auto-Fill feature unless it is turned off. Separate each one with a _space_ so they will be displayed correctly under the Details section of your dictionary.
- **Notes:** Any notes about your constructed language's phonology that you want to share. Uses Markdown.
- **Phonotactics**
- **Onset:** What phonological characters can appear at the beginning of a syllable. Separate each with a comma.
- **Nucleus:** What phonological characters can appear in the middle of a syllable. Separate each with a comma.
- **Coda:** What phonological characters can appear at the end of a syllable. Separate each with a comma.
- **Exceptions:** Any exceptions to the phonotactical rules laid out above. This is a Markdown-enable text area that you can use however you'd like.
- **Onset:** What phonological characters can appear at the beginning of a syllable. Separate each with a _comma_.
- **Nucleus:** What phonological characters can appear in the middle of a syllable. Separate each with a _comma_.
- **Coda:** What phonological characters can appear at the end of a syllable. Separate each with a _comma_.
- **Notes:** Any notes about your phonotactical rules laid out above. Uses Markdown.
- **Orthography**
- **Translations:** The specification for how Lexiconga should translate certain character sequences into other character sequences. Use the format "original=new" where "original" is the old letter or sequence of letters and "new" is what you want those letters to change into separated by an equal sign. Put each translation on a _separate line_.
- **Notes:** Any notes about your constructed language's writing system that you want to share. Uses Markdown.
- **Grammar**
- **Notes:** Any notes about your constructed language's grammar that you want to share. Uses Markdown.
@ -144,6 +147,7 @@ Clicking the "Edit" button under your dictionary's name will display a window wi
- **Words are Case-Sensitive:** Only available when "Prevent Duplicate Words" is checked. Checking this box will allow the creation of words with the exact same spelling if their capitalization is different.
- **Sort by Definition:** Checking this box will sort the words in alphabetical order based on the Definition instead of the Word.
- **Theme:** Set the color theme for the current dictionary.
- **Custom Styling:** Specify custom CSS to change the styling of your dictionary. You can use custom fonts by specifying them here and setting the `font-family` style of the `.orthographic-translation` class!
- **Make Public:** Only visible if logged in with a Lexiconga account. Checking this box will make your dictionary public via a link you can share with others. The link will appear below this checkbox after it is checked.
#### Actions
@ -170,9 +174,11 @@ After making any changes, be sure to click "Save" or "Save & Close" to ensure th
- **M:** Maximize/Minimize Full Screen textbox when typing in the boxes that have the Maximize button.
- **S:** Open the Search panel.
- **Shift + S:** Open the Settings window.
- **X:** Clear the Search box.
- **Backspace/Delete:** Clear the Search box.
## Accounts
**Note:** Lexiconga is 100% functional _without_ creating an account! Using an account only adds additional syncing features that enable you to store more than one dictionary at a time, access your dictionaries from any computer, and optionally share dicitonaries publicly with a link. _An account is not required_ to build your conlang on your local browser.
If you are using an account with Lexiconga, your experience should remain essentially the same, but you will see some additional options in the Settings menu and you might notice some slight changes in performance as it saves to and loads from the database. This saving/loading process prioritizes your local dictionary, so if you ever lose connection, it will keep retrying the upload until connection is re-established. It also attempts to sync every time you load Lexiconga, so please be aware of that if you refresh the page.
### Creating An Account

View File

@ -11,7 +11,7 @@ We may collect personal identification information from Users in a variety of wa
We may collect non-personal identification information about Users whenever they interact with our Site through website analytics tools. Non-personal identification information may include the browser name, the type of computer and technical information about Users means of connection to our Site, such as the operating system and the Internet service providers utilized and other similar information.
### Web browser cookies
Our Site stores exactly one "cookie" that is used to keep Users logged in to their Accounts, and it does use "local storage". User's web browser places local storage on their hard drive for record-keeping purposes and sometimes to track information about them, but we only use this to store your current dictionary. User may choose to set their web browser to refuse local storage usage, but if they do so, the Site will not function properly.
Our Site stores manually-specified "cookies": one that is used to keep Users logged in to their Accounts and others that track what announcements have been dismissed. Our site does use and require "local storage" to function: the User's web browser places local storage on their hard drive, and our Site uses it for the sole purpose of storing your current dictionary. User may choose to set their web browser to refuse local storage usage, but if they do so, the Site will not function properly.
### How we use collected information
Lexiconga may collect and use Users personal information for the following purposes:

View File

@ -1,7 +1,13 @@
[
{
"header": "New Features have Arrived!",
"body": "<p><em>July 15, 2019</em> &ndash; Custom Alphabetical Order, Orthographic Translations, and Custom Styling are now here!</p><p>Check the <a href=\"https://github.com/Alamantus/Lexiconga/releases\" target=\"_blank\" rel=\"noopener\">Updates page</a> for all the new features and bug fixes!</p>",
"expire": "January 1, 2020",
"dismissId": "wave1"
},
{
"header": "Welcome to Lexiconga 2.0!",
"body": "<p>Lexiconga has been rewritten from the ground up!</p><p>Check the <a href=\"https://github.com/Alamantus/Lexiconga/releases\" target=\"_blank\" rel=\"noopener\">Updates page</a> for all the new features, or click Help to get a refresher on how to use Lexiconga!</p>",
"body": "<p><em>July 1, 2019</em> &ndash; Lexiconga has been rewritten from the ground up!</p><p>Check the <a href=\"https://github.com/Alamantus/Lexiconga/releases\" target=\"_blank\" rel=\"noopener\">Updates page</a> for all the new features, or click Help to get a refresher on how to use Lexiconga!</p>",
"expire": "January 1, 2020",
"dismissId": "welcome"
}

View File

@ -11,7 +11,7 @@ class Dictionary {
$this->token = new Token();
$this->defaults = array(
'partsOfSpeech' => 'Noun,Adjective,Verb',
'partsOfSpeech' => 'Noun,Adjective,Verb,Adverb,Preposition,Pronoun,Conjunction',
);
}
@ -33,13 +33,15 @@ class Dictionary {
$insert_dictionary = $this->db->execute($insert_dictionary_query, array($new_id, $user, 'A new dictionary.', time()));
if ($insert_dictionary === true) {
$insert_linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, parts_of_speech, exceptions, orthography_notes, grammar_notes)
VALUES ($new_id, ?, ?, ?, ?)";
$insert_linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, parts_of_speech, phonology_notes, phonotactics_notes, translations, orthography_notes, grammar_notes)
VALUES ($new_id, ?, ?, ?, ?, ?, ?)";
$insert_linguistics = $this->db->execute($insert_linguistics_query, array(
$this->defaults['partsOfSpeech'],
'',
'',
'',
'',
'',
));
if ($insert_linguistics === true) {
@ -88,159 +90,6 @@ VALUES ($new_id, ?, ?, ?, ?)";
return array();
}
public function getPublicDictionaryDetails ($dictionary) {
if (is_numeric($dictionary)) {
$query = "SELECT d.*, dl.*, u.public_name FROM dictionaries d JOIN dictionary_linguistics dl ON dl.dictionary = d.id JOIN users u ON u.id = d.user WHERE d.id=? AND d.is_public=1";
$result = $this->db->query($query, array($dictionary))->fetch();
if ($result) {
// Default json values in case they are somehow not created by front end first
$partsOfSpeech = $result['parts_of_speech'] !== '' ? $result['parts_of_speech'] : $this->defaults['partsOfSpeech'];
return array(
'externalID' => $result['id'],
'name' => $result['name'],
'specification' => $result['specification'],
'description' => $result['description'],
'createdBy' => $result['public_name'],
'partsOfSpeech' => explode(',', $partsOfSpeech),
'alphabeticalOrder' => array(),
'details' => array(
'phonology' => array(
'consonants' => $result['consonants'] !== '' ? explode(' ', $result['consonants']) : array(),
'vowels' => $result['vowels'] !== '' ? explode(' ', $result['vowels']) : array(),
'blends' => $result['blends'] !== '' ? explode(' ', $result['blends']) : array(),
'phonotactics' => array(
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
'exceptions' => $result['exceptions'],
),
),
'orthography' => array(
'notes' => $result['orthography_notes'],
),
'grammar' => array(
'notes' => $result['grammar_notes'],
),
),
'settings' => array(
'allowDuplicates' => $result['allow_duplicates'] === '1' ? true : false,
'caseSensitive' => $result['case_sensitive'] === '1' ? true : false,
'sortByDefinition' => $result['sort_by_definition'] === '1' ? true : false,
'theme' => $result['theme'],
'isPublic' => $result['is_public'] === '1' ? true : false,
),
'lastUpdated' => is_null($result['last_updated']) ? $result['created_on'] : $result['last_updated'],
'createdOn' => $result['created_on'],
);
}
}
return false;
}
public function getPublicDictionaryWords ($dictionary) {
if (is_numeric($dictionary)) {
$query = "SELECT words.* FROM words JOIN dictionaries ON id = dictionary WHERE dictionary=? AND is_public=1";
$results = $this->db->query($query, array($dictionary))->fetchAll();
if ($results) {
return array_map(function ($row) use ($dictionary) {
return array(
'name' => $row['name'],
'pronunciation' => $row['pronunciation'],
'partOfSpeech' => $row['part_of_speech'],
'definition' => $row['definition'],
'details' => $this->parseReferences($row['details'], $dictionary),
'lastUpdated' => is_null($row['last_updated']) ? intval($row['created_on']) : intval($row['last_updated']),
'createdOn' => intval($row['created_on']),
'wordId' => intval($row['word_id']),
);
}, $results);
}
}
return array();
}
public function getSpecificPublicDictionaryWord ($dictionary, $word) {
if (is_numeric($dictionary) && is_numeric($word)) {
$query = "SELECT words.* FROM words JOIN dictionaries ON id = dictionary WHERE dictionary=? AND word_id=? AND is_public=1";
$result = $this->db->query($query, array($dictionary, $word))->fetch();
if ($result) {
return array(
'name' => $result['name'],
'pronunciation' => $result['pronunciation'],
'partOfSpeech' => $result['part_of_speech'],
'definition' => $result['definition'],
'details' => $this->parseReferences($result['details'], $dictionary),
'lastUpdated' => is_null($result['last_updated']) ? intval($result['created_on']) : intval($result['last_updated']),
'createdOn' => intval($result['created_on']),
'wordId' => intval($result['word_id']),
);
}
}
return false;
}
private function parseReferences($details, $dictionary_id) {
$details = strip_tags($details);
if (preg_match_all('/\{\{.+?\}\}/', $details, $references) !== false) {
$references = array_unique($references[0]);
foreach($references as $reference) {
$word_to_find = preg_replace('/\{\{|\}\}/', '', $reference);
$homonymn = 0;
if (strpos($word_to_find, ':') !== false) {
$separator = strpos($word_to_find, ':');
$homonymn = substr($word_to_find, $separator + 1);
$word_to_find = substr($word_to_find, 0, $separator);
if ($homonymn && trim($homonymn) && intval(trim($homonymn)) > 0) {
$homonymn = intval(trim($homonymn));
} else {
$homonymn = false;
}
}
$target_id = false;
$reference_ids = $this->getWordIdsWithName($dictionary_id, $word_to_find);
if (count($reference_ids) > 0) {
if ($homonymn !== false && $homonymn > 0) {
if (isset($reference_ids[$homonymn - 1])) {
$target_id = $reference_ids[$homonymn - 1];
}
} else if ($homonymn !== false) {
$target_id = $reference_ids[0];
}
if ($target_id !== false) {
if ($homonymn < 1) {
$homonymn = 1;
}
$homonymn_sub_html = $homonymn > 0 ? '<sub>' . $homonymn . '</sub>' : '';
$site_root = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], $dictionary_id));
$markdown_link = '<a href="' . $site_root . $dictionary_id . '/' . $target_id .'" target="_blank" title="Link to Reference">'
. $word_to_find . $homonymn_sub_html . '</a>';
$details = str_replace($reference, $markdown_link, $details);
}
}
}
}
return $details;
}
private function getWordIdsWithName($dictionary, $word_name) {
if (is_numeric($dictionary)) {
$query = "SELECT word_id FROM words WHERE dictionary=? AND name=?";
$results = $this->db->query($query, array($dictionary, $word_name))->fetchAll();
if ($results) {
return array_map(function ($row) {
return intval($row['word_id']);
}, $results);
}
}
return array();
}
public function getDetails ($user, $dictionary) {
$query = "SELECT * FROM dictionaries JOIN dictionary_linguistics ON dictionary = id WHERE user=$user AND id=$dictionary";
$result = $this->db->query($query)->fetch();
@ -254,20 +103,22 @@ VALUES ($new_id, ?, ?, ?, ?)";
'specification' => $result['specification'],
'description' => $result['description'],
'partsOfSpeech' => explode(',', $partsOfSpeech),
'alphabeticalOrder' => array(),
'alphabeticalOrder' => $result['alphabetical_order'] !== '' ? explode(' ', $result['alphabetical_order']) : array(),
'details' => array(
'phonology' => array(
'consonants' => $result['consonants'] !== '' ? explode(' ', $result['consonants']) : array(),
'vowels' => $result['vowels'] !== '' ? explode(' ', $result['vowels']) : array(),
'blends' => $result['blends'] !== '' ? explode(' ', $result['blends']) : array(),
'phonotactics' => array(
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
'exceptions' => $result['exceptions'],
),
'notes' => $result['phonology_notes'],
),
'phonotactics' => array(
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
'notes' => $result['phonotactics_notes'],
),
'orthography' => array(
'translations' => $result['translations'] !== '' ? explode(PHP_EOL, $result['translations']) : array(),
'notes' => $result['orthography_notes'],
),
'grammar' => array(
@ -279,6 +130,7 @@ VALUES ($new_id, ?, ?, ?, ?)";
'caseSensitive' => $result['case_sensitive'] === '1' ? true : false,
'sortByDefinition' => $result['sort_by_definition'] === '1' ? true : false,
'theme' => $result['theme'],
'customCSS' => $result['custom_css'],
'isPublic' => $result['is_public'] === '1' ? true : false,
),
'lastUpdated' => is_null($result['last_updated']) ? $result['created_on'] : $result['last_updated'],
@ -297,6 +149,7 @@ SET name=:name,
case_sensitive=:case_sensitive,
sort_by_definition=:sort_by_definition,
theme=:theme,
custom_css=:custom_css,
is_public=:is_public,
last_updated=:last_updated,
created_on=:created_on
@ -311,6 +164,7 @@ WHERE user=$user AND id=$dictionary";
':case_sensitive' => $dictionary_object['settings']['caseSensitive'] ? 1 : 0,
':sort_by_definition' => $dictionary_object['settings']['sortByDefinition'] ? 1 : 0,
':theme' => $dictionary_object['settings']['theme'],
':custom_css' => $dictionary_object['settings']['customCSS'],
':is_public' => $dictionary_object['settings']['isPublic'] ? 1 : 0,
':last_updated' => $dictionary_object['lastUpdated'],
':created_on' => $dictionary_object['createdOn'],
@ -320,13 +174,16 @@ WHERE user=$user AND id=$dictionary";
$linguistics = $dictionary_object['details'];
$query2 = "UPDATE dictionary_linguistics
SET parts_of_speech=:parts_of_speech,
alphabetical_order=:alphabetical_order,
consonants=:consonants,
vowels=:vowels,
blends=:blends,
phonology_notes=:phonology_notes,
onset=:onset,
nucleus=:nucleus,
coda=:coda,
exceptions=:exceptions,
phonotactics_notes=:phonotactics_notes,
translations=:translations,
orthography_notes=:orthography_notes,
grammar_notes=:grammar_notes
WHERE dictionary=$dictionary";
@ -334,13 +191,16 @@ WHERE dictionary=$dictionary";
// $result2 = $this->db->query($query2, array(
$result2 = $this->db->execute($query2, array(
':parts_of_speech' => implode(',', $dictionary_object['partsOfSpeech']),
':alphabetical_order' => implode(' ', $dictionary_object['alphabeticalOrder']),
':consonants' => implode(' ', $linguistics['phonology']['consonants']),
':vowels' => implode(' ', $linguistics['phonology']['vowels']),
':blends' => implode(' ', $linguistics['phonology']['blends']),
':onset' => implode(',', $linguistics['phonology']['phonotactics']['onset']),
':nucleus' => implode(',', $linguistics['phonology']['phonotactics']['nucleus']),
':coda' => implode(',', $linguistics['phonology']['phonotactics']['coda']),
':exceptions' => $linguistics['phonology']['phonotactics']['exceptions'],
':phonology_notes' => $linguistics['phonology']['notes'],
':onset' => implode(',', $linguistics['phonotactics']['onset']),
':nucleus' => implode(',', $linguistics['phonotactics']['nucleus']),
':coda' => implode(',', $linguistics['phonotactics']['coda']),
':phonotactics_notes' => $linguistics['phonotactics']['notes'],
':translations' => implode(PHP_EOL, $linguistics['orthography']['translations']),
':orthography_notes' => $linguistics['orthography']['notes'],
':grammar_notes' => $linguistics['grammar']['notes'],
));

View File

@ -0,0 +1,357 @@
<?php
require_once(realpath(dirname(__FILE__) . '/./Db.php'));
require_once(realpath(dirname(__FILE__) . '/./Token.php'));
class PublicDictionary {
private $db;
private $token;
private $defaults;
private $original_words;
public $details;
public $words;
function __construct ($dictionary_id) {
$this->db = new Db();
$this->token = new Token();
$this->defaults = array(
'partsOfSpeech' => 'Noun,Adjective,Verb',
);
$this->details = $this->getPublicDictionaryDetails($dictionary_id);
$this->words = $this->getPublicDictionaryWords($dictionary_id);
$this->details['wordStats'] = $this->getWordStats();
}
public function getPublicDictionaryDetails ($dictionary) {
if (is_numeric($dictionary)) {
$query = "SELECT d.*, dl.*, u.public_name FROM dictionaries d JOIN dictionary_linguistics dl ON dl.dictionary = d.id JOIN users u ON u.id = d.user WHERE d.id=? AND d.is_public=1";
$result = $this->db->query($query, array($dictionary))->fetch();
if ($result) {
// Default json values in case they are somehow not created by front end first
$partsOfSpeech = $result['parts_of_speech'] !== '' ? $result['parts_of_speech'] : $this->defaults['partsOfSpeech'];
return array(
'externalID' => $result['id'],
'name' => $result['name'],
'specification' => $result['specification'],
'description' => $this->parseReferences(strip_tags($result['description']), $result['id']),
'createdBy' => $result['public_name'],
'partsOfSpeech' => explode(',', $partsOfSpeech),
'alphabeticalOrder' => $result['alphabetical_order'] !== '' ? explode(' ', $result['alphabetical_order']) : array(),
'details' => array(
'phonology' => array(
'consonants' => $result['consonants'] !== '' ? explode(' ', $result['consonants']) : array(),
'vowels' => $result['vowels'] !== '' ? explode(' ', $result['vowels']) : array(),
'blends' => $result['blends'] !== '' ? explode(' ', $result['blends']) : array(),
'notes' => $result['phonology_notes'],
),
'phonotactics' => array(
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
'notes' => $result['phonotactics_notes'],
),
'orthography' => array(
'translations' => $result['translations'] !== '' ? explode(PHP_EOL, $result['translations']) : array(),
'notes' => $result['orthography_notes'],
),
'grammar' => array(
'notes' => $result['grammar_notes'],
),
),
'settings' => array(
'allowDuplicates' => $result['allow_duplicates'] === '1' ? true : false,
'caseSensitive' => $result['case_sensitive'] === '1' ? true : false,
'sortByDefinition' => $result['sort_by_definition'] === '1' ? true : false,
'theme' => $result['theme'],
'customCSS' => $result['custom_css'],
'isPublic' => $result['is_public'] === '1' ? true : false,
),
'lastUpdated' => is_null($result['last_updated']) ? $result['created_on'] : $result['last_updated'],
'createdOn' => $result['created_on'],
);
}
}
return false;
}
public function getPublicDictionaryWords ($dictionary) {
if (is_numeric($dictionary)) {
$words = $this->getWordsAsEntered();
if ($words) {
return array_map(function ($row) use ($dictionary) {
return array(
'name' => $this->translateOrthography($row['name'], $dictionary),
'pronunciation' => $row['pronunciation'],
'partOfSpeech' => $row['part_of_speech'],
'definition' => $row['definition'],
'details' => $this->parseReferences(strip_tags($row['details']), $dictionary),
'lastUpdated' => is_null($row['last_updated']) ? intval($row['created_on']) : intval($row['last_updated']),
'createdOn' => intval($row['created_on']),
'wordId' => intval($row['word_id']),
);
}, $this->sortWords($words));
}
}
return array();
}
public function getSpecificPublicDictionaryWord ($dictionary, $word) {
if (is_numeric($dictionary) && is_numeric($word)) {
$query = "SELECT words.* FROM words JOIN dictionaries ON id = dictionary WHERE dictionary=? AND word_id=? AND is_public=1";
$result = $this->db->query($query, array($dictionary, $word))->fetch();
if ($result) {
return array(
'name' => $this->translateOrthography($result['name'], $dictionary),
'pronunciation' => $result['pronunciation'],
'partOfSpeech' => $result['part_of_speech'],
'definition' => $result['definition'],
'details' => $this->parseReferences(strip_tags($result['details']), $dictionary),
'lastUpdated' => is_null($result['last_updated']) ? intval($result['created_on']) : intval($result['last_updated']),
'createdOn' => intval($result['created_on']),
'wordId' => intval($result['word_id']),
);
}
}
return false;
}
private function getWordsAsEntered() {
if (!isset($this->original_words)) {
$query = "SELECT words.* FROM words JOIN dictionaries ON id = dictionary WHERE dictionary=? AND is_public=1";
$this->original_words = $this->db->query($query, array($this->details['externalID']))->fetchAll();
}
return $this->original_words;
}
private function sortWords($words) {
$sort_by = isset($this->details['settings']) && isset($this->details['settings']['sortByDefinition']) && $this->details['settings']['sortByDefinition'] === true
? 'definition' : 'name';
// Transliterator settings from https://stackoverflow.com/a/35178027
$transliterator = Transliterator::createFromRules(':: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Lower(); :: NFC;', Transliterator::FORWARD);
usort($words, function($a, $b) use($transliterator, $sort_by) {
$word_a = $transliterator->transliterate($a[$sort_by]);
$word_b = $transliterator->transliterate($b[$sort_by]);
return strcasecmp($word_a, $word_b);
});
if (isset($this->details['alphabeticalOrder']) && count($this->details['alphabeticalOrder']) > 0) {
$ordering = array();
for ($i = 0; $i < count($this->details['alphabeticalOrder']); $i++) {
$ordering[$this->details['alphabeticalOrder'][$i]] = $i + 1;
}
usort($words, function($word_a, $word_b) use($sort_by, $ordering) {
if ($word_a[$sort_by] === $word_b[$sort_by]) return 0;
$a_letters = str_split($word_a[$sort_by]);
$b_letters = str_split($word_b[$sort_by]);
for ($i = 0; $i < count($a_letters); $i++) {
$a = $a_letters[$i];
if (!isset($b_letters[$i])) {
return 1;
}
$b = $b_letters[$i];
if (!isset($ordering[$a]) && !isset($ordering[$b])) {
continue;
}
if (!isset($ordering[$a])) {
return 1;
}
if (!isset($ordering[$b])) {
return -1;
}
if ($ordering[$a] === $ordering[$b]) {
if (count($a_letters) < count($b_letters) && $i === count($a_letters) - 1) {
return -1;
}
continue;
}
return $ordering[$a] - $ordering[$b];
}
return 0;
});
}
return $words;
}
private function parseReferences($details, $dictionary_id) {
$details = strip_tags($details);
if (preg_match_all('/\{\{.+?\}\}/', $details, $references) !== false) {
$references = array_unique($references[0]);
foreach($references as $reference) {
$word_to_find = preg_replace('/\{\{|\}\}/', '', $reference);
$homonymn = 0;
if (strpos($word_to_find, ':') !== false) {
$separator = strpos($word_to_find, ':');
$homonymn = substr($word_to_find, $separator + 1);
$word_to_find = substr($word_to_find, 0, $separator);
if ($homonymn && trim($homonymn) && intval(trim($homonymn)) > 0) {
$homonymn = intval(trim($homonymn));
} else {
$homonymn = false;
}
}
$target_id = false;
$reference_ids = $this->getWordIdsWithName($dictionary_id, $word_to_find);
if (count($reference_ids) > 0) {
if ($homonymn !== false && $homonymn > 0) {
if (isset($reference_ids[$homonymn - 1])) {
$target_id = $reference_ids[$homonymn - 1];
}
} else if ($homonymn !== false) {
$target_id = $reference_ids[0];
}
if ($target_id !== false) {
if ($homonymn < 1) {
$homonymn = 1;
}
$homonymn_sub_html = count($reference_ids) > 1 && $homonymn - 1 >= 0 ? '<sub>' . $homonymn . '</sub>' : '';
$site_root = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], $dictionary_id));
$markdown_link = '<span class="word-reference"><a href="' . $site_root . $dictionary_id . '/' . $target_id .'" target="_blank" title="Link to Reference">'
. '<span class="orthographic-translation">' . $this->translateOrthography($word_to_find, $dictionary_id) . '</span>' . $homonymn_sub_html
. '</a></span>';
$details = str_replace($reference, $markdown_link, $details);
}
}
}
}
return $details;
}
private function getWordIdsWithName($dictionary, $word_name) {
if (is_numeric($dictionary)) {
$query = "SELECT word_id FROM words WHERE dictionary=? AND name=?";
$results = $this->db->query($query, array($dictionary, $word_name))->fetchAll();
if ($results) {
return array_map(function ($row) {
return intval($row['word_id']);
}, $results);
}
}
return array();
}
private function translateOrthography($word, $dictionary) {
if (!isset($this->translations)) {
$this->translations = $this->getTranslations($dictionary);
}
foreach($this->translations as $translation) {
$translation = array_map('trim', explode('=', $translation));
if (count($translation) > 1 && $translation[0] !== '' && $translation[1] !== '') {
$word = str_replace($translation[0], $translation[1], $word);
}
};
return $word;
}
private function getTranslations($dictionary) {
if (is_numeric($dictionary)) {
$query = "SELECT translations FROM dictionary_linguistics WHERE dictionary=?";
$result = $this->db->query($query, array($dictionary))->fetch();
if ($result) {
return explode(PHP_EOL, $result['translations']);
}
}
return array();
}
private function getWordStats() {
$words = $this->getWordsAsEntered();
$word_stats = array(
'numberOfWords' => array(
array(
'name' => 'Total',
'value' => count($words),
),
),
'wordLength' => array(
'shortest' => 0,
'longest' => 0,
'average' => 0,
),
'letterDistribution' => array(
/* array(
'letter' => '',
'number' => 0,
'percentage' => 0.00,
) */
),
'totalLetters' => 0,
);
foreach($this->details['partsOfSpeech'] as $part_of_speech) {
$words_with_part_of_speech = array_filter($words, function ($word) use($part_of_speech) {
return $word['part_of_speech'] === $part_of_speech;
});
$word_stats['numberOfWords'][] = array(
'name' => $part_of_speech,
'value' => count($words_with_part_of_speech),
);
};
$word_stats['numberOfWords'][] = array(
'name' => 'Unclassified',
'value' => count(array_filter($words, function ($word) {
return !in_array($word['part_of_speech'], $this->details['partsOfSpeech']);
})),
);
$total_letters = 0;
$number_of_letters = array();
foreach($words as $word) {
$shortest_word = $word_stats['wordLength']['shortest'];
$longest_word = $word_stats['wordLength']['longest'];
$word_letters = str_split($word['name']);
$letters_in_word = count($word_letters);
$total_letters += $letters_in_word;
if ($shortest_word === 0 || $letters_in_word < $shortest_word) {
$word_stats['wordLength']['shortest'] = $letters_in_word;
}
if ($longest_word === 0 || $letters_in_word > $longest_word) {
$word_stats['wordLength']['longest'] = $letters_in_word;
}
foreach($word_letters as $letter) {
$letterToUse = $this->details['settings']['caseSensitive'] ? $letter : strtolower($letter);
if (!isset($number_of_letters[$letterToUse])) {
$number_of_letters[$letterToUse] = 1;
} else {
$number_of_letters[$letterToUse]++;
}
};
};
$word_stats['totalLetters'] = $total_letters;
$word_stats['wordLength']['average'] = count($words) > 0 ? round($total_letters / count($words)) : 0;
foreach ($number_of_letters as $letter => $number) {
if (isset($number_of_letters[$letter])) {
$word_stats['letterDistribution'][] = array(
'letter' => $letter,
'number' => $number,
'percentage' => $number / $total_letters,
);
}
}
usort($word_stats['letterDistribution'], function ($a, $b) {
if ($a['percentage'] === $b['percentage']) return 0;
return ($a['percentage'] > $b['percentage']) ? -1 : 1;
});
return $word_stats;
}
}

View File

@ -14,11 +14,11 @@ switch ($view) {
$html = file_get_contents(realpath(dirname(__FILE__) . '/./template-view.html'));
$dict = isset($_GET['dict']) ? $_GET['dict'] : false;
if ($dict !== false) {
require_once(realpath(dirname(__FILE__) . '/./api/Dictionary.php'));
$dictionary = new Dictionary();
$dictionary_data = $dictionary->getPublicDictionaryDetails($dict);
require_once(realpath(dirname(__FILE__) . '/./api/PublicDictionary.php'));
$dictionary = new PublicDictionary($dict);
$dictionary_data = $dictionary->details;
if ($dictionary_data !== false) {
$dictionary_data['words'] = $dictionary->getPublicDictionaryWords($dict);
$dictionary_data['words'] = $dictionary->words;
$html = str_replace('{{dict}}', $dict, $html);
$html = str_replace('{{dict_name}}', $dictionary_data['name'] . ' ' . $dictionary_data['specification'], $html);
$html = str_replace('{{public_name}}', $dictionary_data['createdBy'], $html);
@ -39,9 +39,9 @@ switch ($view) {
$dict = isset($_GET['dict']) ? $_GET['dict'] : false;
$word = isset($_GET['word']) ? $_GET['word'] : false;
if ($dict !== false && $word !== false) {
require_once(realpath(dirname(__FILE__) . '/./api/Dictionary.php'));
$dictionary = new Dictionary();
$dictionary_data = $dictionary->getPublicDictionaryDetails($dict);
require_once(realpath(dirname(__FILE__) . '/./api/PublicDictionary.php'));
$dictionary = new PublicDictionary($dict, true);
$dictionary_data = $dictionary->details;
if ($dictionary_data !== false) {
$dictionary_name = $dictionary_data['name'] . ' ' . $dictionary_data['specification'];
$word_data = $dictionary->getSpecificPublicDictionaryWord($dict, $word);
@ -80,6 +80,10 @@ switch ($view) {
$announcements = json_decode($announcements, true);
$announcements_html = '';
foreach ($announcements as $announcement) {
if (isset($announcement['dismissId']) && isset($_COOKIE['announcement-' . $announcement['dismissId']])) {
continue;
}
$expire = strtotime($announcement['expire']);
if (time() < $expire) {
$announcements_html .= '<article class="announcement"' . (isset($announcement['dismissId']) ? ' id="announcement-' . $announcement['dismissId'] . '"' : '') . ' data-expires="' . $announcement['expire'] . '">
@ -119,7 +123,7 @@ switch ($view) {
oldLoad && oldLoad();
if (UpUp) {
UpUp.start({
'cache-version': '2.0.2',
'cache-version': '2.1.0',
'content-url': 'offline.html',
'assets': [
\"" . implode('","', $files) . "\"

View File

@ -279,7 +279,11 @@ $nav-font-height: 16px;
#editDescription {
width: 100%;
height: 280px;
height: 240px;
}
#editCustomCSS {
font-family: 'Courier New', Courier, monospace;
}
}

View File

@ -89,7 +89,17 @@ $mobile-word-form-size: 32px;
}
#editDescription {
width: 100%;
height: 260px;
height: 220px;
}
}
#bottom {
position: relative;
bottom: unset;
.separator {
display: block;
visibility: hidden;
}
}

View File

@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS `dictionaries` (
`case_sensitive` tinyint(1) NOT NULL DEFAULT 0,
`sort_by_definition` tinyint(1) NOT NULL DEFAULT 0,
`theme` varchar(20) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'default',
`custom_css` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'CSS',
`is_public` tinyint(1) NOT NULL DEFAULT 0,
`last_updated` int(11) DEFAULT NULL,
`created_on` int(11) NOT NULL,
@ -34,13 +35,16 @@ DELIMITER ;
CREATE TABLE IF NOT EXISTS `dictionary_linguistics` (
`dictionary` int(11) NOT NULL,
`parts_of_speech` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Comma-separated',
`alphabetical_order` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
`consonants` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
`vowels` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
`blends` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
`phonology_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
`onset` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
`nucleus` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
`coda` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
`exceptions` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
`phonotactics_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
`translations` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Newline-separated; Translates left character(s) to right character(s)',
`orthography_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
`grammar_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
UNIQUE KEY `dictionary` (`dictionary`)

View File

@ -81,6 +81,9 @@
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
<label>Translations
<input type="checkbox" id="searchOrthography">
</label>
</div>
</div>
<div class="split">
@ -242,9 +245,11 @@
<section id="editDescriptionTab">
<label>Name<br>
<input id="editName" maxlength="50">
<small>Won't update if left blank.</small>
</label>
<label>Specification<br>
<input id="editSpecification" maxlength="50">
<small>Won't update if left blank.</small>
</label>
<label>Description<a class="label-button maximize-button">Maximize</a><br>
<textarea id="editDescription"></textarea>
@ -255,8 +260,12 @@
<label>Parts of Speech <small>(Comma Separated List)</small><br>
<input id="editPartsOfSpeech" maxlength="2500" placeholder="Noun,Adjective,Verb">
</label>
<label>Alphabetical Order <small>(Comma Separated List. Include every letter!)</small><br>
<input id="editAlphabeticalOrder" disabled value="English Alphabet">
<label>Alphabetical Order <small>(Space Separated List)</small><br>
<input id="editAlphabeticalOrder" placeholder="a A b B c C d D ...">
<a class="label-help-button" onclick="alert('Include every letter and case! Any letters used in your words that are not specified will be sorted in the default order below your alphabetically custom-sorted words.\n\nLexiconga can only sort by single characters and will sort by the words AS ENTERED, not using orthographic translation.')">
Field Info
</a>&nbsp;
<small>Leave blank for default (case-insensitive ASCII/Unicode sorting)</small>
</label>
<h3>Phonology</h3>
<div class="split three">
@ -285,6 +294,9 @@
</label>
</div>
</div>
<label>Notes <small>(Markdown-enabled)</small><br>
<textarea id="editPhonologyNotes"></textarea>
</label>
<h3>Phonotactics</h3>
<div class="split three">
<div>
@ -306,10 +318,17 @@
</label>
</div>
</div>
<label>Exceptions <small>(Markdown-enabled)</small><br>
<textarea id="editExceptions"></textarea>
<label>Notes <small>(Markdown-enabled)</small><br>
<textarea id="editPhonotacticsNotes"></textarea>
</label>
<h3>Orthography</h3>
<label>Translations <small>(One translation per line)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editTranslations" placeholder="ai=I
AA=ay
ou=ow"></textarea>
<small>Use format: <code>sequence=replacement</code></small><br>
<small>Translations occur in the order specified here, so try to avoid double translations!</small>
</label>
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editOrthography"></textarea>
</label>
@ -346,6 +365,9 @@
<option value="grape">Grape</option>
</select>
</label>
<label>Custom Styling <small>(CSS Only)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editCustomCSS" placeholder=".orthographic-translation {font-family: serif;}"></textarea>
</label>
</section>
<section id="editActionsTab" style="display:none;">

View File

@ -81,6 +81,9 @@
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
<label>Translations
<input type="checkbox" id="searchOrthography">
</label>
</div>
</div>
<div class="split">
@ -124,7 +127,7 @@
</ul>
</nav>
<article id="detailsPanel" style="display:none;">
<p>The dictionary details</p>
<p>Loading Dictionary Details</p>
</article>
</section>

378
yarn.lock
View File

@ -853,6 +853,14 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
anymatch@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09"
integrity sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
aproba@^1.0.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
@ -980,16 +988,17 @@ atob@^2.1.1:
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@^9.5.1:
version "9.5.1"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.5.1.tgz#243b1267b67e7e947f28919d786b50d3bb0fb357"
integrity sha512-KJSzkStUl3wP0D5sdMlP82Q52JLy5+atf2MHAre48+ckWkXgixmfHyWmA77wFDy6jTHU6mIgXv6hAQ2mf1PjJQ==
version "9.6.1"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==
dependencies:
browserslist "^4.5.4"
caniuse-lite "^1.0.30000957"
browserslist "^4.6.3"
caniuse-lite "^1.0.30000980"
chalk "^2.4.2"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
postcss "^7.0.14"
postcss-value-parser "^3.3.1"
postcss "^7.0.17"
postcss-value-parser "^4.0.0"
aws-sign2@~0.7.0:
version "0.7.0"
@ -1063,6 +1072,11 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
binary-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
bindings@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11"
@ -1119,6 +1133,13 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
braces@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
brfs@^1.2.0:
version "1.6.1"
resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3"
@ -1207,14 +1228,14 @@ browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.3.4:
electron-to-chromium "^1.3.137"
node-releases "^1.1.21"
browserslist@^4.5.4:
version "4.5.6"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.6.tgz#ea42e8581ca2513fa7f371d4dd66da763938163d"
integrity sha512-o/hPOtbU9oX507lIqon+UvPYqpx3mHc8cV3QemSBTXwkG8gSQSK6UKvXcE/DcleU3+A59XTUHyCvZ5qGy8xVAg==
browserslist@^4.6.3:
version "4.6.6"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453"
integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==
dependencies:
caniuse-lite "^1.0.30000963"
electron-to-chromium "^1.3.127"
node-releases "^1.1.17"
caniuse-lite "^1.0.30000984"
electron-to-chromium "^1.3.191"
node-releases "^1.1.25"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
@ -1322,15 +1343,10 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000971:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000973.tgz#2f8e8e54f9e6c5b7a631c9e69bfa1093d8cfd360"
integrity sha512-/F3t/Yo8LEdRSEPCmI15fLu5vepVh9UCg/9inJXF5AAfW7xRRJkbaM2ut52iRMQMnGCLQouLbFdbOA+VEFOIsg==
caniuse-lite@^1.0.30000957:
version "1.0.30000967"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000967.tgz#a5039577806fccee80a04aaafb2c0890b1ee2f73"
integrity sha512-rUBIbap+VJfxTzrM4akJ00lkvVb5/n5v3EGXfWzSH5zT8aJmGzjA8HWhJ4U6kCpzxozUSnB+yvAYDRPY6mRpgQ==
caniuse-lite@^1.0.30000963:
version "1.0.30000963"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000963.tgz#5be481d5292f22aff5ee0db4a6c049b65b5798b1"
integrity sha512-n4HUiullc7Lw0LyzpeLa2ffP8KxFBGdxqD/8G3bSL6oB758hZ2UE2CVK+tQN958tJIi0/tfpjAc67aAtoHgnrQ==
caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984:
version "1.0.30000984"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000984.tgz#dc96c3c469e9bcfc6ad5bdd24c77ec918ea76fe0"
integrity sha512-n5tKOjMaZ1fksIpQbjERuqCyfgec/m9pferkFQbLmWtqLUdmt12hNhjSwsmPdqeiG2NkITOQhr1VYIwWSAceiA==
caseless@~0.12.0:
version "0.12.0"
@ -1357,6 +1373,21 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
"chokidar@>=2.0.0 <4.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681"
integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==
dependencies:
anymatch "^3.0.1"
braces "^3.0.2"
glob-parent "^5.0.0"
is-binary-path "^2.1.0"
is-glob "^4.0.1"
normalize-path "^3.0.0"
readdirp "^3.1.1"
optionalDependencies:
fsevents "^2.0.6"
chokidar@^1.6.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
@ -1373,25 +1404,6 @@ chokidar@^1.6.0:
optionalDependencies:
fsevents "^1.0.0"
chokidar@^2.0.0:
version "2.1.5"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==
dependencies:
anymatch "^2.0.0"
async-each "^1.0.1"
braces "^2.3.2"
glob-parent "^3.1.0"
inherits "^2.0.3"
is-binary-path "^1.0.0"
is-glob "^4.0.0"
normalize-path "^3.0.0"
path-is-absolute "^1.0.0"
readdirp "^2.2.1"
upath "^1.1.1"
optionalDependencies:
fsevents "^1.2.7"
chokidar@^2.0.3:
version "2.1.6"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
@ -1411,11 +1423,16 @@ chokidar@^2.0.3:
optionalDependencies:
fsevents "^1.2.7"
chownr@^1.0.1, chownr@^1.1.1:
chownr@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==
chownr@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6"
integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@ -1563,9 +1580,9 @@ concat-stream@~1.6.0:
typedarray "^0.0.6"
concurrently@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.1.0.tgz#17fdf067da71210685d9ea554423ef239da30d33"
integrity sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==
version "4.1.1"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.1.1.tgz#42cf84d625163f3f5b2e2262568211ad76e1dbe8"
integrity sha512-48+FE5RJ0qc8azwKv4keVQWlni1hZeSjcWr8shBelOBtBHcKj1aJFM9lHRiSc1x7lq416pkvsqfBMhSRja+Lhw==
dependencies:
chalk "^2.4.1"
date-fns "^1.23.0"
@ -1928,6 +1945,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
debug@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
@ -2132,16 +2156,16 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.127:
version "1.3.129"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.129.tgz#bff32e1840775554aafa301e9dc5002d565aae80"
integrity sha512-puirJsgZnedlFEmRa7WEUIaS8ZgHHn7d7inph+RiapCc0x80hdoDyEEpR9z3aRUSZy4fGxOTOFcxnGmySlrmhA==
electron-to-chromium@^1.3.137:
version "1.3.144"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.144.tgz#d742d2c451090798303a4a5eea4f33f5e60f5d94"
integrity sha512-jNRFJpfNrYm5uJ4x0q9oYMOfbL0JPOlkNli8GS/5zEmCjnE5jAtoCo4BYajHiqSPqEeAjtTdItL4p7EZw+jSfg==
electron-to-chromium@^1.3.191:
version "1.3.191"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.191.tgz#c451b422cd8b2eab84dedabab5abcae1eaefb6f0"
integrity sha512-jasjtY5RUy/TOyiUYM2fb4BDaPZfm6CXRFeJDMfFsXYADGxUN49RBqtgB7EL2RmJXeIRUk9lM1U6A5yk2YJMPQ==
elliptic@^6.0.0:
version "6.4.1"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
@ -2441,6 +2465,13 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
find-index@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4"
@ -2507,9 +2538,9 @@ fs-copy-file-sync@^1.1.1:
integrity sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ==
fs-minipass@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==
version "1.2.6"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==
dependencies:
minipass "^2.2.1"
@ -2526,6 +2557,11 @@ fsevents@^1.0.0, fsevents@^1.2.7:
nan "^2.12.1"
node-pre-gyp "^0.12.0"
fsevents@^2.0.6:
version "2.0.7"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -2602,6 +2638,13 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob-parent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
dependencies:
is-glob "^4.0.1"
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
@ -2614,7 +2657,7 @@ glob2base@^0.0.12:
dependencies:
find-index "^0.1.1"
glob@^7.0.3, glob@^7.0.5:
glob@^7.0.3, glob@^7.0.5, glob@^7.1.3:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
@ -2626,27 +2669,15 @@ glob@^7.0.3, glob@^7.0.5:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
graceful-fs@^4.1.11:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
version "4.2.0"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
grapheme-breaker@^0.3.2:
version "0.3.2"
@ -2902,16 +2933,21 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inherits@2, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.4, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
@ -2965,6 +3001,13 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
is-binary-path@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@ -3089,7 +3132,7 @@ is-glob@^3.1.0:
dependencies:
is-extglob "^2.1.0"
is-glob@^4.0.0:
is-glob@^4.0.0, is-glob@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
@ -3122,12 +3165,17 @@ is-number@^4.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
@ -3424,11 +3472,16 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4:
lodash@^4.13.1, lodash@^4.17.11, lodash@^4.17.4:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
lodash@^4.17.10:
version "4.17.14"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@ -3478,9 +3531,9 @@ map-visit@^1.0.0:
object-visit "^1.0.0"
marked@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.2.tgz#c574be8b545a8b48641456ca1dbe0e37b6dccc1a"
integrity sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==
version "0.6.3"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.3.tgz#79babad78af638ba4d522a9e715cdfdd2429e946"
integrity sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==
math-random@^1.0.1:
version "1.0.4"
@ -3627,7 +3680,7 @@ minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minipass@^2.2.1, minipass@^2.3.4, minipass@^2.3.5:
minipass@^2.2.1, minipass@^2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
@ -3635,7 +3688,7 @@ minipass@^2.2.1, minipass@^2.3.4, minipass@^2.3.5:
safe-buffer "^5.1.2"
yallist "^3.0.0"
minizlib@^1.1.1, minizlib@^1.2.1:
minizlib@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==
@ -3643,9 +3696,9 @@ minizlib@^1.1.1, minizlib@^1.2.1:
minipass "^2.2.1"
mixin-deep@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
dependencies:
for-in "^1.0.2"
is-extendable "^1.0.1"
@ -3662,17 +3715,17 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.1, ms@^2.1.1:
ms@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
nan@^2.12.1:
version "2.13.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nan@^2.13.2:
nan@^2.12.1, nan@^2.13.2:
version "2.14.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
@ -3700,11 +3753,11 @@ napi-build-utils@^1.0.1:
integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==
needle@^2.2.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.1.tgz#d272f2f4034afb9c4c9ab1379aabc17fc85c9388"
integrity sha512-CaLXV3W8Vnbps8ZANqDGz7j4x7Yj1LW4TWF/TQuDfj7Cfx4nAPTvw98qgTevtto1oHDrh3pQkaODbqupXlsWTg==
version "2.4.0"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
dependencies:
debug "^4.1.0"
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
@ -3775,13 +3828,6 @@ node-pre-gyp@^0.12.0:
semver "^5.3.0"
tar "^4"
node-releases@^1.1.17:
version "1.1.17"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.17.tgz#71ea4631f0a97d5cd4f65f7d04ecf9072eac711a"
integrity sha512-/SCjetyta1m7YXLgtACZGDYJdCSIBAWorDWkGCGZlydP2Ll7J48l7j/JxNYZ+xsgSPbWfdulVS/aY+GdjUsQ7Q==
dependencies:
semver "^5.3.0"
node-releases@^1.1.21:
version "1.1.22"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.22.tgz#d90cd5adc59ab9b0f377d4f532b09656399c88bf"
@ -3789,6 +3835,13 @@ node-releases@^1.1.21:
dependencies:
semver "^5.3.0"
node-releases@^1.1.25:
version "1.1.25"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.25.tgz#0c2d7dbc7fed30fbe02a9ee3007b8c90bf0133d3"
integrity sha512-fI5BXuk83lKEoZDdH3gRhtsNgh05/wZacuXkgbiYkceE7+QIMXOg98n9ZV7mz27B+kFHnqHcUpscZZlGRSmTpQ==
dependencies:
semver "^5.3.0"
noop-logger@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
@ -3850,9 +3903,9 @@ npm-bundled@^1.0.1:
integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
npm-packlist@^1.1.6:
version "1.4.1"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc"
integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==
version "1.4.4"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44"
integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==
dependencies:
ignore-walk "^3.0.1"
npm-bundled "^1.0.1"
@ -4257,6 +4310,11 @@ physical-cpu-count@^2.0.0:
resolved "https://registry.yarnpkg.com/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz#18de2f97e4bf7a9551ad7511942b5496f7aba660"
integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA=
picomatch@^2.0.4:
version "2.0.7"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
@ -4574,6 +4632,11 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.1:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
postcss-value-parser@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d"
integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==
postcss@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2"
@ -4592,7 +4655,7 @@ postcss@^6.0.1, postcss@^6.0.14:
source-map "^0.6.1"
supports-color "^5.4.0"
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.14, postcss@^7.0.5:
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.5:
version "7.0.16"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.16.tgz#48f64f1b4b558cb8b52c88987724359acb010da2"
integrity sha512-MOo8zNSlIqh22Uaa3drkdIAgUGEL+AD1ESiSdmElLUmE2uVDo1QloiT/IfW9qRw8Gw+Y/w69UVMGwbufMSftxA==
@ -4601,6 +4664,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.14, postcss@^7.0.5
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^7.0.17:
version "7.0.17"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
posthtml-parser@^0.4.0, posthtml-parser@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.4.1.tgz#95b78fef766fbbe0a6f861b6e95582bc3d1ff933"
@ -4660,9 +4732,9 @@ private@^0.1.6:
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
version "0.11.10"
@ -4843,6 +4915,13 @@ readdirp@^2.0.0, readdirp@^2.2.1:
micromatch "^3.1.10"
readable-stream "^2.0.2"
readdirp@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.1.tgz#b158123ac343c8b0f31d65680269cc0fc1025db1"
integrity sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==
dependencies:
picomatch "^2.0.4"
regenerate-unicode-properties@^8.0.2:
version "8.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
@ -5014,14 +5093,14 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.1.5, resolve@^1.3.2, resolve@^1.4.0:
resolve@^1.1.5, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0:
version "1.11.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e"
integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==
dependencies:
path-parse "^1.0.6"
resolve@^1.1.7, resolve@^1.10.0:
resolve@^1.1.7:
version "1.10.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18"
integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==
@ -5073,11 +5152,16 @@ rxjs@^6.3.3:
dependencies:
tslib "^1.9.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.1.2:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@ -5098,11 +5182,11 @@ safer-eval@^1.3.0:
clones "^1.2.0"
sass@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.19.0.tgz#5de82c713d4299fac57384ef5219534a37fe3e6c"
integrity sha512-8kzKCgxCzh8/zEn3AuRwzLWVSSFj8omkiGwqdJdeOufjM+I88dXxu9LYJ/Gw4rRTHXesN0r1AixBuqM6yLQUJw==
version "1.22.4"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.22.4.tgz#00b433055f00a25ed5f060ca4abc332facaf02fc"
integrity sha512-gQFNzYKlAn9ee6Qy1UhTxy0G24QR5BWP61AN61jAEqwauzVCP5qjUveO/WkIj72po0ljncdVXo96EQR+ig2lRw==
dependencies:
chokidar "^2.0.0"
chokidar ">=2.0.0 <4.0.0"
sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
@ -5161,20 +5245,10 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
set-value@^0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE=
dependencies:
extend-shallow "^2.0.1"
is-extendable "^0.1.1"
is-plain-object "^2.0.1"
to-object-path "^0.3.0"
set-value@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274"
integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==
dependencies:
extend-shallow "^2.0.1"
is-extendable "^0.1.1"
@ -5373,9 +5447,9 @@ spdx-expression-parse@^3.0.0:
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1"
integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==
version "3.0.5"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
@ -5625,20 +5699,7 @@ tar-stream@^1.1.2:
to-buffer "^1.1.1"
xtend "^4.0.0"
tar@^4:
version "4.4.8"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==
dependencies:
chownr "^1.1.1"
fs-minipass "^1.2.5"
minipass "^2.3.4"
minizlib "^1.1.1"
mkdirp "^0.5.0"
safe-buffer "^5.1.2"
yallist "^3.0.2"
tar@^4.4.8:
tar@^4, tar@^4.4.8:
version "4.4.10"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1"
integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==
@ -5720,6 +5781,13 @@ to-regex-range@^2.1.0:
is-number "^3.0.0"
repeat-string "^1.6.1"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
to-regex@^3.0.1, to-regex@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
@ -5769,9 +5837,9 @@ trim-right@^1.0.1:
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
tty-browserify@0.0.0:
version "0.0.0"
@ -5849,14 +5917,14 @@ unicode-trie@^0.3.1:
tiny-inflate "^1.0.0"
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=
version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==
dependencies:
arr-union "^3.1.0"
get-value "^2.0.6"
is-extendable "^0.1.1"
set-value "^0.4.3"
set-value "^2.0.1"
uniq@^1.0.1:
version "1.0.1"
@ -6105,7 +6173,7 @@ yallist@^2.1.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
yallist@^3.0.0, yallist@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==