Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for a newsletter #2517

Merged
merged 11 commits into from
Jun 19, 2024
9 changes: 9 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ external_sources:
- url: https://blog.google/technology/ai/google-gemini-update-flash-ai-assistant-io-2024/
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
# -----------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions _includes/footer.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
</footer>
{% else %}
<footer class="sticky-bottom mt-5" role="contentinfo">
{% if site.newsletter.enabled %}
{% include scripts/newsletter.liquid %}
{% endif %}

<div class="container">
&copy; Copyright {{ site.time | date: '%Y' }}
{{ site.first_name }}
Expand Down
4 changes: 4 additions & 0 deletions _includes/related_posts.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@
<a class="text-pink-700 underline font-semibold hover:text-pink-800" href="{{ site.baseurl }}{{ post.url }}">{{ post.title }}</a>
</li>
{% 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 %}
176 changes: 176 additions & 0 deletions _includes/scripts/newsletter.liquid
Original file line number Diff line number Diff line change
@@ -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"'
>
&larr; 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>
4 changes: 4 additions & 0 deletions _layouts/about.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,9 @@ layout: default
<div class="contact-note">{{ site.contact_note }}</div>
</div>
{% endif %}

{% if site.newsletter.enabled and site.footer_fixed %}
{% include scripts/newsletter.liquid center=true %}
{% endif %}
</article>
</div>
119 changes: 119 additions & 0 deletions _sass/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1128,13 +1128,127 @@ ninja-keys {
--ninja-modal-background: var(--global-bg-color);
--ninja-z-index: 1031;
}

ninja-keys::part(ninja-input) {
color: var(--ninja-selected-text-color);
}

ninja-keys::part(ninja-input-wrapper) {
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 {
background-color: var(--global-bg-color);
Expand All @@ -1144,25 +1258,30 @@ ninja-keys::part(ninja-input-wrapper) {
color: var(--global-text-color); // Header text color
border-bottom: 1px solid var(--global-divider-color);
}

.popover-body {
color: var(--global-text-color); // Body text color
}
}

.bs-popover-top {
// arrow fill color
.arrow::after {
border-top-color: var(--global-bg-color);
}

// arrow border color
.arrow:before {
border-top-color: var(--global-divider-color);
}
}

.bs-popover-bottom {
// arrow fill color
.arrow::after {
border-bottom-color: var(--global-bg-color);
}

// arrow border color
.arrow:before {
border-bottom-color: var(--global-divider-color);
Expand Down
4 changes: 4 additions & 0 deletions _sass/_themes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
--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-text-color: #{$white-color};
--global-newsletter-bg-color: #{$white-color};
--global-newsletter-text-color: #{$black-color};

--global-tip-block: #42b983;
--global-tip-block-bg: #e2f5ec;
Expand Down Expand Up @@ -81,6 +83,8 @@ html[data-theme="dark"] {
--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-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-bg: #e2f5ec;
Expand Down