Skip to content

Commit 2b228fd

Browse files
compscidrclaude
andcommitted
Plugin pages render through page_content.html, not custom templates
Plugins no longer need theme-specific templates. The scholar plugin now generates its article list as HTML and passes it via plugin_content, which page_content.html renders as raw HTML. Regular content pages continue to use Showdown markdown rendering. This means: - Any theme works with any page plugin automatically - No page_research.html needed in themes - Plugin controls its own HTML layout - Citation count and journal info now shown inline - All output properly HTML-escaped Removed: page_research.html and research.html from all themes. Updated: page_content.html in all themes to check for plugin_content. Closes #513 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5021910 commit 2b228fd

8 files changed

Lines changed: 72 additions & 138 deletions

File tree

plugins/scholar/scholar.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"errors"
88
"fmt"
99
"goblog/blog"
10+
"html"
1011
"log"
1112
"sort"
13+
"strconv"
1214
"sync"
1315
"time"
1416

@@ -118,8 +120,8 @@ func (p *ScholarPlugin) RenderPage(ctx *gplugin.HookContext, pageType string) (s
118120
settings := ctx.Settings
119121
scholarID := settings["scholar_id"]
120122
if scholarID == "" {
121-
return "page_research.html", gin.H{
122-
"errors": "Google Scholar ID not configured. Set it in the Scholar plugin settings.",
123+
return "page_content.html", gin.H{
124+
"plugin_content": `<div class="alert alert-warning">Google Scholar ID not configured. Set it in the Scholar plugin settings.</div>`,
123125
}
124126
}
125127

@@ -132,18 +134,44 @@ func (p *ScholarPlugin) RenderPage(ctx *gplugin.HookContext, pageType string) (s
132134
p.ensureScholar(settings)
133135

134136
articles, err := p.sch.QueryProfileWithMemoryCache(scholarID, limit)
135-
data := gin.H{}
136-
if err == nil {
137-
sortArticlesByDateDesc(articles)
138-
p.sch.SaveCache(settings["profile_cache"], settings["article_cache"])
139-
data["articles"] = articles
140-
} else {
137+
if err != nil {
141138
log.Printf("Scholar query failed: %v", err)
142-
data["articles"] = make([]*scholarlib.Article, 0)
143-
data["errors"] = err.Error()
139+
return "page_content.html", gin.H{
140+
"plugin_content": `<div class="alert alert-danger">` + html.EscapeString(err.Error()) + `</div>`,
141+
}
144142
}
145143

146-
return "page_research.html", data
144+
sortArticlesByDateDesc(articles)
145+
p.sch.SaveCache(settings["profile_cache"], settings["article_cache"])
146+
147+
return "page_content.html", gin.H{
148+
"plugin_content": renderArticlesHTML(articles),
149+
}
150+
}
151+
152+
// renderArticlesHTML generates the HTML for the articles list.
153+
func renderArticlesHTML(articles []*scholarlib.Article) string {
154+
out := ""
155+
for _, a := range articles {
156+
out += `<div style="margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #eee;">`
157+
out += `<div><a href="` + html.EscapeString(a.ScholarURL) + `">` + html.EscapeString(a.Title) + `</a></div>`
158+
if a.Authors != "" {
159+
out += `<div style="color: #666; font-size: 13px;">` + html.EscapeString(a.Authors) + `</div>`
160+
}
161+
out += `<div style="color: #888; font-size: 13px;">`
162+
if a.Year > 0 {
163+
out += strconv.Itoa(a.Year)
164+
}
165+
if a.Journal != "" {
166+
out += ` &middot; ` + html.EscapeString(a.Journal)
167+
}
168+
if a.NumCitations > 0 {
169+
out += ` &middot; ` + strconv.Itoa(a.NumCitations) + ` citations`
170+
}
171+
out += `</div>`
172+
out += `</div>`
173+
}
174+
return out
147175
}
148176

149177
func (p *ScholarPlugin) ScheduledJobs() []gplugin.ScheduledJob {

themes/default/templates/page_content.html

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ <h1>{{ .page.Title }}</h1>
2121
{{ if .is_admin }}
2222
<p class="text-left"><a href="/admin/pages/{{ .page.ID }}">Edit this page</a></p>
2323
{{ end }}
24+
{{ if .plugin_content }}
25+
<div id="page-content">{{ .plugin_content | rawHTML }}</div>
26+
{{ else }}
2427
<div id="page-content"></div>
28+
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous"></script>
29+
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js" integrity="sha512-78KH17QLT5e55GJqP76vutp1D2iAoy06WcYBXB6iBCsmO6wWzx0Qdg8EDpm8mKXv68BcvHOyeeP4wxAL0twJGQ==" crossorigin="anonymous"></script>
30+
<script>
31+
var converter = new showdown.Converter({tables: true});
32+
var content = {{ .page.Content }};
33+
document.getElementById('page-content').innerHTML = DOMPurify.sanitize(converter.makeHtml(content));
34+
</script>
35+
{{ end }}
2536
</div>
2637

27-
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous"></script>
28-
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js" integrity="sha512-78KH17QLT5e55GJqP76vutp1D2iAoy06WcYBXB6iBCsmO6wWzx0Qdg8EDpm8mKXv68BcvHOyeeP4wxAL0twJGQ==" crossorigin="anonymous"></script>
29-
<script>
30-
var converter = new showdown.Converter({tables: true});
31-
var content = {{ .page.Content }};
32-
document.getElementById('page-content').innerHTML = DOMPurify.sanitize(converter.makeHtml(content));
33-
</script>
34-
3538
{{ template "footer.html" .}}

themes/default/templates/page_research.html

Lines changed: 0 additions & 30 deletions
This file was deleted.

themes/default/templates/research.html

Lines changed: 0 additions & 25 deletions
This file was deleted.

themes/forest/templates/page_content.html

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ <h1>{{ .page.Title }}</h1>
55
{{ if .is_admin }}
66
<p class="text-left"><a href="/admin/pages/{{ .page.ID }}">Edit this page</a></p>
77
{{ end }}
8+
{{ if .plugin_content }}
9+
<div id="page-content">{{ .plugin_content | rawHTML }}</div>
10+
{{ else }}
811
<div id="page-content"></div>
12+
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous"></script>
13+
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js" integrity="sha512-78KH17QLT5e55GJqP76vutp1D2iAoy06WcYBXB6iBCsmO6wWzx0Qdg8EDpm8mKXv68BcvHOyeeP4wxAL0twJGQ==" crossorigin="anonymous"></script>
14+
<script>
15+
var converter = new showdown.Converter({tables: true});
16+
var content = {{ .page.Content }};
17+
document.getElementById('page-content').innerHTML = DOMPurify.sanitize(converter.makeHtml(content));
18+
</script>
19+
{{ end }}
920
</div></div>
1021

11-
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous"></script>
12-
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js" integrity="sha512-78KH17QLT5e55GJqP76vutp1D2iAoy06WcYBXB6iBCsmO6wWzx0Qdg8EDpm8mKXv68BcvHOyeeP4wxAL0twJGQ==" crossorigin="anonymous"></script>
13-
<script>
14-
var converter = new showdown.Converter({tables: true});
15-
var content = {{ .page.Content }};
16-
document.getElementById('page-content').innerHTML = DOMPurify.sanitize(converter.makeHtml(content));
17-
</script>
18-
1922
{{ template "footer.html" .}}

themes/forest/templates/page_research.html

Lines changed: 0 additions & 24 deletions
This file was deleted.

themes/minimal/templates/page_content.html

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ <h1>{{ .page.Title }}</h1>
55
{{ if .is_admin }}
66
<p class="text-left"><a href="/admin/pages/{{ .page.ID }}">Edit this page</a></p>
77
{{ end }}
8+
{{ if .plugin_content }}
9+
<div id="page-content">{{ .plugin_content | rawHTML }}</div>
10+
{{ else }}
811
<div id="page-content"></div>
12+
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous"></script>
13+
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js" integrity="sha512-78KH17QLT5e55GJqP76vutp1D2iAoy06WcYBXB6iBCsmO6wWzx0Qdg8EDpm8mKXv68BcvHOyeeP4wxAL0twJGQ==" crossorigin="anonymous"></script>
14+
<script>
15+
var converter = new showdown.Converter({tables: true});
16+
var content = {{ .page.Content }};
17+
document.getElementById('page-content').innerHTML = DOMPurify.sanitize(converter.makeHtml(content));
18+
</script>
19+
{{ end }}
920
</div>
1021

11-
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous"></script>
12-
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js" integrity="sha512-78KH17QLT5e55GJqP76vutp1D2iAoy06WcYBXB6iBCsmO6wWzx0Qdg8EDpm8mKXv68BcvHOyeeP4wxAL0twJGQ==" crossorigin="anonymous"></script>
13-
<script>
14-
var converter = new showdown.Converter({tables: true});
15-
var content = {{ .page.Content }};
16-
document.getElementById('page-content').innerHTML = DOMPurify.sanitize(converter.makeHtml(content));
17-
</script>
18-
1922
{{ template "footer.html" .}}

themes/minimal/templates/page_research.html

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)