@@ -7,8 +7,12 @@ import (
77 "errors"
88 "fmt"
99 "goblog/blog"
10+ "html"
1011 "log"
12+ "net/url"
1113 "sort"
14+ "strconv"
15+ "strings"
1216 "sync"
1317 "time"
1418
@@ -115,12 +119,13 @@ func (p *ScholarPlugin) RenderPage(ctx *gplugin.HookContext, pageType string) (s
115119 return "" , nil
116120 }
117121
122+ data := gin.H {"has_plugin_content" : true }
123+
118124 settings := ctx .Settings
119125 scholarID := settings ["scholar_id" ]
120126 if scholarID == "" {
121- return "page_research.html" , gin.H {
122- "errors" : "Google Scholar ID not configured. Set it in the Scholar plugin settings." ,
123- }
127+ data ["plugin_content" ] = `<div class="alert alert-warning" role="alert">Google Scholar ID not configured. Set it in the Scholar plugin settings.</div>`
128+ return "page_content.html" , data
124129 }
125130
126131 limitStr := settings ["article_limit" ]
@@ -132,18 +137,71 @@ func (p *ScholarPlugin) RenderPage(ctx *gplugin.HookContext, pageType string) (s
132137 p .ensureScholar (settings )
133138
134139 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 {
140+ if err != nil {
141141 log .Printf ("Scholar query failed: %v" , err )
142- data ["articles" ] = make ([]* scholarlib.Article , 0 )
143- data ["errors" ] = err .Error ()
142+ data ["plugin_content" ] = `<div class="alert alert-danger" role="alert">` + html .EscapeString (err .Error ()) + `</div>`
143+ return "page_content.html" , data
144+ }
145+
146+ sortArticlesByDateDesc (articles )
147+ p .sch .SaveCache (settings ["profile_cache" ], settings ["article_cache" ])
148+
149+ data ["plugin_content" ] = renderArticlesHTML (articles )
150+ return "page_content.html" , data
151+ }
152+
153+ // safeHref returns the URL only if it uses http or https scheme, otherwise empty.
154+ func safeHref (rawURL string ) string {
155+ u , err := url .Parse (rawURL )
156+ if err != nil {
157+ return ""
158+ }
159+ if u .Scheme != "http" && u .Scheme != "https" {
160+ return ""
161+ }
162+ return html .EscapeString (rawURL )
163+ }
164+
165+ // renderArticlesHTML generates the HTML for the articles list.
166+ func renderArticlesHTML (articles []* scholarlib.Article ) string {
167+ if len (articles ) == 0 {
168+ return `<p>No publications found.</p>`
144169 }
145170
146- return "page_research.html" , data
171+ var b strings.Builder
172+ for _ , a := range articles {
173+ b .WriteString (`<div style="margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #eee;">` )
174+
175+ // Title — link only if URL is safe
176+ href := safeHref (a .ScholarURL )
177+ if href != "" {
178+ b .WriteString (`<div><a href="` + href + `">` + html .EscapeString (a .Title ) + `</a></div>` )
179+ } else {
180+ b .WriteString (`<div>` + html .EscapeString (a .Title ) + `</div>` )
181+ }
182+
183+ if a .Authors != "" {
184+ b .WriteString (`<div style="color: #666; font-size: 13px;">` + html .EscapeString (a .Authors ) + `</div>` )
185+ }
186+
187+ // Meta line: year · journal · citations
188+ var meta []string
189+ if a .Year > 0 {
190+ meta = append (meta , strconv .Itoa (a .Year ))
191+ }
192+ if a .Journal != "" {
193+ meta = append (meta , html .EscapeString (a .Journal ))
194+ }
195+ if a .NumCitations > 0 {
196+ meta = append (meta , strconv .Itoa (a .NumCitations )+ " citations" )
197+ }
198+ if len (meta ) > 0 {
199+ b .WriteString (`<div style="color: #888; font-size: 13px;">` + strings .Join (meta , " · " ) + `</div>` )
200+ }
201+
202+ b .WriteString (`</div>` )
203+ }
204+ return b .String ()
147205}
148206
149207func (p * ScholarPlugin ) ScheduledJobs () []gplugin.ScheduledJob {
0 commit comments