diff --git a/.github/workflows/cf-pages-deploy.yml b/.github/workflows/cf-pages-deploy.yml index 6c4bf2f..0a7dc64 100644 --- a/.github/workflows/cf-pages-deploy.yml +++ b/.github/workflows/cf-pages-deploy.yml @@ -3,7 +3,9 @@ name: Build and Deploy to CF Pages on: workflow_dispatch: push: - branches: [main, master] + branches: [main] + pull_request: + branches: [main] jobs: build: @@ -30,9 +32,10 @@ jobs: name: build-output path: out/ - deploy: + deploy-preview: needs: build runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'workflow_dispatch' steps: - name: Download artifact uses: actions/download-artifact@v4 @@ -40,11 +43,31 @@ jobs: name: build-output path: out/ - - name: Deploy to Cloudflare Pages + - name: Deploy to Cloudflare Pages (Preview) uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} projectName: transchinese-test directory: out + gitHubToken: ${{ secrets.GITHUB_TOKEN }} + + deploy-production: + needs: build + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: build-output + path: out/ + + - name: Deploy to Cloudflare Pages (Production) + uses: cloudflare/pages-action@v1 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: transchinese-org + directory: out gitHubToken: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/app/search/page.tsx b/app/search/page.tsx index 7d43f80..59711a8 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -4,6 +4,7 @@ import { useRouter, useSearchParams } from 'next/navigation' import SearchForm from '@/components/search/SearchForm' import SearchResults from '@/components/search/SearchResults' import { SearchResult } from '@/components/search/SearchResult' +import { clientSearch } from '@/lib/clientSearch' // Loading component const SearchLoading = () => ( @@ -82,11 +83,7 @@ function SearchContent() { router.push(`?${params.toString()}`, { scroll: false }) - const response = await fetch(`/api/search?${params.toString()}`) - if (!response.ok) { - throw new Error('Search failed') - } - const data: SearchResult[] = await response.json() + const data = await clientSearch(query, domain || undefined, tag || undefined, year || undefined, region || undefined) setResults(data) } catch (err) { setError('Search failed. Please try again.') diff --git a/lib/clientSearch.ts b/lib/clientSearch.ts new file mode 100644 index 0000000..8ebd1d7 --- /dev/null +++ b/lib/clientSearch.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import pako from 'pako' +import { SearchResult } from '@/components/search/SearchResult' + +const REPO_INDEXES: Record = { + "digital.transchinese.org": "/search-index/repo-digital-transchinese-org.json.gz", + "novel.transchinese.org": "/search-index/repo-novel-transchinese-org.json.gz", + "comic.transchinese.org": "/search-index/repo-comic-transchinese-org.json.gz", + "archive.cdtsf.com": "/search-index/repo-archive-cdtsf-com.json.gz", + "news.transchinese.org": "/search-index/repo-news-transchinese-org.json.gz", + "enovel.cdtsf.com": "/search-index/repo-enovel-cdtsf-com.json.gz", + "fnovel.cdtsf.com": "/search-index/repo-fnovel-cdtsf-com.json.gz", + "snovel.cdtsf.com": "/search-index/repo-snovel-cdtsf-com.json.gz", + "unovel.transchinese.org": "/search-index/repo-unovel-transchinese-org.json.gz", + "xnovel.transchinese.org": "/search-index/repo-xnovel-transchinese-org.json.gz", +} + +const cache: Record> = {} + +async function loadIndex(domain: string): Promise { + if (domain in cache) { + return cache[domain] + } + const file = REPO_INDEXES[domain] + if (!file) return null + const promise: Promise = (async () => { + try { + const res = await fetch(file) + const compressed = await res.arrayBuffer() + const decompressed = pako.inflate(new Uint8Array(compressed), { to: 'string' }) + const data = JSON.parse(decompressed) + return data + } catch (e) { + console.error('Failed to load', domain, e) + return null + } + })() + cache[domain] = promise + return promise +} + +export async function clientSearch( + query: string, + domain?: string, + tag?: string, + year?: string, + region?: string +): Promise { + const domains = domain + ? domain.split(',').map(d => d.trim()).filter(Boolean) + : Object.keys(REPO_INDEXES) + const lowerQuery = query.toLowerCase() + const found: SearchResult[] = [] + + // Load all requested domain indexes in parallel to reduce overall latency. + const indexResults = await Promise.all( + domains.map(async (d) => { + const index = await loadIndex(d) + return { domain: d, index } + }) + ) + + for (const { domain: d, index } of indexResults) { + if (found.length >= 100) break + if (!index) continue + for (const [key, doc] of Object.entries(index)) { + if (found.length >= 100) break + const docAny = doc as any + const keyLower = key.toLowerCase() + const descLower = (docAny.description || '').toLowerCase() + if (lowerQuery && !keyLower.includes(lowerQuery) && !descLower.includes(lowerQuery)) continue + if (tag && (!docAny.tags || !docAny.tags.includes(tag))) continue + if (year && (!docAny.date || !docAny.date.includes(year))) continue + if (region && docAny.region !== region) continue + found.push({ + url: 'https://' + d + '/' + key.replace(/\.[^/.]+$/, ''), + description: docAny.description || '', + tags: docAny.tags || [], + type: docAny.type || '', + author: docAny.author || '', + date: docAny.date || '', + region: docAny.region || '', + format: docAny.format || '', + size: docAny.size || 0, + link: 'https://' + d + '/' + key.replace(/\.[^/.]+$/, ''), + }) + } + } + return found +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b4c71f1..22cc4cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "next-contentlayer2": "0.5.3", "next-themes": "^0.3.0", "openai": "^4.72.0", + "pako": "^2.1.0", "pliny": "^0.4.0", "postcss": "^8.4.24", "react": "rc", @@ -16231,6 +16232,12 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/package.json b/package.json index 8b963be..29401e0 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "next-contentlayer2": "0.5.3", "next-themes": "^0.3.0", "openai": "^4.72.0", + "pako": "^2.1.0", "pliny": "^0.4.0", "postcss": "^8.4.24", "react": "rc", diff --git a/public/search-index/repo-archive-cdtsf-com.json.gz b/public/search-index/repo-archive-cdtsf-com.json.gz new file mode 100644 index 0000000..6259a82 Binary files /dev/null and b/public/search-index/repo-archive-cdtsf-com.json.gz differ diff --git a/public/search-index/repo-comic-transchinese-org.json.gz b/public/search-index/repo-comic-transchinese-org.json.gz new file mode 100644 index 0000000..56117b8 Binary files /dev/null and b/public/search-index/repo-comic-transchinese-org.json.gz differ diff --git a/public/search-index/repo-digital-transchinese-org.json.gz b/public/search-index/repo-digital-transchinese-org.json.gz new file mode 100644 index 0000000..345c077 Binary files /dev/null and b/public/search-index/repo-digital-transchinese-org.json.gz differ diff --git a/public/search-index/repo-enovel-cdtsf-com.json.gz b/public/search-index/repo-enovel-cdtsf-com.json.gz new file mode 100644 index 0000000..2b2964b Binary files /dev/null and b/public/search-index/repo-enovel-cdtsf-com.json.gz differ diff --git a/public/search-index/repo-fnovel-cdtsf-com.json.gz b/public/search-index/repo-fnovel-cdtsf-com.json.gz new file mode 100644 index 0000000..fbc903d Binary files /dev/null and b/public/search-index/repo-fnovel-cdtsf-com.json.gz differ diff --git a/public/search-index/repo-news-transchinese-org.json.gz b/public/search-index/repo-news-transchinese-org.json.gz new file mode 100644 index 0000000..31abd43 Binary files /dev/null and b/public/search-index/repo-news-transchinese-org.json.gz differ diff --git a/public/search-index/repo-novel-transchinese-org.json.gz b/public/search-index/repo-novel-transchinese-org.json.gz new file mode 100644 index 0000000..575332e Binary files /dev/null and b/public/search-index/repo-novel-transchinese-org.json.gz differ diff --git a/public/search-index/repo-snovel-cdtsf-com.json.gz b/public/search-index/repo-snovel-cdtsf-com.json.gz new file mode 100644 index 0000000..0967d85 Binary files /dev/null and b/public/search-index/repo-snovel-cdtsf-com.json.gz differ diff --git a/public/search-index/repo-unovel-transchinese-org.json.gz b/public/search-index/repo-unovel-transchinese-org.json.gz new file mode 100644 index 0000000..6fe5c6b Binary files /dev/null and b/public/search-index/repo-unovel-transchinese-org.json.gz differ diff --git a/public/search-index/repo-xnovel-transchinese-org.json.gz b/public/search-index/repo-xnovel-transchinese-org.json.gz new file mode 100644 index 0000000..6b1a81a Binary files /dev/null and b/public/search-index/repo-xnovel-transchinese-org.json.gz differ diff --git a/tsconfig.json b/tsconfig.json index 07da1cb..6045b54 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,7 @@ "@/data/*": ["data/*"], "@/layouts/*": ["layouts/*"], "@/css/*": ["css/*"], + "@/lib/*": ["lib/*"], "contentlayer/generated": ["./.contentlayer/generated"] }, "plugins": [ diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..e178cfe --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,6 @@ +name = "transchinese-test" +compatibility_date = "2024-01-01" +account_id = "${CLOUDFLARE_ACCOUNT_ID}" + +[site] +bucket = "./out" \ No newline at end of file diff --git a/wrangler.toml.prod b/wrangler.toml.prod new file mode 100644 index 0000000..3595cc6 --- /dev/null +++ b/wrangler.toml.prod @@ -0,0 +1,6 @@ +name = "transchinese-org" +compatibility_date = "2024-01-01" +account_id = "bc82e687de1803b47adc1dfe23aca0a4" + +[site] +bucket = "./out" \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 332c16a..7ee94e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1510,6 +1510,11 @@ resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz" integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + "@img/sharp-linux-x64@0.33.5": version "0.33.5" resolved "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz" @@ -1517,6 +1522,13 @@ optionalDependencies: "@img/sharp-libvips-linux-x64" "1.0.4" +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" @@ -2023,6 +2035,11 @@ resolved "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.2.tgz" integrity sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA== +"@next/swc-linux-x64-musl@15.0.2": + version "15.0.2" + resolved "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.2.tgz" + integrity sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -2996,6 +3013,11 @@ resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz" integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== +"@unrs/resolver-binding-linux-x64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz" + integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== + abab@^2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" @@ -8002,6 +8024,11 @@ p-try@^2.0.0: resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"