diff --git a/apps/www/components/Blog/ShareArticleActions.tsx b/apps/www/components/Blog/ShareArticleActions.tsx new file mode 100644 index 00000000000..351bf951dc6 --- /dev/null +++ b/apps/www/components/Blog/ShareArticleActions.tsx @@ -0,0 +1,64 @@ +import Link from 'next/link' + +const ShareArticleActions = ({ title, slug }: { title: string; slug: string }) => ( +
+ + + + + + + + + + + + + + + + + + + + + + + +
+) + +export default ShareArticleActions diff --git a/apps/www/components/LaunchWeek/HackernewsShare.tsx b/apps/www/components/LaunchWeek/HackernewsShare.tsx new file mode 100644 index 00000000000..89ed624f49f --- /dev/null +++ b/apps/www/components/LaunchWeek/HackernewsShare.tsx @@ -0,0 +1,32 @@ +import Image from 'next/image' +import React from 'react' +import days from '~/components/LaunchWeek/lw7_days' + +const HackernewsShare = ({ url, title }: { url: string; title: string }) => { + const hackerNewsLink = `https://news.ycombinator.com/submitlink?u=${encodeURIComponent( + url + )}&t=${encodeURIComponent(title)}` + + return ( +
+

Start a conversation about this topic

+ + + + + + +
+ ) +} + +export default HackernewsShare diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index 6044c7dd5da..ffef62e8470 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -11,13 +11,14 @@ import SolutionsData from 'data/Solutions.json' import Solutions from '~/components/Nav/Product' import Developers from '~/components/Nav/Developers' import Announcement from '~/components/Nav/Announcement' +import CountdownBanner from '~/components/LaunchWeek/Banners/CountdownBanner' +import ScrollProgress from '~/components/ScrollProgress' import { useIsLoggedIn, useTheme } from 'common' import TextLink from '../TextLink' import Image from 'next/image' import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png' -import CountdownBanner from '../LaunchWeek/Banners/CountdownBanner' const Nav = () => { const { isDarkMode } = useTheme() @@ -436,6 +437,7 @@ const Nav = () => { + ) diff --git a/apps/www/components/ScrollProgress.tsx b/apps/www/components/ScrollProgress.tsx new file mode 100644 index 00000000000..ddbea562561 --- /dev/null +++ b/apps/www/components/ScrollProgress.tsx @@ -0,0 +1,44 @@ +import { useRouter } from 'next/router' +import React, { useEffect, useState } from 'react' + +const ScrollProgress = () => { + const [progressPercentage, setProgressPercentage] = useState(0) + const { pathname } = useRouter() + + const isBlogPost = pathname.includes('/blog/') + if (!isBlogPost) return null + + const handleScroll = () => { + if (typeof document === 'undefined') return null + const article = document?.querySelector('article') + const { top, height } = (article as any)?.getBoundingClientRect() + let scrollDistance = -top + let progressPercentage = + (scrollDistance / (height - document.documentElement.clientHeight)) * 100 + setProgressPercentage(progressPercentage) + } + + useEffect(() => { + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + let isActive = progressPercentage <= 100 + + return ( +
+
+
+ ) +} + +export default ScrollProgress diff --git a/apps/www/hooks/useActiveAnchors.tsx b/apps/www/hooks/useActiveAnchors.tsx new file mode 100644 index 00000000000..00a48824387 --- /dev/null +++ b/apps/www/hooks/useActiveAnchors.tsx @@ -0,0 +1,49 @@ +import { useEffect, useRef } from 'react' + +const useActiveAnchors = ( + anchorsQuerySelector: string = 'h2', + tocQuerySelector: string = '.prose-toc a', + offset: number = 200 +) => { + const anchors = useRef | null>(null) + const toc = useRef | null>(null) + + const handleScroll = () => { + const pageYOffset = window.pageYOffset + let newActiveAnchor: string = '' + + anchors.current?.forEach((anchor) => { + if (pageYOffset >= anchor.offsetTop - offset) { + newActiveAnchor = anchor.id + } + }) + + toc.current?.forEach((link) => { + link.classList.remove('toc-animate') + // TODO: escape emojis + // The problem is that MDXRemote strips out emojis on slugs + // but ReactMarkdown doesn't,instead it encodes them + const sanitizedHref = (link.getAttribute('href') ?? '').replace('#', '') + const isMatch = sanitizedHref === newActiveAnchor + + if (isMatch) { + link.classList.add('toc-animate') + } + }) + } + + useEffect(() => { + anchors.current = document.querySelectorAll(anchorsQuerySelector) + toc.current = document.querySelectorAll(tocQuerySelector) + + window.addEventListener('scroll', handleScroll) + + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + return null +} + +export default useActiveAnchors diff --git a/apps/www/pages/blog/[slug].tsx b/apps/www/pages/blog/[slug].tsx index 779b9426272..a36670a7840 100644 --- a/apps/www/pages/blog/[slug].tsx +++ b/apps/www/pages/blog/[slug].tsx @@ -1,19 +1,22 @@ -import { Badge, Divider, IconChevronLeft, IconFile } from 'ui' -import matter from 'gray-matter' -import authors from 'lib/authors.json' -import { MDXRemote } from 'next-mdx-remote' import { NextSeo } from 'next-seo' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' -import ReactMarkdown from 'react-markdown' +import matter from 'gray-matter' +import authors from 'lib/authors.json' +import { MDXRemote } from 'next-mdx-remote' +import { Badge, Divider, IconChevronLeft } from 'ui' + import CTABanner from '~/components/CTABanner' import DefaultLayout from '~/components/Layouts/Default' import BlogLinks from '~/components/LaunchWeek/7/BlogLinks' import { generateReadingTime } from '~/lib/helpers' +import ShareArticleActions from '~/components/Blog/ShareArticleActions' +import useActiveAnchors from '~/hooks/useActiveAnchors' import mdxComponents from '~/lib/mdx/mdxComponents' import { mdxSerialize } from '~/lib/mdx/mdxSerialize' import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts' +import { ReactMarkdown } from 'react-markdown/lib/react-markdown' // table of contents extractor const toc = require('markdown-toc') @@ -65,6 +68,7 @@ export async function getStaticProps({ params }: any) { function BlogPostPage(props: any) { const content = props.blog.content const authorArray = props.blog.author.split(',') + // useActiveAnchors('h2, h3, h4') const isLaunchWeek7 = props.blog.launchweek === 7 const author = [] @@ -105,7 +109,7 @@ function BlogPostPage(props: any) { const toc = props.blog.toc && (
-
+ - {isLaunchWeek7 && } -
+
Share this article
-
- - - - - - - - - - - - - - - -
+
@@ -323,21 +292,21 @@ function BlogPostPage(props: any) {
{/* Sidebar */}
-
+
{toc}

Related articles

-
+
{props.relatedPosts.map((post: any, i: number) => (

-
+ {/*
-
+
*/} {post.title} @@ -349,11 +318,15 @@ function BlogPostPage(props: any) { ))} +
+
Share this article
+ +
diff --git a/apps/www/public/images/launchweek/seven/day0/ai-images-thumb.png b/apps/www/public/images/launchweek/seven/day0/ai-images-thumb.png new file mode 100644 index 00000000000..0af5ecd0ff0 Binary files /dev/null and b/apps/www/public/images/launchweek/seven/day0/ai-images-thumb.png differ diff --git a/apps/www/styles/index.css b/apps/www/styles/index.css index d2cdc4c0275..15de303b62c 100644 --- a/apps/www/styles/index.css +++ b/apps/www/styles/index.css @@ -70,11 +70,29 @@ h6 { */ @apply text-xl; } +h1:not(.overwrite), +h2:not(.overwrite), +h3:not(.overwrite), +h4:not(.overwrite), +h5:not(.overwrite), +h6:not(.overwrite) { + scroll-margin-top: 90px; +} + .h1:not(.prose *):not(.overwrite), .h2:not(.prose *):not(.overwrite) { line-height: 1.2 !important; } +.toc-animate { + transform: translate3d(3px, 0, 0) !important; + color: hsla(153, 60%, 53%, 1) !important; +} + +.prose-toc a { + transition: all 0.07s ease-in-out; +} + @media only screen and (max-width: 960px) { .h1:not(.prose *):not(.overwrite) { font-size: 2.2rem !important;