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
Attempt 1
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
timeout-minutes: 45
steps:
- name: Checkout your repository using git
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 21
# Idea from https://www.voorhoede.nl/en/blog/super-fast-npm-install-on-github-actions/
- name: Cache node manually
id: npm-cache
uses: actions/cache@v4
with:
path: ./node_modules
key: modules-${{ hashFiles(format('{0}/package-lock.json', env.BUILD_PATH)) }}
- name: Install dependencies
if: steps.npm-cache.outputs.cache-hit != 'true'
run: npm ci --ignore-scripts
working-directory: ${{ env.BUILD_PATH }}
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Restore cached tmp
if: steps.npm-cache.outputs.cache-hit == 'true'
id: cache-tmp-restore
uses: actions/cache/restore@v4
with:
path: ./tmp
key: ${{ runner.os }}-${{steps.pages.outputs.origin}}-${{steps.pages.outputs.base_path}}-tmp-blogtrotion-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: ${{ runner.os }}-${{steps.pages.outputs.origin}}-${{steps.pages.outputs.base_path}}-tmp-blogtrotion-
- name: Restore cached public
if: steps.npm-cache.outputs.cache-hit == 'true'
id: cache-public-restore
uses: actions/cache/restore@v4
with:
path: ./public
key: ${{ runner.os }}-${{steps.pages.outputs.origin}}-${{steps.pages.outputs.base_path}}-public-blogtrotion-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: ${{ runner.os }}-${{steps.pages.outputs.origin}}-${{steps.pages.outputs.base_path}}-public-blogtrotion-
- name: Set and log environment secrets and variables and then Build with Astro and postbuild for pagefind
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"
npx --no-install pagefind --site dist
working-directory: ${{ env.BUILD_PATH }}
- name: Save new tmp cache
id: cache-tmp-save
uses: actions/cache/save@v4
with:
path: ./tmp
key: ${{ runner.os }}-${{steps.pages.outputs.origin}}-${{steps.pages.outputs.base_path}}-tmp-blogtrotion-${{ github.run_id }}-${{ github.run_attempt }}
- name: Save new public cache
id: cache-public-save
uses: actions/cache/save@v4
with:
path: ./public
key: ${{ runner.os }}-${{steps.pages.outputs.origin}}-${{steps.pages.outputs.base_path}}-public-blogtrotion-${{ github.run_id }}-${{ github.run_attempt }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
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@v4
workflow-keepalive:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- uses: liskin/gh-workflow-keepalive@v1
Info
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:
Process
Nov 29, 2023, 05:48 AM
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
import
"../styles/glightbox.css"
adds some error to captionsI 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
Dec 3, 2023, 11:18 PM
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
The general performance is not perfect anyway
Trying preline
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
import mermaid from mermaid
works there 🤷♀️ !
? 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);
});
}
GPT
Dec 8, 2023
Updating to astro 4
Dec 9, 2023, 11:53 PM
@astro-community/astro-embed-twitter
. Maybe I just copy paste the component from astro-tweet No need. Somehow on build, there is a trailing slash added, but not in dev? So, basically if I do navlink#block-id
, that causes issues in build but not in dev — if I do navlink/#block-id
that causes issues in dev but not in build.
navlink#block-id
, that causes issues in build but not in dev — if I do navlink/#block-id
that causes issues in dev but not in build. Dec 10, 2023, 09:20 PM
So, I have the basic code and I have tried a million methods but they don’t really work to have a clean outcome.
This is the very basic code that I understand right now
<script type="module">
import { computePosition, offset, shift, flip, autoUpdate } from 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.5.3/+esm';
document.addEventListener('DOMContentLoaded', () => {
const popoverTriggers = document.querySelectorAll('[data-popover-target]');
let openPopovers = [];
let cleanupAutoUpdate = new Map();
let hoverTimeout;
const hideAllPopovers = () => {
openPopovers.forEach(popoverEl => hidePopover(popoverEl));
};
const hidePopover = (popoverEl) => {
if (popoverEl) {
popoverEl.style.visibility = 'hidden';
popoverEl.style.opacity = '0';
popoverEl.style.top = '0px';
popoverEl.style.left = '0px';
const cleanup = cleanupAutoUpdate.get(popoverEl);
if (cleanup) {
cleanup();
cleanupAutoUpdate.delete(popoverEl);
}
const openPopoverIndex = openPopovers.indexOf(popoverEl);
if (openPopoverIndex !== -1) {
openPopovers.splice(openPopoverIndex, 1);
}
}
};
const showPopover = (triggerEl, popoverEl) => {
const update = () => {
computePosition(triggerEl, popoverEl, {
middleware: [shift({ padding: 5 }), flip({ padding: 5 }), offset(10)]
}).then(({ x, y }) => {
Object.assign(popoverEl.style, {
left: `${x}px`,
top: `${y}px`,
position: 'absolute'
});
});
};
update();
popoverEl.style.visibility = 'visible';
popoverEl.style.opacity = '1';
openPopovers.push(popoverEl);
cleanupAutoUpdate.set(popoverEl, autoUpdate(triggerEl, popoverEl, update));
};
popoverTriggers.forEach(triggerEl => {
const popoverID = triggerEl.dataset.popoverTarget;
const popoverEl = document.getElementById(popoverID);
triggerEl.addEventListener('mouseenter', () => {
clearTimeout(hoverTimeout);
showPopover(triggerEl, popoverEl);
});
triggerEl.addEventListener('mouseleave', () => {
hoverTimeout = setTimeout(() => {
hidePopover(popoverEl);
}, 100);
});
triggerEl.addEventListener('focus', () => {
showPopover(triggerEl, popoverEl);
});
triggerEl.addEventListener('blur', () => {
hidePopover(popoverEl);
});
popoverEl.addEventListener('mouseenter', () => {
clearTimeout(hoverTimeout);
});
popoverEl.addEventListener('mouseleave', () => {
hidePopover(popoverEl);
});
});
document.addEventListener('click', (event) => {
if (!event.target.closest('[data-popover-target]')) {
hideAllPopovers();
}
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
hideAllPopovers();
}
});
});
</script>
The problem is, if I move from one one triggerEl to another triggerEl quickly within 100ms, then nothing closes that has already been opened. Why? Because it goes to mouseleave here which has a timeout of 100ms to be able to move cursor to popover offset.
triggerEl.addEventListener('mouseenter', () => {
clearTimeout(hoverTimeout);
showPopover(triggerEl, popoverEl);
});
triggerEl.addEventListener('mouseleave', () => {
hoverTimeout = setTimeout(() => {
hidePopover(popoverEl);
}, 100);
});
It should call this and at least close after 100ms BUT the hovertimeout is universal:
const hidePopover = (popoverEl) => {
if (popoverEl) {
popoverEl.style.visibility = 'hidden';
popoverEl.style.opacity = '0';
popoverEl.style.top = '0px';
popoverEl.style.left = '0px';
const cleanup = cleanupAutoUpdate.get(popoverEl);
if (cleanup) {
cleanup();
cleanupAutoUpdate.delete(popoverEl);
}
const openPopoverIndex = openPopovers.indexOf(popoverEl);
if (openPopoverIndex !== -1) {
openPopovers.splice(openPopoverIndex, 1);
}
}
};
So, entering into a new triggerEl just clears the universal hovertimeout. And the hidePopover never gets called.
This is a good thing for nested triggers, because we want their parents to not be closed. But we want only 1 popup of each LEVEL. Let me explain level.
For example: Page has Trigger A and Trigger B. Trigger A has a popover which has Trigger A1 and Trigger A2. Trigger A2 has a popup that has Trigger A21 and Trigger A22.
Say, I hover over Trigger A and then suddenly move under 100ms to Trigger B, so popover from Trigger A will never close because moving to Trigger B has cleared timeout.
But if I hover over Trigger A, and it opens a popup, we hover over Trigger A1 inside the popover from trigger A, and that makes it so that the parent popup does not close which is a good thing. Because we want one popup each level. But if we also suddenly move from Trigger A1 to Trigger A2, the popup from Trigger A1 will not close too because moving to Trigger A2 clears timeout.
The complicated part here is maintaining and tracking levels. Especially consider that this code adds event handlers to all triggers no matter how nested, so this tracking needs to be taken care of in showPopover (maybe add trigger element and the containment level somewhere, check if there is a document contain or popover contain match for trigger element, and then immediately hidepopover if there is).
popoverTriggers.forEach(triggerEl => {
const popoverID = triggerEl.dataset.popoverTarget;
const popoverEl = document.getElementById(popoverID);
triggerEl.addEventListener('mouseenter', () => {
clearTimeout(hoverTimeout);
showPopover(triggerEl, popoverEl);
});
triggerEl.addEventListener('mouseleave', () => {
hoverTimeout = setTimeout(() => {
hidePopover(popoverEl);
}, 100);
});
triggerEl.addEventListener('focus', () => {
showPopover(triggerEl, popoverEl);
});
triggerEl.addEventListener('blur', () => {
hidePopover(popoverEl);
});
popoverEl.addEventListener('mouseenter', () => {
clearTimeout(hoverTimeout);
});
popoverEl.addEventListener('mouseleave', () => {
hidePopover(popoverEl);
});
});
Dec 12, 2023, 06:30 AM
Finished a bunch of optimizations; I am not even sure if most of them worked, but at least now I know what is going into it!
Lol, should have known you can run lighthouse locally and not push stuff 100 times 🤣
^ Oh doesn’t really do what I want
Partytown in define vars thing
and without it
I will later try to figure out what happens with no partytown
Btw,
The main benefit of using lazysizes for me is just that it does not add lazy to the first image on the page, so it reduces that blocking time. If there is another way to do that, we should do it.
Dec 12, 2023, 07:45 PM
Hmm, even after changing popovers to hidden, it still takes really long
Lol
Note: In Firefox, theloading
attribute must be defined before thesrc
attribute, otherwise it has no effect (Firefox bug 1647077).
No, script based change to eager does not really work. As expected to be fair.
OMG WHAT THE HELL
i am gonna laugh so much if this works. i keep forgetting i can use ts files as normal js files for key store values and keep trying to pass build time variables through components or whatever that thing is. 🤣
Dec 13, 2023, 06:05 AM Moved code highlighting to inbuilt shiki
Dec 13, 2023, 08:44 AM
Astro code highlighting
Jan 31, 2024, 09:54 PM
Apr 10, 2024, 08:49 AM
Trying to add collections by streaming a public page component into an iframe similar to embednotion.
Jun 25, 2024, 05:55 AM
It doesn’t have this:
The issue with transformers
I tried using both NACode and direct code component. It is like the second time this is called — transformerNotationFocus, it is not rendering?
<NACode code={`const foo = 'hello'
const bar = ' world'
console.log(foo + bar)
`} lang={language} transformers={[transformerNotationFocus()]}/>
I even tried changing styles to outside
pre.has-focused .line:not(.focused) {
@apply blur-[1px];
}
The problem is repeated component calls
<Code code={`const foo = 'hello'
const bar = ' world'
console.log(foo + bar)
`} lang={language} transformers={[transformerNotationFocus()]}/>
<Code code={`const foo = 'hello'
const bar = ' world'
console.log(foo + bar)
`} lang={language} transformers={[transformerNotationFocus()]}/>
Jun 25, 2024
Trying to make clipboard code better with these rules:
/*
Rules:
1. Two things to do: remove lines and clean up comments in remaining lines
2. For remove lines, only consider these commands: [!code --] or [!code warning] or [!code error]
3. Remove the line when the command is after the comment mark (any number of spaces between comment mark and coommand are fine). there can be text after command.
4. do not consider command or comment mark if they are part of a string literal enclosed with ` ` or " "
5. if a line is not removed, then consider these commands: [!code --] [!code ++] [!code warning] [!code error] [!code highlight] [!code highlight:number_here] [!code word:word_here:number_here] [!code word:word_here] [!code focus] [!code focus:number_here].
6. if these occur right after the comment mark (any number of spaces between comment mark and coommand are fine)
and are not part of string literal, just remove the command from if it occurs after the comment mark (and is not in string literal).
do not remove the line. there can be stuff after the command.
7. if the part after comment mark is empty or only white spaces, remeove the comment mark. again remember comment marks that are part of string literals are not considered for this.
*/
Nothing works and i have been trying for 3 hours now.
code at Jun 25, 2024, 06:37 PM
type CommentStyle = string | string[];
const commentStyles: Record<string, CommentStyle> = {
// Double slash comments
"//": [
"default",
"c",
"cpp",
"java",
"javascript",
"typescript",
"csharp",
"go",
"swift",
"kotlin",
"scala",
"rust",
"dart",
"css",
"php",
"pascal",
"groovy",
],
// Hash comments
"#": [
"python",
"ruby",
"perl",
"bash",
"powershell",
"php",
"yaml",
"toml",
"r",
"julia",
"elixir",
],
// Double dash comments
"--": ["haskell", "lua", "sql", "plsql", "ada"],
// Percent comments
"%": ["matlab", "octave", "latex", "katex", "erlang"],
// Semicolon comments
";": ["lisp", "clojure", "scheme", "ini"],
// Other specific comment styles
"⍝": ["apl"],
"!": ["fortran"],
"'": ["vb"],
"*>": ["cobol"],
"<!--": ["html"],
// Languages without standard comment syntax
"": ["brainfuck", "whitespace"],
};
// Invert the commentStyles object for easier lookup
const languageCommentStyles: Record<string, CommentStyle> = Object.entries(commentStyles).reduce(
(acc, [style, langs]) => {
(Array.isArray(langs) ? langs : [langs]).forEach((lang) => {
acc[lang] = style;
});
return acc;
},
{} as Record<string, CommentStyle>,
);
const getCommentStyle = (language: string): CommentStyle => {
return languageCommentStyles[language.toLowerCase()] || languageCommentStyles.default;
};
const escapeRegExp = (string: string): string => string.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
const createCommentRegex = (style: string | string[]): RegExp => {
const patterns = Array.isArray(style) ? style : [style];
const escapedPatterns = patterns.map(escapeRegExp).join("|");
return new RegExp(`^(.*?)(${escapedPatterns})(.*)$`);
};
const notationRegex = /\[!code (focus|--|\+\+|highlight|word:[^\]]+|error|warning)\]/g;
const createRemoveLineRegex = (style: string | string[]): RegExp => {
const patterns = Array.isArray(style) ? style : [style];
const escapedPatterns = patterns.map(escapeRegExp).join("|");
return new RegExp(`(${escapedPatterns})\\s*\\[!code (--|error|warning)\\](?!\\s{2,})`);
};
const processCode = (code: string, language: string): string => {
if (!languageCommentStyles[language] || language === "plaintext") {
return code;
}
const style = getCommentStyle(language);
const commentRegex = createCommentRegex(style);
const removeLineRegex = /^\s*(\/\/|#|--|%|;|⍝|!|'|\*>|<!--)\s*\[!code\s+(--|error|warning)\]/;
const cleanupRegex = /\[!code\s+(--|error|warning|highlight|word:[^\]]+|\+\+|focus)(:[\d]+)?\]/g;
return code
.split("\n")
.filter((line) => {
const commentMatch = line.match(commentRegex);
if (commentMatch) {
const [, before, commentStart, commentContent] = commentMatch;
// Check if the comment start is within a string literal
const stringLiteralRegex = /(['"`])(?:(?!\1)[^\\]|\\.)*\1/g;
let isInStringLiteral = false;
let match;
while ((match = stringLiteralRegex.exec(before)) !== null) {
if (match[0].includes(commentStart)) {
isInStringLiteral = true;
break;
}
}
if (isInStringLiteral) {
return true;
}
// Check if the line should be removed
return !removeLineRegex.test(line);
}
return true;
})
.map((line) => {
return line.replace(commentRegex, (_, before, commentStart, commentContent) => {
// Clean up the comment content
const cleaned = commentContent.replace(cleanupRegex, (match) => {
// Only remove the command if it's at the start of the comment
return commentContent.trim().startsWith(match) ? "" : match;
}).trim();
if (cleaned) {
return `${before}${commentStart} ${cleaned}`;
} else {
return before.trimEnd();
}
});
})
.join("\n");
};
// Usage remains the same
const code = block.Code.RichTexts.map(
(richText: interfaces.RichText) => richText.Text.Content,
).join("");
const language =
block.Code.Language.toLowerCase() === "plain text"
? "plaintext"
: block.Code.Language.toLowerCase();
const modifiedCode = processCode(code, language);
const code_inject = SHORTCODES["html"] ? code.startsWith(SHORTCODES["html"]) : false;
const plainTextCaption = block.Code?.Caption.map((richText) => richText.PlainText).join(" ");
While these rules make sense, shiki’s own regex isn’t this complicated:
Errors: in second line, comment mark is removed? and in last line it does remove comment mark from both places.
also btw, a line with this console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') // [!code --] [!code focus]
becaxztomes console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') [!code focus]
and this is rendered as ++ console.log('use // [!code ++]') // [!code --]
with text as: console.log('use')
So, it seems like they are just
console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo')
console.log('hello') // [!code --] blah blah come!
console.log('goodbye') // hellllaaaaa [!code --] blah blah come!
console.log('goodbye') // [!code --] blah blah come!
console.log('use // [!code --]')
console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo')
console.log('hello') # [!code --] blah blah come!
console.log('goodbye') # hellllaaaaa [!code --] blah blah come!
console.log('goodbye') # [!code --] blah blah come!
console.log('# [!code --]')
console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo')
console.log('hello') % [!code --] blah blah come!
console.log('goodbye') % hellllaaaaa [!code --] blah blah come!
console.log('goodbye') % [!code --] blah blah come!
console.log('\% [!code --]')
<h1>Welcome to My Website</h1>
<h1>Welcome to My Website</h1> <!-- [!code --] blah blah come! -->
<h1>Welcome to My Website</h1> <!-- helllaaa [!code --] -->
<h1>Welcome to My Website</h1> <!-- [!code --] blah blah come! -->
<h1>Welcome [!code ++] to My Website</h1>
Okay, I think I'm done. Using expressive code is way more complicated because the markdown option does parse normally but the code option doesn't have just like internal parser. You can't just be like code equal to code. You have to parse every single thing out of it like deletions, insertions, etc. It feels like I'm writing a parser of my own and that would be a hassle. I went the easy route and like keyboard clipboard cleanup and did what Shaky was doing. It made more sense because if you wanted Shaky transformer highlighting then you would be using it properly anyway. So yeah.
Jun 26, 2024, 02:16 AM
Started a new block because this now about footnotes.
Footnote testing
[^abr3a]: footnote much before the mention itself
this is a normal footnote [^abra1]
[^abra1]: footnote content on a new block
this is a normal footnote marker with no space before[^abra2]or after
[^abra2]: footnote content on new line, same block
this is a common invalid footnote [^ abr3a]
this is a common valid footnote with no attached footnote content [^abra3]
double mention of same footnote marker [^abra2]
footnote marker for multiline footnote [^abra4]
multiline comment using indented blocks
what if it has a callout?
footnote marker for multiline again? [^abra5]
[^abra5]: hey? how are you doing? multiline comment but in same block
what is up?
[^abra6]: what if i have footnote content that is not a footnote marked anywhere? markdown just removes this
This is what I have.
Here is what I am thinking I guess
- Identify footnote content when it starts with [^anywordwithoutspace]:
- The footnote content is consider till the end of that block including children. This will consider when footnote content is a separate block, when it is in same block at end, when it has multiline footnote with child blocks, when it has multiline footnotes using enter key.
- The only place this does not work is when in a single block someone has multiline footnotes and then the footnote ends when they press double enter in same block. But I am ignoring that case, why are you using notion, end that block?
- Identify footnote marker if any content anywhere in plaintext has [^anywordwithoutspace]. It is not needed that this marker have spaces around it.
Jun 26, 2024
Error in run here:
I tried making it so that avif is default and oh my god, it doubled the time and increased econnreset errors so much!
Jun 30, 2024
Tried making it so that tags aren’t always open on mobile but this is not currently working for pagefind filter
<script>
document.addEventListener('DOMContentLoaded', () => {
if (window.innerWidth <= 640) {
document.querySelectorAll('.pagefind-ui__filter-block').forEach(block => {
block.removeAttribute('open');
console.log("removed!");
});
}
});
</script>
Jul 2, 2024
If I dynamically generate popovers — it helps!
Jul 5, 2024
Figure out basic improvement on tables
Using simple datatables because i really do not want to import jquery too with datatables Jul 5, 2024, 05:47 PM
Options for simple datatables here.
https://fiduswriter.github.io/simple-datatables/demos/10-filters/
https://www.npmjs.com/package/simple-datatables/v/9.0.4?activeTab=code
https://fiduswriter.github.io/simple-datatables/documentation/
- fix row spacing
- fix that on small screens or larger tables overflow-x and table-auto stop working with datatables
- fix filter issues
- Relevant demos
Jul 6, 2024
Removing the css does not fix these issues:
- The top <thead> <tr> is white???
- On search, the auto table layout does not work and overflow X is always none
It fixes this:
- borders between cells are back
I just pushed it out 🤷♀️
Jul 7, 2024
Reducing div size. Both openai and claude suddenly recommended <template>
tag. But know that in astro, that tag should be the end of the file or it is not processed correctly 🤷♀️.
SIGNIFICANT REDUCTION IN DOM SIZE
Merging Jul 7, 2024, 09:24 PM:
Figcaption issue
---
import { ENABLE_LIGHTBOX } from "../../../constants.ts";
import * as interfaces from "../../../lib/interfaces.ts";
import { filePath, getFirstImage } from "../../../lib/blog-helpers";
import Caption from "../Caption.astro";
// import GLightbox from 'glightbox';
export interface Props {
block: interfaces.Block;
setId?:boolean;
}
const { block, setId=true } = Astro.props;
let image = "";
if (block.NImage?.External) {
image = block.NImage.External.Url;
} else if (block.NImage?.File) {
image = filePath(new URL(block.NImage.File.OptimizedUrl));
}
const plainTextCaption = block.NImage?.Caption.map((richText) => richText.PlainText).join(' ');
---
<figure class="flex max-w-full mx-auto mt-1" id={setId?block.Id:undefined}>
{
image && (
<div class="mx-auto min-w-0">
<div>
{ENABLE_LIGHTBOX ? (
<a data-type="image" href={image} class="mediaglightbox" data-description={plainTextCaption} aria-label={`Open image with alt ${plainTextCaption} in lightbox`}>
<img class="imagemedia block max-w-full rounded-md" loading={getFirstImage()?"eager":"lazy"} src={image} alt={plainTextCaption}/>
</a>
) : (
<img class="imagemedia block max-w-full rounded-md" src={image} loading={getFirstImage()?"eager":"lazy"} alt={plainTextCaption}/>
)}
</div>
<Caption richTexts={block.NImage.Caption} block={block} as="figcaption" />
</div>
)
}
</figure>
---
import { ENABLE_LIGHTBOX } from "../../../constants.ts";
import * as interfaces from "../../../lib/interfaces.ts";
import { filePath, getFirstImage } from "../../../lib/blog-helpers";
import Caption from "../Caption.astro";
// import GLightbox from 'glightbox';
export interface Props {
block: interfaces.Block;
setId?:boolean;
}
const { block, setId=true } = Astro.props;
let image = "";
if (block.NImage?.External) {
image = block.NImage.External.Url;
} else if (block.NImage?.File) {
image = filePath(new URL(block.NImage.File.OptimizedUrl));
}
const plainTextCaption = block.NImage?.Caption.map((richText) => richText.PlainText).join(' ');
---
{image && (<figure class="max-w-full mx-auto mt-1" id={setId?block.Id:undefined}>
{
<div class="mx-auto min-w-0">
{ENABLE_LIGHTBOX ? (
<a data-type="image" href={image} class="mediaglightbox" data-description={plainTextCaption} aria-label={`Open image with alt ${plainTextCaption} in lightbox`}>
<img class="imagemedia block max-w-full rounded-md" loading={getFirstImage()?"eager":"lazy"} src={image} alt={plainTextCaption}/>
</a>
) : (
<img class="imagemedia block max-w-full rounded-md" src={image} loading={getFirstImage()?"eager":"lazy"} alt={plainTextCaption}/>
)}
</div>
<Caption richTexts={block.NImage.Caption} block={block} as="figcaption" />
}
</figure>)}
Removing script=module thing. Cannot remove from glightbox btw 🤷♀️ or from floating-ui. But it works with removing from simple-datatables.
I tried adding lazyload to videos and it messed up the rating more???
Sep 13, 2024
Page link mentions now show up in API
I got this for example with a normal website
{
"object": "block",
"id": "4869b8e5-edb5-45b3-a3cf-c30674961562",
"parent": {
"type": "page_id",
"page_id": "85cb68b6-b12f-4a62-af33-3348ea751f77"
},
"created_time": "2024-06-23T20:49:00.000Z",
"last_edited_time": "2024-08-02T12:08:00.000Z",
"created_by": {
"object": "user",
"id": "5aa7609b-54f8-42b7-b4a2-6f38a03279bd"
},
"last_edited_by": {
"object": "user",
"id": "5aa7609b-54f8-42b7-b4a2-6f38a03279bd"
},
"has_children": true,
"archived": false,
"in_trash": false,
"type": "paragraph",
"paragraph": {
"rich_text": [
{
"type": "text",
"text": {
"content": "Paste link as mention: ",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Paste link as mention: ",
"href": null
},
{
"type": "mention",
"mention": {
"type": "link_mention",
"link_mention": {
"href": "https://shiki.style/packages/transformers#shikijs-transformers",
"title": "Shiki",
"icon_url": "https://shiki.style/logo.svg",
"description": "A beautiful yet powerful syntax highlighter",
"link_author": "Pine Wu, Anthony Fu",
"thumbnail_url": "https://shiki.style/og.png"
}
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "https://shiki.style/packages/transformers#shikijs-transformers",
"href": "https://shiki.style/packages/transformers#shikijs-transformers"
}
],
"color": "default"
}
},
when it is like an iframe embed, it is
{
"object": "block",
"id": "101817d0-5c92-80b2-9e24-d20eb882b801",
"parent": {
"type": "page_id",
"page_id": "85cb68b6-b12f-4a62-af33-3348ea751f77"
},
"created_time": "2024-09-14T06:22:00.000Z",
"last_edited_time": "2024-09-14T06:22:00.000Z",
"created_by": {
"object": "user",
"id": "5aa7609b-54f8-42b7-b4a2-6f38a03279bd"
},
"last_edited_by": {
"object": "user",
"id": "5aa7609b-54f8-42b7-b4a2-6f38a03279bd"
},
"has_children": true,
"archived": false,
"in_trash": false,
"type": "paragraph",
"paragraph": {
"rich_text": [
{
"type": "text",
"text": {
"content": "Embed paste link as mention: ",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Embed paste link as mention: ",
"href": null
},
{
"type": "mention",
"mention": {
"type": "link_mention",
"link_mention": {
"href": "https://open.spotify.com/playlist/37i9dQZF1DX5KpP2LN299J",
"title": "This Is Taylor Swift",
"height": 352,
"icon_url": "https://open.spotifycdn.com/cdn/images/favicon32.b64ecc03.png",
"iframe_url": "https://open.spotify.com/embed/playlist/37i9dQZF1DX5KpP2LN299J?utm_source=oembed",
"description": "Playlist · Spotify · 180 items · 6.1M likes",
"link_provider": "Spotify",
"thumbnail_url": "https://i.scdn.co/image/ab67706f0000000252feef11af8c9d412769ec5a"
}
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "https://open.spotify.com/playlist/37i9dQZF1DX5KpP2LN299J",
"href": "https://open.spotify.com/playlist/37i9dQZF1DX5KpP2LN299J"
}
],
"color": "default"
}
},
popup div for spotify
<div style="display: flex; align-items: center; position: relative; flex-direction: column-reverse; transform-origin: 0% top; left: 0px; top: 6px;">
<div
role="dialog"
style="border-radius: 10px; background: white; backdrop-filter: none; position: relative; max-width: calc(-24px + 100vw); box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 2px 4px; overflow: hidden;"
>
<div>
<div style="width: 340px; max-height: 420px; flex-direction: column; align-items: flex-start;">
<div style="position: absolute; top: 8px; right: 8px; z-index: 2;">
<div
role="button"
tabindex="0"
style="user-select: none; transition: background 20ms ease-in; cursor: pointer; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 4px; fill: rgba(55, 53, 47, 0.35); background: rgba(0, 0, 0, 0.6);"
>
<svg
role="graphics-symbol"
viewBox="0 0 13 3"
class="dots"
style="width: 14px; height: 100%; display: block; fill: white; flex-shrink: 0;"
>
<g>
<path d="M3,1.5A1.5,1.5,0,1,1,1.5,0,1.5,1.5,0,0,1,3,1.5Z"></path>
<path d="M8,1.5A1.5,1.5,0,1,1,6.5,0,1.5,1.5,0,0,1,8,1.5Z"></path>
<path d="M13,1.5A1.5,1.5,0,1,1,11.5,0,1.5,1.5,0,0,1,13,1.5Z"></path>
</g>
</svg>
</div>
</div>
<div style="width: 340px; height: 100%; aspect-ratio: auto; position: relative;">
<div style="position: relative; display: flex; justify-content: center; width: 100%; min-height: 100px; height: 352px;">
<div style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; border-radius: 1px;">
<div style="width: 100%; height: 100%;">
<div style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; border-radius: 1px; pointer-events: auto;">
<iframe
src="https://open.spotify.com/embed/playlist/37i9dQZF1DX5KpP2LN299J?utm_source=oembed"
frameborder="0"
sandbox="allow-scripts allow-popups allow-top-navigation-by-user-activation allow-forms allow-same-origin allow-storage-access-by-user-activation allow-popups-to-escape-sandbox"
allowfullscreen=""
style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; border-radius: 1px; pointer-events: auto;"
></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>;
Somehow this doesn’t work:
const heightClass = Height && Height < 380 ? `h-[${Height}px]` : 'h-[380px]';
but this does
<div class="w-full min-h-[100px]" style={Height && Height>380?"height:380px":("height:"+Height?.toString()+"px")}>
it is not reading the tailwind h-[${Height}px]
. I even tried with toString conversion 🤷♀️.
The image stuff
<img src="https://aersf.com/cdn/shop/files/AERL1101_carryon_black_34hero_1200x1200.jpg?v=1715313300" referrerpolicy="same-origin" style="display: block; object-fit: cover; border-radius: 2px; width: 100%; height: 100%; left: 0px; top: calc(50% - 140px);">
Log of I Tried It and It Didn’t Work
I thought maybe I can try changing color of all uploaded icons in dark mode but this doesn’t work
let iconURL = post.Icon && post.Icon.Type === "external" && isNotionIconURL(post.Icon.Url)?post.Icon.Url:null;
//does not work because that needs to be in client side script
if (rootHasDarkClass() && iconURL){iconURL = post.Icon.Url+"?mode=dark";}
So, I am removing this function
export function toggleClass(element: HTMLElement, className: string) {
element.classList.toggle(className);
}
export function elementHasClass(element: HTMLElement, className: string) {
return element.classList.contains(className);
}
export function rootHasDarkClass() {
return elementHasClass(document.documentElement, "dark");
}
export const isNotionIconURL = (url: string): boolean => {
return !url.startsWith("https://www.notion.so/icons/");
};
export const getIconCssFilter = (iconUrl: string): string | null | undefined => {
// Regular expression to match the pattern:
// (1) Any characters after the last underscore
// (2) Ending with ".svg"
const regex = /_([^.]+)\.svg$/;
const match = iconUrl.match(regex);
// If a match is found, return the captured group, which is the color part.
// Otherwise, return null if no match is found.
return match ? match[1] : null;
};
Not supporting reading time/count, so I removed the code related to that
const BLOCKS_FOR_WORD_COUNT: BlockTypes[] = [
"heading_1",
"heading_2",
"heading_3",
"paragraph",
"bulleted_list_item",
"numbered_list_item",
"to_do",
"callout",
"quote",
];
type ContentBlock = {
content: string;
children: ContentBlock[];
};
function getSupportedBlocks(blocks: Block[]): ContentBlock[] {
return blocks
.filter((block) => BLOCKS_FOR_WORD_COUNT.includes(block.Type))
.map((block) => {
if (block.Type === "heading_1" && block.Heading1) {
return {
content: block.Heading1.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.Heading1?.Children || []),
};
}
if (block.Type === "heading_2" && block.Heading2) {
return {
content: block.Heading2.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.Heading2?.Children || []),
};
}
if (block.Type === "heading_3" && block.Heading3) {
return {
content: block.Heading3.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.Heading3?.Children || []),
};
}
if (block.Type === "paragraph" && block.Paragraph) {
return {
content: block.Paragraph.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.Paragraph?.Children || []),
};
}
if (block.Type === "bulleted_list_item" && block.BulletedListItem) {
return {
content: block.BulletedListItem.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.BulletedListItem?.Children || []),
};
}
if (block.Type === "numbered_list_item" && block.NumberedListItem) {
return {
content: block.NumberedListItem.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.NumberedListItem?.Children || []),
};
}
if (block.Type === "to_do" && block.ToDo) {
return {
content: block.ToDo.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.ToDo?.Children || []),
};
}
if (block.Type === "callout" && block.Callout) {
return {
content: block.Callout.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.Callout?.Children || []),
};
}
if (block.Type === "quote" && block.Quote) {
return {
content: block.Quote.RichTexts.map((text) => text.PlainText).join(" "),
children: getSupportedBlocks(block.Quote?.Children || []),
};
}
return { content: "", children: [] };
});
}
function generateTexts(contentBlocks: ContentBlock[]): string {
return contentBlocks
.map((block) => {
return block.content + "\n" + generateTexts(block.children);
})
.join("\n");
}
export function getPageStats(blocks: Block[]) {
const supportedBlocks = getSupportedBlocks(blocks);
const pageText = generateTexts(supportedBlocks);
const wordCount = pageText.split(" ").length;
return {
wordCount,
readTime: `${Math.round((wordCount / 250) * 100) / 100} min`,
};
}
I am using paginate by astro, so I do not need functions to define content by page number
export async function getPageBySlug(slug: string): Promise<Post | null> {
const allPosts = await getAllPages();
return allPosts.find((post) => post.Slug === slug) || null;
}
export async function getPosts(pageSize = 10): Promise<Post[]> {
const allPosts = await getAllPosts();
return allPosts.slice(0, pageSize);
}
export async function getRankedPosts(pageSize = 10): Promise<Post[]> {
const allPosts = await getAllPosts();
return allPosts
.filter((post) => !!post.Rank)
.sort((a, b) => {
if (a.Rank > b.Rank) {
return -1;
} else if (a.Rank === b.Rank) {
return 0;
}
return 1;
})
.slice(0, pageSize);
}
export async function getPostsByTag(tagName: string, pageSize = 10): Promise<Post[]> {
if (!tagName) return [];
const allPosts = await getAllPosts();
return allPosts
.filter((post) => post.Tags.find((tag) => tag.name === tagName))
.slice(0, pageSize);
}
// page starts from 1 not 0
export async function getPostsByPage(page: number): Promise<Post[]> {
if (page < 1) {
return [];
}
const allPosts = await getAllPosts();
const startIndex = (page - 1) * NUMBER_OF_POSTS_PER_PAGE;
const endIndex = startIndex + NUMBER_OF_POSTS_PER_PAGE;
return allPosts.slice(startIndex, endIndex);
}
// page starts from 1 not 0
export async function getPostsByTagAndPage(tagName: string, page: number): Promise<Post[]> {
if (page < 1) {
return [];
}
const allPosts = await getAllPosts();
const posts = allPosts.filter((post) => post.Tags.find((tag) => tag.name === tagName));
const startIndex = (page - 1) * NUMBER_OF_POSTS_PER_PAGE;
const endIndex = startIndex + NUMBER_OF_POSTS_PER_PAGE;
return posts.slice(startIndex, endIndex);
}
export async function getNumberOfPages(): Promise<number> {
const allPosts = await getAllPosts();
return (
Math.floor(allPosts.length / NUMBER_OF_POSTS_PER_PAGE) +
(allPosts.length % NUMBER_OF_POSTS_PER_PAGE > 0 ? 1 : 0)
);
}
export async function getNumberOfPagesByTag(tagName: string): Promise<number> {
const allPosts = await getAllPosts();
const posts = allPosts.filter((post) => post.Tags.find((tag) => tag.name === tagName));
return (
Math.floor(posts.length / NUMBER_OF_POSTS_PER_PAGE) +
(posts.length % NUMBER_OF_POSTS_PER_PAGE > 0 ? 1 : 0)
);
}
Removing stuff from blog-helpers
date formatting is a different file
export const getDateStr = (date: string) => {
const dt = new Date(date);
if (date.indexOf("T") !== -1) {
// Consider timezone
const elements = date.split("T")[1].split(/([+-])/);
if (elements.length > 1) {
const diff = parseInt(`${elements[1]}${elements[2]}`, 10);
dt.setHours(dt.getHours() + diff);
}
}
const y = dt.getFullYear();
const m = ("00" + (dt.getMonth() + 1)).slice(-2);
const d = ("00" + dt.getDate()).slice(-2);
return y + "-" + m + "-" + d;
};
didn’t use page or tag links
export const getTagLink = (tag: string) => {
return path.join(BASE_PATH, `/posts/tag/${encodeURIComponent(tag)}`);
};
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()}`);
};
have 100 functions for file path
export const getStaticFilePath = (pathsup: string): string => {
return path.join(BASE_PATH, pathsup);
};