Merge branch 'master' of github.com:mtthwn/monkeytype

This commit is contained in:
mtthwn 2021-05-10 18:06:48 -04:00
commit 4ac6cd3ac2
45 changed files with 19864 additions and 17439 deletions

View file

@ -2,4 +2,4 @@
"projects": {
"default": "your-firebase-project-id"
}
}
}

View file

@ -6,7 +6,7 @@ labels: bug
assignees: ""
---
<!--
<!--
**DID YOU MAKE SURE TO CLEAR CACHE BEFORE OPENING AN ISSUE?**
Sometimes your browser has old files cached and the bug you are experiencing might be already fixed, or is just a side effect of a new update. If you don't know how to do that, this website should help: https://www.pcmag.com/how-to/how-to-clear-your-cache-on-any-browser
@ -14,12 +14,11 @@ Sometimes your browser has old files cached and the bug you are experiencing mig
**Describe the bug** <!-- A clear and concise description of what the bug is. -->
<!--
<!--
**Did it happen in incognito mode?**
Sometimes things work in incognito mode, which allows me to further track down the issue.
Sometimes things work in incognito mode, which allows me to further track down the issue.
-->
@ -32,10 +31,8 @@ Sometimes things work in incognito mode, which allows me to further track down t
**Expected behavior** <!-- A clear and concise description of what you expected to happen. -->
**Screenshots** <!-- If applicable, add screenshots to further help explain your problem. -->
**Desktop:** <!-- if you encountered an issue while using Monkeytype on your computer please complete the following information, delete this section if not-->
- OS: [] <!-- e.g. Windows 10, MacOS, Linux-->

View file

@ -1,4 +1,4 @@
Adding a language or a theme? Make sure to edit the `_list.json` file and add the `language.json` or `theme.css` as well, otherwise, it will not work!
Adding a language or a theme? For languages, make sure to edit the `_list.json`, `_groups.json` files, and add the `language.json` file as well. For themes, make sure to add the `theme.css` file. It will not work if you don't follow these steps!
Please reference any issues related to your pull request.

View file

@ -1627,7 +1627,7 @@ function updateDiscordRole(discordId, wpm) {
});
}
function isTagValid(name) {
function isTagPresetNameValid(name) {
if (name === null || name === undefined || name === "") return false;
if (name.length > 16) return false;
return /^[0-9a-zA-Z_.-]+$/.test(name);
@ -1635,7 +1635,7 @@ function isTagValid(name) {
exports.addTag = functions.https.onCall((request, response) => {
try {
if (!isTagValid(request.name)) {
if (!isTagPresetNameValid(request.name)) {
return { resultCode: -1 };
} else {
return db
@ -1665,7 +1665,7 @@ exports.addTag = functions.https.onCall((request, response) => {
exports.editTag = functions.https.onCall((request, response) => {
try {
if (!isTagValid(request.name)) {
if (!isTagPresetNameValid(request.name)) {
return { resultCode: -1 };
} else {
return db
@ -1765,7 +1765,7 @@ exports.saveConfig = functions.https.onCall((request, response) => {
if (request.uid === undefined || request.obj === undefined) {
console.error(`error saving config for ${request.uid} - missing input`);
return {
returnCode: -1,
resultCode: -1,
message: "Missing input",
};
}
@ -1807,7 +1807,7 @@ exports.saveConfig = functions.https.onCall((request, response) => {
)}`
);
return {
returnCode: -1,
resultCode: -1,
message: "Bad input. " + errorMessage,
};
}
@ -1823,7 +1823,7 @@ exports.saveConfig = functions.https.onCall((request, response) => {
)
.then((e) => {
return {
returnCode: 1,
resultCode: 1,
message: "Saved",
};
})
@ -1832,7 +1832,7 @@ exports.saveConfig = functions.https.onCall((request, response) => {
`error saving config to DB for ${request.uid} - ${e.message}`
);
return {
returnCode: -1,
resultCode: -1,
message: e.message,
};
});
@ -1845,6 +1845,151 @@ exports.saveConfig = functions.https.onCall((request, response) => {
}
});
exports.addPreset = functions.https.onCall(async (request, response) => {
try {
if (!isTagPresetNameValid(request.obj.name)) {
return { resultCode: -1 };
} else if (request.uid === undefined || request.obj === undefined) {
console.error(`error saving config for ${request.uid} - missing input`);
return {
resultCode: -1,
message: "Missing input",
};
} else {
let config = request.obj.config;
let errorMessage = "";
let err = false;
Object.keys(config).forEach((key) => {
if (err) return;
if (!isConfigKeyValid(key)) {
err = true;
console.error(`${key} failed regex check`);
errorMessage = `${key} failed regex check`;
}
if (err) return;
if (key === "resultFilters") return;
if (key === "customBackground") return;
let val = config[key];
if (Array.isArray(val)) {
val.forEach((valarr) => {
if (!isConfigKeyValid(valarr)) {
err = true;
console.error(`${key}: ${valarr} failed regex check`);
errorMessage = `${key}: ${valarr} failed regex check`;
}
});
} else {
if (!isConfigKeyValid(val)) {
err = true;
console.error(`${key}: ${val} failed regex check`);
errorMessage = `${key}: ${val} failed regex check`;
}
}
});
if (err) {
console.error(
`error adding preset for ${
request.uid
} - bad input - ${JSON.stringify(request.obj)}`
);
return {
resultCode: -1,
message: "Bad input. " + errorMessage,
};
}
let presets = await db.collection(`users/${request.uid}/presets`).get();
if (presets.docs.length >= 10) {
return {
resultCode: -2,
message: "Preset limit",
};
}
return db
.collection(`users/${request.uid}/presets`)
.add(request.obj)
.then((e) => {
return {
resultCode: 1,
message: "Saved",
id: e.id,
};
})
.catch((e) => {
console.error(
`error adding preset to DB for ${request.uid} - ${e.message}`
);
return {
resultCode: -1,
message: e.message,
};
});
}
} catch (e) {
console.error(`error adding preset for ${request.uid} - ${e}`);
return {
resultCode: -999,
message: e,
};
}
});
exports.editPreset = functions.https.onCall((request, response) => {
try {
if (!isTagPresetNameValid(request.name)) {
return { resultCode: -1 };
} else {
return db
.collection(`users/${request.uid}/presets`)
.doc(request.presetid)
.set({
config: request.config,
name: request.name,
})
.then((e) => {
console.log(`user ${request.uid} updated a preset: ${request.name}`);
return {
resultCode: 1,
};
})
.catch((e) => {
console.error(
`error while updating preset for user ${request.uid}: ${e.message}`
);
return { resultCode: -999, message: e.message };
});
}
} catch (e) {
console.error(`error updating preset for ${request.uid} - ${e}`);
return { resultCode: -999, message: e.message };
}
});
exports.removePreset = functions.https.onCall((request, response) => {
try {
return db
.collection(`users/${request.uid}/presets`)
.doc(request.presetid)
.delete()
.then((e) => {
console.log(`user ${request.uid} deleted a tag`);
return {
resultCode: 1,
};
})
.catch((e) => {
console.error(
`error deleting tag for user ${request.uid}: ${e.message}`
);
return { resultCode: -999 };
});
} catch (e) {
console.error(`error deleting tag for ${request.uid} - ${e}`);
return { resultCode: -999 };
}
});
// exports.saveLbMemory = functions.https.onCall((request, response) => {
// try {
// if (request.uid === undefined || request.obj === undefined) {

File diff suppressed because it is too large Load diff

View file

@ -96,6 +96,7 @@ const refactoredSrc = [
"./src/js/theme-controller.js",
"./src/js/config.js",
"./src/js/tag-controller.js",
"./src/js/preset-controller.js",
"./src/js/ui.js",
"./src/js/commandline.js",
"./src/js/commandline-lists.js",
@ -131,6 +132,7 @@ const refactoredSrc = [
"./src/js/popups/word-filter-popup.js",
"./src/js/popups/result-tags-popup.js",
"./src/js/popups/edit-tags-popup.js",
"./src/js/popups/edit-preset-popup.js",
"./src/js/popups/custom-theme-popup.js",
"./src/js/popups/import-settings-popup.js",
"./src/js/popups/custom-background-filter.js",
@ -161,6 +163,7 @@ const refactoredSrc = [
"./src/js/test/test-timer.js",
"./src/js/test/test-config.js",
"./src/js/test/layout-emulator.js",
"./src/js/replay.js",
];
//legacy files

7506
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -45,6 +45,7 @@
"@babel/runtime": "^7.12.5",
"chart.js": "^2.9.4",
"chartjs-plugin-annotation": "^0.5.7",
"chartjs-plugin-trendline": "^0.2.2"
"chartjs-plugin-trendline": "^0.2.2",
"tinycolor2": "^1.4.2"
}
}

View file

@ -117,6 +117,7 @@ export function getDataAndInit() {
if ($(".page.pageTest").hasClass("active")) {
TestLogic.restart(false, true);
}
DB.saveConfig(Config);
}
}
UpdateConfig.setDbConfigLoaded(true);

View file

@ -277,7 +277,7 @@ export function toggle(group, filter) {
save();
} catch (e) {
Notifications.add(
"Something went wrong toggling filter. Reverting to defaults",
"Something went wrong toggling filter. Reverting to defaults.",
0
);
console.log("toggling filter error");

View file

@ -67,7 +67,13 @@ export let result = new Chart($("#wpmChart"), {
let wordEl = $($("#resultWordsHistory .words .word")[wordIndex]);
let input = wordEl.attr("input");
if (input != undefined)
wordEl.append(`<div class="wordInputAfter">${input}</div>`);
wordEl.append(
`<div class="wordInputAfter">${input
.replace(/\t/g, "_")
.replace(/\n/g, "_")
.replace(/</g, "&lt")
.replace(/>/g, "&gt")}</div>`
);
});
} catch {}
},

View file

@ -8,6 +8,9 @@ export const updateResultTags = firebase
.functions()
.httpsCallable("updateResultTags");
export const saveConfig = firebase.functions().httpsCallable("saveConfig");
export const addPreset = firebase.functions().httpsCallable("addPreset");
export const editPreset = firebase.functions().httpsCallable("editPreset");
export const removePreset = firebase.functions().httpsCallable("removePreset");
export const generatePairingCode = firebase
.functions()
.httpsCallable("generatePairingCode");

View file

@ -13,6 +13,7 @@ import * as TestUI from "./test-ui";
import * as TestLogic from "./test-logic";
import * as Funbox from "./funbox";
import * as TagController from "./tag-controller";
import * as PresetController from "./preset-controller";
import * as Commandline from "./commandline";
import * as CustomText from "./custom-text";
@ -241,6 +242,30 @@ export function updateTagCommands() {
}
}
let commandsPresets = {
title: "Apply preset...",
list: [],
};
export function updatePresetCommands() {
if (DB.getSnapshot().presets.length > 0) {
commandsPresets.list = [];
DB.getSnapshot().presets.forEach((preset) => {
let dis = preset.name;
commandsPresets.list.push({
id: "applyPreset" + preset.id,
display: dis,
exec: () => {
PresetController.apply(preset.id);
TestUI.updateModesNotice();
},
});
});
}
}
let commandsRepeatQuotes = {
title: "Change repeat quotes...",
list: [
@ -384,6 +409,20 @@ let commandsRandomTheme = {
UpdateConfig.setRandomTheme("fav");
},
},
{
id: "setRandomLight",
display: "light",
exec: () => {
UpdateConfig.setRandomTheme("light");
},
},
{
id: "setRandomDark",
display: "dark",
exec: () => {
UpdateConfig.setRandomTheme("dark");
},
},
],
};
@ -702,6 +741,13 @@ let commandsKeymapLegendStyle = {
let commandsHighlightMode = {
title: "Change highlight mode...",
list: [
{
id: "setHighlightModeOff",
display: "off",
exec: () => {
UpdateConfig.setHighlightMode("off");
},
},
{
id: "setHighlightModeLetter",
display: "letter",
@ -1313,6 +1359,17 @@ export let defaultCommands = {
Commandline.show();
},
},
{
visible: false,
id: "applyPreset",
display: "Apply preset...",
subgroup: true,
exec: () => {
updatePresetCommands();
current.push(commandsPresets);
Commandline.show();
},
},
{
id: "changeConfidenceMode",
display: "Change confidence mode...",
@ -1750,6 +1807,26 @@ export let defaultCommands = {
Commandline.show();
},
},
{
id: "changeCustomLayoutfluid",
display: "Change custom layoutfluid...",
defaultValue: "qwerty dvorak colemak",
input: true,
exec: (input) => {
UpdateConfig.setCustomLayoutfluid(input);
if (Funbox.active === "layoutfluid") TestLogic.restart();
// UpdateConfig.setLayout(
// Config.customLayoutfluid
// ? Config.customLayoutfluid.split("_")[0]
// : "qwerty"
// );
// UpdateConfig.setKeymapLayout(
// Config.customLayoutfluid
// ? Config.customLayoutfluid.split("_")[0]
// : "qwerty"
// );
},
},
{
id: "changeFontSize",
display: "Change font size...",
@ -1778,9 +1855,9 @@ export let defaultCommands = {
},
},
{
id: "randomiseTheme",
id: "randomizeTheme",
display: "Next random theme",
exec: () => ThemeController.randomiseTheme(),
exec: () => ThemeController.randomizeTheme(),
},
{
id: "viewTypingPage",

View file

@ -16,6 +16,7 @@ import * as PaceCaret from "./pace-caret";
import * as UI from "./ui";
import * as CommandlineLists from "./commandline-lists";
import * as BackgroundFilter from "./custom-background-filter";
import LayoutList from "./layouts";
export let localStorageConfig = null;
export let dbConfigLoaded = false;
@ -117,6 +118,7 @@ let defaultConfig = {
customBackground: "",
customBackgroundSize: "cover",
customBackgroundFilter: [0, 1, 1, 1, 1],
customLayoutfluid: "qwerty#dvorak#colemak",
};
function isConfigKeyValid(name) {
@ -1400,6 +1402,37 @@ export function setCustomBackground(value, nosave) {
}
}
export function setCustomLayoutfluid(value, nosave) {
if (value == null || value == undefined) {
value = "qwerty#dvorak#colemak";
}
value = value.replace(/ /g, "#");
//validate the layouts
let allGood = true;
let list = Object.keys(LayoutList);
value.split("#").forEach((customLayout) => {
if (!list.includes(customLayout)) allGood = false;
});
if (!allGood) {
Notifications.add(
"One of the layouts was not found. Make sure the name matches exactly. Reverting to default",
0,
4
);
value = "qwerty#dvorak#colemak";
nosave = false;
}
config.customLayoutfluid = value;
CommandlineLists.defaultCommands.list.filter(
(command) => command.id == "changeCustomLayoutfluid"
)[0].defaultValue = value.replace(/#/g, " ");
$(".pageSettings .section.customLayoutfluid input").val(
value.replace(/#/g, " ")
);
if (!nosave) saveToLocalStorage();
}
export function setCustomBackgroundSize(value, nosave) {
if (value != "cover" && value != "contain" && value != "max") {
value = "cover";
@ -1430,6 +1463,7 @@ export function apply(configObj) {
setTheme(configObj.theme, true);
setCustomThemeColors(configObj.customThemeColors, true);
setCustomTheme(configObj.customTheme, true, true);
setCustomLayoutfluid(configObj.customLayoutfluid, true);
setCustomBackground(configObj.customBackground, true);
setCustomBackgroundSize(configObj.customBackgroundSize, true);
setCustomBackgroundFilter(configObj.customBackgroundFilter, true);
@ -1709,6 +1743,18 @@ export function loadFromLocalStorage() {
loadDone();
}
export function getConfigChanges() {
let configChanges = {};
Object.keys(config)
.filter((key) => {
return config[key] != defaultConfig[key];
})
.forEach((key) => {
configChanges[key] = config[key];
});
return configChanges;
}
export function setConfig(newConfig) {
config = newConfig;
}

View file

@ -33,6 +33,7 @@ export async function initSnapshot() {
results: undefined,
personalBests: {},
name: undefined,
presets: [],
tags: [],
favouriteThemes: [],
refactored: false,
@ -81,6 +82,29 @@ export async function initSnapshot() {
.catch((e) => {
throw e;
});
await db
.collection(`users/${user.uid}/presets/`)
.get()
.then((data) => {
data.docs.forEach((doc) => {
// console.log(doc);
let preset = doc.data();
preset.id = doc.id;
snap.presets.push(preset);
});
snap.presets = snap.presets.sort((a, b) => {
if (a.name > b.name) {
return 1;
} else if (a.name < b.name) {
return -1;
} else {
return 0;
}
});
})
.catch((e) => {
throw e;
});
await db
.collection("users")
.doc(user.uid)
@ -119,6 +143,7 @@ export async function initSnapshot() {
.catch((e) => {
throw e;
});
// console.log(snap.presets);
dbSnapshot = snap;
} catch (e) {
console.error(e);
@ -486,7 +511,7 @@ export async function saveConfig(config) {
obj: config,
}).then((d) => {
AccountButton.loading(false);
if (d.data.returnCode !== 1) {
if (d.data.resultCode !== 1) {
Notifications.add(`Error saving config to DB! ${d.data.message}`, 4000);
}
return;

View file

@ -20,6 +20,7 @@ import * as TimerProgress from "./timer-progress";
import * as TestTimer from "./test-timer";
import * as Focus from "./focus";
import * as ShiftTracker from "./shift-tracker";
import * as Replay from "./replay.js";
$("#wordsInput").keypress((event) => {
event.preventDefault();
@ -130,6 +131,7 @@ function handleBackspace(event) {
}
}
TestLogic.words.decreaseCurrentIndex();
Replay.addReplayEvent("backWord");
TestUI.setCurrentWordElementIndex(TestUI.currentWordElementIndex - 1);
TestUI.updateActiveElement(true);
Funbox.toggleScript(TestLogic.words.getCurrent());
@ -138,6 +140,7 @@ function handleBackspace(event) {
} else {
if (Config.confidenceMode === "max") return;
if (event["ctrlKey"] || event["altKey"]) {
Replay.addReplayEvent("clearWord");
let limiter = " ";
if (
TestLogic.input.current.lastIndexOf("-") >
@ -165,6 +168,7 @@ function handleBackspace(event) {
TestLogic.input.setCurrent(
TestLogic.input.current.substring(0, TestLogic.input.current.length - 1)
);
Replay.addReplayEvent("deleteLetter");
}
TestUI.updateWordElement(!Config.blindMode);
}
@ -201,10 +205,15 @@ function handleSpace(event, isEnter) {
let currentWord = TestLogic.words.getCurrent();
if (Funbox.active === "layoutfluid" && Config.mode !== "time") {
const layouts = ["qwerty", "dvorak", "colemak"];
// here I need to check if Config.customLayoutFluid exists because of my scuffed solution of returning whenever value is undefined in the setCustomLayoutfluid function
const layouts = Config.customLayoutfluid
? Config.customLayoutfluid.split("#")
: ["qwerty", "dvorak", "colemak"];
let index = 0;
let outof = TestLogic.words.length;
index = Math.floor((TestLogic.input.history.length + 1) / (outof / 3));
index = Math.floor(
(TestLogic.input.history.length + 1) / (outof / layouts.length)
);
if (Config.layout !== layouts[index] && layouts[index] !== undefined) {
Notifications.add(`--- !!! ${layouts[index]} !!! ---`, 0);
}
@ -225,6 +234,7 @@ function handleSpace(event, isEnter) {
dontInsertSpace = true;
if (currentWord == TestLogic.input.current || Config.mode == "zen") {
//correct word or in zen mode
Replay.addReplayEvent("submitCorrectWord");
PaceCaret.handleSpace(true, currentWord);
TestStats.incrementAccuracy(true);
TestLogic.input.pushHistory();
@ -242,6 +252,7 @@ function handleSpace(event, isEnter) {
}
} else {
//incorrect word
Replay.addReplayEvent("submitErrorWord");
PaceCaret.handleSpace(false, currentWord);
if (Funbox.active !== "nospace") {
if (!Config.playSoundOnError || Config.blindMode) {
@ -561,6 +572,7 @@ function handleAlpha(event) {
}
if (!thisCharCorrect) {
Replay.addReplayEvent("incorrectLetter", event.key);
TestStats.incrementAccuracy(false);
TestStats.incrementKeypressErrors();
// currentError.count++;
@ -568,6 +580,7 @@ function handleAlpha(event) {
thisCharCorrect = false;
TestStats.pushMissedWord(TestLogic.words.getCurrent());
} else {
Replay.addReplayEvent("correctLetter", event.key);
TestStats.incrementAccuracy(true);
thisCharCorrect = true;
if (Config.mode == "zen") {

View file

@ -10,7 +10,7 @@ let popup = "#customTextPopup";
export function show() {
if ($(wrapper).hasClass("hidden")) {
if ($(`${popup} .check input`).prop("checked")) {
if ($(`${popup} .checkbox input`).prop("checked")) {
$(`${popup} .inputs .randomInputFields`).removeClass("hidden");
} else {
$(`${popup} .inputs .randomInputFields`).addClass("hidden");
@ -60,8 +60,8 @@ $(wrapper).mousedown((e) => {
}
});
$(`${popup} .inputs .check input`).change(() => {
if ($(`${popup} .check input`).prop("checked")) {
$(`${popup} .inputs .checkbox input`).change(() => {
if ($(`${popup} .checkbox input`).prop("checked")) {
$(`${popup} .inputs .randomInputFields`).removeClass("hidden");
} else {
$(`${popup} .inputs .randomInputFields`).addClass("hidden");
@ -106,11 +106,11 @@ $("#customTextPopup .apply").click(() => {
CustomText.setTime(parseInt($("#customTextPopup .time input").val()));
CustomText.setIsWordRandom(
$("#customTextPopup .check input").prop("checked") &&
$("#customTextPopup .checkbox input").prop("checked") &&
!isNaN(CustomText.word)
);
CustomText.setIsTimeRandom(
$("#customTextPopup .check input").prop("checked") &&
$("#customTextPopup .checkbox input").prop("checked") &&
!isNaN(CustomText.time)
);

View file

@ -0,0 +1,160 @@
import * as Loader from "./loader";
import * as DB from "./db";
import * as CloudFunctions from "./cloud-functions";
import * as Notifications from "./notifications";
import * as Settings from "./settings";
import * as Config from "./config";
export function show(action, id, name) {
if (action === "add") {
$("#presetWrapper #presetEdit").attr("action", "add");
$("#presetWrapper #presetEdit .title").html("Create new preset");
$("#presetWrapper #presetEdit .button").html(`<i class="fas fa-plus"></i>`);
$("#presetWrapper #presetEdit input.text").val("");
$("#presetWrapper #presetEdit input.text").removeClass("hidden");
$("#presetWrapper #presetEdit label").addClass("hidden");
} else if (action === "edit") {
$("#presetWrapper #presetEdit").attr("action", "edit");
$("#presetWrapper #presetEdit").attr("presetid", id);
$("#presetWrapper #presetEdit .title").html("Edit preset");
$("#presetWrapper #presetEdit .button").html(`<i class="fas fa-pen"></i>`);
$("#presetWrapper #presetEdit input.text").val(name);
$("#presetWrapper #presetEdit input.text").removeClass("hidden");
$("#presetWrapper #presetEdit label input").prop("checked", false);
$("#presetWrapper #presetEdit label").removeClass("hidden");
} else if (action === "remove") {
$("#presetWrapper #presetEdit").attr("action", "remove");
$("#presetWrapper #presetEdit").attr("presetid", id);
$("#presetWrapper #presetEdit .title").html("Remove preset " + name);
$("#presetWrapper #presetEdit .button").html(
`<i class="fas fa-check"></i>`
);
$("#presetWrapper #presetEdit input.text").addClass("hidden");
$("#presetWrapper #presetEdit label").addClass("hidden");
}
if ($("#presetWrapper").hasClass("hidden")) {
$("#presetWrapper")
.stop(true, true)
.css("opacity", 0)
.removeClass("hidden")
.animate({ opacity: 1 }, 100, () => {
$("#presetWrapper #presetEdit input").focus();
});
}
}
function hide() {
if (!$("#presetWrapper").hasClass("hidden")) {
$("#presetWrapper #presetEdit").attr("action", "");
$("#presetWrapper #presetEdit").attr("tagid", "");
$("#presetWrapper")
.stop(true, true)
.css("opacity", 1)
.animate(
{
opacity: 0,
},
100,
() => {
$("#presetWrapper").addClass("hidden");
}
);
}
}
function apply() {
let action = $("#presetWrapper #presetEdit").attr("action");
let inputVal = $("#presetWrapper #presetEdit input").val();
let presetid = $("#presetWrapper #presetEdit").attr("presetid");
let configChanges = Config.getConfigChanges();
hide();
if (action === "add") {
Loader.show();
CloudFunctions.addPreset({
uid: firebase.auth().currentUser.uid,
obj: {
name: inputVal,
config: configChanges,
},
}).then((e) => {
Loader.hide();
let status = e.data.resultCode;
if (status === 1) {
Notifications.add("Preset added", 1, 2);
DB.getSnapshot().presets.push({
name: inputVal,
config: configChanges,
id: e.data.id,
});
Settings.update();
} else if (status === -1) {
Notifications.add("Invalid preset name", 0);
} else if (status === -2) {
Notifications.add("You can't add any more presets", 0);
} else if (status < -1) {
Notifications.add("Unknown error: " + e.data.message, -1);
}
});
} else if (action === "edit") {
Loader.show();
CloudFunctions.editPreset({
uid: firebase.auth().currentUser.uid,
name: inputVal,
presetid,
config: configChanges,
}).then((e) => {
Loader.hide();
let status = e.data.resultCode;
if (status === 1) {
Notifications.add("Preset updated", 1);
let preset = DB.getSnapshot().presets.filter(
(preset) => preset.id == presetid
)[0];
preset.name = inputVal;
preset.config = configChanges;
Settings.update();
} else if (status === -1) {
Notifications.add("Invalid preset name", 0);
} else if (status < -1) {
Notifications.add("Unknown error: " + e.data.message, -1);
}
});
} else if (action === "remove") {
Loader.show();
CloudFunctions.removePreset({
uid: firebase.auth().currentUser.uid,
presetid,
}).then((e) => {
Loader.hide();
let status = e.data.resultCode;
if (status === 1) {
Notifications.add("Preset removed", 1);
DB.getSnapshot().presets.forEach((preset, index) => {
if (preset.id === presetid) {
DB.getSnapshot().presets.splice(index, 1);
}
});
Settings.update();
} else if (status < -1) {
Notifications.add("Unknown error: " + e.data.message, -1);
}
});
}
}
$("#presetWrapper").click((e) => {
if ($(e.target).attr("id") === "presetWrapper") {
hide();
}
});
$("#presetWrapper #presetEdit .button").click(() => {
apply();
});
$("#presetWrapper #presetEdit input").keypress((e) => {
if (e.keyCode == 13) {
apply();
}
});

View file

@ -59,6 +59,7 @@ function hide() {
}
function apply() {
// console.log(DB.getSnapshot());
let action = $("#tagsWrapper #tagsEdit").attr("action");
let inputVal = $("#tagsWrapper #tagsEdit input").val();
let tagid = $("#tagsWrapper #tagsEdit").attr("tagid");

View file

@ -0,0 +1,16 @@
import * as Config from "./config";
import * as DB from "./db";
import * as Notifications from "./notifications";
import * as Settings from "./settings";
export function apply(id) {
// console.log(DB.getSnapshot().presets);
DB.getSnapshot().presets.forEach((preset) => {
if (preset.id == id) {
Config.apply(JSON.parse(JSON.stringify(preset.config)));
Notifications.add("Preset applied", 1, 2);
Config.saveToLocalStorage();
Settings.update();
}
});
}

267
src/js/replay.js Normal file
View file

@ -0,0 +1,267 @@
/*
TODO:
Export replay as video
Export replay as typing test file?
.ttr file extension (stands for typing test record)
Should just be json, but fields should be specified by some format
metadata field with rules, website source, mode, name of typist
data field should be a list of objects, like monkeytype replay uses
signature or verfication field should be able to check file validity with server
And add ability to upload file to watch replay
*/
let wordsList = [];
let replayData = [];
let replayStartTime = 0;
let replayRecording = true;
let wordPos = 0;
let curPos = 0;
let targetWordPos = 0;
let targetCurPos = 0;
let timeoutList = [];
const toggleButton = document.getElementById("playpauseReplayButton")
.children[0];
function replayGetWordsList(wordsListFromScript) {
wordsList = wordsListFromScript;
}
function initializeReplayPrompt() {
const replayWordsElement = document.getElementById("replayWords");
replayWordsElement.innerHTML = "";
let wordCount = 0;
replayData.forEach((item, i) => {
//trim wordsList for timed tests
if (item.action === "backWord") {
wordCount--;
} else if (
item.action === "submitCorrectWord" ||
item.action === "submitErrorWord"
) {
wordCount++;
}
});
wordsList.forEach((item, i) => {
if (i > wordCount) return;
let x = document.createElement("div");
x.className = "word";
for (i = 0; i < item.length; i++) {
let letter = document.createElement("LETTER");
letter.innerHTML = item[i];
x.appendChild(letter);
}
replayWordsElement.appendChild(x);
});
}
function startReplayRecording() {
if (!$("#resultReplay").stop(true, true).hasClass("hidden")) {
//hide replay display if user left it open
toggleReplayDisplay();
}
replayData = [];
replayStartTime = performance.now();
replayRecording = true;
targetCurPos = 0;
targetWordPos = 0;
}
function stopReplayRecording() {
replayRecording = false;
}
function addReplayEvent(action, letter = undefined) {
if (replayRecording === false) {
return;
}
let timeDelta = performance.now() - replayStartTime;
if (action === "incorrectLetter" || action === "correctLetter") {
replayData.push({ action: action, letter: letter, time: timeDelta });
} else {
replayData.push({ action: action, time: timeDelta });
}
}
function pauseReplay() {
timeoutList.forEach((item, i) => {
clearTimeout(item);
});
timeoutList = [];
targetCurPos = curPos;
targetWordPos = wordPos;
toggleButton.className = "fas fa-play";
toggleButton.parentNode.setAttribute("aria-label", "Resume replay");
}
function loadOldReplay() {
let startingIndex = 0;
curPos = 0;
wordPos = 0;
replayData.forEach((item, i) => {
if (
wordPos < targetWordPos ||
(wordPos === targetWordPos && curPos < targetCurPos)
) {
//quickly display everything up to the target
handleDisplayLogic(item);
startingIndex = i + 1;
}
});
return startingIndex;
}
function playReplay() {
curPos = 0;
wordPos = 0;
toggleButton.className = "fas fa-pause";
toggleButton.parentNode.setAttribute("aria-label", "Pause replay");
initializeReplayPrompt();
let startingIndex = loadOldReplay();
let lastTime = replayData[startingIndex].time;
replayData.forEach((item, i) => {
if (i < startingIndex) return;
timeoutList.push(
setTimeout(() => {
handleDisplayLogic(item);
}, item.time - lastTime)
);
});
timeoutList.push(
setTimeout(() => {
//after the replay has finished, this will run
targetCurPos = 0;
targetWordPos = 0;
toggleButton.className = "fas fa-play";
toggleButton.parentNode.setAttribute("aria-label", "Start replay");
}, replayData[replayData.length - 1].time - lastTime)
);
}
function handleDisplayLogic(item) {
let activeWord = document.getElementById("replayWords").children[wordPos];
if (item.action === "correctLetter") {
activeWord.children[curPos].classList.add("correct");
curPos++;
} else if (item.action === "incorrectLetter") {
let myElement;
if (curPos >= activeWord.children.length) {
//if letter is an extra
myElement = document.createElement("letter");
myElement.classList.add("extra");
myElement.innerHTML = item.letter;
activeWord.appendChild(myElement);
}
myElement = activeWord.children[curPos];
myElement.classList.add("incorrect");
curPos++;
} else if (item.action === "deleteLetter") {
let myElement = activeWord.children[curPos - 1];
if (myElement.classList.contains("extra")) {
myElement.remove();
} else {
myElement.className = "";
}
curPos--;
} else if (item.action === "submitCorrectWord") {
wordPos++;
curPos = 0;
} else if (item.action === "submitErrorWord") {
activeWord.classList.add("error");
wordPos++;
curPos = 0;
} else if (item.action === "clearWord") {
let promptWord = document.createElement("div");
let wordArr = wordsList[wordPos].split("");
wordArr.forEach((letter, i) => {
promptWord.innerHTML += `<letter>${letter}</letter>`;
});
activeWord.innerHTML = promptWord.innerHTML;
curPos = 0;
} else if (item.action === "backWord") {
wordPos--;
activeWord = document.getElementById("replayWords").children[wordPos];
curPos = activeWord.children.length;
while (activeWord.children[curPos - 1].className === "") curPos--;
activeWord.classList.remove("error");
}
}
function toggleReplayDisplay() {
if ($("#resultReplay").stop(true, true).hasClass("hidden")) {
initializeReplayPrompt();
loadOldReplay();
//show
if (!$("#watchReplayButton").hasClass("loaded")) {
$("#words").html(
`<div class="preloader"><i class="fas fa-fw fa-spin fa-circle-notch"></i></div>`
);
$("#resultReplay")
.removeClass("hidden")
.css("display", "none")
.slideDown(250);
} else {
$("#resultReplay")
.removeClass("hidden")
.css("display", "none")
.slideDown(250);
}
} else {
//hide
if (toggleButton.parentNode.getAttribute("aria-label") != "Start replay") {
pauseReplay();
}
$("#resultReplay").slideUp(250, () => {
$("#resultReplay").addClass("hidden");
});
}
}
$(".pageTest #playpauseReplayButton").click(async (event) => {
if (toggleButton.className === "fas fa-play") {
playReplay();
} else if (toggleButton.className === "fas fa-pause") {
pauseReplay();
}
});
$("#replayWords").click((event) => {
//allows user to click on the place they want to start their replay at
pauseReplay();
const replayWords = document.querySelector("#replayWords");
let range;
let textNode;
if (document.caretPositionFromPoint) {
// standard
range = document.caretPositionFromPoint(event.pageX, event.pageY);
textNode = range.offsetNode;
} else if (document.caretRangeFromPoint) {
// WebKit
range = document.caretRangeFromPoint(event.pageX, event.pageY);
textNode = range.startContainer;
}
const words = [...replayWords.children];
targetWordPos = words.indexOf(textNode.parentNode.parentNode);
const letters = [...words[targetWordPos].children];
targetCurPos = letters.indexOf(textNode.parentNode);
initializeReplayPrompt();
loadOldReplay();
});
$(document).on("keypress", "#watchReplayButton", (event) => {
if (event.keyCode == 13) {
toggleReplayDisplay();
}
});
$(document.body).on("click", "#watchReplayButton", () => {
toggleReplayDisplay();
});
export {
startReplayRecording,
stopReplayRecording,
addReplayEvent,
replayGetWordsList,
};

View file

@ -10,8 +10,10 @@ import * as Loader from "./loader";
import * as CloudFunctions from "./cloud-functions";
import * as Funbox from "./funbox";
import * as TagController from "./tag-controller";
import * as PresetController from "./preset-controller";
import * as SimplePopups from "./simple-popups";
import * as EditTagsPopup from "./edit-tags-popup";
import * as EditPresetPopup from "./edit-preset-popup";
import * as ThemePicker from "./theme-picker";
export let groups = {};
@ -277,6 +279,10 @@ async function initGroups() {
"customBackgroundSize",
UpdateConfig.setCustomBackgroundSize
);
// groups.customLayoutfluid = new SettingsGroup(
// "customLayoutfluid",
// UpdateConfig.setCustomLayoutfluid
// );
}
async function fillSettingsPage() {
@ -383,6 +389,10 @@ async function fillSettingsPage() {
$(".pageSettings .section.customBackgroundSize input").val(
Config.customBackground
);
$(".pageSettings .section.customLayoutfluid input").val(
Config.customLayoutfluid.replace(/#/g, " ")
);
}
export let settingsFillPromise = fillSettingsPage();
@ -446,35 +456,18 @@ function refreshTagsSettingsSection() {
if (tag.pb != undefined && tag.pb > 0) {
tagPbString = `PB: ${tag.pb}`;
}
if (tag.active === true) {
tagsEl.append(`
tagsEl.append(`
<div class="tag" id="${tag.id}">
<div class="active" active="${tag.active}">
<i class="fas fa-check-square"></i>
</div>
<div class="title">${tag.name}</div>
<div class="editButton"><i class="fas fa-pen"></i></div>
<div class="clearPbButton hidden" aria-label="${tagPbString}" data-balloon-pos="up"><i class="fas fa-crown"></i></div>
<div class="removeButton"><i class="fas fa-trash"></i></div>
</div>
<div class="tag" id="${tag.id}">
<div class="active" active="true">
<i class="fas fa-check-square"></i>
</div>
<div class="title">${tag.name}</div>
<div class="editButton"><i class="fas fa-pen"></i></div>
<div class="clearPbButton hidden" aria-label="${tagPbString}" data-balloon-pos="up"><i class="fas fa-crown"></i></div>
<div class="removeButton"><i class="fas fa-trash"></i></div>
</div>
`);
} else {
tagsEl.append(`
<div class="tag" id="${tag.id}">
<div class="active" active="false">
<i class="fas fa-square"></i>
</div>
<div class="title">${tag.name}</div>
<div class="editButton"><i class="fas fa-pen"></i></div>
<div class="clearPbButton hidden" aria-label="${tagPbString}" data-balloon-pos="up"><i class="fas fa-crown"></i></div>
<div class="removeButton"><i class="fas fa-trash"></i></div>
</div>
`);
}
`);
});
$(".pageSettings .section.tags").removeClass("hidden");
} else {
@ -482,10 +475,36 @@ function refreshTagsSettingsSection() {
}
}
function refreshPresetsSettingsSection() {
if (firebase.auth().currentUser !== null && DB.getSnapshot() !== null) {
let presetsEl = $(".pageSettings .section.presets .presetsList").empty();
DB.getSnapshot().presets.forEach((preset) => {
presetsEl.append(`
<div class="buttons preset" id="${preset.id}">
<div class="button presetButton">
<div class="title">${preset.name}</div>
</div>
<div class="editButton button">
<i class="fas fa-pen"></i>
</div>
<div class="removeButton button">
<i class="fas fa-trash"></i>
</div>
</div>
`);
});
$(".pageSettings .section.presets").removeClass("hidden");
} else {
$(".pageSettings .section.presets").addClass("hidden");
}
}
export function showAccountSection() {
$(`.sectionGroupTitle[group='account']`).removeClass("hidden");
$(`.settingsGroup.account`).removeClass("hidden");
refreshTagsSettingsSection();
refreshPresetsSettingsSection();
updateDiscordSection();
}
@ -495,6 +514,7 @@ export function update() {
});
refreshTagsSettingsSection();
refreshPresetsSettingsSection();
LanguagePicker.setActiveGroup();
setActiveFunboxButton();
ThemePicker.updateActiveTab();
@ -652,6 +672,37 @@ $(document).on("click", ".pageSettings .section.tags .addTagButton", (e) => {
EditTagsPopup.show("add");
});
$(document).on(
"click",
".pageSettings .section.presets .addPresetButton",
(e) => {
EditPresetPopup.show("add");
}
);
$(document).on("click", ".pageSettings .section.presets .editButton", (e) => {
let presetid = $(e.currentTarget).parent(".preset").attr("id");
let name = $(e.currentTarget).siblings(".button").children(".title").text();
EditPresetPopup.show("edit", presetid, name);
});
$(document).on("click", ".pageSettings .section.presets .removeButton", (e) => {
let presetid = $(e.currentTarget).parent(".preset").attr("id");
let name = $(e.currentTarget).siblings(".button").children(".title").text();
EditPresetPopup.show("remove", presetid, name);
});
$(document).on(
"click",
".pageSettings .section.presets .presetsList .preset .presetButton",
(e) => {
let target = e.currentTarget;
let presetid = $(target).parent(".preset").attr("id");
console.log("Applying Preset");
PresetController.apply(presetid);
}
);
$(document).on(
"click",
".pageSettings .section.tags .tagsList .tag .clearPbButton",
@ -778,3 +829,24 @@ $(".pageSettings .section.customBackgroundSize .inputAndButton input").keypress(
}
}
);
$(".pageSettings .section.customLayoutfluid .inputAndSave .save").on(
"click",
(e) => {
UpdateConfig.setCustomLayoutfluid(
$(".pageSettings .section.customLayoutfluid .inputAndSave input").val()
);
Notifications.add("Custom layoutfluid saved", 1);
}
);
$(".pageSettings .section.customLayoutfluid .inputAndSave .input").keypress(
(e) => {
if (e.keyCode == 13) {
UpdateConfig.setCustomLayoutfluid(
$(".pageSettings .section.customLayoutfluid .inputAndSave input").val()
);
Notifications.add("Custom layoutfluid saved", 1);
}
}
);

View file

@ -174,14 +174,22 @@ export async function activate(funbox, mode) {
Settings.groups.keymapMode.updateButton();
// UpdateConfig.setSavedLayout(Config.layout);
rememberSetting("layout", Config.layout, UpdateConfig.setLayout);
UpdateConfig.setLayout("qwerty");
UpdateConfig.setLayout(
Config.customLayoutfluid
? Config.customLayoutfluid.split("#")[0]
: "qwerty"
);
Settings.groups.layout.updateButton();
rememberSetting(
"keymapLayout",
Config.keymapLayout,
UpdateConfig.setKeymapLayout
);
UpdateConfig.setKeymapLayout("qwerty");
UpdateConfig.setKeymapLayout(
Config.customLayoutfluid
? Config.customLayoutfluid.split("#")[0]
: "qwerty"
);
Settings.groups.keymapLayout.updateButton();
TestLogic.restart();
} else if (funbox === "memory") {

View file

@ -28,7 +28,7 @@ export function init() {
});
CustomText.setText(newCustomText);
CustomText.setIsWordRandom(true);
CustomText.setWord(50);
CustomText.setWord(Object.keys(TestStats.missedWords).length * 5);
TestLogic.restart();
before.mode = mode;

View file

@ -27,6 +27,7 @@ import * as DB from "./db";
import * as ThemeColors from "./theme-colors";
import * as CloudFunctions from "./cloud-functions";
import * as TestLeaderboards from "./test-leaderboards";
import * as Replay from "./replay.js";
export let notSignedInLastResult = null;
@ -318,6 +319,8 @@ export function startTest() {
console.log("Analytics unavailable");
}
setActive(true);
Replay.startReplayRecording();
Replay.replayGetWordsList(words.list);
TestStats.resetKeypressTimings();
TimerProgress.restart();
TimerProgress.show();
@ -343,6 +346,7 @@ export function startTest() {
export async function init() {
setActive(false);
Replay.stopReplayRecording();
words.reset();
TestUI.setCurrentWordElementIndex(0);
// accuracy = {
@ -584,8 +588,12 @@ export async function init() {
}
if (language.ligatures) {
$("#words").addClass("withLigatures");
$("#resultWordsHistory .words").addClass("withLigatures");
$("#resultReplay .words").addClass("withLigatures");
} else {
$("#words").removeClass("withLigatures");
$("#resultWordsHistory .words").removeClass("withLigatures");
$("#resultReplay .words").removeClass("withLigatures");
}
// if (Config.mode == "zen") {
// // Creating an empty active word element for zen mode
@ -678,6 +686,7 @@ export function restart(withSameWordset = false, nosave = false, event) {
Focus.set(false);
Caret.hide();
setActive(false);
Replay.stopReplayRecording();
LiveWpm.hide();
LiveAcc.hide();
TimerProgress.hide();
@ -704,7 +713,7 @@ export function restart(withSameWordset = false, nosave = false, event) {
!UI.pageTransition &&
!Config.customTheme
) {
ThemeController.randomiseTheme();
ThemeController.randomizeTheme();
}
}
TestUI.setResultVisible(false);
@ -727,6 +736,7 @@ export function restart(withSameWordset = false, nosave = false, event) {
} else {
setRepeated(true);
setActive(false);
Replay.stopReplayRecording();
words.resetCurrentIndex();
input.reset();
PaceCaret.init();
@ -773,8 +783,16 @@ export function restart(withSameWordset = false, nosave = false, event) {
$(".pageTest #premidSecondsLeft").text(Config.time);
if (Funbox.active === "layoutfluid") {
UpdateConfig.setLayout("qwerty");
UpdateConfig.setKeymapLayout("qwerty");
UpdateConfig.setLayout(
Config.customLayoutfluid
? Config.customLayoutfluid.split("#")[0]
: "qwerty"
);
UpdateConfig.setKeymapLayout(
Config.customLayoutfluid
? Config.customLayoutfluid.split("#")[0]
: "qwerty"
);
Keymap.highlightKey(
words
.getCurrent()
@ -944,6 +962,7 @@ export function finish(difficultyFailed = false) {
if (Config.mode == "zen" && input.current.length != 0) {
input.pushHistory();
corrected.pushHistory();
Replay.replayGetWordsList(input.history);
}
TestStats.recordKeypressSpacing();
@ -952,6 +971,7 @@ export function finish(difficultyFailed = false) {
TestUI.setResultVisible(true);
TestStats.setEnd(performance.now());
setActive(false);
Replay.stopReplayRecording();
Focus.set(false);
Caret.hide();
LiveWpm.hide();
@ -961,7 +981,10 @@ export function finish(difficultyFailed = false) {
Keymap.hide();
Funbox.activate("none", null);
if (Misc.roundTo2(TestStats.calculateTestSeconds()) % 1 != 0) {
if (
Misc.roundTo2(TestStats.calculateTestSeconds()) % 1 != 0 &&
Config.mode !== "time"
) {
TestStats.setLastSecondNotRound();
}
@ -1104,8 +1127,7 @@ export function finish(difficultyFailed = false) {
);
}, 125);
$("#testModesNotice").addClass("hidden");
$("#testModesNotice").css("opacity", 0);
$("#result .stats .leaderboards .bottom").text("");
$("#result .stats .leaderboards").addClass("hidden");
@ -1710,9 +1732,9 @@ export function finish(difficultyFailed = false) {
$("#result .stats .testType .bottom").html(testType);
let otherText = "";
if (Config.layout !== "default") {
otherText += "<br>" + Config.layout;
}
// if (Config.layout !== "default") {
// otherText += "<br>" + Config.layout;
// }
if (difficultyFailed) {
otherText += "<br>failed";
}
@ -1754,6 +1776,10 @@ export function finish(difficultyFailed = false) {
}
if (Funbox.funboxSaved !== "none") {
let content = Funbox.funboxSaved;
if (Funbox.funboxSaved === "layoutfluid") {
content += " " + Config.customLayoutfluid.replace(/#/g, " ");
}
ChartController.result.options.annotation.annotations.push({
enabled: false,
type: "line",
@ -1774,7 +1800,7 @@ export function finish(difficultyFailed = false) {
cornerRadius: 3,
position: "left",
enabled: true,
content: `${Funbox.funboxSaved}`,
content: `${content}`,
yAdjust: -11,
},
});
@ -1792,6 +1818,7 @@ export function finish(difficultyFailed = false) {
if (Config.alwaysShowWordsHistory) {
TestUI.toggleResultWords();
}
$("#testModesNotice").addClass("hidden");
});
}

View file

@ -41,25 +41,30 @@ export function start() {
let acc = Misc.roundTo2(TestStats.calculateAccuracy());
if (Funbox.active === "layoutfluid" && Config.mode === "time") {
const layouts = ["qwerty", "dvorak", "colemak"];
const layouts = Config.customLayoutfluid
? Config.customLayoutfluid.split("#")
: ["qwerty", "dvorak", "colemak"];
console.log(Config.customLayoutfluid);
console.log(layouts);
const numLayouts = layouts.length;
let index = 0;
index = Math.floor(time / (Config.time / 3));
index = Math.floor(time / (Config.time / numLayouts));
if (
time == Math.floor(Config.time / 3) - 3 ||
time == (Config.time / 3) * 2 - 3
time == Math.floor(Config.time / numLayouts) - 3 ||
time == (Config.time / numLayouts) * 2 - 3
) {
Notifications.add("3", 0, 1);
}
if (
time == Math.floor(Config.time / 3) - 2 ||
time == Math.floor(Config.time / 3) * 2 - 2
time == Math.floor(Config.time / numLayouts) - 2 ||
time == Math.floor(Config.time / numLayouts) * 2 - 2
) {
Notifications.add("2", 0, 1);
}
if (
time == Math.floor(Config.time / 3) - 1 ||
time == Math.floor(Config.time / 3) * 2 - 1
time == Math.floor(Config.time / numLayouts) - 1 ||
time == Math.floor(Config.time / numLayouts) * 2 - 1
) {
Notifications.add("1", 0, 1);
}

View file

@ -334,6 +334,11 @@ export function updateWordElement(showError) {
charCorrect = false;
}
let correctClass = "correct";
if (Config.highlightMode == "off") {
correctClass = "";
}
let currentLetter = currentWord[i];
let tabChar = "";
let nlChar = "";
@ -346,11 +351,11 @@ export function updateWordElement(showError) {
}
if (charCorrect) {
ret += `<letter class="correct ${tabChar}${nlChar}">${currentLetter}</letter>`;
ret += `<letter class="${correctClass} ${tabChar}${nlChar}">${currentLetter}</letter>`;
} else {
if (!showError) {
if (currentLetter !== undefined) {
ret += `<letter class="correct ${tabChar}${nlChar}">${currentLetter}</letter>`;
ret += `<letter class="${correctClass} ${tabChar}${nlChar}">${currentLetter}</letter>`;
}
} else {
if (currentLetter == undefined) {
@ -602,10 +607,14 @@ export function updateModesNotice() {
export function arrangeCharactersRightToLeft() {
$("#words").addClass("rightToLeftTest");
$("#resultWordsHistory .words").addClass("rightToLeftTest");
$("#resultReplay .words").addClass("rightToLeftTest");
}
export function arrangeCharactersLeftToRight() {
$("#words").removeClass("rightToLeftTest");
$("#resultWordsHistory .words").removeClass("rightToLeftTest");
$("#resultReplay .words").removeClass("rightToLeftTest");
}
async function loadWordsHistory() {
@ -817,7 +826,9 @@ $(document).on("mouseenter", "#resultWordsHistory .words .word", (e) => {
$(e.currentTarget).append(
`<div class="wordInputAfter">${input
.replace(/\t/g, "_")
.replace(/\n/g, "_")}</div>`
.replace(/\n/g, "_")
.replace(/</g, "&lt")
.replace(/>/g, "&gt")}</div>`
);
}
});

View file

@ -4,6 +4,7 @@ import * as Misc from "./misc";
import * as Notifications from "./notifications";
import Config from "./config";
import * as UI from "./ui";
import tinycolor from "tinycolor2";
let isPreviewingTheme = false;
export let randomTheme = null;
@ -129,15 +130,24 @@ export function clearPreview() {
}
}
export function randomiseTheme() {
export function randomizeTheme() {
var randomList;
Misc.getThemesList().then((themes) => {
randomList = themes.map((t) => {
return t.name;
});
if (Config.randomTheme === "fav" && Config.favThemes.length > 0)
if (Config.randomTheme === "fav" && Config.favThemes.length > 0) {
randomList = Config.favThemes;
} else if (Config.randomTheme === "light") {
randomList = themes
.filter((t) => tinycolor(t.bgColor).isLight())
.map((t) => t.name);
} else if (Config.randomTheme === "dark") {
randomList = themes
.filter((t) => tinycolor(t.bgColor).isDark())
.map((t) => t.name);
} else {
randomList = themes.map((t) => {
return t.name;
});
}
const previousTheme = randomTheme;
randomTheme = randomList[Math.floor(Math.random() * randomList.length)];

View file

@ -1,14 +1,6 @@
@media only screen and (max-width: 1050px) {
#centerContent {
.pageSettings .section.themes .buttons,
.pageSettings .section.language .buttons,
.pageSettings .section.layout .buttons,
.pageSettings .section.keymapLayout .buttons,
.pageSettings .section.keymapLegendStyle .buttons,
.pageSettings .section.fontFamily .buttons,
.pageSettings .section.funbox .buttons,
.pageSettings .section.keymapStyle .buttons,
.pageSettings .section.languageGroups .buttons {
.pageSettings .section.fullWidth .buttons {
grid-template-columns: 1fr 1fr 1fr;
}
@ -106,7 +98,7 @@
#result {
.buttons {
grid-template-rows: 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
#nextTestButton {
grid-column: 1/5;
width: 100%;
@ -131,15 +123,7 @@
}
#centerContent {
.pageSettings .section.themes .buttons,
.pageSettings .section.language .buttons,
.pageSettings .section.layout .buttons,
.pageSettings .section.keymapLayout .buttons,
.pageSettings .section.keymapLegendStyle .buttons,
.pageSettings .section.fontFamily .buttons,
.pageSettings .section.funbox .buttons,
.pageSettings .section.keymapStyle .buttons,
.pageSettings .section.languageGroups .buttons {
.pageSettings .section.fullWidth .buttons {
grid-template-columns: 1fr 1fr;
}
}
@ -219,7 +203,7 @@
}
#result {
.buttons {
grid-template-rows: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
#nextTestButton {
grid-column: 1/3;
width: 100%;

View file

@ -269,7 +269,7 @@ html {
}
::-webkit-scrollbar-corner {
background: var(--subcolour);
background: var(--sub-color);
}
a {
@ -499,7 +499,7 @@ a:hover {
}
}
#tagsWrapper {
.wrapper {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
@ -520,6 +520,15 @@ a:hover {
gap: 1rem;
overflow-y: scroll;
}
#presetEdit {
background: var(--bg-color);
border-radius: var(--roundness);
padding: 2rem;
display: grid;
gap: 1rem;
overflow-y: scroll;
}
}
#customTextPopupWrapper {
@ -579,71 +588,37 @@ a:hover {
text-align: center;
align-items: center;
}
}
}
.check {
span {
display: block;
font-size: 0.76rem;
color: var(--sub-color);
margin-left: 1.5rem;
}
label.checkbox {
span {
display: block;
font-size: 0.76rem;
color: var(--sub-color);
margin-left: 1.5rem;
}
input {
margin: 0 !important;
cursor: pointer;
width: 0;
height: 0;
display: none;
input {
margin: 0 !important;
cursor: pointer;
width: 0;
height: 0;
display: none;
& ~ .customTextRandomCheckbox {
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.1);
display: inline-block;
margin: 0 0.5rem 0 0.25rem;
transition: 0.25s;
}
&:checked ~ .customTextRandomCheckbox {
background: var(--main-color);
}
}
& ~ .customTextCheckbox {
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.1);
display: inline-block;
margin: 0 0.5rem 0 0.25rem;
transition: 0.25s;
}
.typographyCheck {
grid-row: 2;
grid-column: 1/3;
span {
display: block;
font-size: 0.76rem;
color: var(--sub-color);
margin-left: 1.5rem;
}
input {
margin: 0 !important;
cursor: pointer;
width: 0;
height: 0;
display: none;
& ~ .customTextTypographyCheckbox {
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.1);
display: inline-block;
margin: 0 0.5rem 0 0.25rem;
transition: 0.25s;
}
&:checked ~ .customTextTypographyCheckbox {
background: var(--main-color);
}
}
&:checked ~ .customTextCheckbox {
background: var(--main-color);
}
}
}
@ -1864,7 +1839,8 @@ key {
grid-column: 1/3;
}
#resultWordsHistory {
#resultWordsHistory,
#resultReplay {
// grid-area: wordsHistory;
color: var(--sub-color);
grid-column: 1/3;
@ -1876,7 +1852,30 @@ key {
user-select: none;
.word {
position: relative;
margin: 0.18rem 0.6rem 0.15rem 0;
}
&.rightToLeftTest {
//flex-direction: row-reverse; // no need for hacking 😉, CSS fully support right-to-left languages
direction: rtl;
.word {
//flex-direction: row-reverse;
direction: rtl;
}
}
&.withLigatures {
letter {
display: inline;
}
}
}
.correct {
color: var(--text-color);
}
.incorrect {
color: var(--error-color);
}
.incorrect.extra {
color: var(--error-extra-color);
}
}
@ -2276,7 +2275,8 @@ key {
#copyResultToClipboardButton,
#restartTestButtonWithSameWordset,
#nextTestButton,
#practiseMissedWordsButton {
#practiseMissedWordsButton,
#watchReplayButton {
position: relative;
border-radius: var(--roundness);
padding: 1rem 2rem;
@ -2297,6 +2297,10 @@ key {
}
}
#replayWords {
cursor: pointer;
}
#restartTestButton {
margin: 0 auto;
margin-top: 1rem;
@ -2603,36 +2607,6 @@ key {
form {
grid-area: form;
#rememberMe {
color: var(--sub-color);
-moz-user-select: none;
user-select: none;
cursor: pointer;
input {
margin: 0 !important;
cursor: pointer;
width: 0;
height: 0;
display: none;
& ~ .customCheckbox {
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.1);
display: inline-block;
margin: 0 0.5rem 0 0.25rem;
transition: 0.25s;
}
&:checked ~ .customCheckbox {
background: var(--main-color);
}
}
}
}
}
}
@ -2769,6 +2743,29 @@ key {
}
}
// I have no idea what I'm doing so I just copied the customBackgroundSize css and changed numbers so it magically worked.
&.customLayoutfluid {
.inputAndSave {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 0.5rem;
margin-bottom: 0.5rem;
input {
grid-column: 1/3;
}
.save {
grid-column: 3/4;
height: auto;
.fas {
margin-right: 0rem;
vertical-align: sub;
}
}
}
}
&.customBackgroundSize {
.inputAndButton {
display: grid;
@ -2967,6 +2964,46 @@ key {
}
}
&.presets {
.presetsListAndButton {
grid-area: buttons;
}
.preset {
grid-template-columns: 5fr 1fr 1fr;
margin-bottom: 0.5rem;
}
.addPresetButton {
margin-top: 0.5rem;
color: var(--text-color);
cursor: pointer;
transition: 0.25s;
padding: 0.2rem 0.5rem;
border-radius: var(--roundness);
background: rgba(0, 0, 0, 0.1);
text-align: center;
-webkit-user-select: none;
display: grid;
align-content: center;
height: min-content;
height: -moz-min-content;
&.active {
background: var(--main-color);
color: var(--bg-color);
}
&:hover,
&:focus {
color: var(--bg-color);
background: var(--text-color);
outline: none;
}
}
}
&.fontSize .buttons {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
@ -3045,16 +3082,7 @@ key {
}
}
&.themes,
&.language,
&.languageGroups,
&.layout,
&.keymapLayout,
&.keymapLegendStyle,
&.fontFamily,
&.funbox,
&.keymapStyle,
&.customBackgroundFilter {
&.themes {
grid-template-columns: 2fr 1fr;
grid-template-areas:
"title tabs"
@ -3103,6 +3131,29 @@ key {
margin-top: 0.5rem;
}
}
&.fullWidth {
grid-template-columns: 2fr 1fr;
grid-template-areas:
"title tabs"
"text text"
"buttons buttons";
column-gap: 2rem;
// row-gap: 0.5rem;
.buttons {
margin-left: 0;
grid-auto-flow: dense;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 0.5rem;
margin-top: 1rem;
}
}
&.randomTheme .buttons {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
}
}
@ -3454,11 +3505,6 @@ key {
height: -moz-min-content;
line-height: 1rem;
.fas,
.far {
margin-right: 0.5rem;
}
&:hover {
color: var(--bg-color);
background: var(--text-color);

File diff suppressed because one or more lines are too long

View file

@ -138,12 +138,12 @@
</div>
<textarea class="textarea" placeholder="Custom text"></textarea>
<div class="inputs">
<label class="check">
<label class="checkbox">
<input type="checkbox" />
<div class="customTextRandomCheckbox"></div>
<div class="customTextCheckbox"></div>
Random
<span>
Randomise the above words, and control how many words are
randomize the above words, and control how many words are
generated.
</span>
</label>
@ -158,9 +158,9 @@
<input type="number" value="" min="1" max="10000" />
</label>
</div>
<label class="typographyCheck">
<label class="checkbox typographyCheck">
<input type="checkbox" checked />
<div class="customTextTypographyCheckbox"></div>
<div class="customTextCheckbox customTextTypographyCheckbox"></div>
Remove Fancy Typography
<span>
Standardises typography symbols (for example “ and ” become ")
@ -203,8 +203,8 @@
(separated by spaces)
</div>
<div class="tip">
"Set" replaces the current custom word list with the filter result, "Add"
appends the filter result to the current custom word list
"Set" replaces the current custom word list with the filter result,
"Add" appends the filter result to the current custom word list
</div>
<i
class="fas fa-fw fa-spin fa-circle-notch hidden loadingIndicator"
@ -250,7 +250,7 @@
<div id="quoteSearchResults" class="quoteSearchResults"></div>
</div>
</div>
<div id="tagsWrapper" class="hidden">
<div id="tagsWrapper" class="wrapper hidden">
<div id="tagsEdit" action="" tagid="">
<div class="title"></div>
<input type="text" />
@ -263,6 +263,18 @@
<div class="button confirmButton"><i class="fas fa-check"></i></div>
</div>
</div>
<div id="presetWrapper" class="wrapper hidden">
<div id="presetEdit" action="" presetid="">
<div class="title"></div>
<input type="text" class="text" />
<label class="checkbox">
<input type="checkbox" />
<div class="customTextCheckbox"></div>
Change preset to current settings
</label>
<div class="button"><i class="fas fa-plus"></i></div>
</div>
</div>
<div id="leaderboardsWrapper" class="hidden">
<div id="leaderboards">
<div class="mainTitle">Leaderboards</div>
@ -1424,6 +1436,23 @@
</div>
<div class="words"></div>
</div>
<div id="resultReplay" class="hidden">
<div class="title">
watch replay
<span
id="playpauseReplayButton"
class="icon-button"
aria-label="Start replay"
data-balloon-pos="up"
style="display: inline-block"
>
<i class="fas fa-play"></i>
</span>
</div>
<div id="wordsWrapper">
<div id="replayWords" class="words"></div>
</div>
</div>
<div class="loginTip">
<a href="/login" tabindex="9">Sign in</a>
to save your results
@ -1441,7 +1470,7 @@
</div>
<div
id="restartTestButtonWithSameWordset"
aria-label="Repeat Test"
aria-label="Repeat test"
data-balloon-pos="down"
tabindex="0"
onclick="this.blur();"
@ -1466,6 +1495,15 @@
>
<i class="fas fa-fw fa-align-left"></i>
</div>
<div
id="watchReplayButton"
aria-label="Watch replay"
data-balloon-pos="down"
tabindex="0"
onclick="this.blur();"
>
<i class="fas fa-fw fa-backward"></i>
</div>
<div
id="copyResultToClipboardButton"
aria-label="Save screenshot"
@ -1560,9 +1598,16 @@
<p>
If you encounter a bug, or have a feature request - join the
<a href="https://discord.gg/yENzqcB" target="_blank">Discord</a>
server, <a href="https://www.reddit.com/users/Miodec" target="_blank">send me a message on Reddit</a> or
<a href="https://github.com/Miodec/monkeytype/issues" target="_blank">
create an issue on GitHub.
server,
<a href="https://www.reddit.com/users/Miodec" target="_blank">
send me a message on Reddit
</a>
or
<a
href="https://github.com/Miodec/monkeytype/issues"
target="_blank"
>
create an issue on GitHub.
</a>
</p>
</div>
@ -1949,18 +1994,36 @@
</div>
</div>
</div>
<div class="section presets">
<h1>presets</h1>
<div class="text">
Create settings presets that can be applied with one click
</div>
<div class="presetsListAndButton">
<div class="presetsList">
<div class="buttons preset" id="0">
<div class="presetButton button">
<div class="title">staggered</div>
</div>
<div class="editButton button">
<i class="fas fa-pen"></i>
</div>
<div class="removeButton button">
<i class="fas fa-trash"></i>
</div>
</div>
</div>
<div class="addPresetButton"><i class="fas fa-plus"></i></div>
</div>
</div>
<div class="sectionSpacer"></div>
</div>
<div
id="group_behavior"
class="sectionGroupTitle"
group="behaviour"
>
behaviour
<div id="group_behavior" class="sectionGroupTitle" group="behavior">
behavior
<i class="fas fa-chevron-down"></i>
</div>
<div class="settingsGroup behaviour">
<div class="settingsGroup behavior">
<div class="section difficulty">
<h1>test difficulty</h1>
<div class="text">
@ -2017,7 +2080,7 @@
<div class="section repeatQuotes" id="repeatQuotes">
<h1>repeat quotes</h1>
<div class="text">
This setting changes the restarting behaviour when typing in
This setting changes the restarting behavior when typing in
quote mode. Changing it to 'typing' will repeat the quote if
you restart while typing.
</div>
@ -2184,24 +2247,43 @@
/>
</div>
</div>
<div class="section languageGroups">
<div class="section languageGroups fullWidth">
<h1>language groups</h1>
<div class="buttons"></div>
</div>
<div class="section language">
<div class="section language fullWidth">
<h1>language</h1>
<div class="buttons"></div>
</div>
<div class="section funbox">
<div class="section funbox fullWidth">
<h1>funbox</h1>
<div class="text">
These are special modes that change the website in some
special way (by altering the word generation, behaviour of the
special way (by altering the word generation, behavior of the
website or the looks). Give each one of them a try!
</div>
<div class="buttons"></div>
</div>
<div class="sectionSpacer"></div>
<div class="section customLayoutfluid">
<h1>custom layoutfluid</h1>
<div class="text">
Select which layouts you want the layoutfluid funbox to cycle
through.
</div>
<div class="inputAndSave">
<input
type="text"
placeholder="layouts (separated by space)"
class="input"
tabindex="0"
/>
<div class="button save" tabindex="0" onclick="this.blur();">
<i class="fas fa-save fa-fw"></i>
</div>
</div>
</div>
<div class="sectionSpacer"></div>
</div>
<div id="group_input" class="sectionGroupTitle" group="input">
input
@ -2358,7 +2440,7 @@
</div>
<div class="section indicateTypos" section="">
<h1>indicate typos</h1>
<div class="text">Show typos underneath the letters</div>
<div class="text">Shows typos underneath the letters.</div>
<div class="buttons">
<div class="button off" tabindex="0" onclick="this.blur();">
off
@ -2387,7 +2469,7 @@
<div class="section swapEscAndTab" section="">
<h1>swap esc and tab</h1>
<div class="text">
Swap the behaviour of tab and escape keys.
Swap the behavior of tab and escape keys.
</div>
<div class="buttons">
<div class="button off" tabindex="0" onclick="this.blur();">
@ -2410,7 +2492,7 @@
</div>
</div>
</div>
<div class="section layout">
<div class="section layout fullWidth">
<h1>layout override</h1>
<div class="text">
With this setting you can emulate other layouts. This setting
@ -2790,6 +2872,14 @@
Change what is highlighted during the test.
</div>
<div class="buttons">
<div
class="button"
highlightMode="off"
tabindex="0"
onclick="this.blur();"
>
off
</div>
<div
class="button"
highlightMode="letter"
@ -2884,118 +2974,6 @@
</div>
</div>
</div>
<div class="section keymapMode">
<h1>keymap</h1>
<div class="text">
Displays your current layout while taking a test. React shows
what you pressed and Next shows what you need to press next.
</div>
<div class="buttons">
<div
class="button"
keymapMode="off"
tabindex="0"
onclick="this.blur();"
>
off
</div>
<div
class="button"
keymapMode="static"
tabindex="0"
onclick="this.blur();"
>
static
</div>
<div
class="button"
keymapMode="react"
tabindex="0"
onclick="this.blur();"
>
react
</div>
<div
class="button"
keymapMode="next"
tabindex="0"
onclick="this.blur();"
>
next
</div>
</div>
</div>
<div class="section keymapStyle">
<h1>keymap style</h1>
<!-- <div class="text">Displays the keymap in a different style.</div> -->
<div class="buttons">
<div
class="button"
keymapStyle="staggered"
tabindex="0"
onclick="this.blur();"
>
staggered
</div>
<div
class="button"
keymapStyle="matrix"
tabindex="0"
onclick="this.blur();"
>
matrix
</div>
<div
class="button"
keymapStyle="split"
tabindex="0"
onclick="this.blur();"
>
split
</div>
<div
class="button"
keymapStyle="split_matrix"
tabindex="0"
onclick="this.blur();"
>
split matrix
</div>
</div>
</div>
<div class="section keymapLegendStyle">
<h1>keymap legend style</h1>
<div class="buttons">
<div
class="button"
keymapLegendStyle="lowercase"
tabindex="0"
onclick="this.blur();"
>
lowercase
</div>
<div
class="button"
keymapLegendStyle="uppercase"
tabindex="0"
onclick="this.blur();"
>
uppercase
</div>
<div
class="button"
keymapLegendStyle="blank"
tabindex="0"
onclick="this.blur();"
>
blank
</div>
</div>
</div>
<div class="section keymapLayout">
<h1>keymap layout</h1>
<div class="buttons"></div>
</div>
<div class="section fontSize">
<h1>font size</h1>
<div class="text">Change the font size of the test words.</div>
@ -3042,7 +3020,7 @@
</div>
</div>
</div>
<div class="section fontFamily">
<div class="section fontFamily fullWidth">
<h1>font family</h1>
<!-- <div class="text">Change the font family for the site</div> -->
<div class="buttons"></div>
@ -3093,6 +3071,118 @@
</div>
</div>
</div>
<div class="section keymapMode">
<h1>keymap</h1>
<div class="text">
Displays your current layout while taking a test. React shows
what you pressed and Next shows what you need to press next.
</div>
<div class="buttons">
<div
class="button"
keymapMode="off"
tabindex="0"
onclick="this.blur();"
>
off
</div>
<div
class="button"
keymapMode="static"
tabindex="0"
onclick="this.blur();"
>
static
</div>
<div
class="button"
keymapMode="react"
tabindex="0"
onclick="this.blur();"
>
react
</div>
<div
class="button"
keymapMode="next"
tabindex="0"
onclick="this.blur();"
>
next
</div>
</div>
</div>
<div class="section keymapStyle fullWidth">
<h1>keymap style</h1>
<!-- <div class="text">Displays the keymap in a different style.</div> -->
<div class="buttons">
<div
class="button"
keymapStyle="staggered"
tabindex="0"
onclick="this.blur();"
>
staggered
</div>
<div
class="button"
keymapStyle="matrix"
tabindex="0"
onclick="this.blur();"
>
matrix
</div>
<div
class="button"
keymapStyle="split"
tabindex="0"
onclick="this.blur();"
>
split
</div>
<div
class="button"
keymapStyle="split_matrix"
tabindex="0"
onclick="this.blur();"
>
split matrix
</div>
</div>
</div>
<div class="section keymapLegendStyle fullWidth">
<h1>keymap legend style</h1>
<div class="buttons">
<div
class="button"
keymapLegendStyle="lowercase"
tabindex="0"
onclick="this.blur();"
>
lowercase
</div>
<div
class="button"
keymapLegendStyle="uppercase"
tabindex="0"
onclick="this.blur();"
>
uppercase
</div>
<div
class="button"
keymapLegendStyle="blank"
tabindex="0"
onclick="this.blur();"
>
blank
</div>
</div>
</div>
<div class="section keymapLayout fullWidth">
<h1>keymap layout</h1>
<div class="buttons"></div>
</div>
<div class="sectionSpacer"></div>
</div>
@ -3134,40 +3224,6 @@
</div>
</div>
</div>
<div class="section randomTheme">
<h1>randomise theme</h1>
<div class="text">
After completing a test, the theme will be set to a random
one. The random themes are not saved to your config. If set to
'fav' only favourite themes will be randomised.
</div>
<div class="buttons">
<div
class="button"
randomTheme="off"
tabindex="0"
onclick="this.blur();"
>
off
</div>
<div
class="button"
randomTheme="on"
tabindex="0"
onclick="this.blur();"
>
on
</div>
<div
class="button"
randomTheme="fav"
tabindex="0"
onclick="this.blur();"
>
fav
</div>
</div>
</div>
<div class="section customBackgroundSize">
<h1>custom background</h1>
<div class="text">
@ -3220,7 +3276,7 @@
</div>
</div>
</div>
<div class="section customBackgroundFilter">
<div class="section customBackgroundFilter fullWidth">
<h1>custom background filter</h1>
<div class="text">
Apply various effects to the custom background.
@ -3254,6 +3310,58 @@
</div>
</div>
</div>
<div class="section randomTheme fullWidth">
<h1>randomize theme</h1>
<div class="text">
After completing a test, the theme will be set to a random
one. The random themes are not saved to your config. If set to
'fav' only favourite themes will be randomized. If set to
'light' or 'dark', only presets with light or dark background
colors will be randomized, respectively.
</div>
<div class="buttons">
<div
class="button"
randomTheme="off"
tabindex="0"
onclick="this.blur();"
>
off
</div>
<div
class="button"
randomTheme="on"
tabindex="0"
onclick="this.blur();"
>
on
</div>
<div
class="button"
randomTheme="fav"
tabindex="0"
onclick="this.blur();"
>
favorite
</div>
<div
class="button"
randomTheme="light"
tabindex="0"
onclick="this.blur();"
>
light
</div>
<div
class="button"
randomTheme="dark"
tabindex="0"
onclick="this.blur();"
>
dark
</div>
</div>
</div>
<div class="section themes">
<h1>theme</h1>
<div class="tabs">
@ -3631,9 +3739,9 @@
autocomplete="password"
/>
<div>
<label id="rememberMe">
<label id="rememberMe" class="checkbox">
<input type="checkbox" checked />
<div class="customCheckbox"></div>
<div class="customTextCheckbox"></div>
Remember me
</label>
</div>

View file

@ -1,7 +1,13 @@
[
{
"name": "english",
"languages": ["english", "english_1k", "english_10k", "english_25k", "english_450k"]
"languages": [
"english",
"english_1k",
"english_10k",
"english_25k",
"english_450k"
]
},
{
"name": "spanish",
@ -29,7 +35,7 @@
},
{
"name": "portuguese",
"languages": ["portuguese","portuguese_3k"]
"languages": ["portuguese", "portuguese_3k"]
},
{
"name": "lithuanian",
@ -41,7 +47,13 @@
},
{
"name": "german",
"languages": ["german", "german_1k", "german_10k", "swiss_german", "swiss_german_1k"]
"languages": [
"german",
"german_1k",
"german_10k",
"swiss_german",
"swiss_german_1k"
]
},
{
"name": "greek",
@ -156,7 +168,7 @@
"languages": [
"code_python",
"code_c",
"code_csharp",
"code_c#",
"code_c++",
"code_javascript",
"code_javascript_1k",

View file

@ -83,7 +83,7 @@
,"pig_latin"
,"code_python"
,"code_c"
,"code_csharp"
,"code_c#"
,"code_c++"
,"code_javascript"
,"code_javascript_1k"

View file

@ -9156,7 +9156,7 @@
"inevitable",
"etiquette",
"rookie",
"coloured",
"colored",
"births",
"cubs",
"interdisciplinary",

View file

@ -5888,7 +5888,7 @@
"taupe",
"epigenesis",
"unerring",
"colourful",
"colorful",
"talmudic",
"technicality",
"intercede",
@ -6843,7 +6843,7 @@
"klipspringer",
"syllabary",
"wayfarer",
"colourless",
"colorless",
"capitalization",
"delegation",
"update",
@ -9027,7 +9027,7 @@
"daffy",
"tallow",
"ursine",
"watercolour",
"watercolor",
"tarpon",
"favourably",
"auricle",
@ -16439,7 +16439,7 @@
"sawbuck",
"props",
"outcry",
"discolouration",
"discoloration",
"sketchbook",
"experiential",
"hypochlorous",
@ -23479,7 +23479,7 @@
"drina",
"precipitately",
"looper",
"tricolour",
"tricolor",
"stewardesses",
"abye",
"materiel",
@ -23616,7 +23616,7 @@
"redundancy",
"taenia",
"bummer",
"colouration",
"coloration",
"kidnapper",
"groove",
"abolish",

View file

@ -36298,15 +36298,15 @@
"behavioristically",
"behaviorists",
"behaviors",
"behaviour",
"behavioural",
"behaviourally",
"behaviourism",
"behaviourisms",
"behaviourist",
"behaviouristic",
"behaviourists",
"behaviours",
"behavior",
"behavioral",
"behaviorally",
"behaviorism",
"behaviorisms",
"behaviorist",
"behavioristic",
"behaviorists",
"behaviors",
"behead",
"beheadal",
"beheadals",
@ -39483,10 +39483,10 @@
"bicolored",
"bicolorous",
"bicolors",
"bicolour",
"bicoloured",
"bicolourous",
"bicolours",
"bicolor",
"bicolored",
"bicolorous",
"bicolors",
"bicompact",
"bicomponent",
"biconcave",
@ -74640,64 +74640,64 @@
"colotomies",
"colotomy",
"colotyphoid",
"colour",
"colourabilities",
"colourability",
"colourable",
"colourableness",
"colourably",
"colourant",
"colourants",
"colouration",
"colourational",
"colourationally",
"colourations",
"colourative",
"Coloured",
"coloureds",
"colourer",
"colourers",
"colourfast",
"colourfastness",
"colourful",
"colourfully",
"colourfulness",
"colourfulnesses",
"colourific",
"colourifics",
"colouring",
"colourings",
"colourisation",
"colourisations",
"colourise",
"colourised",
"colourises",
"colourising",
"colourist",
"colouristic",
"colourists",
"colourization",
"colourizations",
"colourize",
"colourized",
"colourizes",
"colourizing",
"colourless",
"colourlessly",
"colourlessness",
"colourman",
"colourmen",
"colourpoint",
"colourpoints",
"colours",
"colourtype",
"colourwash",
"colourwashed",
"colourwashes",
"colourwashing",
"colourway",
"colourways",
"coloury",
"color",
"colorabilities",
"colorability",
"colorable",
"colorableness",
"colorably",
"colorant",
"colorants",
"coloration",
"colorational",
"colorationally",
"colorations",
"colorative",
"colored",
"coloreds",
"colorer",
"colorers",
"colorfast",
"colorfastness",
"colorful",
"colorfully",
"colorfulness",
"colorfulnesses",
"colorific",
"colorifics",
"coloring",
"colorings",
"colorisation",
"colorisations",
"colorise",
"colorised",
"colorises",
"colorising",
"colorist",
"coloristic",
"colorists",
"colorization",
"colorizations",
"colorize",
"colorized",
"colorizes",
"colorizing",
"colorless",
"colorlessly",
"colorlessness",
"colorman",
"colormen",
"colorpoint",
"colorpoints",
"colors",
"colortype",
"colorwash",
"colorwashed",
"colorwashes",
"colorwashing",
"colorway",
"colorways",
"colory",
"colove",
"Colp",
"colpenchyma",
@ -77115,7 +77115,7 @@
"concolor",
"concolorate",
"concolorous",
"concolour",
"concolor",
"concomitance",
"concomitances",
"concomitancies",
@ -94789,23 +94789,23 @@
"decolorizes",
"decolorizing",
"decolors",
"decolour",
"decolouration",
"decoloured",
"decolouring",
"decolourisation",
"decolourise",
"decolourised",
"decolouriser",
"decolourises",
"decolourising",
"decolourization",
"decolourize",
"decolourized",
"decolourizer",
"decolourizes",
"decolourizing",
"decolours",
"decolor",
"decoloration",
"decolored",
"decoloring",
"decolorisation",
"decolorise",
"decolorised",
"decoloriser",
"decolorises",
"decolorising",
"decolorization",
"decolorize",
"decolorized",
"decolorizer",
"decolorizes",
"decolorizing",
"decolors",
"decommission",
"decommissioned",
"decommissioner",
@ -105635,15 +105635,15 @@
"discolorment",
"discolorments",
"discolors",
"discolour",
"discolouration",
"discolourations",
"discoloured",
"discolouring",
"discolourization",
"discolourment",
"discolourments",
"discolours",
"discolor",
"discoloration",
"discolorations",
"discolored",
"discoloring",
"discolorization",
"discolorment",
"discolorments",
"discolors",
"discomboberate",
"discomboberated",
"discomboberates",
@ -120889,10 +120889,10 @@
"encolden",
"encollar",
"encolor",
"encolour",
"encoloured",
"encolouring",
"encolours",
"encolor",
"encolored",
"encoloring",
"encolors",
"encolpia",
"encolpion",
"encolpions",
@ -176218,8 +176218,8 @@
"hypercoagulabilities",
"hypercoagulability",
"hypercoagulable",
"hypercolour",
"hypercolours",
"hypercolor",
"hypercolors",
"hypercompetitive",
"hypercomplex",
"hypercomposite",
@ -229189,8 +229189,8 @@
"misbehaving",
"misbehavior",
"misbehaviors",
"misbehaviour",
"misbehaviours",
"misbehavior",
"misbehaviors",
"misbeholden",
"misbelief",
"misbeliefs",
@ -229417,10 +229417,10 @@
"miscolored",
"miscoloring",
"miscolors",
"miscolour",
"miscoloured",
"miscolouring",
"miscolours",
"miscolor",
"miscolored",
"miscoloring",
"miscolors",
"miscomfort",
"miscommand",
"miscommit",
@ -236751,9 +236751,9 @@
"multicolored",
"multicolorous",
"multicolors",
"multicolour",
"multicoloured",
"multicolours",
"multicolor",
"multicolored",
"multicolors",
"multicolumn",
"multicomponent",
"multicomputer",
@ -257322,7 +257322,7 @@
"offbreak",
"offcast",
"offcasts",
"offcolour",
"offcolor",
"offcome",
"offcut",
"offcuts",
@ -265904,10 +265904,10 @@
"overcolored",
"overcoloring",
"overcolors",
"overcolour",
"overcoloured",
"overcolouring",
"overcolours",
"overcolor",
"overcolored",
"overcoloring",
"overcolors",
"overcomable",
"overcome",
"overcomer",
@ -299331,9 +299331,9 @@
"precolorable",
"precoloration",
"precoloring",
"precolour",
"precolourable",
"precolouration",
"precolor",
"precolorable",
"precoloration",
"precombat",
"precombatant",
"precombated",
@ -317352,11 +317352,11 @@
"random",
"randomisation",
"randomisations",
"randomise",
"randomised",
"randomiser",
"randomisers",
"randomises",
"randomize",
"randomized",
"randomizer",
"randomizers",
"randomizes",
"randomish",
"randomising",
"randomization",
@ -320558,8 +320558,8 @@
"recolored",
"recoloring",
"recolors",
"recolour",
"recolouration",
"recolor",
"recoloration",
"recomb",
"recombed",
"recombinant",
@ -388954,8 +388954,8 @@
"technicology",
"Technicolor",
"technicolored",
"technicolour",
"technicoloured",
"technicolor",
"technicolored",
"technicon",
"technics",
"technikon",
@ -400780,8 +400780,8 @@
"transcience",
"transcolor",
"transcoloration",
"transcolour",
"transcolouration",
"transcolor",
"transcoloration",
"transconductance",
"transcondylar",
"transcondyloid",
@ -403089,9 +403089,9 @@
"tricolor",
"tricolored",
"tricolors",
"tricolour",
"tricoloured",
"tricolours",
"tricolor",
"tricolored",
"tricolors",
"tricolumnar",
"tricompound",
"tricon",
@ -412522,11 +412522,11 @@
"uncolored",
"uncoloredly",
"uncoloredness",
"uncolourable",
"uncolourably",
"uncoloured",
"uncolouredly",
"uncolouredness",
"uncolorable",
"uncolorably",
"uncolored",
"uncoloredly",
"uncoloredness",
"uncolt",
"uncolted",
"uncolting",
@ -416119,7 +416119,7 @@
"undisclosed",
"undisclosing",
"undiscolored",
"undiscoloured",
"undiscolored",
"undiscomfitable",
"undiscomfited",
"undiscomposed",
@ -419871,8 +419871,8 @@
"unicolorate",
"unicolored",
"unicolorous",
"unicolour",
"unicoloured",
"unicolor",
"unicolored",
"uniconoclastic",
"uniconoclastically",
"uniconstant",
@ -434325,7 +434325,7 @@
"varicoid",
"varicolored",
"varicolorous",
"varicoloured",
"varicolored",
"varicose",
"varicosed",
"varicoseness",
@ -436321,8 +436321,8 @@
"versicolorate",
"versicolored",
"versicolorous",
"versicolour",
"versicoloured",
"versicolor",
"versicolored",
"versicular",
"versicule",
"versiculi",
@ -441676,10 +441676,10 @@
"watercolorist",
"watercolorists",
"watercolors",
"watercolour",
"watercolourist",
"watercolourists",
"watercolours",
"watercolor",
"watercolorist",
"watercolorists",
"watercolors",
"watercooler",
"watercoolers",
"watercourse",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,40 +8,10 @@
],
"quotes": [
{
"text": "System.out.println(\"Hello World\");",
"source": "Print Hello World - programming-idioms.org",
"text": "public class Main {\\npublic static void main(String[] args) {\\n\\tSystem.out.println(\"Hello World\");\\n\\t}\\n}",
"source": "W3Schools - Java Getting Started",
"id": 1,
"length": 34
},
{
"text": "void finish(String name){\n\tSystem.out.println(\"My job here is done. Goodbye \" + name);\n}",
"source": "Create a procedure - programming-idioms.org",
"id": 2,
"length": 89
},
{
"text": "int square(int x){\n\treturn x*x;\n}",
"source": "Create a function which returns the square of an integer - programming-idioms.org",
"id": 3,
"length": 33
},
{
"text": "for (int i=0; i<items.length; i++) {\n\tdoSomething(items[i]);\n}",
"source": "Iterate over list values - programming-idioms.org",
"id": 4,
"length": 33
},
{
"text": "public enum OperatingSystem {\n\tOSX, WIndows, LINUX;\n\tpublic String toString() {\n\t\tswitch(this) {\n\t\t\tcase OSX: return \"Mac OS\",\n\t\t\tcase WINDOWS: return \"Windows\";\n\t\t\tcase LINUX: return \"Linux\";\n\t\t}\\n\t}\n}",
"id": 5,
"source": "Community contribution",
"length": 202
},
{
"text": "boolean contains(int[] list, int x) {\n\tfor(int y: list)\n\t\tif (y == x)\n\t\t\treturn true;\n\treturn false;\n}",
"id": 6,
"source": "Check if list contains a value - programming-idioms.org",
"length": 55
"length": 87
}
]
}

View file

@ -1352,7 +1352,7 @@
"length": 140
},
{
"text": "I've dreamt in my life dreams that have stayed with me ever after, and changed my ideas: they've gone through and through me, like wine through water, and altered the colour of my mind.",
"text": "I've dreamt in my life dreams that have stayed with me ever after, and changed my ideas: they've gone through and through me, like wine through water, and altered the color of my mind.",
"source": "Wuthering Heights",
"id": 227,
"length": 185
@ -20150,7 +20150,7 @@
"length": 274
},
{
"text": "Sure is mellow grazin' in the grass. What a trip just watchin' as the world goes past. There are too many groovy things to see while grazin' in the grass. Flowers with colours for takin', everything outta sight.",
"text": "Sure is mellow grazin' in the grass. What a trip just watchin' as the world goes past. There are too many groovy things to see while grazin' in the grass. Flowers with colors for takin', everything outta sight.",
"source": "Grazing In The Grass",
"id": 3398,
"length": 211
@ -20822,7 +20822,7 @@
"length": 503
},
{
"text": "It's an awful truth that suffering can deepen us, give a greater lustre to our colours, a richer resonance to our words. That is, if it doesn't destroy us, if it doesn't burn away the optimism and the spirit, the capacity for visions, and the respect for simple yet indispensable things.",
"text": "It's an awful truth that suffering can deepen us, give a greater lustre to our colors, a richer resonance to our words. That is, if it doesn't destroy us, if it doesn't burn away the optimism and the spirit, the capacity for visions, and the respect for simple yet indispensable things.",
"source": "Queen of The Damned",
"id": 3512,
"length": 287
@ -25058,7 +25058,7 @@
"length": 63
},
{
"text": "The rotten tiles broke with a noise of disaster and the man barely had time to let out a cry of terror as he cracked his skull and was killed outright on the cement floor. The foreigners who heard the noise in the dining room and hastened to remove the body noticed the suffocating odour of Remedios the Beauty on his skin. It was so deep in his body that the cracks in his skull did not give off blood but an amber-coloured oil that was impregnated with that secret perfume, and then they understood that the smell of Remedios the Beauty kept on torturing the men beyond death, right down to the dust of their bones.",
"text": "The rotten tiles broke with a noise of disaster and the man barely had time to let out a cry of terror as he cracked his skull and was killed outright on the cement floor. The foreigners who heard the noise in the dining room and hastened to remove the body noticed the suffocating odour of Remedios the Beauty on his skin. It was so deep in his body that the cracks in his skull did not give off blood but an amber-colored oil that was impregnated with that secret perfume, and then they understood that the smell of Remedios the Beauty kept on torturing the men beyond death, right down to the dust of their bones.",
"source": "One Hundred Years of Solitude",
"id": 4221,
"length": 617
@ -25406,7 +25406,7 @@
"length": 335
},
{
"text": "Mr Willy Wonka can make marshmallows that taste of violets, and rich caramels that change colour every ten seconds as you suck them, and little feathery sweets that melt away deliciously the moment you put them between your lips. He can make chewing-gum that never loses its taste, and sugar balloons that you can blow up to enormous sizes before you pop them with a pin and gobble them up. And, by a most secret method, he can make lovely blue birds' eggs with black spots on them, and when you put one of these in your mouth, it gradually gets smaller and smaller until suddenly there is nothing left except a tiny little dark red sugary baby bird sitting on the tip of your tongue.",
"text": "Mr Willy Wonka can make marshmallows that taste of violets, and rich caramels that change color every ten seconds as you suck them, and little feathery sweets that melt away deliciously the moment you put them between your lips. He can make chewing-gum that never loses its taste, and sugar balloons that you can blow up to enormous sizes before you pop them with a pin and gobble them up. And, by a most secret method, he can make lovely blue birds' eggs with black spots on them, and when you put one of these in your mouth, it gradually gets smaller and smaller until suddenly there is nothing left except a tiny little dark red sugary baby bird sitting on the tip of your tongue.",
"source": "Charlie and the Chocolate Factory",
"id": 4279,
"length": 684
@ -27200,7 +27200,7 @@
"length": 479
},
{
"text": "I saw a little rocky point ahead of me, as if made on purpose for a war correspondent. By running across some open ground I was on to it. There was good if not ample cover on the top. It was in the middle of the angle made by the line of advance of the men along the ridge and the line of the Devons' main advance, and quite close to the hill. Stretching away on our left over a level khaki-coloured sloping field (if I may so call it) of veldt, were the Devons lying behind ant-hills, placed as if on purpose to give scant but welcome shelter to troops advancing under fire.",
"text": "I saw a little rocky point ahead of me, as if made on purpose for a war correspondent. By running across some open ground I was on to it. There was good if not ample cover on the top. It was in the middle of the angle made by the line of advance of the men along the ridge and the line of the Devons' main advance, and quite close to the hill. Stretching away on our left over a level khaki-colored sloping field (if I may so call it) of veldt, were the Devons lying behind ant-hills, placed as if on purpose to give scant but welcome shelter to troops advancing under fire.",
"source": "Impressions of a War Correspondent",
"id": 4581,
"length": 575
@ -28574,7 +28574,7 @@
"length": 791
},
{
"text": "New thoughts and hopes were whirling through my mind, and all the colours of my life were changing.",
"text": "New thoughts and hopes were whirling through my mind, and all the colors of my life were changing.",
"source": "David Copperfield",
"id": 4812,
"length": 99

View file

@ -24,6 +24,11 @@
"bgColor": "#313131",
"textColor": "#ed6b21"
},
{
"name": "our_theme",
"bgColor": "#ce1226",
"textColor": "#fcd116"
},
{
"name": "dots",
"bgColor": "#121520",

View file

@ -0,0 +1,11 @@
:root {
--bg-color: #ce1226;
--main-color: #fcd116;
--caret-color: #fcd116;
--sub-color: #6d0f19;
--text-color: #ffffff;
--error-color: #fcd116;
--error-extra-color: #fcd116;
--colorful-error-color: #1672fc;
--colorful-error-extra-color: #1672fc;
}