Browse Source

finally got myself a real contact form 📬

pull/605/head
Jake Jarvis 4 months ago
parent
commit
2106b3703d
Signed by: jake GPG Key ID: 2B0C9CF251E69A39
  1. 4
      .env.example
  2. 98
      api/contact.js
  3. 2
      api/tracks.js
  4. 1
      assets/js/index.js
  5. 79
      assets/js/src/contact.js
  6. 2
      assets/sass/abstracts/_responsive.scss
  7. 4
      assets/sass/abstracts/_themes.scss
  8. 1
      assets/sass/main.scss
  9. 111
      assets/sass/pages/_contact.scss
  10. 3
      config.toml
  11. 8
      content/contact/index.md
  12. 14
      content/privacy/index.md
  13. 29
      layouts/_default/contact.html
  14. 17
      package.json
  15. 6
      static/humans.txt
  16. 2
      vercel.json
  17. 76
      yarn.lock

4
.env.example

@ -12,3 +12,7 @@ LHCI_ADMIN_TOKEN=
LHCI_GITHUB_APP_TOKEN=
SENTRY_AUTH_TOKEN=
SENTRY_DSN=
AIRTABLE_API_KEY=
AIRTABLE_BASE=
HCAPTCHA_SITE_KEY=
HCAPTCHA_SECRET_KEY=

98
api/contact.js

@ -0,0 +1,98 @@
import fetch from "node-fetch";
import queryString from "query-string";
// fallback to dummy secret for testing: https://docs.hcaptcha.com/#integration-testing-test-keys
const HCAPTCHA_SITE_KEY = process.env.HCAPTCHA_SITE_KEY || "10000000-ffff-ffff-ffff-000000000001";
const HCAPTCHA_SECRET_KEY =
process.env.HCAPTCHA_SECRET_KEY || "0x0000000000000000000000000000000000000000";
const HCAPTCHA_API_ENDPOINT = "https://hcaptcha.com/siteverify";
const { AIRTABLE_API_KEY, AIRTABLE_BASE } = process.env;
const AIRTABLE_API_ENDPOINT = `https://api.airtable.com/v0/`;
export default async (req, res) => {
// disable caching on both ends
res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate");
res.setHeader("Expires", 0);
res.setHeader("Pragma", "no-cache");
// permissive access control headers
res.setHeader("Access-Control-Allow-Methods", "POST");
res.setHeader("Access-Control-Allow-Origin", "*");
try {
// some rudimentary error handling
if (req.method !== "POST") {
throw new Error(`Method ${req.method} not allowed.`);
}
if (!AIRTABLE_API_KEY || !AIRTABLE_BASE) {
throw new Error("Airtable API credentials aren't set.");
}
const { body } = req;
// all fields are required
if (!body.name || !body.email || !body.message) {
throw new Error("missingData");
}
// either the captcha is wrong or completely missing
if (!body["h-captcha-response"] || !(await validateCaptcha(body["h-captcha-response"]))) {
throw new Error("invalidCaptcha");
}
// sent directly to airtable
const sendResult = await sendMessage({
Name: body.name,
Email: body.email,
Message: body.message,
});
// throw an internal error, not user's fault
if (sendResult !== true) {
throw new Error("airtableApiError");
}
// return in JSON format
res.status(200).json({ success: true });
} catch (error) {
console.error(error);
const message = error instanceof Error ? error.message : "Unknown error.";
res.status(400).json({ success: false, message: message });
}
};
const validateCaptcha = async (formResponse) => {
const response = await fetch(HCAPTCHA_API_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: queryString.stringify({
response: formResponse,
sitekey: HCAPTCHA_SITE_KEY,
secret: HCAPTCHA_SECRET_KEY,
}),
});
const result = await response.json();
return result.success;
};
const sendMessage = async (data) => {
const response = await fetch(`${AIRTABLE_API_ENDPOINT}${AIRTABLE_BASE}/Messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${AIRTABLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
fields: data,
}),
});
return response.ok;
};

2
api/tracks.js

@ -3,7 +3,7 @@
import * as Sentry from "@sentry/node";
import fetch from "node-fetch";
import * as queryString from "query-string";
import queryString from "query-string";
const { SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REFRESH_TOKEN } = process.env;

1
assets/js/index.js

@ -3,6 +3,7 @@ import "./src/emoji.js";
import "./src/counter.js";
import "./src/clipboard.js";
import "./src/anchor.js";
import "./src/contact.js";
import "./src/projects.js";
export default () => {};

79
assets/js/src/contact.js

@ -0,0 +1,79 @@
import "vanilla-hcaptcha";
import fetch from "cross-fetch";
// don't continue if there isn't a contact form on this page
// TODO: be better and only do any of this on /contact/
const contactForm = document.getElementById("contact-form");
if (contactForm) {
contactForm.addEventListener("submit", (event) => {
// immediately prevent <form> from actually submitting to a new page
event.preventDefault();
const submitButton = document.getElementById("contact-form-btn-submit");
// disable the whole form if the button has been disabled below (on success)
if (submitButton.disabled === true) {
return;
}
// change button appearance between click and server response
submitButton.innerText = "Sending...";
submitButton.disabled = true; // prevent accidental multiple submissions
submitButton.style.cursor = "default";
// feedback <span>s for later
const successSpan = document.getElementById("contact-form-result-success");
const errorSpan = document.getElementById("contact-form-result-error");
// https://simonplend.com/how-to-use-fetch-to-post-form-data-as-json-to-your-api/
const formData = Object.fromEntries(new FormData(event.currentTarget).entries());
fetch(contactForm.action, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(formData),
})
.then((response) => response.json())
.then((data) => {
if (data.success === true) {
// handle successful submission
// we can disable submissions & hide the send button now
submitButton.disabled = true;
submitButton.style.display = "none";
// just in case there *was* a PEBCAK error and it was corrected
errorSpan.style.display = "none";
// let user know we were successful
successSpan.innerText = "Success! You should hear from me soon. :)";
} else {
// pass on an error sent by the server
throw new Error(data.message);
}
})
.catch((error) => {
const message = error instanceof Error ? error.message : "Unknown";
// give user feedback based on the error message returned
if (message === "invalidCaptcha") {
errorSpan.innerText = "Error: Did you remember to click the CAPTCHA?";
} else if (message === "invalidEmail") {
errorSpan.innerText = "Error: Please double check your email address.";
} else if (message === "missingData") {
errorSpan.innerText = "Error: Please make sure that all fields are filled in.";
} else {
// something else went wrong, and it's probably my fault...
errorSpan.innerText = "Internal server error. Try again later?";
}
// reset submit button to let user try again
submitButton.innerText = "Try Again";
submitButton.disabled = false;
submitButton.style.cursor = "pointer";
});
});
}

2
assets/sass/abstracts/_responsive.scss

@ -11,6 +11,7 @@
@use "../pages/videos";
@use "../pages/etc";
@use "../pages/projects";
@use "../pages/contact";
@use "../pages/fourOhFour";
// Responsive Awesomeness
@ -26,5 +27,6 @@
@include videos.responsive();
@include etc.responsive();
@include projects.responsive();
@include contact.responsive();
@include fourOhFour.responsive();
}

4
assets/sass/abstracts/_themes.scss

@ -61,6 +61,8 @@ $themes: (
super-light: #f4f4f4,
super-duper-light: #fbfbfb,
links: #0e6dc2,
success: #0b890f,
error: #ff1b1b,
),
dark: (
background-inner: #1e1e1e,
@ -74,6 +76,8 @@ $themes: (
super-light: #272727,
super-duper-light: #1f1f1f,
links: #88c7ff,
success: #78df55,
error: #f54545,
),
);

1
assets/sass/main.scss

@ -16,6 +16,7 @@
@use "pages/videos";
@use "pages/etc";
@use "pages/projects";
@use "pages/contact";
@use "pages/fourOhFour";
// Miscellaneous

111
assets/sass/pages/_contact.scss

@ -0,0 +1,111 @@
@use "../abstracts/themes";
// Contact Styles
div.layout-contact {
max-width: 600px;
padding: 1.5em 0;
h1 {
margin-bottom: 0.4em;
text-align: center;
}
p {
font-size: 0.9em;
margin-bottom: 0.5em;
}
input[type="text"],
input[type="email"],
select,
textarea {
width: 100%;
padding: 0.8em;
margin: 0.6em 0;
border: 1px solid;
border-radius: 0.3em;
font-size: 0.9em;
@include themes.themed(color, "text");
@include themes.themed(background-color, "super-duper-light");
@include themes.themed(border-color, "light");
}
textarea {
height: 10em;
margin-bottom: 0;
// allow vertical resizing & disable horizontal
resize: vertical; // stylelint-disable-line plugin/no-unsupported-browser-features
}
div#contact-form-action-row {
display: flex;
align-items: center;
button {
padding: 0.8em 1.2em;
margin-right: 1.5em;
border: 0;
border-radius: 0.3em;
cursor: pointer;
line-height: 1.5;
user-select: none;
@include themes.themed(color, "text");
@include themes.themed(background-color, "kinda-light");
&:hover,
&:focus {
@include themes.themed(color, "super-duper-light");
@include themes.themed(background-color, "links");
}
img.emoji {
margin-right: 0.4em;
cursor: inherit;
}
}
span {
font-size: 0.9em;
font-weight: 600;
&#contact-form-result-success {
@include themes.themed(color, "success");
}
&#contact-form-result-error {
@include themes.themed(color, "error");
}
}
}
// hcaptcha widget
#contact-form-captcha {
display: block;
margin: 1.2em 0;
}
span#contact-form-md-info {
display: block;
font-size: 0.75em;
margin-top: 0.25em;
margin-left: 0.75em;
a {
// disable fancy underline without `.no-underline`
background: none !important;
padding: 0;
&:first-of-type {
font-weight: 500;
}
}
}
}
// Responsive
// stylelint-disable-next-line block-no-empty
@mixin responsive() {
}

3
config.toml

@ -80,8 +80,7 @@ disableAliases = true
[[menu.main]]
name = "Contact"
pre = "📬"
# encode my email address like it's 2005 ( ͡° ͜ʖ ͡°)
url = "&#x6D;&#x61;&#x69;&#x6C;&#x74;&#x6F;&#x3A;&#x6A;&#x61;&#x6B;&#x65;&#x40;&#x6A;&#x61;&#x72;&#x76;&#x2E;&#x69;&#x73;"
url = "/contact/"
weight = 4
[outputs]

8
content/contact/index.md

@ -0,0 +1,8 @@
---
title: "✉️ Contact Me"
url: /contact
layout: contact
sitemap:
changefreq: never
priority: 0.0
---

14
content/privacy/index.md

@ -9,7 +9,7 @@ sitemap:
Okay, this is an easy one. 😉
## Analytics
## Analytics {#analytics}
A simple hit counter on each page tallies an aggregate number of pageviews (i.e. `hits = hits + 1`). Individual views and identifying (or non-identifying) details are **never stored or logged**.
@ -17,7 +17,7 @@ The [serverless function](https://github.com/jakejarvis/jarv.is/blob/main/api/hi
{{< image src="images/fauna_hits.png" alt="The entire database schema." link="/privacy/images/fauna_hits.png" />}}
## Hosting
## Hosting {#hosting}
Pages and first-party assets on this website are served by [**▲ Vercel**](https://vercel.com/). Refer to their [privacy policy](https://vercel.com/legal/privacy-policy) for more information.
@ -25,7 +25,7 @@ For a likely excessive level of privacy and security, this website is also mirro
> [**jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion**](http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion)
## Third-Party Content
## Third-Party Content {#third-party}
Occasionally, embedded content from third-party services is included in posts, and some may contain tracking code that is outside of my control. Please refer to their privacy policies for more information:
@ -38,3 +38,11 @@ Occasionally, embedded content from third-party services is included in posts, a
- [YouTube](https://policies.google.com/privacy)
The code that requests this content [is open source](https://github.com/jakejarvis/jarv.is/tree/main/layouts/shortcodes).
## Fighting Spam {#hcaptcha}
Using [**hCaptcha**](https://www.hcaptcha.com/) to fight bot spam on the [contact form](/contact/) was an easy choice over seemingly unavoidable alternatives like [reCAPTCHA](https://developers.google.com/recaptcha/).
You can refer to hCaptcha's [privacy policy](https://www.hcaptcha.com/privacy) and [terms of service](https://www.hcaptcha.com/terms) for more details. While some information is sent to the hCaptcha API about your behavior **(on the contact page only)**, at least you won't be helping a certain internet conglomerate [train their self-driving cars](https://blog.cloudflare.com/moving-from-recaptcha-to-hcaptcha/). 🚗
I also enabled the setting to donate 100% of my [HMT token](https://humanprotocol.org/?lng=en-US) earnings to the [Wikimedia Foundation](https://www.wikimedia.org/), for what it's worth. (A few cents, probably... 💰)

29
layouts/_default/contact.html

@ -0,0 +1,29 @@
{{ define "main" }}
<div class="layout layout-contact">
<h1><a href="{{ .Permalink }}">{{ .Title | markdownify }}</a></h1>
<p>Fill out this quick form and I'll get back to you as soon as I can! You can also <a href="&#x6D;&#x61;&#x69;&#x6C;&#x74;&#x6F;&#x3A;&#x6A;&#x61;&#x6B;&#x65;&#x40;&#x6A;&#x61;&#x72;&#x76;&#x2E;&#x69;&#x73;">email me directly</a>, send me a <a href="https://twitter.com/messages/compose?recipient_id=229769022" target="_blank" rel="noopener nofollow">direct message on Twitter</a>, or <a href="sms:+1-617-917-3737">text me</a>.</p>
<form id="contact-form" action="/api/contact/" method="POST">
<input type="text" id="name" name="name" placeholder="Name">
<input type="email" id="email" name="email" placeholder="Email">
<textarea id="message" name="message" placeholder="Write something..."></textarea>
<span id="contact-form-md-info">Basic <a href="https://commonmark.org/help/" title="Markdown reference sheet" target="_blank" rel="noopener">Markdown syntax</a> is allowed here, e.g.: <strong>**bold**</strong>, <em>_italics_</em>, [<a href="https://jarv.is" target="_blank" rel="noopener">links</a>](https://jarv.is), and <code>`code`</code>.</span>
<h-captcha
id="contact-form-captcha"
site-key="{{ getenv "HCAPTCHA_SITE_KEY" | default "10000000-ffff-ffff-ffff-000000000001" }}"
size="normal"
tabindex="0"></h-captcha>
<div id="contact-form-action-row">
<button title="Submit" aria-label="Submit" id="contact-form-btn-submit">📤 Send</button>
<div id="contact-form-result">
<span id="contact-form-result-error"></span>
<span id="contact-form-result-success"></span>
</div>
</div>
</form>
</div>
{{ end }}

17
package.json

@ -40,7 +40,7 @@
"faunadb": "^4.4.1",
"get-canonical-url": "^1.0.1",
"graphql": "^15.6.1",
"graphql-request": "^3.5.0",
"graphql-request": "^3.6.0",
"graphql-tag": "^2.12.5",
"html-entities": "^2.3.2",
"lit-html": "^2.0.1",
@ -52,7 +52,8 @@
"trim-newlines": "^4.0.2",
"twemoji": "^13.1.0",
"twemoji-emojis": "^14.1.0",
"url-parse": "^1.5.3"
"url-parse": "^1.5.3",
"vanilla-hcaptcha": "^1.0.0-alpha"
},
"devDependencies": {
"@babel/core": "^7.15.8",
@ -72,7 +73,7 @@
"css-minimizer-webpack-plugin": "^3.1.1",
"del": "^6.0.0",
"dotenv": "^10.0.0",
"eslint": "~8.0.0",
"eslint": "~8.0.1",
"eslint-config-prettier": "~8.3.0",
"eslint-plugin-compat": "~3.13.0",
"eslint-plugin-import": "~2.25.2",
@ -94,13 +95,13 @@
"postcss-combine-duplicated-selectors": "^10.0.3",
"postcss-discard-duplicates": "^5.0.1",
"postcss-focus": "^5.0.1",
"postcss-loader": "^6.1.1",
"postcss-loader": "^6.2.0",
"postcss-merge-rules": "^5.0.2",
"postcss-normalize-charset": "^5.0.1",
"postcss-reporter": "^7.0.3",
"postcss-reporter": "^7.0.4",
"postcss-svgo": "^5.0.2",
"prettier": "^2.4.1",
"sass": "^1.42.1",
"sass": "^1.43.2",
"sass-loader": "^12.2.0",
"simple-git-hooks": "^2.6.1",
"stylelint": "~13.13.1",
@ -111,7 +112,7 @@
"stylelint-scss": "~3.21.0",
"terser": "^5.9.0",
"terser-webpack-plugin": "^5.2.4",
"webpack": "^5.58.1",
"webpack": "^5.58.2",
"webpack-assets-manifest": "^5.0.6",
"webpack-cli": "^4.9.0",
"webpack-dev-server": "^4.3.1"
@ -135,7 +136,7 @@
"prettier --check"
],
"*.md": [
"markdownlint",
"markdownlint --config .markdownlintrc.json",
"prettier --check"
]
},

6
static/humans.txt

@ -31,9 +31,9 @@
- Hugo
- Vercel
- Webpack
- Gulp
- FaunaDB
- Airtable
- hCaptcha
- Percy
- Sentry
- ...and more: https://jarv.is/uses/
@ -48,7 +48,7 @@
# TOR MIRROR
http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion
http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion/
# LICENSE

2
vercel.json

@ -92,7 +92,7 @@
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; base-uri 'none'; connect-src 'self' api.github.com platform.twitter.com; font-src 'self'; form-action 'none'; frame-ancestors 'self'; frame-src 'self' jakejarvis.github.io buttons.github.io codepen.io platform.twitter.com www.youtube-nocookie.com; img-src 'self' data: https:; manifest-src 'self'; media-src 'self' data: https:; object-src 'none'; prefetch-src 'self'; script-src 'self' buttons.github.io gist.github.com syndication.twitter.com platform.twitter.com 'sha256-y3Xr/40/KQnUvqk/kZO5us6t3i/I49BsbYjsH8ELhVg='; style-src 'self' 'unsafe-inline' github.githubassets.com; worker-src 'self'; block-all-mixed-content; report-uri https://jarv.is/api/csp_wizard/; report-to default"
"value": "default-src 'self'; base-uri 'none'; connect-src 'self' api.github.com hcaptcha.com *.hcaptcha.com platform.twitter.com; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self' jakejarvis.github.io buttons.github.io codepen.io hcaptcha.com *.hcaptcha.com platform.twitter.com www.youtube-nocookie.com; img-src 'self' data: https:; manifest-src 'self'; media-src 'self' data: https:; object-src 'none'; prefetch-src 'self'; script-src 'self' buttons.github.io gist.github.com hcaptcha.com *.hcaptcha.com syndication.twitter.com platform.twitter.com 'sha256-y3Xr/40/KQnUvqk/kZO5us6t3i/I49BsbYjsH8ELhVg='; style-src 'self' 'unsafe-inline' github.githubassets.com; worker-src 'self'; block-all-mixed-content; report-uri https://jarv.is/api/csp_wizard/; report-to default"
},
{
"key": "Report-To",

76
yarn.lock

@ -882,10 +882,10 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
"@eslint/eslintrc@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.2.tgz#6044884f7f93c4ecc2d1694c7486cce91ef8f746"
integrity sha512-x1ZXdEFsvTcnbTZgqcWUL9w2ybgZCw/qbKTPQnab+XnYA2bMQpJCh+/bBzCRfDJaJdlrrQlOk49jNtru9gL/6Q==
"@eslint/eslintrc@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.3.tgz#41f08c597025605f672251dcc4e8be66b5ed7366"
integrity sha512-DHI1wDPoKCBPoLZA3qDR91+3te/wDSc1YhKg3jR8NxKKRJq2hwHwcWv31cSwSYvIBrmbENoYMWcenW8uproQqg==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
@ -1225,9 +1225,9 @@
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
"@types/node@*":
version "16.10.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.7.tgz#6e2b582cb0416b3b1cc3ff884cf4d2418bb87bdb"
integrity sha512-rySHHlZYHNydt9yRm7AhmGAivzxL1M/fdUzMrt2rhl0yLJJLYdamh6Asl3bFzzcJ0r+pEwYrx9KnHiY4CDiXeQ==
version "16.10.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.9.tgz#8f1cdd517972f76a3b928298f4c0747cd6fef25a"
integrity sha512-H9ReOt+yqIJPCutkTYjFjlyK6WEMQYT9hLZMlWtOjFQY2ItppsWZ6RJf8Aw+jz5qTYceuHvFgPIaKOHtLAEWBw==
"@types/normalize-package-data@^2.4.0", "@types/normalize-package-data@^2.4.1":
version "2.4.1"
@ -2315,9 +2315,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001179, caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001264, caniuse-lite@^1.0.30001265:
version "1.0.30001265"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3"
integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==
version "1.0.30001267"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001267.tgz#b1cf2937175afc0570e4615fc2d2f9069fa0ed30"
integrity sha512-r1mjTzAuJ9W8cPBGbbus8E0SKcUP7gn03R14Wk8FlAlqhH9hroy9nLqmpuXlfKEw/oILW+FGz47ipXV2O7x8lg==
careful-downloader@^1.4.0:
version "1.4.0"
@ -3480,9 +3480,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.867:
version "1.3.867"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.867.tgz#7cb484db4b57c28da0b65c51e434c3a1f3f9aa0d"
integrity sha512-WbTXOv7hsLhjJyl7jBfDkioaY++iVVZomZ4dU6TMe/SzucV6mUAs2VZn/AehBwuZMiNEQDaPuTGn22YK5o+aDw==
version "1.3.868"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.868.tgz#ed835023b57ecf0ba63dfe7d50e16b53758ab1da"
integrity sha512-kZYCHqwJ1ctGrYDlOcWQH+/AftAm/KD4lEnLDNwS0kKwx1x6dU4zv+GuDjsPPOGn/2TjnKBaZjDyjXaoix0q/A==
emoji-regex@^8.0.0:
version "8.0.0"
@ -3658,9 +3658,9 @@ eslint-import-resolver-node@^0.3.6:
resolve "^1.20.0"
eslint-module-utils@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.0.tgz#9e97c12688113401259b39d960e6a1f09f966435"
integrity sha512-hqSE88MmHl3ru9SYvDyGrlo0JwROlf9fiEMplEV7j/EAuq9iSlIlyCFbBT6pdULQBSnBYtYKiMLps+hKkyP7Gg==
version "2.7.1"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c"
integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==
dependencies:
debug "^3.2.7"
find-up "^2.1.0"
@ -3748,12 +3748,12 @@ eslint-visitor-keys@^3.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186"
integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==
eslint@~8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.0.0.tgz#2c2d0ac6353755667ac90c9ff4a9c1315e43fcff"
integrity sha512-03spzPzMAO4pElm44m60Nj08nYonPGQXmw6Ceai/S4QK82IgwWO1EXx1s9namKzVlbVu3Jf81hb+N+8+v21/HQ==
eslint@~8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.0.1.tgz#3610e7fe4a05c2154669515ca60835a76a19f700"
integrity sha512-LsgcwZgQ72vZ+SMp4K6pAnk2yFDWL7Ti4pJaRvsZ0Hsw2h8ZjUIW38a9AFn2cZXdBMlScMFYYgsSp4ttFI/0bA==
dependencies:
"@eslint/eslintrc" "^1.0.2"
"@eslint/eslintrc" "^1.0.3"
"@humanwhocodes/config-array" "^0.6.0"
ajv "^6.10.0"
chalk "^4.0.0"
@ -4618,14 +4618,13 @@ get-value@^2.0.3, get-value@^2.0.6:
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
gifsicle@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/gifsicle/-/gifsicle-5.2.0.tgz#b06b25ed7530f033f6ed2c545d6f9b546cc182fb"
integrity sha512-vOIS3j0XoTCxq9pkGj43gEix82RkI5FveNgaFZutjbaui/HH+4fR8Y56dwXDuxYo8hR4xOo6/j2h1WHoQW6XLw==
version "5.2.1"
resolved "https://registry.yarnpkg.com/gifsicle/-/gifsicle-5.2.1.tgz#efadab266a493ef0b4178e34597493349937369e"
integrity sha512-9ewIQQCAnSmkU2DhuWafd1DdsgzAkKqIWnY+023xBLSiK9Az2TDUozWQW+SyRQgFMclbe6RQldUk/49TRO3Aqw==
dependencies:
bin-build "^3.0.0"
bin-wrapper "^4.0.0"
execa "^5.0.0"
logalot "^2.0.0"
glob-parent@^3.1.0:
version "3.1.0"
@ -4858,7 +4857,7 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graphql-request@^3.5.0:
graphql-request@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.6.0.tgz#8f2be900cf07ac87064b15601cc30e0f9a5e7d51"
integrity sha512-p5qIuD+gyjuOJ8z9sEcfcLVK7HUB+/88hf/xGEzX330U3L2OR1JtaupLPmd1D2V7YtqWiEnSA3tX9vqZ4eGMhA==
@ -6955,9 +6954,9 @@ nanocolors@^0.1.12:
integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
nanoid@^3.1.28:
version "3.1.29"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.29.tgz#214fb2d7a33e1a5bef4757b779dfaeb6a4e5aeb4"
integrity sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg==
version "3.1.30"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362"
integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==
nanomatch@^1.2.9:
version "1.2.13"
@ -7896,7 +7895,7 @@ postcss-less@^3.1.4:
dependencies:
postcss "^7.0.14"
postcss-loader@^6.1.1:
postcss-loader@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.0.tgz#714370a3f567141cf4cadcdf9575f5234d186bc5"
integrity sha512-H9hv447QjQJVDbHj3OUdciyAXY3v5+UDduzEytAlZCVHCpNAAg/mCSwhYYqZr9BiGYhmYspU8QXxZwiHTLn3yA==
@ -8084,7 +8083,7 @@ postcss-reduce-transforms@^5.0.1:
cssnano-utils "^2.0.1"
postcss-value-parser "^4.1.0"
postcss-reporter@^7.0.3:
postcss-reporter@^7.0.4:
version "7.0.4"
resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-7.0.4.tgz#640de7ef30fa89374bc0d5029c307ad2ecda25c3"
integrity sha512-jY/fnpGSin7kwJeunXbY35STp5O3VIxSFdjee5JkoPQ+FfGH5JW3N+Xe9oAPcL9UkjWjkK+JC72o8XH4XXKdhw==
@ -8873,10 +8872,10 @@ sass-loader@^12.2.0:
klona "^2.0.4"
neo-async "^2.6.2"
sass@^1.42.1:
version "1.42.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.42.1.tgz#5ab17bebc1cb1881ad2e0c9a932c66ad64e441e2"
integrity sha512-/zvGoN8B7dspKc5mC6HlaygyCBRvnyzzgD5khiaCfglWztY99cYoiTUksVx11NlnemrcfH5CEaCpsUKoW0cQqg==
sass@^1.43.2:
version "1.43.2"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.2.tgz#c02501520c624ad6622529a8b3724eb08da82d65"
integrity sha512-DncYhjl3wBaPMMJR0kIUaH3sF536rVrOcqqVGmTZHQRRzj7LQlyGV7Mb8aCKFyILMr5VsPHwRYtyKpnKYlmQSQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
@ -10371,6 +10370,11 @@ value-or-function@^3.0.0:
resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813"
integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=
vanilla-hcaptcha@^1.0.0-alpha:
version "1.0.0-alpha"
resolved "https://registry.yarnpkg.com/vanilla-hcaptcha/-/vanilla-hcaptcha-1.0.0-alpha.tgz#e929320ffc935afd6110056d1fb8cab136a785b3"
integrity sha512-KG+g94vpZVBQq9NItoG02HyO2RJ0BWzh6Qu9e46Bb2P7dS/obGcAq4YKiFWcs8hTBH71OXl6gQOlAEOAr0LByw==
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@ -10559,7 +10563,7 @@ webpack-sources@^3.2.0:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.1.tgz#251a7d9720d75ada1469ca07dbb62f3641a05b6d"
integrity sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA==
webpack@^5.58.1:
webpack@^5.58.2:
version "5.58.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.58.2.tgz#6b4af12fc9bd5cbedc00dc0a2fc2b9592db16b44"
integrity sha512-3S6e9Vo1W2ijk4F4PPWRIu6D/uGgqaPmqw+av3W3jLDujuNkdxX5h5c+RQ6GkjVR+WwIPOfgY8av+j5j4tMqJw==

Loading…
Cancel
Save