├── supabase ├── migrations │ ├── 20250607083909_remote_schema.sql │ ├── 20250607151037_cleanup_duplicate_policies.sql │ ├── 20250607212538_ensure_chat_realtime.sql │ ├── 20250607205729_fix_chat_rls_policies.sql │ └── 20250607180001_fix_admin_function_security.sql └── .gitignore ├── public ├── favicon.ico ├── images │ ├── lady transparent.png │ ├── sun-transparent-2.png │ ├── hands-transparent-4.png │ ├── arrow-left-2.svg │ ├── arrow-right-1.svg │ ├── arrow-right-3.svg │ └── placeholder-tile.svg └── robots.txt ├── postcss.config.cjs ├── src ├── styles │ └── google-fonts.ts ├── app │ ├── contests │ │ └── layout.tsx │ ├── admin │ │ ├── page.tsx │ │ ├── lookup │ │ │ ├── page.tsx │ │ │ └── layout.tsx │ │ └── layout.tsx │ ├── u │ │ └── [user] │ │ │ ├── template.tsx │ │ │ ├── loading.tsx │ │ │ ├── about │ │ │ └── page.tsx │ │ │ └── bookmarks │ │ │ └── page.tsx │ ├── settings │ │ ├── profile │ │ │ └── page.tsx │ │ ├── app │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── b │ │ │ └── [blog] │ │ │ └── page.tsx │ ├── send │ │ └── page.tsx │ ├── wrap │ │ └── page.tsx │ ├── home │ │ └── loading.tsx │ ├── error.tsx │ ├── chat │ │ └── page.tsx │ ├── bookmarks │ │ └── page.tsx │ ├── sitemap.ts │ ├── notifications │ │ └── page.tsx │ ├── b │ │ └── [blog] │ │ │ ├── loading.tsx │ │ │ └── [slug] │ │ │ └── page.tsx │ ├── api │ │ ├── newsletter │ │ │ ├── utils.ts │ │ │ └── [blog] │ │ │ │ └── subscribers │ │ │ │ └── all │ │ │ │ └── route.ts │ │ ├── contests │ │ │ └── route.ts │ │ └── email │ │ │ └── route.ts │ ├── page.tsx │ ├── search │ │ └── loading.tsx │ └── w │ │ └── [id] │ │ └── page.tsx ├── lib │ ├── lens │ │ ├── storage-client.ts │ │ ├── storage.ts │ │ └── client.ts │ ├── settings │ │ ├── user-settings.ts │ │ ├── get-user-email.ts │ │ ├── get-user-settings.ts │ │ ├── events.ts │ │ └── get-blogs-by-owner.ts │ ├── db │ │ ├── client.ts │ │ ├── middleware.ts │ │ ├── service.ts │ │ └── server.ts │ ├── seo │ │ ├── constants.ts │ │ └── canonical.ts │ ├── utils │ │ ├── is-evm-address.ts │ │ ├── find-blog-by-id.ts │ │ ├── image-optimization.ts │ │ ├── get-post-url.ts │ │ ├── resolve-url.ts │ │ └── ban-filter.ts │ ├── global-window.ts │ ├── auth │ │ ├── clear-cookies.ts │ │ ├── get-token-claims.ts │ │ ├── verify-token.ts │ │ ├── is-guest-user.ts │ │ ├── get-app-token.ts │ │ ├── get-session.ts │ │ ├── validate-token.ts │ │ ├── app-token.ts │ │ ├── verify-auth-request.ts │ │ ├── is-admin.ts │ │ ├── sign-app-token.ts │ │ ├── sign-guest-token.ts │ │ ├── admin-middleware.ts │ │ └── get-user-profile.ts │ ├── get-base-url.ts │ ├── publish │ │ ├── get-feed-address.ts │ │ ├── delete-cloud-draft.ts │ │ ├── create-post-record.ts │ │ └── get-post-content.ts │ ├── extract-subtitle.ts │ ├── load-embed-js.tsx │ ├── scripts │ │ └── get-articles-gql.js │ ├── plate │ │ └── default-content.ts │ ├── slug │ │ └── get-post-by-slug.ts │ └── get-arweave-content.ts ├── components │ ├── ui │ │ ├── aspect-ratio.tsx │ │ ├── search-highlight-leaf.tsx │ │ ├── code-line-element.tsx │ │ ├── skeleton.tsx │ │ ├── static │ │ │ ├── code-line-element-static.tsx │ │ │ ├── table-row-element-static.tsx │ │ │ ├── column-group-element-static.tsx │ │ │ ├── code-syntax-leaf-static.tsx │ │ │ ├── blockquote-element-static.tsx │ │ │ ├── code-leaf-static.tsx │ │ │ ├── comment-leaf-static.tsx │ │ │ ├── link-element-static.tsx │ │ │ ├── paragraph-element-static.tsx │ │ │ ├── column-element-static.tsx │ │ │ ├── hr-element-static.tsx │ │ │ ├── indent-todo-marker-static.tsx │ │ │ ├── media-audio-element-static.tsx │ │ │ ├── code-block-element-static.tsx │ │ │ ├── toggle-element-static.tsx │ │ │ ├── heading-element-static.tsx │ │ │ ├── table-element-static.tsx │ │ │ ├── checkbox-static.tsx │ │ │ ├── callout-element-static.tsx │ │ │ ├── media-video-element-static.tsx │ │ │ ├── image-element-static.tsx │ │ │ ├── media-file-element-static.tsx │ │ │ └── equation-element-static.tsx │ │ ├── fixed-toolbar.tsx │ │ ├── list-item.tsx │ │ ├── collapsible.tsx │ │ ├── highlight-leaf.tsx │ │ ├── blockquote-element.tsx │ │ ├── code-syntax-leaf.tsx │ │ ├── column-group-element.tsx │ │ ├── avatar.tsx │ │ ├── code-leaf.tsx │ │ ├── indent-toolbar-button.tsx │ │ ├── table-row-element.tsx │ │ ├── outdent-toolbar-button.tsx │ │ ├── ai-leaf.tsx │ │ ├── kbd-leaf.tsx │ │ ├── paragraph-element.tsx │ │ ├── link-toolbar-button.tsx │ │ ├── ai-toolbar-button.tsx │ │ ├── toggle-toolbar-button.tsx │ │ ├── indent-todo-toolbar-button.tsx │ │ ├── separator.tsx │ │ ├── media-toolbar-button.tsx │ │ ├── inline-equation-toolbar-button.tsx │ │ ├── emoji-dropdown-menu.tsx │ │ ├── emoji-toolbar-dropdown.tsx │ │ ├── label.tsx │ │ ├── toaster.tsx │ │ ├── link-element.tsx │ │ ├── list-element.tsx │ │ ├── mark-toolbar-button.tsx │ │ ├── column-element.tsx │ │ ├── progress.tsx │ │ ├── ghost-text.tsx │ │ ├── animated-chevron.tsx │ │ ├── color-input.tsx │ │ ├── list-toolbar-button.tsx │ │ ├── indent-list-toolbar-button.tsx │ │ ├── indent-todo-marker.tsx │ │ ├── hr-element.tsx │ │ ├── sonner.tsx │ │ ├── emoji-picker-search-bar.tsx │ │ ├── metadata-display.tsx │ │ ├── todo-list-element.tsx │ │ ├── block-selection.tsx │ │ ├── toggle-element.tsx │ │ ├── slider.tsx │ │ ├── input.tsx │ │ ├── title-element.tsx │ │ ├── subtitle-element.tsx │ │ ├── emoji-picker-search-and-clear.tsx │ │ ├── badge.tsx │ │ ├── caption.tsx │ │ └── hover-card.tsx │ ├── user │ │ ├── user-socials.tsx │ │ ├── user-handle.tsx │ │ ├── user-bio.tsx │ │ ├── user-location.tsx │ │ ├── user-lazy-username.tsx │ │ ├── user-name.tsx │ │ ├── user-following.tsx │ │ ├── user-cover.tsx │ │ ├── user-site.tsx │ │ └── user-navigation.tsx │ ├── misc │ │ ├── loading-spinner.tsx │ │ ├── global-modals.tsx │ │ ├── error-page.tsx │ │ └── truncated-text.tsx │ ├── seo │ │ └── structured-data.tsx │ ├── navigation │ │ ├── article-layout.tsx │ │ ├── search-layout.tsx │ │ ├── gradient-blur.tsx │ │ ├── feed-navigation.tsx │ │ └── page-transition.tsx │ ├── icons │ │ └── bell.tsx │ ├── post │ │ ├── post-skeleton-boundary.tsx │ │ └── post-deleted-view.tsx │ ├── admin │ │ └── admin-auth-check.tsx │ ├── theme │ │ └── theme-toggle.tsx │ ├── auth │ │ └── auth-wallet-button.tsx │ ├── editor │ │ ├── transforms │ │ │ └── insert-iframe.ts │ │ ├── addons │ │ │ └── editor-read-time.tsx │ │ └── plugins │ │ │ ├── title-plugin.ts │ │ │ ├── iframe-plugin.ts │ │ │ └── blockquote-normalize-plugin.ts │ ├── draft │ │ └── draft.ts │ ├── chat │ │ └── chat-user-info.tsx │ ├── settings │ │ └── settings-newsletter.tsx │ └── feed │ │ └── feed-view-toggle.tsx ├── hooks │ ├── use-reconnect-wallet.ts │ ├── use-sync-value-effect.ts │ ├── use-mounted.ts │ ├── use-unmount.ts │ ├── use-sidebar.tsx │ ├── use-image-preload.ts │ ├── use-initial-state.ts │ ├── use-object-version.ts │ ├── use-isomorphic-layout-effect.ts │ ├── use-origin.tsx │ ├── use-scrolled.ts │ ├── use-debounce.ts │ ├── use-safe-memo.ts │ ├── use-is-touch.ts │ ├── use-is-touch-device.ts │ ├── use-enter-submit.ts │ ├── use-lock-body.ts │ ├── use-mobile.tsx │ ├── use-filter-skills.ts │ ├── use-debounce-pending-click.ts │ ├── use-viewport.ts │ ├── use-get-window-height.ts │ ├── use-storage.ts │ ├── use-effect-after-first.ts │ ├── use-feed-view-mode.ts │ ├── use-lens-clients.ts │ ├── use-lock-scroll.ts │ ├── use-copy-to-clipboard.ts │ ├── use-on-screen.ts │ ├── use-pipe-ref.ts │ ├── use-publish-draft.ts │ ├── use-admin-status.ts │ ├── use-at-bottom.ts │ ├── use-document-storage.ts │ ├── use-click-outside.ts │ ├── use-intersection-observer.ts │ ├── use-get-data.ts │ ├── use-local-storage.ts │ ├── use-media-query.ts │ └── use-cookie-storage.ts ├── contexts │ ├── action-bar-context.tsx │ └── feed-context.tsx └── stores │ └── ui-store.ts ├── .github └── ISSUE_TEMPLATE │ ├── feature.md │ └── bug.md ├── components.json ├── .gitignore ├── tsconfig.json ├── emails └── newsletter-email-test.tsx └── next.config.js /supabase/migrations/20250607083909_remote_schema.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fountain-ink/app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/images/lady transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fountain-ink/app/HEAD/public/images/lady transparent.png -------------------------------------------------------------------------------- /public/images/sun-transparent-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fountain-ink/app/HEAD/public/images/sun-transparent-2.png -------------------------------------------------------------------------------- /supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | 5 | # dotenvx 6 | .env.keys 7 | .env.local 8 | .env.*.local 9 | -------------------------------------------------------------------------------- /public/images/hands-transparent-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fountain-ink/app/HEAD/public/images/hands-transparent-4.png -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | }, 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /src/styles/google-fonts.ts: -------------------------------------------------------------------------------- 1 | import { Inter } from "next/font/google"; 2 | 3 | export const inter = Inter({ subsets: ["latin"] }); 4 | -------------------------------------------------------------------------------- /src/app/contests/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function ContestsLayout({ children }: { children: React.ReactNode }) { 2 | return children; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | export default function AdminPage() { 4 | redirect("/admin/feedback"); 5 | } 6 | -------------------------------------------------------------------------------- /src/lib/lens/storage-client.ts: -------------------------------------------------------------------------------- 1 | import { StorageClient } from "@lens-chain/storage-client"; 2 | 3 | export const storageClient = StorageClient.create(); 4 | -------------------------------------------------------------------------------- /src/lib/settings/user-settings.ts: -------------------------------------------------------------------------------- 1 | export interface UserSettings { 2 | app?: { 3 | isSmoothScrolling?: boolean; 4 | isBlurEnabled?: boolean; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root; 6 | 7 | export { AspectRatio }; 8 | -------------------------------------------------------------------------------- /src/components/ui/search-highlight-leaf.tsx: -------------------------------------------------------------------------------- 1 | import { withCn } from "@udecode/cn"; 2 | import { PlateLeaf } from "@udecode/plate/react"; 3 | 4 | export const SearchHighlightLeaf = withCn(PlateLeaf, "bg-yellow-100"); 5 | -------------------------------------------------------------------------------- /src/components/user/user-socials.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Account } from "@lens-protocol/client"; 4 | 5 | export const UserSocials = ({ profile }: { profile?: Account }) => { 6 | return
; 7 | }; 8 | -------------------------------------------------------------------------------- /src/hooks/use-reconnect-wallet.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useModal } from "connectkit"; 3 | 4 | export const useReconnectWallet = () => { 5 | const { setOpen } = useModal(); 6 | return () => setOpen(true); 7 | }; 8 | -------------------------------------------------------------------------------- /src/hooks/use-sync-value-effect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export const useSyncValueEffect = (value: boolean, setter: (value: boolean) => void) => { 4 | useEffect(() => { 5 | setter(value); 6 | }, [value, setter]); 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/db/client.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserClient } from "@supabase/ssr"; 2 | import { env } from "@/env"; 3 | 4 | export function createClient() { 5 | return createBrowserClient(env.NEXT_PUBLIC_SUPABASE_URL, env.NEXT_PUBLIC_SUPABASE_ANON_KEY); 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/seo/constants.ts: -------------------------------------------------------------------------------- 1 | export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://fountain.ink"; 2 | export const SITE_NAME = "Fountain"; 3 | export const SITE_DESCRIPTION = "An open blogging platform"; 4 | export const DEFAULT_AUTHOR = "Fountain"; 5 | -------------------------------------------------------------------------------- /src/app/u/[user]/template.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AnimatePresence } from "motion/react"; 4 | 5 | export default function Template({ children }: { children: React.ReactNode }) { 6 | return{error}
11 | 12 | 13 |13 | Sorry, the content you're looking for is no longer available. 14 |
15 |
14 | {children}
15 |
16 | {truncateUri(address)}
29 |
32 | No blogs found. Create a blog first to set up newsletters.
22 | ) : ( 23 | blogs.map((blog: BlogDataWithSubscriberCount) =>