From 5300e5a423cc6e87c1e1634188ca43a3277064f1 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:46:28 -0500
Subject: [PATCH 01/23] header: highlight "notes" when on a bookmark page
---
src/components/Header.astro | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/components/Header.astro b/src/components/Header.astro
index db9c987f..d2d1842b 100644
--- a/src/components/Header.astro
+++ b/src/components/Header.astro
@@ -5,10 +5,12 @@ import { isPathnameInCollection } from '../utils/collections'
import { getPosts } from '../utils/posts'
import { getNotes } from '../utils/notes'
import { getTILs } from '../utils/tils'
+import { getBookmarks } from '../utils/bookmarks'
const posts = await getPosts()
-const notes = await getNotes()
const tils = await getTILs()
+const notes = await getNotes()
+const bookmarks = await getBookmarks()
const { pathname } = Astro.url
@@ -16,8 +18,9 @@ const { pathname } = Astro.url
const isCurrentPage = (item: NavItem): boolean =>
item.url === pathname ||
(isPathnameInCollection(pathname, posts) && item.url === '/') ||
+ (isPathnameInCollection(pathname, tils) && item.url === '/til/') ||
(isPathnameInCollection(pathname, notes) && item.url === '/notes/') ||
- (isPathnameInCollection(pathname, tils) && item.url === '/til/')
+ (isPathnameInCollection(pathname, bookmarks) && item.url === '/notes/')
---
From fadedfeadddde7ac3f1e8941fe31c8e87b405574 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:46:45 -0500
Subject: [PATCH 02/23] comments: only show on post pages
---
src/layouts/Writing.astro | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/layouts/Writing.astro b/src/layouts/Writing.astro
index 022981d8..f0b01d02 100644
--- a/src/layouts/Writing.astro
+++ b/src/layouts/Writing.astro
@@ -106,8 +106,7 @@ const isDraft = isPathnameInCollection(pathname, drafts)
}
{
- (
- // isPathnameInCollection(pathname, posts) && (
+ isPathnameInCollection(pathname, posts) && (
- // )
)
}
From b42ce2f6d33a16a006fbb05e1085f734addd26f1 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:46:58 -0500
Subject: [PATCH 03/23] slug: render bookmark routes
---
src/pages/[slug].astro | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/pages/[slug].astro b/src/pages/[slug].astro
index b5dad8bc..5ebb1c41 100644
--- a/src/pages/[slug].astro
+++ b/src/pages/[slug].astro
@@ -3,15 +3,17 @@ import Writing from '../layouts/Writing.astro'
import { getTILs } from '../utils/tils'
import { getNotes } from '../utils/notes'
import { getDrafts, getPosts } from '../utils/posts'
+import { getBookmarks } from '../utils/bookmarks'
export async function getStaticPaths() {
const posts = await getPosts()
const tils = await getTILs()
const notes = await getNotes()
+ const bookmarks = await getBookmarks()
const drafts = await getDrafts()
// Generate a path for each writing collection entry
- return [...posts, ...tils, ...notes, ...drafts].map(entry => ({
+ return [...posts, ...tils, ...notes, ...bookmarks, ...drafts].map(entry => ({
params: { slug: entry.slug },
props: { entry },
}))
From 00cb51573aa53b3627233da8c7ad69e2e1680e62 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:47:34 -0500
Subject: [PATCH 04/23] notes: render bookmarks + drafts + notes on index page
---
src/pages/notes.astro | 90 +++++++++++++++++++++++++++++++++++--------
1 file changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/pages/notes.astro b/src/pages/notes.astro
index 87db3c4c..8f90114f 100644
--- a/src/pages/notes.astro
+++ b/src/pages/notes.astro
@@ -1,9 +1,43 @@
---
import Main from '../layouts/Main.astro'
-import type { Writing } from '../utils/collections'
-import { getNestedNotes } from '../utils/notes'
+import { getBookmarks } from '../utils/bookmarks'
+import { isPathnameInCollection, type Bookmark, type Writing, type Draft } from '../utils/collections'
+import { getAllTagsInNotes, getNotes, getNestedNotes } from '../utils/notes'
+import { getDrafts } from '../utils/posts'
+import { cleanTags } from '../utils/tags'
-const notes = await getNestedNotes()
+const bookmarks = await getBookmarks()
+const notesFlat = await getNotes()
+const notesByParent = await getNestedNotes()
+// const notesByTag = await getNotesByTag()
+const tags = await getAllTagsInNotes()
+
+const drafts = await getDrafts()
+
+const getEmoji = (item: Bookmark | Draft | Writing): string => {
+ if (item.data.favicon) {
+ return ``
+ }
+
+ if (item.data.url) {
+ const url = new URL(item.data.url)
+ if (url.hostname.includes('youtube.com')) {
+ return '📺'
+ } else if (url.hostname.includes('github.com')) {
+ return '🧰'
+ } else if (url.hostname.includes('reddit.com')) {
+ return '💬'
+ } else {
+ return '📖'
+ }
+ }
+
+ if (isPathnameInCollection(item.slug, drafts)) {
+ return '✍️'
+ }
+
+ return '📝'
+}
---
@@ -12,21 +46,9 @@ const notes = await getNestedNotes()
Topics
-
-
-
- {
- notes.map(note => {
+ notesByParent.map(note => {
const getLinkText = (item: Writing): string => item.data.linkText || item.data.title || item.slug
const getChildren = (item: Writing, level: number) =>
@@ -64,6 +86,40 @@ const notes = await getNestedNotes()
)
})
}
+
-->
+
+
+
+
+
+
+
+ {
+ [...bookmarks, ...drafts, ...notesFlat].map(item => (
+ -
+
+
+ {item.data.title || item.slug}
+
+ {/* TODO: show item tags? */}
+ {/*
+ {cleanTags(note.data.tags).map(tag => (
+ -
+ {tag}
+
+ ))}
+
*/}
+
+ ))
+ }
From f955898a0e4f876b39b33a986179ca86af412821 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:48:03 -0500
Subject: [PATCH 05/23] collections: add Bookmark type
---
src/utils/collections.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/utils/collections.ts b/src/utils/collections.ts
index c1229327..b99c9200 100644
--- a/src/utils/collections.ts
+++ b/src/utils/collections.ts
@@ -1,5 +1,7 @@
import type { CollectionEntry } from 'astro:content'
+// TODO: add Note? put in dedicated folder?
+export type Bookmark = CollectionEntry<'bookmarks'>
export type Draft = CollectionEntry<'drafts'>
export type TIL = CollectionEntry<'til'>
export type Writing = CollectionEntry<'writing'>
From 238a4b0abc53d3c0f0cbe51b24f7bdcfba4a363e Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:48:22 -0500
Subject: [PATCH 06/23] isPathnameInCollection: ignore leading/trailing slashes
when comparing slugs and paths
---
src/utils/collections.ts | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/utils/collections.ts b/src/utils/collections.ts
index b99c9200..a6cd943c 100644
--- a/src/utils/collections.ts
+++ b/src/utils/collections.ts
@@ -9,8 +9,14 @@ export type Writing = CollectionEntry<'writing'>
/**
* Returns true if the pathname matches any slug in the collection (at any ancestry level)
*/
-export const isPathnameInCollection = (pathname: string, collection: Writing[] | Draft[] | TIL[]): boolean => {
- const pathnameMatchesSlug = (slug: string): boolean => pathname === `/${slug}/`
+export const isPathnameInCollection = (
+ pathname: string | undefined,
+ collection: Writing[] | Draft[] | TIL[] | Bookmark[],
+): boolean => {
+ const removeLeadingAndTrailingSlashes = (str?: string): string => (str ? str.replace(/^\/|\/$/g, '') : '')
+
+ const pathnameMatchesSlug = (slug: string): boolean =>
+ removeLeadingAndTrailingSlashes(pathname) === removeLeadingAndTrailingSlashes(slug)
return collection.some(
item =>
From a5e73dd8b69d24c24545b4b4c7d3432e4d4efc67 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:48:53 -0500
Subject: [PATCH 07/23] notes: add tag helpers
---
src/utils/notes.ts | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/src/utils/notes.ts b/src/utils/notes.ts
index 7d89c3ab..46804d8d 100644
--- a/src/utils/notes.ts
+++ b/src/utils/notes.ts
@@ -1,6 +1,7 @@
import { getCollection } from 'astro:content'
import type { Writing } from './collections'
import { isPost } from './posts'
+import { cleanTags } from './tags'
/**
* Given an array of collection items, returns the array with child items nested under their parents.
@@ -76,6 +77,34 @@ const removePrivateNotes = (notes: Writing[]): Writing[] =>
// return collection
// }
+/**
+ * Returns a flat list of all tags found in all notes.
+ */
+export const getAllTagsInNotes = async (): Promise =>
+ getNotes().then(notes => cleanTags(notes.flatMap(note => note.data.tags)))
+
+/**
+ * Returns a flat list of all notes with matching tags.
+ */
+export const getNotesWithTags = async (tags: string[]): Promise =>
+ getNotes().then(notes => notes.filter(note => (note.data.tags ?? []).some(tag => tags.includes(tag))))
+
+/**
+ * Returns all tags with their respective notes.
+ */
+export const getNotesByTag = async (): Promise> => {
+ const tags = await getAllTagsInNotes()
+
+ const notesByTagEntries = await Promise.all(
+ tags.map(async tag => {
+ const notes = await getNotesWithTags([tag])
+ return [tag, notes]
+ }),
+ )
+
+ return Object.fromEntries(notesByTagEntries)
+}
+
/**
* Returns a flat list of all notes with private notes removed (in production).
*/
From 6743b80a6ddfcd331c59ea14ef81f60367fe5411 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:49:07 -0500
Subject: [PATCH 08/23] bookmarks: add helpers
---
src/utils/bookmarks.ts | 53 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 src/utils/bookmarks.ts
diff --git a/src/utils/bookmarks.ts b/src/utils/bookmarks.ts
new file mode 100644
index 00000000..ba0ec487
--- /dev/null
+++ b/src/utils/bookmarks.ts
@@ -0,0 +1,53 @@
+import { getCollection } from 'astro:content'
+import type { Bookmark } from './collections'
+import { cleanTags } from './tags'
+
+/**
+ * Returns true if file is a non-private bookmark.
+ * TODO: add unit tests
+ */
+const isPublicBookmark = (bookmark: Bookmark): boolean =>
+ !bookmark.data.private && !(bookmark.tags || []).includes('recursion')
+
+/**
+ * In production, remove all private bookmarks.
+ * TODO: add unit tests
+ */
+const removePrivateBookmarks = (bookmarks: Bookmark[]): Bookmark[] =>
+ import.meta.env.PROD ? bookmarks.filter(bookmark => isPublicBookmark(bookmark)) : bookmarks
+
+/**
+ * Returns a flat list of all tags found in all bookmarks.
+ * TODO: add unit tests
+ */
+export const getAllTagsInBookmarks = async (): Promise =>
+ getBookmarks().then(bookmarks => cleanTags(bookmarks.flatMap(bookmark => bookmark.data.tags)))
+
+/**
+ * Returns a flat list of all notes with matching tags.
+ */
+export const getBookmarksWithTags = async (tags: string[]): Promise =>
+ getBookmarks().then(bookmarks =>
+ bookmarks.filter(bookmark => (bookmark.data.tags || []).some(tag => tags.includes(tag))),
+ )
+
+/**
+ * Returns all tags with their respective bookmarks.
+ */
+export const getBookmarksByTag = async (): Promise> => {
+ const tags = await getAllTagsInBookmarks()
+
+ const bookmarksByTagEntries = await Promise.all(
+ tags.map(async tag => {
+ const bookmarks = await getBookmarksWithTags([tag])
+ return [tag, bookmarks]
+ }),
+ )
+
+ return Object.fromEntries(bookmarksByTagEntries)
+}
+
+/**
+ * Returns a flat list of all bookmarks with private bookmarks removed (in production).
+ */
+export const getBookmarks = async (): Promise => removePrivateBookmarks(await getCollection('bookmarks'))
From 39dbe6cb68c61eb45e5cf22a5e2f81396973b8d8 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 13:49:14 -0500
Subject: [PATCH 09/23] tags: add cleaning helper
---
src/utils/tags.ts | 10 ++++++++++
1 file changed, 10 insertions(+)
create mode 100644 src/utils/tags.ts
diff --git a/src/utils/tags.ts b/src/utils/tags.ts
new file mode 100644
index 00000000..14e2029a
--- /dev/null
+++ b/src/utils/tags.ts
@@ -0,0 +1,10 @@
+export const cleanTags = (tags?: string[]): string[] =>
+ Array.from(
+ new Set(
+ (tags ?? [])
+ .filter(Boolean)
+ .filter(tag => !['note'].includes(tag))
+ .map(tag => tag.replace('topic/', ''))
+ .map(tag => tag.replaceAll('-', ' ')),
+ ),
+ ).sort()
From 54aed5ad4747134384c518efc9e042b157de927d Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:21:32 -0500
Subject: [PATCH 10/23] remark: get last modified date of content files in
submodule
---
lib/remark/last-modified.ts | 49 +++++++++++++++++++++++++++++++++++++
lib/remark/plugins.ts | 2 ++
src/pages/[slug].astro | 7 ++++--
src/pages/notes.astro | 10 ++++++++
src/utils/bookmarks.ts | 7 ++++--
src/utils/collections.ts | 9 +++++++
src/utils/notes.ts | 10 +++++---
src/utils/posts.ts | 24 +++++++++++-------
src/utils/tils.ts | 6 ++---
9 files changed, 103 insertions(+), 21 deletions(-)
create mode 100644 lib/remark/last-modified.ts
diff --git a/lib/remark/last-modified.ts b/lib/remark/last-modified.ts
new file mode 100644
index 00000000..6865219a
--- /dev/null
+++ b/lib/remark/last-modified.ts
@@ -0,0 +1,49 @@
+// see: https://docs.astro.build/en/recipes/modified-time/
+
+import { execSync } from 'child_process'
+import { resolve } from 'path'
+
+function remarkModifiedTime() {
+ return function (tree, file) {
+ const filepath = file.history[0]
+
+ try {
+ // Resolve the absolute path of the file
+ const absoluteFilePath = resolve(filepath)
+
+ // Get the root directory of the repository
+ const repoRoot = execSync('git rev-parse --show-toplevel').toString().trim()
+
+ // Get the relative path of the file from the repository root
+ const relativeFilePath = absoluteFilePath.replace(`${repoRoot}/`, '')
+
+ // Check if the file is within a submodule
+ const submodulePath = execSync(`git submodule foreach --quiet 'echo $path'`)
+ .toString()
+ .trim()
+ .split('\n')
+ .find(submodule => relativeFilePath.startsWith(submodule))
+
+ let result
+ if (submodulePath) {
+ // Navigate to the submodule directory and get the last modified time
+ const submoduleAbsolutePath = resolve(repoRoot, submodulePath)
+ const submoduleRelativeFilePath = absoluteFilePath.replace(`${submoduleAbsolutePath}/`, '')
+ result = execSync(
+ `cd ${submoduleAbsolutePath} && git log -1 --pretty="format:%cI" "${submoduleRelativeFilePath}"`,
+ )
+ .toString()
+ .trim()
+ } else {
+ // Get the last modified time in the main repository
+ result = execSync(`git log -1 --pretty="format:%cI" "${relativeFilePath}"`).toString().trim()
+ }
+
+ file.data.astro.frontmatter.lastModified = result
+ } catch (error) {
+ console.error('Error getting last modified time:', error)
+ }
+ }
+}
+
+export default remarkModifiedTime
diff --git a/lib/remark/plugins.ts b/lib/remark/plugins.ts
index 9346a9aa..af015586 100644
--- a/lib/remark/plugins.ts
+++ b/lib/remark/plugins.ts
@@ -1,9 +1,11 @@
import remarkUnwrapImages from 'remark-unwrap-images'
import remarkWikiLink from '@portaljs/remark-wiki-link'
+import remarkLastModified from './last-modified'
import remarkRemoveTags from './remove-tags'
export default [
+ remarkLastModified,
remarkRemoveTags,
remarkUnwrapImages,
[
diff --git a/src/pages/[slug].astro b/src/pages/[slug].astro
index 5ebb1c41..06798bef 100644
--- a/src/pages/[slug].astro
+++ b/src/pages/[slug].astro
@@ -20,9 +20,12 @@ export async function getStaticPaths() {
}
const { entry } = Astro.props
-const { Content } = await entry.render()
+const { Content, remarkPluginFrontmatter } = await entry.render()
+
+// see: https://docs.astro.build/en/recipes/modified-time/
+const entryWithRemarkFrontmatter = { ...entry, data: { ...entry.data, ...remarkPluginFrontmatter } }
---
-
+
diff --git a/src/pages/notes.astro b/src/pages/notes.astro
index 8f90114f..f442170c 100644
--- a/src/pages/notes.astro
+++ b/src/pages/notes.astro
@@ -14,6 +14,16 @@ const tags = await getAllTagsInNotes()
const drafts = await getDrafts()
+const notesByLastModified = [...bookmarks, ...notes, ...drafts].sort((a, b) => {
+ if (a.data.lastModified < b.data.lastModified) {
+ return 1
+ }
+ if (a.data.lastModified > b.data.lastModified) {
+ return -1
+ }
+ return 0
+})
+
const getEmoji = (item: Bookmark | Draft | Writing): string => {
if (item.data.favicon) {
return ``
diff --git a/src/utils/bookmarks.ts b/src/utils/bookmarks.ts
index ba0ec487..96059923 100644
--- a/src/utils/bookmarks.ts
+++ b/src/utils/bookmarks.ts
@@ -1,5 +1,5 @@
import { getCollection } from 'astro:content'
-import type { Bookmark } from './collections'
+import { addRemarkFrontmatter, type Bookmark } from './collections'
import { cleanTags } from './tags'
/**
@@ -50,4 +50,7 @@ export const getBookmarksByTag = async (): Promise> =
/**
* Returns a flat list of all bookmarks with private bookmarks removed (in production).
*/
-export const getBookmarks = async (): Promise => removePrivateBookmarks(await getCollection('bookmarks'))
+export const getBookmarks = async (): Promise => {
+ const bookmarksToShow = removePrivateBookmarks(await getCollection('bookmarks'))
+ return Promise.all(bookmarksToShow.map(bookmark => addRemarkFrontmatter(bookmark)))
+}
diff --git a/src/utils/collections.ts b/src/utils/collections.ts
index a6cd943c..b85fe0a8 100644
--- a/src/utils/collections.ts
+++ b/src/utils/collections.ts
@@ -24,3 +24,12 @@ export const isPathnameInCollection = (
(item.data.children || []).some((child: Writing): boolean => pathnameMatchesSlug(child.slug)),
)
}
+
+export const addRemarkFrontmatter = async (entry: CollectionEntry): Promise => {
+ const { remarkPluginFrontmatter } = await entry.render()
+
+ // see: https://docs.astro.build/en/recipes/modified-time/
+ const entryWithRemarkFrontmatter = { ...entry, data: { ...entry.data, ...remarkPluginFrontmatter } }
+
+ return entryWithRemarkFrontmatter
+}
diff --git a/src/utils/notes.ts b/src/utils/notes.ts
index 46804d8d..20094e54 100644
--- a/src/utils/notes.ts
+++ b/src/utils/notes.ts
@@ -1,5 +1,5 @@
import { getCollection } from 'astro:content'
-import type { Writing } from './collections'
+import { addRemarkFrontmatter, type Writing } from './collections'
import { isPost } from './posts'
import { cleanTags } from './tags'
@@ -106,10 +106,12 @@ export const getNotesByTag = async (): Promise> => {
}
/**
- * Returns a flat list of all notes with private notes removed (in production).
+ * Returns a flat list of all notes with private notes removed (in production) and sorted by last modified date.
*/
-export const getNotes = async (): Promise =>
- removePrivateNotes(await getCollection('writing', note => isNote(note)))
+export const getNotes = async (): Promise => {
+ const notesToShow = removePrivateNotes(await getCollection('writing', note => isNote(note)))
+ return Promise.all(notesToShow.map(note => addRemarkFrontmatter(note)))
+}
/**
* Returns all notes with child notes nested under their parents (always) and private notes removed (in production).
diff --git a/src/utils/posts.ts b/src/utils/posts.ts
index 776252c4..774da7c2 100644
--- a/src/utils/posts.ts
+++ b/src/utils/posts.ts
@@ -1,6 +1,6 @@
import { getCollection } from 'astro:content'
-import type { Draft, Writing } from './collections'
+import { addRemarkFrontmatter, type Draft, type Writing } from './collections'
// TODO: move post definitions to src/content/config.ts?
export type Post = Writing & { data: { destination?: 'blog'; tags?: string[]; date?: string } }
@@ -22,7 +22,7 @@ const isPublished = (post: Writing): post is PostWithDate =>
/**
* Sorts two drafts in ascending order by slug.
*/
-const sortBySlug = (a: Draft, b: Draft): number => a.slug.localeCompare(b.slug)
+// const sortBySlug = (a: Draft, b: Draft): number => a.slug.localeCompare(b.slug)
/**
* Sorts two posts in descending order by publish date (or the current date if either post is a draft with no date).
@@ -38,7 +38,7 @@ const sortPosts = (posts: Post[]): Post[] => posts.sort(sortByDate)
/**
* Returns drafts sorted in ascending order by slug, then descending order by date (so scheduled drafts appear first, followed by unscheduled drafts in alphabetical order).
*/
-const sortDrafts = (drafts: Draft[]): Draft[] => drafts.sort(sortBySlug).sort(sortByDate)
+// const sortDrafts = (drafts: Draft[]): Draft[] => drafts.sort(sortBySlug).sort(sortByDate)
/**
* Returns all published posts, in descending order by date (useful for RSS feed).
@@ -47,13 +47,19 @@ export const getPublishedPosts = async (): Promise =>
sortPosts(await getCollection('writing', isPublished))
/**
- * Returns all posts in development and only published posts in production, in descending order by date.
+ * Returns all posts in development and only published posts in production, in descending order by date, with last modified date added.
*/
-export const getPosts = async (): Promise =>
- sortPosts(await getCollection('writing', post => (import.meta.env.PROD ? isPublished(post) : isPost(post))))
+export const getPosts = async (): Promise => {
+ const postsToShow = sortPosts(
+ await getCollection('writing', post => (import.meta.env.PROD ? isPublished(post) : isPost(post))),
+ )
+ return Promise.all(postsToShow.map(post => addRemarkFrontmatter(post)))
+}
/**
- * Returns all drafts in development and none in production, in descending order by date (if scheduled), then ascending order by title.
+ * Returns all drafts in both development and production, with last modified date added.
*/
-export const getDrafts = async (): Promise =>
- sortDrafts(await getCollection('drafts', () => import.meta.env.DEV))
+export const getDrafts = async (): Promise => {
+ const draftsToShow = await getCollection('drafts')
+ return Promise.all(draftsToShow.map(draft => addRemarkFrontmatter(draft)))
+}
diff --git a/src/utils/tils.ts b/src/utils/tils.ts
index 29735481..178e3637 100644
--- a/src/utils/tils.ts
+++ b/src/utils/tils.ts
@@ -42,12 +42,10 @@ export const getTILs = async (): Promise => {
const tilsWithContent: TILWithContent[] = await Promise.all(
tilProperties.map(async til => {
const entry = await getEntry('til', til.slug)
- const { Content, headings } = await entry!.render() // TODO: make safer
- return { ...til, Content, headings }
+ const { Content, headings, remarkPluginFrontmatter } = await entry!.render() // TODO: make safer
+ return { ...til, Content, headings, data: { ...til.data, ...remarkPluginFrontmatter } }
}),
)
return tilsWithContent.sort(sortByDate)
-
- // return sortTILs(tilsWithContent)
}
From 7d420a0ac196cb4b5b35783adefedd50abe8fe12 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:22:00 -0500
Subject: [PATCH 11/23] header: highlight notes nav link when on a draft page
---
src/components/Header.astro | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/components/Header.astro b/src/components/Header.astro
index d2d1842b..a9424ff3 100644
--- a/src/components/Header.astro
+++ b/src/components/Header.astro
@@ -2,13 +2,14 @@
import nav, { type NavItem } from '../data/nav'
import site from '../data/site'
import { isPathnameInCollection } from '../utils/collections'
-import { getPosts } from '../utils/posts'
+import { getDrafts, getPosts } from '../utils/posts'
import { getNotes } from '../utils/notes'
import { getTILs } from '../utils/tils'
import { getBookmarks } from '../utils/bookmarks'
const posts = await getPosts()
const tils = await getTILs()
+const drafts = await getDrafts()
const notes = await getNotes()
const bookmarks = await getBookmarks()
@@ -19,6 +20,7 @@ const isCurrentPage = (item: NavItem): boolean =>
item.url === pathname ||
(isPathnameInCollection(pathname, posts) && item.url === '/') ||
(isPathnameInCollection(pathname, tils) && item.url === '/til/') ||
+ (isPathnameInCollection(pathname, drafts) && item.url === '/notes/') ||
(isPathnameInCollection(pathname, notes) && item.url === '/notes/') ||
(isPathnameInCollection(pathname, bookmarks) && item.url === '/notes/')
---
From 35adcc2da80693852b19747d80a7a17437646871 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:22:17 -0500
Subject: [PATCH 12/23] base: block indexing drafts and bookmarks
---
src/layouts/Base.astro | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
index 4caa2067..ac520676 100644
--- a/src/layouts/Base.astro
+++ b/src/layouts/Base.astro
@@ -1,10 +1,11 @@
---
import '../styles/global.css'
import site from '../data/site'
-import { getPosts } from '../utils/posts'
+import { getDrafts, getPosts } from '../utils/posts'
import { getNotes } from '../utils/notes'
import { isPathnameInCollection } from '../utils/collections'
import { getTILs } from '../utils/tils'
+import { getBookmarks } from '../utils/bookmarks'
export type BaseProps = {
description?: string
@@ -19,7 +20,15 @@ const { pathname } = Astro.url
const posts = await getPosts()
const tils = await getTILs()
+const drafts = await getDrafts()
const notes = await getNotes()
+const bookmarks = await getBookmarks()
+
+const isPost = isPathnameInCollection(pathname, posts)
+const isTIL = isPathnameInCollection(pathname, tils)
+const isDraft = isPathnameInCollection(pathname, drafts)
+const isNote = isPathnameInCollection(pathname, notes)
+const isBookmark = isPathnameInCollection(pathname, bookmarks)
const isHome = pathname === '/'
@@ -73,12 +82,16 @@ const pageTitleWithSuffix = pageTitle + (isHome ? '' : ` • ${site.title}`)
const pageDescription = description
? description
- : isPathnameInCollection(pathname, notes)
- ? `Notes about ${title} by ${site.author.name}.`
- : isPathnameInCollection(pathname, posts)
+ : isPost
? `Blog post by ${site.author.name}.`
- : isPathnameInCollection(pathname, tils)
+ : isTIL
? `Something ${site.author.name} learned about today.`
+ : isDraft
+ ? `Notes for a future blog post by ${site.author.name}.`
+ : isNote
+ ? `Notes about ${title} by ${site.author.name}.`
+ : isBookmark
+ ? `Notes about ${title}.`
: site.description.site
const borderWidth = 0
@@ -160,6 +173,10 @@ const socialImage = ogImage
+
+
+
+ {isDraft || isBookmark ? : null}
From 5e7ae12a834860c64028f4501b565748a59bd72b Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:23:56 -0500
Subject: [PATCH 13/23] related: extract to separate component
---
src/components/Related.astro | 58 ++++++++++++++++++
src/layouts/Writing.astro | 110 ++++++-----------------------------
2 files changed, 77 insertions(+), 91 deletions(-)
create mode 100644 src/components/Related.astro
diff --git a/src/components/Related.astro b/src/components/Related.astro
new file mode 100644
index 00000000..9a568fd1
--- /dev/null
+++ b/src/components/Related.astro
@@ -0,0 +1,58 @@
+---
+import { type Draft, isPathnameInCollection, type Writing } from '../utils/collections'
+import { getNotes } from '../utils/notes'
+import { getDrafts, getPosts } from '../utils/posts'
+import { getHumanReadableDate, getMachineReadableDate } from '../utils/dates'
+import { getTILs } from '../utils/tils'
+import { getBookmarks } from '../utils/bookmarks'
+import { getEntriesWithTags as getEntriesWithSameTags } from '../utils/tags'
+
+type Props = {
+ entry: Writing | Draft
+}
+
+const { entry } = Astro.props
+const { pathname } = Astro.url
+
+const posts = await getPosts()
+const tils = await getTILs()
+const bookmarks = await getBookmarks()
+const drafts = await getDrafts()
+const notes = await getNotes()
+
+const isPost = isPathnameInCollection(pathname, posts)
+
+const entriesWithSameTags = await getEntriesWithSameTags(entry, [...posts, ...tils, ...drafts, ...notes, ...bookmarks])
+console.log('entriesWithSameTags:', entriesWithSameTags)
+
+const hasRelated = Object.keys(entriesWithSameTags).length > 0
+---
+
+{
+ isPost || !hasRelated ? null : (
+
+ )
+}
diff --git a/src/layouts/Writing.astro b/src/layouts/Writing.astro
index f0b01d02..4ff18d29 100644
--- a/src/layouts/Writing.astro
+++ b/src/layouts/Writing.astro
@@ -1,9 +1,9 @@
---
import { type Draft, isPathnameInCollection, type Writing } from '../utils/collections'
-import { getNotes } from '../utils/notes'
import { getDrafts, getPosts } from '../utils/posts'
import Main from './Main.astro'
import { getHumanReadableDate, getMachineReadableDate } from '../utils/dates'
+import Related from '../components/Related.astro'
type Props = {
entry: Writing | Draft
@@ -13,49 +13,15 @@ const { entry } = Astro.props
const { pathname } = Astro.url
const title = entry.data.title || entry.slug
-const date = entry.data.date || Date.now()
-
-// TODO: consider tags as well as slug prefixes?
-const getEntryTopics = (entry: Writing | Draft): string[] => {
- const topic: string = entry.slug.split('-').at(0) ?? ''
- const parentTopic: string = entry.data?.parent?.split('-').at(0) ?? ''
- const allTopics = [...new Set([topic, parentTopic].filter(Boolean))]
- return allTopics
-}
-
-const entryTopics = getEntryTopics(entry)
+const date = entry.data.date || entry.data.lastModified
const posts = await getPosts()
-const relatedPosts = posts
- .filter(post => {
- if (post.slug === entry.slug) return false
- const postTopics = getEntryTopics(post)
- return postTopics.some(topic => entryTopics.includes(topic))
- })
- .map(post => ({
- ...post,
- topics: getEntryTopics(post),
- }))
-
-const notes = await getNotes()
-// TODO: add relatedNotes or topics to notes in notes.ts?
-// TODO: walk entire related tree of topic? use getNestedNotes instead of this flat list?
-const relatedNotes = notes
- .filter(note => {
- if (note.slug === entry.slug) return false
- const noteTopics = getEntryTopics(note)
- return noteTopics.some(topic => entryTopics.includes(topic))
- })
- .map(note => ({
- ...note,
- topics: getEntryTopics(note),
- }))
+const drafts = await getDrafts()
const isPost = isPathnameInCollection(pathname, posts)
-const hasRelated = relatedPosts.length > 0 || relatedNotes.length > 0
-
-const drafts = await getDrafts()
const isDraft = isPathnameInCollection(pathname, drafts)
+
+// const tags = cleanTags(entry.data.tags)
---
@@ -70,40 +36,23 @@ const isDraft = isPathnameInCollection(pathname, drafts)
}
{title}
-
-
-
- {
- !isPost && hasRelated && (
-
-->
From 76e93253d5505a32228a763f700af35a5fcc8c1b Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:25:55 -0500
Subject: [PATCH 14/23] tag: extract markup (may not be necessary)
---
src/components/Tag.astro | 9 +++++++++
src/pages/notes.astro | 9 ++++++---
2 files changed, 15 insertions(+), 3 deletions(-)
create mode 100644 src/components/Tag.astro
diff --git a/src/components/Tag.astro b/src/components/Tag.astro
new file mode 100644
index 00000000..a50f6e45
--- /dev/null
+++ b/src/components/Tag.astro
@@ -0,0 +1,9 @@
+---
+const { tag } = Astro.props
+
+/* */
+---
+
+
+ {tag}
+
diff --git a/src/pages/notes.astro b/src/pages/notes.astro
index f442170c..0885818f 100644
--- a/src/pages/notes.astro
+++ b/src/pages/notes.astro
@@ -1,4 +1,5 @@
---
+import Tag from '../components/Tag.astro'
import Main from '../layouts/Main.astro'
import { getBookmarks } from '../utils/bookmarks'
import { isPathnameInCollection, type Bookmark, type Writing, type Draft } from '../utils/collections'
@@ -100,14 +101,16 @@ const getEmoji = (item: Bookmark | Draft | Writing): string => {
+
{
tags.map(tag => (
- -
- {tag}
+
-
+
))
}
-
-->
+
From 3911155b2b938ce26f2b6c7606323aa4714598dc Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:26:09 -0500
Subject: [PATCH 15/23] css: add todo re selection styles
---
src/styles/global.css | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/styles/global.css b/src/styles/global.css
index 9a4ad1f6..51c5d438 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -20,6 +20,11 @@
@apply flex flex-col overflow-x-hidden py-7 px-4 sm:px-6 min-h-screen text-[1.1rem] leading-relaxed;
@apply bg-zinc-900 text-zinc-400;
}
+
+ /* FIXME: bg color doesn't look right... */
+ /* ::selection {
+ @apply bg-[--moonlight-red] text-black;
+ } */
}
@layer utilities {
From 7f8911ed7c79e785b49b3c119a3423f866dd4f3a Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:26:26 -0500
Subject: [PATCH 16/23] bookmark: fix path to tags for privacy detection
---
src/utils/bookmarks.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/bookmarks.ts b/src/utils/bookmarks.ts
index 96059923..6d6d1b61 100644
--- a/src/utils/bookmarks.ts
+++ b/src/utils/bookmarks.ts
@@ -7,7 +7,7 @@ import { cleanTags } from './tags'
* TODO: add unit tests
*/
const isPublicBookmark = (bookmark: Bookmark): boolean =>
- !bookmark.data.private && !(bookmark.tags || []).includes('recursion')
+ !bookmark.data.private && !(bookmark.data.tags || []).includes('recursion')
/**
* In production, remove all private bookmarks.
From 213abd2d936791073b41fea6d97f2e8aaff81ee9 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:26:47 -0500
Subject: [PATCH 17/23] tags: find matching entries
---
src/utils/tags.ts | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/src/utils/tags.ts b/src/utils/tags.ts
index 14e2029a..81c31d55 100644
--- a/src/utils/tags.ts
+++ b/src/utils/tags.ts
@@ -1,10 +1,44 @@
+import type { Bookmark, Draft, TIL, Writing } from './collections'
+import type { Post } from './posts'
+
export const cleanTags = (tags?: string[]): string[] =>
Array.from(
new Set(
(tags ?? [])
.filter(Boolean)
- .filter(tag => !['note'].includes(tag))
+ .filter(tag => !['bookmark', 'note', 'post', 'til'].includes(tag))
+ .map(tag => tag.replace('s/', ''))
+ .map(tag => tag.replace('t/', ''))
.map(tag => tag.replace('topic/', ''))
.map(tag => tag.replaceAll('-', ' ')),
),
).sort()
+
+/**
+ * Returns a mapping of the entry's tags to lists of other content entries with that tag.
+ */
+export const getEntriesWithTags = async (
+ entry: Bookmark | Draft | TIL | Post | Writing,
+ collections: (Bookmark | Draft | TIL | Post | Writing)[],
+): Promise> => {
+ const relatedByTag: Record = {}
+
+ for (const item of collections) {
+ // Go in order of the entry's tags, which are presumably sorted from most to least relevant
+ for (const tag of cleanTags(entry.data.tags) ?? []) {
+ if ((item.data.tags ?? []).includes(tag)) {
+ if (item.data.title === entry.data.title) {
+ continue
+ }
+
+ if (!relatedByTag[tag]) {
+ relatedByTag[tag] = []
+ }
+
+ relatedByTag[tag].push(item)
+ }
+ }
+ }
+
+ return relatedByTag
+}
From 27366ad79d83587e2137d03c46a4038cbf4793fa Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:27:19 -0500
Subject: [PATCH 18/23] drafts: move to notes page
---
src/pages/index.astro | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 18e355b8..8c5d7dc9 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,15 +1,12 @@
---
import site from '../data/site'
import Main from '../layouts/Main.astro'
-import { type Draft, isPathnameInCollection } from '../utils/collections'
import { getHumanReadableDate, getMachineReadableDate } from '../utils/dates'
-import { getDrafts, getPosts, type Post } from '../utils/posts'
+import { getPosts, type Post } from '../utils/posts'
const posts = await getPosts()
-const drafts = await getDrafts()
-const isDraft = (post: Post | Draft): post is Draft => isPathnameInCollection(`/${post.slug}/`, drafts)
-const isScheduled = (post: Post | Draft): boolean => post.data.date && Date.parse(post.data.date) > Date.now()
+const isScheduled = (post: Post): boolean => post.data.date && Date.parse(post.data.date) > Date.now()
---
@@ -20,7 +17,7 @@ const isScheduled = (post: Post | Draft): boolean => post.data.date && Date.pars
{
- [...drafts, ...posts].map(post => {
+ posts.map(post => {
// TODO: my goal is to group the posts under a heading for each topic (tag) or perhaps the even-higher level category
// (e.g. Languages, Frameworks, etc) so I can do the same on the notes page and start publishing small notes quickly
// without worrying about nesting them hierarchically or inserting them into a topic note. All hidden "drafts" could
@@ -44,7 +41,7 @@ const isScheduled = (post: Post | Draft): boolean => post.data.date && Date.pars
{post.data.title || post.slug}
- {isScheduled(post) ? 📆 : isDraft(post) ? ✏️ : null}
+ {isScheduled(post) ? 📆 : null}
)
From caf69e4c898ca6d2f220d7951d3dc4ff710d80b2 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:27:44 -0500
Subject: [PATCH 19/23] notes: show notes, drafts and bookmarks by last
modified date
---
src/pages/notes.astro | 76 +++++++++----------------------------------
1 file changed, 16 insertions(+), 60 deletions(-)
diff --git a/src/pages/notes.astro b/src/pages/notes.astro
index 0885818f..fef9b113 100644
--- a/src/pages/notes.astro
+++ b/src/pages/notes.astro
@@ -3,14 +3,11 @@ import Tag from '../components/Tag.astro'
import Main from '../layouts/Main.astro'
import { getBookmarks } from '../utils/bookmarks'
import { isPathnameInCollection, type Bookmark, type Writing, type Draft } from '../utils/collections'
-import { getAllTagsInNotes, getNotes, getNestedNotes } from '../utils/notes'
+import { getAllTagsInNotes, getNotes } from '../utils/notes'
import { getDrafts } from '../utils/posts'
-import { cleanTags } from '../utils/tags'
const bookmarks = await getBookmarks()
-const notesFlat = await getNotes()
-const notesByParent = await getNestedNotes()
-// const notesByTag = await getNotesByTag()
+const notes = await getNotes()
const tags = await getAllTagsInNotes()
const drafts = await getDrafts()
@@ -27,7 +24,7 @@ const notesByLastModified = [...bookmarks, ...notes, ...drafts].sort((a, b) => {
const getEmoji = (item: Bookmark | Draft | Writing): string => {
if (item.data.favicon) {
- return ``
+ return ``
}
if (item.data.url) {
@@ -55,52 +52,8 @@ const getEmoji = (item: Bookmark | Draft | Writing): string => {
Notes
- Topics
+ Bookmarks, post drafts and topic notes
-
-
-
-
{
@@ -112,16 +65,19 @@ const getEmoji = (item: Bookmark | Draft | Writing): string => {
}
-
-
-
+
+
{
- [...bookmarks, ...drafts, ...notesFlat].map(item => (
- -
-
-
- {item.data.title || item.slug}
-
+ notesByLastModified.map((item, index) => (
+
-
+
+
+
+ {item.data.title || item.slug} {item.data.author ? ` (${item.data.author})` : ''}
+
+
+
+ {index < notesByLastModified.length - 1 ? • : null}
{/* TODO: show item tags? */}
{/*
{cleanTags(note.data.tags).map(tag => (
From 8f0a42c96476d9a8cb03e928017c9e5245fe8868 Mon Sep 17 00:00:00 2001
From: Michael Uloth
Date: Sun, 22 Dec 2024 22:49:45 -0500
Subject: [PATCH 20/23] remark: generate youtube embed markup from obsidian
youtube image links
---
lib/remark/plugins.ts | 2 ++
lib/remark/youtube-embed-from-image-link.ts | 40 +++++++++++++++++++++
2 files changed, 42 insertions(+)
create mode 100644 lib/remark/youtube-embed-from-image-link.ts
diff --git a/lib/remark/plugins.ts b/lib/remark/plugins.ts
index af015586..47a62971 100644
--- a/lib/remark/plugins.ts
+++ b/lib/remark/plugins.ts
@@ -3,6 +3,7 @@ import remarkWikiLink from '@portaljs/remark-wiki-link'
import remarkLastModified from './last-modified'
import remarkRemoveTags from './remove-tags'
+import remarkYouTubeEmbedFromImageLink from './youtube-embed-from-image-link'
export default [
remarkLastModified,
@@ -17,4 +18,5 @@ export default [
wikiLinkResolver: (slug: string): string[] => [`${slug}/`], // expects all pages to have root-level paths
},
],
+ remarkYouTubeEmbedFromImageLink,
]
diff --git a/lib/remark/youtube-embed-from-image-link.ts b/lib/remark/youtube-embed-from-image-link.ts
new file mode 100644
index 00000000..27f23f41
--- /dev/null
+++ b/lib/remark/youtube-embed-from-image-link.ts
@@ -0,0 +1,40 @@
+// Obsidian uses ![](