@zeropress/build-core 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -205,6 +205,17 @@ Build-core now derives:
205
205
  - `reading_time`
206
206
  - `comments_html`
207
207
 
208
+ Comment shell rendering is derived from preview-data policy:
209
+
210
+ - `site.disallowComments`
211
+ - `content.posts[].allow_comments`
212
+
213
+ The canonical `preview-data v0.4` site contract uses:
214
+
215
+ - `site.locale`
216
+ - `site.timezone`
217
+ - `site.disallowComments`
218
+
208
219
  Optional route templates behave as rendering capabilities, not guaranteed outputs:
209
220
 
210
221
  - `archive.html`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeropress/build-core",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Shared ZeroPress build and partial-render core",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "node": ">=18.18.0"
34
34
  },
35
35
  "dependencies": {
36
- "@zeropress/preview-data-validator": "^0.3.0",
36
+ "@zeropress/preview-data-validator": "^0.3.1",
37
37
  "@zeropress/theme-validator": "^0.2.2"
38
38
  }
39
39
  }
@@ -16,8 +16,6 @@ const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
16
16
  const DEFAULT_TIME_FORMAT = 'HH:mm';
17
17
  const DEFAULT_TIMEZONE = 'UTC';
18
18
  const DEFAULT_LOCALE = 'en-US';
19
- const COMMENTS_HTML = '<section id="comments"></section>';
20
-
21
19
  export async function buildSite(input) {
22
20
  const options = { ...DEFAULT_OPTIONS, ...(input.options || {}) };
23
21
  const state = await createBuildState(input, options);
@@ -58,8 +56,10 @@ export async function buildSite(input) {
58
56
 
59
57
  if (options.generateSpecialFiles) {
60
58
  await maybeRenderNotFoundPage(state);
61
- await writeOutput(state.writer, state.summaries, 'sitemap.xml', buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt), 'application/xml');
62
- await writeOutput(state.writer, state.summaries, 'feed.xml', buildFeedXml(state.previewData.site, state.emitted, state.generatedAt), 'application/rss+xml');
59
+ if (hasCanonicalSiteUrl(state.previewData.site.url)) {
60
+ await writeOutput(state.writer, state.summaries, 'sitemap.xml', buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt), 'application/xml');
61
+ await writeOutput(state.writer, state.summaries, 'feed.xml', buildFeedXml(state.previewData.site, state.emitted, state.generatedAt), 'application/rss+xml');
62
+ }
63
63
  await writeOutput(state.writer, state.summaries, 'robots.txt', buildRobotsTxt(state.previewData.site), 'text/plain');
64
64
  await writeOutput(state.writer, state.summaries, 'meta.json', buildMetaJson(state.previewData.site, state.emitted, state.generatedAt), 'application/json');
65
65
  }
@@ -278,8 +278,9 @@ function normalizePreviewData(previewData) {
278
278
  : DEFAULT_POSTS_PER_PAGE,
279
279
  dateFormat: normalizeNonEmptyString(previewData.site.dateFormat, DEFAULT_DATE_FORMAT),
280
280
  timeFormat: typeof previewData.site.timeFormat === 'string' ? previewData.site.timeFormat : DEFAULT_TIME_FORMAT,
281
- siteTimezone: normalizeNonEmptyString(previewData.site.siteTimezone, DEFAULT_TIMEZONE),
282
- siteLocale: normalizeLocale(previewData.site.siteLocale || previewData.site.language || DEFAULT_LOCALE),
281
+ timezone: normalizeNonEmptyString(previewData.site.timezone, DEFAULT_TIMEZONE),
282
+ locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
283
+ disallowComments: previewData.site.disallowComments === true,
283
284
  },
284
285
  content: {
285
286
  ...previewData.content,
@@ -370,10 +371,52 @@ function preparePost(post, site, categoriesBySlug, tagsBySlug) {
370
371
  reading_time: calculateReadingTime(post.html),
371
372
  categories_html: renderInlineTaxonomyLinks(post.category_slugs, categoriesBySlug, 'category'),
372
373
  tags_html: renderInlineTaxonomyLinks(post.tag_slugs, tagsBySlug, 'tag'),
373
- comments_html: COMMENTS_HTML,
374
+ comments_html: renderCommentsContainer(site, post),
374
375
  };
375
376
  }
376
377
 
378
+ function renderCommentsContainer(site, post) {
379
+ if (site.disallowComments === true || post.allow_comments !== true) {
380
+ return '';
381
+ }
382
+
383
+ const safePostId = encodeURIComponent(post.id);
384
+ return `<section id="comments" class="zp-comments">
385
+ <h2>댓글</h2>
386
+ <div id="comment-list"
387
+ hx-get="/api/comments/${safePostId}"
388
+ hx-trigger="load"
389
+ hx-swap="innerHTML">
390
+ <p class="zp-comments-loading">댓글을 불러오는 중...</p>
391
+ </div>
392
+ <form id="comment-form"
393
+ hx-post="/api/comments/${safePostId}"
394
+ hx-target="#comment-form-result"
395
+ hx-swap="innerHTML">
396
+ <div id="comment-form-result"></div>
397
+ <div>
398
+ <label for="author_name">이름</label>
399
+ <input type="text" id="author_name" name="author_name" required>
400
+ </div>
401
+ <div>
402
+ <label for="author_email">이메일</label>
403
+ <input type="email" id="author_email" name="author_email" required>
404
+ </div>
405
+ <div>
406
+ <label for="content">댓글</label>
407
+ <textarea id="content" name="content" required minlength="1" maxlength="5000"></textarea>
408
+ </div>
409
+ <div style="position:absolute;left:-9999px;" aria-hidden="true">
410
+ <input type="text" name="website" tabindex="-1" autocomplete="off">
411
+ </div>
412
+ <button type="submit">댓글 작성</button>
413
+ </form>
414
+ <noscript>
415
+ <p>댓글 기능을 사용하려면 JavaScript를 활성화해 주세요.</p>
416
+ </noscript>
417
+ </section>`;
418
+ }
419
+
377
420
  function buildTaxonomyRoutes(options) {
378
421
  const routes = [];
379
422
 
@@ -590,10 +633,10 @@ function renderPagination(paginationData) {
590
633
 
591
634
  function formatTimestamp(value, site) {
592
635
  const date = toDate(value);
593
- const locale = normalizeLocale(site.siteLocale || site.language || DEFAULT_LOCALE);
636
+ const locale = normalizeLocale(site.locale || DEFAULT_LOCALE);
594
637
  const dateFormat = normalizeNonEmptyString(site.dateFormat, DEFAULT_DATE_FORMAT);
595
638
  const timeFormat = typeof site.timeFormat === 'string' ? site.timeFormat : DEFAULT_TIME_FORMAT;
596
- const siteTimezone = normalizeNonEmptyString(site.siteTimezone, DEFAULT_TIMEZONE);
639
+ const siteTimezone = normalizeNonEmptyString(site.timezone, DEFAULT_TIMEZONE);
597
640
 
598
641
  const dateParts = new Intl.DateTimeFormat(locale, {
599
642
  timeZone: siteTimezone,
@@ -773,7 +816,7 @@ function createRenderContext(site, currentUrl) {
773
816
  return {
774
817
  site,
775
818
  currentUrl,
776
- language: site.language,
819
+ language: site.locale,
777
820
  };
778
821
  }
779
822
 
@@ -879,7 +922,7 @@ function buildFeedXml(site, emitted, generatedAt) {
879
922
  })
880
923
  .join('\n');
881
924
 
882
- return `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">\n <channel>\n <title>${escapeXml(site.title)}</title>\n <link>${escapeXml(channelLink)}</link>\n <description>${escapeXml(site.description)}</description>\n <language>${site.language}</language>\n <lastBuildDate>${generatedAt.toUTCString()}</lastBuildDate>\n <atom:link href="${escapeXml(selfLink)}" rel="self" type="application/rss+xml" />\n${items}\n </channel>\n</rss>`;
925
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">\n <channel>\n <title>${escapeXml(site.title)}</title>\n <link>${escapeXml(channelLink)}</link>\n <description>${escapeXml(site.description)}</description>\n <language>${site.locale}</language>\n <lastBuildDate>${generatedAt.toUTCString()}</lastBuildDate>\n <atom:link href="${escapeXml(selfLink)}" rel="self" type="application/rss+xml" />\n${items}\n </channel>\n</rss>`;
883
926
  }
884
927
 
885
928
  function buildRobotsTxt(site) {
@@ -915,7 +958,7 @@ function buildMetaJson(site, emitted, generatedAt) {
915
958
  title: site.title,
916
959
  description: site.description,
917
960
  url: site.url,
918
- language: site.language,
961
+ locale: site.locale,
919
962
  },
920
963
  pages,
921
964
  }, null, 2);
@@ -983,6 +1026,10 @@ function resolveSiteUrl(siteUrl, relativePath) {
983
1026
  return decodeURI(new URL(relativePath, siteUrl).toString());
984
1027
  }
985
1028
 
1029
+ function hasCanonicalSiteUrl(siteUrl) {
1030
+ return typeof siteUrl === 'string' && siteUrl.trim() !== '';
1031
+ }
1032
+
986
1033
  function toDate(value) {
987
1034
  return value ? new Date(value) : new Date();
988
1035
  }