Compare commits

...

8 Commits

Author SHA1 Message Date
Greg Shuflin
beb9413a90 Generalize to all conlangs 2021-09-15 01:36:12 -07:00
Greg Shuflin
4a73e9983a Generalize entry logic into EntryBase 2021-09-15 01:30:51 -07:00
Greg Shuflin
0b7ee6b3c6 Code reorg 2021-09-15 00:33:29 -07:00
Greg Shuflin
198bb6128a Make editing Saimiar entries' english work 2021-09-15 00:28:47 -07:00
Greg Shuflin
321afa3b12 Add jwt 2021-09-13 13:29:38 -07:00
Greg Shuflin
2ac5b0527a Fix weird type bug 2021-09-13 01:36:16 -07:00
Greg Shuflin
5b3355a651 Use double-quotes 2021-09-13 01:05:41 -07:00
Greg Shuflin
48d8cef2fd Fix result pluralization 2021-09-13 01:04:43 -07:00
9 changed files with 333 additions and 107 deletions

View File

@ -25,6 +25,8 @@ module.exports = {
"unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": ["error"]
"@typescript-eslint/no-redeclare": ["error"],
"quotes": ["error", "double"],
"no-warning-comments": "off"
},
};

View File

@ -26,6 +26,7 @@
"typescript": "^4.4.3"
},
"dependencies": {
"jsonwebtoken": "^8.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}

View File

@ -37,6 +37,10 @@ input {
padding: 5px;
}
.searchResultHeader {
padding-bottom: 1em;
}
.synclass {
color: #a63333;
i {

View File

@ -1,72 +1,50 @@
import React, {useState} from 'react';
import React, {useState} from "react";
import './App.scss';
import {SaiEntryProps, JutEntryProps, ElesuEntryProps, TukEntryProps} from './dbtypes';
import {SaiEntry, JutEntry, ElesuEntry, TukEntry} from './Entries';
import "./App.scss";
import {SaiEntryProps, JutEntryProps, ElesuEntryProps, TukEntryProps, Conlang, SearchDirection} from "./dbtypes";
import {SaiEntry, JutEntry, ElesuEntry, TukEntry} from "./Entries";
import {setPassword, searchEntry} from "./requests";
const backendUrl = 'https://kucinakobackend.ichigo.everydayimshuflin.com';
const PasswordDialog = (_props) => {
const [password, setPasswordStr] = useState("");
enum Conlang {
Saimiar = 'saimiar',
Elesu = 'elesu',
Tukvaysi = 'tukvaysi',
Juteyuji = 'juteyuji',
}
const modal = (): any => document.querySelector(".passwordDialog");
const save = () => {
setPassword(password);
modal().close();
location.reload(); // TODO this is a hack
};
enum SearchDirection {
ToConlang,
ToEnglish
}
const cancel = () => {
modal().close();
};
return (
<dialog className="passwordDialog">
<input type="password" placeholder="enter password" value={password} onChange={ (evt) => setPasswordStr(evt.target.value) } />
<button onClick={save}>Save</button>
<button onClick={cancel}>Cancel</button>
</dialog>);
};
const renderConlang = (conlang: Conlang): string => {
if (conlang === Conlang.Saimiar) {
return 'Saimiar';
return "Saimiar";
}
if (conlang === Conlang.Elesu) {
return 'Elésu';
return "Elésu";
}
if (conlang === Conlang.Juteyuji) {
return 'Juteyuji';
return "Juteyuji";
}
if (conlang === Conlang.Tukvaysi) {
return 'Tukvaysi';
return "Tukvaysi";
}
};
function buildRequest(searchTerm: string, conlang: Conlang, direction: SearchDirection, jsonHandler: (json: Object) => void) {
const specForConlang = {
[Conlang.Saimiar]: 'sai',
[Conlang.Juteyuji]: 'jut',
[Conlang.Tukvaysi]: 'tuk',
[Conlang.Elesu]: 'elesu',
};
const offset = 0;
const limit = 20;
const conlangDb = conlang.toString();
const conlangSpec = specForConlang[conlang];
const field = direction === SearchDirection.ToConlang ? 'eng' : conlangSpec;
const params = new URLSearchParams([
[field, `like.*${searchTerm}*`],
['order', conlangSpec],
['limit', limit],
['offset', offset],
]);
const effectiveUri = `${backendUrl}/${conlangDb}?${params}`;
fetch(`${effectiveUri}`)
.then((resp) => resp.json())
.then((json) => {
jsonHandler(json);
});
}
interface EntryProps {
conlang: Conlang;
entry: SaiEntryProps | JutEntryProps | ElesuEntryProps | TukEntryProps;
@ -106,9 +84,10 @@ const Results = (props: ResultsProps) => {
const renderedName = renderConlang(conlang);
const searchType = (props.direction === SearchDirection.ToConlang) ? `English -> ${renderedName}` : `${renderedName} -> English`;
const result = num === 1 ? "result" : "results";
const header = (
<div className="searchResultHeader" key="header">
Searched for <b>{ props.searchTerm }</b>, { searchType }, found { num } result(s)
Searched for <b>{ props.searchTerm }</b>, { searchType }, found { num } { result }
</div>);
const entries = props.searchResults.map(
(entry, _idx) => <Entry entry={ entry } key= { entry.id } conlang={ conlang } />,
@ -119,35 +98,35 @@ const Results = (props: ResultsProps) => {
const results = props.searchResults;
return (
<div className="results">
{ results ? content() : 'No search' }
{ results ? content() : "No search" }
</div>
);
};
const App = (_props) => {
const defaultConlang = window.sessionStorage.getItem('conlang') as Conlang || Conlang.Saimiar;
const defaultConlang = window.sessionStorage.getItem("conlang") as Conlang || Conlang.Saimiar;
const [searchResults, setSearchResults] = useState(null);
const [conlang, setConlangState] = useState(defaultConlang);
const [direction, setDirection] = useState(null);
const [searchTerm, setSearchTerm] = useState(null);
const [searchBoxInput, setSearchBoxInput] = useState('');
const [searchBoxInput, setSearchBoxInput] = useState("");
const setConlang = (conlang: Conlang) => {
setConlangState(conlang);
window.sessionStorage.setItem('conlang', conlang);
window.sessionStorage.setItem("conlang", conlang);
};
const searchConlang = (_evt) => {
const searchTerm = searchBoxInput;
if (searchTerm === '') {
if (searchTerm === "") {
setSearchResults(null);
setSearchTerm(null);
setDirection(null);
return;
}
buildRequest(searchTerm, conlang, SearchDirection.ToEnglish, (json) => {
searchEntry(searchTerm, conlang, SearchDirection.ToEnglish, (json) => {
setSearchResults(json);
setSearchTerm(searchTerm);
setDirection(SearchDirection.ToEnglish);
@ -156,12 +135,12 @@ const App = (_props) => {
const searchEng = (_evt) => {
const searchTerm = searchBoxInput;
if (searchTerm === '') {
if (searchTerm === "") {
setSearchResults(null);
setSearchTerm(null);
setDirection(null);
} else {
buildRequest(searchTerm, conlang, SearchDirection.ToConlang, (json) => {
searchEntry(searchTerm, conlang, SearchDirection.ToConlang, (json) => {
setSearchResults(json);
setSearchTerm(searchTerm);
setDirection(SearchDirection.ToConlang);
@ -182,8 +161,14 @@ const App = (_props) => {
</select>
);
const showPasswordBox = () => {
const modal: any = document.querySelector(".passwordDialog");
modal.showModal();
};
return (
<main>
<PasswordDialog />
<div className="container">
<div className="search">
<h1>Kucinako - Wordbook of Arzhanai languages</h1>
@ -198,6 +183,7 @@ const App = (_props) => {
{ langSelectDropdown }
<button onClick={ searchConlang } className="searchButton">{renderConlang(conlang)}</button>
<button onClick={ searchEng } className="searchButton">English</button>
<button onClick={ showPasswordBox } className="searchButton">Password</button>
</div>
<Results
searchResults={ searchResults }

View File

@ -1,15 +1,86 @@
import React from 'react';
import {declineSaimiar} from './saimiar_morphology';
import {SaiEntryProps, JutEntryProps, ElesuEntryProps, TukEntryProps} from './dbtypes';
import React, {useState} from "react";
import {Conlang} from "./dbtypes";
import {updateEntry, getPassword} from "./requests";
import {declineSaimiar} from "./saimiar_morphology";
import {SaiEntryProps, JutEntryProps, ElesuEntryProps, TukEntryProps} from "./dbtypes";
interface BaseProps {
id: number;
conlang: Conlang;
conlangEntry: string;
english: string;
langSpecific: React.ReactNode;
}
const EntryBase = (props: BaseProps) => {
const [editing, setEditing] = useState(false);
const [english, setEnglish] = useState(props.english);
const mainEntryStyle = {
display: "flex",
justifyContent: "space-between",
flexDirection: "row",
};
const controlStyle = {
display: "flex",
justifyContent: "space-between",
flexDirection: "row",
minWidth: "20%",
};
const save = () => {
updateEntry(props.conlang, props.id, english);
};
const engTranslation = editing ? <input type="text" value={ english } onChange={ (evt) => setEnglish(evt.target.value) }/>
: english;
const EditControls = ({onSave}: { onSave: () => void }) => {
const cancel = () => setEditing(false);
const edit = (evt) => {
evt.preventDefault();
setEditing(true);
};
if (!getPassword()) {
return null;
}
return (editing ? (<span>
<button onClick={ onSave }>Save</button>
<button onClick={ cancel }>Cancel</button>
</span>)
: <a href="" onClick={edit}>Edit</a>);
};
const expand = (evt) => {
evt.preventDefault();
};
return (
<div className="searchResult" key={ props.id }>
<div style={mainEntryStyle}>
<span><b>{ props.conlangEntry }</b> { engTranslation }</span>
<span style={controlStyle}>
<a href="" onClick={expand}>Expand</a>
<EditControls onSave={save} />
</span>
</div>
{ props.langSpecific }
</div>
);
};
const SaiEntry = (props: {entry: SaiEntryProps}) => {
const {entry} = props;
const synCategory = entry.syn_category;
const isNominal = synCategory === 'nominal';
return (
<div className="searchResult" key={ entry.id }>
<b>{ entry.sai }</b> - { entry.eng }
<br />
const isNominal = synCategory === "nominal";
const langSpecific = (
<div>
<span className="synclass">
<i>{ entry.syn_category }</i>
{ entry.morph_type ? `\t\t${entry.morph_type}` : null }
@ -18,15 +89,17 @@ const SaiEntry = (props: {entry: SaiEntryProps}) => {
</span>
</div>
);
return <EntryBase id={entry.id} conlang={Conlang.Saimiar} conlangEntry={entry.sai} english={entry.eng} langSpecific={langSpecific} />;
};
function formatMorphology(entry) {
const decl = declineSaimiar(entry);
if (!decl) {
return '';
return "";
}
return (<span style={ {fontSize: 'medium', color: '#6a3131'} } >
return (<span style={ {fontSize: "medium", color: "#6a3131"} } >
Abs: <i>{decl.abs}</i>, Erg: <i>{decl.erg}</i>,
Adp: <i>{decl.adp}</i>,
All: <i>{decl.all}</i>,
@ -40,40 +113,36 @@ function formatMorphology(entry) {
const JutEntry = (props: {entry: JutEntryProps}) => {
const {entry} = props;
return (
<div className="searchResult" key={ entry.id }>
<b>{ entry.jut }</b> - { entry.eng }
<br/>
<span className="synclass">
{ entry.syn_category === 'noun' ? entry.gender : null }
</span>
</div>);
const langSpecific = (<div>
<span className="synclass">
{entry.syn_category} { entry.syn_category === "noun" ? `- ${entry.gender}` : null }
</span>
</div>);
return <EntryBase id={entry.id} conlang={Conlang.Juteyuji} conlangEntry={entry.jut} english={entry.eng} langSpecific={langSpecific} />;
};
const ElesuEntry = (props: {entry: ElesuEntryProps}) => {
const {entry} = props;
return (
<div className="searchResult" key={ entry.id }>
<b>{ entry.elesu }</b> - { entry.eng }
<br/>
<span className="synclass">
{ entry.syn_category }
</span>
</div>);
const langSpecific = <div>
<span className="synclass">
{ entry.syn_category }
</span>
</div>;
return <EntryBase id={entry.id} conlang={Conlang.Elesu} conlangEntry={entry.elesu} english={entry.eng} langSpecific={langSpecific} />;
};
const TukEntry = (props: {entry: TukEntryProps}) => {
const {entry} = props;
const langSpecific = <div>
<span className="synclass">
{ entry.syn_category }
</span>
</div>;
return (
<div className="searchResult" key={ entry.id }>
<b>{ entry.tuk }</b> - { entry.eng }
<br/>
<span className="synclass">
{ entry.syn_category }
</span>
</div>);
return <EntryBase id={entry.id} conlang={Conlang.Tukvaysi} conlangEntry={entry.tuk} english={entry.eng} langSpecific={langSpecific} />;
};
export {SaiEntry, ElesuEntry, JutEntry, TukEntry};

View File

@ -1,3 +1,15 @@
enum Conlang {
Saimiar = "saimiar",
Elesu = "elesu",
Tukvaysi = "tukvaysi",
Juteyuji = "juteyuji",
}
enum SearchDirection {
ToConlang,
ToEnglish
}
interface SaiEntryProps {
id: number;
sai: string;
@ -35,4 +47,4 @@ interface TukEntryProps {
notes: string;
}
export {SaiEntryProps, JutEntryProps, ElesuEntryProps, TukEntryProps};
export {SaiEntryProps, JutEntryProps, ElesuEntryProps, TukEntryProps, Conlang, SearchDirection};

67
src/requests.ts Normal file
View File

@ -0,0 +1,67 @@
import jwt from "jsonwebtoken";
import {Conlang, SearchDirection} from "./dbtypes";
const backendUrl = "https://kucinakobackend.ichigo.everydayimshuflin.com";
const getPassword = (): string | null => window.sessionStorage.getItem("password");
const setPassword = (password: string) => {
window.sessionStorage.setItem("password", password);
};
const makeAuthorizationHeader = (key: string): string => {
const unixTime = Date.now() / 1000;
const token = jwt.sign({role: "conlang_postgrest_rw", exp: unixTime + 60}, key);
return `Bearer ${token}`;
};
const updateEntry = (conlang: Conlang, id: number, english: string) => {
const url = `${backendUrl}/${conlang.toString()}?id=eq.${id}`;
const request = new Request(url, {
method: "PATCH",
headers: {
Authorization: makeAuthorizationHeader(getPassword()),
"Content-Type": "application/json",
},
body: JSON.stringify({
eng: english,
}),
});
fetch(request).then((resp) => console.log(resp));
};
function searchEntry(searchTerm: string, conlang: Conlang, direction: SearchDirection, jsonHandler: (json: Object) => void) {
const specForConlang = {
[Conlang.Saimiar]: "sai",
[Conlang.Juteyuji]: "jut",
[Conlang.Tukvaysi]: "tuk",
[Conlang.Elesu]: "elesu",
};
const offset = 0;
const limit = 20;
const conlangDb = conlang.toString();
const conlangSpec = specForConlang[conlang];
const field = direction === SearchDirection.ToConlang ? "eng" : conlangSpec;
const params = new URLSearchParams([
[field, `like.*${searchTerm}*`],
["order", conlangSpec],
["limit", limit],
["offset", offset],
] as string[][]);
const effectiveUri = `${backendUrl}/${conlangDb}?${params}`;
fetch(`${effectiveUri}`)
.then((resp) => resp.json())
.then((json) => {
jsonHandler(json);
});
}
export {backendUrl, updateEntry, getPassword, setPassword, searchEntry};

View File

@ -12,23 +12,23 @@ type SaimiarDeclension = {
};
function declineSaimiar(entry): SaimiarDeclension {
const split = entry.sai.split(' ');
const split = entry.sai.split(" ");
const sai = split.at(-1);
const morph = entry.morph_type;
if (morph === '-V') {
if (morph === "-V") {
return vowelDeclension(sai);
}
if (morph === '-a/i') {
if (morph === "-a/i") {
return aiDeclension(sai);
}
if (morph === 'e-') {
if (morph === "e-") {
return initalDeclension(sai);
}
if (morph === '-C') {
if (morph === "-C") {
return consonantDeclension(sai);
}
@ -39,7 +39,7 @@ function declineSaimiar(entry): SaimiarDeclension {
function vowelDeclension(sai: string): SaimiarDeclension {
const {root, ending} = rootEndingPair(sai);
const adpEnding = ending === 'u' ? 'ys' : `${ending}s`;
const adpEnding = ending === "u" ? "ys" : `${ending}s`;
return {
abs: `${root}${ending}`,
@ -69,8 +69,8 @@ function aiDeclension(sai: string): SaimiarDeclension {
function consonantDeclension(sai: string): SaimiarDeclension {
const split = rootEndingPair(sai);
const root = split.ending === 'ø' ? split.root : sai;
const absFinal = split.ending === 'ø' ? 'ø' : '';
const root = split.ending === "ø" ? split.root : sai;
const absFinal = split.ending === "ø" ? "ø" : "";
return {
abs: `${root}${absFinal}`,
@ -84,7 +84,7 @@ function consonantDeclension(sai: string): SaimiarDeclension {
};
}
const vowels = ['a', 'e', 'ê', 'i', 'o', 'ô', 'u', 'y'];
const vowels = ["a", "e", "ê", "i", "o", "ô", "u", "y"];
function initalDeclension(sai: string): SaimiarDeclension {
const initial = sai.slice(0, 1);
@ -92,8 +92,8 @@ function initalDeclension(sai: string): SaimiarDeclension {
const finalRootSound = root.slice(-1);
const finalVowel = vowels.includes(finalRootSound);
const instEnding = finalVowel ? 'ŕø' : 'ar';
const relEnding = finalVowel ? 'źi' : 'ai';
const instEnding = finalVowel ? "ŕø" : "ar";
const relEnding = finalVowel ? "źi" : "ai";
return {
abs: `${initial}${root}`,

View File

@ -1432,6 +1432,11 @@ browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4
escalade "^3.1.1"
node-releases "^1.1.75"
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
@ -2173,6 +2178,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
@ -3573,6 +3585,22 @@ json5@^2.1.0, json5@^2.1.2:
dependencies:
minimist "^1.2.5"
jsonwebtoken@^8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz"
@ -3591,6 +3619,23 @@ jsprim@^1.2.2:
array-includes "^3.1.2"
object.assign "^4.1.2"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz"
@ -3678,6 +3723,36 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
@ -3688,6 +3763,11 @@ lodash.merge@^4.6.2:
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz"
@ -3895,6 +3975,11 @@ ms@2.1.2:
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
msgpackr-extract@^1.0.13:
version "1.0.13"
resolved "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-1.0.13.tgz"
@ -5122,7 +5207,7 @@ scheduler@^0.20.2:
loose-envify "^1.1.0"
object-assign "^4.1.1"
semver@^5.4.1, semver@^5.5.0, semver@^5.7.0:
semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0:
version "5.7.1"
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==