skip to content
Site header image Nerdy Momo Cat

Making Webtrotion

Last Updated:

Table of Contents

Normally, if you want to add a page with Astro, you need to place an .astro- or .md- or .html- file in your src/pages-folder.
Main future goal Dec 11, 2023, 01:38 AM
Currently, the repo reads all blocks off notion, and writes them directly to renderable HTML. For saving re-computation of API calls, it saves API response to a json for posts. Instead of doing that, it might be ideal to write it as a content file that is rendered using MDX. Why? Because astro just released this Incremental Content Caching (Experimental). Caching the json, only caches the API request, but the build still often takes around 500ms at the very least. That would be the main incremental future goal from here on.

Nice to have #LATER

scrolling expanding table of contents (this can be done using intersection observer — I am not sure it is worth doing though? Or it might be)
Very much later, possibly never: include random .md or .html files on page with the nice layout, tags, rss, sitemap, post listings etc.
And someday figure out lite embed for tiktok
And maybe move metascraper out and just use noembed instead?
Starlight also has an interesting mobile menu implementation btw
Add sort, filter to tables?
Headings on same page should not show a popup, they should directly link to that position on click. The popup is useless in that case.
Problem? Currently there is no way to know if the linked block is on the same page or is on a different page; and the idea is we want the headings popup to show up if it is on a different page.
Liamm_top asked to see if there is a way to fix this for Chinese deployment which needs:
Fix the slug regex to support chinese characters Mar 8, 2024
Deploy to vercel
Caching in vercel?
Collections are being cleared in slug for chinese characters and tags Mar 8, 2024
Icon has issues in satori when deploying on custom domains or has base URL? Mar 7, 2024
Remove pnpm Feb 25, 2024
OG image font for a different language Mar 7, 2024
Uploaded icons to pages do not show up in page mentions (liked ones do)
Cannot correctly figure out if there is interlinked content or not which leads to empty table of contents sometimes, fix that
Fix issue with notion.site being messed up because it thinks everything is part of its own site
There is no difference in API response, which is why 🤷‍♀️
Use tag descriptions for hover and tag page and collection description for collection page
Sadly, the descriptions are only returned with database, not with page properties 🤷‍♀️
Tag descriptions
Collection descriptions
Descriptions in OG images
Completed
In March 2024
Clicking read more (on mobile at least) does not close the popup?
dynamically load og image fonts
Fix OG image generation with the logo
add a print stylesheet
Set the bookmarks to take full width?
Add error fixing to astro notion tweet, because if someone deletes the tweet that has been embedded, the build stops
Fix web-bookmark sizing being all over the place
Tags on the page when they wrap do not have vertical spacing
Do not height restrict mermaid previews
bold headings by default
extend bookmark full width if no image

Menu bar on mobile only clicks on words not the whole line
sticky header (CANCELLED - I DO NOT LIKE THIS MUCH)
referenced pages, referenced in pages etc
Remember for later, we did not move collections out like tags because we would kinda need to have all collections list page and that makes no sense
Oh, it already gives an error anyway
html embedded into iframe rather than inserted into div - some 5 min exploration did not work
Fix lightbox
google maps embed
pdf embed
OG only for recent entries in single page preview collections
Lol! Later? I did it Nov 28, 2023, 05:44 AM just because it was absolutely NOT needed
Only run cron if anything in notion has been updated (might need to look into build adapters)
incremental builds (On-Demand Incremental Static Regeneration) when astro implements them
kinda doing them right now with aggressive caching of API requests at elast
Sometimes when the page height is tiny, like this one because information is all hidden in toggles or stream posts, giscus loads immediately causing a 500ms block time at least. Look into this for that
Jun 14, 2024
Pinned posts for a collection?
Email clicks should copy email and show a toast, not open a mailto: link.
When linking to a block that is a toggle, and it is shown in popup, it feels weird, that I can click the arrow, but nothing opens up

Astro

These are some links that are important to consider for astro:
Some other websites with same theme

Checking out other stuff

I could have used Nuxt or Next or maybe even hugo but I don’t know why I do not want to use them Oct 27, 2023, 06:58 AM
Let me start with basic questions
Do I need to use npm or can I do styling with CSS/Tailwind and generate mds using n2y?
And what happens if I do that?
What is not supported in mdx basically?
I will need to fix this stuff
class CalloutBlock(Block):
    def __init__(self, client, notion_data, page, get_children=True):
        super().__init__(client, notion_data, page, get_children)
        self.rich_text = client.wrap_notion_rich_text_array(
            self.notion_type_data["rich_text"], self
        )
        # the color and icon are not currently used

    def to_pandoc(self):
        content = self.rich_text.to_pandoc()
        if self.has_children:
            children = self.children_to_pandoc()
            result = [Para(content)]
            result.extend(children)
        else:
            result = Para(content)
        return result

class AudioBlock(ContentBlock):
    pass


class VideoBlock(ContentBlock):
    pass
class ToggleBlock(Block):
    """
    Generates a bulleted list item with indented children. A plugin may be used
    to add html classes and replicate the interactive behavior found in Notion.
    """

    def __init__(self, client, notion_data, page, get_children=True):
        super().__init__(client, notion_data, page, get_children)
        self.rich_text = client.wrap_notion_rich_text_array(
            self.notion_type_data["rich_text"], self
        )

    def to_pandoc(self):
        header = self.rich_text.to_pandoc()
        children = self.children_to_pandoc()
        content = [Para(header)]
        content.extend(children)
        return BulletList([content])

Components and libraries for astro

Comments with giscus

Other components

Log

Nov 22, 2023, 01:12 PM
Files that still have original style
bookmark.astro
callout.astro
Nov 22, 2023, 08:48 PM
Nov 23, 2023, 09:02 PM
Initial stuff about filter done but remember it sometimes gives error, so figure that out!
Default slugs
Nov 24, 2023, 02:32 AM
same page href
code copy
i added svg to make it look nice AND GRR
The issue you're encountering is due to event delegation. When you click on the svg or path, they become the target of the event, not the button. Since the data-code attribute is on the button, clicking on the svg results in null because ev.target is the svg which does not have the data-code attribute. To consistently get the data-code attribute from the button regardless of where the click occurs (on the button or its child elements), you should use ev.currentTarget instead of ev.target. The currentTarget is always the element to which the event handler has been attached, in this case, the button.
fix code highlight! move to shiki because that is default with astro!
including import "../styles/syntax-coloring.css"; worked! but i am still gonna try a tiny bit to move to shiki!
nah prism is good enough!
WTF is the z-index changing to be on top of the header on mobile man?
Well, I found a nice svg and thought, hey, i can start with -90 degree angle, then rotate it on click, BIG MISTAKE!
<svg class="-rotate-90  transform transition-all duration-300 group-open:rotate-180" fill="none" height="20" width="20" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24">
 <polyline points="6 9 12 15 18 9"></polyline>
</svg>
Nov 24, 2023, 10:22 AM
tags color in notion to tags color on page
on post
on blog tags
Nov 24, 2023, 07:16 PM
If I want notes on my homepage which I very well might, change here src/pages/[...page].astro.
how do i embed any html in page?
Image optimization maybe Nov 24, 2023 (I used astro image tools, I am not sure it works though?)
Stream/JSON/preview — i really do not know the solution
YAY! I DID IT!
So, I think initially I was thinking of using notion relations and items and sub-items to load them into a single page. Instead I have an idea. What if the collection page preview shows the complete content/full post instead of just the preview? That basically solves our problem, right?
Remove notion extras
Figure out how to create post-preview with all blocks
I think this is where the information is: src/pages/posts/collection/ [collection]/ [...page].astro
This seems to be the post preview code
<ul class="space-y-8 text-start">
  { page.data.map((post) => (
  <li class="flex flex-col flex-wrap gap-2 sm:flex-row [&_q]:basis-full">
    <PostPreview post="{post}" as="h2" withDesc />
  </li>
  )) }
</ul>
This is post preview. astro
---
// import type { CollectionEntry } from "astro:content";
// import type { HTMLTag, Polymorphic } from "astro/types";
import type { HTMLTag } from "astro/types";
import FormattedDate from "../FormattedDate.astro";
import type { Post } from "src/lib/interfaces";
import { getPostBySlug } from "@/lib/notion/client";

// type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
// 	post: CollectionEntry<"post">;
// 	withDesc?: boolean;
// };
//FIXME: why is this a new method?

interface Props {
	post: Post;
	as?: HTMLTag;
	withDesc?: boolean;
}

const { post, as: Tag = "div", withDesc = false } = Astro.props;

//TODO: what is this?
// const postDate = post.data.updatedDate ?? post.data.publishDate;
---
<FormattedDate date={new Date(post.Date)} class="min-w-[120px] text-gray-500" />
<Tag>
	<a
		href={`/posts/${post.Slug}/`}
		class="cactus-link"
		rel="prefetch"
	>
		{post.Title}
	</a>
</Tag>
{withDesc && <q class="line-clamp-3 block italic">{post.Excerpt}</q>}
This is how it is done for direct root pages
<PageLayout meta={{ title: page.Title || siteConfig.title }}>
	<section>
		<NotionBlocks blocks={blocks} isRoot={true} />
	</section>
	<!-- {
    //NOTE: This added a recent posts thing to my page. I can add it back later if needed
		!slug && (
			<section aria-label="Blog post list" class="mt-16">
				<h2 class="title mb-4 text-xl">Posts</h2>
				<ul class="space-y-4 sm:space-y-2">
					{rankedPosts.map((p) => (
						<li class="flex flex-col gap-x-2 sm:flex-row">
							<PostPreview post={p} />
						</li>
					))}
				</ul>
			</section>
		)
	} -->
</PageLayout>
Figure out how to create post preview with all blocks for only particular collections
toggle issue, if i toggle parent, the arrow for all child blocks toggles too (the content does not show, but it does toggle)
HTML Injection?
Figure out later how to scope these scripts or load these into iframe instead maybe?
Should these scripts be at a component level or the whole blogpost level?
Me:
<script>
	import mermaid from "mermaid";
	mermaid.initialize({ startOnLoad: true, theme: "neutral" });
</script>

<script>
  document.querySelectorAll("button.copy").forEach((button) => {
  button.addEventListener("click", (ev) => {
    const svg = button.querySelector('.svg-icon path');
    const code = ev.currentTarget.getAttribute("data-code");
    navigator.clipboard.writeText(code);
    if (svg) {
      svg.setAttribute("fill", "limegreen");
      setTimeout(() => {
        svg.setAttribute("fill", "white");
      }, 1000);
    }
  });
});
</script>
ChatGPT:
Deciding whether to place scripts at the component level or at the whole blog post level in an Astro project depends on the specific use case and context of the scripts. Here are some guidelines to help you make this decision for the two scripts you've mentioned:
  1. Mermaid Initialization Script:
    • If the Mermaid charts are used only within a specific component and not throughout the entire blog post, it makes sense to keep the Mermaid initialization script at the component level. This way, the script will only run where it's needed, which can improve performance and maintainability.
    • If Mermaid charts are a common feature across various parts of your blog post or site, you might consider placing this script at a higher level (like the layout or page level) so that it's available throughout the blog post or across multiple pages.
  2. Button Copy Script:
    • If the copy functionality for code blocks is specific to a component (like a code snippet component), then it's appropriate to have this script at the component level. This approach encapsulates the functionality within the component, making it reusable and easy to manage.
    • However, if you have a similar copy functionality required in multiple components across the blog post, you might want to centralize this script at the blog post level to avoid redundancy and keep the code DRY (Don't Repeat Yourself).
Here are some additional considerations:
  • Performance and Load Times: Keeping scripts at the component level ensures they're only loaded and run when the component is used, which can be beneficial for performance.
  • Reusability: If you foresee the need to use these features (like Mermaid charts or copy buttons) in multiple components across different parts of your site, extracting the scripts to a higher level and making them globally available might be more efficient.
  • Maintainability: Keeping scripts close to where they are used enhances maintainability, as it's easier to understand and modify the code when everything related to a particular feature is in one place.
Ultimately, the decision depends on the specific requirements of your project and how you plan to scale or maintain it in the future.
If you have multiple <AlertButton /> components on a page, Astro will not run the script multiple times. Scripts are bundled and only included once per page. Using querySelectorAll ensures that this script attaches the event listener to every button with the alert class found on the page. GRRRR (link here)
Make sure all onclick are either inside the element or have selector all.
Move tweets to static tweet-astro
Nov 25, 2023, 12:13 AM
fix site properties !MAJOR
In the original otoyo’s repo, those things are in src/layouts/Layout.astro
Can’t use astro global in typescript 😠
getSite is repeated in blog-helpers.ts and astro.config.ts at the moment.
Somehow could not run getDatabase in site.config.ts; so I created a file of my own: siteInfoSetup
THE WHOLE FUDGING ISSUE WAS IN DATE.TS NOT IN CONFIG FILE
Now it is not running at all?
So, there is a circular dependency, getNavLink was calling pathJoin in index, which was trying to import siteInfo.ts. Awesome
temp code holder
import { getDatabase } from "@/lib/notion/client";
import { getNavLink, getSite } from "@/lib/blog-helpers";
import { SiteInfo } from "@/types";

export async function siteInfoFunc(): Promise<SiteInfo> {
  const tl = "", ds = "", path = "/", oim = "";
  const database = await getDatabase();

  const rawTitle = database.Title.includes("||") ? database.Title.split(" || ")[0] : database.Title;
  const siteAuthor = database.Title.includes("||") ? database.Title.split(" || ")[1] : "";
  const siteTitle = tl ? `${tl} - ${rawTitle}` : rawTitle;
  const siteDescription = ds ? ds : database.Description;
  const siteURL = new URL(getNavLink(path), getSite()).toString();

  // console.log(siteTitle, siteDescription, siteURL);


  // export const siteInfo = () => {
  //   return [siteTitle, siteDescription, siteURL];
  // }

  const siteInfoF: SiteInfo = {
    title: siteTitle,
    description: siteDescription,
    author: siteAuthor,
    lang: "en",
    homePageSlug: "index",
    // Include view-transitions: https://docs.astro.build/en/guides/view-transitions/
    includeViewTransitions: false,
    // Meta property, found in src/components/BaseHead.astro L:42
    ogLocale: "en",
    // Date.prototype.toLocaleDateString() parameters, found in src/utils/date.ts.
    date: {
      locale: "en",
      options: {
        day: "numeric",
        month: "short",
        year: "numeric",
      },
    },
    webmentions: {
      link: "https://webmention.io/astro-cactus.chriswilliams.dev/webmention",
      //  // site: "https://astro-cactus.chriswilliams.dev/",
    },
  };
  return siteInfoF;
}
dump site.config.ts original
import type { SiteConfig } from "@/types";


export const siteConfig: SiteConfig = {
  // Used as both a meta property (src/components/BaseHead.astro L:31 + L:49) & the generated satori png (src/pages/og-image/[slug].png.ts)
  //todo: get this from notion maybe? and we do not need locale.
  author: "Chris Williams",
  // Meta property used to construct the meta title property, found in src/components/BaseHead.astro L:11
  title: "Astro Theme Cactus",
  // Meta property used as the default description meta property
  description: "An opinionated starter theme for Astro",
  // HTML lang property, found in src/layouts/Base.astro L:18
  lang: "en-GB",
  // Meta property, found in src/components/BaseHead.astro L:42
  ogLocale: "en_GB",
  // Date.prototype.toLocaleDateString() parameters, found in src/utils/date.ts.
  date: {
    locale: "en-GB",
    options: {
      day: "numeric",
      month: "short",
      year: "numeric",
    },
  },
  homePageSlug: "index",
  // Include view-transitions: https://docs.astro.build/en/guides/view-transitions/
  includeViewTransitions: false,
  webmentions: {
    link: "https://webmention.io/astro-cactus.chriswilliams.dev/webmention",
  },
};
So, now after 6 hours, we are at a point, where build works but dev does not and I do not know why.
Cannot read properties of undefined (reading 'date') It has something to do with date. I am not sure what.
Error dump
npm run dev
astro-notion-cms-cactus@3.5.0 dev
astro dev
🚀  astro  v3.4.0 started in 491ms

 Local    http://localhost:4321/
 Network  use --host to expose

(node:44831) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
8:57:33 PM [vite] Error when evaluating SSR module /src/utils/date.ts:
|- TypeError: Cannot read properties of undefined (reading 'date')
at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

8:57:33 PM [vite] Error when evaluating SSR module /src/utils/index.ts: failed to import "/src/utils/date.ts"
|- TypeError: Cannot read properties of undefined (reading 'date')
at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

8:57:33 PM [vite] Error when evaluating SSR module /src/lib/notion/client.ts: failed to import "/src/utils/index.ts"
|- TypeError: Cannot read properties of undefined (reading 'date')
at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

8:57:33 PM [vite] Error when evaluating SSR module /src/site.config.ts: failed to import "/src/lib/notion/client.ts"
|- TypeError: Cannot read properties of undefined (reading 'date')
at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

8:57:33 PM [vite] Error when evaluating SSR module /src/components/BaseHead.astro: failed to import "/src/site.config.ts"
|- TypeError: Cannot read properties of undefined (reading 'date')
at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

8:57:33 PM [vite] Error when evaluating SSR module /src/layouts/Base.astro: failed to import "/src/components/BaseHead.astro"
|- TypeError: Cannot read properties of undefined (reading 'date')
at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

8:57:33 PM [vite] Error when evaluating SSR module /Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/pages/[...page].astro: failed to import "/src/layouts/Base.astro"
|- TypeError: Cannot read properties of undefined (reading 'date')
at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

error   Cannot read properties of undefined (reading 'date')
File:
/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:9:53
Code:
> 9 | const dateFormat = new Intl.DateTimeFormat(siteInfo.date.locale, siteInfo.date.options);
|                                                     ^
11 | export function getFormattedDate(
12 |   date: string | number | Date,
Stacktrace:
TypeError: Cannot read properties of undefined (reading 'date')
at /Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:9:53
at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)

So basically, astro config can only be static in dev mode at least, in build mode I can use github process variables. But I need database information? So, I do not really know what to do about it?
Like the getSite we wrote technically doesn’t really work?
export const CUSTOM_DOMAIN =
  import.meta.env.CUSTOM_DOMAIN || process.env.CUSTOM_DOMAIN || 'alpacat.com' // <- Set your costom domain if you have. e.g. alpacat.com
export const BASE_PATH =
  import.meta.env.BASE_PATH || process.env.BASE_PATH || '' // <- Set sub directory path if you want. e.g. /docs/
import { CUSTOM_DOMAIN, BASE_PATH } from "./src/constants";

const getSite = function () {
  if (CUSTOM_DOMAIN) {
    return new URL(BASE_PATH, `https://${CUSTOM_DOMAIN}`).toString();
  }

  if (process.env.VERCEL && process.env.VERCEL_URL) {
    return new URL(BASE_PATH, `https://${process.env.VERCEL_URL}`).toString();
  }

  if (process.env.CF_PAGES) {
    if (process.env.CF_PAGES_BRANCH !== 'main') {
      return new URL(BASE_PATH, process.env.CF_PAGES_URL).toString();
    }

    return new URL(
      BASE_PATH,
      `https://${new URL(process.env.CF_PAGES_URL).host
        .split('.')
        .slice(1)
        .join('.')}`
    ).toString();
  }

  return new URL(BASE_PATH, 'http://localhost:4321').toString();
};
So, right now it works with me using export key=value on CLI
Nov 25, 2023, 08:26 AM
Do not show pages with slug starting with underscore in
collection index page,
posts page,
tag page,
rss,
sitemap
fix robots.txt somehow?
check audio embeds
So, I've got this mobile button that opens and closes a navigation bar when I click on it. But here's the thing - when the menu is open, I want it to close if I click anywhere outside of it.
giscus
how do i use webmentions?
JSON options rather than everything in env? Only Notion values in env!
Look at the cache stuff in otoyo’s post. Maybe it is just for cloudfare? Idk!
Change getMenu to be: instead of excluding pages with no rank or rank 0, just set ranks of those to 99. remember that homePageSlug if available will always have the top rank.
Fix post preview for empty desc
THEME AND FONTS!
Notion API does not say that block type can be audio , add audio support man
This is duplicated across 5 files, maybe fix it later?
const fileAtacchedBlocks = extractTargetBlocks("image", blocks)
	.concat(extractTargetBlocks("video", blocks))
	.concat(extractTargetBlocks("file", blocks))
	.filter((block) => {
		if (!block) {
			return false;
		}
		const imageOrVideoOrAudioOrFile = block.NImage || block.File || block.Video || block.Audio;
		return (
			imageOrVideoOrAudioOrFile &&
			imageOrVideoOrAudioOrFile.File &&
			imageOrVideoOrAudioOrFile.File.Url
		);
	});

// Download files
await Promise.all(
	fileAtacchedBlocks
		.map(async (block) => {
			const expiryTime = (block.NImage || block.File || block.Video || block.Audio).File.ExpiryTime;
			if (Date.parse(expiryTime) > Date.now()) {
				return Promise.resolve(block);
			}
			return getBlock(block.Id);
		})
		.map((promise) =>
			promise.then((block) => {
				let url!: URL;
				try {
					url = new URL((block.NImage || block.File || block.Video || block.Audio).File.Url);
				} catch (err) {
					console.log("Invalid file URL");
					return Promise.reject();
				}
				return Promise.resolve(url);
			}),
		)
		.map((promise) => promise.then(downloadFile)),
);
Side note, apparently if it is a video embed but not youtube, it is not rendered? Maybe call the generic embed in that case?
Code here
videoUrl &&
			(url ? (
				isYouTubeURL(url) ? (
					<iframe
          class="w-full h-[340px] max-w-sm:h-[220px]"
						src={`https://www.youtube.com/embed/${parseYouTubeVideoId(url)}`}
						title="YouTube video player"
						allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
						allowfullscreen
					/>
				) : null
Nov 25, 2023, 07:39 PM
Issue with toggle 2 headings? (FIXED! I had not changed the polyline for some reason!)
Do not show pages with slug starting with underscore in
collection index page,
posts page,
tag page,
rss,
sitemap
fix robots.txt somehow?
giscus
how do i use webmentions?
links to main pages and pages as URL seems to be broken?
OG image with satori
og image creation using satori
Stuff like this is more complicated than the existing one. So, I am gonna try to figure out the existing one!
Need to change this function to have all of them:
export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await getAllPosts();
  return posts.filter((post) => !post.FeaturedImage).map(({ Slug }) => ({ params: { slug: Slug } }));
};
Made huge progress on how to include image on side
<div
    style={{
      height: "100%",
      width: "100%",
      display: "flex",
      backgroundColor: "white",

      backgroundSize: "100px 100px",
      fontFamily: "JetBrainsMono-Bold",
    }}
  >
    <div
      style={{
        padding: "20px",
        display: "flex",
        width: "100%",
        height: "100%",
        justifyContent: "center",
        alignItem: "stretch",
      }}
    >

      <div
        style={{
          display: "flex",
          flexDirection: "row",
          justifyContent: "space-between",
          border: "1px solid #374151",
          boxShadow: "5px 5px 0px #374151",
          width: "100%",
          height: "100%",
          padding: "10px",
        }}
      >
      <div style={{display:"flex", flex: 1}}>
              <img src="https://nerdymomocat.github.io/images/me.png" width={400} height={400} style={{
      width: '100%', 
      height: '100%', 
      objectFit: 'contain', 
      objectPosition: 'center'
    }} />

      </div>
      <div style={{display:"flex", flexDirection:"column", flex: 1,}}>
        <div
          style={{
            fontSize: "32px",
            fontWeight: "900",
            lineHeight: "3rem",
            padding: "10px 0 50px 0",
            color: "#374151",
            flex: 1,
            display: "flex",
            fontFamily: "PlusJakartaSans",
          }}
        >
          Demo New Page: Trying to get Astro+Notion working how I want to
        </div>
        <div
          style={{
            fontSize: "16px",
            fontWeight: "900",
            color: "#374151",
            display: "flex",
            flexDirection: "row",
            justifyContent: "space-between",
            alignItems: "center",
          }}
        >
          <div>
            DATE HERE
          </div>
          <div style={{ display: "flex", alignItems: "center" }}>
            <span style={{ marginRight: "16px" }}>Jafar Aziz</span>
          </div>
        </div>
        </div>
      </div>
    </div>
  </div>
Next up, image as background blur or maybe a div on bottom?
contain does not work, i need to set specific size for it to fit by ratio? →no assume it is a square, if not a square, it goes into background mode!
This works with HTML native, but not with svg or svg→png
<div
  style={{
    position: 'relative',
    height: "100%",
    width: "100%",
    display: "flex",
    fontFamily: "JetBrainsMono-Bold",
  }}
>
  <div
    style={{
      position: 'absolute',
      top: 0,
      left: 0,
      height: '100%',
      width: '100%',
      backgroundImage: `url('https://nerdymomocat.github.io/images/me.png')`,
      backgroundRepeat: 'no-repeat',
      backgroundSize: 'contain',
      backgroundPosition: 'center',
      filter: 'blur(8px)',
      zIndex: -1, // Ensures this div is behind the content
      backgroundColor: "white"
    }}
  ></div>
    <div
      style={{
        padding: "20px",
        display: "flex",
        width: "100%",
        height: "100%",
        justifyContent: "center",
        alignItem: "stretch",
      }}
    >

      <div
        style={{
          display: "flex",
          flexDirection: "row",
          justifyContent: "space-between",
          border: "1px solid #374151",
          boxShadow: "5px 5px 0px #374151",
          width: "100%",
          height: "100%",
          padding: "10px",
        }}
      >

      <div style={{display:"flex", flexDirection:"column", flex: 1,}}>
        <div
          style={{
            fontSize: "32px",
            fontWeight: "900",
            lineHeight: "3rem",
            padding: "10px 0 50px 0",
            color: "#374151",
            flex: 1,
            display: "flex",
            fontFamily: "PlusJakartaSans",
          }}
        >
          Demo New Page: Trying to get Astro+Notion working how I want to
        </div>
        <div
          style={{
            fontSize: "16px",
            fontWeight: "900",
            color: "#374151",
            display: "flex",
            flexDirection: "row",
            justifyContent: "space-between",
            alignItems: "center",
          }}
        >
          <div>
            DATE HERE
          </div>
          <div style={{ display: "flex", alignItems: "center" }}>
            <span style={{ marginRight: "16px" }}>Jafar Aziz</span>
          </div>
        </div>
        </div>
      </div>
    </div>
  </div>
await satori(
  <img src="data:image/png;base64,..." width={200} height={300} />,
  // Or src={arrayBuffer}, src={buffer}
  options
)
Masking with white image or white linear translucent layer somehow does not work?
Somehow this started working, we do not question it, we roll with it!
<div
  style={{
    height: '100%',
    width: '100%',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: 32,
    fontWeight: 600,
    backgroundColor: 'white'
  }}
>
<div
    style={{
      position: 'absolute', // Absolutely positioned
      top: 0,
      left: 0,
      height: '100%',
      width: '100%',
      background: 'url(https://maggieappleton.com/images/posts/squish-structure/sms2.62.jpeg)',
      backgroundRepeat: "no-repeat",
      maskImage: 'linear-gradient(to bottom, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.3))',
      backgroundColor: 'white'
    }}
  ></div>

<div
      style={{
        padding: "20px",
        display: "flex",
        width: "100%",
        height: "100%",
        justifyContent: "center",
        alignItem: "stretch",
      }}
    >

      <div
        style={{
          display: "flex",
          flexDirection: "row",
          justifyContent: "space-between",
          border: "1px solid #374151",
          boxShadow: "5px 5px 0px #374151",
          width: "100%",
          height: "100%",
          padding: "10px",
        }}
      >

      <div style={{display:"flex", flexDirection:"column", flex: 1,}}>
        <div
          style={{
            fontSize: "px",
            fontWeight: "900",
            lineHeight: "3rem",
            padding: "10px 0 50px 0",
            color: "#374151",
            flex: 1,
            display: "flex",
            fontFamily: "PlusJakartaSans",
          }}
        >
          Demo New Page: Trying to get Astro+Notion working how I want to
        </div>
        <div
          style={{
            fontSize: "16px",
            fontWeight: "900",
            color: "#374151",
            display: "flex",
            flexDirection: "row",
            justifyContent: "space-between",
            alignItems: "center",
          }}
        >
          <div>
            DATE HERE
          </div>
          <div style={{ display: "flex", alignItems: "center" }}>
            <span style={{ marginRight: "16px" }}>Nerdy Momo Cat</span>
          </div>
        </div>
        </div>
      </div>
    </div>
</div>
And this too! (but it looks weird with colored background images)
<div
  style={{
    height: '100%',
    width: '100%',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: 32,
    fontWeight: 600,
    backgroundColor: 'white'
  }}
>
   <img src="https://maggieappleton.com/images/posts/squish-structure/sms2.66.jpeg" style={{position: 'absolute', // Absolutely positioned
      top: 0,
      left: 0,
      height: '100%',
      width: '100%',
      objectFit: 'contain', 
      objectPosition: 'center',
      maskImage: 'linear-gradient(to bottom, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2))'
    }} />

<div
      style={{
        padding: "20px",
        display: "flex",
        width: "100%",
        height: "100%",
        justifyContent: "center",
        alignItem: "stretch",
      }}
    >

      <div
        style={{
          display: "flex",
          flexDirection: "row",
          justifyContent: "space-between",
          border: "1px solid #374151",
          boxShadow: "5px 5px 0px #374151",
          width: "100%",
          height: "100%",
          padding: "10px",
        }}
      >

      <div style={{display:"flex", flexDirection:"column", flex: 1,}}>
        <div
          style={{
            fontSize: "px",
            fontWeight: "900",
            lineHeight: "3rem",
            padding: "10px 0 50px 0",
            color: "#374151",
            flex: 1,
            display: "flex",
            fontFamily: "PlusJakartaSans",
          }}
        >
          Demo New Page: Trying to get Astro+Notion working how I want to
        </div>
        <div
          style={{
            fontSize: "16px",
            fontWeight: "900",
            color: "#374151",
            display: "flex",
            flexDirection: "row",
            justifyContent: "space-between",
            alignItems: "center",
          }}
        >
          <div>
            DATE HERE
          </div>
          <div style={{ display: "flex", alignItems: "center" }}>
            <span style={{ marginRight: "16px" }}>Nerdy Momo Cat</span>
          </div>
        </div>
        </div>
      </div>
    </div>
</div>
I like this, 3 part linear gradient
<div
  style={{
    height: '100%',
    width: '100%',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: 32,
    fontWeight: 600,
    backgroundColor: 'white'
  }}
>
   <img src="https://maggieappleton.com/images/posts/squish-structure/sms2.66.jpeg" style={{position: 'absolute', // Absolutely positioned
      top: 0,
      left: 0,
      height: '100%',
      width: '100%',
maskImage: 'linear-gradient(to bottom, rgba(255, 255, 255, 0.15) 25%, rgba(255, 255, 255, 0.15) 25%, rgba(255, 255, 255, 0.3) 90%, rgba(255, 255, 255, 0.1) 90%)',
objectFit: 'cover'
    }} />

<div
      style={{
        padding: "20px",
        display: "flex",
        width: "100%",
        height: "100%",
        justifyContent: "center",
        alignItem: "stretch",
      }}
    >

      <div
        style={{
          display: "flex",
          flexDirection: "row",
          justifyContent: "space-between",
          border: "1px solid #374151",
          boxShadow: "5px 5px 0px #374151",
          width: "100%",
          height: "100%",
          padding: "10px",
        }}
      >

      <div style={{display:"flex", flexDirection:"column", flex: 1,}}>
        <div
          style={{
            fontSize: "px",
            fontWeight: "900",
            lineHeight: "3rem",
            padding: "10px 0 50px 0",
            color: "#374151",
            flex: 1,
            display: "flex",
            fontFamily: "PlusJakartaSans",
          }}
        >
          Demo New Page: Trying to get Astro+Notion working how I want to
        </div>
        <div
          style={{
            fontSize: "16px",
            fontWeight: "900",
            color: "#374151",
            display: "flex",
            flexDirection: "row",
            justifyContent: "space-between",
            alignItems: "center",
          }}
        >
          <div>
            DATE HERE
          </div>
          <div style={{ display: "flex", alignItems: "center" }}>
            <span style={{ marginRight: "16px" }}>Nerdy Momo Cat</span>
          </div>
        </div>
        </div>
      </div>
    </div>
</div>
📝 Using sharp instead of resvg because that is what the blogpost above uses and I ain’t figuring it out myself
Converting to tailwind satori format because uh, that is what cactus uses and I do not have enough brain or brainpower to figure it out.
Apparently, no conversion NEEDED. Why are these frameworks so complicated smh?
Alright, here is every template
const markup_img_bg = (title: string, pubDate: string, img_url: string) =>
  html`<div
  style="height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 32px; font-weight: 700; background-color: white; font-family: JetBrainsMono;"
>
   <img src="${img_url}"
        style="position: absolute; top: 0; left: 0; height: 100%; width: 100%; mask-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.15) 25%, rgba(255, 255, 255, 0.15) 25%, rgba(255, 255, 255, 0.3) 90%, rgba(255, 255, 255, 0.1) 90%); object-fit: cover;"
   />

  <div
    style="padding: 20px; display: flex; width: 100%; height: 100%; justify-content: center; align-items: stretch;"
  >
    <div
      style="display: flex; flex-direction: row; justify-content: space-between; border: 1px solid #374151; box-shadow: 5px 5px 0px #374151; width: 100%; height: 100%; padding: 10px;"
    >
      <div style="display: flex; flex-direction: column; flex: 1;">
        <div
          style="font-size: px; font-weight: 700; line-height: 3rem; padding: 10px 0 50px 0; color: #374151; flex: 1; display: flex;"
        >
          ${title}
        </div>
        <div
          style="font-size: 16px; font-weight: 700; color: #374151; display: flex; flex-direction: row; justify-content: space-between; align-items: center;"
        >
          <div>
            ${pubDate}
          </div>
          <div style="display: flex; align-items: center;">
            <span style="margin-right: 16px;">${siteInfo.author}</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>`;

const markup_img_sq_with_desc = (title: string, pubDate: string, desc: string, img_url: string) =>
  html`<div
  style="height: 100%; width: 100%; display: flex; background-color: white; font-family: JetBrainsMono-Bold;"
>
  <div
      style="padding: 20px; display: flex; width: 100%; height: 100%; justify-content: center; align-items: stretch;"
  >
      <div
          style="display: flex; flex-direction: row; justify-content: space-between; border: 1px solid #374151; box-shadow: 5px 5px 0px #374151; width: 100%; height: 100%; padding: 10px;"
      >
          <div style="display: flex; flex: 1;">
              <img src="${img_url}"
                   style="width: 100%; height: 100%; object-fit: contain; object-position: center;" />
          </div>
          <div style="display: flex; flex-direction: column; flex: 1;">
              <div
                  style="font-size: 32px; font-weight: 700; line-height: 3rem; padding: 10px 0 50px 0; color: #374151; flex: 0.3; display: flex; font-family: monospace;"
              >
              ${title}
              </div>
              <div
                  style="font-size: 24px; font-weight: 700; line-height: 3rem; padding: 10px 0 50px 0; color: #374151; flex: 1; display: flex; font-family: monospace;"
              >
              ${desc}
              </div>
              <div
                  style="font-size: 16px; font-weight: 700; color: #374151; display: flex; flex-direction: row; justify-content: space-between; align-items: center;"
              >
                  <div>
                      ${pubDate}
                  </div>
                  <div style="display: flex; align-items: center;">
                      <span style="margin-right: 16px;">${siteInfo.author}</span>
                  </div>
              </div>
          </div>
      </div>
  </div>
</div>`;

const markup_img_sq_without_desc = (title: string, pubDate: string, img_url: string) =>
  html`<div
  style="height: 100%; width: 100%; display: flex; background-color: white; font-family: JetBrainsMono-Bold;"
>
  <div
      style="padding: 20px; display: flex; width: 100%; height: 100%; justify-content: center; align-items: stretch;"
  >
      <div
          style="display: flex; flex-direction: row; justify-content: space-between; border: 1px solid #374151; box-shadow: 5px 5px 0px #374151; width: 100%; height: 100%; padding: 10px;"
      >
          <div style="display: flex; flex: 1;">
              <img src="${img_url}"
                   style="width: 100%; height: 100%; object-fit: contain; object-position: center;" />
          </div>
          <div style="display: flex; flex-direction: column; flex: 1;">
              <div
                  style="font-size: 32px; font-weight: 700; line-height: 3rem; padding: 10px 0 50px 0; color: #374151; flex: 1; display: flex; font-family: monospace;"
              >
              ${title}
              </div>
              <div
                  style="font-size: 16px; font-weight: 700; color: #374151; display: flex; flex-direction: row; justify-content: space-between; align-items: center;"
              >
                  <div>
                      ${pubDate}
                  </div>
                  <div style="display: flex; align-items: center;">
                      <span style="margin-right: 16px;">${siteInfo.author}</span>
                  </div>
              </div>
          </div>
      </div>
  </div>
</div>`;

const markup_img_none_with_desc = (title: string, pubDate: string, desc: string) =>
  html`<div style="height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 32px; font-weight: 700; background-color: white; background-image: radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%); background-size: 100px 100px; font-family: JetBrainsMono-Bold;">

  <div style="padding: 20px; display: flex; width: 100%; height: 100%; justify-content: center; align-items: stretch;">

      <div style="display: flex; flex-direction: row; justify-content: space-between; border: 1px solid #374151; box-shadow: 5px 5px 0px #374151; width: 100%; height: 100%; padding: 10px;">

          <div style="display: flex; flex-direction: column; flex: 1;">
              <div style="font-size: 52px; font-weight: 700; line-height: 4rem; padding: 10px 50px; color: #374151; flex: 0.4; display: flex;">
                  ${title}
              </div>
              <div style="font-size: 30px; font-weight: 700; line-height: 4rem; padding: 10px 50px; color: #374151; flex: 1; display: flex;">
                  ${desc}
              </div>
              <div style="font-size: 24px; font-weight: 700; color: #374151; display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 10px;">
                  <div>
                      ${pubDate}
                  </div>
                  <div style="display: flex; align-items: center;">
                      <span style="margin-right: 16px;">Nerdy Momo Cat</span>
                  </div>
              </div>
          </div>
      </div>
  </div>
</div>
`;

const markup_img_none_without_desc = (title: string, pubDate: string) =>
  html`<div style="height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 32px; font-weight: 700; background-color: white; background-image: radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%); background-size: 100px 100px; font-family: JetBrainsMono-Bold;">

  <div style="padding: 20px; display: flex; width: 100%; height: 100%; justify-content: center; align-items: stretch;">

      <div style="display: flex; flex-direction: row; justify-content: space-between; border: 1px solid #374151; box-shadow: 5px 5px 0px #374151; width: 100%; height: 100%; padding: 10px;">

          <div style="display: flex; flex-direction: column; flex: 1;">
              <div style="font-size: 52px; font-weight: 700; line-height: 4rem; padding: 10px 50px; color: #374151; flex: 1; display: flex;">
                  ${title}
              </div>
              <div style="font-size: 24px; font-weight: 700; color: #374151; display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 10px;">
                  <div>
                      ${pubDate}
                  </div>
                  <div style="display: flex; align-items: center;">
                      <span style="margin-right: 16px;">Nerdy Momo Cat</span>
                  </div>
              </div>
          </div>
      </div>
  </div>
</div>
`;
I need to fix:
create different kinds of og images based on featured image
og images for non-post pages
Processing dump
  1. The only place meta property is set is in BaseHead.astro <meta property="og:image" content={socialImageURL} />
    There is an indication in Layout.astro, but remember that is just copied from otoyo’s blog for reference, and not actually used
    Now we have 2 things:
    const { title, description, ogImage, articleDate } = Astro.props;
    and
    const socialImageURL = new URL(ogImage ? ogImage : "/social-card.png", Astro.url).href;
  2. The only place BaseHead is imported is Base.Astro by import BaseHead from "@/components/BaseHead";
    const {
    	meta: { title = siteInfo.title, description = siteInfo.description, ogImage, articleDate },
    } = Astro.props;
    <BaseHead title={title} description={description} ogImage={ogImage} articleDate={articleDate} />
  3. Finally Base is imported at multiple places
    BlogPost.Astro
    const { post, ogImage, headings } = Astro.props;
    const socialImage = ogImage ?? `/og-image/${post.Slug}.png`;
    <BaseLayout
    	meta={{ title: post.Title, description: post.Excerpt, articleDate, ogImage: socialImage }}
    >
    Now, in src/pages/posts/[slug].astro
    import PostLayout from "@/layouts/BlogPost.astro";
    
    export async function getStaticPaths() {
    	const posts = await getAllPosts();
    
    	return posts.map((post) => ({
    		params: { slug: post.Slug },
    		props: { post },
    	}));
    }
    interface Props {
    	post: Post;
    }
    const { post } = Astro.props;
    
    if (!post) {
    	throw new Error("Post not found. slug: ${slug}");
    }
    
    let ogImage;
    if (post.FeaturedImage) {
    	ogImage = new URL(filePath(new URL(post.FeaturedImage.Url)), Astro.site).toString();
    }
    
    <PostLayout post={post} ogImage={ogImage} headings={headings}>
    It is not possible to know size of the image at build time, so I cannot do the square, non-square thing. I am just gonna put it as an option!
    [..page].astro in src/pages
    export async function getStaticPaths() {
    	const pages = await getPages();
    
    	return pages.map((page) => ({
    		params: { page: page.Slug === siteInfo.homePageSlug ? undefined : `/${page.Slug}` },
    		props: { page },
    	}));
    }
    
    interface Props {
    	page: Post;
    }
    
    const { page: slug } = Astro.params;
    const { page } = Astro.props;
    <PageLayout meta={{ title: page.Title || siteInfo.title }}>
    404.astro in src/pages
    const meta = {
    	title: "Oops! You found a missing page!",
    	description: "Oops! It looks like this page is lost in space!",
    };
    ---
    
    <PageLayout meta={meta}>
    [..page].astro in src/pages/posts
    const meta = {
    	title: "Posts",
    	description: "Ready my collection of posts and the things that interest me",
    };
    
    <PageLayout meta={meta}>
    [..page].astro in src/pages/posts/collection/[collection]
    const meta = {
    	title: `${collection}`,
    	description: "Read my collection of posts.",
    };
    
    <PageLayout meta={meta}>
    index.astro in src/pages/tags
    const meta = {
    	title: "All Tags",
    	description: "A list of all the topics I've written about in my posts",
    };
    ---
    
    <PageLayout meta={meta}>
    [..page].astro in src/pages/tags/[tag]/[...page].astro
    const meta = {
    	title: `Tag: ${tag}`,
    	description: `View all posts with the tag - ${tag}`,
    };
    
    <PageLayout meta={meta}>
Where are these download files?
export async function downloadFile(url: URL) {
  let res!: AxiosResponse;
  try {
    res = await axios({
      method: "get",
      url: url.toString(),
      timeout: REQUEST_TIMEOUT_MS,
      responseType: "stream",
    });
  } catch (err) {
    console.log(err);
    return Promise.resolve();
  }

  if (!res || res.status != 200) {
    console.log(res);
    return Promise.resolve();
  }

  const BASE_DIR = "./public/notion/";
  if (!fs.existsSync(BASE_DIR)) {
    fs.mkdirSync(BASE_DIR);
  }

  const dir = BASE_DIR + url.pathname.split("/").slice(-2)[0];
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir);
  }

  const filename = decodeURIComponent(url.pathname.split("/").slice(-1)[0]);
  const filepath = `${dir}/${filename}`;

  const writeStream = fs.createWriteStream(filepath);
  const rotate = sharp().rotate();

  let stream = res.data;

  if (res.headers["content-type"] === "image/jpeg") {
    stream = stream.pipe(rotate);
  }
  stream.pipe(new ExifTransformer()).pipe(writeStream);
}
Lol! We are back to the issue of image not being generated, and satori needs absolute path, and base64 just refuses to work with larger images?
Thought — we are eventually generating a png anyway. If the data has not expired — we can maybe just use that. Expiry duration seems to be an hour
.webp does not work for this, but that is okay. just use png or jpeg.
Example of new URL(filePath(new URL(post.FeaturedImage.Url)), Astro.site).toString(); and post.FeaturedImage.Url
Dumped code of using sharp and buffer etc in case I need it later
var dimensions = sizeOf('public/notion/90518bf5-b2a3-49ce-af8d-5fa59d9e11f4/ai-forest2x.webp');
  console.log(new URL(post.FeaturedImage.Url)).toString();
  // console.log(dimensions.width, dimensions.height);

  const buffimg = await sharp("public/notion/90518bf5-b2a3-49ce-af8d-5fa59d9e11f4/ai-forest2x.webp").toBuffer();
  // console.log(`data:image/png;base64,${buffimg.toString('base64')}`);
  // console.log(metadata);
  // let chosen_markup;
  // let chosen_markup = markup_img_none_without_desc(title, postDate);
  let chosen_markup = markup_img_sq_without_desc(title, postDate, "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
  console.log(chosen_markup);
http://localhost:4321/notion/d0d55339-5e7e-4aae-8fea-af6b97142bf9/1548870028.png
https://prod-files-secure.s3.us-west-2.amazonaws.com/6083354a-c9d6-441d-886e-3fa82c5b48de/d0d55339-5e7e-4aae-8fea-af6b97142bf9/1548870028.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20231126%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20231126T024537Z&X-Amz-Expires=3600&X-Amz-Signature=60184f099b79dfa496ebc2d27c6cf58ffea13e8f98e0c28352e60c06a98346eb&X-Amz-SignedHeaders=host&x-id=GetObject
So image URLs here have & which satori very nicely (NOT!) escapes leading to invalid url for image. I just…cannot stop laughing. It has been 10 hours.
Nov 26, 2023, 06:31 AM : got it to work for all posts. It might fail if it does not understand width/height, but I think that is okay?
Lol, I added this for my sanity. GPT is awesome for such things
let svg;
  try {
    svg = await satori(chosen_markup, ogOptions);
  } catch (error) {
    console.error("Error in satori:", error);
    // Fallback to a basic markup if satori fails
    const fallback_markup = post?.Excerpt ? obj_img_none_with_desc(title, postDate, post?.Excerpt) : obj_img_none_without_desc(title, postDate);
    svg = await satori(fallback_markup, ogOptions);
  }

Look at the cache stuff in otoyo’s post. Maybe it is just for cloudfare? Idk!
THEME AND FONTS!
Logo to database icon!
Set favicon!
Side note, apparently if it is a video embed but not youtube, it is not rendered? Maybe call the generic embed in that case?
Code here
videoUrl &&
			(url ? (
				isYouTubeURL(url) ? (
					<iframe
          class="w-full h-[340px] max-w-sm:h-[220px]"
						src={`https://www.youtube.com/embed/${parseYouTubeVideoId(url)}`}
						title="YouTube video player"
						allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
						allowfullscreen
					/>
				) : null
Caption is not rendering links because of this, need to shift it to a rich text output. Caption cannot have children, I think! Yes, they cannot!
{
	richTexts.length > 0 && richTexts[0].Text.Content && (
		<div class="caption pt-1 text-sm">{richTexts[0].Text.Content}</div>
	)
}
Heading toggle clashes with the # sign, what is a good idea here? maybe especially change margin for those??
Nov 26, 2023
Do not show pages with slug starting with underscore in
collection index page,
posts page,
tag page,
rss,
sitemap
links to main pages and pages as URL seems to be broken?
Logo to database icon!
Side note, apparently if it is a video embed but not youtube, it is not rendered? Maybe call the generic embed in that case?
Code here
videoUrl &&
			(url ? (
				isYouTubeURL(url) ? (
					<iframe
          class="w-full h-[340px] max-w-sm:h-[220px]"
						src={`https://www.youtube.com/embed/${parseYouTubeVideoId(url)}`}
						title="YouTube video player"
						allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
						allowfullscreen
					/>
				) : null
Why the fudge does it keep scrolling to tiktok man?
I am having scroll issues with it and can’t set muted and sometimes a scrollbar shows up in iframe. I just used muted on blockquote
<lite-tiktok videoid={videoId} autoload></lite-tiktok>;

<script>
	//Copied from https://github.com/justinribeiro/lite-tiktok because I could not get import to work for some stupid reason
	/**
	 *
	 * A web component that lazy loads TikTok embeds. Currently experimental and a work in progress.
	 *
	 * A less interesting variation of lite-youtube (https://github.com/justinribeiro/lite-youtube)
	 *
	 */
	export class LiteTTEmbed extends HTMLElement {
		shadowRoot!: ShadowRoot;
		private domRefFrame!: HTMLDivElement;
		private domRefImg!: {
			fallback: HTMLImageElement;
			webp: HTMLSourceElement;
			jpeg: HTMLSourceElement;
		};
		private domRefPlayButton!: HTMLButtonElement;
		private static isPreconnected = false;
		private isIframeLoaded = false;

		constructor() {
			super();
			this.setupDom();
		}

		static get observedAttributes(): string[] {
			return ["videoid", "playlistid"];
		}

		connectedCallback(): void {
			this.addEventListener("pointerover", LiteTTEmbed.warmConnections, {
				once: true,
			});

			this.addEventListener("click", () => this.addIframe());
		}

		get videoId(): string {
			return encodeURIComponent(this.getAttribute("videoid") || "");
		}

		set videoId(id: string) {
			this.setAttribute("videoid", id);
		}

		get autoLoad(): boolean {
			return this.hasAttribute("autoload");
		}

		set __data(obj: any) {
			this.__data = obj;
		}

		get __data(): any {
			return this.__data;
		}

		/**
		 * Define our shadowDOM for the component
		 */
		private setupDom(): void {
			const shadowDom = this.attachShadow({ mode: "open" });
			shadowDom.innerHTML = `
      <style>
        :host {
          contain: content;
          display: block;
          position: relative;
          width: 100%;
          height: 735px;
        }

        #frame, iframe {
          position: absolute;
          width: 100%;
          height: 100%;
          left: 0;
        }

        #frame {
          cursor: pointer;
        }

        #fake {
          display: flex;
          position: relative;
          height: 100%;
          justify-content: center;
          background: #000;
          width: 80%;
          margin: auto;
        }

        #logo {
          position: absolute;
          width: 48px;
          height: 48px;
          margin: 1rem;
          right: 0;
        }

        #fallbackPlaceholder {
          height: 100%;
          position: fixed;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }

        #playButton {
          width: 150px;
          height: 150px;
          background-color: transparent;
          z-index: 1;
          border: 0;
        }

        #playButton:before {
          content: '';
          border-style: solid;
          border-width: 22px 0 22px 40px;
          border-color: transparent transparent transparent #fff;
        }

        #playButton,
        #playButton:before {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate3d(-50%, -50%, 0);
          cursor: inherit;
        }

        /* Post-click styles */
        .activated {
          cursor: unset;
        }

        #frame.activated::before,
        #frame.activated > #fake {
          display: none;
        }
      </style>
      <div id="frame">
        <div id="fake">
          <picture>
            <source id="jpegPlaceholder" type="image/jpeg">
            <img id="fallbackPlaceholder" referrerpolicy="origin" loading="lazy">
          </picture>
          <button id="playButton"></button>
          <svg id="logo" xmlns="http://www.w3.org/2000/svg"><path d="M19.8 19.8v-1.6L18 18a12.1 12.1 0 0 0-7 22 12 12 0 0 1 8.6-20.3Z" fill="#25F4EE"/><path d="M20 37.4c3 0 5.5-2.4 5.6-5.3V5.7h4.8L30.3 4h-6.6v26.4a5.5 5.5 0 0 1-8.1 4.7c1 1.4 2.6 2.3 4.4 2.3Zm19.4-22.7v-1.5c-1.8 0-3.5-.6-5-1.5a9 9 0 0 0 5 3Z" fill="#25F4EE"/><path d="M34.4 11.7a9.1 9.1 0 0 1-2.2-6h-1.8c.4 2.5 2 4.6 4 6ZM18.1 24.6c-3 0-5.6 2.5-5.6 5.6 0 2.1 1.3 4 3 4.9a5.5 5.5 0 0 1 6.2-8.5v-6.7l-1.7-.2h-.2v5.1l-1.7-.2Z" fill="#FE2C55"/><path d="M39.4 14.6v5.1c-3.4 0-6.6-1.1-9.2-3v13.5a12 12 0 0 1-19 10 12.1 12.1 0 0 0 21-8.3V18.5a16 16 0 0 0 9.1 3v-6.7c-.6 0-1.3 0-1.9-.2Z" fill="#FE2C55"/><path d="M30.3 30.2V16.8a15 15 0 0 0 9.1 3v-5.2c-2-.4-3.7-1.4-5-3a9.2 9.2 0 0 1-4-6h-4.8v26.5a5.5 5.5 0 0 1-10 3 5.5 5.5 0 0 1 4.2-10.2v-5.2a12.1 12.1 0 0 0-8.6 20.4 12 12 0 0 0 19-9.9Z" fill="#fff"/></svg>
        </div>
      </div>
    `;
			this.domRefFrame = shadowDom.querySelector<HTMLDivElement>("#frame")!;
			this.domRefImg = {
				fallback: shadowDom.querySelector("#fallbackPlaceholder")!,
				webp: shadowDom.querySelector("#webpPlaceholder")!,
				jpeg: shadowDom.querySelector("#jpegPlaceholder")!,
			};
			this.domRefPlayButton = shadowDom.querySelector("#playButton")!;
		}

		/**
		 * Parse our attributes and fire up some placeholders
		 */
		private async setupComponent(): Promise<void> {
			// Don't take the hit if the autoload is set because this is all just too
			// expensive given the base embeds weight
			if (!this.autoLoad) {
				const request = await fetch(
					`https://www.tiktok.com/oembed?url=https://www.tiktok.com/video/${this.videoId}`,
				);
				const data = await request.json();

				this.initImagePlaceholder(data);

				this.domRefPlayButton.setAttribute("aria-label", `Play: ${data.title}`);
				this.setAttribute("title", `Play: ${data.title}`);
			} else {
				this.initIntersectionObserver();
			}
		}

		/**
		 * Lifecycle method that we use to listen for attribute changes to period
		 * @param {*} name
		 * @param {*} oldVal
		 * @param {*} newVal
		 */
		attributeChangedCallback(name: string, oldVal: unknown, newVal: unknown): void {
			switch (name) {
				case "videoid": {
					if (oldVal !== newVal) {
						this.setupComponent();

						// if we have a previous iframe, remove it and the activated class
						if (this.domRefFrame.classList.contains("activated")) {
							this.domRefFrame.classList.remove("activated");
							this.shadowRoot.querySelector("iframe")!.remove();
							this.isIframeLoaded = false;
						}
					}
					break;
				}
				default:
					break;
			}
		}

		/**
		 * Inject the iframe into the component body
		 * @param {boolean} isIntersectionObserver
		 */
		private addIframe(isIntersectionObserver = false): void {
			if (!this.isIframeLoaded) {
				const iframeHTML = `
<iframe frameborder="0"
  sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-top-navigation allow-same-origin"
  src="https://www.tiktok.com/embed/v2/${this.videoId}">
</iframe>
`;
				this.domRefFrame.insertAdjacentHTML("beforeend", iframeHTML);
				this.domRefFrame.classList.add("activated");
				this.isIframeLoaded = true;
			}
		}

		/**
		 * Setup the placeholder image for the component
		 */
		private initImagePlaceholder(data: any): void {
			this.domRefImg.jpeg.srcset = data.thumbnail_url;
			this.domRefImg.fallback.src = data.thumbnail_url;
			this.domRefImg.fallback.setAttribute("aria-label", `Play: ${data.title}`);
			this.domRefImg?.fallback?.setAttribute("alt", `Play: ${data.title}`);
		}

		/**
		 * Setup the Intersection Observer to load the iframe when scrolled into view
		 */
		private initIntersectionObserver(): void {
			const options = {
				root: null,
				rootMargin: "0px",
				threshold: 0,
			};

			const observer = new IntersectionObserver((entries, observer) => {
				entries.forEach((entry) => {
					if (entry.isIntersecting && !this.isIframeLoaded) {
						LiteTTEmbed.warmConnections();
						this.addIframe(true);
						observer.unobserve(this);
					}
				});
			}, options);

			observer.observe(this);
		}

		/**
		 * Add a <link rel={preload | preconnect} ...> to the head
		 * @param {string} kind
		 * @param {string} url
		 * @param {string} as
		 */
		private static addPrefetch(kind: string, url: string): void {
			const linkElem = document.createElement("link");
			linkElem.rel = kind;
			linkElem.href = url;
			linkElem.crossOrigin = "true";
			document.head.append(linkElem);
		}

		/**
		 * Begin preconnecting to warm up the iframe load Since the embed's network
		 * requests load within its iframe, preload/prefetch'ing them outside the
		 * iframe will only cause double-downloads. So, the best we can do is warm up
		 * a few connections to origins that are in the critical path.
		 *
		 * Maybe `<link rel=preload as=document>` would work, but it's unsupported:
		 * http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site
		 * Isolation and split caches adding serious complexity.
		 */
		private static warmConnections(): void {
			if (LiteTTEmbed.isPreconnected) return;

			LiteTTEmbed.addPrefetch("preconnect", "https://www.tiktok.com");
			LiteTTEmbed.addPrefetch("preconnect", "https://mcs.us.tiktok.com");
			LiteTTEmbed.addPrefetch("preconnect", "https://mon.us.tiktokv.com");

			LiteTTEmbed.isPreconnected = true;
		}
	}
	// Register custom element
	customElements.define("lite-tiktok", LiteTTEmbed);
</script>
Nov 26, 2023, 10:42 PM
how do i use webmentions? #LATER
Look at the cache stuff in otoyo’s post. Maybe it is just for cloudfare? Idk! #LATER
Set favicon!
Why does otoyo add everything to public and not to src? Maybe adding to src will help with optimization?
This is gonna be difficult because while Image has bundling things, everything else does not? and because everything else does not, it needs to be referenced using public anyway. So, I can technically do this where I route downloaded images, videos, audio and files to different places and import using that, but that is too much work and I ain’t doing that!
Trying to optimize just images
Original
export async function downloadFile(url: URL) {
  let res!: AxiosResponse;
  try {
    res = await axios({
      method: "get",
      url: url.toString(),
      timeout: REQUEST_TIMEOUT_MS,
      responseType: "stream",
    });
  } catch (err) {
    console.log(err);
    return Promise.resolve();
  }

  if (!res || res.status != 200) {
    console.log(res);
    return Promise.resolve();
  }

  const BASE_DIR = "./public/notion/";
  // const BASE_DIR = "./src/notion-assets/";
  if (!fs.existsSync(BASE_DIR)) {
    fs.mkdirSync(BASE_DIR);
  }

  const dir = BASE_DIR + url.pathname.split("/").slice(-2)[0];
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir);
  }

  const filename = decodeURIComponent(url.pathname.split("/").slice(-1)[0]);
  const filepath = `${dir}/${filename}`;

  const writeStream = fs.createWriteStream(filepath);
  const rotate = sharp().rotate();

  let stream = res.data;

  if (res.headers["content-type"] === "image/jpeg") {
    stream = stream.pipe(rotate);
  }
  stream.pipe(new ExifTransformer()).pipe(writeStream);
}
New
export async function downloadFile(url: URL, optimize_img = true) {
  let res!: AxiosResponse;
  try {
    res = await axios({
      method: "get",
      url: url.toString(),
      timeout: REQUEST_TIMEOUT_MS,
      responseType: "stream",
    });
  } catch (err) {
    console.log(err);
    return Promise.resolve();
  }

  if (!res || res.status != 200) {
    console.log(res);
    return Promise.resolve();
  }

  const BASE_DIR = "./public/notion/";
  if (!fs.existsSync(BASE_DIR)) {
    fs.mkdirSync(BASE_DIR);
  }

  const dir = BASE_DIR + url.pathname.split("/").slice(-2)[0];
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir);
  }

  const filename = decodeURIComponent(url.pathname.split("/").slice(-1)[0]);
  const filepath = `${dir}/${filename}`;

  let stream = res.data;
  if (res.headers["content-type"] === "image/jpeg") {
    stream = stream.pipe(sharp().rotate());
  }

  const isImage = res.headers["content-type"]?.startsWith("image/");
  if (isImage && optimize_img) {
    // Process and write only the optimized WebP image
    // const webpPath = `${dir}/${filename.split('.')[0]}.webp`;
    const webpPath = `${dir}/${filename.substring(0, filename.lastIndexOf('.'))}.webp`
    stream.pipe(sharp()
      // .resize({ width: 1024 }) // Adjust the size as needed for "medium"
      .webp({ quality: 80 })) // Adjust quality as needed
      .toFile(webpPath)
      .catch(err => {
        console.error('Error processing image:', err);
      });
  } else {
    // Original behavior for non-image files or when not optimizing
    const writeStream = fs.createWriteStream(filepath);
    stream.pipe(new ExifTransformer()).pipe(writeStream);
  }
}
Headings as nested children do not show up in ToC
Process
So, blocks show up as
{
    Id: 'f042d61d-6289-49b0-9276-945047b8561f',
    Type: 'column_list',
    HasChildren: true,
    ColumnList: { Columns: [Array] }
  },
{
    Id: '44a84a9a-60e6-443d-8434-e5a9d43ff06a',
    Type: 'numbered_list_item',
    HasChildren: true,
    NumberedListItem: { RichTexts: [Array], Color: 'default', Children: [Array] }
  },
so, I will need to flatten these I guess?

I can do something like this but I am not sure I want it. Plan cancelled (even Notion does not do it!)

export function buildHeadings(blocks: Block[]): Heading[] {
  // Helper function to flatten blocks recursively
  function flattenBlocks(blocks: Block[]): Block[] {
    return blocks.reduce((acc, block) => {
      acc.push(block);

      // Handle column_list blocks
      if (block.Type === 'column_list' && block.ColumnList) {
        block.ColumnList.Columns.forEach(column => {
          if (column.HasChildren && column.Children) {
            acc.push(...flattenBlocks(column.Children));
          }
        });
      }
      // Recursively flatten other blocks except table_row
      else if (block.Type !== 'table_row' && block.HasChildren && (block.Paragraph || block.Heading1 || block.Heading2 || block.Heading3 || block.BulletedListItem || block.NumberedListItem || block.ToDo || block.Quote || block.Callout || block.SyncedBlock || block.Toggle).Children) {
        acc.push(...flattenBlocks((block.Paragraph || block.Heading1 || block.Heading2 || block.Heading3 || block.BulletedListItem || block.NumberedListItem || block.ToDo || block.Quote || block.Callout || block.SyncedBlock || block.Toggle).Children));
      }

      return acc;
    }, [] as Block[]);
  }

  // Flatten the block structure
  const allBlocks = flattenBlocks(blocks);

  // Filter for heading blocks
  return allBlocks.filter(block => HEADING_BLOCKS.includes(block.Type)).map(cleanHeading);
}
Links on this page #Medium Term LATER Might need some flattening like thisShow information for the linked content . And see this is a good use of not having all headings show up in ToC because I can use them for linking internally while still not cluttering stuff! Win Win!
Add any html/md content to posts. I am not sure how this works and I’ll need to figure out a way but that is too much work man.
Only show last updated if it is not same
THEME AND FONTS AND CSS!!
Replaced 404 page from here
Colors
Original tailwind.config.cs for safety purposes
import type { Config } from "tailwindcss";
import { fontFamily } from "tailwindcss/defaultTheme";
import plugin from "tailwindcss/plugin";
export default {
  content: ["./src/**/*.{astro,html,js,jsx,md,svelte,ts,tsx,vue}"],
  darkMode: "class",
  corePlugins: {
    // disable aspect ratio as per docs -> @tailwindcss/aspect-ratio
    aspectRatio: false,
    // disable some core plugins as they are included in the css, even when unused
    touchAction: false,
    ringOffsetWidth: false,
    ringOffsetColor: false,
    scrollSnapType: false,
    borderOpacity: false,
    textOpacity: false,
    fontVariantNumeric: false,
  },
  theme: {
    extend: {
      colors: {
        bgColor: "hsl(var(--theme-bg) / <alpha-value>)",
        textColor: "hsl(var(--theme-text) / <alpha-value>)",
        link: "hsl(var(--theme-link) / <alpha-value>)",
        accent: "hsl(var(--theme-accent) / <alpha-value>)",
        "accent-2": "hsl(var(--theme-accent-2) / <alpha-value>)",
        quote: "hsl(var(--theme-quote) / <alpha-value>)",
      },
      fontFamily: {
        // Add any custom fonts here
        sans: [...fontFamily.sans],
        serif: [...fontFamily.serif],
        mono: [...fontFamily.mono]
      },
      transitionProperty: {
        height: "height",
      },
 eslint-disable-next-line @typescript-eslint/ban-ts-comment
      @ts-ignore
      Remove above once tailwindcss exposes theme type
      typography: (theme) => ({
        cactus: {
          css: {
            "--tw-prose-body": theme("colors.textColor / 1"),
            "--tw-prose-headings": theme("colors.accent-2 / 1"),
            "--tw-prose-links": theme("colors.textColor / 1"),
            "--tw-prose-bold": theme("colors.textColor / 1"),
            "--tw-prose-bullets": theme("colors.textColor / 1"),
            "--tw-prose-quotes": theme("colors.quote / 1"),
            "--tw-prose-code": theme("colors.textColor / 1"),
            "--tw-prose-hr": "0.5px dashed #666",
            "--tw-prose-th-borders": "#666",
          },
        },
        DEFAULT: {
          css: {
            a: {
              "@apply cactus-link no-underline": "",
            },
            strong: {
              fontWeight: "700",
            },
            code: {
              border: "1px dotted #666",
              borderRadius: "2px",
            },
            blockquote: {
              borderLeftWidth: "0",
            },
            hr: {
              borderTopStyle: "dashed",
            },
            thead: {
              borderBottomWidth: "none",
            },
            "thead th": {
              fontWeight: "700",
              borderBottom: "1px dashed #666",
            },
            "tbody tr": {
              borderBottomWidth: "none",
            },
            tfoot: {
              borderTop: "1px dashed #666",
            },
            sup: {
              "@apply ms-0.5": "",
              a: {
                "@apply bg-none": "",
                "&:hover": {
                  "@apply text-link no-underline bg-none": "",
                },
                "&:before": {
                  content: "'['",
                },
                "&:after": {
                  content: "']'",
                },
              },
            },
          },
        },
        sm: {
          css: {
            code: {
              fontSize: theme("fontSize.sm")[0],
              fontWeight: "400",
            },
          },
        },
      }),
    },
  },
  plugins: [
    // require("@tailwindcss/typography"),
    require("@tailwindcss/aspect-ratio"),
    plugin(function ({ addComponents }) {
      addComponents({
        ".cactus-link": {
          "@apply bg-[size:100%_6px] bg-bottom bg-repeat-x": {},
          backgroundImage:
            "linear-gradient(transparent,transparent 5px,hsl(var(--theme-text)) 5px,hsl(var(--theme-text)))",
          "&:hover": {
            backgroundImage:
              "linear-gradient(transparent,transparent 4px,hsl(var(--theme-link)) 4px,hsl(var(--theme-link)))",
          },
        },
        ".title": {
          "@apply text-2xl font-semibold text-accent-2": {},
        },
      });
    }),
  ],
} satisfies Config;
Original global.css for safety purposes
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    color-scheme: light;
    /* https://tailwindcss.com/docs/customizing-colors#using-css-variables */
    --theme-bg: 0deg 0% 98%;
    --theme-link: 172deg 18% 41%;
    --theme-text: 203deg 11% 15%;
    --theme-accent: 351deg 66% 48%;
    --theme-accent-2: 0deg 0% 7%;
    --theme-quote: 351deg 66% 48%;
  }

  :root.dark {
    color-scheme: dark;
    --theme-bg: 210deg 6% 12%;
    --theme-link: 330deg 49% 67%;
    --theme-text: 220deg 3% 79%;
    --theme-accent: 159deg 64% 45%;
    --theme-accent-2: 0deg 0% 93%;
    --theme-quote: 102deg 100% 86%;
  }

  html {
    @apply scroll-smooth;
  }

  html body {
    @apply mx-auto flex min-h-screen max-w-3xl flex-col bg-bgColor px-8 pt-8 text-textColor antialiased overflow-x-hidden;
  }
}
Process
Focussing here
extend: {
      colors: {
        bgColor: "hsl(var(--theme-bg) / <alpha-value>)",
        textColor: "hsl(var(--theme-text) / <alpha-value>)",
        link: "hsl(var(--theme-link) / <alpha-value>)",
        accent: "hsl(var(--theme-accent) / <alpha-value>)",
        "accent-2": "hsl(var(--theme-accent-2) / <alpha-value>)",
        quote: "hsl(var(--theme-quote) / <alpha-value>)",
      },
      fontFamily: {
        // Add any custom fonts here
        sans: [...fontFamily.sans],
        serif: [...fontFamily.serif],
        mono: [...fontFamily.mono]
      },
Why are theses themed this way? I am losing hope for my method
<style is:global>
	:root {
		--pagefind-ui-font: inherit;
	}

	#cactus__search .pagefind-ui__search-clear {
		width: calc(60px * var(--pagefind-ui-scale));
		padding: 0;
		background-color: transparent;
		overflow: hidden;
	}
	#cactus__search .pagefind-ui__search-clear:focus {
		outline: 1px solid theme("colors.accent-2.DEFAULT");
	}
	#cactus__search .pagefind-ui__search-clear::before {
		content: "";
		-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' %3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12'%3E%3C/path%3E%3C/svg%3E")
			center / 60% no-repeat;
		mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' %3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12'%3E%3C/path%3E%3C/svg%3E")
			center / 60% no-repeat;
		background-color: theme("colors.accent.DEFAULT");
		display: block;
		width: 100%;
		height: 100%;
	}

	#cactus__search .pagefind-ui__result {
		border: 0;
	}

	#cactus__search .pagefind-ui__result-link {
		background-size: 100% 6px;
		background-position: bottom;
		background-repeat: repeat-x;
		background-image: linear-gradient(
			transparent,
			transparent 5px,
			theme("colors.textColor.DEFAULT") 5px,
			theme("colors.textColor.DEFAULT")
		);
	}

	#cactus__search .pagefind-ui__result-link:hover {
		text-decoration: none;
		background-image: linear-gradient(
			transparent,
			transparent 4px,
			theme("colors.link.DEFAULT") 4px,
			theme("colors.link.DEFAULT")
		);
	}

	#cactus__search mark {
		color: theme("colors.quote.DEFAULT");
		background-color: transparent;
		font-weight: 600;
	}
</style>

<style>
	#cactus__search {
		--pagefind-ui-primary: theme("colors.accent.DEFAULT");
		--pagefind-ui-text: theme("colors.textColor.DEFAULT");
		--pagefind-ui-background: theme("colors.bgColor.DEFAULT");
		--pagefind-ui-border: theme("colors.zinc.400");
		--pagefind-ui-border-width: 1px;
	}
</style>
Especially because, there is this too!
const colorThemeMetaTag = document.querySelector("meta[name='theme-color']");
		document.documentElement.classList.toggle("dark", newTheme === "dark");
		const bgColour = getComputedStyle(document.body).getPropertyValue("--theme-bg");

		colorThemeMetaTag.setAttribute("content", `hsl(${bgColour})`);
		if (typeof localStorage !== "undefined") {
			localStorage.setItem("theme", newTheme);
		}
	}
Saving current mod if I change my mind
colors: {
        bgColor:
        {
          DEFAULT: "hsl(0deg 0% 98% / <alpha-value>)",
          "dark": "hsl(210deg 6% 12% / <alpha-value>)"
        },
        textColor:
        {
          DEFAULT: "hsl(var(--theme-text) / <alpha-value>)",
          "dark": "hsl(var(--theme-text) / <alpha-value>)"
        },
        link:
        {
          DEFAULT: "hsl(var(--theme-link) / <alpha-value>)",
          "dark": "hsl(var(--theme-link) / <alpha-value>)"
        },
        accent:
        {
          DEFAULT: "hsl(var(--theme-accent) / <alpha-value>)",
          "dark": "hsl(var(--theme-accent) / <alpha-value>)"
        },
        "accent-2":
        {
          DEFAULT: "hsl(var(--theme-accent-2) / <alpha-value>)",
          "dark": "hsl(var(--theme-accent-2) / <alpha-value>)"
        },
        "quote":
        {
          DEFAULT: "hsl(var(--theme-quote) / <alpha-value>)",
          "dark": "hsl(var(--theme-quote) / <alpha-value>)"
        }
      },
Fonts
From lillian weng’s blog
font-family: -apple-system,BlinkMacSystemFont,segoe ui,Roboto,Oxygen,Ubuntu,Cantarell,open sans,helvetica neue,sans-serif;
Header expands completely?
Process
Somehow, the sticky header part is not working, will look later
Is it even adding much benefit then? I do not like the part where it shifts layout rather than popup 😕
Code if I need again (Search terms, astro, navbar, astro-navbar)
---
import { Dropdown as DropdownContainer, DropdownItems } from "astro-navbar";
const { title, lastItem, children } = Astro.props;
---

<li class="relative">
  <DropdownContainer class="group">
    <button
      class="flex items-center gap-1 w-full lg:w-auto lg:px-3 py-2 text-gray-600 hover:text-gray-900">
      <span>{title}</span>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        stroke-width="3"
        stroke="currentColor"
        class="w-3 h-3 mt-0.5 group-open:rotate-180">
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path>
      </svg>
    </button>
    <DropdownItems>
      <div
        class:list={[
          "lg:absolute w-full lg:w-48 z-10",
          lastItem
            ? "lg:right-0 origin-top-right"
            : "lg:left-0 origin-top-left",
        ]}>
        <div
          class="px-3 lg:py-2 lg:bg-white lg:rounded-md lg:shadow lg:border flex flex-col">
          {
            children.map((item) => (
              <a
                href={item.path}
                class="py-1 text-gray-600 hover:text-gray-900">
                {item.title}
              </a>
            ))
          }
        </div>
      </div>
    </DropdownItems>
  </DropdownContainer>
</li>
---
interface Props {
  href: string;
  size?: "md" | "lg";
  block?: boolean;
  style?: "outline" | "primary" | "inverted" | "muted";
  class?: string;
  [x: string]: any;
}

const {
  href,
  block,
  size = "lg",
  style = "primary",
  class: className,
  ...rest
} = Astro.props;

const sizes = {
  lg: "px-5 py-2.5",
  md: "px-4 py-2",
};

const styles = {
  outline: "bg-white border-2 border-black hover:bg-gray-100 text-black ",
  primary: "bg-black text-white hover:bg-gray-800  border-2 border-transparent",
  inverted: "bg-white text-black   border-2 border-transparent",
  muted: "bg-gray-100 hover:bg-gray-200   border-2 border-transparent",
};
---

<a
  href={href}
  {...rest}
  class:list={[
    "rounded text-center transition focus-visible:ring-2 ring-offset-2 ring-gray-200",
    block && "w-full",
    sizes[size],
    styles[style],
    className,
  ]}
  ><slot />
</a>
---
import Link from "./link.astro";
import Dropdown from "./dropdown.astro";
import { Astronav, MenuItems, MenuIcon, StickyHeader } from "astro-navbar";

import ThemeToggle from "../ThemeToggle.astro";
import Search from "../Search.astro";
import { getMenu } from "@/utils";
import { siteInfo } from "@/site.config";
import { filePath } from "@/lib/blog-helpers";

const menuitems = await getMenu();

const url = new URL(Astro.request.url);

let customIconURL: string = "";
if (siteInfo.logo && siteInfo.logo.Type === "file") {
	try {
		customIconURL = filePath(new URL(siteInfo.logo.Url));
	} catch (err) {
		console.log("Invalid DB custom icon URL");
	}
}

const menuitemsold = [
	{
		title: "Features",
		path: "#",
		children: [
			{ title: "Action", path: "/" },
			{ title: "Another action", path: "#" },
			{ title: "Dropdown Submenu", path: "#" },
			{ title: "404 Page", path: "/404" },
		],
	},
	{
		title: "Pricing",
		path: "/pricing",
	},
	{
		title: "About",
		path: "/about",
	},
	{
		title: "Blog",
		path: "/blog",
	},
	{
		title: "Contact",
		path: "/contact",
	},
	{
		title: "Pro Version",
		badge: true,
		path: "https://astroship-pro.web3templates.com/",
	},
];
---

<div class="max-w-screen-xl px-5">
	<StickyHeader
		class="sticky top-0 z-20 transition-all"
		scrollY={50}
		defaultClass="py-5 border-transparent"
		activeClass="py-2 backdrop-blur-lg"
	>
		<Astronav closeOnClick>
			<div class="flex w-full items-center justify-between lg:w-auto">
				<a
					href="/"
					class="inline-flex items-center grayscale hover:filter-none sm:relative sm:inline-block"
					aria-current={url.pathname === "/" ? "page" : false}
				>
					{
						siteInfo.logo && siteInfo.logo.Type === "emoji" ? (
							<>
								<span class="sm:h-15 sm:w-15 me-3 h-10 w-10 sm:absolute sm:start-[-4.5rem] sm:me-0">
									{siteInfo.logo.Emoji}
								</span>
								<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
							</>
						) : siteInfo.logo && siteInfo.logo.Type === "external" ? (
							<>
								<img
									class="sm:h-15 sm:w-15 me-3 h-10 w-10 object-contain sm:absolute sm:start-[-4.5rem] sm:me-0"
									src={siteInfo.logo.Url}
								/>
								<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
							</>
						) : siteInfo.logo && siteInfo.logo.Type === "file" && customIconURL ? (
							<>
								<img
									src={customIconURL}
									class="sm:h-15 sm:w-15 me-3 h-10 w-10 object-contain sm:start-[-4.5rem] sm:me-0"
								/>
								<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
							</>
						) : (
							<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
						)
					}
				</a>
				<div class="block lg:hidden">
					<MenuIcon class="h-4 w-4 text-gray-800" />
				</div>
			</div>
			<MenuItems class="mt-2 hidden w-full lg:mt-0 lg:flex lg:w-auto">
				<ul class="flex flex-col lg:flex-row lg:gap-3">
					{
						menuitems.map((item, index) => (
							<>
								{item.children && (
									<Dropdown
										title={item.title}
										children={item.children}
										lastItem={index === menuitems.length - 1}
									/>
								)}

								{!item.children && (
									<li>
										<a
											href={item.path}
											class="flex items-center py-2 text-gray-600 hover:text-gray-900 lg:px-3"
										>
											<span> {item.title}</span>
											{item.badge && (
												<span class="ml-1 animate-pulse rounded-full bg-indigo-600 px-2 py-0.5 text-[10px] font-semibold uppercase text-white">
													New
												</span>
											)}
										</a>
									</li>
								)}
							</>
						))
					}
				</ul>
				<div class="mt-3 flex items-center gap-4 lg:hidden">
					<Link href="#" style="muted" block size="md">Log in</Link>
					<Link href="#" size="md" block>Sign up</Link>
				</div>
			</MenuItems>
		</Astronav>
		<div>
			<div class="hidden items-center gap-4 lg:flex">
				<a href="#">Log in</a>
				<Link href="#" size="md">Sign up</Link>
			</div>
		</div>
	</StickyHeader>
</div>
I don’t know what I messed up, restart time for CSS, dumping modified header code here (Changed <main id="main" class="mx-auto max-w-prose px-8"> to have no class
---
import ThemeToggle from "../ThemeToggle.astro";
import Search from "../Search.astro";
import { getMenu } from "@/utils";
import { siteInfo } from "@/site.config";
import { filePath } from "@/lib/blog-helpers";

const menuLinks = await getMenu();

const url = new URL(Astro.request.url);

let customIconURL: string = "";
if (siteInfo.logo && siteInfo.logo.Type === "file") {
	try {
		customIconURL = filePath(new URL(siteInfo.logo.Url));
	} catch (err) {
		console.log("Invalid DB custom icon URL");
	}
}
---

<script>
	class MobileNavBtn extends HTMLElement {
		constructor() {
			super();

			const mobileButtonEl = document.getElementById("toggle-navigation-menu") as HTMLButtonElement;
			let menuOpen = false;

			function toggleMobileMenu() {
				const headerEl = document.getElementById("main-header")!;
				headerEl.classList.toggle("menu-open");
				menuOpen = !menuOpen;
				mobileButtonEl.setAttribute("aria-expanded", menuOpen.toString());
			}

			mobileButtonEl.addEventListener("click", () => toggleMobileMenu());

			// document.addEventListener("astro:after-swap", () => {
			// 	if (menuOpen) toggleMobileMenu();
			// });
		}
	}

	customElements.define("mobile-button", MobileNavBtn);

	document.addEventListener("DOMContentLoaded", function () {
		var toggleButton = document.getElementById("toggle-navigation-menu");

		document.addEventListener("click", function (event) {
			var navMenu = document.getElementById("navigation-menu");
			var mainHeader = document.getElementById("main-header");

			// Check if the click is on the toggle button or a descendant of it
			var isClickInsideToggleButton = toggleButton.contains(event.target);

			if (!isClickInsideToggleButton && event.target instanceof Node) {
				var isClickInsideNav = navMenu.contains(event.target);

				if (!isClickInsideNav && mainHeader) {
					// If the click is outside the navMenu and mainHeader exists, remove 'menu-open' attribute
					mainHeader.classList.remove("menu-open");
				}
			}
		});
	});
</script>
<!-- <div class="mx-auto max-w-screen-xl px-5"> -->
<header
	id="main-header"
	class="group relative my-5 mb-16 flex items-center justify-between sm:ps-[4.5rem]"
	transition:animate="none"
>
	<div class="flex w-full items-center justify-between lg:w-auto">
		<a
			href="/"
			class="inline-flex items-center grayscale hover:filter-none sm:relative sm:inline-block"
			aria-current={url.pathname === "/" ? "page" : false}
		>
			{
				siteInfo.logo && siteInfo.logo.Type === "emoji" ? (
					<>
						<span class="sm:h-15 sm:w-15 me-3 h-10 w-10 sm:absolute sm:start-[-4.5rem] sm:me-0">
							{siteInfo.logo.Emoji}
						</span>
						<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
					</>
				) : siteInfo.logo && siteInfo.logo.Type === "external" ? (
					<>
						<img
							class="sm:h-15 sm:w-15 me-3 h-10 w-10 object-contain sm:absolute sm:start-[-4.5rem] sm:me-0"
							src={siteInfo.logo.Url}
						/>
						<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
					</>
				) : siteInfo.logo && siteInfo.logo.Type === "file" && customIconURL ? (
					<>
						<img
							src={customIconURL}
							class="sm:h-15 sm:w-15 me-3 h-10 w-10 object-contain sm:absolute sm:start-[-4.5rem] sm:me-0"
						/>
						<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
					</>
				) : (
					<span class="text-xl font-bold sm:text-2xl">{siteInfo.title}</span>
				)
			}
		</a>
	</div>

	<nav
		id="navigation-menu"
		class="absolute -inset-x-4 top-14 mt-2 flex hidden w-full flex-col items-end rounded-md bg-bgColor/[.97] py-2 text-accent shadow backdrop-blur group-[.menu-open]:z-50 group-[.menu-open]:flex sm:static sm:z-auto sm:-ms-4 sm:mt-1 sm:flex sm:flex-row sm:items-center sm:rounded-none sm:bg-transparent sm:py-0 sm:shadow-none sm:backdrop-blur-none lg:mt-0 lg:flex lg:w-auto lg:flex-row lg:gap-3"
		aria-label="Main menu"
	>
		{
			menuLinks.map((link) => (
				<a
					href={link.path}
					class="flex flex-col px-4 py-4 sm:py-0 sm:hover:underline lg:flex-row lg:gap-3"
					aria-current={url.pathname === link.path ? "page" : false}
					rel="prefetch"
				>
					{link.title}
				</a>
			))
		}
	</nav>
	<div class="items-center gap-4 lg:flex">
		<Search />
		<ThemeToggle />
	</div>
	<mobile-button>
		<button
			id="toggle-navigation-menu"
			class="group relative ms-4 h-7 w-7 sm:invisible sm:hidden"
			type="button"
			aria-label="Open main menu"
			aria-expanded="false"
			aria-haspopup="menu"
		>
			<svg
				id="line-svg"
				class="absolute start-1/2 top-1/2 h-full w-full -translate-x-1/2 -translate-y-1/2 transition-all group-aria-expanded:scale-0 group-aria-expanded:opacity-0"
				aria-hidden="true"
				focusable="false"
				xmlns="http://www.w3.org/2000/svg"
				fill="none"
				viewBox="0 0 24 24"
				stroke-width="1.5"
				stroke="currentColor"
			>
				<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9h16.5m-16.5 6.75h16.5"
				></path>
			</svg>
			<svg
				id="cross-svg"
				class="absolute start-1/2 top-1/2 h-full w-full -translate-x-1/2 -translate-y-1/2 scale-0 text-accent opacity-0 transition-all group-aria-expanded:scale-100 group-aria-expanded:opacity-100"
				class="text-accent"
				aria-hidden="true"
				focusable="false"
				xmlns="http://www.w3.org/2000/svg"
				fill="none"
				viewBox="0 0 24 24"
				stroke-width="1.5"
				stroke="currentColor"
			>
				<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path>
			</svg>
		</button>
	</mobile-button>
</header>
<!-- </div> -->
I THINK IT IS THE CODEPEN EMBED CAUSING ISSUES ON LARGE SCREENS WITH ToC! NOT THE CODE 😠
I do not know what it is
Discarding some commits
import { filePath } from "../../../lib/blog-helpers";
import EmbedGeneric from "./EmbedGeneric.astro";
export interface Props {
	block: interfaces.Block;
@@ -32,7 +33,9 @@ if (block.Video.External) {
						allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
						allowfullscreen
					/>
				) : null
				) : (
					<EmbedGeneric url={url} />
				)
			) : (
style="max-width: 605px;"
	class="tiktok-embed"
	cite={url.toString()}
	data-video-id={videoId}
	style="max-width: 605px;"
	muted
>
<iframe
	allowtransparency="true"
	class="max-w-sm:h-[220px] h-[340px] w-full border-none"
	src={url.toString()}
	loading="lazy"></iframe>
in index.ts
export async function getMenu(): Promise<
  { title: string; path: string; children?: { title: string; path: string }[] }[]
> {
The fudge man, this is the issue? I had removed max-w-prose from here.
<PostLayout post={post} headings={headings}>
	<div class="post-body max-w-prose">
		<NotionBlocks blocks={blocks} isRoot={true} />
	</div>
</PostLayout>
Okay, so moving max-w-prose from body to main basically does not work. Great! Wrapping the main into a different div and applying max-w-prose to it also does not work.
Social icons in footer
Astro Icon also supports Iconify out-of-the-box with minimal configuration. To use an icon set from Iconify follow the instructions below:
  1. Find an Icon Set to use on the Iconify Icon Sets website
  2. Install the package (eg. npm i -D @iconify-json/mdi)
  3. Add an entry to the astro.config.mjs file astro.config.mjs
This does not work. Apparently all icons shown on icones are not in astro-icon 🤷‍♀️
Make footer look nicer
giscusShow information for the linked content
Process
giscus: 
    repo: nerdymomocat/nerdymomocat.github.io
    repo-id: R_kgDOJoxbeA
    category: Announcements
    category-id: DIC_kwDOJoxbeM4CWzrO
    mapping: pathname
    loading: lazy
    input-position: top
    reactions-enabled: true
toggle headings click issue
Done by adding a javascript that adds details to the closest parent
<details class="toggle mb-0 mt-8">
			<summary class="flex cursor-pointer list-none gap-2">
				<div>
					<svg
						class="rotate-svg mt-1 flex-shrink-0 transition-all duration-300"
						fill="none"
						height="20"
						width="20"
						stroke="currentColor"
						stroke-linecap="round"
						stroke-linejoin="round"
						stroke-width="2"
						viewBox="0 0 24 24"
					>
						<polyline points="12 8 18 14 12 20" />
					</svg>
				</div>
				<a
					class="inline"
					href={`#${id}`}
					id={id}
					onclick="var fullUrl = `${window.location.origin}${window.location.pathname}#${this.id}`; navigator.clipboard.writeText(fullUrl); this.closest('details').open = !this.closest('details').open;"
				>
					<h4 class="toggle-h4 mb-4 mt-8 inline text-lg">
						{block.Heading3.RichTexts.map((richText: interfaces.RichText) => (
							<RichText richText={richText} blockID={block.Id} />
						))}
					</h4>
				</a>
			</summary>
			<div class="pl-10">
				{block.Heading3.Children && (
					<NotionBlocks blocks={block.Heading3.Children} headings={headings} />
				)}
			</div>
		</details>
And the mobile menu is transparent again?
Nov 27, 2023, 09:35 PM
Github workflow
Suggested github pages astro file
# Sample workflow for building and deploying an Astro site to GitHub Pages
#
# To get started with Astro see: https://docs.astro.build/en/getting-started/
#
name: Deploy Astro site to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["main"]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: "pages"
  cancel-in-progress: false

env:
  BUILD_PATH: "." # default value when not using subfolders
  # BUILD_PATH: subfolder

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Detect package manager
        id: detect-package-manager
        run: |
          if [ -f "${{ github.workspace }}/yarn.lock" ]; then
            echo "manager=yarn" >> $GITHUB_OUTPUT
            echo "command=install" >> $GITHUB_OUTPUT
            echo "runner=yarn" >> $GITHUB_OUTPUT
            exit 0
          elif [ -f "${{ github.workspace }}/package.json" ]; then
            echo "manager=npm" >> $GITHUB_OUTPUT
            echo "command=ci" >> $GITHUB_OUTPUT
            echo "runner=npx --no-install" >> $GITHUB_OUTPUT
            exit 0
          else
            echo "Unable to determine package manager"
            exit 1
          fi
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: "16"
          cache: ${{ steps.detect-package-manager.outputs.manager }}
          cache-dependency-path: ${{ env.BUILD_PATH }}/package-lock.json
      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v3
      - name: Install dependencies
        run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
        working-directory: ${{ env.BUILD_PATH }}
      - name: Build with Astro
        run: |
          ${{ steps.detect-package-manager.outputs.runner }} astro build \
            --site "${{ steps.pages.outputs.origin }}" \
            --base "${{ steps.pages.outputs.base_path }}"
        working-directory: ${{ env.BUILD_PATH }}
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v2
        with:
          path: ${{ env.BUILD_PATH }}/dist

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    needs: build
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2
Current file copied from my previous attempts
name: Deploy to GitHub Pages

on:
  # push:
  #   branches: [ main ]
  # schedule:
  #   - cron: '0 */8 * * *'
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

env:
  NOTION_API_SECRET: ${{ secrets.NOTION_API_SECRET }}

jobs:
  build:
    runs-on: ubuntu-latest
    environment: github-pages
    steps:
      - name: Checkout your repository using git
        uses: actions/checkout@v3

      - name: Set and log environment secrets and variables
        run: |
          # echo "DATABASE_ID = $DATABASE_ID"
          echo "NOTION_API_SECRET = $NOTION_API_SECRET"

      - name: Install, build, and upload your site
        uses: withastro/action@v1
        with:
          path: .
          node-version: 20

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1
Modification attempt
name: Deploy to GitHub Pages

on:
  # push:
  #   branches: [ main ]
  # schedule:
  #   - cron: '0 */8 * * *'
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

env:
  BUILD_PATH: "."
  NOTION_API_SECRET: ${{ secrets.NOTION_API_SECRET }}

jobs:
  build:
    runs-on: ubuntu-latest
    environment: github-pages
    steps:
      - name: Checkout your repository using git
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 21
          cache: 'npm'
          cache-dependency-path: ${{ env.BUILD_PATH }}/package-lock.json

      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v3

      - name: Install dependencies
        run: npm ci
        working-directory: ${{ env.BUILD_PATH }}

      - name: Set and log environment secrets and variables
        run: |
          # echo "DATABASE_ID = $DATABASE_ID"
          echo "NOTION_API_SECRET = $NOTION_API_SECRET"
          echo "GITHUB_PAGES = true"
          echo "SITE = ${{ steps.pages.outputs.origin }}"
          echo "BASE = ${{ steps.pages.outputs.base_path }}"

      - name: Build with Astro
        env:
          NOTION_API_SECRET: ${{ secrets.NOTION_API_SECRET }}
          GITHUB_PAGES: true
          SITE: ${{ steps.pages.outputs.origin }}
          BASE: ${{ steps.pages.outputs.base_path }}
        run: |
          echo "NOTION_API_SECRET = $NOTION_API_SECRET"
          echo "GITHUB_PAGES = $GITHUB_PAGES"
          echo "SITE = $SITE"
          echo "BASE = $BASE"
          npx --no-install astro build
          # or if you prefer yarn, use: yarn build
          # --site and --base flags can be included if needed for Astro
          # --site steps.pages.outputs.origin" \
          # --base steps.pages.outputs.base_path"
        working-directory: ${{ env.BUILD_PATH }}

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v2
        with:
          path: ${{ env.BUILD_PATH }}/dist



  deploy:
    needs: build
    runs-on: ubuntu-latest
    name: Deploy
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2

Alright, as expected site/base is messed up because it is hardcoded but I should have that fixed by tonight and we are done!
//Might need to modify this to also include collection paths
export const getPageLink = (page: number, tag: string) => {
  if (page === 1) {
    return tag ? getTagLink(tag) : path.join(BASE_PATH, "/");
  }
  return tag
    ? path.join(BASE_PATH, `/posts/tag/${encodeURIComponent(tag)}/page/${page.toString()}`)
    : path.join(BASE_PATH, `/posts/page/${page.toString()}`);
};
Pagefind does not work?
Some final bug squashes Nov 27, 2023, 07:00 PM
💡
Well, you cannot fork your own repo and I want to use that repo as a template, so irritating! Now, searching for a solution.
Now transferring giscs is an issue and someone has had that problem before of course
Specifically here
Weird two line menu items on half screen, maybe set the font size to text-sm on mid sized screens?
I thought I wanted rank of pages to change but I don’t
Nov 28, 2023, 11:15 PM
TF is up with performance?
Screenshots
ALSO GOOGLE
Performance
Keywords: #On-Demand Incremental Static Regeneration
Basic steps:
use two files buildtimestart.txt and buildtimeend.txt in the folder .tmp/
know that the last updated time does not pass to parent blocks other than pages; so if a post is updated, you cannot use some blocks and not use some blocks; do not make it complicated
remember notion does not tell you when pages have been deleted, so they will still exist in the cache? OMG? maybe I do retrieve every post in a call but use cached blocks — wtf.
Points
Steps:
Add partytown for GA script
Check how many times getAllPosts is being called
Cache getAllPosts results and move filters out
Cache block results but invalidate them if post is new
Cache og-images generated or just generate them in public folder so that they are easier to figure out if they have been cached?
Cache things downloaded from Notion
Process
Nov 29, 2023, 05:48 AM
change how posts are filtered
change getAllPosts
change getAllPosts references
change getAllPostsAndPages references
Complicated cache:
From otoyo
"scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "build:cached": "npm run cache:fetch && astro build",
    "preview": "astro preview",
    "astro": "astro",
    "lint": "eslint --ext .js,.ts,.astro src",
    "format": "npx prettier --write .",
    "cache:fetch": "node scripts/blog-contents-cache.cjs",
    "cache:purge": "nx reset && rm -f tmp/*",
    "_fetch-notion-blocks": "node scripts/retrieve-block-children.cjs"
  },
Articles
Dec 2, 2023, 08:49 AM
I tried using <Code> component from Astro, but it is hard to add styles to it
For lightbox, doing import "../styles/glightbox.css" adds some error to captions
I need to use import
I need to use window loaded or something to connect it
Can’t get dynamic jsx based import to work
Landed on CDN for both
And need npm install glightbox
Dec 3, 2023, 07:51 AM
Mainly adding stuff for on this page and linked to this page
get all on this page
get all to this page
decide on a tooltip library
trying flowbite
Dec 3, 2023, 11:18 PM
I got this error once while building but cannot reproduce it: error Cannot read properties of undefined (reading 'title')
Orphan heading found: Heading 2.
@notionhq/client warn: request fail {
  code: 'object_not_found',
  message: 'Could not find block with ID: 3cb777e3-4815-41c8-8f30-d790b6ce7685. Make sure the relevant pages and databases are shared with your integration.'
}
Request was aborted
look at tailwind aspect ratio plugin that is imported later (might help with google maps and everything there!!
popover
fix current page block popover for links in post body
add bookmark as popover for external links?
add references to table of contents
FLOWBITE IS SO DARN HEAVY
The general performance is not perfect anyway
Trying preline
Nah, pretty much the same: first load of preline || Mobile
Second load on preline || mobile
Second load on flowbite || Mobile
Nah, pretty much the same: first load of preline || Desktop
Second load on flowbite || Desktop
Second load on flowbite || Desktop

Npage
<span class="text-link no-underline hover:underline hover:decoration-accent-2 hover:underline-offset-4"><button data-popover-target=`popover-description-${id}` data-popover-placement="bottom-end" type="button" id=`popover-click-${id}`>{popoverSpanText?popoverSpanText:<slot/>}<span class="sr-only">Show information for the linked content</span></button></span>
  <div data-popover id=`popover-description-${id}` role="tooltip" class="absolute z-10 invisible inline-block text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 w-72 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400">
    <a href={linkedTo}>
      <div class="p-3 space-y-2">
          <h3 class="font-semibold underline decoration-wavy decoration-accent-2 text-accent">{popoverTitle}</h3>
          {excerpt && <p>{excerpt}</p>}
          <a href={linkedTo} class="flex items-center font-medium text-blue-600 dark:text-blue-500 dark:hover:text-blue-600 hover:text-blue-700 hover:underline">{linkText}<svg class="w-2 h-2 ms-1.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
      <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
    </svg></a>
      </div>
    </a>
      <div data-popper-arrow></div>
  </div>
NBlock
<span class="text-link no-underline hover:underline hover:decoration-accent-2 hover:underline-offset-4"><button data-popover-target=`popover-description-${id}` data-popover-placement="bottom-end" type="button">{popoverSpanText?popoverSpanText:<slot/>}<span class="sr-only">Show information for the linked content</span></button></span>
  <div data-popover id=`popover-description-${id}` role="tooltip" class="absolute z-10 invisible inline-block text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 w-72 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400">
    <a href={linkedTo}>
      <div class="p-3 space-y-2">
          {popoverTitle && <h3 class="font-semibold font-semibold underline decoration-wavy decoration-accent-2 text-accent">{popoverTitle}</h3>}
          <NotionBlocks blocks={[block]} renderChildren={false}/>
          <a href={linkedTo} class="flex items-center font-medium text-blue-600 dark:text-blue-500 dark:hover:text-blue-600 hover:text-blue-700 hover:underline">{linkText}<svg class="w-2 h-2 ms-1.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
      <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
    </svg></a>
      </div>
    </a>
      <div data-popper-arrow></div>
  </div>
This does not work
REFERENCES && (
<script>
  import {initPopovers} from 'flowbite';
  window.addEventListener('load', () => {
    initPopovers();
  });
</script>
  )
}
Neither does this
{
REFERENCES && (
<script is:inline defer>
  import {initPopovers} from 'flowbite';
  window.addEventListener('load', () => {
    initPopovers();
  });
</script>
  )
}
Maybe the new popover API? (too complicated to position and auto close!!)
<span class="text-link no-underline hover:underline hover:decoration-accent-2 hover:underline-offset-4"><button popovertarget=`popover-description-${id}` type="button" id=`popover-click-${id}`>{popoverSpanText?popoverSpanText:<slot/>}<span class="sr-only">Show information for the linked content</span></button></span>
  <div popover id=`popover-description-${id}` role="tooltip">
    <a href={linkedTo}>
      <div class="p-3 space-y-2">
          <h3 class="font-semibold underline decoration-wavy decoration-accent-2 text-accent">{popoverTitle}</h3>
          {excerpt && <p>{excerpt}</p>}
          <a href={linkedTo} class="flex items-center font-medium text-blue-600 dark:text-blue-500 dark:hover:text-blue-600 hover:text-blue-700 hover:underline">{linkText}<svg class="w-2 h-2 ms-1.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
      <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
    </svg></a>
      </div>
    </a>
      <div data-popper-arrow></div>
  </div>
Dec 8, 2023, 02:20 AM
The async tag on mermaid was making it not render at all. Oh, and the import mermaid from mermaid works there 🤷‍♀️
Fixing scroll on bottom nav and wth is this code with double ! ?
function callback(entries: IntersectionObserverEntry[]) {
      entries.forEach((entry) => {
        if (entry.target === targetHeader) {
          scrollBtn.dataset.show = (!entry.isIntersecting).toString();
        }

        const isBottomNavVisible = !entries.some((entry) => entry.isIntersecting);
        bottomTocNav.classList.toggle("hidden", !isBottomNavVisible);
      });
    }
btw, the audio element in popup is causing horizontal scroll:
lol at z-index and gpt absolutely making sure to be incorrect (patchy fix - does not work for gray/light-gray)
GPT
Dec 8, 2023
Updating to astro 4
Dec 9, 2023, 11:53 PM
Astro-tweet has not updated and I do not like looks of @astro-community/astro-embed-twitter . Maybe I just copy paste the component from astro-tweet
Remove URL normalization for same page popups on anchor links (the interlinked content part works fine)
Add URL normalization for different page links in popups