Merge branch 'strict-equality' of https://github.com/monkeytypegame/monkeytype into strict-equality

This commit is contained in:
Evan 2023-03-10 13:22:29 -06:00
commit 0ee13d8ac8
26 changed files with 332 additions and 201 deletions

View file

@ -84,7 +84,15 @@ export async function sendVerificationEmail(
e.message.includes("TOO_MANY_ATTEMPTS_TRY_LATER")
) {
// for some reason this error is not handled with a custom auth/ code, so we have to do it manually
throw new MonkeyError(429, "Too many requests. Please try again later.");
throw new MonkeyError(429, "Too many requests. Please try again later");
}
if (e.code === "auth/user-not-found") {
throw new MonkeyError(
500,
"Auth user not found when the user was found in the database",
JSON.stringify({ email: email, userInfoEmail: email, stack: e.stack }),
userInfo.uid
);
}
throw e;
}

View file

@ -61,6 +61,16 @@ export function updateData(data: MonkeyTypes.ChartData): void {
ChartController.miniResult.updateColors();
}
$(document).on("keydown", (event) => {
if (
event.key === "Escape" &&
Misc.isElementVisible(".pageAccount .miniResultChartWrapper")
) {
hide();
event.preventDefault();
}
});
$(".pageAccount").on("click", ".miniResultChartBg", () => {
hide();
});

View file

@ -1,10 +1,10 @@
import * as Misc from "../utils/misc";
import * as DB from "../db";
import Config from "../config";
import * as Notifications from "../elements/notifications";
import Ape from "../ape/index";
import Config from "../config";
import * as DB from "../db";
import * as Loader from "../elements/loader";
import * as Notifications from "../elements/notifications";
import { showNewResultFilterPresetPopup } from "../popups/new-result-filter-preset-popup";
import * as Misc from "../utils/misc";
export const defaultResultFilters: MonkeyTypes.ResultFilters = {
_id: "default-result-filters-id",
@ -80,17 +80,41 @@ export async function load(): Promise<void> {
console.log("loading filters");
try {
const newResultFilters = window.localStorage.getItem("resultFilters");
if (
newResultFilters !== null &&
newResultFilters !== "" &&
Object.keys(JSON.parse(newResultFilters)).length >=
Object.keys(defaultResultFilters).length
) {
filters = JSON.parse(newResultFilters);
// save();
} else {
if (newResultFilters === null) {
filters = defaultResultFilters;
// save();
} else {
const newFiltersObject = JSON.parse(newResultFilters);
let reset = false;
for (const key of Object.keys(defaultResultFilters)) {
if (reset === true) break;
if (newFiltersObject[key] === undefined) {
reset = true;
break;
}
if (
typeof defaultResultFilters[
key as keyof typeof defaultResultFilters
] === "object"
) {
for (const subKey of Object.keys(
defaultResultFilters[key as keyof typeof defaultResultFilters]
)) {
if (newFiltersObject[key][subKey] === undefined) {
reset = true;
break;
}
}
}
}
if (reset) {
filters = defaultResultFilters;
} else {
filters = newFiltersObject;
}
}
const newTags: {
@ -532,6 +556,8 @@ $(".pageAccount .topFilters .button.allFilters").on("click", () => {
// user is changing the filters -> current filter is no longer a filter preset
deSelectFilterPreset();
console.log(getFilters());
(Object.keys(getFilters()) as MonkeyTypes.Group[]).forEach((group) => {
// id and name field do not correspond to any ui elements, no need to update
if (group === "_id" || group === "name") {

View file

@ -485,8 +485,11 @@ $(document).ready(() => {
event.shiftKey)
) {
const popupVisible = isAnyPopupVisible();
const miniResultPopupVisible = isElementVisible(
".pageAccount .miniResultChartWrapper"
);
if (popupVisible) return;
if (popupVisible || miniResultPopupVisible) return;
if (Config.quickRestart === "esc" && ActivePage.get() === "login") return;
event.preventDefault();

View file

@ -10,7 +10,7 @@ import * as TestLogic from "../test/test-logic";
import * as Loader from "../elements/loader";
import * as PageTransition from "../states/page-transition";
import * as ActivePage from "../states/active-page";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
import * as LoadingPage from "../pages/loading";
import * as LoginPage from "../pages/login";
import * as ResultFilters from "../account/result-filters";
@ -47,6 +47,8 @@ import { navigate } from "../observables/navigate-event";
import { update as updateTagsCommands } from "../commandline/lists/tags";
import * as ConnectionState from "../states/connection";
let signedOutThisSession = false;
export const gmailProvider = new GoogleAuthProvider();
export async function sendVerificationEmail(): Promise<void> {
@ -217,7 +219,7 @@ export async function getDataAndInit(): Promise<boolean> {
AccountButton.loading(false);
}
if (Config.paceCaret === "pb" || Config.paceCaret === "average") {
if (!TestActive.get()) {
if (!TestState.isActive) {
PaceCaret.init();
}
}
@ -261,7 +263,7 @@ export async function loadUser(user: UserType): Promise<void> {
// showFavouriteThemesAtTheTop();
if (TestLogic.notSignedInLastResult !== null) {
if (TestLogic.notSignedInLastResult !== null && !signedOutThisSession) {
TestLogic.setNotSignedInUid(user.uid);
const response = await Ape.results.save(TestLogic.notSignedInLastResult);
@ -717,6 +719,7 @@ $("#top .signInOut").on("click", () => {
}
if (Auth.currentUser) {
signOut();
signedOutThisSession = true;
} else {
navigate("/login");
}

View file

@ -3,7 +3,7 @@ import * as Misc from "../utils/misc";
import * as ConfigEvent from "../observables/config-event";
import * as BannerEvent from "../observables/banner-event";
import Config from "../config";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
const breakpoint = 900;
let widerThanBreakpoint = true;
@ -27,7 +27,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>`);
setInterval(() => {
if (TestActive.get()) {
if (TestState.isActive) {
return;
}
refreshVisible();

View file

@ -20,7 +20,7 @@ import * as Replay from "../test/replay";
import * as MonkeyPower from "../elements/monkey-power";
import * as WeakSpot from "../test/weak-spot";
import * as ActivePage from "../states/active-page";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
import * as CompositionState from "../states/composition";
import * as TestInput from "../test/test-input";
import * as TestWords from "../test/test-words";
@ -100,7 +100,7 @@ function updateUI(): void {
}
function backspaceToPrevious(): void {
if (!TestActive.get()) return;
if (!TestState.isActive) return;
if (
TestInput.input.history.length === 0 ||
@ -141,7 +141,7 @@ function backspaceToPrevious(): void {
}
function handleSpace(): void {
if (!TestActive.get()) return;
if (!TestState.isActive) return;
if (TestInput.input.current === "") return;
@ -441,7 +441,7 @@ function handleChar(
}
//start the test
if (!TestActive.get() && !TestLogic.startTest()) {
if (!TestState.isActive && !TestLogic.startTest()) {
return;
}

View file

@ -1,9 +0,0 @@
let testActive = false;
export function get(): boolean {
return testActive;
}
export function set(active: boolean): void {
testActive = active;
}

View file

@ -2,7 +2,7 @@ import * as Misc from "../utils/misc";
import Config from "../config";
import * as TestInput from "./test-input";
import * as SlowTimer from "../states/slow-timer";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
export let caretAnimating = true;
const caret = $("#caret");
@ -134,7 +134,7 @@ export async function updatePosition(): Promise<void> {
if (
newTop >= middlePos &&
contentHeight > browserHeight &&
TestActive.get()
TestState.isActive
) {
const newscrolltop = newTop - middlePos / 2;
window.scrollTo({

View file

@ -1,5 +1,5 @@
import Config from "../config";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
import * as ConfigEvent from "../observables/config-event";
export function update(acc: number): void {
@ -14,7 +14,7 @@ export function update(acc: number): void {
export function show(): void {
if (!Config.showLiveAcc) return;
if (!TestActive.get()) return;
if (!TestState.isActive) return;
if (Config.timerStyle === "mini") {
if (!$("#miniTimerAndLiveWpm .acc").hasClass("hidden")) return;
$("#miniTimerAndLiveWpm .acc")

View file

@ -1,5 +1,5 @@
import Config from "../config";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
import * as ConfigEvent from "../observables/config-event";
export async function update(burst: number): Promise<void> {
@ -12,7 +12,7 @@ export async function update(burst: number): Promise<void> {
export function show(): void {
if (!Config.showLiveBurst) return;
if (!TestActive.get()) return;
if (!TestState.isActive) return;
if (Config.timerStyle === "mini") {
if (!$("#miniTimerAndLiveWpm .burst").hasClass("hidden")) return;
$("#miniTimerAndLiveWpm .burst")

View file

@ -1,5 +1,5 @@
import Config from "../config";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
import * as ConfigEvent from "../observables/config-event";
const liveWpmElement = document.querySelector("#liveWpm") as Element;
@ -8,11 +8,6 @@ const miniLiveWpmElement = document.querySelector(
) as Element;
export function update(wpm: number, raw: number): void {
// if (!TestActive.get() || !Config.showLiveWpm) {
// hideLiveWpm();
// } else {
// showLiveWpm();
// }
let number = wpm;
if (Config.blindMode) {
number = raw;
@ -26,7 +21,7 @@ export function update(wpm: number, raw: number): void {
export function show(): void {
if (!Config.showLiveWpm) return;
if (!TestActive.get()) return;
if (!TestState.isActive) return;
if (Config.timerStyle === "mini") {
if (!$("#miniTimerAndLiveWpm .wpm").hasClass("hidden")) return;
$("#miniTimerAndLiveWpm .wpm")

View file

@ -1,10 +1,10 @@
import { mapRange } from "../utils/misc";
import Config from "../config";
import * as ConfigEvent from "../observables/config-event";
import * as TestActive from "../states/test-active";
import * as TestState from "../test/test-state";
ConfigEvent.subscribe((eventKey) => {
if (eventKey === "monkey" && TestActive.get()) {
if (eventKey === "monkey" && TestState.isActive) {
if (Config.monkey) {
$("#monkey").removeClass("hidden");
} else {

View file

@ -5,7 +5,6 @@ import Config from "../config";
import * as DB from "../db";
import * as SlowTimer from "../states/slow-timer";
import * as Misc from "../utils/misc";
import * as TestActive from "../states/test-active";
import * as TestState from "./test-state";
import * as ConfigEvent from "../observables/config-event";
@ -113,7 +112,7 @@ export async function init(): Promise<void> {
}
export function update(expectedStepEnd: number): void {
if (settings === null || !TestActive.get() || TestUI.resultVisible) {
if (settings === null || !TestState.isActive || TestUI.resultVisible) {
return;
}
// if ($("#paceCaret").hasClass("hidden")) {

View file

@ -1,31 +1,31 @@
import * as TestUI from "./test-ui";
import { Chart } from "chart.js";
import Config from "../config";
import * as Misc from "../utils/misc";
import * as TestStats from "./test-stats";
import * as Keymap from "../elements/keymap";
import * as AdController from "../controllers/ad-controller";
import * as ChartController from "../controllers/chart-controller";
import * as ThemeColors from "../elements/theme-colors";
import QuotesController from "../controllers/quotes-controller";
import * as DB from "../db";
import * as TodayTracker from "./today-tracker";
import * as PbCrown from "./pb-crown";
import * as Keymap from "../elements/keymap";
import * as Loader from "../elements/loader";
import * as Notifications from "../elements/notifications";
import * as ThemeColors from "../elements/theme-colors";
import { Auth } from "../firebase";
import * as QuoteRatePopup from "../popups/quote-rate-popup";
import * as GlarsesMode from "../states/glarses-mode";
import * as TestInput from "./test-input";
import * as Notifications from "../elements/notifications";
import * as Loader from "../elements/loader";
import QuotesController from "../controllers/quotes-controller";
import * as AdController from "../controllers/ad-controller";
import * as TestConfig from "./test-config";
import { Chart } from "chart.js";
import { Auth } from "../firebase";
import * as SlowTimer from "../states/slow-timer";
import * as Misc from "../utils/misc";
import * as FunboxList from "./funbox/funbox-list";
import * as PbCrown from "./pb-crown";
import * as TestConfig from "./test-config";
import * as TestInput from "./test-input";
import * as TestStats from "./test-stats";
import * as TestUI from "./test-ui";
import * as TodayTracker from "./today-tracker";
import confetti from "canvas-confetti";
// eslint-disable-next-line no-duplicate-imports -- need to ignore because eslint doesnt know what import type is
import type { PluginChartOptions, ScaleChartOptions } from "chart.js";
import type { AnnotationOptions } from "chartjs-plugin-annotation";
import Ape from "../ape";
import confetti from "canvas-confetti";
let result: MonkeyTypes.Result<MonkeyTypes.Mode>;
let maxChartVal: number;
@ -417,7 +417,7 @@ export async function updateCrown(): Promise<void> {
);
}
function updateTags(dontSave: boolean): void {
async function updateTags(dontSave: boolean): Promise<void> {
const activeTags: MonkeyTypes.Tag[] = [];
const userTagsCount = DB.getSnapshot()?.tags?.length ?? 0;
try {
@ -445,6 +445,14 @@ function updateTags(dontSave: boolean): void {
);
$("#result .stats .tags .editTagsButton").addClass("invisible");
const funboxes = result.funbox?.split("#") ?? [];
const funboxObjects = await Promise.all(
funboxes.map(async (f) => Misc.getFunbox(f))
);
const allFunboxesCanGetPb = funboxObjects.every((f) => f?.canGetPb);
let annotationSide = "start";
let labelAdjust = 15;
activeTags.forEach(async (tag) => {
@ -460,7 +468,11 @@ function updateTags(dontSave: boolean): void {
$("#result .stats .tags .bottom").append(`
<div tagid="${tag._id}" aria-label="PB: ${tpb}" data-balloon-pos="up">${tag.display}<i class="fas fa-crown hidden"></i></div>
`);
if (Config.mode !== "quote" && !dontSave) {
if (
Config.mode !== "quote" &&
!dontSave &&
(result.funbox === "none" || funboxes.length === 0 || allFunboxesCanGetPb)
) {
if (tpb < result.wpm) {
//new pb for that tag
DB.saveLocalTagPB(
@ -725,7 +737,7 @@ export async function update(
updateQuoteFavorite(randomQuote);
await updateGraph();
await updateGraphPBLine();
updateTags(dontSave);
await updateTags(dontSave);
updateOther(difficultyFailed, failReason, afkDetected, isRepeated, tooShort);
((ChartController.result.options as PluginChartOptions<"line" | "scatter">)

View file

@ -1,62 +1,61 @@
import objectHash from "object-hash";
import Ape from "../ape";
import * as TestUI from "./test-ui";
import * as ManualRestart from "./manual-restart-tracker";
import Config, * as UpdateConfig from "../config";
import * as Misc from "../utils/misc";
import * as AdController from "../controllers/ad-controller";
import * as AnalyticsController from "../controllers/analytics-controller";
import * as ChallengeContoller from "../controllers/challenge-controller";
import QuotesController from "../controllers/quotes-controller";
import * as Notifications from "../elements/notifications";
import * as CustomText from "./custom-text";
import * as CustomTextState from "../states/custom-text-name";
import * as TestStats from "./test-stats";
import * as PractiseWords from "./practise-words";
import * as ShiftTracker from "./shift-tracker";
import * as Focus from "./focus";
import * as Funbox from "./funbox/funbox";
import * as Keymap from "../elements/keymap";
import * as ThemeController from "../controllers/theme-controller";
import * as PaceCaret from "./pace-caret";
import * as Caret from "./caret";
import * as LiveWpm from "./live-wpm";
import * as LiveAcc from "./live-acc";
import * as LiveBurst from "./live-burst";
import * as TimerProgress from "./timer-progress";
import * as DB from "../db";
import * as AccountButton from "../elements/account-button";
import * as Keymap from "../elements/keymap";
import * as Last10Average from "../elements/last-10-average";
import * as ModesNotice from "../elements/modes-notice";
import * as MonkeyPower from "../elements/monkey-power";
import * as Notifications from "../elements/notifications";
import { Auth } from "../firebase";
import * as ConfigEvent from "../observables/config-event";
import * as KeymapEvent from "../observables/keymap-event";
import * as TimerEvent from "../observables/timer-event";
import * as QuoteRatePopup from "../popups/quote-rate-popup";
import * as QuoteSearchPopup from "../popups/quote-search-popup";
import * as QuoteSubmitPopup from "../popups/quote-submit-popup";
import * as PbCrown from "./pb-crown";
import * as TestTimer from "./test-timer";
import * as OutOfFocus from "./out-of-focus";
import * as AccountButton from "../elements/account-button";
import * as DB from "../db";
import * as Replay from "./replay";
import * as TodayTracker from "./today-tracker";
import * as Wordset from "./wordset";
import * as ChallengeContoller from "../controllers/challenge-controller";
import * as QuoteRatePopup from "../popups/quote-rate-popup";
import * as BritishEnglish from "./british-english";
import * as EnglishPunctuation from "./english-punctuation";
import * as LazyMode from "./lazy-mode";
import * as Result from "./result";
import * as MonkeyPower from "../elements/monkey-power";
import * as ActivePage from "../states/active-page";
import * as TestActive from "../states/test-active";
import * as TestInput from "./test-input";
import * as TestWords from "./test-words";
import * as TestState from "./test-state";
import * as ModesNotice from "../elements/modes-notice";
import * as PageTransition from "../states/page-transition";
import * as ConfigEvent from "../observables/config-event";
import * as TimerEvent from "../observables/timer-event";
import * as Last10Average from "../elements/last-10-average";
import * as Monkey from "./monkey";
import objectHash from "object-hash";
import * as AnalyticsController from "../controllers/analytics-controller";
import { Auth } from "../firebase";
import * as AdController from "../controllers/ad-controller";
import * as TestConfig from "./test-config";
import * as ConnectionState from "../states/connection";
import * as CustomTextState from "../states/custom-text-name";
import * as PageTransition from "../states/page-transition";
import * as Misc from "../utils/misc";
import * as BritishEnglish from "./british-english";
import * as Caret from "./caret";
import * as CustomText from "./custom-text";
import * as EnglishPunctuation from "./english-punctuation";
import * as Focus from "./focus";
import * as Funbox from "./funbox/funbox";
import * as FunboxList from "./funbox/funbox-list";
import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer";
import * as KeymapEvent from "../observables/keymap-event";
import * as LazyMode from "./lazy-mode";
import * as LiveAcc from "./live-acc";
import * as LiveBurst from "./live-burst";
import * as LiveWpm from "./live-wpm";
import * as ManualRestart from "./manual-restart-tracker";
import * as Monkey from "./monkey";
import * as OutOfFocus from "./out-of-focus";
import * as PaceCaret from "./pace-caret";
import * as PbCrown from "./pb-crown";
import * as PractiseWords from "./practise-words";
import * as Replay from "./replay";
import * as Result from "./result";
import * as ShiftTracker from "./shift-tracker";
import * as TestConfig from "./test-config";
import * as TestInput from "./test-input";
import * as TestState from "./test-state";
import * as TestStats from "./test-stats";
import * as TestTimer from "./test-timer";
import * as TestUI from "./test-ui";
import * as TestWords from "./test-words";
import * as TimerProgress from "./timer-progress";
import * as TodayTracker from "./today-tracker";
import * as Wordset from "./wordset";
let failReason = "";
const koInputVisual = document.getElementById("koInputVisual") as HTMLElement;
@ -320,7 +319,7 @@ export function startTest(): boolean {
AnalyticsController.log("testStartedNoLogin");
}
TestActive.set(true);
TestState.setActive(true);
Replay.startReplayRecording();
Replay.replayGetWordsList(TestWords.words.list);
TestInput.resetKeypressTimings();
@ -417,7 +416,7 @@ export function restart(options = {} as RestartOptions): void {
// }
}
}
if (TestActive.get()) {
if (TestState.isActive) {
if (
Config.repeatQuotes === "typing" &&
Config.mode === "quote" &&
@ -486,7 +485,7 @@ export function restart(options = {} as RestartOptions): void {
TestInput.corrected.reset();
ShiftTracker.reset();
Caret.hide();
TestActive.set(false);
TestState.setActive(false);
Replay.stopReplayRecording();
LiveWpm.hide();
LiveAcc.hide();
@ -584,7 +583,7 @@ export function restart(options = {} as RestartOptions): void {
} else {
TestState.setRepeated(true);
TestState.setPaceRepeat(repeatWithPace);
TestActive.set(false);
TestState.setActive(false);
Replay.stopReplayRecording();
TestWords.words.resetCurrentIndex();
TestInput.input.reset();
@ -820,7 +819,7 @@ async function getNextWord(
let rememberLazyMode: boolean;
export async function init(): Promise<void> {
TestActive.set(false);
TestState.setActive(false);
MonkeyPower.reset();
Replay.stopReplayRecording();
TestWords.words.reset();
@ -1481,7 +1480,7 @@ function buildCompletedEvent(difficultyFailed: boolean): CompletedEvent {
}
export async function finish(difficultyFailed = false): Promise<void> {
if (!TestActive.get()) return;
if (!TestState.isActive) return;
if (TestInput.input.current.length !== 0) {
TestInput.input.pushHistory();
TestInput.corrected.pushHistory();
@ -1493,7 +1492,7 @@ export async function finish(difficultyFailed = false): Promise<void> {
TestUI.setResultCalculating(true);
TestUI.setResultVisible(true);
TestStats.setEnd(performance.now());
TestActive.set(false);
TestState.setActive(false);
Replay.stopReplayRecording();
Focus.set(false);
Caret.hide();
@ -1898,7 +1897,7 @@ $(".pageTest").on("click", "#restartTestButton", () => {
ManualRestart.set();
if (TestUI.resultCalculating) return;
if (
TestActive.get() &&
TestState.isActive &&
Config.repeatQuotes === "typing" &&
Config.mode === "quote"
) {

View file

@ -1,5 +1,6 @@
export let isRepeated = false;
export let isPaceRepeat = false;
export let isActive = false;
export let activeChallenge: null | MonkeyTypes.Challenge = null;
export let savingEnabled = true;
@ -11,6 +12,10 @@ export function setPaceRepeat(tf: boolean): void {
isPaceRepeat = tf;
}
export function setActive(tf: boolean): void {
isActive = tf;
}
export function setActiveChallenge(val: null | MonkeyTypes.Challenge): void {
activeChallenge = val;
}

View file

@ -13,7 +13,7 @@ import * as Misc from "../utils/misc";
import * as Notifications from "../elements/notifications";
import * as Caret from "./caret";
import * as SlowTimer from "../states/slow-timer";
import * as TestActive from "../states/test-active";
import * as TestState from "./test-state";
import * as Time from "../states/time";
import * as TimerEvent from "../observables/timer-event";
@ -236,7 +236,7 @@ export async function start(): Promise<void> {
timer = setTimeout(function () {
// time++;
if (!TestActive.get()) {
if (!TestState.isActive) {
if (timer !== null) clearTimeout(timer);
SlowTimer.clear();
slowTimerCount = 0;

View file

@ -5,7 +5,7 @@ import * as TestWords from "./test-words";
import * as TestInput from "./test-input";
import * as Time from "../states/time";
import * as SlowTimer from "../states/slow-timer";
import * as TestActive from "../states/test-active";
import * as TestState from "./test-state";
import * as ConfigEvent from "../observables/config-event";
export function show(): void {
@ -201,7 +201,7 @@ export function update(): void {
}
export function updateStyle(): void {
if (!TestActive.get()) return;
if (!TestState.isActive) return;
hide();
update();
setTimeout(() => {
@ -211,7 +211,7 @@ export function updateStyle(): void {
ConfigEvent.subscribe((eventKey, eventValue) => {
if (eventKey === "showTimerProgress") {
if (eventValue === true && TestActive.get()) {
if (eventValue === true && TestState.isActive) {
show();
} else {
hide();

View file

@ -2,7 +2,7 @@ import Config from "./config";
import * as Caret from "./test/caret";
import * as Notifications from "./elements/notifications";
import * as CustomText from "./test/custom-text";
import * as TestActive from "./states/test-active";
import * as TestState from "./test/test-state";
import * as ConfigEvent from "./observables/config-event";
import { debounce, throttle } from "throttle-debounce";
import * as TestUI from "./test/test-ui";
@ -75,7 +75,7 @@ window.addEventListener("beforeunload", (event) => {
) {
//ignore
} else {
if (TestActive.get()) {
if (TestState.isActive) {
event.preventDefault();
// Chrome requires returnValue to be set.
event.returnValue = "";

View file

@ -1341,5 +1341,27 @@
"row4": ["ဖဇ", "ထဌ", "ခဃ", "လဠ", "ဘယ", "ညဉ", "ာဦ", ",၊", ".။", "/?"],
"row5": [" "]
}
},
"gallium": {
"keymapShowTopRow": false,
"type": "ansi",
"keys": {
"row1": ["`~", "1!", "2@", "3#", "4$", "5%", "6^", "7&", "8*", "9(", "0)", "-_", "=+"],
"row2": ["bB", "lL", "dD", "cC", "vV", "zZ", "yY", "oO", "uU", ",<", "[{", "]}", "\\|"],
"row3": ["nN", "rR", "tT", "sS", "gG", "pP", "hH", "aA", "eE", "iI", "/?"],
"row4": ["qQ", "xX", "mM", "wW", "jJ", "kK", "fF", "'\"", ";:", ",)"],
"row5": [" "]
}
},
"gallium_angle": {
"keymapShowTopRow": false,
"type": "ansi",
"keys": {
"row1": ["`~", "1!", "2@", "3#", "4$", "5%", "6^", "7&", "8*", "9(", "0)", "-_", "=+"],
"row2": ["bB", "lL", "dD", "cC", "jJ", "zZ", "yY", "oO", "uU", ",<", "[{", "]}", "\\|"],
"row3": ["nN", "rR", "tT", "sS", "vV", "pP", "hH", "aA", "eE", "iI", "/?"],
"row4": ["xX", "mM", "wW", "gG", "qQ", "kK", "fF", "'\"", ";:", ",)"],
"row5": [" "]
}
}
}

View file

@ -184,6 +184,17 @@
<li>How many typing tests you've started and completed</li>
<li>How long you've been typing on the website</li>
</ul>
<p>Monkeytype does NOT collect:</p>
<ul>
<li>
custom texts (they are stored in your browser's local storage)
</li>
</ul>
<i>
If you believe a certain data type is missing from the lists above,
feel free to contact us and we will answer any questions and update
the privacy policy.
</i>
<h1 id="How_is_Data_Collected">How do we collect your data?</h1>

View file

@ -1399,12 +1399,6 @@
"id": 236,
"length": 286
},
{
"text": "I think if you don't really like a girl, you shouldn't horse around with her at all, and if you do like her, then you're supposed to like her face, and if you like her face, you ought to be careful about doing crumby stuff to it, like squirting water all over it. It's really too bad that so much crumby stuff is a lot of fun sometimes.",
"source": "The Catcher in the Rye",
"id": 237,
"length": 336
},
{
"text": "And now it's time for Silly Songs with Larry, the part of the show where Larry comes out and sings a silly song.",
"source": "VeggieTales",
@ -2623,12 +2617,6 @@
"id": 440,
"length": 179
},
{
"text": "But what leaves us out of breath the most isn't the moments spent on a bed too small for two but the expense of our hearts by the labor of our bodies. I guess that two float around the point. But I wanna say it's more than just those moments, at least more than the surface. It's the tempo within those seconds where time seems to slow to a crawl and I can't help but fall into the rhythm of your skin and your shaking breath and I sink into the warmest dreams and the softest skin.",
"source": "Twin Sized Sheets",
"id": 441,
"length": 482
},
{
"text": "I'm not very good at this, out, being in public. But I felt the need to speak up for this city that I love with all my heart. No one should have to live in fear, in fear of madmen who have no regard for who they injure.",
"source": "Daredevil",
@ -11402,7 +11390,7 @@
"length": 252
},
{
"text": "I had a job, I had a girl. I had something going, mister, in this world. I got laid off down at the lumber yard, our love went bad, times got hard. Now I work down at the carwash, where all it ever does is rain. Don't you feel like you're a rider on a downbound train.",
"text": "I had a job, I had a girl. I had something going, mister, in this world. I got laid off down at the lumber yard, our love went bad, times got hard. Now I work down at the carwash, where all it ever does is rain. Don't you feel like you're a rider on a downbound train?",
"source": "Downbound Train",
"id": 1950,
"length": 268
@ -13167,7 +13155,7 @@
},
{
"text": "Most test subjects do experience some, uh, cognitive deterioration after a few months in suspension. Now, you've been under for... quite a bit longer, and it's not out of the question that you might have a very minor case of serious brain damage. But don't be alarmed, all right? Although, if you do feel alarmed, try to hold onto that feeling, because that is the proper reaction to being told you have brain damage.",
"source": "Portal 2",
"source": "Wheatley, Portal 2",
"id": 2253,
"length": 417
},
@ -15489,7 +15477,7 @@
},
{
"text": "Every moment there are a million miracles happening around you: a flower blossoming, a bird tweeting, a bee humming, a raindrop falling, a snowflake wafting along the clear evening air. There is magic everywhere. If you learn how to live it, life is nothing short of a daily miracle.",
"source": "Inner Engineering: A Yogi’s Guide to Joy",
"source": "Inner Engineering: A Yogi's Guide to Joy",
"id": 2661,
"length": 283
},
@ -23761,12 +23749,6 @@
"id": 4089,
"length": 420
},
{
"text": "There is a difference between the Holy Spirit coming and residing within us and us receiving the clothing with power that Jesus spoke of - the baptism of the Holy Spirit.",
"source": "The Bourne Identity",
"id": 4090,
"length": 170
},
{
"text": "There is no physical separation after the slicing, so that edge can be ignored and we can treat the pizza, for thermal purposes, as an infinite plane. The procedure reduces the heat-transfer problem to one dimension represented by a vector normal to the pizza surface.",
"source": "The Thermodynamics of Pizza",
@ -27331,12 +27313,6 @@
"id": 4711,
"length": 76
},
{
"text": "This was the story of Howard Beale: the first known instance of a man who was killed because he had lousy ratings.",
"source": "Network",
"id": 4712,
"length": 114
},
{
"text": "Corn is what feeds the steer that becomes the steak. Corn feeds the chicken and the pig, the turkey and the lamb, the catfish and the tilapia and, increasingly, even the salmon, a carnivore by nature that the fish farmers are reengineering to tolerate corn. The eggs are made of corn. The milk and cheese and yogurt, which once came from dairy cows that grazed on grass, now typically come from cows that spend their working lives indoors tethered to machines, eating corn.",
"source": "The Omnivore's Dilemma: A Natural History of Four Meals",
@ -27433,12 +27409,6 @@
"id": 4728,
"length": 465
},
{
"text": "But I will tell you this. I'd use tonight to get myself organized. Ride out in the morning clear-headed. And startin' tomorrow morning, I will offer a personal $50 bounty for every decapitated head of as many of these godless heathens as anyone can bring in.",
"source": "Deadwood",
"id": 4729,
"length": 258
},
{
"text": "By venturing too close to the dragon's flame, you made an ash out of yourself.",
"source": "King's Quest I",
@ -31111,12 +31081,6 @@
"length": 494,
"id": 5382
},
{
"text": "Crying is for little girls, babies, and men who just had their ears ripped off.",
"source": "Oobeedoob Benubi, Thumb Wars",
"length": 79,
"id": 5383
},
{
"text": "I can't stand the thought of looking at you someday, this face I love, and not knowing who you are.",
"source": "Lisa Genova, Still Alice",
@ -32143,12 +32107,6 @@
"length": 302,
"id": 5599
},
{
"text": "Only once in your life, I truly believe, you find someone who can completely turn your world around. You tell them things that you've never shared with another soul and they absorb everything you say and actually want to hear more. You share hopes for the future, dreams that will never come true, goals that were never achieved and the many disappointments life has thrown at you. When something wonderful happens, you can't wait to tell them about it, knowing they will share in your excitement. They are not embarrassed to cry with you when you are hurting or laugh with you when you make a fool of yourself. Never do they hurt your feelings or make you feel like you are not good enough, but rather they build you up and show you the things about yourself that make you special and even beautiful. There is never any pressure, jealousy or competition but only a quiet calmness when they are around. You can be yourself and not worry about what they will think of you because they love you for who you are. The things that seem insignificant to most people such as a note, song or walk become invaluable treasures kept safe in your heart to cherish forever. Memories of your childhood come back and are so clear and vivid it's like being young again. Colours seem brighter and more brilliant. Laughter seems part of daily life where before it was infrequent or didn't exist at all. A phone call or two during the day helps to get you through a long day's work and always brings a smile to your face. In their presence, there's no need for continuous conversation, but you find you're quite content in just having them nearby. Things that never interested you before become fascinating because you know they are important to this person who is so special to you. You think of this person on every occasion and in everything you do. Simple things bring them to mind like a pale blue sky, gentle wind or even a storm cloud on the horizon. You open your heart knowing that there's a chance it may be broken one day and in opening your heart, you experience a love and joy that you never dreamed possible. You find that being vulnerable is the only way to allow your heart to feel true pleasure that's so real it scares you. You find strength in knowing you have a true friend and possibly a soul mate who will remain loyal to the end. Life seems completely different, exciting and worthwhile. Your only hope and security is in knowing that they are a part of your life.",
"source": "Bob Marley",
"length": 2468,
"id": 5600
},
{
"text": "All that is gold does not glitter, not all those who wander are lost; the old that is strong does not wither, deep roots are not reached by the frost. From the ashes a fire shall be woken, a light from the shadows shall spring; renewed shall be blade that was broken, the crownless again shall be king.",
"source": "J.R.R. Tolkien, The Fellowship of the Ring",
@ -32774,9 +32732,9 @@
"id": 5715
},
{
"text": "Christie! Ms. Esposito! Hold up. Hey, Jimmy McGill, we met inside. Hi. You didn't get it. You were never gonna get it. They dangle these things in front of you. They tell you, you got a chance, but I'm sorry. It's a lie because they had already made up their mind and they knew what they were going to do before you walked in the door. You made a mistake and they are never forgetting it. As far as they're concerned, your mistake is just, it's who you are. And it's all you are. And I'm not just talking about the scholarship here, I'm talking about everything. I mean, they'll smile at you, they'll pat you on the head, but they are never, ever letting you in. But listen... Listen... it doesn't matter. It doesn't because you don't need them. They're not going to give it to you? So what? You're going to take it. You're going to do whatever it takes. Do you hear me? You are not going to play by the rules. You're going to go your own way. You're going to do what they won't do. You're going to be smart. You are going to cut corners and you are going to win. They're on the 35th floor? You're going to be on the 50th floor. You're going to be looking down on them. And the higher you rise, the more they're going to hate you. Good. Good! You rub their noses in it. You make them suffer. Because you don't matter all that much to them. So what? So what? Screw them! Remember... the winner takes it all. You understand what I'm trying to tell you, right? All right. All right. Go get them.",
"text": "Kristy! Ms. Esposito! Hold up. Hey, Jimmy McGill, we met inside. Hi. You didn't get it. You were never gonna get it. They dangle these things in front of you. They tell you, you got a chance, but I'm sorry. It's a lie because they had already made up their mind and they knew what they were going to do before you walked in the door. You made a mistake and they are never forgetting it. As far as they're concerned, your mistake is just, it's who you are. And it's all you are. And I'm not just talking about the scholarship here, I'm talking about everything. I mean, they'll smile at you, they'll pat you on the head, but they are never, ever letting you in. But listen. Listen. it doesn't matter. It doesn't because you don't need them. They're not going to give it to you? So what? You're going to take it. You're going to do whatever it takes. Do you hear me? You are not going to play by the rules. You're going to go your own way. You're going to do what they won't do. You're going to be smart. You are going to cut corners and you are going to win. They're on the 35th floor? You're going to be on the 50th floor. You're going to be looking down on them. And the higher you rise, the more they're going to hate you. Good. Good! You rub their noses in it. You make them suffer. Because you don't matter all that much to them. So what? So what? Screw them! Remember... the winner takes it all. You understand what I'm trying to tell you, right? All right. All right. Go get them.",
"source": "Better Call Saul",
"length": 1492,
"length": 1486,
"id": 5716
},
{
@ -35973,9 +35931,9 @@
"id": 6359
},
{
"text": "I'd rather have loyalty than love, 'cause love really don't mean jack. See, love is just a feeling - you can love somebody and talk behind their back. It don't take much to love, you can love somebody just by being attached. See, loyalty is a action, you can love or hate me and still have my back.",
"text": "I'd rather have loyalty than love, 'cause love really don't mean jack. See, love is just a feeling - you can love somebody and talk behind their back. It don't take much to love, you can love somebody just by being attached. See, loyalty is an action, you can love or hate me and still have my back.",
"source": "21 Savage, Ball w/o You",
"length": 298,
"length": 299,
"approvedBy": "Smithster",
"id": 6360
},
@ -37856,7 +37814,7 @@
"id": 6636
},
{
"text": "Don't leave, it's my fault. Cause' when it all comes crashing down I'll need you.",
"text": "Don't leave, it's my fault. 'Cause when it all comes crashing down I'll need you.",
"source": "EARFQUAKE, Tyler, the Creator",
"length": 81,
"approvedBy": "Smithster",
@ -37939,13 +37897,6 @@
"approvedBy": "Smithster",
"id": 6648
},
{
"text": "When you reach the end of your rope, tie a knot in it and hang on.",
"source": "Franklin D. Roosevelt",
"length": 66,
"approvedBy": "Smithster",
"id": 6649
},
{
"text": "Always remember that you are absolutely unique. Just like everyone else.",
"source": "Margaret Mead",
@ -38065,13 +38016,6 @@
"approvedBy": "Smithster",
"id": 6666
},
{
"text": "Only poetry could best fit into the vast emptiness created by men.",
"source": "Manning Marable",
"length": 66,
"approvedBy": "Smithster",
"id": 6667
},
{
"text": "A hot desert storm eddied around him and rushed to me, making my skin contract, and my pores slam shut... His hair was the color of burning embers and his eyes pierced.",
"source": "Maya Angelou",
@ -38707,6 +38651,90 @@
"source": "Conduction Heat Transfer",
"id": 6768,
"length": 250
},
{
"text": "The secret of happiness, you see, is not found in seeking more, but in developing the capacity to enjoy less.",
"source": "Unknown",
"id": 6769,
"length": 109
},
{
"text": "My wife and I had lived in our house for over a dozen years at that point. Holly's family had lived in theirs even longer. We were both active families, involved in the community, with work, and with our churches. Yet, the thick woods covering the lots we each occupied along a cul-de-sac was enough of a barrier to our getting to know each other that we didn't even realize our neighbors across the street had a little girl the same age as ours. That is, until they met at the kindergarten in the elementary school six miles away.",
"source": "Strong Towns - A Bottom-Up Revolution to Rebuild American Prosperity",
"id": 6770,
"length": 531
},
{
"text": "The single quality that is common across every living creature on this planet, is fear. Its funny, then, that as common as fear is, we so easily underestimate its power. Fear of growing close to someone, the subsequent fear of loss, fear of failure. And as more people depend on you, those fears can take on greater power. But fear itself isnt worthy of concern, it is who we become while in its clutches. Will you be proud of that person? Will you forgive them? Will you understand why they felt the need to do the things they did? Will you even recognize them? Or will the person staring back at you be the very thing you should have feared from the start? I suppose we all find out sooner or later.",
"source": "RWBY",
"id": 6771,
"length": 703
},
{
"text": "I once told you I made more mistakes than any man, woman, or child on this planet. I wasn't exaggerating. I'm... cursed. For thousands of years I have walked the surface of Remnant; living, dying, and reincarnating in the body of a like-minded soul. The Professor Ozpin you all met was not my first form, and it clearly wasn't my last. It's... an extraordinarily strenuous process, on everyone involved.",
"source": "RWBY",
"id": 6772,
"length": 403
},
{
"text": "Being a team leader isn't just a title you carry into battle, but a badge you wear constantly. If you are not always performing at your absolute best, then what reason do you give others to follow you?",
"source": "RWBY",
"id": 6773,
"length": 201
},
{
"text": "I'll...keep this brief. You have traveled here today in search of knowledge--to hone your craft and acquire new skills. And when you have finished, you plan to dedicate your life to the protection of the people. But I look amongst you, and all I see is wasted energy, in need of purpose -- direction. You assume knowledge will free you of this, but your time at this school will prove that knowledge can only carry you so far. It is up to you to take the first step.",
"source": "RWBY",
"id": 6774,
"length": 466
},
{
"text": "It's important not to lose sight of what drives us: love, justice, reverence... but the moment you put your desires before my own... they will be lost to you. This isn't a threat, this is simply the truth. The path to your desires is only found... through me.",
"source": "RWBY",
"id": 6775,
"length": 259
},
{
"text": "You told me once that bad things just happen. You were angry when you said it, and I didn't want to listen. But you were right. Bad things do happen, all the time, every day. Which is why I'm out here, to do whatever I can, wherever I can, and hopefully do some good.",
"source": "RWBY",
"id": 6776,
"length": 267
},
{
"text": "We've all lost something, and I've seen what loss can do to people. But if we gave up every time we lost, then we'd never be able to move forward.",
"source": "RWBY",
"id": 6777,
"length": 146
},
{
"text": "You're wrong. We've done things that most people would call impossible, and I know the only reason we were able to do it is because we didn't do it alone! We had people to teach us, people to help us, we had each other. Work with us. At least I know we'll have a better chance if we try together. Please.",
"source": "RWBY",
"id": 6778,
"length": 304
},
{
"text": "Just because you don't have an idea, doesn't mean we're out of options! Oz hasn't been here to tell us what to do, but we still managed to get this far anyway. We've been in bad situations before, and we don't need an adult to come save us or tell us what to do. We just did it our way! And I say we do it our way. And if you think you can keep up with us \"kids\"... we'd be happy to have you.",
"source": "RWBY",
"id": 6779,
"length": 392
},
{
"text": "The Threads of Fate are not puppet strings. They connect life to destiny. You are fate-touched, able to see and bend the threads around you. Understand the wholeness of what you serve, champion. All the living share one experience: death. Your mother, your sister, even you. We must safeguard that beautiful moment when the soul transitions to a new purpose.",
"source": "The Legend of Vox Machina",
"id": 6780,
"length": 358
},
{
"text": "Protecting the sanctity between life and death. That is our cause. Your cause. Do you accept this charge?",
"source": "The Legend of Vox Machina",
"id": 6781,
"length": 105
},
{
"text": "I've been watching you longer than you know. Recklessly drawn to your destiny, like a flame. My beautiful champion. There is much to fear, but not death. For it gives meaning to life.",
"source": "The Legend of Vox Machina",
"id": 6782,
"length": 183
}
]
}

View file

@ -136,9 +136,9 @@
availability of Monkeytype services, please send your disclosure via
<span style="display: inline-flex">
<a href="mailto:contact@monkeytype.com" rel="noopener">email</a>
,
.
</span>
. For non-security related platform bugs, follow the bug submission
For non-security related platform bugs, follow the bug submission
<span style="display: inline-flex">
<a
href="https://github.com/monkeytypegame/monkeytype#bug-report-or-feature-request"

View file

@ -1146,5 +1146,12 @@
"mainColor": "#13005A",
"subColor": "#1c82adc4",
"textColor": "#125d98"
}
},
{
"name": "breeze",
"bgColor": "#e8d5c4",
"mainColor": "#7d67a9",
"subColor": "#3a98b9",
"textColor": "#1b4c5e"
}
]

View file

@ -0,0 +1,12 @@
:root {
--bg-color: #e8d5c4;
--main-color: #7d67a9;
--caret-color: #7d67a9;
--sub-color: #3a98b9;
--sub-alt-color: #f6e6da;
--text-color: #1b4c5e;
--error-color: #7d67a9;
--error-extra-color: #9f3e6d;
--colorful-error-color: #f9f871;
--colorful-error-extra-color: #67dfa1;
}