build: replace webpack with vite (#5096)

This commit is contained in:
Jack 2024-02-22 02:24:20 +01:00 committed by GitHub
parent c29220323b
commit 8f6bfbb708
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 5658 additions and 12097 deletions

View file

@ -95,7 +95,7 @@ jobs:
- name: Create stub firebase config
working-directory: ./frontend/src/ts/constants
run: mv ./firebase-config-example.ts ./firebase-config-live.ts
run: mv ./firebase-config-example.ts ./firebase-config.ts && cp ./firebase-config.ts ./firebase-config-live.ts
- name: Install dependencies
run: npm ci & cd frontend && npm ci
@ -107,7 +107,7 @@ jobs:
run: npm run lint-fe
- name: Build
run: npm run pr-check-ts
run: npm run pr-check-build-fe
- name: Test
run: npm run test-fe

1
.gitignore vendored
View file

@ -87,6 +87,7 @@ dist/
frontend/public/
backend/globalConfig.json
backend/server.version
vite-build/
#cloudflare y
.cloudflareKey.txt

View file

@ -3,10 +3,10 @@
"before:init": [
"npm run lint-fe",
"npm run test-fe",
"cd frontend && npx gulp build-production"
"cd frontend && npm run validate-json && npm run build"
],
"before:release": [
"cd frontend && npm run deploy-live",
"cd frontend && firebase deploy -P live --only hosting",
"sh ./bin/purgeCfCache.sh"
]
},

View file

@ -3,11 +3,11 @@
"before:init": [
"npm run lint",
"npm run test",
"cd frontend && npx gulp build-production"
"cd frontend && npm run validate-json && npm run build"
],
"before:release": [
"sh ./bin/deployBackend.sh",
"cd frontend && npm run deploy-live",
"cd frontend && firebase deploy -P live --only hosting",
"sh ./bin/purgeCfCache.sh"
]
},

View file

@ -1,6 +1,6 @@
{
"hosting": {
"public": "public",
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{

View file

@ -1,91 +1,43 @@
const { webpack } = require("webpack");
const eslint = require("gulp-eslint-new");
const { task, src, series, watch } = require("gulp");
const { resolve } = require("path");
const fs = require("fs");
const webpackDevConfig = require("./webpack/config.dev.js");
const webpackProdConfig = require("./webpack/config.prod.js");
const JSONValidation = require("./scripts/json-validation");
import eslint, { format, failAfterError } from "gulp-eslint-new";
import gulp from "gulp";
import {
validateAll,
validateQuotes,
validateLanguages,
validateOthers,
} from "./scripts/json-validation.cjs";
const eslintConfig = "../.eslintrc.json";
const { task, src, series } = gulp;
task("lint", function () {
return src(["./src/ts/**/*.ts"])
.pipe(eslint(eslintConfig))
.pipe(eslint.format())
.pipe(eslint.failAfterError());
.pipe(format())
.pipe(failAfterError());
});
task("lint-json", function () {
return src("./static/**/*.json")
.pipe(eslint(eslintConfig))
.pipe(eslint.format())
.pipe(eslint.failAfterError());
.pipe(format())
.pipe(failAfterError());
});
task("validate-json-schema", function () {
return JSONValidation.validateAll();
return validateAll();
});
const taskWithWebpackConfig = (webpackConfig) => {
return async () => {
return new Promise((resolve, reject) => {
webpack(webpackConfig, (err, stats) => {
if (err) {
return reject(err);
}
if (stats.hasErrors()) {
return reject(new Error(stats.compilation.errors.join("\n")));
}
console.log(
`Finished building in ${
(stats.endTime - stats.startTime) / 1000
} second(s)`
);
resolve();
});
});
};
};
task("webpack", taskWithWebpackConfig(webpackDevConfig));
task("webpack-production", taskWithWebpackConfig(webpackProdConfig));
task("compile", series("lint", "lint-json", "webpack"));
task(
"compile-production",
series("lint", "lint-json", "validate-json-schema", "webpack-production")
);
task("watch", function () {
watch(["./src/ts/**/*.ts", "./src/ts/*.ts"], series("lint"));
watch(["./static/**/*.*", "./static/*.*"], series("lint-json"));
});
task("build", series("compile"));
task("build-production", series("compile-production"));
//PR CHECK
task("validate-quote-json-schema", function () {
return JSONValidation.validateQuotes();
task("pr-check-quote-json", function () {
return validateQuotes();
});
task("validate-language-json-schema", function () {
return JSONValidation.validateLanguages();
task("pr-check-language-json", function () {
return validateLanguages();
});
task("validate-other-json-schema", function () {
return JSONValidation.validateOthers();
task("pr-check-other-json", function () {
return validateOthers();
});
task("pr-check-lint-json", series("lint-json"));
task("pr-check-quote-json", series("validate-quote-json-schema"));
task("pr-check-language-json", series("validate-language-json-schema"));
task("pr-check-other-json", series("validate-other-json-schema"));
task("pr-check-lint", series("lint"));
task("pr-check-ts", series("webpack-production"));

16459
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,15 +2,18 @@
"name": "monkeytype-frontend",
"license": "GPL-3.0",
"private": true,
"type": "module",
"scripts": {
"live": "npm run build-live && cd public && npx serve -p 3000",
"audit": "npx webpack --config ./webpack/config.audit.js",
"lint": "eslint \"./**/*.ts\"",
"validate-json": "npx gulp validate-json-schema",
"audit": "vite-bundle-visualizer",
"dep-graph": "madge -c -i \"dep-graph.png\" ./src/ts",
"build": "npx gulp build",
"build-live": "export COMMIT_HASH=`git rev-parse --short HEAD` && npx gulp build-production",
"dev": "concurrently \"webpack serve --config=./webpack/config.dev.js\" \"npx gulp watch\"",
"deploy-live": "npm run build-live && firebase deploy -P live --only hosting",
"deploy-preview": "npm run build-live && firebase hosting:channel:deploy preview -P live --expires 2h",
"build": "npm run madge && vite build",
"madge": " madge --circular --extensions ts ./",
"live": "npm run build && vite preview --port 3000",
"dev": "vite dev",
"deploy-live": "npm run validate-json && npm run build && firebase deploy -P live --only hosting",
"deploy-preview": "npm run validate-json && npm run build && firebase hosting:channel:deploy preview -P live --expires 2h",
"test": "jest --verbose --collect-coverage --runInBand",
"tsc": "tsc",
"knip": "knip"
@ -32,50 +35,28 @@
"@types/node": "18.19.1",
"@types/object-hash": "2.2.1",
"@types/throttle-debounce": "2.1.0",
"@types/tinycolor2": "1.4.3",
"autoprefixer": "10.4.14",
"buffer": "6.0.3",
"circular-dependency-plugin": "5.2.2",
"concurrently": "7.4.0",
"copy-webpack-plugin": "10.2.4",
"css-loader": "6.7.1",
"css-minimizer-webpack-plugin": "3.4.1",
"dotenv": "16.3.1",
"dotenv": "16.4.5",
"eslint": "8.43.0",
"eslint-webpack-plugin": "3.1.1",
"extra-watch-webpack-plugin": "1.0.3",
"filemanager-webpack-plugin": "8.0.0",
"gulp": "4.0.2",
"gulp-eslint-new": "1.4.2",
"html-minimizer-webpack-plugin": "3.5.0",
"html-webpack-plugin": "5.5.0",
"gulp-eslint-new": "1.9.1",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-environment-node": "29.7.0",
"json-minimizer-webpack-plugin": "3.3.0",
"jsonschema": "1.4.0",
"madge": "5.0.1",
"memfs": "4.2.1",
"mini-css-extract-plugin": "2.6.0",
"normalize.css": "8.0.1",
"postcss": "8.4.27",
"postcss-loader": "7.3.3",
"postcss-scss": "4.0.6",
"sass-loader": "12.6.0",
"serve": "13.0.2",
"stream-browserify": "3.0.0",
"string-replace-loader": "3.1.0",
"style-loader": "3.3.3",
"sass": "1.70.0",
"ts-jest": "29.1.2",
"ts-loader": "9.2.6",
"ts-node-dev": "2.0.0",
"typescript": "5.3.3",
"webpack": "5.72.0",
"webpack-bundle-analyzer": "4.5.0",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.15.1",
"webpack-merge": "5.8.0",
"workbox-webpack-plugin": "6.5.4"
"vite": "5.1.2",
"vite-bundle-visualizer": "1.0.1",
"vite-plugin-checker": "0.6.4",
"vite-plugin-filter-replace": "0.1.13",
"vite-plugin-html-inject": "1.1.2",
"vite-plugin-inspect": "0.8.3",
"vite-plugin-pwa": "0.19.0"
},
"dependencies": {
"axios": "1.6.4",
@ -85,7 +66,6 @@
"chartjs-plugin-annotation": "1.4.0",
"chartjs-plugin-trendline": "1.0.2",
"color-blend": "4.0.0",
"crypto-browserify": "3.12.0",
"damerau-levenshtein": "1.0.8",
"date-fns": "2.28.0",
"firebase": "10.8.0",
@ -100,7 +80,6 @@
"object-hash": "3.0.0",
"slim-select": "2.8.1",
"stemmer": "2.0.0",
"throttle-debounce": "3.0.1",
"workbox-window": "6.5.4"
"throttle-debounce": "3.0.1"
}
}

View file

@ -1,7 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
parser: "postcss-scss",
plugins: [require("autoprefixer")],
};
module.exports = config;

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Email Handler | Monkeytype</title>
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
<link rel="stylesheet" href="css/balloon.css" />
<link rel="stylesheet" href="css/balloon.min.css" />
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
<link rel="stylesheet" href="" id="funBoxTheme" />
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
@ -17,9 +17,7 @@
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[css] %>" />
<% } %>
<link rel="stylesheet" href="styles/index.scss" />
<meta name="name" content="Monkeytype" />
<meta name="image" content="https://monkeytype.com/mtsocial.png" />
<meta
@ -164,21 +162,10 @@
</main>
</div>
</body>
<script
src="https://code.jquery.com/jquery-3.7.1.slim.min.js"
integrity="sha256-kmHvs0B+OpCW5GVHUNjv9rOmY0IvSIRcf7zGUDTDQM8="
crossorigin="anonymous"
></script>
<script src="/__/firebase/8.4.2/firebase-app.js"></script>
<script type="module">
import $ from "jquery";
import { app as firebase } from "./ts/firebase";
<script src="/__/firebase/8.4.2/firebase-analytics.js"></script>
<script src="/__/firebase/8.4.2/firebase-auth.js"></script>
<script src="/__/firebase/8.4.2/firebase-firestore.js"></script>
<script src="/__/firebase/8.4.2/firebase-functions.js"></script>
<!-- Initialize Firebase -->
<script src="/__/firebase/init.js?useEmulator=true'"></script>
<script defer>
function isPasswordStrong(password) {
const hasCapital = !!password.match(/[A-Z]/);
const hasNumber = !!password.match(/[\d]/);

View file

@ -55,15 +55,15 @@
<i class="fab fa-fw fa-twitter"></i>
<div class="text">Twitter</div>
</a>
<a href="/./terms-of-service.html" class="textButton" target="_blank">
<a href="/terms-of-service.html" class="textButton" target="_blank">
<i class="fas fa-fw fa-file-contract"></i>
<div class="text">Terms</div>
</a>
<a href="/./security-policy.html" class="textButton" target="_blank">
<a href="/security-policy.html" class="textButton" target="_blank">
<i class="fas fa-fw fa-shield-alt"></i>
<div class="text">Security</div>
</a>
<a href="/./privacy-policy.html" class="textButton" target="_blank">
<a href="/privacy-policy.html" class="textButton" target="_blank">
<i class="fas fa-fw fa-lock"></i>
<div class="text">Privacy</div>
</a>

View file

@ -26,22 +26,22 @@
rel="icon"
type="image/x-icon"
sizes="32x32"
href="/./images/favicon/favicon.ico"
href="/images/favicon/favicon.ico"
/>
<link
id="favicon"
rel="shortcut icon"
type="image/svg+xml"
href="/./images/favicon/favicon.svg"
href="/images/favicon/favicon.svg"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/./images/favicon/apple-touch-icon.png"
href="/images/favicon/apple-touch-icon.png"
/>
<link
rel="mask-icon"
href="/./images/favicon/safari-pinned-tab.svg"
href="/images/favicon/safari-pinned-tab.svg"
color="#eee"
/>
<meta name="msapplication-TileColor" content="#e2b714" />
@ -50,7 +50,7 @@
content="/images/favicon/browserconfig.xml"
/>
<meta id="metaThemeColor" name="theme-color" content="#e2b714" />
<link rel="manifest" href="/./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<meta
name="name"
content="Monkeytype | A minimalistic, customizable typing test"
@ -90,4 +90,5 @@
<meta name="twitter:card" content="summary_large_image" />
<meta name="darkreader-lock" />
<meta http-equiv="Cache-Control" content="no-store" />
<link rel="stylesheet" href="styles/index.scss" />
</head>

View file

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<%= compilation.assets["html/head.html"].source() %>
<load src="html/head.html" />
<body>
<%= compilation.assets["html/warnings.html"].source() %>
<load src="html/warnings.html" />
<div id="fpsCounter" class="hidden"></div>
<div class="customBackground"></div>
<div id="backgroundLoader" style="display: none"></div>
@ -19,7 +19,7 @@
<div id="timer" class="timerMain"></div>
</div>
<div id="popups">
<%= compilation.assets["html/popups.html"].source() %>
<load src="html/popups.html" />
</div>
<!-- <div id="div-gpt-ad-mkt-0" style="height: 100vh"></div> -->
<div id="app" class="focus">
@ -30,19 +30,19 @@
</div>
</div>
<div id="contentWrapper" class="hidden">
<%= compilation.assets["html/header.html"].source() %>
<load src="html/header.html" />
<main style="height: 100%">
<%= compilation.assets["html/pages/loading.html"].source() %> <%=
compilation.assets["html/pages/about.html"].source() %> <%=
compilation.assets["html/pages/settings.html"].source() %> <%=
compilation.assets["html/pages/login.html"].source() %> <%=
compilation.assets["html/pages/account.html"].source() %> <%=
compilation.assets["html/pages/profile.html"].source() %> <%=
compilation.assets["html/pages/test.html"].source() %> <%=
compilation.assets["html/pages/404.html"].source() %>
<load src="html/pages/loading.html" />
<load src="html/pages/about.html" />
<load src="html/pages/settings.html" />
<load src="html/pages/login.html" />
<load src="html/pages/account.html" />
<load src="html/pages/profile.html" />
<load src="html/pages/test.html" />
<load src="html/pages/404.html" />
</main>
<%= compilation.assets["html/footer.html"].source() %>
<load src="html/footer.html" />
<div id="ad-footer-wrapper" class="ad advertisement ad-h focus">
<div class="icon"><i class="fas fa-ad"></i></div>
@ -65,8 +65,9 @@
async
defer
></script>
<link rel="stylesheet" href="/./themes/serika_dark.css" id="currentTheme" />
<link rel="stylesheet" href="/themes/serika_dark.css" id="currentTheme" />
<link rel="stylesheet" href="" id="funBoxTheme" />
<link rel="stylesheet" href="" id="globalFunBoxTheme" type="text/css" />
<script type="module" src="ts/index.ts"></script>
</body>
</html>

View file

@ -5,14 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Privacy Policy | Monkeytype</title>
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
<link rel="stylesheet" href="css/balloon.css" />
<link rel="stylesheet" href="css/balloon.min.css" />
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
<link rel="stylesheet" href="" id="funBoxTheme" />
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
<link rel="shortcut icon" href="images/fav.png" />
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[css] %>" />
<% } %>
<link rel="stylesheet" href="styles/index.scss" />
<meta name="name" content="Monkeytype" />
<meta name="image" content="https://monkeytype.com/mtsocial.png" />
<meta
@ -46,15 +44,15 @@
}
h1 {
font-weight: unset;
color: var(--main-color);
font-size: 2rem;
margin-top: 3rem;
font-weight: unset !important;
color: var(--main-color) !important;
font-size: 2rem !important;
margin-top: 3rem !important;
}
body {
justify-content: center;
display: flex;
justify-content: center !important;
display: flex !important;
}
</style>
</head>

View file

@ -5,14 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Security Policy | Monkeytype</title>
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
<link rel="stylesheet" href="css/balloon.css" />
<link rel="stylesheet" href="css/balloon.min.css" />
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
<link rel="stylesheet" href="" id="funBoxTheme" />
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
<link rel="shortcut icon" href="images/fav.png" />
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[css] %>" />
<% } %>
<link rel="stylesheet" href="styles/index.scss" />
<meta name="name" content="Monkeytype" />
<meta name="image" content="https://monkeytype.com/mtsocial.png" />
<meta
@ -46,19 +44,19 @@
}
#contentWrapper {
align-items: flex-start;
align-items: flex-start !important;
}
h1 {
font-weight: unset;
color: var(--main-color);
font-size: 2rem;
margin-top: 3rem;
font-weight: unset !important;
color: var(--main-color) !important;
font-size: 2rem !important;
margin-top: 3rem !important;
}
body {
justify-content: center;
display: flex;
justify-content: center !important;
display: flex !important;
}
</style>
</head>

View file

@ -12,7 +12,7 @@
.image {
width: 100%;
align-self: center;
background-image: url("./../images/monkeymeme.jpg");
background-image: url("/images/monkeymeme.jpg");
aspect-ratio: 300/199;
background-size: contain;
border-radius: var(--roundness);

View file

@ -30,7 +30,7 @@
&.carrot {
background-color: transparent;
background-image: url("../images/carrot.png");
background-image: url("/images/carrot.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
@ -39,7 +39,7 @@
&.banana {
background-color: transparent;
background-image: url("../images/banana.png");
background-image: url("/images/banana.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;

View file

@ -3,7 +3,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/SourceCodePro-Regular.ttf") format("truetype");
src: url("/webfonts/SourceCodePro-Regular.ttf") format("truetype");
}
@font-face {
@ -11,7 +11,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/JetBrainsMono-Regular.ttf") format("truetype");
src: url("/webfonts/JetBrainsMono-Regular.ttf") format("truetype");
}
@font-face {
@ -19,7 +19,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Montserrat-Regular.ttf") format("truetype");
src: url("/webfonts/Montserrat-Regular.ttf") format("truetype");
}
@font-face {
@ -27,7 +27,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Roboto-Regular.ttf") format("truetype");
src: url("/webfonts/Roboto-Regular.ttf") format("truetype");
}
@font-face {
@ -35,7 +35,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/TitilliumWeb-Regular.ttf") format("truetype");
src: url("/webfonts/TitilliumWeb-Regular.ttf") format("truetype");
}
@font-face {
@ -43,7 +43,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Oxygen-Regular.ttf") format("truetype");
src: url("/webfonts/Oxygen-Regular.ttf") format("truetype");
}
@font-face {
@ -51,7 +51,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Itim-Regular.ttf") format("truetype");
src: url("/webfonts/Itim-Regular.ttf") format("truetype");
}
@font-face {
@ -59,7 +59,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Comfortaa-Regular.ttf") format("truetype");
src: url("/webfonts/Comfortaa-Regular.ttf") format("truetype");
}
@font-face {
@ -67,7 +67,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/ComingSoon-Regular.ttf") format("truetype");
src: url("/webfonts/ComingSoon-Regular.ttf") format("truetype");
}
@font-face {
@ -75,7 +75,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/AtkinsonHyperlegible-Regular.ttf") format("truetype");
src: url("/webfonts/AtkinsonHyperlegible-Regular.ttf") format("truetype");
}
@font-face {
@ -83,7 +83,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Lato-Regular.ttf") format("truetype");
src: url("/webfonts/Lato-Regular.ttf") format("truetype");
}
@font-face {
@ -91,7 +91,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Lalezar-Regular.ttf") format("truetype");
src: url("/webfonts/Lalezar-Regular.ttf") format("truetype");
}
@font-face {
@ -99,7 +99,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/NotoNaskhArabic-Regular.ttf") format("truetype");
src: url("/webfonts/NotoNaskhArabic-Regular.ttf") format("truetype");
}
@font-face {
@ -107,7 +107,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Vazirmatn-Regular.ttf") format("truetype");
src: url("/webfonts/Vazirmatn-Regular.ttf") format("truetype");
}
@font-face {
@ -115,7 +115,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Ubuntu-Regular.ttf") format("truetype");
src: url("/webfonts/Ubuntu-Regular.ttf") format("truetype");
}
@font-face {
@ -123,7 +123,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/UbuntuMono-Regular.ttf") format("truetype");
src: url("/webfonts/UbuntuMono-Regular.ttf") format("truetype");
}
@font-face {
@ -131,7 +131,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Inconsolata-Regular.ttf") format("truetype");
src: url("/webfonts/Inconsolata-Regular.ttf") format("truetype");
}
@font-face {
@ -139,7 +139,7 @@
font-style: normal;
font-weight: 600;
font-display: block;
src: url("../webfonts/IBMPlexSans-SemiBold.ttf") format("truetype");
src: url("/webfonts/IBMPlexSans-SemiBold.ttf") format("truetype");
}
@font-face {
@ -147,7 +147,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/LexendDeca-Regular.ttf") format("truetype");
src: url("/webfonts/LexendDeca-Regular.ttf") format("truetype");
}
@font-face {
@ -155,7 +155,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/FiraCode-Regular.ttf") format("truetype");
src: url("/webfonts/FiraCode-Regular.ttf") format("truetype");
}
@font-face {
@ -163,7 +163,7 @@
font-style: normal;
font-weight: 700;
font-display: block;
src: url("../webfonts/Nunito-Bold.ttf") format("truetype");
src: url("/webfonts/Nunito-Bold.ttf") format("truetype");
}
@font-face {
@ -171,7 +171,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/RobotoMono-Regular.ttf") format("truetype");
src: url("/webfonts/RobotoMono-Regular.ttf") format("truetype");
}
@font-face {
@ -179,8 +179,8 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/Boon-400.ttf") format("truetype"),
url("../webfonts/Boon-400.otf") format("opentype");
src: url("/webfonts/Boon-400.ttf") format("truetype"),
url("/webfonts/Boon-400.otf") format("opentype");
}
@font-face {
@ -188,7 +188,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/OpenDyslexic.otf") format("opentype");
src: url("/webfonts/OpenDyslexic.otf") format("opentype");
}
@font-face {
@ -196,7 +196,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/CascadiaMono-Regular.ttf") format("truetype");
src: url("/webfonts/CascadiaMono-Regular.ttf") format("truetype");
}
@font-face {
@ -204,7 +204,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/IBMPlexMono-Regular.ttf") format("truetype");
src: url("/webfonts/IBMPlexMono-Regular.ttf") format("truetype");
}
@font-face {
@ -212,7 +212,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/OverpassMono-VariableFont_wght.ttf") format("truetype");
src: url("/webfonts/OverpassMono-VariableFont_wght.ttf") format("truetype");
}
@font-face {
@ -220,7 +220,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/HackNerdFont-Regular.ttf") format("truetype");
src: url("/webfonts/HackNerdFont-Regular.ttf") format("truetype");
}
@font-face {
@ -228,5 +228,5 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/CommitMono-400-Regular.otf") format("opentype");
src: url("/webfonts/CommitMono-400-Regular.otf") format("opentype");
}

View file

@ -9,29 +9,29 @@
position: fixed;
}
.up {
background-image: url("../images/monkey/m3.png");
background-image: url("/images/monkey/m3.png");
}
.left {
background-image: url("../images/monkey/m1.png");
background-image: url("/images/monkey/m1.png");
}
.right {
background-image: url("../images/monkey/m2.png");
background-image: url("/images/monkey/m2.png");
}
.both {
background-image: url("../images/monkey/m4.png");
background-image: url("/images/monkey/m4.png");
}
.fast {
.up {
background-image: url("../images/monkey/m3_fast.png");
background-image: url("/images/monkey/m3_fast.png");
}
.left {
background-image: url("../images/monkey/m1_fast.png");
background-image: url("/images/monkey/m1_fast.png");
}
.right {
background-image: url("../images/monkey/m2_fast.png");
background-image: url("/images/monkey/m2_fast.png");
}
.both {
background-image: url("../images/monkey/m4_fast.png");
background-image: url("/images/monkey/m4_fast.png");
}
}
}

View file

@ -5,14 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Terms of Service | Monkeytype</title>
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
<link rel="stylesheet" href="css/balloon.css" />
<link rel="stylesheet" href="css/balloon.min.css" />
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
<link rel="stylesheet" href="" id="funBoxTheme" />
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
<link rel="shortcut icon" href="images/fav.png" />
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[css] %>" />
<% } %>
<link rel="stylesheet" href="styles/index.scss" />
<meta name="name" content="Monkeytype" />
<meta name="image" content="https://monkeytype.com/mtsocial.png" />
<meta
@ -38,23 +36,23 @@
<meta name="twitter:card" content="summary_large_image" />
<style>
header {
font-size: 2.5rem;
font-size: 2.5rem !important;
}
main {
color: var(--text-color);
color: var(--text-color) !important;
}
h1 {
font-weight: unset;
color: var(--main-color);
font-size: 2rem;
margin-top: 3rem;
font-weight: unset !important;
color: var(--main-color) !important;
font-size: 2rem !important;
margin-top: 3rem !important;
}
body {
justify-content: center;
display: flex;
justify-content: center !important;
display: flex !important;
}
</style>
</head>

View file

@ -1,7 +1,7 @@
import { getAuthenticatedUser, isAuthenticated } from "../../firebase";
import { getIdToken } from "firebase/auth";
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { CLIENT_VERSION } from "../../version";
import { envConfig } from "../../constants/env-config";
type AxiosClientMethod = (
endpoint: string,
@ -29,7 +29,7 @@ async function adaptRequestOptions<TQuery, TPayload>(
Accept: "application/json",
"Content-Type": "application/json",
...(idToken && { Authorization: `Bearer ${idToken}` }),
"X-Client-Version": CLIENT_VERSION,
"X-Client-Version": envConfig.clientVersion,
},
};
}

View file

@ -18,11 +18,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "click",
configValue: "1",
hover: (): void => {
SoundController.previewClick("1");
void SoundController.previewClick("1");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("1");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -30,11 +30,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "beep",
configValue: "2",
hover: (): void => {
SoundController.previewClick("2");
void SoundController.previewClick("2");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("2");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -42,11 +42,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "pop",
configValue: "3",
hover: (): void => {
SoundController.previewClick("3");
void SoundController.previewClick("3");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("3");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -54,11 +54,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "nk creams",
configValue: "4",
hover: (): void => {
SoundController.previewClick("4");
void SoundController.previewClick("4");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("4");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -66,11 +66,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "typewriter",
configValue: "5",
hover: (): void => {
SoundController.previewClick("5");
void SoundController.previewClick("5");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("5");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -78,11 +78,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "osu",
configValue: "6",
hover: (): void => {
SoundController.previewClick("6");
void SoundController.previewClick("6");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("6");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -90,11 +90,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "hitmarker",
configValue: "7",
hover: (): void => {
SoundController.previewClick("7");
void SoundController.previewClick("7");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("7");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -174,11 +174,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "fist fight",
configValue: "14",
hover: (): void => {
SoundController.previewClick("14");
void SoundController.previewClick("14");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("14");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -186,11 +186,11 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
display: "rubber keys",
configValue: "15",
hover: (): void => {
SoundController.previewClick("15");
void SoundController.previewClick("15");
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("15");
SoundController.playClick();
void SoundController.playClick();
},
},
],

View file

@ -19,7 +19,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
configValue: "1",
exec: (): void => {
UpdateConfig.setPlaySoundOnError("1");
SoundController.playError();
void SoundController.playError();
},
},
{
@ -28,7 +28,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
configValue: "2",
exec: (): void => {
UpdateConfig.setPlaySoundOnError("2");
SoundController.playError();
void SoundController.playError();
},
},
{
@ -37,7 +37,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
configValue: "3",
exec: (): void => {
UpdateConfig.setPlaySoundOnError("3");
SoundController.playError();
void SoundController.playError();
},
},
{
@ -46,7 +46,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
configValue: "4",
exec: (): void => {
UpdateConfig.setPlaySoundOnError("4");
SoundController.playError();
void SoundController.playError();
},
},
],

View file

@ -11,7 +11,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
configValue: "0.1",
exec: (): void => {
UpdateConfig.setSoundVolume("0.1");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -20,7 +20,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
configValue: "0.5",
exec: (): void => {
UpdateConfig.setSoundVolume("0.5");
SoundController.playClick();
void SoundController.playClick();
},
},
{
@ -29,7 +29,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
configValue: "1.0",
exec: (): void => {
UpdateConfig.setSoundVolume("1.0");
SoundController.playClick();
void SoundController.playClick();
},
},
],

View file

@ -1,14 +1,18 @@
type Config = {
backendUrl: string;
isDevelopment: boolean;
clientVersion: string;
};
// @ts-expect-error
//@ts-expect-error these get replaced by vite
const backendUrl = BACKEND_URL;
// @ts-expect-error
const isDevelopment = IS_DEVELOPMENT;
// @ts-expect-error
const clientVersion = CLIENT_VERSION;
export const envConfig: Config = {
backendUrl,
isDevelopment,
clientVersion,
};

View file

@ -223,7 +223,7 @@ function handleSpace(): void {
TestInput.incrementKeypressCount();
TestInput.pushKeypressWord(TestWords.words.currentIndex);
if (!nospace) {
Sound.playClick();
void Sound.playClick();
}
Replay.addReplayEvent("submitCorrectWord");
if (TestWords.words.currentIndex === TestWords.words.length) {
@ -234,9 +234,9 @@ function handleSpace(): void {
} else {
if (!nospace) {
if (Config.playSoundOnError === "off" || Config.blindMode) {
Sound.playClick();
void Sound.playClick();
} else {
Sound.playError();
void Sound.playError();
}
}
TestInput.pushMissedWord(TestWords.words.getCurrent());
@ -583,12 +583,12 @@ function handleChar(
);
if (thisCharCorrect) {
Sound.playClick();
void Sound.playClick();
} else {
if (Config.playSoundOnError === "off" || Config.blindMode) {
Sound.playClick();
void Sound.playClick();
} else {
Sound.playError();
void Sound.playError();
}
}
@ -1000,7 +1000,7 @@ $(document).on("keydown", async (event) => {
//blocking firefox from going back in history with backspace
if (event.key === "Backspace") {
Sound.playClick();
void Sound.playClick();
const t = /INPUT|SELECT|TEXTAREA/i;
if (
!t.test((event.target as unknown as Element).tagName)
@ -1068,7 +1068,7 @@ $(document).on("keydown", async (event) => {
//show dead keys
if (event.key === "Dead" && !CompositionState.getComposing()) {
Sound.playClick();
void Sound.playClick();
const word: HTMLElement | null = document.querySelector<HTMLElement>(
"#words .word.active"
);

View file

@ -1,5 +1,4 @@
import Config from "../config";
import { Howler, Howl } from "howler";
import * as ConfigEvent from "../observables/config-event";
import {
createErrorMessage,
@ -10,6 +9,12 @@ import { leftState, rightState } from "../test/shift-tracker";
import { capsState } from "../test/caps-warning";
import * as Notifications from "../elements/notifications";
import type { Howl } from "howler";
async function gethowler(): Promise<typeof import("howler")> {
return await import("howler");
}
type ClickSounds = Record<
string,
{
@ -29,7 +34,8 @@ type ErrorSounds = Record<
let errorSounds: ErrorSounds | null = null;
let clickSounds: ClickSounds | null = null;
function initErrorSound(): void {
async function initErrorSound(): Promise<void> {
const Howl = (await gethowler()).Howl;
if (errorSounds !== null) return;
errorSounds = {
1: [
@ -76,9 +82,11 @@ function initErrorSound(): void {
},
],
};
Howler.volume(parseFloat(Config.soundVolume));
}
function init(): void {
async function init(): Promise<void> {
const Howl = (await gethowler()).Howl;
if (clickSounds !== null) return;
clickSounds = {
1: [
@ -380,10 +388,11 @@ function init(): void {
},
],
};
Howler.volume(parseFloat(Config.soundVolume));
}
export function previewClick(val: string): void {
if (clickSounds === null) init();
export async function previewClick(val: string): Promise<void> {
if (clickSounds === null) await init();
const safeClickSounds = clickSounds as ClickSounds;
@ -525,8 +534,8 @@ function createPreviewScale(scaleName: ValidScales): () => void {
direction: 1,
};
return () => {
if (clickSounds === null) init();
return async () => {
if (clickSounds === null) await init();
playScale(scaleName, scale);
};
}
@ -632,7 +641,7 @@ export function playNote(
oscillatorNode.stop(audioCtx.currentTime + 0.5);
}
export function playClick(codeOverride?: string): void {
export async function playClick(codeOverride?: string): Promise<void> {
if (Config.playSoundOnClick === "off") return;
if (Config.playSoundOnClick in scaleConfigurations) {
@ -649,7 +658,7 @@ export function playClick(codeOverride?: string): void {
return;
}
if (clickSounds === null) init();
if (clickSounds === null) await init();
const sounds = (clickSounds as ClickSounds)[Config.playSoundOnClick];
@ -664,9 +673,9 @@ export function playClick(codeOverride?: string): void {
soundToPlay.play();
}
export function playError(): void {
export async function playError(): Promise<void> {
if (Config.playSoundOnError === "off") return;
if (errorSounds === null) initErrorSound();
if (errorSounds === null) await initErrorSound();
const sounds = (errorSounds as ErrorSounds)[Config.playSoundOnError];
if (sounds === undefined) throw new Error("Invalid error sound ID");
@ -681,10 +690,14 @@ export function playError(): void {
}
function setVolume(val: number): void {
Howler.volume(val);
try {
Howler.volume(val);
} catch (e) {
//
}
}
ConfigEvent.subscribe((eventKey, eventValue) => {
if (eventKey === "playSoundOnClick" && eventValue !== "off") init();
if (eventKey === "playSoundOnClick" && eventValue !== "off") void init();
if (eventKey === "soundVolume") setVolume(parseFloat(eventValue as string));
});

View file

@ -114,9 +114,9 @@ export async function loadStyle(name: string): Promise<void> {
resolve();
};
if (name === "custom") {
link.href = `/./themes/serika_dark.css`;
link.href = `/themes/serika_dark.css`;
} else {
link.href = `/./themes/${name}.css`;
link.href = `/themes/${name}.css`;
}
if (headScript === null) {

View file

@ -255,3 +255,15 @@ export async function update(): Promise<void> {
}
} catch {}
}
if (import.meta.hot !== undefined) {
import.meta.hot.dispose(() => {
//
});
import.meta.hot.accept(() => {
//
});
import.meta.hot.on("vite:afterUpdate", () => {
void update();
});
}

View file

@ -1,4 +1,4 @@
import { CLIENT_VERSION } from "../version";
import { envConfig } from "../constants/env-config";
$("#nocss .requestedStylesheets").html(
"Requested stylesheets:<br>" +
@ -19,7 +19,7 @@ $("#nocss .requestedJs").html(
.filter((l) => /(\/js\/mon|\/js\/vendor)/gi.test(l))
.join("<br>") +
"<br><br>Client version:<br>" +
CLIENT_VERSION
envConfig.clientVersion
);
if (window.navigator.userAgent.toLowerCase().includes("mac")) {

View file

@ -1,10 +1,7 @@
// this file should be concatenated at the top of the legacy ts files
import "jquery";
import "jquery-color";
import "jquery.easing";
import "../styles/index.scss";
import "./firebase";
import * as Logger from "./utils/logger";
@ -44,35 +41,32 @@ import "./states/connection";
import "./test/tts";
import "./elements/fps-counter";
import "./controllers/profile-search-controller";
import "./version";
import { isDevEnvironment } from "./utils/misc";
type ExtendedGlobal = typeof globalThis & MonkeyTypes.Global;
function addToGlobal(items: Record<string, unknown>): void {
for (const [name, item] of Object.entries(items)) {
//@ts-expect-error
window[name] = item;
}
}
const extendedGlobal = global as ExtendedGlobal;
extendedGlobal.snapshot = DB.getSnapshot;
extendedGlobal.config = Config;
extendedGlobal.toggleFilterDebug = Account.toggleFilterDebug;
extendedGlobal.glarsesMode = enable;
extendedGlobal.stats = TestStats.getStats;
extendedGlobal.replay = Replay.getReplayExport;
extendedGlobal.enableTimerDebug = TestTimer.enableTimerDebug;
extendedGlobal.getTimerStats = TestTimer.getTimerStats;
extendedGlobal.toggleUnsmoothedRaw = Result.toggleUnsmoothedRaw;
extendedGlobal.egVideoListener = egVideoListener;
extendedGlobal.toggleDebugLogs = Logger.toggleDebugLogs;
addToGlobal({
snapshot: DB.getSnapshot,
config: Config,
toggleFilterDebug: Account.toggleFilterDebug,
glarsesMode: enable,
stats: TestStats.getStats,
replay: Replay.getReplayExport,
enableTimerDebug: TestTimer.enableTimerDebug,
getTimerStats: TestTimer.getTimerStats,
toggleUnsmoothedRaw: Result.toggleUnsmoothedRaw,
egVideoListener: egVideoListener,
toggleDebugLogs: Logger.toggleDebugLogs,
});
if (isDevEnvironment()) {
//@ts-expect-error
extendedGlobal.$ = $;
void import("jquery").then((jq) => {
addToGlobal({ $: jq.default });
});
}

View file

@ -270,7 +270,7 @@ async function initGroups(): Promise<void> {
UpdateConfig.setPlaySoundOnError,
"button",
() => {
if (Config.playSoundOnError !== "off") Sound.playError();
if (Config.playSoundOnError !== "off") void Sound.playError();
}
) as SettingsGroup<SharedTypes.ConfigValue>;
groups["playSoundOnClick"] = new SettingsGroup(
@ -278,7 +278,7 @@ async function initGroups(): Promise<void> {
UpdateConfig.setPlaySoundOnClick,
"button",
() => {
if (Config.playSoundOnClick !== "off") Sound.playClick("KeyQ");
if (Config.playSoundOnClick !== "off") void Sound.playClick("KeyQ");
}
) as SettingsGroup<SharedTypes.ConfigValue>;
groups["showAllLines"] = new SettingsGroup(

View file

@ -8,7 +8,6 @@ import * as Focus from "./test/focus";
import * as CookiePopup from "./popups/cookie-popup";
import * as PSA from "./elements/psa";
import * as ConnectionState from "./states/connection";
import { Workbox } from "workbox-window";
import * as FunboxList from "./test/funbox/funbox-list";
//@ts-expect-error
import Konami from "konami";
@ -37,9 +36,9 @@ void UpdateConfig.loadFromLocalStorage();
Focus.set(true, true);
$(document).ready(() => {
Misc.loadCSS("/./css/slimselect.min.css", true);
Misc.loadCSS("/./css/balloon.min.css", true);
Misc.loadCSS("/./css/fa.min.css", true);
Misc.loadCSS("/css/slimselect.min.css", true);
Misc.loadCSS("/css/balloon.min.css", true);
Misc.loadCSS("/css/fa.min.css", true);
CookiePopup.check();
@ -95,67 +94,6 @@ $(document).ready(() => {
new Konami("https://keymash.io/");
});
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
// disabling service workers on localhost - they dont really work well with local development
// and cause issues with hot reloading
if (Misc.isDevEnvironment()) {
void navigator.serviceWorker
.getRegistrations()
.then(function (registrations) {
for (const registration of registrations) {
// if (registration.scope !== "https://monkeytype.com/")
void registration.unregister();
}
});
} else {
const wb = new Workbox("/service-worker.js");
let updateBannerId: number;
// Add an event listener to detect when the registered
// service worker has installed but is waiting to activate.
wb.addEventListener("waiting", (event) => {
// set up a listener that will show a banner as soon as
// the previously waiting service worker has taken control.
wb.addEventListener("controlling", (event2) => {
if (
(event.isUpdate || event2.isUpdate) &&
updateBannerId === undefined
) {
// updateBannerId = Notifications.addBanner(
// "Update ready - please refresh",
// 1,
// "gift",
// true
// );
}
});
wb.messageSkipWaiting();
});
wb.register()
.then((registration) => {
// if (registration?.waiting) {
// //@ts-ignore
// registration?.onupdatefound = (): void => {
// Notifications.add("Downloading update...", 1, 0, "Update");
// };
// }
console.log("Service worker registration succeeded:", registration);
setInterval(() => {
void wb.update(); //check for updates every 15 minutes
}, 900000);
})
.catch((e) => {
console.log("Service worker registration failed:", e);
});
}
});
}
window.onerror = function (message, url, line, column, error): void {
void log("error", {
error: error?.stack ?? "",

View file

@ -88,12 +88,12 @@ export function pauseReplay(): void {
function playSound(error = false): void {
if (error) {
if (config.playSoundOnError !== "off") {
Sound.playError();
void Sound.playError();
} else {
Sound.playClick();
void Sound.playClick();
}
} else {
Sound.playClick();
void Sound.playClick();
}
}

View file

@ -21,8 +21,12 @@ import * as FunboxList from "./funbox/funbox-list";
import { debounce } from "throttle-debounce";
import * as ResultWordHighlight from "../elements/result-word-highlight";
import * as ActivePage from "../states/active-page";
import html2canvas from "html2canvas";
import Format from "../utils/format";
import * as Loader from "../elements/loader";
async function gethtml2canvas(): Promise<typeof import("html2canvas").default> {
return (await import("html2canvas")).default;
}
const debouncedZipfCheck = debounce(250, async () => {
const supports = await Misc.checkIfLanguageSupportsZipf(Config.language);
@ -398,6 +402,7 @@ export function colorful(tc: boolean): void {
let firefoxClipboardNotificatoinShown = false;
export async function screenshot(): Promise<void> {
Loader.show();
let revealReplay = false;
let revertCookie = false;
@ -409,6 +414,7 @@ export async function screenshot(): Promise<void> {
}
function revertScreenshot(): void {
Loader.hide();
$("#ad-result-wrapper").removeClass("hidden");
$("#ad-result-small-wrapper").removeClass("hidden");
$("#testConfig").removeClass("hidden");
@ -488,7 +494,10 @@ export async function screenshot(): Promise<void> {
try {
const paddingX = Misc.convertRemToPixels(2);
const paddingY = Misc.convertRemToPixels(2);
const canvas = await html2canvas(document.body, {
const canvas = await (
await gethtml2canvas()
)(document.body, {
backgroundColor: await ThemeColors.get("bg"),
width: sourceWidth + paddingX * 2,
height: sourceHeight + paddingY * 2,

View file

@ -248,23 +248,6 @@ declare namespace MonkeyTypes {
nextDelay: number;
};
type Global = {
snapshot(): Snapshot | undefined;
config: SharedTypes.Config;
toggleFilterDebug(): void;
glarsesMode(): void;
stats(): void;
replay(): string;
enableTimerDebug(): void;
getTimerStats(): TimerStats[];
toggleUnsmoothedRaw(): void;
enableSpacingDebug(): void;
noGoogleNoMo(): void;
egVideoListener(options: Record<string, string>): void;
wpmCalculationDebug(): void;
toggleDebugLogs(): void;
};
type GithubRelease = {
url: string;
assets_url: string;

View file

@ -4,7 +4,7 @@ const nativeLog = console.log;
const nativeWarn = console.warn;
const nativeError = console.error;
let debugLogs = localStorage.getItem("debugLogs") === "true" ?? false;
let debugLogs = localStorage.getItem("debugLogs") === "true";
if (isDevEnvironment()) {
debugLogs = true;

View file

@ -26,7 +26,7 @@ export const cachedFetchJson = memoizeAsync<string, typeof fetchJson>(
export async function getLayoutsList(): Promise<MonkeyTypes.Layouts> {
try {
const layoutsList = await cachedFetchJson<MonkeyTypes.Layouts>(
"/./layouts/_list.json"
"/layouts/_list.json"
);
return layoutsList;
} catch (e) {
@ -52,7 +52,7 @@ let themesList: MonkeyTypes.Theme[] | undefined;
export async function getThemesList(): Promise<MonkeyTypes.Theme[]> {
if (!themesList) {
let themes = await cachedFetchJson<MonkeyTypes.Theme[]>(
"/./themes/_list.json"
"/themes/_list.json"
);
themes = themes.sort(function (a: MonkeyTypes.Theme, b: MonkeyTypes.Theme) {
@ -94,7 +94,7 @@ export async function getSortedThemesList(): Promise<MonkeyTypes.Theme[]> {
export async function getLanguageList(): Promise<string[]> {
try {
const languageList = await cachedFetchJson<string[]>(
"/./languages/_list.json"
"/languages/_list.json"
);
return languageList;
} catch (e) {
@ -108,7 +108,7 @@ export async function getLanguageGroups(): Promise<
try {
const languageGroupList = await cachedFetchJson<
MonkeyTypes.LanguageGroup[]
>("/./languages/_groups.json");
>("/languages/_groups.json");
return languageGroupList;
} catch (e) {
throw new Error("Language groups JSON fetch failed");
@ -122,7 +122,7 @@ export async function getLanguage(
// try {
if (currentLanguage === undefined || currentLanguage.name !== lang) {
currentLanguage = await cachedFetchJson<MonkeyTypes.LanguageObject>(
`/./languages/${lang}.json`
`/languages/${lang}.json`
);
}
return currentLanguage;
@ -130,7 +130,7 @@ export async function getLanguage(
// console.error(`error getting language`);
// console.error(e);
// currentLanguage = await cachedFetchJson<MonkeyTypes.LanguageObject>(
// `/./language/english.json`
// `/language/english.json`
// );
// return currentLanguage;
// }
@ -161,7 +161,7 @@ let funboxList: MonkeyTypes.FunboxMetadata[] | undefined;
export async function getFunboxList(): Promise<MonkeyTypes.FunboxMetadata[]> {
if (!funboxList) {
let list = await cachedFetchJson<MonkeyTypes.FunboxMetadata[]>(
"/./funbox/_list.json"
"/funbox/_list.json"
);
list = list.sort(function (
a: MonkeyTypes.FunboxMetadata,
@ -193,7 +193,7 @@ let fontsList: MonkeyTypes.FontObject[] | undefined;
export async function getFontsList(): Promise<MonkeyTypes.FontObject[]> {
if (!fontsList) {
let list = await cachedFetchJson<MonkeyTypes.FontObject[]>(
"/./fonts/_list.json"
"/fonts/_list.json"
);
list = list.sort(function (
a: MonkeyTypes.FontObject,
@ -215,7 +215,7 @@ export async function getFontsList(): Promise<MonkeyTypes.FontObject[]> {
export async function getChallengeList(): Promise<MonkeyTypes.Challenge[]> {
try {
const data = await cachedFetchJson<MonkeyTypes.Challenge[]>(
"/./challenges/_list.json"
"/challenges/_list.json"
);
return data;
} catch (e) {
@ -225,7 +225,7 @@ export async function getChallengeList(): Promise<MonkeyTypes.Challenge[]> {
export async function getSupportersList(): Promise<string[]> {
try {
const data = await cachedFetchJson<string[]>("/./about/supporters.json");
const data = await cachedFetchJson<string[]>("/about/supporters.json");
return data;
} catch (e) {
throw new Error("Supporters list JSON fetch failed");
@ -234,7 +234,7 @@ export async function getSupportersList(): Promise<string[]> {
export async function getContributorsList(): Promise<string[]> {
try {
const data = await cachedFetchJson<string[]>("/./about/contributors.json");
const data = await cachedFetchJson<string[]>("/about/contributors.json");
return data;
} catch (e) {
throw new Error("Contributors list JSON fetch failed");

View file

@ -1,7 +1,7 @@
export const CLIENT_VERSION = "DEVELOPMENT-CLIENT";
import { envConfig } from "./constants/env-config";
$(document.body).on("click", ".currentVersion", (e) => {
if (e.shiftKey) {
alert(CLIENT_VERSION);
alert(envConfig.clientVersion);
}
});

View file

@ -1,22 +0,0 @@
{
"short_name": "Monkeytype",
"name": "Monkeytype",
"start_url": "/",
"icons": [
{
"src": "/images/icons/maskable_icon_x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/images/icons/general_icon_x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
}
],
"background_color": "#323437",
"display": "standalone",
"theme_color": "#323437"
}

View file

@ -2,9 +2,9 @@
"compilerOptions": {
"target": "ES6",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "CommonJS",
"module": "ESNext",
"moduleResolution": "node",
"types": ["node", "jest"],
"types": ["vite/client", "node", "jest", "jquery"],
"allowUmdGlobalAccess": true,
"resolveJsonModule": true,
"outDir": "./build",
@ -12,6 +12,7 @@
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noEmit": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,

200
frontend/vite.config.js Normal file
View file

@ -0,0 +1,200 @@
import { defineConfig, splitVendorChunkPlugin, mergeConfig } from "vite";
import path from "node:path";
import injectHTML from "vite-plugin-html-inject";
import childProcess from "child_process";
import { checker } from "vite-plugin-checker";
import { VitePWA } from "vite-plugin-pwa";
import replace from "vite-plugin-filter-replace";
import Inspect from "vite-plugin-inspect";
import autoprefixer from "autoprefixer";
import "dotenv/config";
function pad(numbers, maxLength, fillString) {
return numbers.map((number) =>
number.toString().padStart(maxLength, fillString)
);
}
function buildClientVersion() {
const date = new Date();
const versionPrefix = pad(
[date.getFullYear(), date.getMonth() + 1, date.getDate()],
2,
"0"
).join(".");
const versionSuffix = pad([date.getHours(), date.getMinutes()], 2, "0").join(
"."
);
const version = [versionPrefix, versionSuffix].join("_");
const commitHash = childProcess
.execSync("git rev-parse --short HEAD")
.toString();
return `${version}.${commitHash}`;
}
/** @type {import("vite").UserConfig} */
const BASE_CONFIG = {
plugins: [
{
name: "simple-jquery-inject",
async transform(src, id) {
if (id.endsWith(".ts")) {
//check if file has a jQuery or $() call
if (/(?:jQuery|\$)\([^)]*\)/.test(src)) {
//if file has "use strict"; at the top, add it below that line, if not, add it at the very top
if (src.startsWith(`"use strict";`)) {
return src.replace(
/("use strict";)/,
`$1\nimport $ from "jquery";`
);
} else {
return `import $ from "jquery";\n${src}`;
}
}
}
},
},
checker({
typescript: {
root: path.resolve(__dirname, "./"),
},
eslint: {
lintCommand: `eslint "${path.resolve(__dirname, "./src/ts/**/*.ts")}"`,
},
overlay: {
initialIsOpen: false,
},
}),
injectHTML(),
Inspect(),
],
server: {
open: true,
port: 3000,
host: process.env.BACKEND_URL !== undefined,
},
root: "src",
publicDir: "../static",
css: {
devSourcemap: true,
postcss: {
plugins: [autoprefixer({})],
},
},
envDir: "../",
build: {
emptyOutDir: true,
outDir: "../dist",
rollupOptions: {
input: {
monkeytype: path.resolve(__dirname, "src/index.html"),
email: path.resolve(__dirname, "src/email-handler.html"),
privacy: path.resolve(__dirname, "src/privacy-policy.html"),
security: path.resolve(__dirname, "src/security-policy.html"),
terms: path.resolve(__dirname, "src/terms-of-service.html"),
},
output: {
assetFileNames: (assetInfo) => {
let extType = assetInfo.name.split(".").at(1);
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = "images";
}
return `${extType}/[name].[hash][extname]`;
},
chunkFileNames: "js/[name].[hash].js",
entryFileNames: "js/[name].[hash].js",
},
},
},
// resolve: {
// alias: {
// "/src": path.resolve(process.cwd(), "src"),
// $: "jquery",
// },
// },
define: {
BACKEND_URL: JSON.stringify(
process.env.BACKEND_URL || "http://localhost:5005"
),
IS_DEVELOPMENT: JSON.stringify(true),
CLIENT_VERSION: JSON.stringify("DEVELOPMENT_CLIENT"),
},
optimizeDeps: {
include: ["jquery"],
},
};
/** @type {import("vite").UserConfig} */
const BUILD_CONFIG = {
plugins: [
splitVendorChunkPlugin(),
VitePWA({
registerType: "autoUpdate",
manifest: {
short_name: "Monkeytype",
name: "Monkeytype",
start_url: "/",
icons: [
{
src: "/images/icons/maskable_icon_x512.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
{
src: "/images/icons/general_icon_x512.png",
sizes: "512x512",
type: "image/png",
purpose: "any",
},
],
background_color: "#323437",
display: "standalone",
theme_color: "#323437",
},
manifestFilename: "manifest.json",
workbox: {
clientsClaim: true,
cleanupOutdatedCaches: true,
globIgnores: ["**/.*"],
globPatterns: [],
runtimeCaching: [
{
urlPattern: ({ request, url }) => {
const sameOrigin =
new URL(request.url).origin === new URL(url).origin;
const isApi = request.url.includes("api.monkeytype.com");
return sameOrigin && !isApi;
},
handler: "NetworkFirst",
options: {},
},
],
},
}),
replace([
{
filter: /firebase\.ts$/,
replace: {
from: /\.\/constants\/firebase-config/gi,
to: "./constants/firebase-config-live",
},
},
]),
],
define: {
BACKEND_URL: JSON.stringify("https://api.monkeytype.com"),
IS_DEVELOPMENT: JSON.stringify(false),
CLIENT_VERSION: JSON.stringify(buildClientVersion()),
},
};
export default defineConfig(({ command }) => {
if (command === "build") {
return mergeConfig(BASE_CONFIG, BUILD_CONFIG);
} else {
return BASE_CONFIG;
}
});

View file

@ -1,11 +0,0 @@
const { merge } = require("webpack-merge");
const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const PROD_CONFIG = require("./config.prod");
/** @type { import('webpack').Configuration } */
const AUDIT_CONFIG = {
plugins: [new BundleAnalyzerPlugin()],
};
module.exports = merge(PROD_CONFIG, AUDIT_CONFIG);

View file

@ -1,137 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { resolve } = require("path");
const webpack = require("webpack");
const CopyPlugin = require("copy-webpack-plugin");
const CircularDependencyPlugin = require("circular-dependency-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
let circularImports = 0;
const htmlWebpackPlugins = [
"terms-of-service",
"security-policy",
"privacy-policy",
"email-handler",
].map((name) => {
return new HtmlWebpackPlugin({
filename: `${name}.html`,
template: resolve(__dirname, `../static/${name}.html`),
inject: false,
});
});
/** @type { import('webpack').Configuration } */
const BASE_CONFIG = {
entry: {
monkeytype: resolve(__dirname, "../src/ts/index.ts"),
},
resolve: { extensions: [".ts", ".js"] },
output: {
publicPath: "/",
filename: "./js/[name].[chunkhash:8].js",
path: resolve(__dirname, "../public/"),
clean: true,
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
transpileOnly: true,
},
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
url: false,
importLoaders: 2,
sourceMap: true,
},
},
{
loader: "postcss-loader",
},
{
loader: "sass-loader",
options: {
sourceMap: true,
},
},
],
},
],
},
optimization: {
splitChunks: {
chunks: "all",
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
name: "vendor",
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
},
},
},
plugins: [
new CircularDependencyPlugin({
exclude: /node_modules/,
include: /./,
failOnError: true,
allowAsyncCycles: false,
cwd: process.cwd(),
onStart() {
circularImports = 0;
},
onDetected({ paths }) {
circularImports++;
const joinedPaths = paths.join("\u001b[31m -> \u001b[0m");
console.log(`\u001b[31mCircular import found: \u001b[0m${joinedPaths}`);
},
onEnd() {
const colorCode = circularImports === 0 ? 32 : 31;
const countWithColor = `\u001b[${colorCode}m${circularImports}\u001b[0m`;
console.log(`Found ${countWithColor} circular imports`);
},
}),
new CopyPlugin({
patterns: [
{
from: resolve(__dirname, "../static"),
to: "./",
globOptions: {
ignore: ["**/static/*.html"],
},
},
],
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
jQueryColor: "jquery-color",
jQueryEasing: "jquery.easing",
}),
new HtmlWebpackPlugin({
filename: "./index.html",
template: resolve(__dirname, "../static/main.html"),
inject: "body",
}),
...htmlWebpackPlugins,
new MiniCssExtractPlugin({
filename: "./css/style.[chunkhash:8].css",
}),
],
};
module.exports = BASE_CONFIG;

View file

@ -1,35 +0,0 @@
const { resolve } = require("path");
const { merge } = require("webpack-merge");
const BASE_CONFIG = require("./config.base");
const ExtraWatchWebpackPlugin = require("extra-watch-webpack-plugin");
const webpack = require("webpack");
require("dotenv").config();
/** @type { import('webpack').Configuration } */
const DEV_CONFIG = {
mode: "development",
devtool: "inline-source-map",
devServer: {
compress: true,
port: 3000,
open: true,
hot: false,
historyApiFallback: true,
client: {
overlay: false,
},
},
plugins: [
new ExtraWatchWebpackPlugin({
dirs: [resolve(__dirname, "../static/html")],
}),
new webpack.DefinePlugin({
BACKEND_URL: JSON.stringify(
process.env.BACKEND_URL || "http://localhost:5005"
),
IS_DEVELOPMENT: JSON.stringify(true),
}),
],
};
module.exports = merge(BASE_CONFIG, DEV_CONFIG);

View file

@ -1,171 +0,0 @@
const { resolve } = require("path");
const { merge } = require("webpack-merge");
const FilemanagerPlugin = require("filemanager-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
const JsonMinimizerPlugin = require("json-minimizer-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const BASE_CONFIG = require("./config.base");
const webpack = require("webpack");
function pad(numbers, maxLength, fillString) {
return numbers.map((number) =>
number.toString().padStart(maxLength, fillString)
);
}
const { COMMIT_HASH = "NO_HASH" } = process.env;
/** @type { import('webpack').Configuration } */
const PRODUCTION_CONFIG = {
mode: "production",
module: {
rules: [
{
test: /version\.ts$/,
loader: "string-replace-loader",
options: {
search: /^export const CLIENT_VERSION =.*/,
replace() {
const date = new Date();
const versionPrefix = pad(
[date.getFullYear(), date.getMonth() + 1, date.getDate()],
2,
"0"
).join(".");
const versionSuffix = pad(
[date.getHours(), date.getMinutes()],
2,
"0"
).join(".");
const version = [versionPrefix, versionSuffix].join("_");
return `export const CLIENT_VERSION = "${version}.${COMMIT_HASH}";`;
},
flags: "g",
},
},
{
test: /firebase\.ts$/,
loader: "string-replace-loader",
options: {
search: /\.\/constants\/firebase-config/,
replace() {
return "./constants/firebase-config-live";
},
flags: "g",
},
},
],
},
optimization: {
minimize: true,
minimizer: [
`...`,
new HtmlMinimizerPlugin(),
new JsonMinimizerPlugin(),
new CssMinimizerPlugin(),
],
},
plugins: [
new FilemanagerPlugin({
events: {
onEnd: {
delete: [resolve(__dirname, "../public/html")],
},
},
}),
new WorkboxPlugin.GenerateSW({
// these options encourage the ServiceWorkers to get in there fast
// and not allow any straggling "old" SWs to hang around
clientsClaim: true,
skipWaiting: false,
//include the generated css and js files
maximumFileSizeToCacheInBytes: 11000000,
cleanupOutdatedCaches: true,
exclude: [
// /html\/.*\.html/,
// /LICENSE\.txt/,
// /\.DS_Store/,
// /\.map$/,
// /^manifest.*\.js$/,
// /languages\/.*\.json/,
// /quotes\/.*\.json/,
// /themes\/.*\.css/,
// /challenges\/.*\.txt/,
// /sound\/.*\.wav/,
// /images\/.*\.(png|jpg)/,
// /webfonts\/.+/,
/./,
],
runtimeCaching: [
// {
// urlPattern: /.+\.(jpg|jpeg|gif|png|svg)/,
// handler: "CacheFirst",
// },
// {
// urlPattern: /.+\.(eot|otf|ttf|ttc|woff|woff2)/,
// handler: "CacheFirst",
// },
// // {
// // urlPattern: /.+\.(json|wav|txt|css)/,
// // handler: "NetworkFirst",
// // },
// {
// urlPattern: /.+\.min\.(js|css)/,
// handler: "CacheFirst",
// },
// {
// urlPattern: /.+\..{8}\.(js|css)/,
// handler: "CacheFirst",
// },
{
urlPattern: /\/\/monkeytype.com.+/,
handler: "NetworkFirst",
options: {},
},
// {
// urlPattern: /languages\/.*/,
// handler: "StaleWhileRevalidate",
// },
// {
// urlPattern: /quotes\/.*/,
// handler: "StaleWhileRevalidate",
// },
// {
// urlPattern: /themes\/.*/,
// handler: "StaleWhileRevalidate",
// },
// {
// urlPattern: /challenges\/.*/,
// handler: "StaleWhileRevalidate",
// },
// {
// urlPattern: /layouts\/.*/,
// handler: "StaleWhileRevalidate",
// },
// {
// urlPattern: /sound\/.*\.wav/,
// handler: "StaleWhileRevalidate",
// },
// {
// urlPattern: /images\/.*\.(png|jpg)/,
// handler: "StaleWhileRevalidate",
// },
// {
// urlPattern: /webfonts\/.+/,
// handler: "CacheFirst",
// },
],
}),
new webpack.DefinePlugin({
BACKEND_URL: JSON.stringify("https://api.monkeytype.com"),
IS_DEVELOPMENT: JSON.stringify(false),
}),
],
};
module.exports = merge(BASE_CONFIG, PRODUCTION_CONFIG);

View file

@ -22,17 +22,17 @@
"release": "release-it -c .release-it.json",
"release-fe": "release-it -c .release-it-fe.json",
"hotfix": "cd frontend && npm run deploy-live && cd .. && sh ./bin/purgeCfCache.sh",
"build-fe": "cd ./frontend && npm run build-live",
"build-fe": "cd ./frontend && npm run build",
"pretty": "prettier --check \"./backend/**/*.{ts,json,js,css,html}\" \"./frontend/**/*.{ts,js,scss}\" \"./frontend/static/**/*.{json,html,css}\"",
"pretty-code": "prettier --check \"./backend/**/*.{ts,js,json,css,html}\" \"./frontend/**/*.{ts,js}\" \"./frontend/src/**/*.scss\"",
"pretty-code-be": "prettier --check \"./backend/**/*.{ts,js,json,css,html}\"",
"pretty-code-fe": "prettier --check \"./frontend/**/*.{ts,js}\" \"./frontend/src/**/*.scss\"",
"pretty-fix": "prettier --write \"./backend/**/*.{ts,json,js,css,html}\" \"./frontend/**/*.{ts,js,scss}\" \"./frontend/static/**/*.{json,html,css}\"",
"pr-check-lint-json": "cd frontend && npx gulp pr-check-lint-json",
"pr-check-lint-json": "cd frontend && eslint './static/**/*.json'",
"pr-check-quote-json": "cd frontend && npx gulp pr-check-quote-json",
"pr-check-language-json": "cd frontend && npx gulp pr-check-language-json",
"pr-check-other-json": "cd frontend && npx gulp pr-check-other-json",
"pr-check-ts": "cd frontend && npx gulp pr-check-ts",
"pr-check-build-fe": "cd frontend && npm run build",
"pr-check-build-be": "cd backend && npm run build"
},
"engines": {