Added support for a newsletter (#2517)
In reference to idea: https://github.com/alshedivat/al-folio/discussions/2097 In reference to request: https://github.com/alshedivat/al-folio/issues/923#issuecomment-2171924663 Added support to integrate a [loops.so](https://loops.so/) mailing list into the site. To use, you need to enable `newsletter` in `_config.yml`. You also must specify a loops endpoint (although I think any mailing list endpoint can work), which you can get when you set up a mailing list on loops. More documentation on loops: [here](https://loops.so/docs/forms/custom-form). Once that is enabled, the behavior is different depending on how you specified your footer to behave in `_config.yml`. If `footer_fixed: true`, then the sign up will appear at the bottom of the about page, as well as at the bottom of blog posts, if you enable `related_posts`. If `footer_fixed: false`, then the newsletter signup will be in the footer (on every page), like it is in on [my website](https://asboyer.com). I'm not attached to the placement of the signup, and you can choose to include it wherever you want with `{% include scripts/newsletter.liquid %}`. Also if you include positional variables into that, you can choose how you center the signup. So `{% include scripts/newsletter.liquid left=true %}` positions the signup bar to the left. Here are some screenshots below: ## Dark version  ## Light version  I think the input field color should probably change to maybe be light for both themes? What do you think? I think the dark background looks cool, but I don't usually see that done like that on other sites. ## Footer fixed   ## Footer not fixed   To clarify, if footer isn't fixed, the email signup will appear on every page. --------- Co-authored-by: George <31376482+george-gca@users.noreply.github.com>
This commit is contained in:
parent
a25df79188
commit
0ac9e447ca
|
|
@ -174,6 +174,15 @@ external_sources:
|
||||||
- url: https://blog.google/technology/ai/google-gemini-update-flash-ai-assistant-io-2024/
|
- url: https://blog.google/technology/ai/google-gemini-update-flash-ai-assistant-io-2024/
|
||||||
published_date: 2024-05-14
|
published_date: 2024-05-14
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Newsletter
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
newsletter:
|
||||||
|
enabled: false
|
||||||
|
endpoint: # your loops endpoint (e.g., https://app.loops.so/api/newsletter-form/YOUR-ENDPOINT)
|
||||||
|
# https://loops.so/docs/forms/custom-form
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Collections
|
# Collections
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@
|
||||||
</footer>
|
</footer>
|
||||||
{% else %}
|
{% else %}
|
||||||
<footer class="sticky-bottom mt-5" role="contentinfo">
|
<footer class="sticky-bottom mt-5" role="contentinfo">
|
||||||
|
{% if site.newsletter.enabled %}
|
||||||
|
{% include scripts/newsletter.liquid %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
© Copyright {{ site.time | date: '%Y' }}
|
© Copyright {{ site.time | date: '%Y' }}
|
||||||
{{ site.first_name }}
|
{{ site.first_name }}
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,7 @@
|
||||||
<a class="text-pink-700 underline font-semibold hover:text-pink-800" href="{{ site.baseurl }}{{ post.url }}">{{ post.title }}</a>
|
<a class="text-pink-700 underline font-semibold hover:text-pink-800" href="{{ site.baseurl }}{{ post.url }}">{{ post.title }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if site.newsletter.enabled and site.footer_fixed %}
|
||||||
|
<p class="mb-2" style="margin-top: 1.5rem !important">Subscribe to be notified of future articles:</p>
|
||||||
|
{% include scripts/newsletter.liquid left=true %}
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
<div
|
||||||
|
class="newsletter-form-container"
|
||||||
|
{% if include.center %}
|
||||||
|
style="margin: 20px"
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
class="newsletter-form"
|
||||||
|
action="https://app.loops.so/api/newsletter-form/{{ site.newsletter.endpoint }}"
|
||||||
|
method="POST"
|
||||||
|
style="justify-content: {% if include.left %}flex-start{% elsif include.right %}flex-end{% else %}center{% endif %}"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="newsletter-form-input"
|
||||||
|
name="newsletter-form-input"
|
||||||
|
type="email"
|
||||||
|
placeholder="user@example.com"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="newsletter-form-button"
|
||||||
|
style="justify-content: {% if include.left %}flex-start{% elsif include.right %}flex-end{% else %}center{% endif %}"
|
||||||
|
>
|
||||||
|
subscribe
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="newsletter-loading-button"
|
||||||
|
style="justify-content: {% if include.left %}flex-start{% elsif include.right %}flex-end{% else %}center{% endif %}"
|
||||||
|
>
|
||||||
|
Please wait...
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="newsletter-success"
|
||||||
|
style="justify-content: {% if include.left %}flex-start{% elsif include.right %}flex-end{% else %}center{% endif %}"
|
||||||
|
>
|
||||||
|
<p class="newsletter-success-message">You're subscribed!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="newsletter-error"
|
||||||
|
style="justify-content: {% if include.left %}flex-start{% elsif include.right %}flex-end{% else %}center{% endif %}"
|
||||||
|
>
|
||||||
|
<p class="newsletter-error-message">Oops! Something went wrong, please try again</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="newsletter-back-button"
|
||||||
|
type="button"
|
||||||
|
onmouseout='this.style.textDecoration="none"'
|
||||||
|
onmouseover='this.style.textDecoration="underline"'
|
||||||
|
>
|
||||||
|
← Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function submitHandler(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var container = event.target.parentNode;
|
||||||
|
var form = container.querySelector('.newsletter-form');
|
||||||
|
var formInput = container.querySelector('.newsletter-form-input');
|
||||||
|
var success = container.querySelector('.newsletter-success');
|
||||||
|
var errorContainer = container.querySelector('.newsletter-error');
|
||||||
|
var errorMessage = container.querySelector('.newsletter-error-message');
|
||||||
|
var backButton = container.querySelector('.newsletter-back-button');
|
||||||
|
var submitButton = container.querySelector('.newsletter-form-button');
|
||||||
|
var loadingButton = container.querySelector('.newsletter-loading-button');
|
||||||
|
|
||||||
|
const rateLimit = () => {
|
||||||
|
errorContainer.style.display = 'flex';
|
||||||
|
errorMessage.innerText = 'Too many signups, please try again in a little while';
|
||||||
|
submitButton.style.display = 'none';
|
||||||
|
formInput.style.display = 'none';
|
||||||
|
backButton.style.display = 'block';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compare current time with time of previous sign up
|
||||||
|
var time = new Date();
|
||||||
|
var timestamp = time.valueOf();
|
||||||
|
var previousTimestamp = localStorage.getItem('loops-form-timestamp');
|
||||||
|
|
||||||
|
// If last sign up was less than a minute ago
|
||||||
|
// display error
|
||||||
|
if (previousTimestamp && Number(previousTimestamp) + 60000 > timestamp) {
|
||||||
|
rateLimit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localStorage.setItem('loops-form-timestamp', timestamp);
|
||||||
|
|
||||||
|
submitButton.style.display = 'none';
|
||||||
|
loadingButton.style.display = 'flex';
|
||||||
|
|
||||||
|
var formBody = 'userGroup=&email=' + encodeURIComponent(formInput.value);
|
||||||
|
fetch(event.target.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formBody,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => [res.ok, res.json(), res])
|
||||||
|
.then(([ok, dataPromise, res]) => {
|
||||||
|
if (ok) {
|
||||||
|
// If response successful
|
||||||
|
// display success
|
||||||
|
success.style.display = 'flex';
|
||||||
|
form.reset();
|
||||||
|
} else {
|
||||||
|
// If response unsuccessful
|
||||||
|
// display error message or response status
|
||||||
|
dataPromise.then((data) => {
|
||||||
|
errorContainer.style.display = 'flex';
|
||||||
|
errorMessage.innerText = data.message ? data.message : res.statusText;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// check for cloudflare error
|
||||||
|
if (error.message === 'Failed to fetch') {
|
||||||
|
rateLimit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If error caught
|
||||||
|
// display error message if available
|
||||||
|
errorContainer.style.display = 'flex';
|
||||||
|
if (error.message) errorMessage.innerText = error.message;
|
||||||
|
localStorage.setItem('loops-form-timestamp', '');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
formInput.style.display = 'none';
|
||||||
|
loadingButton.style.display = 'none';
|
||||||
|
backButton.style.display = 'block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function resetFormHandler(event) {
|
||||||
|
var container = event.target.parentNode;
|
||||||
|
var formInput = container.querySelector('.newsletter-form-input');
|
||||||
|
var success = container.querySelector('.newsletter-success');
|
||||||
|
var errorContainer = container.querySelector('.newsletter-error');
|
||||||
|
var errorMessage = container.querySelector('.newsletter-error-message');
|
||||||
|
var backButton = container.querySelector('.newsletter-back-button');
|
||||||
|
var submitButton = container.querySelector('.newsletter-form-button');
|
||||||
|
|
||||||
|
success.style.display = 'none';
|
||||||
|
errorContainer.style.display = 'none';
|
||||||
|
errorMessage.innerText = 'Oops! Something went wrong, please try again';
|
||||||
|
backButton.style.display = 'none';
|
||||||
|
formInput.style.display = 'flex';
|
||||||
|
submitButton.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
var formContainers = document.getElementsByClassName('newsletter-form-container');
|
||||||
|
|
||||||
|
for (var i = 0; i < formContainers.length; i++) {
|
||||||
|
var formContainer = formContainers[i];
|
||||||
|
var handlersAdded = formContainer.classList.contains('newsletter-handlers-added');
|
||||||
|
if (handlersAdded) continue;
|
||||||
|
formContainer.querySelector('.newsletter-form').addEventListener('submit', submitHandler);
|
||||||
|
formContainer.querySelector('.newsletter-back-button').addEventListener('click', resetFormHandler);
|
||||||
|
formContainer.classList.add('newsletter-handlers-added');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<style>
|
||||||
|
.newsletter-form-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</noscript>
|
||||||
|
|
@ -72,5 +72,9 @@ layout: default
|
||||||
<div class="contact-note">{{ site.contact_note }}</div>
|
<div class="contact-note">{{ site.contact_note }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if site.newsletter.enabled and site.footer_fixed %}
|
||||||
|
{% include scripts/newsletter.liquid center=true %}
|
||||||
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
119
_sass/_base.scss
119
_sass/_base.scss
|
|
@ -1128,13 +1128,127 @@ ninja-keys {
|
||||||
--ninja-modal-background: var(--global-bg-color);
|
--ninja-modal-background: var(--global-bg-color);
|
||||||
--ninja-z-index: 1031;
|
--ninja-z-index: 1031;
|
||||||
}
|
}
|
||||||
|
|
||||||
ninja-keys::part(ninja-input) {
|
ninja-keys::part(ninja-input) {
|
||||||
color: var(--ninja-selected-text-color);
|
color: var(--ninja-selected-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
ninja-keys::part(ninja-input-wrapper) {
|
ninja-keys::part(ninja-input-wrapper) {
|
||||||
background: var(--global-bg-color);
|
background: var(--global-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newsletter
|
||||||
|
.newsletter-form-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-form-input {
|
||||||
|
color: var(--global-newsletter-text-color);
|
||||||
|
background: var(--global-newsletter-bg-color);
|
||||||
|
border: 1px solid var(--global-newsletter-text-color);
|
||||||
|
outline: none;
|
||||||
|
margin: 0px 10px 0px 0px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 350px;
|
||||||
|
min-width: 100px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-form-input:focus {
|
||||||
|
border-color: var(--global-theme-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-form-button {
|
||||||
|
background: var(--global-theme-color);
|
||||||
|
color: var(--global-bg-color);
|
||||||
|
display: flex;
|
||||||
|
width: min-content;
|
||||||
|
max-width: 200px;
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 38px;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 9px 17px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-loading-button {
|
||||||
|
background: var(--global-theme-color);
|
||||||
|
color: var(--global-bg-color);
|
||||||
|
display: none;
|
||||||
|
width: min-content;
|
||||||
|
max-width: 300px;
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 38px;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 9px 17px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-success {
|
||||||
|
color: var(--global-text-color);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-error {
|
||||||
|
color: var(--global-theme-color);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsletter-back-button {
|
||||||
|
color: var(--global-theme-color);
|
||||||
|
margin: 10px auto;
|
||||||
|
text-align: center;
|
||||||
|
display: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 575px) {
|
||||||
|
.newsletter-form-input,
|
||||||
|
.newsletter-form-button,
|
||||||
|
.newsletter-loading-button,
|
||||||
|
.newsletter-success,
|
||||||
|
.newsletter-error {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
.newsletter-form-container {
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// popover is used for annotation in bib.liquid
|
// popover is used for annotation in bib.liquid
|
||||||
.popover {
|
.popover {
|
||||||
background-color: var(--global-bg-color);
|
background-color: var(--global-bg-color);
|
||||||
|
|
@ -1144,25 +1258,30 @@ ninja-keys::part(ninja-input-wrapper) {
|
||||||
color: var(--global-text-color); // Header text color
|
color: var(--global-text-color); // Header text color
|
||||||
border-bottom: 1px solid var(--global-divider-color);
|
border-bottom: 1px solid var(--global-divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-body {
|
.popover-body {
|
||||||
color: var(--global-text-color); // Body text color
|
color: var(--global-text-color); // Body text color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-popover-top {
|
.bs-popover-top {
|
||||||
// arrow fill color
|
// arrow fill color
|
||||||
.arrow::after {
|
.arrow::after {
|
||||||
border-top-color: var(--global-bg-color);
|
border-top-color: var(--global-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// arrow border color
|
// arrow border color
|
||||||
.arrow:before {
|
.arrow:before {
|
||||||
border-top-color: var(--global-divider-color);
|
border-top-color: var(--global-divider-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-popover-bottom {
|
.bs-popover-bottom {
|
||||||
// arrow fill color
|
// arrow fill color
|
||||||
.arrow::after {
|
.arrow::after {
|
||||||
border-bottom-color: var(--global-bg-color);
|
border-bottom-color: var(--global-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// arrow border color
|
// arrow border color
|
||||||
.arrow:before {
|
.arrow:before {
|
||||||
border-bottom-color: var(--global-divider-color);
|
border-bottom-color: var(--global-divider-color);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
--global-highlight-color: #{$red-color-dark};
|
--global-highlight-color: #{$red-color-dark};
|
||||||
--global-back-to-top-bg-color: rgba(#{red($black-color)}, #{green($black-color)}, #{blue($black-color)}, 0.4);
|
--global-back-to-top-bg-color: rgba(#{red($black-color)}, #{green($black-color)}, #{blue($black-color)}, 0.4);
|
||||||
--global-back-to-top-text-color: #{$white-color};
|
--global-back-to-top-text-color: #{$white-color};
|
||||||
|
--global-newsletter-bg-color: #{$white-color};
|
||||||
|
--global-newsletter-text-color: #{$black-color};
|
||||||
|
|
||||||
--global-tip-block: #42b983;
|
--global-tip-block: #42b983;
|
||||||
--global-tip-block-bg: #e2f5ec;
|
--global-tip-block-bg: #e2f5ec;
|
||||||
|
|
@ -81,6 +83,8 @@ html[data-theme="dark"] {
|
||||||
--global-card-bg-color: #{$grey-900};
|
--global-card-bg-color: #{$grey-900};
|
||||||
--global-back-to-top-bg-color: rgba(#{red($white-color)}, #{green($white-color)}, #{blue($white-color)}, 0.5);
|
--global-back-to-top-bg-color: rgba(#{red($white-color)}, #{green($white-color)}, #{blue($white-color)}, 0.5);
|
||||||
--global-back-to-top-text-color: #{$black-color};
|
--global-back-to-top-text-color: #{$black-color};
|
||||||
|
--global-newsletter-bg-color: #{$grey-color-light};
|
||||||
|
--global-newsletter-text-color: #{$grey-color-dark};
|
||||||
|
|
||||||
--global-tip-block: #42b983;
|
--global-tip-block: #42b983;
|
||||||
--global-tip-block-bg: #e2f5ec;
|
--global-tip-block-bg: #e2f5ec;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue