diff --git a/.github/workflows/deploy_dryrun.yaml b/.github/workflows/deploy_dryrun.yaml index 996a00b..f6e186a 100644 --- a/.github/workflows/deploy_dryrun.yaml +++ b/.github/workflows/deploy_dryrun.yaml @@ -28,10 +28,13 @@ jobs: filters: | pages: - "pages/**" + - "packages/api-types/**" ogp-data-fetcher: - "ogp-data-fetcher/**" + - "packages/api-types/src/ogp_types.ts" cms-data-fetcher: - "cms-data-fetcher/**" + - "packages/api-types/src/cms_types.ts" workflow: - ".github/workflows/**" diff --git a/.github/workflows/deploy_prd.yaml b/.github/workflows/deploy_prd.yaml index b995348..91638c7 100644 --- a/.github/workflows/deploy_prd.yaml +++ b/.github/workflows/deploy_prd.yaml @@ -32,10 +32,13 @@ jobs: filters: | pages: - "pages/**" + - "packages/api-types/**" ogp-data-fetcher: - "ogp-data-fetcher/**" + - "packages/api-types/src/ogp_types.ts" cms-data-fetcher: - "cms-data-fetcher/**" + - "packages/api-types/src/cms_types.ts" workflow: - ".github/workflows/**" diff --git a/.github/workflows/deploy_stg.yaml b/.github/workflows/deploy_stg.yaml index 4ebc00e..be673a4 100644 --- a/.github/workflows/deploy_stg.yaml +++ b/.github/workflows/deploy_stg.yaml @@ -34,10 +34,13 @@ jobs: filters: | pages: - "pages/**" + - "packages/api-types/**" ogp-data-fetcher: - "ogp-data-fetcher/**" + - "packages/api-types/src/ogp_types.ts" cms-data-fetcher: - "cms-data-fetcher/**" + - "packages/api-types/src/cms_types.ts" workflow: - ".github/workflows/**" diff --git a/packages/api-types/index.ts b/packages/api-types/index.ts index a460d8c..8d9a4c9 100644 --- a/packages/api-types/index.ts +++ b/packages/api-types/index.ts @@ -1,3 +1,2 @@ export * from './src/ogp_types' export * from './src/cms_types' -export * from './src/bande_dessinee_types' diff --git a/packages/api-types/src/bande_dessinee_types.ts b/packages/api-types/src/bande_dessinee_types.ts deleted file mode 100644 index 746cb71..0000000 --- a/packages/api-types/src/bande_dessinee_types.ts +++ /dev/null @@ -1,11 +0,0 @@ -type BandeDessineeConfig = { - cover: string - format: string - filename: string - first_page: number - last_page: number - first_page_left_right: 'right' | 'left' - back_cover: string -} - -export type { BandeDessineeConfig } diff --git a/packages/api-types/src/cms_types.ts b/packages/api-types/src/cms_types.ts index 0bcc933..1a58de8 100644 --- a/packages/api-types/src/cms_types.ts +++ b/packages/api-types/src/cms_types.ts @@ -70,6 +70,13 @@ type bandeDessineeResult = { previous_id: string | null series: bandeDessineeSerires | null tag: bandeDessineeTag + cover: string | null + back_cover: string | null + format: string[] + filename: string + first_page: number + last_page: number + first_left_right: ('right' | 'left')[] parsed_description: ParsedContent[] table_of_contents: TableOfContents } diff --git a/pages/app/blog/[article_id]/page.tsx b/pages/app/blog/[article_id]/page.tsx index 3fc9ad3..8e5b8a9 100644 --- a/pages/app/blog/[article_id]/page.tsx +++ b/pages/app/blog/[article_id]/page.tsx @@ -11,14 +11,12 @@ import LoadingBlogPage from './loading_article' export const runtime = 'edge' -export async function generateMetadata( - props: { - params: Promise<{ article_id: string }> - searchParams: Promise<{ [key: string]: string | undefined }> - } -) { - const searchParams = await props.searchParams; - const params = await props.params; +export async function generateMetadata(props: { + params: Promise<{ article_id: string }> + searchParams: Promise<{ [key: string]: string | undefined }> +}) { + const searchParams = await props.searchParams + const params = await props.params const articleID = params.article_id const draftKey = searchParams['draftKey'] @@ -50,14 +48,12 @@ export async function generateMetadata( } as Metadata } -export default async function BlogArticlePage( - props: { - params: Promise<{ article_id: string }> - searchParams: Promise<{ [key: string]: string | undefined }> - } -) { - const searchParams = await props.searchParams; - const params = await props.params; +export default async function BlogArticlePage(props: { + params: Promise<{ article_id: string }> + searchParams: Promise<{ [key: string]: string | undefined }> +}) { + const searchParams = await props.searchParams + const params = await props.params const articleID = params.article_id const draftKey = searchParams['draftKey'] diff --git a/pages/app/comics/[id]/error.tsx b/pages/app/comics/[id]/error.tsx new file mode 100644 index 0000000..aa31b86 --- /dev/null +++ b/pages/app/comics/[id]/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import { ErrorPageComic } from '@/components/large/error' + +export const runtime = 'edge' + +export default function ComicErrorPage() { + return ( +
+ +
+ ) +} diff --git a/pages/app/comics/[id]/page.tsx b/pages/app/comics/[id]/page.tsx index a22a588..06ce57d 100644 --- a/pages/app/comics/[id]/page.tsx +++ b/pages/app/comics/[id]/page.tsx @@ -1,12 +1,14 @@ import { metadata } from '@/app/layout' -import { ComicBookPage, ComicDetailPage } from '@/components/large/comics' +import { ComicDetailPage } from '@/components/large/comics' +import { LoadingComicBook } from '@/components/large/loading_comics' +import ComicBook from '@/components/middle/comicbook' import FooterButtons from '@/components/small/footer' import { getBandeDessineeByID } from '@/lib/api/workers' import { getHostname } from '@/lib/env' import { rewriteImageURL } from '@/lib/image' -import { ogpImageOption, originImageOption } from '@/lib/static' -import { BandeDessineeConfig } from 'api-types' +import { ogpImageOption } from '@/lib/static' import { Metadata } from 'next' +import { Suspense } from 'react' export const runtime = 'edge' @@ -14,16 +16,19 @@ export async function generateMetadata(props: { params: Promise<{ id: string }> searchParams: Promise<{ [key: string]: string | undefined }> }) { - const id = (await props.params).id - const draftKey = (await props.searchParams)['draftKey'] + const searchParams = await props.searchParams + const params = await props.params + const id = params.id + const draftKey = searchParams['draftKey'] + const data = await getBandeDessineeByID(id, draftKey) const contentsUrl = data.contents_url - const contentsUrlResponse = await fetch(contentsUrl, { next: { revalidate: 60 } }) - const config = (await contentsUrlResponse.json()) as BandeDessineeConfig const contentsBaseUrl = contentsUrl.replaceAll('/index.json', '') const title = data.title_name - const coverImageURL = contentsBaseUrl + '/' + config.cover + // TODO: 1ページ目のファイル指定はもっときれいにする + const ogpImageFile = data.cover || data.filename + '_00' + data.first_page + data.format[0] + const coverImageURL = contentsBaseUrl + '/' + ogpImageFile const ogpImage = rewriteImageURL(ogpImageOption, coverImageURL) return { @@ -48,38 +53,21 @@ export default async function ComicPage(props: { params: Promise<{ id: string }> searchParams: Promise<{ [key: string]: string | undefined }> }) { - const id = (await props.params).id - const draftKey = (await props.searchParams)['draftKey'] - const data = await getBandeDessineeByID(id, draftKey) - const contentsUrl = data.contents_url - const contentsUrlResponse = await fetch(contentsUrl, { next: { revalidate: 60 } }) - const config = (await contentsUrlResponse.json()) as BandeDessineeConfig - const contentsBaseUrl = contentsUrl.replaceAll('/index.json', '') + const params = await props.params + const searchParams = await props.searchParams + const id = params.id + const draftKey = searchParams['draftKey'] + const asyncData = getBandeDessineeByID(id, draftKey) + const data = await asyncData const title = data.title_name - const firstPage = config.first_page - const lastPage = config.last_page + const firstPage = data.filename + '_00' + data.first_page + data.format[0] return (
- + }> + +
diff --git a/pages/components/large/comics.tsx b/pages/components/large/comics.tsx index 8cde5f9..fd90da1 100644 --- a/pages/components/large/comics.tsx +++ b/pages/components/large/comics.tsx @@ -1,14 +1,13 @@ -import { BandeDessineeConfig, ParsedContent, TableOfContents } from 'api-types' +import { ParsedContent, TableOfContents } from 'api-types' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitleH1 } from '../ui/card' import { convertJST, convertJSTDate } from '@/lib/time' import ClientImage from '../small/client_image' import { rewriteImageURL } from '@/lib/image' -import { imageOption, originImageOption } from '@/lib/static' +import { imageOption } from '@/lib/static' import ArticleContent from '../middle/article_content' import { Button } from '../ui/button' import Link from 'next/link' import { ArrowLeftSquareIcon, ArrowRightSquareIcon, ArrowUpSquareIcon, BookImageIcon, HomeIcon } from 'lucide-react' -import ComicBook from '../middle/comicbook' import ShareButton from '../small/share' import { getHostname } from '@/lib/env' @@ -26,40 +25,21 @@ type ComicArticleProps = { seriesName: string | null tagId: string tagName: string + cover: string | null + firstPage: string // 1ページ目のファイル名。結合済みで渡す parsedDescription: ParsedContent[] tableOfContents: TableOfContents } -type ComicBookProps = { - id: string - baseUrl: string - coverImage: string - backCoverImage: string - firstPageNumber: number - lastPageNumber: number - firstPageLeftRight: 'left' | 'right' - format: string - filename: string - parsedDescription: ParsedContent[] - next: string | null - previous: string | null - seriesId: string | null - seriesName: string | null - tagId: string - tagName: string -} - export async function ComicOverview(props: ComicArticleProps) { const contentsUrl = props.contentsUrl - const contentsJSON = await fetch(contentsUrl) - const contentsConfig = (await contentsJSON.json()) as BandeDessineeConfig - const contentsBaseURL = contentsUrl.replaceAll('/index.json', '') - // 表紙 - const coverImageOriginURL = contentsBaseURL + '/' + contentsConfig.cover - const coverImageURL = rewriteImageURL(imageOption, coverImageOriginURL) + // 表紙、または1ページ目の画像URL + const imageFile = props.cover || props.firstPage + const imageOriginURL = contentsBaseURL + '/' + imageFile + const imageURL = rewriteImageURL(imageOption, imageOriginURL) // 説明文 const description = props.parsedDescription // マンガのタイトル @@ -67,8 +47,8 @@ export async function ComicOverview(props: ComicArticleProps) { // マンガページのリンクURL const linkURL = `/comics/${props.id}` // 公開日・イベント情報 - const publishDate = props.publishDate ? convertJSTDate(props.publishDate) : 'Web初公開' - const publishEvent = props.publishEvent ? props.publishEvent : 'Web初公開' + const publishDate = props.publishDate ? convertJSTDate(props.publishDate) : '-' + const publishEvent = props.publishEvent || '-' // シリーズ設定 const series = props.seriesName || '-' // ジャンル設定 @@ -78,8 +58,10 @@ export async function ComicOverview(props: ComicArticleProps) {
- - + +
+ +
@@ -125,37 +107,17 @@ export async function ComicOverview(props: ComicArticleProps) { ) } -export async function ComicBookPage(props: ComicBookProps) { - // 表紙の画像URL - const coverPageSrc = props.baseUrl + '/' + props.coverImage - // 裏表紙の画像URL - const backCoverPageSrc = props.baseUrl + '/' + props.backCoverImage - - // 各種ページのソースURL - const pageSrcArray = Array.from({ length: props.lastPageNumber - props.firstPageNumber + 1 }, (_, i) => { - return getPageImageSrc(props.baseUrl, props.filename, props.firstPageNumber + i, props.format) - }) - - return ( -
- -
- ) -} - -export async function ComicDetailPage(props: ComicArticleProps) { +export function ComicDetailPage(props: ComicArticleProps) { const url = getHostname() + '/comics/' + props.id const isNextExist = props.nextId !== null const isPreviousExist = props.previousId !== null const isSereies = props.seriesName !== null + const nextLink = isNextExist ? `/comics/${props.nextId}` : '' + const previousLink = isPreviousExist ? `/comics/${props.previousId}` : '' + const seriesLink = isSereies ? `/comics?series=${props.seriesId}` : '' - const publishDate = props.publishDate ? convertJSTDate(props.publishDate) : 'Web初公開' - const publishEvent = props.publishEvent ? props.publishEvent : 'Web初公開' + const publishDate = props.publishDate ? convertJSTDate(props.publishDate) : '-' + const publishEvent = props.publishEvent || '-' const seriesName = props.seriesName || '-' const tagName = props.tagName || '-' @@ -165,18 +127,18 @@ export async function ComicDetailPage(props: ComicArticleProps) {

{props.titleName}

- - + +
+
+
+ +
+
+
+ + +
+

{title}

+
+
+ + +
+
+ +
+
+
+ +

作品詳細

+
+
+

タイトル : {title}

+

シリーズ : -

+

ジャンル : -

+
+
+

作成日 : -

+

最終更新日 : -

+
+
+

初公開イベント名 : -

+

初公開イベント日 : -

+
+
+
+ + +
+
+
+ + {' '} +
+

対象のマンガが見つかりませんでした

+

リンクが正確でなかったか、公開が停止された可能性があります

+

Comics Page Top のリンクから探すか、Maretolに連絡してください

+
+
+
+
+
+ +
+ © 2024 Maretol +
+ DO NOT REPOST WITHOUT PERMISSION +
+
+
+
+
+
+ ) +} diff --git a/pages/components/large/loading_comics.tsx b/pages/components/large/loading_comics.tsx index 4cb4329..e2a166a 100644 --- a/pages/components/large/loading_comics.tsx +++ b/pages/components/large/loading_comics.tsx @@ -1,3 +1,4 @@ +import { LoaderCircleIcon } from 'lucide-react' import { Card, CardContent } from '../ui/card' import { Skeleton } from '../ui/skeleton' @@ -30,3 +31,11 @@ export function LoadingComics() {
) } + +export function LoadingComicBook() { + return ( +
+ +
+ ) +} diff --git a/pages/components/middle/article_dom/comiccard.tsx b/pages/components/middle/article_dom/comiccard.tsx index d5cb896..3862ed4 100644 --- a/pages/components/middle/article_dom/comiccard.tsx +++ b/pages/components/middle/article_dom/comiccard.tsx @@ -4,7 +4,6 @@ import { getBandeDessineeByID } from '@/lib/api/workers' import { getNoImage, rewriteImageURL } from '@/lib/image' import { ogpImageOption } from '@/lib/static' import { convertJST } from '@/lib/time' -import { BandeDessineeConfig } from 'api-types' import Link from 'next/link' export default async function ComicPageCard({ link }: { link: string }) { @@ -21,10 +20,10 @@ export default async function ComicPageCard({ link }: { link: string }) { const series = data.series?.series_name || '-' const shortDescription = data.parsed_description[0].text const configURL = data.contents_url - const config = (await fetch(configURL).then((res) => res.json())) as BandeDessineeConfig const baseURL = configURL.replaceAll('index.json', '') - const coverURL = baseURL + config.cover + const sumnailImage = data.cover || data.filename + '_00' + data.first_page + data.format[0] + const coverURL = baseURL + sumnailImage return (
@@ -50,8 +49,10 @@ export default async function ComicPageCard({ link }: { link: string }) {

{shortDescription}

-
- +
+
+ Read this +
diff --git a/pages/components/middle/comicbook.tsx b/pages/components/middle/comicbook.tsx index 2aff497..53f8949 100644 --- a/pages/components/middle/comicbook.tsx +++ b/pages/components/middle/comicbook.tsx @@ -1,21 +1,17 @@ 'use client' import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import React, { use, useCallback, useMemo, useState } from 'react' import { Button } from '../ui/button' -import { getHeaderImage, rewriteImageURL } from '@/lib/image' -import { originImageOption } from '@/lib/static' +import { getHeaderImage } from '@/lib/image' import ClientImage from '../small/client_image' -import { preload } from 'react-dom' import ComicImage from '../small/comic_image' import { cn } from '@/lib/utils' import Link from 'next/link' +import { bandeDessineeResult } from 'api-types' type ComicBookProps = { - originPageSrc: string[] - coverPageSrc: string - backCoverPageSrc: string - startPageLeftRight: 'left' | 'right' + cmsResult: Promise } type pageState = { @@ -24,12 +20,31 @@ type pageState = { } export default function ComicBook(props: ComicBookProps) { - const { originPageSrc, coverPageSrc, backCoverPageSrc, startPageLeftRight } = props + const { cmsResult } = props + const data = use(cmsResult) + + const baseUrl = data.contents_url.replaceAll('/index.json', '') + const filename = data.filename + const startPage = data.first_page + const lastPage = data.last_page + const format = data.format[0] + const pageArray = Array.from({ length: lastPage - startPage + 1 }, (_, i) => i + startPage) + + const coverPageSrc = data.cover ? baseUrl + '/' + data.cover : null + const backCoverPageSrc = data.back_cover ? baseUrl + '/' + data.back_cover : null + const startPageLeftRight = data.first_left_right[0] + const originPageSrc = pageArray.map((i) => getPageImageSrc(baseUrl, filename, i, format)) + const headerImage = getHeaderImage() const [currentPage, setCurrentPage] = useState(0) const memoPageList = useMemo(() => { - const pageList = [{ position: 'center', src: coverPageSrc }] as pageState[] // 表紙 + const pageList: pageState[] = [] + // 表紙が指定済みの場合(ない場合スキップ + if (coverPageSrc) { + pageList.push({ position: 'center', src: coverPageSrc }) + } + if (startPageLeftRight === 'left') { // 本文1ページ目が左だった場合、最初のページは左だけで、そこから残りのページは2ページペアで処理する pageList.push({ position: 'left', src: originPageSrc[0] }) @@ -52,7 +67,11 @@ export default function ComicBook(props: ComicBookProps) { pageList.push({ position: 'pair', src: [originPageSrc[i], originPageSrc[i + 1]] }) } } - pageList.push({ position: 'center', src: backCoverPageSrc }) // 裏表紙 + // 裏表紙が指定済みの場合(ない場合スキップ + if (backCoverPageSrc) { + pageList.push({ position: 'center', src: backCoverPageSrc }) // 裏表紙 + } + return pageList }, [startPageLeftRight, coverPageSrc, backCoverPageSrc, originPageSrc]) @@ -92,12 +111,20 @@ export default function ComicBook(props: ComicBookProps) {
-
-
@@ -111,7 +138,7 @@ export default function ComicBook(props: ComicBookProps) { key={i} className={cn('h-full w-full flex justify-center items-center', i === currentPage ? '' : 'hidden')} > - +
) case 'pair': @@ -158,3 +185,9 @@ export default function ComicBook(props: ComicBookProps) { ) } + +function getPageImageSrc(baseUrl: string, filename: string, pageNumber: number, format: string) { + // 3桁まで0埋め + const pageNumberStr = pageNumber.toString().padStart(3, '0') + return `${baseUrl}/${filename}_${pageNumberStr}.${format}` +} diff --git a/pages/lib/api/workers.ts b/pages/lib/api/workers.ts index 0128651..375cfa7 100644 --- a/pages/lib/api/workers.ts +++ b/pages/lib/api/workers.ts @@ -143,14 +143,17 @@ async function getBandeDessineeByID(contentID: string, draftKey?: string) { const host = env.HOST const url = new URL(host + '/api/cms/bande_dessinee') url.searchParams.set('content_id', contentID) - if (draftKey) url.searchParams.set('draftKey', draftKey) + url.searchParams.set('draftKey', draftKey || 'undefined') const cmsAPIKey = env.CMS_FETCHER_API_KEY const request = new Request(url, { headers: { 'x-api-key': cmsAPIKey }, method: 'GET' }) - const res = await fetch(request, { next: { revalidate: revalidateTime } }) + console.log('fetch start') + const res = await fetch(request, { cache: 'no-store' }) + console.log('fetch finished') const data = (await res.json()) as bandeDessineeResult + console.log('data set') return data }