diff --git a/.storybook/main.ts b/.storybook/main.ts index 7dcb010d26..5b1419fca1 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -8,6 +8,16 @@ const config = { backgrounds: false, }, async viteFinal(config) { + config.plugins ??= [] + + config.plugins.push({ + name: 'ignore-internals', + transform(_, id) { + if (id.includes('/app/pages/blog/') && id.endsWith('.md')) { + return 'export default {}' + } + }, + }) // Replace the built-in vue-docgen plugin with a fault-tolerant version. // vue-docgen-api can crash on components that import types from other // .vue files (it tries to parse the SFC with @babel/parser as plain TS). diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 43ead35e43..ddccbc9019 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -25,6 +25,9 @@ const closeModal = () => modalRef.value?.close?.() {{ $t('footer.about') }} + + {{ $t('footer.blog') }} + {{ $t('privacy_policy.title') }} diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index 7a0d8980df..1600bb4c90 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -59,6 +59,14 @@ const mobileLinks = computed(() => [ external: false, iconClass: 'i-lucide:info', }, + { + name: 'Blog', + label: $t('footer.blog'), + to: { name: 'blog' }, + type: 'link', + external: false, + iconClass: 'i-lucide:notebook-pen', + }, { name: 'Privacy Policy', label: $t('privacy_policy.title'), diff --git a/app/components/AuthorAvatar.vue b/app/components/AuthorAvatar.vue new file mode 100644 index 0000000000..0e68ae41a3 --- /dev/null +++ b/app/components/AuthorAvatar.vue @@ -0,0 +1,45 @@ + + + diff --git a/app/components/AuthorList.vue b/app/components/AuthorList.vue new file mode 100644 index 0000000000..ee5de3464a --- /dev/null +++ b/app/components/AuthorList.vue @@ -0,0 +1,49 @@ + + + diff --git a/app/components/BlogPostListCard.vue b/app/components/BlogPostListCard.vue new file mode 100644 index 0000000000..d5e4f1b13c --- /dev/null +++ b/app/components/BlogPostListCard.vue @@ -0,0 +1,53 @@ + + + diff --git a/app/components/BlueskyComment.vue b/app/components/BlueskyComment.vue new file mode 100644 index 0000000000..12fe336a1f --- /dev/null +++ b/app/components/BlueskyComment.vue @@ -0,0 +1,203 @@ + + + diff --git a/app/components/BlueskyComments.vue b/app/components/BlueskyComments.vue new file mode 100644 index 0000000000..54655a46c1 --- /dev/null +++ b/app/components/BlueskyComments.vue @@ -0,0 +1,116 @@ + + + diff --git a/app/components/OgImage/BlogPost.d.vue.ts b/app/components/OgImage/BlogPost.d.vue.ts new file mode 100644 index 0000000000..75e2a9ad17 --- /dev/null +++ b/app/components/OgImage/BlogPost.d.vue.ts @@ -0,0 +1,13 @@ +// This type declaration file is required to break a circular type resolution in vue-tsc. +// And is based off Package.d.vue.ts + +import type { DefineComponent } from 'vue' + +declare const _default: DefineComponent<{ + title: string + authors?: { name: string; blueskyHandle?: string }[] + date?: string + primaryColor?: string +}> + +export default _default diff --git a/app/components/OgImage/BlogPost.vue b/app/components/OgImage/BlogPost.vue new file mode 100644 index 0000000000..2192be4f7e --- /dev/null +++ b/app/components/OgImage/BlogPost.vue @@ -0,0 +1,142 @@ + + + diff --git a/app/components/Readme.vue b/app/components/Readme.vue index b44225797c..c571e63bc2 100644 --- a/app/components/Readme.vue +++ b/app/components/Readme.vue @@ -61,7 +61,7 @@ function handleClick(event: MouseEvent) {