diff --git a/LICENSE b/LICENSE index 0eaea3f6..28a0ea08 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-present Proselog. +Copyright (c) 2022-present xLog. Portions of this software are licensed as follows: diff --git a/package.json b/package.json index 67477086..6b979428 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@trpc/next": "^9.27.2", "@trpc/react": "^9.27.2", "@trpc/server": "^9.27.2", + "@urql/core": "^3.0.3", "@web-std/form-data": "^3.0.2", "async-busboy": "^1.1.0", "axios": "^0.27.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b541aa82..d17ee467 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,7 @@ specifiers: '@types/uuid': ^8.3.4 '@unocss/cli': ^0.45.25 '@unocss/preset-icons': ^0.45.25 + '@urql/core': ^3.0.3 '@web-std/form-data': ^3.0.2 async-busboy: ^1.1.0 autoprefixer: ^10.4.12 @@ -153,6 +154,7 @@ dependencies: '@trpc/next': 9.27.2_dyodlyrl4jlccmeg4rab7q5slm '@trpc/react': 9.27.2_r3nohbkvzkycnbsdj5gzhfx2rm '@trpc/server': 9.27.2 + '@urql/core': 3.0.3 '@web-std/form-data': 3.0.2 async-busboy: 1.1.0 axios: 0.27.2 @@ -1318,6 +1320,12 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /@graphql-typed-document-node/core/3.1.1: + resolution: {integrity: sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + dev: false + /@graphql-typed-document-node/core/3.1.1_graphql@16.5.0: resolution: {integrity: sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==} peerDependencies: @@ -2774,6 +2782,15 @@ packages: wonka: 4.0.15 dev: false + /@urql/core/3.0.3: + resolution: {integrity: sha512-raQP51ERNtg5BvlN8x8mHVRvk4K0ugWQ69n53BdkjKpXVV5kuWp7trnwriGv1fQKa8HuiGNSCfyslUucc0OVQg==} + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + '@graphql-typed-document-node/core': 3.1.1 + wonka: 6.1.0 + dev: false + /@vanilla-extract/css/1.7.0: resolution: {integrity: sha512-2Abpr5aJskwKS+waMWxWnTBqq/rj4J93KtGteW0VSORMUsijrupwjOXECd7Llh+nSFfx/Y6usuexF5/ffyDF8A==} dependencies: @@ -11096,6 +11113,10 @@ packages: resolution: {integrity: sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==} dev: false + /wonka/6.1.0: + resolution: {integrity: sha512-VgiMCz7BXOiDbgpVhf5iNhK7hurteY5Jv0fDJewUkY0s4fbxQD2iKqfGxNXNTwp2v3bgT8QVu2l5H7YdkZ5WIA==} + dev: false + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} diff --git a/showcase.json b/showcase.json new file mode 100644 index 00000000..2961a201 --- /dev/null +++ b/showcase.json @@ -0,0 +1,48 @@ +[ + "xlog", + "diygod", + "rss3", + "crossbell-blog", + "hey", + "song", + "atlas-thinking", + "joshua", + "jeff", + "walter", + "crossbell-io-updates", + "xtao", + "polebug", + "pengchao", + "boyuand", + "lilo", + "coolcat", + "dao", + "0x55f110395c844963b075674e2956eb414018a7a7", + "139", + "just1lovewrl", + "kanikig", + "coosir", + "atryte", + "qihangsun", + "brucexx", + "0x1", + "duogeyu", + "stone", + "lastmelody", + "12138", + "isheep", + "atryte", + "henryqw", + "whost", + "twifag", + "hominsu", + "exodus-exodus", + "zoo", + "colojayce", + "stars", + "somehacker", + "nss", + "somehacker", + "novonine", + "nafidurmus" +] diff --git a/src/models/site.model.ts b/src/models/site.model.ts index 8a57c129..6d8bd595 100644 --- a/src/models/site.model.ts +++ b/src/models/site.model.ts @@ -4,6 +4,8 @@ import unidata from "~/queries/unidata.server" import { renderPageContent } from "~/markdown" import { toGateway } from "~/lib/ipfs-parser" import type Unidata from "unidata.js" +import { createClient } from "@urql/core" +import axios from "axios" export const checkSubdomain = async ({ subdomain, @@ -94,6 +96,72 @@ export const getSite = async (input: string, customUnidata?: Unidata) => { return site } +export const getSites = async (input: string[]) => { + const client = createClient({ + url: "https://indexer.crossbell.io/v1/graphql", + }) + const result = await client + .query( + ` + query getCharacters($identities: [String!], $limit: Int) { + characters( where: { handle: { in: $identities } }, orderBy: [{ updatedAt: desc }], take: $limit ) { + handle + updatedAt + notes { + updatedAt + } + metadata { + uri + content + } + } + }`, + { + identities: input, + }, + ) + .toPromise() + + await Promise.all( + result.data?.characters?.map(async (site: any) => { + if (!site?.metadata?.content && site?.metadata?.uri) { + site.metadata.content = ( + await axios.get( + toGateway(site?.metadata?.uri, { + needRequestAtServerSide: true, + }), + { + ...(typeof window === "undefined" && { + headers: { + "User-Agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", + }, + }), + }, + ) + ).data + } + site.metadata.content.name = site.metadata.content.name || site.handle + + site.custom_domain = + site.metadata?.content?.attributes?.find( + (a: any) => a.trait_type === "xlog_custom_domain", + )?.value || "" + + site.updatedAt = [...site.notes, site] + .map((i) => i.updatedAt) + .sort() + .pop() + }), + ) + + result.data?.characters?.sort((a: any, b: any) => { + return b.updatedAt > a.updatedAt ? 1 : -1 + }) + + return result.data?.characters +} + export const getSubscription = async ( data: { userId: string diff --git a/src/pages/index.tsx b/src/pages/index.tsx index cc0f0e4a..ff7f8280 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -16,10 +16,18 @@ import { GITHUB_LINK, APP_NAME, CSB_SCAN } from "~/lib/env" import { getSiteLink } from "~/lib/helpers" import { Link, Element } from "react-scroll" import { Image } from "~/components/ui/Image" +import { dehydrate, QueryClient } from "@tanstack/react-query" +import { prefetchGetSites } from "~/queries/site.server" +import { useGetSites } from "~/queries/site" +import showcase from "../../showcase.json" export const getServerSideProps: GetServerSideProps = async (ctx) => { + const queryClient = new QueryClient() + await prefetchGetSites(showcase, queryClient) + return { props: { + dehydratedState: dehydrate(queryClient), region: FLY_REGION, }, } @@ -30,6 +38,7 @@ export default function Home({ region }: { region: string | null }) { const { address } = useAccount() const { openConnectModal } = useConnectModal() const router = useRouter() + const showcaseSites = useGetSites(showcase) useEffect(() => { console.log("-> region", region) @@ -99,60 +108,8 @@ export default function Home({ region }: { region: string | null }) { }, ] - const comparing = [ - "Only Controlled by Yourself", - "Open Source", - "Self-hosting", - "Like Posts", - "Comment Posts", - "Custom Domain", - "Custom CSS", - "Navigation", - "Pages", - "Scheduled Publishing", - ] - - const stars = [ - { - username: "rss3", - avartar: "bafkreigjinhazhgvqaj423fhuuev7ormzf5vsm4iu4vrx2zgk2l5nugm4m", - }, - { - username: "crossbell-blog", - avartar: "bafkreic5k3zvarbsondfrowy7kpbj6xo7cj25hobksgileqbbupjvvmkoq", - }, - { - username: "diygod", - avartar: "bafybeibefx2tyow77m2wcnsh5anaaxfy7ypxbcuapb52c4h255onqp72ye", - }, - { - username: "hey", - avartar: "bafkreigfhmqizqi7jwg5sw522kwcqilq7m6g4mbicpyit4avezaztvovey", - }, - { - username: "song", - avartar: "QmQArk2yEF9t2Ud7vbhMyAQu8DJnTzc3EduSm7rcF2LQv4", - }, - { - username: "atlas-thinking", - avartar: "QmNks3cJWDXzG5WSBzTVv1regkhXhzChK6LhJYu1HiBLS2", - }, - { - username: "joshua", - avartar: "QmTUYz29S5bMqozK2uyQ1Y91DZzka4PLjReAD7J2nkbLMN", - }, - { - username: "jeff", - avartar: "QmdvVh2w375eVMfDmi9yb2hks1X3MDDEAbbJB7FH2FVKxd", - }, - { - username: "walter", - avartar: "QmRXkowLwfqjMKZS41KrnjM6JjAG5eerGWyFbo6ZzB1uUD", - }, - ] - return ( - +
@@ -223,45 +180,57 @@ export default function Home({ region }: { region: string | null }) { ))} - -
Why {APP_NAME}
-
- We believe freedom of expression is a universal right. In your - long-form writing, you should be able to express your thoughts - freely; without fear of your post being removed or censored. - xLog ensures that whatever you write, it will be immutable and - permanently stored. -
+ +
Showcase
- Trusted by these awesome teams and geeks -
    - {stars.map((item) => ( + Discover these awesome teams and geeks (sorted by update time) +
      + {showcaseSites.data?.map((site: any) => (
    • - {item.username} + + {site.handle} + + + + {site.metadata.content?.name} + + {site.metadata.content?.bio && ( + + {site.metadata.content?.bio} + + )} +
    • ))} -
    • +
    • - + Submit yours
    diff --git a/src/queries/site.server.ts b/src/queries/site.server.ts index 0bb55994..16e9b5d7 100644 --- a/src/queries/site.server.ts +++ b/src/queries/site.server.ts @@ -28,3 +28,13 @@ export const prefetchGetSiteSubscriptions = async ( return cacheGet(key, () => siteModel.getSiteSubscriptions(input)) }) } + +export const prefetchGetSites = async ( + input: string[], + queryClient: QueryClient, +) => { + const key = ["getSites", input] + return await queryClient.fetchQuery(key, async () => { + return cacheGet(key, () => siteModel.getSites(input)) + }) +} diff --git a/src/queries/site.ts b/src/queries/site.ts index 494330c4..db7c4e00 100644 --- a/src/queries/site.ts +++ b/src/queries/site.ts @@ -22,6 +22,15 @@ export const useGetSite = (input: string) => { }) } +export const useGetSites = (input: string[]) => { + return useQuery(["getSites", input], async () => { + if (!input) { + return null + } + return siteModel.getSites(input) + }) +} + export const useGetSubscription = (data: { userId: string siteId: string