mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-20 03:14:06 -06:00
feat:ReplyByEmail button
adds the ability to add a ReplyByEmail button to your notes. Email address and pages to include/exclude can be added via layout file. Base64 encoding is used to *obfuscate* the email address from bots
This commit is contained in:
parent
e98d97a271
commit
b4b22aad94
142
quartz/components/ReplyByEmail.tsx
Normal file
142
quartz/components/ReplyByEmail.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
import { classNames } from "../util/lang"
|
||||
|
||||
interface ReplyByEmailOptions {
|
||||
username?: string
|
||||
domain?: string
|
||||
includeTitles?: string[]
|
||||
excludeTitles?: string[]
|
||||
}
|
||||
|
||||
// Default options will be used if not provided in the layout file
|
||||
const defaultOptions: ReplyByEmailOptions = {
|
||||
username: "ZW1haWw=", // "email" in base64
|
||||
domain: "ZXhhbXBsZS5jb20=", // "email.com" in base64
|
||||
includeTitles: [],
|
||||
excludeTitles: ["Home", "About me", "Contact me"]
|
||||
}
|
||||
|
||||
const ReplyByEmail: QuartzComponent = ({
|
||||
fileData,
|
||||
displayClass,
|
||||
username,
|
||||
domain,
|
||||
includeTitles,
|
||||
excludeTitles
|
||||
}: QuartzComponentProps & ReplyByEmailOptions) => {
|
||||
const title = fileData.frontmatter?.title
|
||||
|
||||
// Use provided values or defaults
|
||||
const encodedPart1 = username || defaultOptions.username
|
||||
const encodedPart2 = domain || defaultOptions.domain
|
||||
const includeList = includeTitles || defaultOptions.includeTitles
|
||||
const excludeList = excludeTitles || defaultOptions.excludeTitles
|
||||
|
||||
// Display logic:
|
||||
// 1. If includeTitles is not empty, only show on those pages
|
||||
// 2. If includeTitles is empty, show on all pages except those in excludeTitles
|
||||
const shouldDisplay = title && (
|
||||
(includeList.length > 0 && includeList.includes(title)) ||
|
||||
(includeList.length === 0 && !excludeList.includes(title))
|
||||
)
|
||||
|
||||
if (shouldDisplay) {
|
||||
return (
|
||||
<div class="center-wrapper">
|
||||
<button
|
||||
class={classNames(displayClass, "reply-by-email-button")}
|
||||
data-username={encodedPart1}
|
||||
data-domain={encodedPart2}
|
||||
data-title={encodeURIComponent(title)}
|
||||
>
|
||||
Reply by email
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
ReplyByEmail.css = `
|
||||
.center-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.reply-by-email-button {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--highlight);
|
||||
color: var(--secondary);
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.reply-by-email-button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
`
|
||||
|
||||
// Script that works with SPA navigation
|
||||
ReplyByEmail.beforeDOMLoaded = `
|
||||
// Function to attach email button handlers
|
||||
function attachEmailHandlers() {
|
||||
document.querySelectorAll('.reply-by-email-button').forEach(function(button) {
|
||||
// Remove existing event listeners first to prevent duplicates
|
||||
button.removeEventListener('click', handleEmailButtonClick);
|
||||
// Add fresh event listener
|
||||
button.addEventListener('click', handleEmailButtonClick);
|
||||
});
|
||||
}
|
||||
|
||||
// Handler function for the email button click
|
||||
function handleEmailButtonClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get data attributes
|
||||
const username = atob(this.getAttribute('data-username'));
|
||||
const domain = atob(this.getAttribute('data-domain'));
|
||||
const title = this.getAttribute('data-title');
|
||||
|
||||
// Create email address and mailto link
|
||||
const email = username + '@' + domain;
|
||||
const mailtoLink = 'mailto:' + email + '?subject=' + title;
|
||||
|
||||
// Open email client
|
||||
window.location.href = mailtoLink;
|
||||
}
|
||||
|
||||
// Initial attachment when the page loads
|
||||
document.addEventListener('DOMContentLoaded', attachEmailHandlers);
|
||||
|
||||
// Re-attach handlers after SPA navigation
|
||||
document.addEventListener('nav', function() {
|
||||
// Small delay to ensure the new buttons are in the DOM
|
||||
setTimeout(attachEmailHandlers, 10);
|
||||
});
|
||||
`
|
||||
|
||||
export default ((opts?: ReplyByEmailOptions) => {
|
||||
// Component constructor that accepts options
|
||||
const component: QuartzComponent = (props) => {
|
||||
return ReplyByEmail({
|
||||
...props,
|
||||
username: opts?.username,
|
||||
domain: opts?.domain,
|
||||
includeTitles: opts?.includeTitles,
|
||||
excludeTitles: opts?.excludeTitles
|
||||
})
|
||||
}
|
||||
|
||||
// Pass through the CSS and beforeDOMLoaded
|
||||
component.css = ReplyByEmail.css
|
||||
component.beforeDOMLoaded = ReplyByEmail.beforeDOMLoaded
|
||||
|
||||
return component
|
||||
}) satisfies QuartzComponentConstructor
|
||||
@ -23,6 +23,7 @@ import Breadcrumbs from "./Breadcrumbs"
|
||||
import Comments from "./Comments"
|
||||
import Flex from "./Flex"
|
||||
import ConditionalRender from "./ConditionalRender"
|
||||
import ReplyByEmail from "./ReplyByEmail"
|
||||
|
||||
export {
|
||||
ArticleTitle,
|
||||
@ -50,4 +51,5 @@ export {
|
||||
Comments,
|
||||
Flex,
|
||||
ConditionalRender,
|
||||
ReplyByEmail,
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user