Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions ai/agents/import-notion-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# import-notion-post

Converts a Notion page into a production-ready blog post for `ajeetchaulagain.com`.

---

## Prerequisites

`NOTION_TOKEN` must be set in `.env.local`. The Notion page must be shared with the integration.

---

## How to run

Say **"execute with \<notion-page-url\>"** to target a page directly, or **"execute"** to be prompted for a URL.

---

## Execution steps

1. **Fetch the draft** — read `NOTION_TOKEN` from `.env.local`, then call the Notion API to fetch the page content:

```bash
# Get page title
curl -s "https://api.notion.com/v1/pages/<page-id>" \
-H "Authorization: Bearer <token>" \
-H "Notion-Version: 2022-06-28"

# Get page blocks (paginate with start_cursor if has_more=true)
curl -s "https://api.notion.com/v1/blocks/<page-id>/children?page_size=100" \
-H "Authorization: Bearer <token>" \
-H "Notion-Version: 2022-06-28"
```

Extract the page ID from the URL (last 32-char hex segment). Convert the block JSON to markdown yourself — handle `paragraph`, `heading_1/2/3`, `bulleted_list_item`, `numbered_list_item`, `code`, `callout`, `image`, and `quote` block types.

2. **Read the reference** — read `src/posts/react-image-gallery.md` for the canonical frontmatter and formatting style.
3. **Convert** — apply all conversion rules below, in order.
4. **Proofread** — fix grammar and spelling silently (see [Proofreading](#proofreading)).
5. **Copy images** — move draft images to `src/images/<post-slug>/` with descriptive names.
6. **Save** — write to `src/posts/<post-slug>.md`, overwriting any existing file.
7. **Verify** — run `yarn build` and confirm it passes.

---

## Conversion rules

### Frontmatter

Remove all Notion metadata at the top (`Assign:`, `Status:`, `Deadline:`).

Use the top-level `# H1` as the post title — put it in frontmatter, remove it from the body.

```yaml
---
title: '<post title from H1>'
description: '<one sentence: what the reader will learn>'
date: '<YYYY-MM-DD>'
tags: ['tag1', 'tag2']
thumbnail:
{ src: ../images/thumbnails/<slug>.png, altText: '<descriptive alt text>' }
author: 'Ajeet Chaulagain'
---
```

> If no matching thumbnail exists, use `image-gallery-icon.png` as a placeholder.

---

### Preserve the author's formatting

Your job is **format conversion, not editing**. Copy the author's words exactly.

| If the draft has… | Output… |
| ------------------------------------------------ | ------------------------------------------------ |
| A bullet list | A bullet list — never collapse to prose |
| A numbered list | A numbered list — never reorder |
| A paragraph followed by bullets | That exact structure |
| Two near-identical paragraphs | Both — they are intentional |
| A byte-for-byte duplicate paragraph | Only one — Notion export artifact |
| A duplicate intro sentence before unique content | Better-worded version + all content that follows |

**The only words you may change:**

- Broken Notion link syntax (see [Content cleanup](#content-cleanup))
- Unfilled placeholders (`<repo.url>`, `[Video will be attached here]`)
- Notion metadata lines

---

### No imports

Do **not** add import statements. All components (`InfoCallToAction`, etc.) are registered globally via the MDX provider.

---

### Code blocks

Notion exports everything as `tsx` or `jsx`. Reclassify each block:

| Content | Language tag |
| ---------------------------- | ------------- |
| Shell commands | `bash` |
| TypeScript | `ts` |
| JSON | `json` |
| YAML | `yaml` |
| JSX / TSX | `jsx` / `tsx` |
| Output, file trees, diagrams | `bash:noCopy` |

**Key files and configs** — add title and line highlight annotations:

````
```ts:title=src/lambda.ts {20-37}{numberLines:true}
````

**Directory structures and terminal output** — disable the copy button:

````
```bash:title=Directory_Structure&noCopy
````

Remove `$ ` prefixes from shell commands.

---

### Callout / aside blocks

Convert Notion `<aside>` blocks to `<InfoCallToAction>` using markdown children (blank lines inside the tags trigger MDX markdown parsing):

```md
<InfoCallToAction>

Text here with **bold**, `inline code`, and [links](url).

A second paragraph if needed.

</InfoCallToAction>
```

---

### Images

- Copy to `src/images/<post-slug>/` with a descriptive name (`cdk-deploy-output.png`, not `image 3.png`)
- Reference as: `![Descriptive alt text](../images/<post-slug>/filename.png)`
- Remove Notion's URL-encoded syntax: `![image.png](Folder%20Name/image.png)`

---

### Content cleanup

Fix Notion export artifacts only — do not touch the author's prose:

- **Broken links** — `[NestJS](<[https://docs.nestjs.com/](https://docs.nestjs.com/)>)` → `[NestJS](https://docs.nestjs.com/)`
- **Placeholders** — remove `<repo.url>`, `[Video will be attached here]`, etc.
- **Duplicate paragraphs** — remove byte-for-byte duplicates; keep near-duplicates
- **List indentation** — fix Notion's broken nesting, do not change the content
- **Malformed headings** — e.g. `### ### Writing CDK Stack` → `### Writing CDK Stack`
- **Instructional notes** — remove Notion-internal notes like `(selected line → 3-6, 12-37)`
- **Emoji flow diagrams / file trees** — keep as-is in a `bash:noCopy` block

---

### Section headings

- `##` — main sections
- `###` — subsections
- `####` — sub-subsections
- Never add a `##` that repeats the post title

---

## Proofreading

After conversion, fix grammar and spelling silently — no changelog, no summary.

**Fix:**

- Spelling mistakes and typos
- Subject-verb agreement and tense consistency
- Missing or incorrect punctuation

**Do not touch:**

- Word choice, tone, or style
- Technical terms, package names, CLI flags, code references
- Run-on sentences that are clear and intentional

---

## Output location

`src/posts/<post-slug>.md`

Match the `notion-markdown-posts/` subdirectory name, shortened if sensible (e.g. `deploy-nestjs-app-to-aws-lambda-using-aws-cdk` → `nestjs-aws-serverless`). If a file already exists for the same post, use that filename.
13 changes: 8 additions & 5 deletions src/components/about-jumbotron/AboutJumbotron.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ export const AboutJumbotron = (): JSX.Element => {
</ImageWrapper>
<TextContentWrapper>
<StyledParagraph>
Hi, I am Ajeet, a pragmatic software engineer based in Melbourne,
Australia. I write an article about
<em> modern software development and my side projects</em>. If this
site has helped you somehow to learn, I would be grateful if you
consider supporting me.
Hey — I'm Ajeet, a Software Engineer with commercial experience
building and maintaining production systems. I write about
<em>
{' '}
modern software development, side projects, and things I find
interesting along the way
</em>
. If something here saved you time, a coffee is always welcome 😊
</StyledParagraph>
<ButtonLink
to="https://ko-fi.com/ajeetchaulagain"
Expand Down
2 changes: 1 addition & 1 deletion src/components/button-link/PropTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ButtonLinkMarkdownType } from 'markdown-types';

export type ButtonVariant = 'text' | 'contained' | 'outlined';
export type ButtonSize = 'small' | 'medium' | 'large';
export type ButtonColor = 'primary' | 'secondary';
export type ButtonColor = 'primary';

type MakeRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

Expand Down
12 changes: 7 additions & 5 deletions src/components/design-system/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const HeroEyebrow = styled.span`
display: block;
font-family: 'Fira Code', monospace;
font-size: ${({ theme }) => theme.fontSizes.xsmall};
color: ${({ theme }) => theme.colors.heroParagraph};
color: ${({ theme }) => theme.colors.primaryText};
letter-spacing: 0.12em;
text-transform: uppercase;
opacity: 0.75;
Expand All @@ -44,7 +44,7 @@ export const HeroTitle = styled.h1`
font-family: ${({ theme }) => theme.fonts.primaryHeading};
font-size: 2.8rem;
font-weight: ${({ theme }) => theme.fontWeights[7]};
color: ${({ theme }) => theme.colors.heroText};
color: ${({ theme }) => theme.colors.primaryText};
line-height: ${({ theme }) => theme.lineHeights.extraCondensed};
${mb(4)};

Expand All @@ -56,7 +56,7 @@ export const HeroTitle = styled.h1`
export const HeroDescription = styled.p`
font-family: ${({ theme }) => theme.fonts.body};
font-size: ${({ theme }) => theme.fontSizes.medium};
color: ${({ theme }) => theme.colors.heroParagraph};
color: ${({ theme }) => theme.colors.primaryText};
line-height: ${({ theme }) => theme.lineHeights.condensed};
max-width: 38rem;
${mb(6)};
Expand All @@ -71,7 +71,7 @@ export const HeroTagList = styled.div`
export const HeroTag = styled.span`
font-family: 'Fira Code', monospace;
font-size: ${({ theme }) => theme.fontSizes.xsmall};
color: ${({ theme }) => theme.colors.heroText};
color: ${({ theme }) => theme.colors.primaryText};
background: rgba(128, 128, 128, 0.15);
border: 1px solid rgba(128, 128, 128, 0.25);
border-radius: ${({ theme }) => theme.borderRadius.base};
Expand Down Expand Up @@ -169,7 +169,9 @@ export const SwatchCard = styled.div`
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
border-radius: 0.375rem;
overflow: hidden;
transition: transform 0.18s ease, box-shadow 0.18s ease;
transition:
transform 0.18s ease,
box-shadow 0.18s ease;

&:hover {
transform: translateY(-4px);
Expand Down
19 changes: 6 additions & 13 deletions src/components/info-call-to-action/InfoCallToAction.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import React from 'react';
import React, { ReactNode } from 'react';
import { Icon } from '../icon/Icon';
import {
Container,
IconWrapper,
ContentWrapper,
StyledParagraph,
} from './styles';
import { Container, IconWrapper, ContentWrapper } from './styles';

type InfoCallToAction = {
htmlString: string;
type InfoCallToActionProps = {
children: ReactNode;
};

export const InfoCallToAction = ({ htmlString }: InfoCallToAction) => {
export const InfoCallToAction = ({ children }: InfoCallToActionProps) => {
return (
<Container>
<IconWrapper>
<Icon iconName="InfoCircle" />
</IconWrapper>
<ContentWrapper>
<StyledParagraph dangerouslySetInnerHTML={{ __html: htmlString }} />
</ContentWrapper>
<ContentWrapper>{children}</ContentWrapper>
</Container>
);
};
Loading