Chris Padilla/Blog

You can follow by Newsletter or RSS! (What's RSS?) Full archive here.

    Dreamy Fields

    Ba

    My favorite thing about learning to paint is how sunsets are now even more beautiful to look at. 🌅 🐑

    Sending Email From a Next.JS App

    My favorite spot to catch up with writers and artists is from the comfort of my own email inbox. Social media algorithms are fickle and too bite-sized to be sustaining. But sitting down for a couple of minutes to read an email long form is a delight!

    I was curious about implementing the infrastructure for this very site. I don't plan on going full-on email marketer! I am interested, though, in a once or twice a year short note on what's going on in my corner of the internet.

    And, the fun part — It means getting to write some node code to set up an email subscription flow!

    API

    My implementation started as a fork of Pete Houston's Simple Newsletter repo on GitHub. If you want to get started quickly with a project like this, I'd highly recommend taking a look there!

    I'll breeze through, doing a few steps "off-camera first":

    1. Setting Up a MongoDB database to store subscriptions
    2. Writing my Schema for the above.
    3. Importing Pete's mailer.js file for formatting and sending email templates
    4. Integrating the API endpoint in my Next.js app under /pages/api/subscribe.js

    This setup is fairly similar to my DIY Analytics put in place for a separate React project.

    Writing /pages/api/subscribe.js will expose my endpoint at chrisdpadilla.com/api/subscribe. A POST request there will submit data to save the user subscription and send a welcome email.

    Client Flow

    Writing the client flow is just good-ol' React. I'll be using state, submitting a form, and handling the results.

    On my subscribe.js page, I'll start by setting up state for form fields:

    export default function Subscribe() {
      const [firstName, setFirstName] = useState('');
      const [lastName, setLastName] = useState('');
      const [email, setEmail] = useState('');
      const [frequency, setFrequency] = useState('ALL'); 
      const [submitting, setSubmitting] = useState(false);
      const [error, setError] = useState(false);
      const router = useRouter();
      
      ...
      
    }

    I'm also bringing in the useRouter hook to later forward the user to a success page on completion.

    Next, we'll render the form:

    return (
        <Layout>
          <Head>
            <title>Newsletter | Chris Padilla</title>
          </Head>
          <Header />
          <Container>
            <h1>Email Newsletter</h1>
            <p>A quiet set of updates — just a couple a year — on what I'm up to. Unsubscribe anytime.</p>
            <p>Looing to update your subscription? <Link href={`/update-sub`}>Click here</Link>.</p>
                    <form onSubmit={onSubmit}>
              <label htmlFor="firstName">First name:</label><br />
              <input type="text" id="firstName" name="firstName" required value={firstName} onChange={e => setFirstName(e.currentTarget.value)} /><br />
              <label htmlFor="lastName">Last name:</label><br />
              <input type="text" id="lastName" name="lastName" required value={lastName} onChange={e => setLastName(e.currentTarget.value)} /><br />
              <label htmlFor="email">Email:</label><br />
              <input type="email" id="email" name="email" required value={email} onChange={e => setEmail(e.currentTarget.value)} /><br />
      
              <button type="submit" disabled={submitting}>{submitting ? 'Submitting...' : 'Sign Me Up!'}</button>
              {error && (
                <p>Oops! Something went wrong... try refreshing. If all else fails,  <Link href={`/contact`}>
                <a>reach out to me directly.</a>
              </Link>.</p>
              )}
                    </form>
          </Container>
        </Layout>
      );

    Next I'll handle radio elements for emailing frequency. Most input fields are simple enough with one element. Radio elements, however, require special logic to work. Here, I'm setting the value to true only if our state matches the current element. The onChange is then responsible for updating the state to its value.

    <fieldset value={frequency}>
      <legend>How often would you like to receive emails?</legend>
        <input type="radio" id="contactChoice1" name="frequency" value="ALL" checked={frequency === 'ALL'} onChange={e => setFrequency("ALL")}/>
        <label htmlFor="contactChoice1">Sometimes (a few times a year)</label>
    
        <input type="radio" id="contactChoice2" name="frequency" value="SOME" checked={frequency === 'SOME'} onChange={e => setFrequency("SOME")}/>
        <label htmlFor="contactChoice2">Rarely (once a year)</label>
    
        <input type="radio" id="contactChoice3" name="frequency" value="UNSUBSCRIBE" checked={frequency === 'UNSUBSCRIBE'} onChange={e => setFrequency("UNSUBSCRIBE")}/>
        <label htmlFor="contactChoice3">None</label>
      </fieldset><br />

    Now for submitting the form! My submit handler will set the submitting state so the input button won't accidentally be double-clicked.

    Then, I'll gather the values from state. From there, the fetch API is used to send the request. And upon successful submission, the user is then redirected to /subscribed?n={firstName}. The query param is added to customize the message when they land.

    That's all there is to it! There's more work involved with handling updating subscription status and using the templates. If your curious, I'd recommend digging into Pete Houston's Simple Newsletter repo and playing with it yourself!

    Oh, by the way! You can see the working page here!

    Shirō Sagisu – Rei 1

    Listen on Youtube

    From Neon Genesis Evangelion! So mysterious. Who is Rei? What is her DEAL even???

    Campion Trail

    🌳🌳🌳

    Beautiful trail views close to home! 🌳

    Generating Back Links For Optimal Digital Gardening

    I came across Maggie Appleton's tremendous post "A Brief History & Ethos of the Digital Garden"!

    I've heard of the publishing philosophy in passing and found the term itself to resonate. A counter to high-production, corporate leaning purposes for owning a domain name, a digital garden assumes work in progress, a broad spectrum of topics and interests, and an ever evolving space online where ideas and things of beauty can blossom. Lovely!

    There are a few patterns that show up with folks that have taken on the spirit of digital gardening. One that caught my eye was "Topography over Timelines."

    Gardens are organized around contextual relationships and associative links; the concepts and themes within each note determine how it's connected to others.

    This runs counter to the time-based structure of traditional blogs: posts presented in reverse chronological order based on publication date.

    Gardens don't consider publication dates the most important detail of a piece of writing. Dates might be included on posts, but they aren't the structural basis of how you navigate around the garden. Posts are connected to other by posts through related themes, topics, and shared context.

    One of the best ways to do this is through Bi-Directional Links – links that make both the destination page and the source page visible to the reader. This makes it easy to move between related content.

    Because garden notes are densely linked, a garden explorer can enter at any location and follow any trail they link through the content, rather than being dumped into a "most recent” feed.

    Love it! My favorite discoveries are with sites that link well. It's a blast hopping around, continuing the conversation from page to page. Wikis are the prime example of this. Tough, some bloggers like Austin Kleon also do this particularly well.

    So! Why only be bound by linking in one chronological direction? I took to the idea and whipped up a script to employ this myself!

    Developing Bi-Directional Linking

    This site uses markdown for posts. So doing this job is largely about text parsing. Much of the logic, in fact, is similar to how I parse my posts to display an art grid.

    I'll start by writing the function to actually get the url value from my links. The regex is looking for the value with parenthesis in the typical markdown shorthand for links: ![alt text](link)

    // api.js
    
    export const getInternalLinksFromMarkdown = (md) => {
      const regex =
        /(?:__|[*#])|\[(.*?)\]\(\/(.*?)\)/g;
      return Array.from(md.matchAll(regex)).map((res) => {
        if (res && res.length > 1) {
          return res[2];
        }
      });
    };

    The value of index 2 of the array will give me the capture group I've targeted because that's how it's done in Node!

    From here, I'll then pass in my posts and systematically generate an object that grabs both the targeted url as well as the current post url.

    // api.js
    
    export function getAllPostRefs(
      fields = ['content', 'slug', 'title'],
      options = {}
    ) {
      const slugs = getPostSlugs();
      let posts = slugs
        .map((slug) => getPostBySlug(slug, fields))
        // Filter false values (.DS_STORE)
        .filter((post) => post)
        // sort posts by date in descending order
        .sort((post1, post2) => (post1.date > post2.date ? -1 : 1));
    
      const links = {}
      posts.forEach((post) => {
        const postLinks = getInternalLinksFromMarkdown(post.content);
        postLinks.forEach((src) => {
          if (src && !src.includes('/')) {
            if (!links[src]) {
              links[src] = [];
            }
            if (!links[src].find(entry => entry.slug === post.slug))
            links[src].push({
              slug: post.slug,
              title: post.title,
            })
          }
        });
      })
    
      return links;
    }

    A Set data structure would be ideal for keeping duplicates out of the list, but we'll be converting this to JSON, and I'd rather avoid the hassle of bringing in a library for the conversion.

    Finally, I'll call this function and save the results to a JSON file for refference.

    biDirectionalLink.js
    
    import { getAllPostRefs } from "./api"
    const FileSystem = require("fs");
    
    export const getRefs = () => {
        const links = getAllPostRefs();
        FileSystem.writeFile('_cache/backlinks.json', JSON.stringify(links), (error) => {
        if (error) throw error;
      });
    }

    Here's an snippet of what it generates:

    {
        "30": [
            {
                "slug": "2022",
                "title": "2022"
            },
            {
                "slug": "iwataonpeople",
                "title": "Iwata on Creative People"
            },
            {
                "slug": "transcience",
                "title": "Transience"
            },
            {
                "slug": "web2000",
                "title": "A Love Letter to 2000s Websites"
            }
        ],
        "2022": [
            {
                "slug": "testingandwriting",
                "title": "Testing Software for the Same Reason You Write Notes"
            }
        ],
        
        ...
    }

    Voilà! Now I have the data of pages that are referenced. I can now call this method anytime the site regenerates and use this as the source of truth for back-linking.

    To consume this in Next.js, I'm going to read the file in getStaticProps (or in an RSC if I were using the App Router)

    // [slug].js
    
    export async function getStaticProps({ params }) {
      if (post) {
        const file = await fs.readFile(process.cwd() + '/_cache/backlinks.json', 'utf8');
        const backlinks = JSON.parse(file);
        let pagesLinkingBackTo = null;
    
        if (backlinks[params.slug]) {
          pagesLinkingBackTo = backlinks[params.slug];
        }

    And, following some prop drilling, I can now programmatically display these on matching pages:

    // backLinkSection.js
    
    import React from 'react';
    import NextLink from './NextLink';
    
    const BacklinksSection = ({pagesLinkingBackTo}) => {
        if (!pagesLinkingBackTo) return <></>
        return (
            <aside>
                <h4>
                    Pages referencing this post:
                    <ul>
    
                    {pagesLinkingBackTo.map(link => (
                        <li><NextLink href={link.slug}>{link.title}</NextLink> </li>
                    ))}
                    </ul>
                </h4>
            </aside>
        );
    };
    
    export default BacklinksSection;

    Assuming I haven't link to this page yet, you can see this in action at the bottom of my Parsing Mardkown in Node post. Now with handy links to click and explore related topics.

    I'm excited to keep tending the garden! I've already seen themes emerge through the regular tags I use. Here's to a great harvest someday!