Table of contents
.astro
- or .md
- or .html
- file in your src/pages
-folder. 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
Image opt
Or
There is no difference in API response, which is why 🤷♀️
Sadly, the descriptions are only returned with database, not with page properties 🤷♀️
Doesn’t show up in API
Astro.request.headers
is unavailable in "static" output mode, and in prerendered pages within "hybrid" and "server" output modes. If you need access to request headers, make sure that output
is configured as either "server"
or output: "hybrid"
in your config file, and that the page accessing the headers is rendered on-demand. — started happening when I upgraded to astro 4.16 😠 Completed
In Dec 2024
Some internet stuff for this
In Nov 2024
In Notion
Liamm_top asked to see if there is a way to fix this for Chinese deployment which needs:
In Sept 2024
The fudge, does not show up in API too?
In Aug 2024
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.
In June/July 2024
Details
Seems like a kofi blocking thing? Yeah and found Jul 25, 2024 that even using dub.sh or something like that also does not work
I tried doing something like this but I do not like how it looks:
<!--Commenting here so that it does not inject -->
<!DOCTYPE html> <!-- inject -->
<script src='https://storage.ko-fi.com/cdn/scripts/overlay-widget.js'></script>
<script>
kofiWidgetOverlay.draw('nerdymomocat', {
'type': 'floating-chat',
'floating-chat.donateButton.text': 'Buy me a coffee',
'floating-chat.donateButton.background-color': '#F3E1C6',
'floating-chat.donateButton.text-color': '#4F1116'
});
</script>
i tried this too but did not like how long af it was\
<!--Commenting here so that it does not inject -->
<!DOCTYPE html> <!-- inject -->
<a
href="https://ko-fi.com/nerdymomocat"
target="_blank"
rel="noopener noreferrer"
class="flex w-full min-w-0 max-w-full grow select-none items-stretch overflow-hidden rounded border border-gray-200 no-underline dark:border-gray-800"
>
<div
class="flex-bookmark-text overflow-hidden p-3 text-left text-textColor/90"
>
<div
class="mb-0.5 h-6 overflow-hidden truncate whitespace-nowrap leading-5"
>
Support Nerdy Momo Cat on Ko-fi! Buy her a hot chocolate ❤️
</div>
<div
class="h-8 overflow-hidden text-xs leading-4 opacity-80"
>
Support Nerdy Momo Cat On Ko-fi. Ko-fi lets you support the people and causes you love with small donations
</div>
<div class="mt-1.5 flex max-w-full">
<div class="mr-1.5 h-4 w-4 min-w-4">
<img
class="inline-block max-w-full"
loading="lazy"
src="https://www.google.com/s2/favicons?domain=ko-fi.com"
alt="title"
decoding="async"
/>
</div>
<div
class="overflow-hidden truncate whitespace-nowrap text-xs leading-4"
>
https://ko-fi.com/
</div>
</div>
</div>
<div
class="flex-bookmark-image relative hidden sm:block"
>
<img
class="!absolute h-full w-full object-contain object-right"
loading="lazy"
src="https://storage.ko-fi.com/cdn/useruploads/138404e3-801f-4922-beb3-0db70efc0578_ed507fa6-95c6-4a2f-9dc9-e97d8d19cab4.png"
alt="title"
decoding="async"
/>
</div>
</a>
thought about using this but i do not like how it adds extra useless javascript
<!--Commenting here so that it does not inject -->
<!DOCTYPE html> <!-- inject -->
<script type='text/javascript' src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Support my bread addiction', '#4A1510', 'I2I6X8MM3');kofiwidget2.draw();</script>
this looks ugly
<!--Commenting here so that it does not inject -->
<!DOCTYPE html> <!-- inject -->
<div>
<a href="https://www.buymeacoffee.com/yourusername" target="_blank" rel="noopener noreferrer"
style="
background-color: #f59e0b;
color: white;
font-weight: 500;
border-radius: 6px;
padding: 8px 16px;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
transition: background-color 0.3s ease-in-out;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
"
onmouseover="this.style.backgroundColor='#d97706'"
onmouseout="this.style.backgroundColor='#f59e0b'"
>
<span style="font-size: 1.125rem; margin-right: 8px;">☕</span>
Buy me a coffee
</a>
</div>
[Shiki] 10 instances have been created. Shiki is supposed to be used as a singleton, consider refactoring your code to cache your highlighter instance; Or call highlighter.dispose()
to release unused instances.
Solved — use this transformers
= {TRANSFORMER_FUNCTIONS_ARR}
instead of changing array based on shortcode because it creates a new highlighter then.
Also update here:
More update here:
Astro Astro 4.11 | Astro with Pine Wu, Anthony Fu Shiki
- Attempted here: 1Show information for the linked content
In March 2024
Oh, it already gives an error anyway
Lol! Later? I did it Nov 28, 2023, 05:44 AM just because it was absolutely NOT needed
kinda doing them right now with aggressive caching of API requests at elast
Jun 14, 2024
mailto:
link. 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
Nov 22, 2023, 08:48 PM
Nov 23, 2023, 09:02 PM
Nov 24, 2023, 02:32 AM
i added svg to make it look nice AND GRR
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!
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
Nov 24, 2023, 07:16 PM
src/pages/[...page].astro
. 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?
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>
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:
- 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.
- 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.
<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) Nov 25, 2023, 12:13 AM
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
I don’t get how astro satori works 🤷♀️
Stuff like this is more complicated than the existing one. So, I am gonna try to figure out the existing one!
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)),
);
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
og image creation using satori
I don’t get how astro satori works 🤷♀️
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?
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>
Also, There is no z-index
support in SVG. Elements that come later in the document will be painted on top.
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.
Lol! satori does not support obejctFit in tailwind 🤣
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:
Processing dump
- 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 usedNow we have 2 things:
const
{ title, description, ogImage, articleDate } = Astro.props;
and
const
socialImageURL =
new
URL(ogImage ? ogImage : "/social-card.png", Astro.url).href;
- The only place
BaseHead
is imported isBase.Astro
byimport
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} />
- Finally
Base
is imported at multiple placesBlogPost.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, insrc/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
insrc/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
insrc/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
insrc/pages/posts
const meta = { title: "Posts", description: "Ready my collection of posts and the things that interest me", }; <PageLayout meta={meta}>
[..page].astro
insrc/pages/posts/collection/[collection]
const meta = { title: `${collection}`, description: "Read my collection of posts.", }; <PageLayout meta={meta}>
index.astro
insrc/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
insrc/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);
}
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
{
richTexts.length > 0 && richTexts[0].Text.Content && (
<div class="caption pt-1 text-sm">{richTexts[0].Text.Content}</div>
)
}
Nov 26, 2023
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
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
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
Using this
Can change policy too if ever needed
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);
}
}
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);
}
THEME AND FONTS AND CSS!!
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>)"
}
},
From lillian weng’s blog
font-family: -apple-system,BlinkMacSystemFont,segoe ui,Roboto,Oxygen,Ubuntu,Cantarell,open sans,helvetica neue,sans-serif;
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
<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.
Astro Icon also supports Iconify out-of-the-box with minimal configuration. To use an icon set from Iconify follow the instructions below:
- Find an Icon Set to use on the Iconify Icon Sets website
- Install the package (eg.
npm i -D @iconify-json/mdi
) - Add an entry to the
astro.config.mjs
fileastro.config.mjs
This does not work. Apparently all icons shown on icones are not in astro-icon 🤷♀️
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
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>
Nov 27, 2023, 09:35 PM
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
//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()}`);
};
Some final bug squashes Nov 27, 2023, 07:00 PM
Specifically here
Nov 28, 2023, 11:15 PM
Screenshots
ALSO GOOGLE
Keywords: #On-Demand Incremental Static Regeneration
Points
Steps: