@roottale/cms-renderer-next 0.65.1 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/{blog-filter-qE9Lj49C.d.ts → blog-filter-CS4GZws8.d.ts} +7 -0
- package/dist/chunk-22MB5YER.js +122 -0
- package/dist/chunk-22MB5YER.js.map +1 -0
- package/dist/{chunk-GDP2VGJR.js → chunk-3UBTE7BV.js} +1 -1
- package/dist/chunk-3UBTE7BV.js.map +1 -0
- package/dist/cms-public.css +376 -228
- package/dist/fonts/LICENSE +94 -0
- package/dist/fonts/PretendardVariable.woff2 +0 -0
- package/dist/image-lightbox.d.ts +10 -0
- package/dist/image-lightbox.js +8 -0
- package/dist/image-lightbox.js.map +1 -0
- package/dist/routes.d.ts +94 -11
- package/dist/routes.js +117 -39
- package/dist/routes.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +17 -11
- package/dist/server.js.map +1 -1
- package/package.json +11 -6
- package/dist/chunk-GDP2VGJR.js.map +0 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Copyright (c) 2021, Kil Hyung-jin (https://github.com/orioncactus/pretendard),
|
|
2
|
+
with Reserved Font Name Pretendard.
|
|
3
|
+
|
|
4
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
5
|
+
This license is copied below, and is also available with a FAQ at:
|
|
6
|
+
https://scripts.sil.org/OFL
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
11
|
+
-----------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
PREAMBLE
|
|
14
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
15
|
+
development of collaborative font projects, to support the font creation
|
|
16
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
17
|
+
open framework in which fonts may be shared and improved in partnership
|
|
18
|
+
with others.
|
|
19
|
+
|
|
20
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
21
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
22
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
23
|
+
redistributed and/or sold with any software provided that any reserved
|
|
24
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
25
|
+
however, cannot be released under any other type of license. The
|
|
26
|
+
requirement for fonts to remain under this license does not apply
|
|
27
|
+
to any document created using the fonts or their derivatives.
|
|
28
|
+
|
|
29
|
+
DEFINITIONS
|
|
30
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
31
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
32
|
+
include source files, build scripts and documentation.
|
|
33
|
+
|
|
34
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
35
|
+
copyright statement(s).
|
|
36
|
+
|
|
37
|
+
"Original Version" refers to the collection of Font Software components as
|
|
38
|
+
distributed by the Copyright Holder(s).
|
|
39
|
+
|
|
40
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
41
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
42
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
43
|
+
new environment.
|
|
44
|
+
|
|
45
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
46
|
+
writer or other person who contributed to the Font Software.
|
|
47
|
+
|
|
48
|
+
PERMISSION & CONDITIONS
|
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
50
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
51
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
52
|
+
Software, subject to the following conditions:
|
|
53
|
+
|
|
54
|
+
1) Neither the Font Software nor any of its individual components,
|
|
55
|
+
in Original or Modified Versions, may be sold by itself.
|
|
56
|
+
|
|
57
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
58
|
+
redistributed and/or sold with any software, provided that each copy
|
|
59
|
+
contains the above copyright notice and this license. These can be
|
|
60
|
+
included either as stand-alone text files, human-readable headers or
|
|
61
|
+
in the appropriate machine-readable metadata fields within text or
|
|
62
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
63
|
+
|
|
64
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
65
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
66
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
67
|
+
presented to the users.
|
|
68
|
+
|
|
69
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
70
|
+
Software shall not be used to promote, endorse or advertise any
|
|
71
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
72
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
73
|
+
permission.
|
|
74
|
+
|
|
75
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
76
|
+
must be distributed entirely under this license, and must not be
|
|
77
|
+
distributed under any other license. The requirement for fonts to
|
|
78
|
+
remain under this license does not apply to any document created
|
|
79
|
+
using the Font Software.
|
|
80
|
+
|
|
81
|
+
TERMINATION
|
|
82
|
+
This license becomes null and void if any of the above conditions are
|
|
83
|
+
not met.
|
|
84
|
+
|
|
85
|
+
DISCLAIMER
|
|
86
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
87
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
88
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
89
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
90
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
91
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
92
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
93
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
94
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/** HTML·Tiptap 본문 모두에 적용한다. 이미 링크나 버튼인 이미지는 원래 동작을 유지한다. */
|
|
5
|
+
declare function RootTaleImageLightbox({ children, imageSelector }: {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
imageSelector?: string;
|
|
8
|
+
}): react_jsx_runtime.JSX.Element;
|
|
9
|
+
|
|
10
|
+
export { RootTaleImageLightbox };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CmsPostContent, RootTaleRedirect, CmsPostTranslationRef } from '@roottale/cms-client/server';
|
|
2
|
-
import { RouteCollection, RoutablePost } from '@roottale/cms-core';
|
|
3
|
-
export { RouteCollection, resolvePostCollection, resolvePostUrl } from '@roottale/cms-core';
|
|
4
|
-
import { B as BlogCategoryWithCount } from './blog-filter-
|
|
5
|
-
export { m as makeCategoryHref } from './blog-filter-
|
|
2
|
+
import { RouteCollection, MetadataRobotsPolicy, MetadataValues, MetadataResolutionInput, RoutablePost } from '@roottale/cms-core';
|
|
3
|
+
export { RouteCollection, normalizeRedirectPath, resolvePostCollection, resolvePostUrl } from '@roottale/cms-core';
|
|
4
|
+
import { B as BlogCategoryWithCount } from './blog-filter-CS4GZws8.js';
|
|
5
|
+
export { m as makeCategoryHref } from './blog-filter-CS4GZws8.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* `routes` 공통 — config 타입, collections resolve, fetch 헬퍼. feed/sitemap/llms/
|
|
@@ -428,12 +428,6 @@ interface RedirectMiddlewareConfig {
|
|
|
428
428
|
/** 규칙 캐시 TTL(ms). 기본 60_000 (1분). */
|
|
429
429
|
cacheTtlMs?: number;
|
|
430
430
|
}
|
|
431
|
-
/**
|
|
432
|
-
* 경로 정규화 — 앞 슬래시 보장, 뒤 슬래시 제거(루트 제외), 쿼리/해시 제거,
|
|
433
|
-
* percent-decode. 출발 경로 비교·한글 slug 매칭·루프 탐지에 요청·규칙 양쪽에
|
|
434
|
-
* 동일 적용한다. 쿼리 제거는 `/a → /a?x` 류의 무한 루프 방지에 중요.
|
|
435
|
-
*/
|
|
436
|
-
declare function normalizeRedirectPath(path: string): string;
|
|
437
431
|
/**
|
|
438
432
|
* 요청 경로에 맞는 규칙을 찾는다(순수 함수, fetch 비의존 — 테스트 용이).
|
|
439
433
|
* 정확 경로 일치만(와일드카드 없음).
|
|
@@ -541,6 +535,12 @@ interface PostMetadataInput {
|
|
|
541
535
|
authors?: readonly string[] | null;
|
|
542
536
|
}
|
|
543
537
|
interface BuildPostMetadataOptions {
|
|
538
|
+
/** 기존 canonical override는 기본 유지. 코드가 URL을 소유하는 사이트는 false로 지정한다. */
|
|
539
|
+
allowCanonicalOverride?: boolean;
|
|
540
|
+
/** 미리보기·비공개 등의 강제 제한은 직접 SEO 설정으로 해제하지 않는다. */
|
|
541
|
+
robots?: MetadataRobotsPolicy;
|
|
542
|
+
/** 글 값도 비었을 때 적용할 사이트 설명·공유 이미지. */
|
|
543
|
+
siteDefaults?: MetadataValues;
|
|
544
544
|
/**
|
|
545
545
|
* self-canonical 기본값 origin (예: `https://example.com`).
|
|
546
546
|
* `seo.canonical` override 가 있으면 무시된다. `path` 또는 `collections` 와
|
|
@@ -711,6 +711,89 @@ declare function buildPostMetadata(post: PostMetadataInput, options?: BuildPostM
|
|
|
711
711
|
*/
|
|
712
712
|
declare function buildHreflangLanguages(translations: readonly CmsPostTranslationRef[] | undefined, siteUrl: string, locales: PostLocaleOptions): Record<string, string> | undefined;
|
|
713
713
|
|
|
714
|
+
interface MetadataGoogleBotPolicy {
|
|
715
|
+
index?: boolean;
|
|
716
|
+
follow?: boolean;
|
|
717
|
+
noimageindex?: boolean;
|
|
718
|
+
"max-image-preview"?: "none" | "standard" | "large";
|
|
719
|
+
"max-snippet"?: number;
|
|
720
|
+
"max-video-preview"?: number;
|
|
721
|
+
}
|
|
722
|
+
interface BuildPageMetadataOptions {
|
|
723
|
+
siteUrl?: string;
|
|
724
|
+
path?: string;
|
|
725
|
+
/** 문자열은 사이트의 최종 정책. null이면 생략하고, undefined이면 siteUrl+path를 사용한다. */
|
|
726
|
+
canonical?: string | null;
|
|
727
|
+
/** 예: " | 사이트 이름". 이미 붙어 있거나 제목이 사이트 이름이면 다시 붙이지 않는다. */
|
|
728
|
+
titleSuffix?: string;
|
|
729
|
+
openGraphType?: "website" | "article";
|
|
730
|
+
/** 사이트가 기존에 적용하던 봇별 제한. false 제한은 CMS 설정으로 해제하지 않는다. */
|
|
731
|
+
googleBot?: MetadataGoogleBotPolicy;
|
|
732
|
+
/** 실제 SSR 결과와 현재 저장 버전을 대조하는 공개 확인 표식. */
|
|
733
|
+
verification?: {
|
|
734
|
+
routeKey: string;
|
|
735
|
+
locale: string;
|
|
736
|
+
version?: string;
|
|
737
|
+
contractChecksum?: string;
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
interface PageMetadata {
|
|
741
|
+
title: {
|
|
742
|
+
absolute: string;
|
|
743
|
+
};
|
|
744
|
+
description?: string;
|
|
745
|
+
alternates?: {
|
|
746
|
+
canonical: string;
|
|
747
|
+
};
|
|
748
|
+
robots: {
|
|
749
|
+
index: boolean;
|
|
750
|
+
follow: boolean;
|
|
751
|
+
googleBot?: MetadataGoogleBotPolicy;
|
|
752
|
+
};
|
|
753
|
+
openGraph: {
|
|
754
|
+
type: "website" | "article";
|
|
755
|
+
title: string;
|
|
756
|
+
description?: string;
|
|
757
|
+
url?: string;
|
|
758
|
+
images?: {
|
|
759
|
+
url: string;
|
|
760
|
+
alt: string;
|
|
761
|
+
}[];
|
|
762
|
+
};
|
|
763
|
+
twitter: {
|
|
764
|
+
card: "summary_large_image";
|
|
765
|
+
title: string;
|
|
766
|
+
description?: string;
|
|
767
|
+
images?: {
|
|
768
|
+
url: string;
|
|
769
|
+
alt: string;
|
|
770
|
+
}[];
|
|
771
|
+
};
|
|
772
|
+
other?: Record<string, string>;
|
|
773
|
+
}
|
|
774
|
+
/** 글·분류·코드 페이지의 동일한 최종값을 Next Metadata로 변환한다. 네트워크 호출은 없다. */
|
|
775
|
+
declare function buildPageMetadata(input: MetadataResolutionInput, options?: BuildPageMetadataOptions): PageMetadata;
|
|
776
|
+
|
|
777
|
+
interface SitemapMetadataPolicy {
|
|
778
|
+
/** SSR buildPageMetadata에 전달한 것과 같은 입력을 사용한다. */
|
|
779
|
+
metadata?: MetadataResolutionInput;
|
|
780
|
+
/** 사이트의 최종 canonical. 다른 URL이 대표 주소면 현재 entry를 제외한다. */
|
|
781
|
+
canonical?: string | null;
|
|
782
|
+
published?: boolean;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* 이미 조회한 메타데이터·활성 이동 규칙을 사용한다. 네트워크 호출을 하지 않으며
|
|
786
|
+
* 조회 실패와 빈 설정을 구별하는 책임은 호출자에게 있다(미확인 값은 기존 포함 유지).
|
|
787
|
+
*/
|
|
788
|
+
declare function filterMetadataSitemapEntries<T extends {
|
|
789
|
+
url: string;
|
|
790
|
+
}>(entries: readonly T[], policyFor: (entry: T) => SitemapMetadataPolicy | undefined, options?: {
|
|
791
|
+
redirects?: readonly {
|
|
792
|
+
fromPath: string;
|
|
793
|
+
enabled?: boolean;
|
|
794
|
+
}[];
|
|
795
|
+
}): T[];
|
|
796
|
+
|
|
714
797
|
/**
|
|
715
798
|
* 글의 공개 URL — ADR-0105 Amendment 1.
|
|
716
799
|
*
|
|
@@ -1076,4 +1159,4 @@ interface FleetInfoRouteConfig {
|
|
|
1076
1159
|
*/
|
|
1077
1160
|
declare function createFleetInfoRoute(config?: FleetInfoRouteConfig): () => Promise<Response>;
|
|
1078
1161
|
|
|
1079
|
-
export { ARCHIVE_POST_COUNT_LIMIT, type AlwaysNoindexArchiveKind, type ArchiveCategoriesConfig, type ArchiveCategoriesOptions, type ArchiveIndexDecision, type ArchiveIndexInput, type ArchiveKind, type ArchiveMetadata, type ArchiveMetadataFields, type ArchiveMetadataInput, type ArchivePromotion, type BuildArchiveMetadataOptions, type BuildPostMetadataOptions, type CollectionsInput, type ExposureRouteConfig, type FleetInfoRouteConfig, type ImageResponseLike, type LlmsTxtLink, type LlmsTxtRouteConfig, type LlmsTxtSection, OG_IMAGE_CONTENT_TYPE, OG_IMAGE_SIZE, type OgImageRouteConfig, type PostLocaleOptions, type PostMetadata, type PostMetadataInput, type PostSeoOverrides, type PreviewMetadata, type PromotableArchiveKind, type PublicUrlPost, type RedirectMiddlewareConfig, type RevalidateRejectReason, type RevalidateRelatedPathsContext, type RevalidateRouteConfig, type RobotsMeta, type RootTaleSiteConfig, type SitemapEntry, type SitemapIndexOptions, type SitemapSectionId, buildArchiveMetadata, buildHreflangLanguages, buildPostMetadata, buildPreviewMetadata, createExposureRoute, createFeedRoute, createFleetInfoRoute, createLlmsTxtRoute, createPostOgImage, createRedirectMiddleware, createRevalidateRoute, createRevalidateRouteHandlers, createSitemap, createSitemapIndex, decideArchiveIndex, fetchBlogArchiveCategories, fetchBlogArchivePosts,
|
|
1162
|
+
export { ARCHIVE_POST_COUNT_LIMIT, type AlwaysNoindexArchiveKind, type ArchiveCategoriesConfig, type ArchiveCategoriesOptions, type ArchiveIndexDecision, type ArchiveIndexInput, type ArchiveKind, type ArchiveMetadata, type ArchiveMetadataFields, type ArchiveMetadataInput, type ArchivePromotion, type BuildArchiveMetadataOptions, type BuildPageMetadataOptions, type BuildPostMetadataOptions, type CollectionsInput, type ExposureRouteConfig, type FleetInfoRouteConfig, type ImageResponseLike, type LlmsTxtLink, type LlmsTxtRouteConfig, type LlmsTxtSection, type MetadataGoogleBotPolicy, OG_IMAGE_CONTENT_TYPE, OG_IMAGE_SIZE, type OgImageRouteConfig, type PageMetadata, type PostLocaleOptions, type PostMetadata, type PostMetadataInput, type PostSeoOverrides, type PreviewMetadata, type PromotableArchiveKind, type PublicUrlPost, type RedirectMiddlewareConfig, type RevalidateRejectReason, type RevalidateRelatedPathsContext, type RevalidateRouteConfig, type RobotsMeta, type RootTaleSiteConfig, type SitemapEntry, type SitemapIndexOptions, type SitemapMetadataPolicy, type SitemapSectionId, buildArchiveMetadata, buildHreflangLanguages, buildPageMetadata, buildPostMetadata, buildPreviewMetadata, createExposureRoute, createFeedRoute, createFleetInfoRoute, createLlmsTxtRoute, createPostOgImage, createRedirectMiddleware, createRevalidateRoute, createRevalidateRouteHandlers, createSitemap, createSitemapIndex, decideArchiveIndex, fetchBlogArchiveCategories, fetchBlogArchivePosts, filterMetadataSitemapEntries, matchRedirect, postRedirectPath, resolveAuthorPromotion, resolveCategoryPromotion, resolveCollections, resolveCollectionsResult, resolveRobotsMeta, safeFetchPosts, storedPostPath, trimSlash };
|
package/dist/routes.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
safeFetchPosts,
|
|
11
11
|
storedPostPath,
|
|
12
12
|
trimSlash
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-3UBTE7BV.js";
|
|
14
14
|
import {
|
|
15
15
|
exposureHomePaths,
|
|
16
16
|
isExposureHome
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from "@roottale/cms-core";
|
|
31
31
|
|
|
32
32
|
// src/routes-metadata.ts
|
|
33
|
-
import { resolvePostPath } from "@roottale/cms-core";
|
|
33
|
+
import { resolveMetadata, resolvePostPath } from "@roottale/cms-core";
|
|
34
34
|
function resolveRobotsMeta(flags) {
|
|
35
35
|
if (!flags.noindex && !flags.nofollow) return void 0;
|
|
36
36
|
return { index: !flags.noindex, follow: !flags.nofollow };
|
|
@@ -45,10 +45,17 @@ function buildPreviewMetadata(input) {
|
|
|
45
45
|
}
|
|
46
46
|
function buildPostMetadata(post, options = {}) {
|
|
47
47
|
const seo = post.seo ?? void 0;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const resolved = resolveMetadata({
|
|
49
|
+
overrides: seo,
|
|
50
|
+
defaults: { title: post.title, ...post.description ? { description: post.description } : {}, ...post.image ? { ogImage: post.image } : {} },
|
|
51
|
+
siteDefaults: options.siteDefaults,
|
|
52
|
+
robots: options.robots,
|
|
53
|
+
imageBaseUrl: options.siteUrl
|
|
54
|
+
});
|
|
55
|
+
const title = resolved.title ?? post.title;
|
|
56
|
+
const description = resolved.description;
|
|
57
|
+
const ogImage = resolved.ogImage;
|
|
58
|
+
let canonical = options.allowCanonicalOverride === false ? void 0 : seo?.canonical || void 0;
|
|
52
59
|
if (!canonical && options.siteUrl) {
|
|
53
60
|
let path = options.path ?? storedPostPath({
|
|
54
61
|
path: post.path,
|
|
@@ -75,7 +82,7 @@ function buildPostMetadata(post, options = {}) {
|
|
|
75
82
|
if (post.tags && post.tags.length > 0) openGraph.tags = [...post.tags];
|
|
76
83
|
if (post.authors && post.authors.length > 0) openGraph.authors = [...post.authors];
|
|
77
84
|
if (ogImage) {
|
|
78
|
-
openGraph.images = [{ url: ogImage, alt:
|
|
85
|
+
openGraph.images = [{ url: ogImage, alt: resolved.ogImageAlt ?? title }];
|
|
79
86
|
}
|
|
80
87
|
const meta = { title, openGraph };
|
|
81
88
|
if (description) meta.description = description;
|
|
@@ -101,8 +108,8 @@ function buildPostMetadata(post, options = {}) {
|
|
|
101
108
|
};
|
|
102
109
|
}
|
|
103
110
|
const baseRobots = resolveRobotsMeta({
|
|
104
|
-
...
|
|
105
|
-
...
|
|
111
|
+
...resolved.noindex !== void 0 && { noindex: resolved.noindex },
|
|
112
|
+
...resolved.nofollow !== void 0 && { nofollow: resolved.nofollow }
|
|
106
113
|
});
|
|
107
114
|
const index = baseRobots?.index ?? true;
|
|
108
115
|
const follow = baseRobots?.follow ?? true;
|
|
@@ -209,7 +216,9 @@ import {
|
|
|
209
216
|
import {
|
|
210
217
|
isInCollectionMode,
|
|
211
218
|
resolveCategoryPath,
|
|
212
|
-
resolvePostCollection as resolvePostCollection2
|
|
219
|
+
resolvePostCollection as resolvePostCollection2,
|
|
220
|
+
readMetadataValues,
|
|
221
|
+
resolveMetadata as resolveMetadata2
|
|
213
222
|
} from "@roottale/cms-core";
|
|
214
223
|
|
|
215
224
|
// src/archive-categories.ts
|
|
@@ -251,6 +260,11 @@ async function fetchServerCategoryCounts(config, options) {
|
|
|
251
260
|
...category.description ? { description: category.description } : {},
|
|
252
261
|
...category.seoTitle ? { seoTitle: category.seoTitle } : {},
|
|
253
262
|
...category.seoDescription ? { seoDescription: category.seoDescription } : {},
|
|
263
|
+
...category.seoOgImage ? { seoOgImage: category.seoOgImage } : {},
|
|
264
|
+
...category.seoOgImageAlt ? { seoOgImageAlt: category.seoOgImageAlt } : {},
|
|
265
|
+
...typeof category.seoNofollow === "boolean" ? { seoNofollow: category.seoNofollow } : {},
|
|
266
|
+
...typeof category.hubPromoted === "boolean" ? { hubPromoted: category.hubPromoted } : {},
|
|
267
|
+
...category.path ? { path: category.path } : {},
|
|
254
268
|
...category.imageUrl ? { imageUrl: category.imageUrl } : {},
|
|
255
269
|
...category.imageAlt ? { imageAlt: category.imageAlt } : {}
|
|
256
270
|
}));
|
|
@@ -347,6 +361,7 @@ function postEntries(config, siteUrl, posts, collections, settings, defaultLocal
|
|
|
347
361
|
const inCollection = isInCollectionMode(collections);
|
|
348
362
|
const blogBase = config.blogBasePath ?? "/blog";
|
|
349
363
|
return posts.flatMap((p) => {
|
|
364
|
+
if (resolveMetadata2({ overrides: readMetadataValues(p.metaJson.seo) }).noindex === true) return [];
|
|
350
365
|
const url = postPublicUrl(p, {
|
|
351
366
|
siteUrl,
|
|
352
367
|
collections,
|
|
@@ -627,6 +642,15 @@ function modelPathMismatch(body, collections) {
|
|
|
627
642
|
function createRevalidateRoute(config) {
|
|
628
643
|
const blogBase = config.blogBasePath ?? "/blog";
|
|
629
644
|
const maxBodyBytes = config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
645
|
+
const INVARIANTS = [
|
|
646
|
+
"/feed.xml",
|
|
647
|
+
"/llms.txt",
|
|
648
|
+
"/sitemap.xml",
|
|
649
|
+
"/sitemap/static.xml",
|
|
650
|
+
"/sitemap/blog.xml",
|
|
651
|
+
"/sitemap/categories.xml",
|
|
652
|
+
"/sitemap/authors.xml"
|
|
653
|
+
];
|
|
630
654
|
return async function POST(req) {
|
|
631
655
|
const apiKey = config.apiKey?.trim();
|
|
632
656
|
if (!apiKey || apiKey === "local_unconfigured") {
|
|
@@ -657,7 +681,7 @@ function createRevalidateRoute(config) {
|
|
|
657
681
|
}
|
|
658
682
|
}
|
|
659
683
|
const parsed = body ?? { paths: [], postId: null, modelKey: null };
|
|
660
|
-
if (result.event === "theme.updated") {
|
|
684
|
+
if (result.event === "theme.updated" || result.event === "taxonomy.updated") {
|
|
661
685
|
const themeTag = config.themeTag ?? THEME_CACHE_TAG;
|
|
662
686
|
const requestedTags = [
|
|
663
687
|
.../* @__PURE__ */ new Set([
|
|
@@ -665,7 +689,7 @@ function createRevalidateRoute(config) {
|
|
|
665
689
|
...SETTINGS_CACHE_TAGS.filter((t) => t !== THEME_CACHE_TAG)
|
|
666
690
|
])
|
|
667
691
|
];
|
|
668
|
-
const paths2 = parsed.paths;
|
|
692
|
+
const paths2 = result.event === "taxonomy.updated" ? [.../* @__PURE__ */ new Set([...parsed.paths, ...INVARIANTS, ...config.alsoRevalidate ?? []])] : parsed.paths;
|
|
669
693
|
const failed = [];
|
|
670
694
|
const attempt = async (target, run) => {
|
|
671
695
|
try {
|
|
@@ -674,7 +698,7 @@ function createRevalidateRoute(config) {
|
|
|
674
698
|
const reason = e instanceof Error ? e.message : String(e);
|
|
675
699
|
failed.push({ target, reason });
|
|
676
700
|
console.error(
|
|
677
|
-
`[@roottale/cms-renderer-next/routes]
|
|
701
|
+
`[@roottale/cms-renderer-next/routes] ${result.event} \uBB34\uD6A8\uD654 \uC2E4\uD328(${target}):`,
|
|
678
702
|
reason
|
|
679
703
|
);
|
|
680
704
|
}
|
|
@@ -709,17 +733,6 @@ function createRevalidateRoute(config) {
|
|
|
709
733
|
failed.length === 0 ? 200 : 500
|
|
710
734
|
);
|
|
711
735
|
}
|
|
712
|
-
const INVARIANTS = [
|
|
713
|
-
"/feed.xml",
|
|
714
|
-
"/llms.txt",
|
|
715
|
-
"/sitemap.xml",
|
|
716
|
-
"/sitemap/static.xml",
|
|
717
|
-
"/sitemap/blog.xml",
|
|
718
|
-
"/sitemap/categories.xml",
|
|
719
|
-
// 작가 사이트맵 — 글 발행/해제로 작가 등장·lastmod 가 바뀐다(authors 토글 off 면
|
|
720
|
-
// 빈 사이트맵이라 무효화해도 무해).
|
|
721
|
-
"/sitemap/authors.xml"
|
|
722
|
-
];
|
|
723
736
|
let alsoRevalidate;
|
|
724
737
|
let warning;
|
|
725
738
|
let knownCollections;
|
|
@@ -941,18 +954,9 @@ function createPostOgImage(config, deps) {
|
|
|
941
954
|
}
|
|
942
955
|
|
|
943
956
|
// src/routes-redirects.ts
|
|
957
|
+
import { normalizeRedirectPath, normalizeRedirectSource, normalizeRedirectTarget, isProtectedRedirectPath } from "@roottale/cms-core";
|
|
958
|
+
import { normalizeRedirectPath as normalizeRedirectPath2 } from "@roottale/cms-core";
|
|
944
959
|
import { fetchRedirects } from "@roottale/cms-client/server";
|
|
945
|
-
function normalizeRedirectPath(path) {
|
|
946
|
-
let p = path;
|
|
947
|
-
try {
|
|
948
|
-
p = decodeURIComponent(path);
|
|
949
|
-
} catch {
|
|
950
|
-
}
|
|
951
|
-
p = p.split(/[?#]/)[0] ?? p;
|
|
952
|
-
if (!p.startsWith("/")) p = `/${p}`;
|
|
953
|
-
if (p.length > 1) p = p.replace(/\/+$/, "");
|
|
954
|
-
return p === "" ? "/" : p;
|
|
955
|
-
}
|
|
956
960
|
function isAbsoluteUrl(target) {
|
|
957
961
|
return /^https?:\/\//i.test(target);
|
|
958
962
|
}
|
|
@@ -967,10 +971,13 @@ function internalTargetPath(target, origin) {
|
|
|
967
971
|
}
|
|
968
972
|
}
|
|
969
973
|
function matchRedirect(redirects, pathname, origin) {
|
|
974
|
+
if (isProtectedRedirectPath(pathname)) return null;
|
|
970
975
|
const byFrom = /* @__PURE__ */ new Map();
|
|
971
976
|
for (const r of redirects) {
|
|
972
|
-
const
|
|
973
|
-
|
|
977
|
+
const source = normalizeRedirectSource(r.fromPath);
|
|
978
|
+
const target = normalizeRedirectTarget(r.toTarget);
|
|
979
|
+
if (!source.ok || !target.ok) continue;
|
|
980
|
+
if (!byFrom.has(source.value)) byFrom.set(source.value, { ...r, toTarget: target.value });
|
|
974
981
|
}
|
|
975
982
|
const start = normalizeRedirectPath(pathname);
|
|
976
983
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
@@ -1042,6 +1049,75 @@ function createRedirectMiddleware(config) {
|
|
|
1042
1049
|
};
|
|
1043
1050
|
}
|
|
1044
1051
|
|
|
1052
|
+
// src/routes-page-metadata.ts
|
|
1053
|
+
import { resolveMetadata as resolveMetadata3 } from "@roottale/cms-core";
|
|
1054
|
+
function buildPageMetadata(input, options = {}) {
|
|
1055
|
+
const canonical = options.canonical === void 0 ? options.siteUrl && options.path ? new URL(options.path, options.siteUrl).href : void 0 : options.canonical ?? void 0;
|
|
1056
|
+
const values = resolveMetadata3({ ...input, imageBaseUrl: input.imageBaseUrl ?? options.siteUrl ?? canonical });
|
|
1057
|
+
const baseTitle = values.title ?? "";
|
|
1058
|
+
const suffix = options.titleSuffix ?? "";
|
|
1059
|
+
const siteName = suffix.replace(/^[\s|·—–\-:]+/, "").trim();
|
|
1060
|
+
const title = suffix && baseTitle !== siteName && !baseTitle.endsWith(suffix) ? `${baseTitle}${suffix}` : baseTitle;
|
|
1061
|
+
const description = values.description;
|
|
1062
|
+
const images = values.ogImage ? [{ url: values.ogImage, alt: values.ogImageAlt ?? title }] : void 0;
|
|
1063
|
+
const index = values.noindex !== true;
|
|
1064
|
+
const follow = values.nofollow !== true;
|
|
1065
|
+
return {
|
|
1066
|
+
title: { absolute: title },
|
|
1067
|
+
...description ? { description } : {},
|
|
1068
|
+
...canonical ? { alternates: { canonical } } : {},
|
|
1069
|
+
robots: {
|
|
1070
|
+
index,
|
|
1071
|
+
follow,
|
|
1072
|
+
...options.googleBot ? { googleBot: {
|
|
1073
|
+
...options.googleBot,
|
|
1074
|
+
index: index && options.googleBot.index !== false,
|
|
1075
|
+
follow: follow && options.googleBot.follow !== false
|
|
1076
|
+
} } : {}
|
|
1077
|
+
},
|
|
1078
|
+
openGraph: {
|
|
1079
|
+
type: options.openGraphType ?? "website",
|
|
1080
|
+
title,
|
|
1081
|
+
...description ? { description } : {},
|
|
1082
|
+
...canonical ? { url: canonical } : {},
|
|
1083
|
+
...images ? { images } : {}
|
|
1084
|
+
},
|
|
1085
|
+
twitter: { card: "summary_large_image", title, ...description ? { description } : {}, ...images ? { images } : {} },
|
|
1086
|
+
...options.verification ? { other: {
|
|
1087
|
+
"rt:metadata-route-key": options.verification.routeKey,
|
|
1088
|
+
"rt:metadata-locale": options.verification.locale,
|
|
1089
|
+
...options.verification.version ? { "rt:metadata-version": options.verification.version } : {},
|
|
1090
|
+
...options.verification.contractChecksum ? { "rt:metadata-contract": options.verification.contractChecksum } : {}
|
|
1091
|
+
} } : {}
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// src/routes-metadata-sitemap.ts
|
|
1096
|
+
import { resolveMetadata as resolveMetadata4 } from "@roottale/cms-core";
|
|
1097
|
+
function filterMetadataSitemapEntries(entries, policyFor, options = {}) {
|
|
1098
|
+
const redirected = new Set((options.redirects ?? []).filter((rule) => rule.enabled !== false).map((rule) => normalizeRedirectPath2(rule.fromPath)));
|
|
1099
|
+
return entries.filter((entry) => {
|
|
1100
|
+
let url;
|
|
1101
|
+
try {
|
|
1102
|
+
url = new URL(entry.url);
|
|
1103
|
+
} catch {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) return false;
|
|
1107
|
+
if (redirected.has(normalizeRedirectPath2(url.pathname))) return false;
|
|
1108
|
+
const policy = policyFor(entry);
|
|
1109
|
+
if (policy?.published === false || resolveMetadata4(policy?.metadata ?? {}).noindex === true) return false;
|
|
1110
|
+
if (policy?.canonical) {
|
|
1111
|
+
try {
|
|
1112
|
+
if (new URL(policy.canonical, url).href !== url.href) return false;
|
|
1113
|
+
} catch {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return true;
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1045
1121
|
// src/routes.ts
|
|
1046
1122
|
function postRedirectPath(post, requestedSlug, blogBasePath = "/blog") {
|
|
1047
1123
|
let requested = requestedSlug;
|
|
@@ -1060,7 +1136,7 @@ function createFleetInfoRoute(config = {}) {
|
|
|
1060
1136
|
site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
|
|
1061
1137
|
framework: "next",
|
|
1062
1138
|
cms_client: CMS_CLIENT_VERSION,
|
|
1063
|
-
cms_renderer: true ? "0.
|
|
1139
|
+
cms_renderer: true ? "0.67.0" : "dev",
|
|
1064
1140
|
build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null,
|
|
1065
1141
|
...config.exposures ? { exposures: config.exposures } : {}
|
|
1066
1142
|
},
|
|
@@ -1074,6 +1150,7 @@ export {
|
|
|
1074
1150
|
OG_IMAGE_SIZE,
|
|
1075
1151
|
buildArchiveMetadata,
|
|
1076
1152
|
buildHreflangLanguages,
|
|
1153
|
+
buildPageMetadata,
|
|
1077
1154
|
buildPostMetadata,
|
|
1078
1155
|
buildPreviewMetadata,
|
|
1079
1156
|
createExposureRoute,
|
|
@@ -1089,9 +1166,10 @@ export {
|
|
|
1089
1166
|
decideArchiveIndex,
|
|
1090
1167
|
fetchBlogArchiveCategories,
|
|
1091
1168
|
fetchBlogArchivePosts,
|
|
1169
|
+
filterMetadataSitemapEntries,
|
|
1092
1170
|
makeCategoryHref,
|
|
1093
1171
|
matchRedirect,
|
|
1094
|
-
normalizeRedirectPath,
|
|
1172
|
+
normalizeRedirectPath2 as normalizeRedirectPath,
|
|
1095
1173
|
postRedirectPath,
|
|
1096
1174
|
resolveAuthorPromotion,
|
|
1097
1175
|
resolveCategoryPromotion,
|