mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-04-26 04:45:22 -04:00
finally got myself a real contact form 📬
This commit is contained in:
parent
d4ad28b494
commit
2106b3703d
@ -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
Normal file
98
api/contact.js
Normal file
@ -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;
|
||||
};
|
@ -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;
|
||||
|
||||
|
@ -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
Normal file
79
assets/js/src/contact.js
Normal file
@ -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";
|
||||
});
|
||||
});
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -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
Normal file
111
assets/sass/pages/_contact.scss
Normal file
@ -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() {
|
||||
}
|
@ -80,8 +80,7 @@ disableAliases = true
|
||||
[[menu.main]]
|
||||
name = "Contact"
|
||||
pre = "📬"
|
||||
# encode my email address like it's 2005 ( ͡° ͜ʖ ͡°)
|
||||
url = "mailto:jake@jarv.is"
|
||||
url = "/contact/"
|
||||
weight = 4
|
||||
|
||||
[outputs]
|
||||
|
8
content/contact/index.md
Normal file
8
content/contact/index.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "✉️ Contact Me"
|
||||
url: /contact
|
||||
layout: contact
|
||||
sitemap:
|
||||
changefreq: never
|
||||
priority: 0.0
|
||||
---
|
@ -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
Normal file
29
layouts/_default/contact.html
Normal file
@ -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="mailto:jake@jarv.is">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
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"
|
||||
]
|
||||
},
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
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…
x
Reference in New Issue
Block a user