Skip to content

Commit 7e9f56c

Browse files
committed
update
1 parent fdac953 commit 7e9f56c

5 files changed

Lines changed: 96 additions & 33 deletions

File tree

src/components/PostTemplatePanel.jsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState } from 'react'
22
import { BLOG_INDEX } from '../lib/blogPaths'
33
import { DEFAULT_POST_TEMPLATE_HTML, persistPostTemplate } from '../lib/postTemplate'
4+
import { getPostPageTemplate } from '../lib/publishTemplates'
45
import { serializePost } from '../lib/postSerializer'
56
import { fetchRepoFileText } from '../lib/github'
67
import { getFriendlyGithubError } from '../lib/githubFriendlyMessages'
@@ -62,8 +63,9 @@ export function PostTemplatePanel({
6263
}
6364

6465
function handleReset() {
65-
onHtmlChange(DEFAULT_POST_TEMPLATE_HTML)
66-
persistPostTemplate(DEFAULT_POST_TEMPLATE_HTML)
66+
const bundled = getPostPageTemplate()
67+
onHtmlChange(bundled)
68+
persistPostTemplate(bundled)
6769
onTemplateSaved?.('Restored default template.')
6870
setDetectNotes([])
6971
setDetectError('')
@@ -96,9 +98,10 @@ export function PostTemplatePanel({
9698
<div className="post-template__header">
9799
<h2 id="post-template-heading">Post template</h2>
98100
<p className="post-template__lede">
99-
This app publishes to a fixed layout: <code>blog/index.html</code>, <code>blog/posts/*.html</code>, and{' '}
100-
<code>blog/style.css</code>. Load your live homepage from GitHub to copy navigation, footer, and listing-card
101-
markup into the per-post HTML below (<code>{'{{content}}'}</code>).
101+
<strong>Publishing</strong> always uses the bundled file <code>templates/post-page-template.html</code>{' '}
102+
(header, footer, <code>{'{{CONTENT}}'}</code> area). This editor is for preview and optional tweaks; it does
103+
not change what gets uploaded unless you edit the repo template in the project. Load{' '}
104+
<code>blog/index.html</code> from GitHub to refresh nav/footer markup here.
102105
</p>
103106
</div>
104107

src/lib/__tests__/publishTemplates.test.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { describe, expect, it } from 'vitest'
2-
import { buildPublishTemplateData, renderPostCardHtml, renderPostPageHtml } from '../publishTemplates'
2+
import {
3+
POST_PAGE_TEMPLATE_MARKER,
4+
buildPublishTemplateData,
5+
getPostPageTemplate,
6+
renderPostCardHtml,
7+
renderPostPageHtml,
8+
} from '../publishTemplates'
9+
import { READ_ONLY_TEMPLATES } from '../readOnlyTemplates'
310
import { replaceTemplateVars as replaceVars } from '../templateVars'
411
import { tryUpdateIndexWithCard, MARKER_START, MARKER_END } from '../blogIndex'
512

@@ -13,6 +20,14 @@ const SAMPLE = {
1320
slug: 'top-five-things-i-look-for-in-github-repositories-as-a-non-coder',
1421
}
1522

23+
describe('getPostPageTemplate', () => {
24+
it('loads templates/post-page-template.html via READ_ONLY_TEMPLATES', () => {
25+
expect(getPostPageTemplate()).toBe(READ_ONLY_TEMPLATES.postPageTemplateHtml)
26+
expect(getPostPageTemplate()).toContain(POST_PAGE_TEMPLATE_MARKER)
27+
expect(getPostPageTemplate()).toContain('<!DOCTYPE html>')
28+
})
29+
})
30+
1631
describe('replaceTemplateVars', () => {
1732
it('escapes title but not content', () => {
1833
const out = replaceVars('{{TITLE}} {{CONTENT}}', {
@@ -53,6 +68,7 @@ describe('sample post card', () => {
5368
content: '<p>Body</p>',
5469
})
5570
const page = renderPostPageHtml(data)
71+
expect(page).toContain(POST_PAGE_TEMPLATE_MARKER)
5672
expect(page).toContain('<nav class="nb-nav">')
5773
expect(page).toContain('<footer class="nb-section">')
5874
expect(page).toContain('href="../style.css"')

src/lib/postTemplate.js

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,12 @@
1+
/**
2+
* Code-mode “post template” panel (preview + localStorage only).
3+
* Publishing always uses templates/post-page-template.html via publishTemplates.js.
4+
*/
5+
import { getPostPageTemplate } from './publishTemplates'
6+
17
const STORAGE_KEY = 'blog-editor-post-template-html'
28

3-
export const DEFAULT_POST_TEMPLATE_HTML = `<!doctype html>
4-
<html lang="en">
5-
<head>
6-
<meta charset="UTF-8" />
7-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8-
<title>{{title}}</title>
9-
<meta name="description" content="{{excerpt}}" />
10-
<meta name="blog-editor:category" content="{{category}}" />
11-
<link rel="stylesheet" href="../styles.css" />
12-
</head>
13-
<body>
14-
<main class="blog-post">
15-
<h1>{{title}}</h1>
16-
<article>
17-
{{content}}
18-
</article>
19-
</main>
20-
</body>
21-
</html>
22-
`
9+
export const DEFAULT_POST_TEMPLATE_HTML = getPostPageTemplate()
2310

2411
function escapeHtml(s) {
2512
return String(s)
@@ -29,6 +16,7 @@ function escapeHtml(s) {
2916
.replaceAll('"', '&quot;')
3017
}
3118

19+
/** Preview-only wrapper ({{title}} / {{content}} placeholders). Not used when publishing to GitHub. */
3220
export function applyPostTemplate(templateHtml, {
3321
title,
3422
content,
@@ -44,17 +32,27 @@ export function applyPostTemplate(templateHtml, {
4432
const safeDate = escapeHtml(date ?? '')
4533
return String(templateHtml)
4634
.replaceAll('{{title}}', safeTitle)
35+
.replaceAll('{{TITLE}}', safeTitle)
4736
.replaceAll('{{excerpt}}', safeExcerpt)
37+
.replaceAll('{{EXCERPT}}', safeExcerpt)
4838
.replaceAll('{{category}}', safeCategory)
39+
.replaceAll('{{CATEGORY}}', safeCategory)
4940
.replaceAll('{{slug}}', safeSlug)
41+
.replaceAll('{{SLUG}}', safeSlug)
5042
.replaceAll('{{date}}', safeDate)
43+
.replaceAll('{{DATE}}', safeDate)
5144
.replaceAll('{{content}}', content ?? '')
45+
.replaceAll('{{CONTENT}}', content ?? '')
5246
}
5347

5448
export function loadPostTemplate() {
5549
try {
5650
const raw = localStorage.getItem(STORAGE_KEY)
5751
if (raw == null || raw.trim() === '') return DEFAULT_POST_TEMPLATE_HTML
52+
// Replace stale minimal templates saved before the bundled full-page shell.
53+
if (!raw.includes('nb-nav') || !raw.includes('<footer')) {
54+
return DEFAULT_POST_TEMPLATE_HTML
55+
}
5856
return raw
5957
} catch {
6058
return DEFAULT_POST_TEMPLATE_HTML

src/lib/publishTemplates.js

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,28 @@ import { READ_ONLY_TEMPLATES } from './readOnlyTemplates'
33
import { replaceTemplateVars } from './templateVars'
44
import { slugify } from './slugify'
55

6+
/** Bundled marker — must appear in every published blog/posts/*.html file. */
7+
export const POST_PAGE_TEMPLATE_MARKER = 'POST PAGE TEMPLATE ACTIVE'
8+
69
export class PublishValidationError extends Error {
710
constructor(message) {
811
super(message)
912
this.name = 'PublishValidationError'
1013
}
1114
}
1215

16+
/**
17+
* Full-page template for blog/posts/{slug}.html (templates/post-page-template.html).
18+
* Publish path: publishPipeline → renderPostPageHtml → getPostPageTemplate → this string.
19+
*/
20+
export function getPostPageTemplate() {
21+
return READ_ONLY_TEMPLATES.postPageTemplateHtml
22+
}
23+
24+
export function getPostCardTemplate() {
25+
return READ_ONLY_TEMPLATES.postCardTemplateHtml
26+
}
27+
1328
function formatDate(d = new Date()) {
1429
try {
1530
return new Date(d).toLocaleString(undefined, { dateStyle: 'long', timeStyle: 'short' })
@@ -35,12 +50,35 @@ export function buildPublishTemplateData({ title, slug, content, excerpt, catego
3550
}
3651
}
3752

53+
/**
54+
* Renders blog/posts/{slug}.html from templates/post-page-template.html only.
55+
*/
56+
export function renderPostPageHtml(data) {
57+
const html = replaceTemplateVars(getPostPageTemplate(), data)
58+
assertRenderedPostPage(html)
59+
return html
60+
}
61+
3862
export function renderPostCardHtml(data) {
39-
return replaceTemplateVars(READ_ONLY_TEMPLATES.postCardTemplateHtml, data)
63+
return replaceTemplateVars(getPostCardTemplate(), data)
4064
}
4165

42-
export function renderPostPageHtml(data) {
43-
return replaceTemplateVars(READ_ONLY_TEMPLATES.postPageTemplateHtml, data)
66+
/** Ensures publish output used the bundled full-page template, not a stale/minimal fallback. */
67+
export function assertRenderedPostPage(html) {
68+
const h = String(html)
69+
const missing = []
70+
if (!h.includes(POST_PAGE_TEMPLATE_MARKER)) missing.push(POST_PAGE_TEMPLATE_MARKER)
71+
if (!h.includes('<nav class="nb-nav">')) missing.push('header (nb-nav)')
72+
if (!h.includes('<footer class="nb-section">')) missing.push('footer')
73+
if (!h.includes('href="../style.css"')) missing.push('stylesheet ../style.css')
74+
if (!h.includes('href="../index.html"')) missing.push('home link ../index.html')
75+
if (!h.includes('class="nb-card nb-stack-md blog-post"')) missing.push('post article')
76+
if (missing.length) {
77+
throw new PublishValidationError(
78+
`Post page was not built from templates/post-page-template.html (missing: ${missing.join(', ')}). ` +
79+
'Stop and restart the dev server (npm run dev), then publish again.',
80+
)
81+
}
4482
}
4583

4684
export function validatePublishInputs({ title, content, slug }) {
@@ -51,11 +89,18 @@ export function validatePublishInputs({ title, content, slug }) {
5189
if (!t) throw new PublishValidationError('Add a title before publishing.')
5290
if (!c || c === '<p></p>') throw new PublishValidationError('Add some post content before publishing.')
5391
if (!s) throw new PublishValidationError('Add a slug (or title) before publishing.')
54-
if (!READ_ONLY_TEMPLATES.postCardTemplateHtml.trim()) {
55-
throw new PublishValidationError('Missing post-card template.')
92+
93+
const pageTpl = getPostPageTemplate().trim()
94+
if (!pageTpl) {
95+
throw new PublishValidationError('Missing templates/post-page-template.html (empty bundle).')
96+
}
97+
if (!pageTpl.includes(POST_PAGE_TEMPLATE_MARKER)) {
98+
throw new PublishValidationError(
99+
'Bundled post-page template is outdated. Restart npm run dev and try again.',
100+
)
56101
}
57-
if (!READ_ONLY_TEMPLATES.postPageTemplateHtml.trim()) {
58-
throw new PublishValidationError('Missing post-page template.')
102+
if (!getPostCardTemplate().trim()) {
103+
throw new PublishValidationError('Missing templates/post-card-template.html.')
59104
}
60105

61106
return { title: t, content: c, slug: s }

templates/post-page-template.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<!DOCTYPE html>
2+
<!-- POST PAGE TEMPLATE ACTIVE -->
23
<html lang="en">
34
<head>
45
<meta charset="UTF-8" />

0 commit comments

Comments
 (0)