Merge pull request #499 from Crossbell-Box/feature/nextjs-app-router
Co-authored-by: enpitsulin <enpitsulin@gmail.com> Co-authored-by: Innei <tukon479@gmail.com> Co-authored-by: runjuu <hi@runjuu.com>
This commit is contained in:
commit
c64989da70
|
|
@ -1,7 +0,0 @@
|
|||
module.exports = {
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en", "zh", "zh-TW", "ja"],
|
||||
reloadOnPrerender: process.env.NODE_ENV === "development",
|
||||
},
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
// @ts-check
|
||||
const pkg = require("./package.json")
|
||||
const spawn = require("cross-spawn")
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
const execSync = require("child_process").execSync
|
||||
const { i18n } = require("./next-i18next.config")
|
||||
|
||||
const cache = require("next-pwa/cache")
|
||||
const withPWA = require("next-pwa")({
|
||||
|
|
@ -63,12 +61,30 @@ module.exports = withBundleAnalyzer(
|
|||
},
|
||||
experimental: {
|
||||
scrollRestoration: true,
|
||||
appDir: true,
|
||||
serverComponentsExternalPackages: ["rehype-react"],
|
||||
},
|
||||
output: "standalone",
|
||||
productionBrowserSourceMaps: true,
|
||||
|
||||
webpack(config) {
|
||||
config.resolve.fallback = { fs: false } // polyfill node-id3
|
||||
|
||||
// https://github.com/WalletConnect/walletconnect-monorepo/blob/7716e164281c2f531145d682c3658f761fa0a823/providers/universal-provider/src/utils/deepLinks.ts#L39
|
||||
// @walletconnect/universal-provider imports react-native conditionally.
|
||||
// Since this is a NextJS app, simply mark it as external to avoid the webpack bundling warning.
|
||||
config.externals.push("react-native")
|
||||
|
||||
// https://github.com/WalletConnect/walletconnect-utils/blob/b7d7dc003c25dd33ef74c2fac483140f71a51d86/jsonrpc/http-connection/src/http.ts#L2
|
||||
// `@walletconnect/jsonrpc-http-connection` imports `cross-fetch` to support fetch in Node.js. It's unnecessary for Next.JS app.
|
||||
config.resolve.alias["cross-fetch"] = require.resolve(
|
||||
"next/dist/build/polyfills/fetch/index.js",
|
||||
)
|
||||
|
||||
// https://github.com/kkomelin/isomorphic-dompurify/issues/54
|
||||
// Fix isomorphic-dompurify in app router
|
||||
config.externals = [...config.externals, "canvas", "jsdom"]
|
||||
|
||||
return config
|
||||
},
|
||||
|
||||
|
|
|
|||
69
package.json
69
package.json
|
|
@ -18,7 +18,8 @@
|
|||
"prisma:studio": "prisma studio",
|
||||
"docker:db": "docker-compose -f docker-compose.db.yml up -d",
|
||||
"spell-check": "cspell lint . --no-progress",
|
||||
"analyze": "NODE_ENV=production ANALYZE=true BUNDLE_ANALYZE=browser next build"
|
||||
"analyze": "NODE_ENV=production ANALYZE=true BUNDLE_ANALYZE=browser next build",
|
||||
"postinstall": "prisma generate"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
|
|
@ -27,30 +28,30 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "6.2.3",
|
||||
"@codemirror/commands": "6.2.4",
|
||||
"@codemirror/lang-markdown": "6.1.1",
|
||||
"@codemirror/language": "6.6.0",
|
||||
"@codemirror/language-data": "6.3.0",
|
||||
"@codemirror/language-data": "6.3.1",
|
||||
"@codemirror/state": "6.2.0",
|
||||
"@codemirror/theme-one-dark": "6.1.2",
|
||||
"@codemirror/view": "6.10.0",
|
||||
"@crossbell/connect-kit": "0.0.79",
|
||||
"@crossbell/contract": "0.0.79",
|
||||
"@crossbell/indexer": "0.0.79",
|
||||
"@crossbell/notification": "0.0.79",
|
||||
"@crossbell/ui": "0.0.79",
|
||||
"@crossbell/util-hooks": "0.0.79",
|
||||
"@codemirror/view": "6.11.0",
|
||||
"@crossbell/connect-kit": "0.0.82",
|
||||
"@crossbell/contract": "0.0.82",
|
||||
"@crossbell/indexer": "0.0.82",
|
||||
"@crossbell/notification": "0.0.82",
|
||||
"@crossbell/ui": "0.0.82",
|
||||
"@crossbell/util-hooks": "0.0.82",
|
||||
"@ddietr/codemirror-themes": "1.4.1",
|
||||
"@dqbd/tiktoken": "^1.0.7",
|
||||
"@emoji-mart/data": "1.1.2",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@floating-ui/react": "^0.23.1",
|
||||
"@floating-ui/react": "^0.24.0",
|
||||
"@headlessui/react": "1.7.14",
|
||||
"@heroicons/react": "2.0.17",
|
||||
"@iconify-json/mingcute": "^1.1.6",
|
||||
"@lezer/highlight": "^1.1.4",
|
||||
"@mantine/core": "^6.0.9",
|
||||
"@monaco-editor/react": "4.5.0",
|
||||
"@mantine/core": "^6.0.10",
|
||||
"@monaco-editor/react": "4.5.1",
|
||||
"@popperjs/core": "^2.11.7",
|
||||
"@prisma/client": "^4.13.0",
|
||||
"@tanstack/react-query": "4.29.5",
|
||||
|
|
@ -58,8 +59,9 @@
|
|||
"@tanstack/react-query-persist-client": "4.29.5",
|
||||
"@uiw/codemirror-extensions-events": "4.19.16",
|
||||
"@urql/core": "4.0.7",
|
||||
"ahooks": "^3.7.6",
|
||||
"ahooks": "^3.7.7",
|
||||
"aplayer-react": "^1.1.0",
|
||||
"canvas": "^2.11.2",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "1.2.1",
|
||||
|
|
@ -72,14 +74,15 @@
|
|||
"hast-util-to-html": "^8.0.4",
|
||||
"hast-util-to-text": "^3.1.2",
|
||||
"i18next": "^22.4.15",
|
||||
"idb-keyval": "6.2.0",
|
||||
"i18next-resources-to-backend": "^1.1.3",
|
||||
"idb-keyval": "6.2.1",
|
||||
"immer": "10.0.1",
|
||||
"ioredis": "5.3.2",
|
||||
"isomorphic-dompurify": "^1.3.0",
|
||||
"isomorphic-dompurify": "^1.4.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonfeed-to-rss": "^3.0.6",
|
||||
"jsonfeed-to-rss": "^3.0.7",
|
||||
"katex": "^0.16.7",
|
||||
"langchain": "^0.0.66",
|
||||
"langchain": "^0.0.71",
|
||||
"lottie-react": "^2.4.0",
|
||||
"mdast-util-to-hast": "^12.3.0",
|
||||
"mdast-util-to-string": "3.2.0",
|
||||
|
|
@ -87,24 +90,22 @@
|
|||
"medium-zoom": "1.0.8",
|
||||
"mermaid": "10.1.0",
|
||||
"nanoid": "4.0.2",
|
||||
"next": "13.3.1",
|
||||
"next-i18next": "^13.2.2",
|
||||
"nextjs-progressbar": "0.0.16",
|
||||
"next": "13.4.1",
|
||||
"node-id3": "^0.2.6",
|
||||
"pangu": "^4.0.7",
|
||||
"pinyin": "3.0.0-alpha.5",
|
||||
"pinyin-pro": "^3.14.0",
|
||||
"prismjs": "1.29.0",
|
||||
"qier-progress": "1.0.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "7.43.9",
|
||||
"react-hot-toast": "2.4.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-parallax-tilt": "^1.7.131",
|
||||
"react-parallax-tilt": "^1.7.137",
|
||||
"react-popper": "^2.3.0",
|
||||
"react-scroll": "1.8.9",
|
||||
"react-sortablejs": "6.1.4",
|
||||
"react-string-replace": "^1.1.0",
|
||||
"react-virtuoso": "4.3.2",
|
||||
"react-virtuoso": "4.3.5",
|
||||
"refractor": "4.8.1",
|
||||
"rehype-autolink-headings": "6.1.1",
|
||||
"rehype-infer-description-meta": "^1.1.0",
|
||||
|
|
@ -138,12 +139,12 @@
|
|||
"unist-util-visit": "4.1.2",
|
||||
"use-onclickoutside": "0.4.1",
|
||||
"uuid": "9.0.0",
|
||||
"wagmi": "0.12.12",
|
||||
"zustand": "^4.3.7"
|
||||
"wagmi": "0.12.13",
|
||||
"zustand": "^4.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/tailwind": "^0.1.2",
|
||||
"@next/bundle-analyzer": "13.3.1",
|
||||
"@next/bundle-analyzer": "13.4.1",
|
||||
"@playwright/test": "1.33.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "4.1.1",
|
||||
"@types/canvas-confetti": "^1.6.0",
|
||||
|
|
@ -154,13 +155,12 @@
|
|||
"@types/katex": "^0.16.0",
|
||||
"@types/mdast": "^3.0.11",
|
||||
"@types/mjml": "4.7.1",
|
||||
"@types/node": "18.16.2",
|
||||
"@types/node": "20.1.0",
|
||||
"@types/pangu": "^4.0.0",
|
||||
"@types/prismjs": "1.26.0",
|
||||
"@types/react": "18.2.0",
|
||||
"@types/react-dom": "18.2.1",
|
||||
"@types/react": "18.2.6",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"@types/react-infinite-scroller": "1.2.3",
|
||||
"@types/react-scroll": "1.8.6",
|
||||
"@types/remove-markdown": "0.3.1",
|
||||
"@types/serialize-javascript": "^5.0.2",
|
||||
"@types/sortablejs": "1.15.1",
|
||||
|
|
@ -168,12 +168,11 @@
|
|||
"@types/uuid": "9.0.1",
|
||||
"autoprefixer": "10.4.14",
|
||||
"babel-loader": "^9.1.2",
|
||||
"cross-spawn": "7.0.3",
|
||||
"cspell": "^6.31.1",
|
||||
"esbuild": "0.17.18",
|
||||
"esbuild-register": "3.4.2",
|
||||
"eslint": "8.39.0",
|
||||
"eslint-config-next": "13.3.1",
|
||||
"eslint": "8.40.0",
|
||||
"eslint-config-next": "13.4.1",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-unused-imports": "2.0.0",
|
||||
"husky": "8.0.3",
|
||||
|
|
|
|||
1875
pnpm-lock.yaml
1875
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"intlDateTime": "{{val, datetime}}",
|
||||
"ago": "{{time}} ago",
|
||||
"joined ago": "Joined {{time}} ago"
|
||||
"joined ago": "Joined {{time}} ago",
|
||||
"signed and stored on the blockchain": "Ownership of this {{name}} data is guaranteed by blockchain and smart contracts to the creator alone."
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
{
|
||||
"load more": "There are {{count}} more {{name}}, click to load more",
|
||||
"signed and stored on the blockchain": "Ownership of this {{name}} data is guaranteed by blockchain and smart contracts to the creator alone."
|
||||
"load more": "There are {{count}} more {{name}}, click to load more"
|
||||
}
|
||||
|
|
@ -55,5 +55,6 @@
|
|||
"Showcase": "ショーケース",
|
||||
"AI-generated summary": "AIが生成した要約",
|
||||
"Generating": "生成中",
|
||||
"Show more": "もっと表示する"
|
||||
"Show more": "もっと表示する",
|
||||
"signed and stored on the blockchain": "{{name}}は、創作者によって署名され、ブロックチェーンに安全に保存されています。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
"About": "紹介",
|
||||
"Tags": "タグ",
|
||||
"load more": "まだ{{count}}件の{{name}}があります。",
|
||||
"signed and stored on the blockchain": "{{name}}は、創作者によって署名され、ブロックチェーンに安全に保存されています。",
|
||||
"Write a comment on the blockchain": "ブロックチェーンでコメントする",
|
||||
"powered by": "<span>このサイトは </span><name/><span> によって作動します</span>"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,87 +1,88 @@
|
|||
{
|
||||
"Connect": "連線",
|
||||
"Followers": "粉絲",
|
||||
"Following": "追蹤中",
|
||||
"Followings": "追蹤中",
|
||||
"Follow": "追蹤",
|
||||
"Unfollow": "取消追蹤",
|
||||
"intlDateTime": "{{val, datetime}}",
|
||||
"post": "文章",
|
||||
"posts": "文章",
|
||||
"page": "頁面",
|
||||
"pages": "頁面",
|
||||
"comment": "留言",
|
||||
"comments": "留言",
|
||||
"Comment": "留言",
|
||||
"Comments": "留言",
|
||||
"reply": "回覆",
|
||||
"replies": "回覆",
|
||||
"like": "讚",
|
||||
"likes": "讚",
|
||||
"blog": "部落格",
|
||||
"Owner": "擁有者",
|
||||
"Transaction Hash": "交易雜湊值",
|
||||
"IPFS Address": "IPFS 位址",
|
||||
"BNB Greenfield Address": "BNB Greenfield 位址",
|
||||
"Creation": "創建",
|
||||
"Last Update": "最後更新",
|
||||
"Create Character": "創建身份",
|
||||
"Submit": "提交",
|
||||
"Reply": "回覆",
|
||||
"Cancel Reply": "取消回覆",
|
||||
"Edit": "編輯",
|
||||
"Cancel Edit": "取消編輯",
|
||||
"Confirm Modification": "確認編輯",
|
||||
"ago": "{{time}}前",
|
||||
"joined ago": "{{time}}前加入",
|
||||
"obtained ago": "{{time}}前獲得",
|
||||
"Like": "點讚",
|
||||
"Mint to an NFT": "珍藏為 NFT",
|
||||
"Like successfully": "點讚成功",
|
||||
"Mint successfully": "珍藏完成~",
|
||||
"like stored": "你的點讚已被安全地儲存在區塊鏈上,可以在 <2>Crossbell Scan</2> 上查看",
|
||||
"mint stored": "你已將這篇文章珍藏成 NFT,可以在 <2>xChar</2> 或 <6>Crossbell Scan</6> 上查看",
|
||||
"Got it, thanks!": "知道啦,感恩的心~",
|
||||
"Like List": "按讚列表",
|
||||
"Mint List": "珍藏列表",
|
||||
"Revert": "收回",
|
||||
"Confirm to revert": "確定收回",
|
||||
"like revert": "請確定是否想要收回這個點讚操作?",
|
||||
"Cancel": "算了",
|
||||
"Confirm": "確定",
|
||||
"No Content Yet.": "什麼都沒",
|
||||
"Close": "關閉",
|
||||
"Dashboard": "主控臺",
|
||||
"Copied!": "拷貝完成",
|
||||
"Operator Sign": "簽名授權",
|
||||
"Switch Characters": "切換身份",
|
||||
"Upgrade to Wallet": "升級為錢包認證",
|
||||
"Disconnect": "中斷連線",
|
||||
"Loading": "載入中...",
|
||||
"Showcase": "展示櫃",
|
||||
"AI-generated summary": "AI 生成的摘要",
|
||||
"Generating": "生成中...",
|
||||
"Show more": "顯示更多",
|
||||
"Patron": "贊助",
|
||||
"Become a patron of {{name}}": "成為 {{name}} 的贊助者",
|
||||
"Latest patrons": "最新贊助者",
|
||||
"Latest tipper": "最新贊助者",
|
||||
"You are here to be the first patron.": "你是第一位贊助者。",
|
||||
"You are here to be the first tipper.": "你是第一位贊助者。",
|
||||
"Select a tier": "選擇一個等級",
|
||||
"One-time": "一次性",
|
||||
"Monthly and NFT Rewards": "每月和 NFT 獎勵",
|
||||
"Coming soon": "即將推出",
|
||||
"What is MIRA? Where can I get some?": "MIRA 是什麼?能吃嗎?我在哪裡可以獲得?",
|
||||
"Tip": "贊助",
|
||||
"Tip the post: {{name}}": "贊助文章:{{name}}",
|
||||
"Custom": "自訂",
|
||||
"Mintable": "Mintable",
|
||||
"Successfully followed": "嘿!你已經開始<2>追蹤他們的最新動態</2>囉。",
|
||||
"Enable AI Filtering": "AI 過濾",
|
||||
"Filter out possible low-quality content based on AI ratings.": "基於 AI 評分過濾可能不佳的內容",
|
||||
"Refreshing": "重新載入中",
|
||||
"Search for your interest": "搜你所想",
|
||||
"results": "項結果",
|
||||
"My xLog": "你的 xLog"
|
||||
"Connect": "連線",
|
||||
"Followers": "粉絲",
|
||||
"Following": "追蹤中",
|
||||
"Followings": "追蹤中",
|
||||
"Follow": "追蹤",
|
||||
"Unfollow": "取消追蹤",
|
||||
"intlDateTime": "{{val, datetime}}",
|
||||
"post": "文章",
|
||||
"posts": "文章",
|
||||
"page": "頁面",
|
||||
"pages": "頁面",
|
||||
"comment": "留言",
|
||||
"comments": "留言",
|
||||
"Comment": "留言",
|
||||
"Comments": "留言",
|
||||
"reply": "回覆",
|
||||
"replies": "回覆",
|
||||
"like": "讚",
|
||||
"likes": "讚",
|
||||
"blog": "部落格",
|
||||
"Owner": "擁有者",
|
||||
"Transaction Hash": "交易雜湊值",
|
||||
"IPFS Address": "IPFS 位址",
|
||||
"BNB Greenfield Address": "BNB Greenfield 位址",
|
||||
"Creation": "創建",
|
||||
"Last Update": "最後更新",
|
||||
"Create Character": "創建身份",
|
||||
"Submit": "提交",
|
||||
"Reply": "回覆",
|
||||
"Cancel Reply": "取消回覆",
|
||||
"Edit": "編輯",
|
||||
"Cancel Edit": "取消編輯",
|
||||
"Confirm Modification": "確認編輯",
|
||||
"ago": "{{time}}前",
|
||||
"joined ago": "{{time}}前加入",
|
||||
"obtained ago": "{{time}}前獲得",
|
||||
"Like": "點讚",
|
||||
"Mint to an NFT": "珍藏為 NFT",
|
||||
"Like successfully": "點讚成功",
|
||||
"Mint successfully": "珍藏完成~",
|
||||
"like stored": "你的點讚已被安全地儲存在區塊鏈上,可以在 <2>Crossbell Scan</2> 上查看",
|
||||
"mint stored": "你已將這篇文章珍藏成 NFT,可以在 <2>xChar</2> 或 <6>Crossbell Scan</6> 上查看",
|
||||
"Got it, thanks!": "知道啦,感恩的心~",
|
||||
"Like List": "按讚列表",
|
||||
"Mint List": "珍藏列表",
|
||||
"Revert": "收回",
|
||||
"Confirm to revert": "確定收回",
|
||||
"like revert": "請確定是否想要收回這個點讚操作?",
|
||||
"Cancel": "算了",
|
||||
"Confirm": "確定",
|
||||
"No Content Yet.": "什麼都沒",
|
||||
"Close": "關閉",
|
||||
"Dashboard": "主控臺",
|
||||
"Copied!": "拷貝完成",
|
||||
"Operator Sign": "簽名授權",
|
||||
"Switch Characters": "切換身份",
|
||||
"Upgrade to Wallet": "升級為錢包認證",
|
||||
"Disconnect": "中斷連線",
|
||||
"Loading": "載入中...",
|
||||
"Showcase": "展示櫃",
|
||||
"AI-generated summary": "AI 生成的摘要",
|
||||
"Generating": "生成中...",
|
||||
"Show more": "顯示更多",
|
||||
"Patron": "贊助",
|
||||
"Become a patron of {{name}}": "成為 {{name}} 的贊助者",
|
||||
"Latest patrons": "最新贊助者",
|
||||
"Latest tipper": "最新贊助者",
|
||||
"You are here to be the first patron.": "你是第一位贊助者。",
|
||||
"You are here to be the first tipper.": "你是第一位贊助者。",
|
||||
"Select a tier": "選擇一個等級",
|
||||
"One-time": "一次性",
|
||||
"Monthly and NFT Rewards": "每月和 NFT 獎勵",
|
||||
"Coming soon": "即將推出",
|
||||
"What is MIRA? Where can I get some?": "MIRA 是什麼?能吃嗎?我在哪裡可以獲得?",
|
||||
"Tip": "贊助",
|
||||
"Tip the post: {{name}}": "贊助文章:{{name}}",
|
||||
"Custom": "自訂",
|
||||
"Mintable": "Mintable",
|
||||
"Successfully followed": "嘿!你已經開始<2>追蹤他們的最新動態</2>囉。",
|
||||
"Enable AI Filtering": "AI 過濾",
|
||||
"Filter out possible low-quality content based on AI ratings.": "基於 AI 評分過濾可能不佳的內容",
|
||||
"Refreshing": "重新載入中",
|
||||
"Search for your interest": "搜你所想",
|
||||
"results": "項結果",
|
||||
"My xLog": "你的 xLog",
|
||||
"signed and stored on the blockchain": "此{{name}}數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。"
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
"About": "關於",
|
||||
"Tags": "標籤",
|
||||
"load more": "還有{{count}}篇{{name}},點選載入更多文章",
|
||||
"signed and stored on the blockchain": "此{{name}}數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。",
|
||||
"Write a comment on the blockchain": "在區塊鏈上留言。",
|
||||
"powered by": "<span>由 </span><name/><span> 提供支援</span>",
|
||||
"This address is in local editing preview mode and cannot be viewed by the public.": "此地址處於本地編輯預覽模式,尚未公開。",
|
||||
|
|
|
|||
|
|
@ -83,5 +83,6 @@
|
|||
"Refreshing": "刷新中",
|
||||
"Search for your interest": "搜索你感兴趣的内容",
|
||||
"results": "条结果",
|
||||
"My xLog": "我的 xLog"
|
||||
"My xLog": "我的 xLog",
|
||||
"signed and stored on the blockchain": "此{{name}}数据所有权由区块链加密技术和智能合约保障仅归创作者所有。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
"About": "关于",
|
||||
"Tags": "标签",
|
||||
"load more": "还有 {{count}} 篇{{name}},点击加载更多",
|
||||
"signed and stored on the blockchain": "此{{name}}数据所有权由区块链加密技术和智能合约保障仅归创作者所有。",
|
||||
"Write a comment on the blockchain": "在区块链上写下你的评论",
|
||||
"powered by": "<span>由 </span><name/><span> 提供支持</span>",
|
||||
"This address is in local editing preview mode and cannot be viewed by the public.":
|
||||
|
|
@ -15,5 +14,6 @@
|
|||
"View on Crossbell Scan": "在 Crossbell Scan 上查看",
|
||||
"Subscribe to JSON Feed": "订阅 JSON Feed",
|
||||
"Subscribe to RSS": "订阅 RSS",
|
||||
"Search on this site": "在本站搜索"
|
||||
"Search on this site": "在本站搜索",
|
||||
"404 - Whoops, this page is gone.": "404 - 哎呀,这个页面不见了。"
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { Metadata } from "next"
|
||||
|
||||
import { Hydrate, dehydrate } from "@tanstack/react-query"
|
||||
|
||||
import { HomeFeed } from "~/components/home/HomeFeed"
|
||||
import { HomeSidebar } from "~/components/home/HomeSidebar"
|
||||
import { APP_NAME } from "~/lib/env"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { prefetchGetFeed } from "~/queries/home.server"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `Activities - ${APP_NAME}`,
|
||||
}
|
||||
|
||||
async function Activities() {
|
||||
const queryClient = getQueryClient()
|
||||
await prefetchGetFeed(
|
||||
{
|
||||
type: "latest",
|
||||
},
|
||||
queryClient,
|
||||
)
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
return (
|
||||
<Hydrate state={dehydratedState}>
|
||||
<section className="pt-24">
|
||||
<div className="max-w-screen-lg px-5 mx-auto flex">
|
||||
<div className="flex-1 min-w-[300px]">
|
||||
<HomeFeed />
|
||||
</div>
|
||||
<HomeSidebar />
|
||||
</div>
|
||||
</section>
|
||||
</Hydrate>
|
||||
)
|
||||
}
|
||||
|
||||
export default Activities
|
||||
|
|
@ -1,61 +1,26 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { Hydrate, dehydrate } from "@tanstack/react-query"
|
||||
|
||||
import { ConnectButton } from "~/components/common/ConnectButton"
|
||||
import { DarkModeSwitch } from "~/components/common/DarkModeSwitch"
|
||||
import { Logo } from "~/components/common/Logo"
|
||||
import {
|
||||
APP_DESCRIPTION,
|
||||
APP_NAME,
|
||||
DISCORD_LINK,
|
||||
GITHUB_LINK,
|
||||
TWITTER_LINK,
|
||||
} from "~/lib/env"
|
||||
import HomeTabs from "~/components/home/HomeTabs"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { APP_NAME, DISCORD_LINK, GITHUB_LINK, TWITTER_LINK } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { cn } from "~/lib/utils"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { prefetchGetShowcase } from "~/queries/home.server"
|
||||
|
||||
import { ConnectButton } from "../common/ConnectButton"
|
||||
import { DarkModeSwitch } from "../common/DarkModeSwitch"
|
||||
import { SEOHead } from "../common/SEOHead"
|
||||
import { Image } from "../ui/Image"
|
||||
import { UniLink } from "../ui/UniLink"
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: "Home",
|
||||
link: "/",
|
||||
},
|
||||
{
|
||||
name: "Activities",
|
||||
link: "/activities",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<Image
|
||||
className="no-optimization w-24"
|
||||
alt="github stars"
|
||||
src="https://img.shields.io/github/stars/Crossbell-Box/xLog?color=white&label=Stars&logo=github&style=social"
|
||||
/>
|
||||
),
|
||||
link: GITHUB_LINK,
|
||||
},
|
||||
]
|
||||
|
||||
export function MainLayout({
|
||||
export default async function HomeLayout({
|
||||
children,
|
||||
title,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
title?: string
|
||||
}) {
|
||||
const { t } = useTranslation("index")
|
||||
const router = useRouter()
|
||||
const queryClient = getQueryClient()
|
||||
await prefetchGetShowcase(queryClient)
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEOHead
|
||||
title={title}
|
||||
siteName={APP_NAME}
|
||||
description={APP_DESCRIPTION}
|
||||
/>
|
||||
<Hydrate state={dehydratedState}>
|
||||
<header className="py-5 fixed w-full top-0 bg-white z-10">
|
||||
<div className="max-w-screen-lg px-5 mx-auto flex justify-between items-center">
|
||||
<UniLink
|
||||
|
|
@ -68,20 +33,7 @@ export function MainLayout({
|
|||
xLog
|
||||
</UniLink>
|
||||
<div className="space-x-14 text-zinc-500 flex">
|
||||
{tabs?.map((tab, index) => (
|
||||
<UniLink
|
||||
className={cn(
|
||||
"cursor-pointer items-center hidden sm:flex hover:text-accent text-lg",
|
||||
{
|
||||
"text-accent": router.pathname === tab.link,
|
||||
},
|
||||
)}
|
||||
key={tab.link}
|
||||
href={tab.link}
|
||||
>
|
||||
{typeof tab.name === "string" ? t(tab.name) : tab.name}
|
||||
</UniLink>
|
||||
))}
|
||||
<HomeTabs />
|
||||
<ConnectButton size="base" variantColor="black" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -122,6 +74,6 @@ export function MainLayout({
|
|||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</>
|
||||
</Hydrate>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,341 @@
|
|||
import Link from "next/link"
|
||||
import { memo } from "react"
|
||||
|
||||
import { Logo } from "~/components/common/Logo"
|
||||
import { default as OriginEntranceButton } from "~/components/home/EntranceButton"
|
||||
import { Integration } from "~/components/home/Integrations"
|
||||
import { ShowCase } from "~/components/home/Showcase"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { CSB_SCAN, GITHUB_LINK } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { Trans, useTranslation } from "~/lib/i18n"
|
||||
|
||||
async function Home() {
|
||||
const { t, i18n } = await useTranslation("index")
|
||||
|
||||
const EntranceButton = memo(function Button() {
|
||||
return (
|
||||
<OriginEntranceButton
|
||||
connectedContent={
|
||||
<>
|
||||
<span className="icon-[mingcute--grid-line] text-xl mr-2 inline-block"></span>
|
||||
<span>{t("Dashboard")}</span>
|
||||
</>
|
||||
}
|
||||
unconnectedContent={t("Get my xLog in 5 minutes")}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const features: {
|
||||
title: string
|
||||
subfeatures: {
|
||||
screenshot?: {
|
||||
src: string
|
||||
}
|
||||
icon: string
|
||||
title: string
|
||||
}[]
|
||||
extra?: boolean
|
||||
}[] = [
|
||||
{
|
||||
title: "Write",
|
||||
subfeatures: [
|
||||
{
|
||||
screenshot: {
|
||||
src: "/assets/easy.png",
|
||||
},
|
||||
icon: "🤪",
|
||||
title: "Easy to get started",
|
||||
},
|
||||
{
|
||||
screenshot: {
|
||||
src: "/assets/experience.png",
|
||||
},
|
||||
icon: "😆",
|
||||
title: "Elegant experience",
|
||||
},
|
||||
{
|
||||
screenshot: {
|
||||
src: "/assets/fast.png",
|
||||
},
|
||||
icon: "🚀",
|
||||
title: "Fast",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Own",
|
||||
subfeatures: [
|
||||
{
|
||||
screenshot: {
|
||||
src: "/assets/safe.png",
|
||||
},
|
||||
icon: "🔒",
|
||||
title: "Safe",
|
||||
},
|
||||
{
|
||||
screenshot: {
|
||||
src: "/assets/customizable.png",
|
||||
},
|
||||
icon: "🎨",
|
||||
title: "Customizable",
|
||||
},
|
||||
{
|
||||
screenshot: {
|
||||
src: "/assets/open.png",
|
||||
},
|
||||
icon: "🌐",
|
||||
title: "Open",
|
||||
},
|
||||
],
|
||||
extra: true,
|
||||
},
|
||||
{
|
||||
title: "Earn",
|
||||
subfeatures: [
|
||||
{
|
||||
icon: "🪙",
|
||||
title: "Creator Incentives",
|
||||
},
|
||||
{
|
||||
icon: "🏟️",
|
||||
title: "DAO",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="max-w-screen-lg px-5 mx-auto">
|
||||
<div className="h-screen w-full flex justify-center flex-col relative text-center">
|
||||
<h2 className="text-6xl sm:text-8xl font-bold mb-5">
|
||||
{features.map((feature) => (
|
||||
<span
|
||||
key={feature.title}
|
||||
className={`text-feature text-feature-${feature.title.toLocaleLowerCase()} block sm:inline-block sm:mr-1 mb-2`}
|
||||
>
|
||||
{t(feature.title)}
|
||||
{t(".")}
|
||||
</span>
|
||||
))}
|
||||
</h2>
|
||||
<h3 className="mt-3 sm:mt-5 text-zinc-800 text-2xl sm:text-4xl font-light">
|
||||
<Trans i18n={i18n} i18nKey="description" ns="index">
|
||||
<strong className="font-medium">xLog</strong> is the best{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
className="underline decoration-2 text-yellow-400 font-medium"
|
||||
href={CSB_SCAN}
|
||||
>
|
||||
on-chain
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
className="underline decoration-2 text-green-400 font-medium"
|
||||
href={GITHUB_LINK}
|
||||
>
|
||||
open-source
|
||||
</a>{" "}
|
||||
blogging community for everyone.
|
||||
</Trans>
|
||||
</h3>
|
||||
<div className="my-4 sm:my-12 flex items-center justify-center sm:flex-row flex-col">
|
||||
<EntranceButton />
|
||||
<Button
|
||||
className="text-accent h-10 sm:ml-4 mt-4 flex"
|
||||
size="2xl"
|
||||
variant="outline"
|
||||
variantColor="gradient"
|
||||
>
|
||||
<span>
|
||||
<Link href="/activities">{t("Look at others'")}</Link>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-center absolute bottom-4 sm:bottom-14 w-full flex items-center justify-center flex-col">
|
||||
<span className="mb-1 sm:mb-3">{t("Explore the xLog way")}</span>
|
||||
<a
|
||||
href="#features"
|
||||
className="cursor-pointer inline-block icon-[mingcute--down-line] text-3xl"
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div id="features">
|
||||
{features.map((feature, index) => (
|
||||
<div className="text-center mt-28" key={feature.title}>
|
||||
<div className="">
|
||||
<div
|
||||
className={`h-20 w-[1px] bg-gradient-to-b mx-auto text-feature-${feature.title.toLocaleLowerCase()}`}
|
||||
style={
|
||||
{
|
||||
"--tw-gradient-from": "transparent",
|
||||
} as any
|
||||
}
|
||||
></div>
|
||||
<div
|
||||
className={`block rounded-full w-10 h-10 mx-auto p-2 text-white bg-gradient-to-br text-feature-${feature.title.toLocaleLowerCase()}`}
|
||||
>
|
||||
{index + 1}
|
||||
</div>
|
||||
<h3
|
||||
className={`mt-6 inline-block text-3xl font-bold text-feature text-feature-${feature.title.toLocaleLowerCase()}`}
|
||||
>
|
||||
{t(feature.title)}
|
||||
</h3>
|
||||
<h4 className="mt-6 text-5xl sm:text-6xl font-bold">
|
||||
{t(`features.${feature.title}.subtitle`)}
|
||||
</h4>
|
||||
<p className="mt-6 text-zinc-500">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
i18nKey={`features.${feature.title}.description`}
|
||||
components={{
|
||||
aown: (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
className="underline"
|
||||
href="https://github.com/Crossbell-Box/Crossbell-Contracts/wiki"
|
||||
>
|
||||
.
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
ns="index"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<ul className="pt-10 grid grid-cols-1 sm:grid-cols-3 gap-8">
|
||||
{feature.subfeatures.map((item) => (
|
||||
<li
|
||||
className="border rounded-xl overflow-hidden bg-white hover:shadow-md hover:scale-105 transition duration-300 cursor-default"
|
||||
key={item.title}
|
||||
>
|
||||
{item.screenshot?.src && (
|
||||
<div className="w-full h-44">
|
||||
<Image
|
||||
className="object-cover"
|
||||
src={item.screenshot.src}
|
||||
alt={item.title}
|
||||
fill
|
||||
></Image>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-2xl font-bold mt-5 flex items-center px-5">
|
||||
<span className="mr-3">{item.icon}</span>
|
||||
<span
|
||||
className={`text-feature text-feature-${feature.title.toLocaleLowerCase()}`}
|
||||
>
|
||||
{t(item.title)}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-base font-light mt-3 mb-4 leading-normal px-5 text-left">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
i18nKey={`${item.title} text`}
|
||||
components={{
|
||||
strong: <strong className="font-bold" />,
|
||||
axfm: (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
className="underline"
|
||||
href={`${getSiteLink({
|
||||
subdomain: "xlog",
|
||||
})}/xfm`}
|
||||
>
|
||||
.
|
||||
</a>
|
||||
),
|
||||
aincentive: (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
className="underline"
|
||||
href={`${getSiteLink({
|
||||
subdomain: "xlog",
|
||||
})}/creator-incentive-plan`}
|
||||
>
|
||||
.
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
ns="index"
|
||||
/>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{feature.extra && (
|
||||
<div className="border rounded-xl overflow-hidden bg-white hover:shadow-md hover:scale-105 transition duration-300 cursor-default mt-10 px-5 py-8 space-y-4">
|
||||
<div className="font-bold text-3xl">
|
||||
🤫{" "}
|
||||
<span
|
||||
className={`text-feature text-feature-${feature.title.toLocaleLowerCase()}`}
|
||||
>
|
||||
{t(`features.${feature.title}.extra.title`)}
|
||||
</span>
|
||||
</div>
|
||||
<div>{t(`features.${feature.title}.extra.description`)}</div>
|
||||
<a
|
||||
target="_blank"
|
||||
href={getSiteLink({ subdomain: "xlog" })}
|
||||
role="button"
|
||||
className="button is-black rounded-lg"
|
||||
>
|
||||
{t(`features.${feature.title}.extra.button`)}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div id="showcase" className="text-center">
|
||||
<div className="pt-32 text-5xl sm:text-6xl font-bold">
|
||||
{t("Showcase")}
|
||||
</div>
|
||||
<div className="my-10 text-zinc-700">
|
||||
<p className="text-lg">
|
||||
{t(
|
||||
"Discover these awesome teams and creators on xLog (sorted by update time)",
|
||||
)}
|
||||
</p>
|
||||
<ShowCase />
|
||||
</div>
|
||||
</div>
|
||||
<div id="integration" className="text-center">
|
||||
<div className="pt-32 text-5xl sm:text-6xl font-bold">
|
||||
{t("Integration")}
|
||||
</div>
|
||||
<div className="my-10 text-zinc-700">
|
||||
<p className="text-xl">
|
||||
{t(
|
||||
"xLog's open design allows it to integrate with many other open protocols and applications without friction.",
|
||||
)}
|
||||
</p>
|
||||
<ul className="mt-14 grid grid-cols-3 sm:grid-cols-5 gap-y-14 gap-x-2">
|
||||
<Integration />
|
||||
<li className="flex items-center justify-center">
|
||||
<i className="icon-[mingcute--more-1-line] text-5xl" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-20 text-center">
|
||||
<div className="w-[100px] h-[100px] mx-auto mb-10">
|
||||
<Logo type="lottie" width={100} height={100} loop={true} />
|
||||
</div>
|
||||
<EntranceButton />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { Metadata } from "next"
|
||||
|
||||
import { SearchInput } from "~/components/common/SearchInput"
|
||||
import { HomeFeed } from "~/components/home/HomeFeed"
|
||||
import { HomeSidebar } from "~/components/home/HomeSidebar"
|
||||
import { APP_NAME } from "~/lib/env"
|
||||
|
||||
export function generateMetadata({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: {
|
||||
[key: string]: string | string[] | undefined
|
||||
}
|
||||
}): Metadata {
|
||||
return {
|
||||
title: `Search: ${searchParams.q} - ${APP_NAME}`,
|
||||
}
|
||||
}
|
||||
|
||||
async function Search() {
|
||||
return (
|
||||
<section className="pt-24">
|
||||
<div className="max-w-screen-lg px-5 mx-auto flex">
|
||||
<div className="flex-1 min-w-[300px]">
|
||||
<SearchInput />
|
||||
<div className="mt-10">
|
||||
<HomeFeed type="search" />
|
||||
</div>
|
||||
</div>
|
||||
<HomeSidebar hideSearch={true} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Search
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Metadata } from "next"
|
||||
|
||||
import { HomeFeed } from "~/components/home/HomeFeed"
|
||||
import { HomeSidebar } from "~/components/home/HomeSidebar"
|
||||
import { APP_NAME } from "~/lib/env"
|
||||
|
||||
import topics from "../../../../../data/topics.json"
|
||||
|
||||
export function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
topic: string
|
||||
}
|
||||
}): Metadata {
|
||||
return {
|
||||
title: `Topic: ${params.topic} - ${APP_NAME}`,
|
||||
}
|
||||
}
|
||||
|
||||
function Topic({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
topic: string
|
||||
}
|
||||
}) {
|
||||
const info = topics.find((t) => t.name === params.topic)
|
||||
|
||||
return (
|
||||
<section className="pt-24">
|
||||
<div className="max-w-screen-lg px-5 mx-auto flex">
|
||||
<div className="flex-1 min-w-[300px]">
|
||||
<h2 className="text-3xl font-bold">Topic: {params.topic}</h2>
|
||||
<p className="text-zinc-400 mt-4">{info?.description}</p>
|
||||
<div className="mt-10">
|
||||
<HomeFeed type="topic" noteIds={info?.notes} />
|
||||
</div>
|
||||
</div>
|
||||
<HomeSidebar />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Topic
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { NextResponse } from "next/server"
|
||||
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
import { checkDomainServer } from "~/models/site.model"
|
||||
|
||||
// /api/check-domain?handle=innei-4525&domain=blog.innei.ren
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
const { handle, domain } = getQuery(req)
|
||||
|
||||
if (!handle || !domain) {
|
||||
return NextResponse.json({ error: "Missing characterId or domain" })
|
||||
}
|
||||
const res = new NextServerResponse()
|
||||
|
||||
return res.status(200).json({
|
||||
data: await checkDomainServer(domain as string, handle as string),
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { NextServerResponse } from "~/lib/server-helper"
|
||||
|
||||
// /api/healthcheck
|
||||
export async function GET() {
|
||||
const res = new NextServerResponse()
|
||||
return res.status(200).json({
|
||||
ok: true,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
import { OUR_DOMAIN } from "~/lib/env"
|
||||
import { cacheGet } from "~/lib/redis.server"
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
import { checkDomainServer, fetchTenant } from "~/models/site.model"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
let { host } = req.query
|
||||
// /api/host2handle?host=blog.innei.ren
|
||||
export async function GET(req: Request) {
|
||||
let { host } = getQuery(req)
|
||||
|
||||
let realHost: string
|
||||
if (Array.isArray(host)) {
|
||||
|
|
@ -57,6 +54,6 @@ export default async function handler(
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
res.status(200).json(result)
|
||||
const res = new NextServerResponse()
|
||||
return res.status(200).json(result)
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import { getDefaultSlug } from "~/lib/default-slug"
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const { address } = req.query
|
||||
export async function GET(req: Request) {
|
||||
const { address } = getQuery(req)
|
||||
|
||||
const result = await (
|
||||
await fetch("https://mirror.xyz/api/graphql", {
|
||||
|
|
@ -20,5 +18,22 @@ export default async function handler(
|
|||
})
|
||||
).json()
|
||||
|
||||
res.status(200).json(result)
|
||||
if (result.data?.projectFeed?.posts?.length) {
|
||||
result.data.projectFeed.posts = result.data.projectFeed.posts.map(
|
||||
(post: any) => {
|
||||
return {
|
||||
title: post.title,
|
||||
date_published: new Date(
|
||||
post.publishedAtTimestamp * 1000,
|
||||
).toISOString(),
|
||||
slug: getDefaultSlug(post.title, post.digest),
|
||||
tags: ["Mirror.xyz"],
|
||||
content: post.body,
|
||||
external_urls: [`https://mirror.xyz/${address}/${post.digest}`],
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return new NextServerResponse().status(200).json(result)
|
||||
}
|
||||
|
|
@ -1,19 +1,15 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
import { getPagesBySite } from "~/models/page.model"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const query = req.query
|
||||
export async function GET(req: Request) {
|
||||
const query = getQuery(req)
|
||||
|
||||
const result = await getPagesBySite({
|
||||
characterId: +(query.characterId || 0) as number,
|
||||
type: query.type as "post" | "page",
|
||||
visibility: query.visibility as PageVisibilityEnum,
|
||||
limit: query.take ? parseInt(query.take as string) : undefined,
|
||||
limit: query.limit ? parseInt(query.limit as string) : undefined,
|
||||
cursor: query.cursor as string,
|
||||
useStat: true,
|
||||
...(query.tags && {
|
||||
|
|
@ -22,6 +18,6 @@ export default async function handler(
|
|||
: [query.tags as string],
|
||||
}),
|
||||
})
|
||||
|
||||
res.status(200).json(result)
|
||||
const res = new NextServerResponse()
|
||||
return res.status(200).json(result)
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { NoteEntity } from "crossbell.js"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
import { IS_VERCEL_PREVIEW } from "~/lib/constants"
|
||||
import { getDefaultSlug } from "~/lib/default-slug"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
import { checkDomainServer } from "~/models/site.model"
|
||||
|
||||
async function getOriginalNote(
|
||||
|
|
@ -30,15 +31,12 @@ async function getOriginalNote(
|
|||
}
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
let { characterId, noteId } = req.query
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
let { characterId, noteId } = getQuery(req)
|
||||
|
||||
const res = new NextServerResponse()
|
||||
if (!characterId || typeof characterId !== "string") {
|
||||
res.status(400).json({ error: "Missing characterId" })
|
||||
return
|
||||
return res.status(400).json({ error: "Missing characterId" })
|
||||
}
|
||||
|
||||
let character
|
||||
|
|
@ -84,6 +82,8 @@ export default async function handler(
|
|||
if (IS_VERCEL_PREVIEW) {
|
||||
const path = new URL(link).pathname
|
||||
|
||||
res.redirect(`/_site/${character.handle}${path}`)
|
||||
} else res.redirect(link)
|
||||
redirect(`/site/${character.handle}${path}`)
|
||||
} else {
|
||||
redirect(link)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
import { toGateway } from "~/lib/ipfs-parser"
|
||||
import prisma from "~/lib/prisma.server"
|
||||
import { cacheGet } from "~/lib/redis.server"
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
|
||||
const getOriginalScore = async (cid: string) => {
|
||||
try {
|
||||
|
|
@ -61,7 +60,7 @@ const getOriginalScore = async (cid: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getScore(cid: string) {
|
||||
async function getScore(cid: string) {
|
||||
const score = await cacheGet({
|
||||
key: ["summary_score", cid],
|
||||
getValueFun: async () => {
|
||||
|
|
@ -113,18 +112,16 @@ export async function getScore(cid: string) {
|
|||
return score
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
let { cid } = req.query
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
let { cid } = getQuery(req)
|
||||
|
||||
const res = new NextServerResponse()
|
||||
|
||||
if (!cid) {
|
||||
res.status(400).send("Bad Request")
|
||||
return
|
||||
return res.status(400).send("Bad Request")
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
return res.status(200).json({
|
||||
data: await getScore(cid as string),
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
import { getIdBySlug } from "~/queries/page.server"
|
||||
|
||||
// GET /api/slug2id?characterId=52055&slug=note-144
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
let { characterId, slug } = getQuery(req)
|
||||
const res = new NextServerResponse()
|
||||
if (!slug || !characterId) {
|
||||
return res.status(400).send("Bad Request")
|
||||
}
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.send(await getIdBySlug(slug as string, characterId as string))
|
||||
}
|
||||
|
|
@ -1,25 +1,30 @@
|
|||
import { OpenAI } from "langchain"
|
||||
import { AnalyzeDocumentChain, loadSummarizationChain } from "langchain/chains"
|
||||
import { OpenAI } from "langchain/llms/openai"
|
||||
import { PromptTemplate } from "langchain/prompts"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import removeMarkdown from "remove-markdown"
|
||||
|
||||
import { Metadata } from "@prisma/client"
|
||||
|
||||
import { toGateway } from "~/lib/ipfs-parser"
|
||||
import prisma from "~/lib/prisma.server"
|
||||
import { cacheGet } from "~/lib/redis.server"
|
||||
import removeMarkdown from "remove-markdown"
|
||||
import { NextServerResponse, getQuery } from "~/lib/server-helper"
|
||||
|
||||
const model = new OpenAI({
|
||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
modelName: "gpt-3.5-turbo",
|
||||
temperature: 0.3,
|
||||
maxTokens: 400,
|
||||
})
|
||||
let model: OpenAI | undefined
|
||||
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
model = new OpenAI({
|
||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
modelName: "gpt-3.5-turbo",
|
||||
temperature: 0.3,
|
||||
maxTokens: 400,
|
||||
})
|
||||
}
|
||||
|
||||
const chains = new Map<string, AnalyzeDocumentChain>()
|
||||
|
||||
const getOriginalSummary = async (cid: string, lang: string) => {
|
||||
if (!model) return
|
||||
try {
|
||||
let { content } = await (await fetch(toGateway(`ipfs://${cid}`))).json()
|
||||
|
||||
|
|
@ -70,7 +75,7 @@ const getOriginalSummary = async (cid: string, lang: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getSummary(cid: string, lang: string = "en") {
|
||||
async function getSummary(cid: string, lang: string = "en") {
|
||||
const summary = await cacheGet({
|
||||
key: ["summary", cid, lang],
|
||||
getValueFun: async () => {
|
||||
|
|
@ -120,18 +125,15 @@ export async function getSummary(cid: string, lang: string = "en") {
|
|||
return summary
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
let { cid, lang } = req.query
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
let { cid, lang } = getQuery(req)
|
||||
const res = new NextServerResponse()
|
||||
|
||||
if (!cid) {
|
||||
res.status(400).send("Bad Request")
|
||||
return
|
||||
return res.status(400).send("Bad Request")
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
return res.status(200).json({
|
||||
data: await getSummary(cid as string, lang as string),
|
||||
})
|
||||
}
|
||||
|
|
@ -1,29 +1,14 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { useRouter } from "next/router"
|
||||
import type { ReactElement } from "react"
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
|
||||
import { AchievementItem } from "~/components/common/AchievementItem"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useGetAchievements, useGetSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function AchievementsPage() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
const achievement = useGetAchievements(site.data?.characterId)
|
||||
|
|
@ -62,7 +47,3 @@ export default function AchievementsPage() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
AchievementsPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Achievements">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,34 +1,19 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import type { ReactElement } from "react"
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { CommentItem } from "~/components/common/CommentItem"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { useGetCommentsBySite, useGetSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function CommentsPage() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const { t } = useTranslation(["dashboard", "common"])
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const { t } = useTranslation("dashboard")
|
||||
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
|
|
@ -137,10 +122,6 @@ export default function CommentsPage() {
|
|||
)
|
||||
}
|
||||
|
||||
CommentsPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Comments">{page}</DashboardLayout>
|
||||
}
|
||||
|
||||
const Loader = () => {
|
||||
const { t } = useTranslation("common")
|
||||
return (
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
"use client"
|
||||
|
||||
import { useDebounceEffect } from "ahooks"
|
||||
import type { Root } from "mdast"
|
||||
import { nanoid } from "nanoid"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation"
|
||||
import NodeID3 from "node-id3"
|
||||
import {
|
||||
ChangeEvent,
|
||||
|
|
@ -15,7 +15,6 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import type { ReactElement } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
import { shallow } from "zustand/shallow"
|
||||
|
||||
|
|
@ -23,8 +22,6 @@ import type { EditorView } from "@codemirror/view"
|
|||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import { PageContent } from "~/components/common/PageContent"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { OptionsButton } from "~/components/dashboard/OptionsButton"
|
||||
import { PublishButton } from "~/components/dashboard/PublishButton"
|
||||
|
|
@ -50,8 +47,8 @@ import { showConfetti } from "~/lib/confetti"
|
|||
import { getDefaultSlug } from "~/lib/default-slug"
|
||||
import { CSB_SCAN } from "~/lib/env"
|
||||
import { getSiteLink, getTwitterShareUrl } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { getPageVisibility } from "~/lib/page-helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { delStorage, setStorage } from "~/lib/storage"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
import { cn, pick } from "~/lib/utils"
|
||||
|
|
@ -64,18 +61,6 @@ import {
|
|||
} from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const getInputDatetimeValue = (date: Date | string, dayjs: any) => {
|
||||
const str = dayjs(date).format()
|
||||
return str.substring(0, ((str.indexOf("T") | 0) + 6) | 0)
|
||||
|
|
@ -85,10 +70,12 @@ export default function SubdomainEditor() {
|
|||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
const { t } = useTranslation("dashboard")
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
let pageId = router.query.id as string | undefined
|
||||
const subdomain = router.query.subdomain as string
|
||||
const isPost = router.query.type === "post"
|
||||
let pageId = searchParams?.get("id") as string | undefined
|
||||
const isPost = searchParams?.get("type") === "post"
|
||||
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
|
|
@ -105,7 +92,9 @@ export default function SubdomainEditor() {
|
|||
site.data?.characterId,
|
||||
])
|
||||
router.replace(
|
||||
`/dashboard/${subdomain}/editor?id=local-${randomId}&type=${router.query.type}`,
|
||||
`/dashboard/${subdomain}/editor?id=local-${randomId}&type=${searchParams?.get(
|
||||
"type",
|
||||
)}`,
|
||||
)
|
||||
} else {
|
||||
key = `draft-${site.data?.characterId}-${pageId}`
|
||||
|
|
@ -117,7 +106,14 @@ export default function SubdomainEditor() {
|
|||
.replace(`draft-${site.data?.characterId}-`, ""),
|
||||
)
|
||||
}
|
||||
}, [subdomain, pageId, queryClient, router, site.data?.characterId])
|
||||
}, [
|
||||
subdomain,
|
||||
pageId,
|
||||
queryClient,
|
||||
router,
|
||||
site.data?.characterId,
|
||||
searchParams,
|
||||
])
|
||||
|
||||
const page = useGetPage({
|
||||
characterId: site.data?.characterId,
|
||||
|
|
@ -272,7 +268,9 @@ export default function SubdomainEditor() {
|
|||
|
||||
if (createOrUpdatePage.data.data) {
|
||||
router.replace(
|
||||
`/dashboard/${subdomain}/editor?id=${site.data?.characterId}-${createOrUpdatePage.data.data}&type=${router.query.type}`,
|
||||
`/dashboard/${subdomain}/editor?id=${site.data?.characterId}-${
|
||||
createOrUpdatePage.data.data
|
||||
}&type=${searchParams?.get("type")}`,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -507,7 +505,7 @@ export default function SubdomainEditor() {
|
|||
|
||||
const onPreviewButtonClick = useCallback(() => {
|
||||
window.open(
|
||||
`/_site/${subdomain}/preview/${draftKey.replace(
|
||||
`/site/${subdomain}/preview/${draftKey.replace(
|
||||
`draft-${site.data?.characterId}-`,
|
||||
"",
|
||||
)}`,
|
||||
|
|
@ -763,10 +761,6 @@ export default function SubdomainEditor() {
|
|||
)
|
||||
}
|
||||
|
||||
SubdomainEditor.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Editor">{page}</DashboardLayout>
|
||||
}
|
||||
|
||||
const EditorExtraProperties: FC<{
|
||||
updateValue: <K extends keyof Values>(key: K, value: Values[K]) => void
|
||||
isPost: boolean
|
||||
|
|
@ -1,35 +1,20 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import type { ReactElement } from "react"
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { Avatar } from "~/components/ui/Avatar"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { getStorage, setStorage } from "~/lib/storage"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { useGetPagesBySite } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function SiteAvatar({ siteId }: { siteId: string }) {
|
||||
const site = useGetSite(siteId)
|
||||
return (
|
||||
|
|
@ -189,7 +174,3 @@ export default function EventsPage() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
EventsPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Events">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,38 +1,24 @@
|
|||
"use client"
|
||||
|
||||
import type { NoteMetadata } from "crossbell.js"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { ReactElement, useEffect, useState } from "react"
|
||||
import { useParams } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "react-hot-toast"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { ImportPreview } from "~/components/dashboard/ImportPreview"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { readFiles } from "~/lib/read-files"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { usePostNotes } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function ImportMarkdownPage() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const site = useGetSite(subdomain)
|
||||
const { t } = useTranslation("dashboard")
|
||||
const form = useForm({
|
||||
|
|
@ -131,9 +117,3 @@ export default function ImportMarkdownPage() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
ImportMarkdownPage.getLayout = (page: ReactElement) => {
|
||||
return (
|
||||
<DashboardLayout title="Import from Markdown files">{page}</DashboardLayout>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,36 +1,22 @@
|
|||
"use client"
|
||||
|
||||
import type { NoteMetadata } from "crossbell.js"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { ReactElement, useEffect } from "react"
|
||||
import { useParams } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "react-hot-toast"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { ImportPreview } from "~/components/dashboard/ImportPreview"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { useCheckMirror, useGetMirrorXyz, usePostNotes } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function ImportMarkdownPage() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const site = useGetSite(subdomain)
|
||||
const { t } = useTranslation("dashboard")
|
||||
const form = useForm({
|
||||
|
|
@ -123,9 +109,3 @@ export default function ImportMarkdownPage() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
ImportMarkdownPage.getLayout = (page: ReactElement) => {
|
||||
return (
|
||||
<DashboardLayout title="Import from Mirror.xyz">{page}</DashboardLayout>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,29 +1,14 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import type { ReactElement } from "react"
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
|
||||
export default function ImportPage() {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { t } = useTranslation("dashboard")
|
||||
|
||||
const options = [
|
||||
|
|
@ -46,7 +31,7 @@ export default function ImportPage() {
|
|||
<UniLink
|
||||
className="prose p-6 bg-slate-100 rounded-lg relative flex items-center"
|
||||
key={option.name}
|
||||
href={router.asPath + option.path}
|
||||
href={pathname + option.path}
|
||||
>
|
||||
<span className="w-8 h-8 mr-4">
|
||||
<Image
|
||||
|
|
@ -64,7 +49,3 @@ export default function ImportPage() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
ImportPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Import">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { useParams, usePathname } from "next/navigation"
|
||||
import React, { useEffect } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
|
|
@ -10,10 +12,13 @@ import {
|
|||
|
||||
import { ConnectButton } from "~/components/common/ConnectButton"
|
||||
import { Logo } from "~/components/common/Logo"
|
||||
import { DashboardSidebar } from "~/components/dashboard/DashboardSidebar"
|
||||
import { DashboardTopbar } from "~/components/dashboard/DashboardTopbar"
|
||||
import { Avatar } from "~/components/ui/Avatar"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
|
||||
import { useUserRole } from "~/hooks/useUserRole"
|
||||
import { APP_NAME, DISCORD_LINK } from "~/lib/env"
|
||||
import { DISCORD_LINK } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { toGateway } from "~/lib/ipfs-parser"
|
||||
import { getStorage } from "~/lib/storage"
|
||||
|
|
@ -21,20 +26,15 @@ import { cn } from "~/lib/utils"
|
|||
import { useGetPagesBySite } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
import { SEOHead } from "../common/SEOHead"
|
||||
import { UniLink } from "../ui/UniLink"
|
||||
import { DashboardSidebar } from "./DashboardSidebar"
|
||||
import { DashboardTopbar } from "./DashboardTopbar"
|
||||
|
||||
export function DashboardLayout({
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
title,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
title: string
|
||||
}) {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
const userRole = useUserRole(subdomain)
|
||||
|
|
@ -48,6 +48,7 @@ export function DashboardLayout({
|
|||
const { t } = useTranslation("dashboard")
|
||||
|
||||
const isMobileLayout = useIsMobileLayout()
|
||||
const pathname = usePathname()
|
||||
|
||||
useEffect(() => {
|
||||
if (ssrReady) {
|
||||
|
|
@ -92,7 +93,7 @@ export function DashboardLayout({
|
|||
const links: {
|
||||
href?: string
|
||||
onClick?: () => void
|
||||
isActive: (ctx: { href: string; pathname: string }) => boolean
|
||||
isActive: (ctx: { href: string; pathname: string | null }) => boolean
|
||||
icon: React.ReactNode
|
||||
text: string
|
||||
}[] = [
|
||||
|
|
@ -131,7 +132,7 @@ export function DashboardLayout({
|
|||
{
|
||||
href: `/dashboard/${subdomain}/import`,
|
||||
isActive: ({ pathname }) =>
|
||||
pathname.startsWith(`/dashboard/${subdomain}/import`),
|
||||
!!pathname?.startsWith(`/dashboard/${subdomain}/import`),
|
||||
icon: "icon-[mingcute--file-import-line]",
|
||||
text: "Import",
|
||||
},
|
||||
|
|
@ -158,7 +159,7 @@ export function DashboardLayout({
|
|||
{
|
||||
href: `/dashboard/${subdomain}/settings/general`,
|
||||
isActive: ({ pathname }) =>
|
||||
pathname.startsWith(`/dashboard/${subdomain}/settings`),
|
||||
!!pathname?.startsWith(`/dashboard/${subdomain}/settings`),
|
||||
icon: "icon-[mingcute--settings-3-line]",
|
||||
text: "Settings",
|
||||
},
|
||||
|
|
@ -167,7 +168,8 @@ export function DashboardLayout({
|
|||
return ready ? (
|
||||
hasPermission ? (
|
||||
<>
|
||||
<SEOHead title={t(title) || ""} siteName={APP_NAME} />
|
||||
{/* TODO */}
|
||||
{/* <SEOHead title={t(title) || ""} siteName={APP_NAME} /> */}
|
||||
{site?.data?.metadata?.content?.css && (
|
||||
<link
|
||||
type="text/css"
|
||||
|
|
@ -212,7 +214,7 @@ export function DashboardLayout({
|
|||
const active =
|
||||
link.href &&
|
||||
link.isActive({
|
||||
pathname: router.asPath,
|
||||
pathname: pathname,
|
||||
href: link.href,
|
||||
})
|
||||
return (
|
||||
|
|
@ -313,7 +315,7 @@ export function DashboardLayout({
|
|||
const active =
|
||||
link.href &&
|
||||
link.isActive({
|
||||
pathname: router.asPath,
|
||||
pathname: pathname,
|
||||
href: link.href,
|
||||
})
|
||||
return (
|
||||
|
|
@ -1,38 +1,23 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import type { ReactElement } from "react"
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
|
||||
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { CSB_SCAN, DISCORD_LINK, GITHUB_LINK, TWITTER_LINK } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { useGetShowcase } from "~/queries/home"
|
||||
import { useGetPagesBySite } from "~/queries/page"
|
||||
import { useGetSite, useGetStat, useGetTips } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function SubdomainIndex() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const site = useGetSite(subdomain)
|
||||
const characterId = site.data?.characterId
|
||||
const stat = useGetStat({
|
||||
|
|
@ -280,7 +265,3 @@ export default function SubdomainIndex() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
SubdomainIndex.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Dashboard">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"use client"
|
||||
|
||||
import { PagesManager } from "~/components/dashboard/PagesManager"
|
||||
|
||||
export default function SubdomainPages() {
|
||||
return <PagesManager isPost={false} />
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"use client"
|
||||
|
||||
import { PagesManager } from "~/components/dashboard/PagesManager"
|
||||
|
||||
export default function SubdomainPosts() {
|
||||
return <PagesManager isPost={true} />
|
||||
}
|
||||
|
|
@ -1,34 +1,19 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import type { ReactElement } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
import { MonacoEditor } from "~/components/common/Monaco"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { FieldLabel } from "~/components/ui/FieldLabel"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function SettingsCSSPage() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
|
|
@ -132,7 +117,3 @@ export default function SettingsCSSPage() {
|
|||
</SettingsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
SettingsCSSPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Site Settings">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { useParams, useRouter } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import type { ReactElement } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
|
|
@ -11,8 +10,6 @@ import {
|
|||
useUpgradeEmailAccountModal,
|
||||
} from "@crossbell/connect-kit"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
|
|
@ -20,30 +17,19 @@ import { UniLink } from "~/components/ui/UniLink"
|
|||
import { useUserRole } from "~/hooks/useUserRole"
|
||||
import { OUR_DOMAIN } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { checkDomain, getSite } from "~/models/site.model"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function SettingsDomainsPage() {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
const userRole = useUserRole(subdomain)
|
||||
const { t } = useTranslation("dashboard")
|
||||
const router = useRouter()
|
||||
|
||||
const isEmailAccount = useAccountState(
|
||||
(s) => s.computed.account?.type === "email",
|
||||
|
|
@ -312,7 +298,3 @@ export default function SettingsDomainsPage() {
|
|||
</SettingsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
SettingsDomainsPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Site Settings">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,38 +1,22 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import type { ReactElement } from "react"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { ImageUploader } from "~/components/ui/ImageUploader"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { toIPFS } from "~/lib/ipfs-parser"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function SiteSettingsGeneralPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
|
|
@ -224,8 +208,8 @@ export default function SiteSettingsGeneralPage() {
|
|||
help={
|
||||
<p>
|
||||
<Trans i18nKey="Integrate Umami Cloud Analytics" ns="dashboard">
|
||||
Integrate Umami Cloud Analytics into your site. You can follow the
|
||||
instructions{" "}
|
||||
Integrate Umami Cloud Analytics into your site. You can follow
|
||||
the instructions{" "}
|
||||
<UniLink
|
||||
className="underline"
|
||||
href="https://umami.is/docs/collect-data"
|
||||
|
|
@ -259,7 +243,3 @@ export default function SiteSettingsGeneralPage() {
|
|||
</SettingsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
SiteSettingsGeneralPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Site Settings">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,34 +1,19 @@
|
|||
"use client"
|
||||
|
||||
import equal from "fast-deep-equal"
|
||||
import { nanoid } from "nanoid"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useParams } from "next/navigation"
|
||||
import { ChangeEvent, FormEvent, useEffect, useMemo, useState } from "react"
|
||||
import type { ReactElement } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
import { ReactSortable } from "react-sortablejs"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { SiteNavigationItem } from "~/lib/types"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
type UpdateItem = (id: string, newItem: Partial<SiteNavigationItem>) => void
|
||||
|
||||
type RemoveItem = (id: string) => void
|
||||
|
|
@ -80,9 +65,8 @@ const SortableNavigationItem: React.FC<{
|
|||
}
|
||||
|
||||
export default function SiteSettingsNavigationPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
|
|
@ -211,7 +195,3 @@ export default function SiteSettingsNavigationPage() {
|
|||
</SettingsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
SiteSettingsNavigationPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Site Settings">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import type { ReactElement } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
import {
|
||||
|
|
@ -12,15 +11,13 @@ import {
|
|||
import { Dialog } from "@headlessui/react"
|
||||
|
||||
import { CharacterCard } from "~/components/common/CharacterCard"
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { useUserRole } from "~/hooks/useUserRole"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import {
|
||||
useAddOperator,
|
||||
useGetOperators,
|
||||
|
|
@ -28,18 +25,6 @@ import {
|
|||
useRemoveOperator,
|
||||
} from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
type RemoveItem = (operator: string) => void
|
||||
|
||||
const SortableNavigationItem: React.FC<{
|
||||
|
|
@ -78,9 +63,8 @@ const SortableNavigationItem: React.FC<{
|
|||
}
|
||||
|
||||
export default function SettingsOperatorPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
const addOperator = useAddOperator()
|
||||
const removeOperator = useRemoveOperator()
|
||||
|
|
@ -275,7 +259,3 @@ export default function SettingsOperatorPage() {
|
|||
</SettingsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
SettingsOperatorPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Site Settings">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,35 +1,20 @@
|
|||
"use client"
|
||||
|
||||
import equal from "fast-deep-equal"
|
||||
import { nanoid } from "nanoid"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useParams } from "next/navigation"
|
||||
import { ChangeEvent, FormEvent, useEffect, useMemo, useState } from "react"
|
||||
import type { ReactElement } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
import { ReactSortable } from "react-sortablejs"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
|
||||
import { Platform } from "~/components/site/Platform"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { useGetSite, useUpdateSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
type Item = {
|
||||
identity: string
|
||||
platform: string
|
||||
|
|
@ -90,9 +75,8 @@ const SortableNavigationItem: React.FC<{
|
|||
}
|
||||
|
||||
export default function SiteSettingsNavigationPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
const updateSite = useUpdateSite()
|
||||
const site = useGetSite(subdomain)
|
||||
|
|
@ -251,7 +235,3 @@ export default function SiteSettingsNavigationPage() {
|
|||
</SettingsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
SiteSettingsNavigationPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Site Settings">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { GetServerSideProps } from "next"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import type { ReactElement } from "react"
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
|
||||
import {
|
||||
useAccountBalance,
|
||||
|
|
@ -9,32 +8,19 @@ import {
|
|||
useWalletClaimCSBModal,
|
||||
} from "@crossbell/connect-kit"
|
||||
|
||||
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
|
||||
import { getServerSideProps as getLayoutServerSideProps } from "~/components/dashboard/DashboardLayout.server"
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { MIRA_LINK } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { serverSidePropsHandler } from "~/lib/server-side-props"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { useGetMiraBalance, useGetSite } from "~/queries/site"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
|
||||
async (ctx) => {
|
||||
const { props: layoutProps } = await getLayoutServerSideProps(ctx)
|
||||
|
||||
return {
|
||||
props: {
|
||||
...layoutProps,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default function TokensPage() {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation(["dashboard", "index"])
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const { t } = useTranslation("dashboard")
|
||||
const { t: indexT } = useTranslation("index")
|
||||
const subdomain = params?.subdomain as string
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
const miraBalance = useGetMiraBalance(site.data?.characterId)
|
||||
|
|
@ -138,7 +124,7 @@ export default function TokensPage() {
|
|||
<DashboardMain title="Tokens">
|
||||
<div className="min-w-[270px] max-w-screen-lg flex flex-col space-y-8">
|
||||
<div className="text-sm text-zinc-500 leading-relaxed">
|
||||
{t("features.Earn.description", { ns: "index" })}
|
||||
{indexT("features.Earn.description")}
|
||||
</div>
|
||||
{tokens.map((token) => {
|
||||
return (
|
||||
|
|
@ -161,7 +147,3 @@ export default function TokensPage() {
|
|||
</DashboardMain>
|
||||
)
|
||||
}
|
||||
|
||||
TokensPage.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout title="Tokens">{page}</DashboardLayout>
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
import {
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { SITE_URL } from "~/lib/env"
|
||||
import { parsePost } from "~/lib/json-feed"
|
||||
import { NextServerResponse } from "~/lib/server-helper"
|
||||
import { ExpandedNote } from "~/lib/types"
|
||||
import { getFeed } from "~/models/home.model"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const searchParams = new URLSearchParams(request.url.split("?")[1])
|
||||
|
||||
const feed = await getFeed({
|
||||
type: "hot",
|
||||
daysInterval: parseInt(searchParams.get("interval") || "0"),
|
||||
})
|
||||
|
||||
const data = {
|
||||
version: "https://jsonfeed.org/version/1",
|
||||
title: "xLog Latest",
|
||||
icon: "https://ipfs.4everland.xyz/ipfs/bafkreigxdnr5lvtjxqin5upquomrti2s77hlgtjy5zaeu43uhpny75rbga",
|
||||
home_page_url: `${SITE_URL}/activities`,
|
||||
feed_url: `${SITE_URL}/feed/latest`,
|
||||
items: feed?.list?.map((post: ExpandedNote) => parsePost(post)),
|
||||
}
|
||||
|
||||
const format = searchParams.get("format") === "xml" ? "xml" : "json"
|
||||
|
||||
const res = new NextServerResponse()
|
||||
return res.status(200).rss(data, format)
|
||||
}
|
||||
|
|
@ -1,15 +1,10 @@
|
|||
// @ts-ignore
|
||||
import jsonfeedToRSS from "jsonfeed-to-rss"
|
||||
import { GetServerSideProps } from "next"
|
||||
|
||||
import { SITE_URL } from "~/lib/env"
|
||||
import { parsePost, setHeader } from "~/lib/json-feed"
|
||||
import { parsePost } from "~/lib/json-feed"
|
||||
import { NextServerResponse } from "~/lib/server-helper"
|
||||
import { ExpandedNote } from "~/lib/types"
|
||||
import { getFeed } from "~/models/home.model"
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
setHeader(ctx)
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const feed = await getFeed({
|
||||
type: "latest",
|
||||
})
|
||||
|
|
@ -23,16 +18,11 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|||
items: feed?.list?.map((post: ExpandedNote) => parsePost(post)),
|
||||
}
|
||||
|
||||
ctx.res.write(
|
||||
ctx.query.format === "xml" ? jsonfeedToRSS(data) : JSON.stringify(data),
|
||||
)
|
||||
ctx.res.end()
|
||||
const format =
|
||||
new URLSearchParams(request.url.split("?")[1]).get("format") === "xml"
|
||||
? "xml"
|
||||
: "json"
|
||||
|
||||
return {
|
||||
props: {},
|
||||
}
|
||||
const res = new NextServerResponse()
|
||||
return res.status(200).rss(data, format)
|
||||
}
|
||||
|
||||
const LatestFeed: React.FC = () => null
|
||||
|
||||
export default LatestFeed
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import "aplayer-react/dist/index.css"
|
||||
import { dir } from "i18next"
|
||||
import { Metadata } from "next"
|
||||
import { Toaster } from "react-hot-toast"
|
||||
|
||||
import "~/css/main.css"
|
||||
import { useAcceptLang } from "~/hooks/useAcceptLang"
|
||||
import { APP_DESCRIPTION, APP_NAME, APP_SLOGAN, SITE_URL } from "~/lib/env"
|
||||
|
||||
import Providers from "./providers"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `${APP_NAME} - ${APP_SLOGAN}`,
|
||||
description: APP_DESCRIPTION,
|
||||
applicationName: APP_NAME,
|
||||
generator: APP_NAME,
|
||||
keywords: [
|
||||
"blog",
|
||||
"xlog",
|
||||
"blockchain",
|
||||
"ethereum",
|
||||
"web3",
|
||||
"dapp",
|
||||
"crypto",
|
||||
],
|
||||
themeColor: "#ffffff",
|
||||
alternates: {
|
||||
types: {
|
||||
"application/rss+xml": [
|
||||
{ url: "/feed/latest?format=xml", title: "xLog Latest" },
|
||||
{ url: "/feed/hottest?interval=0&format=xml", title: "xLog Hottest" },
|
||||
{
|
||||
url: "/feed/hottest?interval=1&format=xml",
|
||||
title: "xLog Hottest of the Day",
|
||||
},
|
||||
{
|
||||
url: "/feed/hottest?interval=7&format=xml",
|
||||
title: "xLog Hottest of the Week",
|
||||
},
|
||||
{
|
||||
url: "/feed/hottest?interval=30&format=xml",
|
||||
title: "xLog Hottest of the Month",
|
||||
},
|
||||
],
|
||||
"application/feed+json": [
|
||||
{ url: "/feed/latest", title: "xLog Latest" },
|
||||
{ url: "/feed/hottest?interval=0", title: "xLog Hottest" },
|
||||
{ url: "/feed/hottest?interval=1", title: "xLog Hottest of the Day" },
|
||||
{ url: "/feed/hottest?interval=7", title: "xLog Hottest of the Week" },
|
||||
{
|
||||
url: "/feed/hottest?interval=30",
|
||||
title: "xLog Hottest of the Month",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
icons: `${SITE_URL}/assets/logo.svg`,
|
||||
openGraph: {
|
||||
siteName: `${APP_NAME} - ${APP_SLOGAN}`,
|
||||
description: APP_DESCRIPTION,
|
||||
images: [`${SITE_URL}/assets/logo.svg`],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: `${APP_NAME} - ${APP_SLOGAN}`,
|
||||
description: APP_DESCRIPTION,
|
||||
images: [`${SITE_URL}/assets/logo.svg`],
|
||||
site: "@_xLog",
|
||||
creator: "@_xLog",
|
||||
},
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const lang = useAcceptLang()
|
||||
|
||||
return (
|
||||
<html lang={lang} dir={dir(lang)}>
|
||||
<body>
|
||||
<Providers lang={lang}>{children}</Providers>
|
||||
<Toaster />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
import "aplayer-react/dist/index.css"
|
||||
import { Network } from "crossbell.js"
|
||||
import { appWithTranslation } from "next-i18next"
|
||||
import NextNProgress from "nextjs-progressbar"
|
||||
import { Toaster } from "react-hot-toast"
|
||||
import { AppPropsWithLayout } from "types/next"
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { WagmiConfig, createClient } from "wagmi"
|
||||
|
||||
import {
|
||||
|
|
@ -14,30 +11,21 @@ import {
|
|||
NotificationModal,
|
||||
NotificationModalColorScheme,
|
||||
} from "@crossbell/notification"
|
||||
import { Hydrate, QueryClient } from "@tanstack/react-query"
|
||||
import { QueryClient } from "@tanstack/react-query"
|
||||
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"
|
||||
|
||||
import "~/css/main.css"
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { useDarkMode } from "~/hooks/useDarkMode"
|
||||
import { useMobileLayout } from "~/hooks/useMobileLayout"
|
||||
import { APP_NAME, IPFS_GATEWAY } from "~/lib/env"
|
||||
import { useNProgress } from "~/hooks/useNProgress"
|
||||
import { APP_NAME } from "~/lib/env"
|
||||
import { toGateway } from "~/lib/ipfs-parser"
|
||||
import { createIDBPersister } from "~/lib/persister.client"
|
||||
import { urlComposer } from "~/lib/url-composer"
|
||||
|
||||
Network.setIpfsGateway(IPFS_GATEWAY)
|
||||
import { LangProvider } from "~/providers/LangProvider"
|
||||
|
||||
const wagmiClient = createClient(getDefaultClientConfig({ appName: APP_NAME }))
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const persister = createIDBPersister()
|
||||
|
||||
const colorScheme: NotificationModalColorScheme = {
|
||||
|
|
@ -47,11 +35,27 @@ const colorScheme: NotificationModalColorScheme = {
|
|||
border: `var(--border-color)`,
|
||||
}
|
||||
|
||||
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
const getLayout = Component.getLayout ?? ((page) => page)
|
||||
|
||||
export default function Providers({
|
||||
children,
|
||||
lang,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
lang: string
|
||||
}) {
|
||||
useDarkMode()
|
||||
useMobileLayout()
|
||||
useNProgress()
|
||||
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
return (
|
||||
<WagmiConfig client={wagmiClient}>
|
||||
|
|
@ -78,31 +82,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
|||
signInStrategy="simple"
|
||||
ignoreWalletDisconnectEvent={true}
|
||||
>
|
||||
<Hydrate state={pageProps.dehydratedState}>
|
||||
{/* <ReactQueryDevtools /> */}
|
||||
<NextNProgress
|
||||
options={{ easing: "linear", speed: 500, trickleSpeed: 100 }}
|
||||
/>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
<Toaster />
|
||||
<NotificationModal colorScheme={colorScheme} />
|
||||
</Hydrate>
|
||||
<LangProvider lang={lang}>{children}</LangProvider>
|
||||
<NotificationModal colorScheme={colorScheme} />
|
||||
</ConnectKitProvider>
|
||||
</PersistQueryClientProvider>
|
||||
</WagmiConfig>
|
||||
)
|
||||
}
|
||||
|
||||
// Only uncomment this method if you have blocking data requirements for
|
||||
// every single page in your application. This disables the ability to
|
||||
// perform automatic static optimization, causing every page in your app to
|
||||
// be server-side rendered.
|
||||
//
|
||||
// MyApp.getInitialProps = async (appContext) => {
|
||||
// // calls page's `getInitialProps` and fills `appProps.pageProps`
|
||||
// const appProps = await App.getInitialProps(appContext);
|
||||
//
|
||||
// return { ...appProps }
|
||||
// }
|
||||
|
||||
export default appWithTranslation(MyApp)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { MetadataRoute } from "next"
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
disallow: ["/dashboard/", "/preview/", "/api/"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { Hydrate, dehydrate } from "@tanstack/react-query"
|
||||
|
||||
import { SitePage } from "~/components/site/SitePage"
|
||||
import { SITE_URL } from "~/lib/env"
|
||||
import { useTranslation } from "~/lib/i18n"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { fetchGetPage } from "~/queries/page.server"
|
||||
import { fetchGetSite } from "~/queries/site.server"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
slug: string
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
const page = await fetchGetPage(
|
||||
{
|
||||
characterId: site?.characterId,
|
||||
slug: params.slug,
|
||||
useStat: true,
|
||||
},
|
||||
queryClient,
|
||||
)
|
||||
|
||||
const title = `${page?.metadata?.content?.title} - ${
|
||||
site?.metadata?.content?.name || site?.handle
|
||||
}`
|
||||
const description = page?.metadata?.content?.summary
|
||||
const siteImages =
|
||||
site?.metadata?.content?.avatars || `${SITE_URL}/assets/logo.svg`
|
||||
const images = page?.metadata?.content?.cover || siteImages
|
||||
const twitterCreator =
|
||||
"@" +
|
||||
site?.metadata?.content?.connected_accounts
|
||||
?.find((account) => account?.endsWith?.("@twitter"))
|
||||
?.match(/csb:\/\/account:([^@]+)@twitter/)?.[1]
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
siteName: title,
|
||||
description,
|
||||
images,
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
site: "@_xLog",
|
||||
creator: twitterCreator,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SitePagePage({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
slug: string
|
||||
}
|
||||
}) {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
const page = await fetchGetPage(
|
||||
{
|
||||
characterId: site?.characterId,
|
||||
slug: params.slug,
|
||||
useStat: true,
|
||||
},
|
||||
queryClient,
|
||||
)
|
||||
|
||||
if (
|
||||
!page ||
|
||||
new Date(page!.metadata?.content?.date_published || "") > new Date()
|
||||
) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
const { t } = await useTranslation("site")
|
||||
|
||||
return (
|
||||
<Hydrate state={dehydratedState}>
|
||||
<SitePage page={page || undefined} site={site || undefined} t={t} />
|
||||
</Hydrate>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { Metadata } from "next"
|
||||
|
||||
import { Hydrate, dehydrate } from "@tanstack/react-query"
|
||||
|
||||
import { SiteArchives } from "~/components/site/SiteArchives"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
import { prefetchGetPagesBySite } from "~/queries/page.server"
|
||||
import { fetchGetSite } from "~/queries/site.server"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
const title = `Archives - ${site?.metadata?.content?.name || site?.handle}`
|
||||
|
||||
return {
|
||||
title,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SiteArchivesPage({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
}
|
||||
}) {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
await prefetchGetPagesBySite(
|
||||
{
|
||||
characterId: site?.characterId,
|
||||
type: "post",
|
||||
visibility: PageVisibilityEnum.Published,
|
||||
limit: 100,
|
||||
},
|
||||
queryClient,
|
||||
)
|
||||
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
return (
|
||||
<Hydrate state={dehydratedState}>
|
||||
<SiteArchives />
|
||||
</Hydrate>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
import { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { Hydrate, dehydrate } from "@tanstack/react-query"
|
||||
|
||||
import { BlockchainInfo } from "~/components/common/BlockchainInfo"
|
||||
import { Style } from "~/components/common/Style"
|
||||
import { BackToTopFAB } from "~/components/site/BackToTopFAB"
|
||||
import { SiteFooter } from "~/components/site/SiteFooter"
|
||||
import { SiteHeader } from "~/components/site/SiteHeader"
|
||||
import { FABContainer } from "~/components/ui/FAB"
|
||||
import { SITE_URL } from "~/lib/env"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { cn } from "~/lib/utils"
|
||||
import {
|
||||
fetchGetSite,
|
||||
prefetchGetSiteSubscriptions,
|
||||
prefetchGetSiteToSubscriptions,
|
||||
} from "~/queries/site.server"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
const title = site?.metadata?.content?.name || site?.handle
|
||||
const description = site?.metadata?.content?.bio
|
||||
const images =
|
||||
site?.metadata?.content?.avatars || `${SITE_URL}/assets/logo.svg`
|
||||
const twitterCreator =
|
||||
"@" +
|
||||
site?.metadata?.content?.connected_accounts
|
||||
?.find((account) => account?.endsWith?.("@twitter"))
|
||||
?.match(/csb:\/\/account:([^@]+)@twitter/)?.[1]
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
themeColor: "#ffffff", // TODO
|
||||
alternates: {
|
||||
types: {
|
||||
"application/rss+xml": [
|
||||
{ url: "/feed?format=xml", title },
|
||||
{ url: "/feed/comments?format=xml", title: `Comments on ${title}` },
|
||||
],
|
||||
"application/feed+json": [
|
||||
{ url: "/feed", title },
|
||||
{ url: "/feed/comments", title: `Comments on ${title}` },
|
||||
],
|
||||
},
|
||||
},
|
||||
icons: images,
|
||||
openGraph: {
|
||||
siteName: title,
|
||||
description,
|
||||
images,
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images,
|
||||
site: "@_xLog",
|
||||
creator: twitterCreator,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SiteLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
params: {
|
||||
site: string
|
||||
slug?: string
|
||||
tag?: string
|
||||
}
|
||||
}) {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
let page
|
||||
if (site?.characterId) {
|
||||
await Promise.all([
|
||||
prefetchGetSiteSubscriptions(
|
||||
{
|
||||
characterId: site.characterId,
|
||||
},
|
||||
queryClient,
|
||||
),
|
||||
prefetchGetSiteToSubscriptions(
|
||||
{
|
||||
characterId: site.characterId,
|
||||
},
|
||||
queryClient,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
return (
|
||||
<Hydrate state={dehydratedState}>
|
||||
<div
|
||||
className={
|
||||
cn()
|
||||
// TODO
|
||||
// {
|
||||
// "xlog-user": true,
|
||||
// "xlog-user-login": isConnected,
|
||||
// "xlog-user-site-owner": userRole?.data === "owner",
|
||||
// "xlog-user-site-operator": userRole?.data === "operator",
|
||||
// "xlog-user-site-follower": subscription?.data,
|
||||
// "xlog-user-post-liker": isLiked,
|
||||
// "xlog-user-post-minter": isMint?.data?.count,
|
||||
// },
|
||||
// `xlog-page-${type}`,
|
||||
}
|
||||
>
|
||||
<Style content={site?.metadata?.content?.css} />
|
||||
{site && <SiteHeader handle={params.site} />}
|
||||
<div
|
||||
className={cn(
|
||||
"max-w-screen-md mx-auto px-5 pt-12 relative",
|
||||
// page && `xlog-post-id-${page.characterId}-${page.noteId}`,
|
||||
// page?.metadata?.content?.tags?.map((tag) => `xlog-post-tag-${tag}`),
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{site && (
|
||||
<div className="max-w-screen-md mx-auto pt-12 pb-10">
|
||||
<BlockchainInfo site={site} page={page || undefined} />
|
||||
</div>
|
||||
)}
|
||||
<SiteFooter site={site || undefined} />
|
||||
|
||||
<FABContainer>
|
||||
<BackToTopFAB />
|
||||
</FABContainer>
|
||||
</div>
|
||||
</Hydrate>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import { Metadata } from "next"
|
||||
import Script from "next/script"
|
||||
import type { Asset } from "unidata.js"
|
||||
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { UniMedia } from "~/components/ui/UniMedia"
|
||||
import { useTranslation } from "~/lib/i18n"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { fetchGetSite, getNFTs } from "~/queries/site.server"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
const title = `NFT - ${site?.metadata?.content?.name || site?.handle}`
|
||||
|
||||
return {
|
||||
title,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SiteNFTPage({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
}
|
||||
}) {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
const { t } = await useTranslation("common")
|
||||
|
||||
const nfts = await getNFTs(site?.owner)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
type="module"
|
||||
src="https://cdn.jsdelivr.net/npm/@google/model-viewer/dist/model-viewer.min.js"
|
||||
></Script>
|
||||
<h2 className="text-xl font-bold page-title">NFT {t("Showcase")}</h2>
|
||||
<div className="mt-8">
|
||||
<div className="grid grid-cols-3 md:grid-cols-4 gap-10">
|
||||
{nfts.list
|
||||
?.filter((nft: Asset) => nft.items?.[0]?.address)
|
||||
.map((nft: Asset) => (
|
||||
<UniLink
|
||||
key={nft.metadata?.proof}
|
||||
className="xlog-nft flex items-center flex-col"
|
||||
href={nft.related_urls?.[nft.related_urls.length - 1]}
|
||||
data-collection={nft.metadata?.collection_address}
|
||||
data-network={nft.metadata?.network}
|
||||
data-token-id={nft.metadata?.token_id}
|
||||
data-name={nft.name}
|
||||
>
|
||||
<UniMedia
|
||||
src={nft.items?.[0]?.address || ""}
|
||||
mime_type={nft.items?.[0].mime_type}
|
||||
/>
|
||||
<div className="text-xs mt-2 text-center font-medium">
|
||||
{nft.name}
|
||||
</div>
|
||||
</UniLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { SitePage } from "~/components/site/SitePage"
|
||||
import { SITE_URL } from "~/lib/env"
|
||||
import { useTranslation } from "~/lib/i18n"
|
||||
|
||||
export default async function NotFound() {
|
||||
const { t } = await useTranslation("site")
|
||||
|
||||
return (
|
||||
<SitePage
|
||||
page={
|
||||
{
|
||||
metadata: {
|
||||
content: {
|
||||
title: t("404 - Whoops, this page is gone."),
|
||||
content: `
|
||||
- [Back to Home](/)
|
||||
- [All posts](/archives)
|
||||
|
||||
`,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
}
|
||||
t={t}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { Hydrate, dehydrate } from "@tanstack/react-query"
|
||||
|
||||
import SiteHome from "~/components/site/SiteHome"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
import { prefetchGetPagesBySite } from "~/queries/page.server"
|
||||
import { fetchGetSite } from "~/queries/site.server"
|
||||
|
||||
async function SiteIndexPage({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
}
|
||||
}) {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
await prefetchGetPagesBySite(
|
||||
{
|
||||
characterId: site?.characterId,
|
||||
type: "post",
|
||||
visibility: PageVisibilityEnum.Published,
|
||||
useStat: true,
|
||||
},
|
||||
queryClient,
|
||||
)
|
||||
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
return (
|
||||
<Hydrate state={dehydratedState}>
|
||||
<SiteHome handle={params.site} />
|
||||
</Hydrate>
|
||||
)
|
||||
}
|
||||
|
||||
export default SiteIndexPage
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
"use client"
|
||||
|
||||
import { SitePage } from "~/components/site/SitePage"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { useGetPage } from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
export default function SitePreviewPage({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
previewId: string
|
||||
}
|
||||
}) {
|
||||
const site = useGetSite(params.site)
|
||||
|
||||
const page = useGetPage({
|
||||
characterId: site.data?.characterId,
|
||||
noteId:
|
||||
params.previewId && /\d+/.test(params.previewId)
|
||||
? +params.previewId
|
||||
: undefined,
|
||||
slug: params.previewId,
|
||||
useStat: true,
|
||||
})
|
||||
|
||||
const { t } = useTranslation("site")
|
||||
|
||||
return (
|
||||
<SitePage
|
||||
page={page.data || undefined}
|
||||
site={site.data || undefined}
|
||||
t={t}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { Metadata } from "next"
|
||||
|
||||
import { SearchInput } from "~/components/common/SearchInput"
|
||||
import { SiteSearch } from "~/components/site/SiteSearch"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { fetchGetSite } from "~/queries/site.server"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
}
|
||||
searchParams: {
|
||||
[key: string]: string | string[] | undefined
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
const title = `Search: ${searchParams.q} - ${
|
||||
site?.metadata?.content?.name || site?.handle
|
||||
}`
|
||||
|
||||
return {
|
||||
title,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SiteSearchPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="sm:-mx-5">
|
||||
<SearchInput />
|
||||
</div>
|
||||
<SiteSearch />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { Metadata } from "next"
|
||||
|
||||
import { Hydrate, dehydrate } from "@tanstack/react-query"
|
||||
|
||||
import { SiteArchives } from "~/components/site/SiteArchives"
|
||||
import getQueryClient from "~/lib/query-client"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
import { prefetchGetPagesBySite } from "~/queries/page.server"
|
||||
import { fetchGetSite } from "~/queries/site.server"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
tag: string
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
|
||||
params.tag = decodeURIComponent(params.tag)
|
||||
const title = `Tag: ${params.tag} - ${
|
||||
site?.metadata?.content?.name || site?.handle
|
||||
}`
|
||||
|
||||
return {
|
||||
title,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SiteTagPage({
|
||||
params,
|
||||
}: {
|
||||
params: {
|
||||
site: string
|
||||
tag: string
|
||||
}
|
||||
}) {
|
||||
params.tag = decodeURIComponent(params.tag)
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
const site = await fetchGetSite(params.site, queryClient)
|
||||
await prefetchGetPagesBySite(
|
||||
{
|
||||
characterId: site?.characterId,
|
||||
type: "post",
|
||||
visibility: PageVisibilityEnum.Published,
|
||||
limit: 100,
|
||||
tags: [params.tag],
|
||||
},
|
||||
queryClient,
|
||||
)
|
||||
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
return (
|
||||
<Hydrate state={dehydratedState}>
|
||||
<SiteArchives />
|
||||
</Hydrate>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useState } from "react"
|
||||
|
||||
import { Indicator } from "@mantine/core"
|
||||
|
|
@ -6,6 +5,7 @@ import { Indicator } from "@mantine/core"
|
|||
import { AchievementModal } from "~/components/common/AchievementModal"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import type { AchievementSection } from "~/models/site.model"
|
||||
|
||||
export const Badge = ({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import Tilt from "react-parallax-tilt"
|
||||
|
||||
import { Modal, Stepper } from "@mantine/core"
|
||||
|
|
@ -8,6 +7,7 @@ import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
|
|||
import { Button } from "~/components/ui/Button"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
import type { AchievementSection } from "~/models/site.model"
|
||||
import { useMintAchievement } from "~/queries/site"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
import { Disclosure } from "@headlessui/react"
|
||||
|
||||
import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
|
||||
import { CSB_SCAN } from "~/lib/env"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { toCid, toGateway, toIPFS } from "~/lib/ipfs-parser"
|
||||
import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
|
@ -13,7 +14,7 @@ export const BlockchainInfo: React.FC<{
|
|||
site?: ExpandedCharacter
|
||||
page?: ExpandedNote
|
||||
}> = ({ site, page }) => {
|
||||
const { t } = useTranslation(["common", "site"])
|
||||
const { t } = useTranslation("common")
|
||||
|
||||
const ipfs = (page ? page.metadata?.uri : site?.metadata?.uri) || ""
|
||||
const greenfieldId = useGetGreenfieldId(toCid(ipfs))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
|
||||
import { FollowingButton } from "~/components/common/FollowingButton"
|
||||
import { FollowingCount } from "~/components/common/FollowingCount"
|
||||
import { Titles } from "~/components/common/Titles"
|
||||
import { Avatar } from "~/components/ui/Avatar"
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { useGetCharacterCard } from "~/queries/site"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import React, { useCallback, useState } from "react"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { Modal } from "~/components/ui/Modal"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { ExpandedCharacter } from "~/lib/types"
|
||||
|
||||
import { Button } from "../ui/Button"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { CommentInput } from "~/components/common/CommentInput"
|
||||
import { CommentItem } from "~/components/common/CommentItem"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { ExpandedNote } from "~/lib/types"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { useGetComments } from "~/queries/page"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { CharacterEntity, NoteEntity } from "crossbell.js"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
|
|
@ -9,6 +8,7 @@ import { Popover } from "@headlessui/react"
|
|||
import { Avatar } from "~/components/ui/Avatar"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { useCommentPage, useUpdateComment } from "~/queries/page"
|
||||
|
||||
import { EmojiPicker } from "./EmojiPicker"
|
||||
|
|
@ -33,7 +33,7 @@ export const CommentInput: React.FC<{
|
|||
const account = useAccountState((s) => s.computed.account)
|
||||
const commentPage = useCommentPage()
|
||||
const updateComment = useUpdateComment()
|
||||
const { t } = useTranslation(["common", "site"])
|
||||
const { t } = useTranslation("site")
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
|
|
@ -94,9 +94,7 @@ export const CommentInput: React.FC<{
|
|||
multiline
|
||||
maxLength={600}
|
||||
className="mb-2"
|
||||
placeholder={
|
||||
t("Write a comment on the blockchain", { ns: "site" }) || ""
|
||||
}
|
||||
placeholder={t("Write a comment on the blockchain") || ""}
|
||||
{...form.register("content", {})}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { CharacterEntity, NoteEntity } from "crossbell.js"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useState } from "react"
|
||||
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
|
|
@ -16,6 +17,7 @@ import { UniLink } from "~/components/ui/UniLink"
|
|||
import { useDate } from "~/hooks/useDate"
|
||||
import { CSB_SCAN } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
export const CommentItem: React.FC<{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
import React, { useEffect, useState } from "react"
|
||||
|
||||
import {
|
||||
|
|
@ -31,6 +32,7 @@ import { Button, type Variant, type VariantColor } from "~/components/ui/Button"
|
|||
import { Menu } from "~/components/ui/Menu"
|
||||
import { SITE_URL } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
type HeaderLinkType = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use client"
|
||||
|
||||
import { Switch } from "@headlessui/react"
|
||||
|
||||
import { useDarkModeSwitch, useIsDark } from "~/hooks/useDarkMode"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
import { useRefCallback } from "@crossbell/util-hooks"
|
||||
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { getSubscriptionsFromList } from "~/models/site.model"
|
||||
import { useSubscribeToSites } from "~/queries/site"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { useEffect } from "react"
|
||||
import { toast } from "react-hot-toast"
|
||||
|
||||
|
|
@ -6,6 +5,7 @@ import { Button } from "~/components/ui/Button"
|
|||
import type { Variant } from "~/components/ui/Button"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { SITE_URL } from "~/lib/env"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { ExpandedCharacter } from "~/lib/types"
|
||||
import { cn } from "~/lib/utils"
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useState } from "react"
|
||||
|
||||
import { CharacterList } from "~/components/common/CharacterList"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import {
|
||||
useGetSiteSubscriptions,
|
||||
useGetSiteToSubscriptions,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use client"
|
||||
|
||||
import Lottie, { type LottieRefCurrentProps } from "lottie-react"
|
||||
import React, { useRef } from "react"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
"use client"
|
||||
|
||||
import { MutableRefObject, useEffect, useMemo } from "react"
|
||||
import { scroller } from "react-scroll"
|
||||
|
||||
import { PostToc } from "~/components/site/PostToc"
|
||||
import { useCodeCopy } from "~/hooks/useCodeCopy"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { calculateElementTop, cn } from "~/lib/utils"
|
||||
import { renderPageContent } from "~/markdown"
|
||||
|
||||
export const PageContent: React.FC<{
|
||||
|
|
@ -42,10 +43,18 @@ export const PageContent: React.FC<{
|
|||
const hashChangeHandler = () => {
|
||||
const hash = decodeURIComponent(location.hash.slice(1))
|
||||
if (hash) {
|
||||
scroller.scrollTo(`user-content-${decodeURIComponent(hash)}`, {
|
||||
smooth: true,
|
||||
offset: -20,
|
||||
duration: 500,
|
||||
if (history.state?.preventScrollToToc) {
|
||||
history.state.preventScrollToToc = false
|
||||
return
|
||||
}
|
||||
const targetElement = document.querySelector(
|
||||
`#user-content-${decodeURIComponent(hash)}`,
|
||||
) as HTMLElement
|
||||
if (!targetElement) return
|
||||
|
||||
window.scrollTo({
|
||||
top: calculateElementTop(targetElement) - 20,
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useState } from "react"
|
||||
|
||||
import { PatronModal } from "~/components/common/PatronModal"
|
||||
import { Button } from "~/components/ui/Button"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { ExpandedCharacter } from "~/lib/types"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import confetti from "canvas-confetti"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { toast } from "react-hot-toast"
|
||||
|
||||
|
|
@ -11,6 +10,7 @@ import { Button } from "~/components/ui/Button"
|
|||
import { Modal } from "~/components/ui/Modal"
|
||||
import { CSB_SCAN, MIRA_LINK } from "~/lib/env"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
|
||||
import { useGetTips, useTipCharacter } from "~/queries/site"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import confetti from "canvas-confetti"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
|
||||
import { CharacterList } from "~/components/common/CharacterList"
|
||||
|
|
@ -7,6 +8,7 @@ import { Modal } from "~/components/ui/Modal"
|
|||
import { Tooltip } from "~/components/ui/Tooltip"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { CSB_SCAN } from "~/lib/env"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
import {
|
||||
useCheckLike,
|
||||
|
|
@ -24,7 +26,7 @@ export const ReactionLike: React.FC<{
|
|||
noteId?: number
|
||||
}> = ({ size, characterId, noteId }) => {
|
||||
const toggleLikePage = useToggleLikePage()
|
||||
const { t } = useTranslation("common")
|
||||
const { t, i18n } = useTranslation("common")
|
||||
|
||||
const [isLikeOpen, setIsLikeOpen] = useState(false)
|
||||
const [isLikeListOpen, setIsLikeListOpen] = useState(false)
|
||||
|
|
@ -149,7 +151,7 @@ export const ReactionLike: React.FC<{
|
|||
title={t("Like successfully") || ""}
|
||||
>
|
||||
<div className="p-5">
|
||||
<Trans i18nKey="like stored">
|
||||
<Trans i18nKey="like stored" i18n={i18n}>
|
||||
Your like has been stored on the blockchain, view it on{" "}
|
||||
<UniLink
|
||||
className="text-accent"
|
||||
|
|
@ -181,7 +183,7 @@ export const ReactionLike: React.FC<{
|
|||
title={t("Confirm to revert")}
|
||||
>
|
||||
<div className="p-5">
|
||||
<Trans i18nKey="like revert">
|
||||
<Trans i18nKey="like revert" i18n={i18n}>
|
||||
Do you really want to revert this like action?
|
||||
</Trans>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import confetti from "canvas-confetti"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
|
|
@ -10,6 +11,7 @@ import { Modal } from "~/components/ui/Modal"
|
|||
import { Tooltip } from "~/components/ui/Tooltip"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { CSB_SCAN, CSB_XCHAR } from "~/lib/env"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { noopArr } from "~/lib/noop"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { useCheckMint, useGetMints, useMintPage } from "~/queries/page"
|
||||
|
|
@ -23,7 +25,7 @@ export const ReactionMint: React.FC<{
|
|||
characterId?: number
|
||||
}> = ({ size, noteId, characterId }) => {
|
||||
const mintPage = useMintPage()
|
||||
const { t } = useTranslation("common")
|
||||
const { t, i18n } = useTranslation("common")
|
||||
|
||||
const account = useAccountState((s) => s.computed.account)
|
||||
|
||||
|
|
@ -136,7 +138,7 @@ export const ReactionMint: React.FC<{
|
|||
title={t("Mint successfully") || ""}
|
||||
>
|
||||
<div className="p-5">
|
||||
<Trans i18nKey="mint stored">
|
||||
<Trans i18nKey="mint stored" i18n={i18n}>
|
||||
This post has been minted to NFT by you, view it on{" "}
|
||||
<UniLink
|
||||
className="text-accent"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
import { useMemo, useRef, useState } from "react"
|
||||
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
|
||||
import { PatronModal } from "~/components/common/PatronModal"
|
||||
import { Tooltip } from "~/components/ui/Tooltip"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { noopArr } from "~/lib/noop"
|
||||
import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
|
||||
import { useGetTips } from "~/queries/site"
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
import Head from "next/head"
|
||||
|
||||
import { SITE_URL } from "~/lib/env"
|
||||
|
||||
export const SEOHead: React.FC<{
|
||||
siteName: string
|
||||
title: string | undefined
|
||||
description?: string | null
|
||||
image?: string | null
|
||||
icon?: string | null
|
||||
site?: string
|
||||
}> = ({ siteName, title, description, image, icon, site }) => {
|
||||
return (
|
||||
<Head>
|
||||
<title>{title ? `${title} - ${siteName}` : `${siteName}`}</title>
|
||||
<meta name="og:site_name" content={siteName} />
|
||||
<meta name="og:title" content={title} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="description" content={description || ""} />
|
||||
<meta name="og:description" content={description || ""} />
|
||||
<meta name="twitter:description" content={description || ""} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
{image && (
|
||||
<>
|
||||
<meta name="og:image" content={new URL(image, SITE_URL).toString()} />
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content={new URL(image, SITE_URL).toString()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<link rel="icon" href={icon || `${SITE_URL}/assets/logo.svg`}></link>
|
||||
{site ? (
|
||||
<>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed"
|
||||
title={siteName}
|
||||
type="application/feed+json"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed?format=xml"
|
||||
title={siteName}
|
||||
type="application/rss+xml"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/comments"
|
||||
title={`Comments on ${siteName}`}
|
||||
type="application/feed+json"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/comments?format=xml"
|
||||
title={`Comments on ${siteName}`}
|
||||
type="application/rss+xml"
|
||||
></link>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/latest"
|
||||
title="xLog Latest"
|
||||
type="application/feed+json"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/latest?format=xml"
|
||||
title="xLog Latest"
|
||||
type="application/rss+xml"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/0"
|
||||
title="xLog Hot"
|
||||
type="application/feed+json"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/0?format=xml"
|
||||
title="xLog Hot"
|
||||
type="application/rss+xml"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/1"
|
||||
title="xLog Daily Hot"
|
||||
type="application/feed+json"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/1?format=xml"
|
||||
title="xLog Daily Hot"
|
||||
type="application/rss+xml"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/7"
|
||||
title="xLog Weekly Hot"
|
||||
type="application/feed+json"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/7?format=xml"
|
||||
title="xLog Weekly Hot"
|
||||
type="application/rss+xml"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/30"
|
||||
title="xLog Monthly Hot"
|
||||
type="application/feed+json"
|
||||
></link>
|
||||
<link
|
||||
rel="alternate"
|
||||
href="/feed/hottest/30?format=xml"
|
||||
title="xLog Monthly Hot"
|
||||
type="application/rss+xml"
|
||||
></link>
|
||||
</>
|
||||
)}
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
export const SearchInput: React.FC<{
|
||||
value?: string
|
||||
noBorder?: boolean
|
||||
onSubmit?: (value?: string) => void
|
||||
}> = ({ value, noBorder, onSubmit }) => {
|
||||
}> = ({ noBorder, onSubmit }) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation(["common", "site"])
|
||||
const searchParams = useSearchParams()
|
||||
const { t } = useTranslation("common")
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
content: value || "",
|
||||
content: searchParams?.get("q") || "",
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||
|
||||
import { languageDetector } from "~/lib/language-detector"
|
||||
|
||||
export const getServerSideProps = async (ctx: any) => {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(languageDetector(ctx), [
|
||||
"common",
|
||||
"dashboard",
|
||||
"index",
|
||||
"site",
|
||||
])),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
|
||||
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
export const DashboardMain: React.FC<{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { NoteMetadata } from "crossbell.js"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useState } from "react"
|
||||
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
|
||||
import { PageContent } from "../common/PageContent"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import { nanoid } from "nanoid"
|
||||
import { Trans, useTranslation } from "next-i18next"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/router"
|
||||
import {
|
||||
useParams,
|
||||
usePathname,
|
||||
useRouter,
|
||||
useSearchParams,
|
||||
} from "next/navigation"
|
||||
import { Fragment, useMemo, useState } from "react"
|
||||
|
||||
import { Menu } from "@headlessui/react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { Trans, useTranslation } from "~/lib/i18n/client"
|
||||
import { getPageVisibility } from "~/lib/page-helpers"
|
||||
import { readFiles } from "~/lib/read-files"
|
||||
import { setStorage } from "~/lib/storage"
|
||||
|
|
@ -28,19 +33,23 @@ import { PagesManagerMenu } from "./PagesManagerMenu"
|
|||
export const PagesManager: React.FC<{
|
||||
isPost: boolean
|
||||
}> = ({ isPost }) => {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const site = useGetSite(subdomain)
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const visibility = useMemo<PageVisibilityEnum>(
|
||||
() =>
|
||||
router.query.visibility
|
||||
? (router.query.visibility as PageVisibilityEnum)
|
||||
searchParams?.get("visibility")
|
||||
? (searchParams?.get("visibility") as PageVisibilityEnum)
|
||||
: PageVisibilityEnum.All,
|
||||
[router.query.visibility],
|
||||
[searchParams],
|
||||
)
|
||||
|
||||
const { t } = useTranslation(["dashboard", "site"])
|
||||
const { t } = useTranslation("dashboard")
|
||||
const { t: siteT } = useTranslation("site")
|
||||
const date = useDate()
|
||||
|
||||
const pages = useGetPagesBySite({
|
||||
|
|
@ -75,16 +84,14 @@ export const PagesManager: React.FC<{
|
|||
text: item.text,
|
||||
onClick: () => {
|
||||
const newQuery: Record<string, any> = {
|
||||
...router.query,
|
||||
...searchParams,
|
||||
visibility: item.value,
|
||||
}
|
||||
if (item.value === PageVisibilityEnum.All) {
|
||||
delete newQuery["visibility"]
|
||||
}
|
||||
const search = new URLSearchParams(newQuery).toString()
|
||||
router.push({
|
||||
search,
|
||||
})
|
||||
router.push(pathname + "?" + search)
|
||||
},
|
||||
active: item.value === visibility,
|
||||
}))
|
||||
|
|
@ -349,7 +356,7 @@ export const PagesManager: React.FC<{
|
|||
onClick={pages.fetchNextPage as () => void}
|
||||
isLoading={pages.isFetchingNextPage}
|
||||
>
|
||||
{t("load more", {
|
||||
{siteT("load more", {
|
||||
name: t(
|
||||
isPost
|
||||
? "post"
|
||||
|
|
@ -359,7 +366,6 @@ export const PagesManager: React.FC<{
|
|||
: ""),
|
||||
),
|
||||
count: (pages.data?.pages?.[0].count || 0) - currentLength,
|
||||
ns: "site",
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useParams } from "next/navigation"
|
||||
import React, { useState } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
|
|
@ -8,6 +7,7 @@ import { useQueryClient } from "@tanstack/react-query"
|
|||
|
||||
import { type TabItem, Tabs } from "~/components/ui/Tabs"
|
||||
import { APP_NAME } from "~/lib/env"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { delStorage, getStorage, setStorage } from "~/lib/storage"
|
||||
import { ExpandedNote } from "~/lib/types"
|
||||
import { useCreateOrUpdatePage, useDeletePage } from "~/queries/page"
|
||||
|
|
@ -23,10 +23,10 @@ export const PagesManagerBatchSelectActionTab: React.FC<{
|
|||
batchSelected: (string | number)[]
|
||||
setBatchSelected: (selected: string[]) => void
|
||||
}> = ({ isPost, isNotxLogContent, pages, batchSelected, setBatchSelected }) => {
|
||||
const { t } = useTranslation(["dashboard", "site"])
|
||||
const { t } = useTranslation("dashboard")
|
||||
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useParams, useRouter } from "next/navigation"
|
||||
import { FC, useEffect, useState } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
|
|
@ -9,6 +8,7 @@ import { useQueryClient } from "@tanstack/react-query"
|
|||
import { useGetState } from "~/hooks/useGetState"
|
||||
import { APP_NAME } from "~/lib/env"
|
||||
import { getNoteSlugFromNote, getTwitterShareUrl } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { delStorage, getStorage, setStorage } from "~/lib/storage"
|
||||
import { ExpandedNote } from "~/lib/types"
|
||||
import { useCreateOrUpdatePage, useDeletePage } from "~/queries/page"
|
||||
|
|
@ -17,8 +17,8 @@ import { useGetSite } from "~/queries/site"
|
|||
import { DeleteConfirmationModal } from "./DeleteConfirmationModal"
|
||||
|
||||
const usePageEditLink = (page: ExpandedNote, isPost: boolean) => {
|
||||
const router = useRouter()
|
||||
const subdomain = router.query.subdomain as string
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
|
||||
return `/dashboard/${subdomain}/editor?id=${page.noteId}&type=${
|
||||
isPost ? "post" : "page"
|
||||
|
|
@ -35,14 +35,15 @@ export const PagesManagerMenu: FC<{
|
|||
page: ExpandedNote
|
||||
onClick: () => void
|
||||
}> = ({ isPost, page, onClick: onClose }) => {
|
||||
const { t } = useTranslation(["dashboard", "site"])
|
||||
const { t } = useTranslation("dashboard")
|
||||
|
||||
const isCrossbell = !page.metadata?.content?.sources?.includes("xlog")
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const createOrUpdatePage = useCreateOrUpdatePage()
|
||||
|
||||
const editLink = usePageEditLink(page, isPost)
|
||||
const subdomain = router.query.subdomain as string
|
||||
const queryClient = useQueryClient()
|
||||
const deletePage = useDeletePage()
|
||||
|
||||
|
|
@ -157,7 +158,7 @@ export const PagesManagerMenu: FC<{
|
|||
onClick() {
|
||||
const slug = getNoteSlugFromNote(page)
|
||||
if (!slug) return
|
||||
window.open(`/_site/${subdomain}/${slug}`)
|
||||
window.open(`/site/${subdomain}/${slug}`)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import useOnClickOutside from "use-onclickoutside"
|
||||
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
|
||||
import { Button, ButtonGroup } from "../ui/Button"
|
||||
import { DeleteConfirmationModal } from "./DeleteConfirmationModal"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useParams, usePathname } from "next/navigation"
|
||||
import React from "react"
|
||||
|
||||
import { useXSettingsModal } from "@crossbell/connect-kit"
|
||||
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
|
||||
import { type TabItem, Tabs } from "../ui/Tabs"
|
||||
import { DashboardMain } from "./DashboardMain"
|
||||
|
||||
|
|
@ -11,11 +12,12 @@ export const SettingsLayout: React.FC<{
|
|||
title: string
|
||||
children: React.ReactNode
|
||||
}> = ({ title, children }) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation("dashboard")
|
||||
const xSettingsModal = useXSettingsModal()
|
||||
|
||||
const subdomain = router.query.subdomain as string
|
||||
const pathname = usePathname()
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const tabItems: TabItem[] = [
|
||||
{ text: "General", href: `/dashboard/${subdomain}/settings/general` },
|
||||
{
|
||||
|
|
@ -40,7 +42,7 @@ export const SettingsLayout: React.FC<{
|
|||
text: "Export data",
|
||||
href: `https://export.crossbell.io/?handle=${subdomain}`,
|
||||
},
|
||||
].map((item) => ({ ...item, active: router.asPath === item.href }))
|
||||
].map((item) => ({ ...item, active: pathname === item.href }))
|
||||
|
||||
return (
|
||||
<DashboardMain>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
"use client"
|
||||
|
||||
import { useRouter } from "next/navigation"
|
||||
import React from "react"
|
||||
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
|
||||
import { Button } from "~/components/ui/Button"
|
||||
|
||||
interface Props {
|
||||
connectedContent: React.ReactNode
|
||||
unconnectedContent: React.ReactNode
|
||||
}
|
||||
|
||||
export default function EntranceButton(props: Props) {
|
||||
const router = useRouter()
|
||||
const isConnected = useAccountState((s) => !!s.computed.account)
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="text-accent h-10 mt-4 flex items-center"
|
||||
onClick={() => router.push("/dashboard")}
|
||||
size="2xl"
|
||||
variantColor="black"
|
||||
>
|
||||
{isConnected ? props.connectedContent : props.unconnectedContent}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/router"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { memo, useEffect, useState } from "react"
|
||||
import reactStringReplace from "react-string-replace"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
import { useAccountState, useConnectModal } from "@crossbell/connect-kit"
|
||||
import { Switch } from "@headlessui/react"
|
||||
|
||||
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
|
||||
|
|
@ -14,6 +15,7 @@ import { Image } from "~/components/ui/Image"
|
|||
import { Tabs } from "~/components/ui/Tabs"
|
||||
import { Tooltip } from "~/components/ui/Tooltip"
|
||||
import { useDate } from "~/hooks/useDate"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { getStorage, setStorage } from "~/lib/storage"
|
||||
import { ExpandedNote } from "~/lib/types"
|
||||
import type { FeedType, SearchType } from "~/models/home.model"
|
||||
|
|
@ -31,7 +33,7 @@ const Post = ({
|
|||
keyword?: string
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation(["common", "site"])
|
||||
const { t } = useTranslation("common")
|
||||
const date = useDate()
|
||||
|
||||
if (
|
||||
|
|
@ -50,6 +52,7 @@ const Post = ({
|
|||
target="_blank"
|
||||
href={`/api/redirection?characterId=${post.characterId}`}
|
||||
className="flex items-center space-x-4 cursor-pointer"
|
||||
prefetch={false}
|
||||
>
|
||||
<span className="w-10 h-10 inline-block">
|
||||
<Image
|
||||
|
|
@ -89,6 +92,7 @@ const Post = ({
|
|||
target="_blank"
|
||||
href={`/api/redirection?characterId=${post.characterId}¬eId=${post.noteId}`}
|
||||
className="xlog-post sm:hover:bg-hover bg-white transition-all p-4 ml-10 sm:rounded-xl flex flex-col sm:flex-row items-center"
|
||||
prefetch={false}
|
||||
>
|
||||
<div className="flex-1 flex justify-center flex-col w-full min-w-0">
|
||||
<h3 className="xlog-post-title text-2xl font-bold text-zinc-700">
|
||||
|
|
@ -159,12 +163,15 @@ const Post = ({
|
|||
|
||||
const MemoedPost = memo(Post)
|
||||
|
||||
export const MainFeed: React.FC<{
|
||||
type?: FeedType
|
||||
export const HomeFeed: React.FC<{
|
||||
noteIds?: string[]
|
||||
keyword?: string
|
||||
}> = ({ type, noteIds, keyword }) => {
|
||||
const { t } = useTranslation(["common", "site"])
|
||||
type?: FeedType
|
||||
}> = ({ noteIds, keyword, type }) => {
|
||||
const { t } = useTranslation("common")
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const [feedType, setFeedType] = useState<FeedType>(type || "latest")
|
||||
|
||||
const currentCharacterId = useAccountState(
|
||||
(s) => s.computed.account?.characterId,
|
||||
|
|
@ -174,15 +181,15 @@ export const MainFeed: React.FC<{
|
|||
const [searchType, setSearchType] = useState<SearchType>("latest")
|
||||
|
||||
const feed = useGetFeed({
|
||||
type: type,
|
||||
type: feedType,
|
||||
characterId: currentCharacterId,
|
||||
noteIds: noteIds,
|
||||
daysInterval: hotInterval,
|
||||
searchKeyword: keyword,
|
||||
searchKeyword: searchParams?.get("q") || undefined,
|
||||
searchType,
|
||||
})
|
||||
|
||||
const hasFiltering = type === "latest"
|
||||
const hasFiltering = feedType === "latest"
|
||||
|
||||
const [aiFiltering, setAiFiltering] = useState(true)
|
||||
|
||||
|
|
@ -226,8 +233,35 @@ export const MainFeed: React.FC<{
|
|||
},
|
||||
]
|
||||
|
||||
const connectModal = useConnectModal()
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
text: "Latest",
|
||||
onClick: () => setFeedType("latest"),
|
||||
active: feedType === "latest",
|
||||
},
|
||||
{
|
||||
text: "Hottest",
|
||||
onClick: () => setFeedType("hot"),
|
||||
active: feedType === "hot",
|
||||
},
|
||||
{
|
||||
text: "Following",
|
||||
onClick: () => {
|
||||
if (!currentCharacterId) {
|
||||
connectModal.show()
|
||||
} else {
|
||||
setFeedType("following")
|
||||
}
|
||||
},
|
||||
active: feedType === "following",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
{!type && <Tabs items={tabs} className="border-none text-lg"></Tabs>}
|
||||
<div className="space-y-10">
|
||||
{hasFiltering && (
|
||||
<div className="flex items-center text-zinc-500">
|
||||
|
|
@ -263,10 +297,10 @@ export const MainFeed: React.FC<{
|
|||
</Switch>
|
||||
</div>
|
||||
)}
|
||||
{type === "hot" && (
|
||||
{feedType === "hot" && (
|
||||
<Tabs items={hotTabs} className="border-none text-sm -my-4"></Tabs>
|
||||
)}
|
||||
{type === "search" && (
|
||||
{feedType === "search" && (
|
||||
<Tabs items={searchTabs} className="border-none text-sm -my-4"></Tabs>
|
||||
)}
|
||||
|
||||
|
|
@ -288,7 +322,7 @@ export const MainFeed: React.FC<{
|
|||
key={`${post.characterId}-${post.noteId}`}
|
||||
post={post}
|
||||
filtering={aiFiltering ? 60 : 0}
|
||||
keyword={keyword}
|
||||
keyword={searchParams?.get("q") || undefined}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
|
||||
|
|
@ -6,12 +7,13 @@ import { SearchInput } from "~/components/common/SearchInput"
|
|||
import { Image } from "~/components/ui/Image"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { useGetShowcase } from "~/queries/home"
|
||||
|
||||
import topics from "../../../data/topics.json"
|
||||
import { FollowAllButton } from "../common/FollowAllButton"
|
||||
|
||||
export function MainSidebar({ hideSearch }: { hideSearch?: boolean }) {
|
||||
export function HomeSidebar({ hideSearch }: { hideSearch?: boolean }) {
|
||||
const showcaseSites = useGetShowcase()
|
||||
const { t } = useTranslation("index")
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { GITHUB_LINK } from "~/lib/env"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
import { UniLink } from "../ui/UniLink"
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: "Home",
|
||||
link: "/",
|
||||
},
|
||||
{
|
||||
name: "Activities",
|
||||
link: "/activities",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<Image
|
||||
className="no-optimization w-24"
|
||||
alt="github stars"
|
||||
src="https://img.shields.io/github/stars/Crossbell-Box/xLog?color=white&label=Stars&logo=github&style=social"
|
||||
/>
|
||||
),
|
||||
link: GITHUB_LINK,
|
||||
},
|
||||
]
|
||||
|
||||
export default function HomeTabs() {
|
||||
const pathname = usePathname()
|
||||
const { t } = useTranslation("index")
|
||||
|
||||
return (
|
||||
<>
|
||||
{tabs?.map((tab, index) => (
|
||||
<UniLink
|
||||
className={cn(
|
||||
"cursor-pointer items-center hidden sm:flex hover:text-accent text-lg",
|
||||
{
|
||||
"text-accent": pathname === tab.link,
|
||||
},
|
||||
)}
|
||||
key={tab.link}
|
||||
href={tab.link}
|
||||
>
|
||||
{typeof tab.name === "string" ? t(tab.name) : tab.name}
|
||||
</UniLink>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
"use client"
|
||||
|
||||
import {
|
||||
CrossbellChainLogo,
|
||||
XCharLogo,
|
||||
XFeedLogo,
|
||||
XShopLogo,
|
||||
XSyncLogo,
|
||||
} from "@crossbell/ui"
|
||||
import { RssIcon } from "@heroicons/react/24/outline"
|
||||
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { Tooltip } from "~/components/ui/Tooltip"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
|
||||
export function Integration() {
|
||||
const integrations = [
|
||||
{
|
||||
name: "RSS",
|
||||
icon: <RssIcon className="w-full h-full text-orange-500" />,
|
||||
url:
|
||||
getSiteLink({
|
||||
subdomain: "xlog",
|
||||
}) + "/feed?format=xml",
|
||||
},
|
||||
{
|
||||
name: "JSON Feed",
|
||||
icon: <Image src="/assets/json-feed.png" alt="JSON Feed" />,
|
||||
url:
|
||||
getSiteLink({
|
||||
subdomain: "xlog",
|
||||
}) + "/feed",
|
||||
},
|
||||
{
|
||||
name: "xChar",
|
||||
icon: <XCharLogo className="w-full h-full" />,
|
||||
url: "https://xchar.app/",
|
||||
},
|
||||
{
|
||||
name: "xFeed",
|
||||
icon: <XFeedLogo className="w-full h-full" />,
|
||||
url: "https://crossbell.io/feed",
|
||||
},
|
||||
{
|
||||
name: "xSync",
|
||||
icon: <XSyncLogo className="w-full h-full" />,
|
||||
url: "https://xsync.app/",
|
||||
},
|
||||
{
|
||||
name: "xShop",
|
||||
icon: <XShopLogo className="w-full h-full" />,
|
||||
text: "Coming soon",
|
||||
},
|
||||
{
|
||||
name: "Crossbell Scan",
|
||||
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />,
|
||||
url: "https://scan.crossbell.io/",
|
||||
},
|
||||
{
|
||||
name: "Crossbell Faucet",
|
||||
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />,
|
||||
url: "https://faucet.crossbell.io/",
|
||||
},
|
||||
{
|
||||
name: "Crossbell Export",
|
||||
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />,
|
||||
url: "https://export.crossbell.io/",
|
||||
},
|
||||
{
|
||||
name: "Crossbell SDK",
|
||||
icon: <CrossbellChainLogo className="w-full h-full text-[#E7B75B]" />,
|
||||
url: "https://crossbell-box.github.io/crossbell.js/",
|
||||
},
|
||||
{
|
||||
name: "RSS3",
|
||||
icon: (
|
||||
<Image alt="RSS3" src="/assets/rss3.svg" className="rounded" fill />
|
||||
),
|
||||
url: "https://rss3.io/",
|
||||
},
|
||||
{
|
||||
name: "Hoot It",
|
||||
icon: (
|
||||
<Image alt="Hoot It" src="/assets/hoot.svg" className="rounded" fill />
|
||||
),
|
||||
url: "https://hoot.it/search/xLog",
|
||||
},
|
||||
{
|
||||
name: "Raycast",
|
||||
icon: <Image src="/assets/raycast.png" alt="Raycast" />,
|
||||
url: "https://www.raycast.com/Songkeys/crossbell",
|
||||
},
|
||||
{
|
||||
name: "Obsidian",
|
||||
icon: (
|
||||
<Image
|
||||
src="/assets/obsidian.svg"
|
||||
alt="Obsidian"
|
||||
className="rounded"
|
||||
fill
|
||||
/>
|
||||
),
|
||||
text: "Coming soon",
|
||||
},
|
||||
]
|
||||
return (
|
||||
<>
|
||||
{integrations.map((item, index) => (
|
||||
<li
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
key={index}
|
||||
>
|
||||
{item.url ? (
|
||||
<UniLink
|
||||
href={item.url}
|
||||
className="w-full flex items-center flex-col justify-center"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-md overflow-hidden">
|
||||
{item.icon}
|
||||
</div>
|
||||
<div className="font-medium sm:text-lg mt-2 text-center">
|
||||
{item.name}
|
||||
</div>
|
||||
</UniLink>
|
||||
) : (
|
||||
<Tooltip label={item.text!}>
|
||||
<div className="w-full h-full flex items-center flex-col justify-center">
|
||||
<div className="w-12 h-12 rounded-md overflow-hidden">
|
||||
{item.icon}
|
||||
</div>
|
||||
<div className="font-medium sm:text-lg mt-2 text-center">
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
|
||||
import { FollowAllButton } from "~/components/common/FollowAllButton"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { UniLink } from "~/components/ui/UniLink"
|
||||
import { getSiteLink } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { useGetShowcase } from "~/queries/home"
|
||||
|
||||
export function ShowCase() {
|
||||
const showcaseSites = useGetShowcase()
|
||||
const [showcaseMore, setShowcaseMore] = useState(false)
|
||||
const { t } = useTranslation("index")
|
||||
|
||||
return (
|
||||
<>
|
||||
<FollowAllButton
|
||||
className="mt-5"
|
||||
size="xl"
|
||||
characterIds={showcaseSites.data
|
||||
?.map((s: { characterId?: string }) => s.characterId)
|
||||
.filter(Boolean)
|
||||
.map(Number)}
|
||||
siteIds={showcaseSites.data?.map((s: { handle: string }) => s.handle)}
|
||||
/>
|
||||
<ul
|
||||
className={`pt-10 grid grid-cols-2 md:grid-cols-3 gap-10 overflow-y-hidden relative text-left ${
|
||||
showcaseMore ? "" : "max-h-[540px]"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`absolute bottom-0 h-20 left-0 right-0 bg-gradient-to-t from-white via-white flex items-end justify-center font-bold cursor-pointer ${
|
||||
showcaseMore ? "hidden" : ""
|
||||
}`}
|
||||
onClick={() => setShowcaseMore(true)}
|
||||
>
|
||||
{t("Show more")}
|
||||
</div>
|
||||
{showcaseSites.data?.map((site) => (
|
||||
<li className="inline-flex align-middle" key={site.handle}>
|
||||
<UniLink
|
||||
href={getSiteLink({
|
||||
subdomain: site.handle,
|
||||
})}
|
||||
className="inline-flex align-middle w-full"
|
||||
>
|
||||
<CharacterFloatCard siteId={site.handle}>
|
||||
<span className="w-14 h-14 inline-block">
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src={
|
||||
site.metadata?.content?.avatars?.[0] ||
|
||||
"ipfs://bafkreiabgixxp63pg64moxnsydz7hewmpdkxxi3kdsa4oqv4pb6qvwnmxa"
|
||||
}
|
||||
alt={site.handle}
|
||||
width="56"
|
||||
height="56"
|
||||
></Image>
|
||||
</span>
|
||||
</CharacterFloatCard>
|
||||
<span className="ml-3 min-w-0 flex-1 justify-center inline-flex flex-col">
|
||||
<span className="truncate w-full inline-block font-medium">
|
||||
{site.metadata?.content?.name}
|
||||
</span>
|
||||
{site.metadata?.content?.bio && (
|
||||
<span className="text-gray-500 text-xs truncate w-full inline-block mt-1">
|
||||
{site.metadata.content?.bio}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</UniLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import { throttle } from "~/lib/utils"
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import { Link } from "react-scroll"
|
||||
|
||||
export const PostLink: React.FC<any> = ({ children, ...props }) => {
|
||||
if (props.href?.startsWith("#")) {
|
||||
return (
|
||||
<Link
|
||||
to={decodeURI(props.href.slice(1))}
|
||||
spy={true}
|
||||
smooth={true}
|
||||
duration={500}
|
||||
offset={-20}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return <a {...props}>{children}</a>
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { useTranslation } from "next-i18next"
|
||||
"use client"
|
||||
|
||||
// TODO
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
|
||||
|
|
@ -6,6 +8,7 @@ import { UniLink } from "~/components/ui/UniLink"
|
|||
import { useDate } from "~/hooks/useDate"
|
||||
import { useUserRole } from "~/hooks/useUserRole"
|
||||
import { CSB_SCAN, SITE_URL } from "~/lib/env"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { toCid } from "~/lib/ipfs-parser"
|
||||
import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
|
||||
import { useGetSummary } from "~/queries/page"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ import katex from "katex"
|
|||
import type { List } from "mdast"
|
||||
import { toHast } from "mdast-util-to-hast"
|
||||
import type { Result as TocResult } from "mdast-util-toc"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { Link } from "react-scroll"
|
||||
import React, { createElement, useEffect, useRef, useState } from "react"
|
||||
|
||||
const inlineElements = ["delete", "strong", "emphasis", "inlineCode"]
|
||||
|
||||
|
|
@ -40,6 +39,13 @@ function useActiveId(itemIds: string[]) {
|
|||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const state = history.state
|
||||
|
||||
history.replaceState(
|
||||
{ ...state, preventScrollToToc: true },
|
||||
"",
|
||||
entry.target.getAttribute("href"),
|
||||
)
|
||||
setActiveId(entry.target.getAttribute("href"))
|
||||
}
|
||||
})
|
||||
|
|
@ -63,12 +69,13 @@ function useActiveId(itemIds: string[]) {
|
|||
}, [itemIds])
|
||||
return activeId
|
||||
}
|
||||
|
||||
function renderItems(
|
||||
items: TocResult["map"],
|
||||
activeId?: string | null,
|
||||
prefix = "",
|
||||
) {
|
||||
interface ItemsProps {
|
||||
items: TocResult["map"]
|
||||
activeId?: string | null
|
||||
prefix?: string
|
||||
}
|
||||
function Items(props: ItemsProps) {
|
||||
const { items, activeId, prefix = "" } = props
|
||||
return (
|
||||
<ol className={prefix ? "pl-5" : ""}>
|
||||
{items?.children?.map((item, index) => (
|
||||
|
|
@ -91,14 +98,7 @@ function renderItems(
|
|||
return (
|
||||
<span key={index + "-" + i}>
|
||||
{child.type === "paragraph" && child.children?.[0]?.url && (
|
||||
<Link
|
||||
to={`user-content-${decodeURIComponent(
|
||||
child.children[0].url.slice(1),
|
||||
)}`}
|
||||
spy={true}
|
||||
smooth={true}
|
||||
duration={500}
|
||||
offset={-20}
|
||||
<a
|
||||
href={child.children[0].url}
|
||||
title={content}
|
||||
className={
|
||||
|
|
@ -113,10 +113,14 @@ function renderItems(
|
|||
__html: content,
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
</a>
|
||||
)}
|
||||
{child.type === "list" &&
|
||||
renderItems(child, activeId, `${index + 1}.`)}
|
||||
createElement(Items, {
|
||||
items: child,
|
||||
activeId,
|
||||
prefix: `${index + 1}.`,
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
|
|
@ -161,7 +165,7 @@ export const PostToc: React.FC<{
|
|||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{renderItems(data?.map, activeId)}
|
||||
<Items items={data?.map} activeId={activeId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue