Further improve post title rendering

This commit is contained in:
Jan-Lukas Else 2021-08-05 08:09:34 +02:00
parent 4c1f7fcde4
commit 4c8c147c4c
21 changed files with 71 additions and 64 deletions

View File

@ -106,7 +106,7 @@ func (a *goBlog) toASNote(p *post) *asNote {
AttributedTo: a.apIri(a.cfg.Blogs[p.Blog]), AttributedTo: a.apIri(a.cfg.Blogs[p.Blog]),
} }
// Name and Type // Name and Type
if title := a.renderMdTitle(p.Title()); title != "" { if title := p.RenderedTitle; title != "" {
as.Name = title as.Name = title
as.Type = "Article" as.Type = "Article"
} else { } else {

View File

@ -17,7 +17,7 @@ import (
func (a *goBlog) checkAllExternalLinks() { func (a *goBlog) checkAllExternalLinks() {
// Get all published posts without parameters // Get all published posts without parameters
posts, err := a.db.getPosts(&postsRequestConfig{status: statusPublished, withoutParameters: true}) posts, err := a.getPosts(&postsRequestConfig{status: statusPublished, withoutParameters: true})
if err != nil { if err != nil {
log.Println(err.Error()) log.Println(err.Error())
return return

View File

@ -41,7 +41,7 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
post, err := a.db.getPost(parsedURL.Path) post, err := a.getPost(parsedURL.Path)
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return

View File

@ -42,7 +42,7 @@ func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r
} }
for _, p := range posts { for _, p := range posts {
feed.Add(&feeds.Item{ feed.Add(&feeds.Item{
Title: a.renderMdTitle(p.Title()), Title: p.RenderedTitle,
Link: &feeds.Link{Href: a.fullPostURL(p)}, Link: &feeds.Link{Href: a.fullPostURL(p)},
Description: a.postSummary(p), Description: a.postSummary(p),
Id: p.Path, Id: p.Path,

View File

@ -20,7 +20,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogKey).(string) blog := r.Context().Value(blogKey).(string)
bc := a.cfg.Blogs[blog] bc := a.cfg.Blogs[blog]
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{ allPostsWithLocation, err := a.getPosts(&postsRequestConfig{
blog: blog, blog: blog,
status: statusPublished, status: statusPublished,
parameter: a.cfg.Micropub.LocationParam, parameter: a.cfg.Micropub.LocationParam,

View File

@ -33,30 +33,28 @@ func (a *goBlog) initMarkdown() {
emoji.Emoji, emoji.Emoji,
), ),
} }
publicAddress := ""
if srv := a.cfg.Server; srv != nil {
publicAddress = srv.PublicAddress
}
a.md = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{ a.md = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{
absoluteLinks: false, absoluteLinks: false,
publicAddress: a.cfg.Server.PublicAddress, publicAddress: publicAddress,
}))...) }))...)
a.absoluteMd = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{ a.absoluteMd = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{
absoluteLinks: true, absoluteLinks: true,
publicAddress: a.cfg.Server.PublicAddress, publicAddress: publicAddress,
}))...) }))...)
a.titleMd = goldmark.New( a.titleMd = goldmark.New(
goldmark.WithParser( goldmark.WithParser(
// Override, no need for special Markdown parsers // Override, no need for special Markdown parsers
parser.NewParser( parser.NewParser(
parser.WithBlockParsers( parser.WithBlockParsers(
util.Prioritized(parser.NewHTMLBlockParser(), 900),
util.Prioritized(parser.NewParagraphParser(), 1000)), util.Prioritized(parser.NewParagraphParser(), 1000)),
parser.WithInlineParsers( parser.WithInlineParsers(),
util.Prioritized(parser.NewRawHTMLParser(), 400),
),
parser.WithParagraphTransformers(), parser.WithParagraphTransformers(),
), ),
), ),
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
goldmark.WithExtensions( goldmark.WithExtensions(
extension.Typographer, extension.Typographer,
emoji.Emoji, emoji.Emoji,

View File

@ -73,6 +73,7 @@ func Test_markdown(t *testing.T) {
assert.Equal(t, "3. **Test**", app.renderMdTitle("3. **Test**")) assert.Equal(t, "3. **Test**", app.renderMdTitle("3. **Test**"))
assert.Equal(t, "Tests", app.renderMdTitle("Test's")) assert.Equal(t, "Tests", app.renderMdTitle("Test's"))
assert.Equal(t, "😂", app.renderMdTitle(":joy:")) assert.Equal(t, "😂", app.renderMdTitle(":joy:"))
assert.Equal(t, "<b></b>", app.renderMdTitle("<b></b>"))
// Template func // Template func

View File

@ -40,7 +40,7 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
p, err := a.db.getPost(u.Path) p, err := a.getPost(u.Path)
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
@ -49,7 +49,7 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
} else { } else {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
posts, err := a.db.getPosts(&postsRequestConfig{ posts, err := a.getPosts(&postsRequestConfig{
limit: limit, limit: limit,
offset: offset, offset: offset,
}) })
@ -473,7 +473,7 @@ func (a *goBlog) micropubUpdate(w http.ResponseWriter, r *http.Request, u string
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
p, err := a.db.getPost(uu.Path) p, err := a.getPost(uu.Path)
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return

View File

@ -29,9 +29,10 @@ type post struct {
Status postStatus Status postStatus
Priority int Priority int
// Not persisted // Not persisted
Slug string Slug string
renderCache map[bool]template.HTML RenderedTitle string
renderMutex sync.RWMutex renderCache map[bool]template.HTML
renderMutex sync.RWMutex
} }
type postStatus string type postStatus string
@ -45,7 +46,7 @@ const (
) )
func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) { func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
p, err := a.db.getPost(r.URL.Path) p, err := a.getPost(r.URL.Path)
if err == errPostNotFound { if err == errPostNotFound {
a.serve404(w, r) a.serve404(w, r)
return return
@ -89,12 +90,12 @@ func (a *goBlog) redirectToRandomPost(rw http.ResponseWriter, r *http.Request) {
type postPaginationAdapter struct { type postPaginationAdapter struct {
config *postsRequestConfig config *postsRequestConfig
nums int64 nums int64
db *database a *goBlog
} }
func (p *postPaginationAdapter) Nums() (int64, error) { func (p *postPaginationAdapter) Nums() (int64, error) {
if p.nums == 0 { if p.nums == 0 {
nums, _ := p.db.countPosts(p.config) nums, _ := p.a.db.countPosts(p.config)
p.nums = int64(nums) p.nums = int64(nums)
} }
return p.nums, nil return p.nums, nil
@ -105,7 +106,7 @@ func (p *postPaginationAdapter) Slice(offset, length int, data interface{}) erro
modifiedConfig.offset = offset modifiedConfig.offset = offset
modifiedConfig.limit = length modifiedConfig.limit = length
posts, err := p.db.getPosts(&modifiedConfig) posts, err := p.a.getPosts(&modifiedConfig)
reflect.ValueOf(data).Elem().Set(reflect.ValueOf(&posts).Elem()) reflect.ValueOf(data).Elem().Set(reflect.ValueOf(&posts).Elem())
return err return err
} }
@ -247,7 +248,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
publishedDay: ic.day, publishedDay: ic.day,
status: status, status: status,
priorityOrder: true, priorityOrder: true,
}, db: a.db}, a.cfg.Blogs[blog].Pagination) }, a: a}, a.cfg.Blogs[blog].Pagination)
p.SetPage(pageNo) p.SetPage(pageNo)
var posts []*post var posts []*post
err := p.Results(&posts) err := p.Results(&posts)

View File

@ -190,7 +190,7 @@ func (db *database) savePost(p *post, o *postCreationOptions) error {
} }
func (a *goBlog) deletePost(path string) error { func (a *goBlog) deletePost(path string) error {
p, err := a.db.deletePost(path) p, err := a.deletePostFromDb(path)
if err != nil || p == nil { if err != nil || p == nil {
return err return err
} }
@ -201,17 +201,17 @@ func (a *goBlog) deletePost(path string) error {
return nil return nil
} }
func (db *database) deletePost(path string) (*post, error) { func (a *goBlog) deletePostFromDb(path string) (*post, error) {
if path == "" { if path == "" {
return nil, nil return nil, nil
} }
db.pcm.Lock() a.db.pcm.Lock()
defer db.pcm.Unlock() defer a.db.pcm.Unlock()
p, err := db.getPost(path) p, err := a.getPost(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = db.exec( _, err = a.db.exec(
`begin; `begin;
delete from posts where path = ?; delete from posts where path = ?;
delete from post_parameters where path = ?; delete from post_parameters where path = ?;
@ -222,7 +222,7 @@ func (db *database) deletePost(path string) (*post, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
db.rebuildFTSIndex() a.db.rebuildFTSIndex()
return p, nil return p, nil
} }
@ -389,10 +389,10 @@ func (d *database) loadPostParameters(posts []*post, parameters ...string) (err
return nil return nil
} }
func (d *database) getPosts(config *postsRequestConfig) (posts []*post, err error) { func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error) {
// Query posts // Query posts
query, queryParams := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, priority") query, queryParams := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, priority")
rows, err := d.query(query, queryParams...) rows, err := a.db.query(query, queryParams...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -417,16 +417,22 @@ func (d *database) getPosts(config *postsRequestConfig) (posts []*post, err erro
posts = append(posts, p) posts = append(posts, p)
} }
if !config.withoutParameters { if !config.withoutParameters {
err = d.loadPostParameters(posts, config.withOnlyParameters...) err = a.db.loadPostParameters(posts, config.withOnlyParameters...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
// Render post title
for _, p := range posts {
if t := p.Title(); t != "" {
p.RenderedTitle = a.renderMdTitle(t)
}
}
return posts, nil return posts, nil
} }
func (d *database) getPost(path string) (*post, error) { func (a *goBlog) getPost(path string) (*post, error) {
posts, err := d.getPosts(&postsRequestConfig{path: path, limit: 1}) posts, err := a.getPosts(&postsRequestConfig{path: path, limit: 1})
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(posts) == 0 { } else if len(posts) == 0 {

View File

@ -29,6 +29,7 @@ func Test_postsDb(t *testing.T) {
}, },
} }
_ = app.initDatabase(false) _ = app.initDatabase(false)
app.initMarkdown()
now := toLocalSafe(time.Now().String()) now := toLocalSafe(time.Now().String())
nowPlus1Hour := toLocalSafe(time.Now().Add(1 * time.Hour).String()) nowPlus1Hour := toLocalSafe(time.Now().Add(1 * time.Hour).String())
@ -51,7 +52,7 @@ func Test_postsDb(t *testing.T) {
must.NoError(err) must.NoError(err)
// Check post // Check post
p, err := app.db.getPost("/test/abc") p, err := app.getPost("/test/abc")
must.NoError(err) must.NoError(err)
is.Equal("/test/abc", p.Path) is.Equal("/test/abc", p.Path)
is.Equal("ABC", p.Content) is.Equal("ABC", p.Content)
@ -75,7 +76,7 @@ func Test_postsDb(t *testing.T) {
is.Len(pp, 0) is.Len(pp, 0)
// Check drafts // Check drafts
drafts, _ := app.db.getPosts(&postsRequestConfig{ drafts, _ := app.getPosts(&postsRequestConfig{
blog: "en", blog: "en",
status: statusDraft, status: statusDraft,
}) })
@ -90,7 +91,7 @@ func Test_postsDb(t *testing.T) {
is.Equal(0, count) is.Equal(0, count)
// Delete post // Delete post
_, err = app.db.deletePost("/test/abc") _, err = app.deletePostFromDb("/test/abc")
must.NoError(err) must.NoError(err)
// Check that there is no post // Check that there is no post
@ -220,6 +221,7 @@ func Test_ftsWithoutTitle(t *testing.T) {
}, },
} }
_ = app.initDatabase(false) _ = app.initDatabase(false)
app.initMarkdown()
err := app.db.savePost(&post{ err := app.db.savePost(&post{
Path: "/test/abc", Path: "/test/abc",
@ -232,7 +234,7 @@ func Test_ftsWithoutTitle(t *testing.T) {
}, &postCreationOptions{new: true}) }, &postCreationOptions{new: true})
require.NoError(t, err) require.NoError(t, err)
ps, err := app.db.getPosts(&postsRequestConfig{ ps, err := app.getPosts(&postsRequestConfig{
search: "ABC", search: "ABC",
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -250,6 +252,7 @@ func Test_postsPriority(t *testing.T) {
}, },
} }
_ = app.initDatabase(false) _ = app.initDatabase(false)
app.initMarkdown()
err := app.db.savePost(&post{ err := app.db.savePost(&post{
Path: "/test/abc", Path: "/test/abc",
@ -272,7 +275,7 @@ func Test_postsPriority(t *testing.T) {
}, &postCreationOptions{new: true}) }, &postCreationOptions{new: true})
require.NoError(t, err) require.NoError(t, err)
ps, err := app.db.getPosts(&postsRequestConfig{ ps, err := app.getPosts(&postsRequestConfig{
priorityOrder: true, priorityOrder: true,
}) })
require.NoError(t, err) require.NoError(t, err)

View File

@ -109,7 +109,7 @@ func (a *goBlog) postTranslations(p *post) []*post {
if translationkey == "" { if translationkey == "" {
return nil return nil
} }
posts, err := a.db.getPosts(&postsRequestConfig{ posts, err := a.getPosts(&postsRequestConfig{
parameter: "translationkey", parameter: "translationkey",
parameterValue: translationkey, parameterValue: translationkey,
}) })

View File

@ -153,7 +153,7 @@ func (a *goBlog) serveSitemapBlogPosts(w http.ResponseWriter, r *http.Request) {
// Create sitemap // Create sitemap
sm := sitemap.New() sm := sitemap.New()
// Request posts // Request posts
posts, _ := a.db.getPosts(&postsRequestConfig{ posts, _ := a.getPosts(&postsRequestConfig{
status: statusPublished, status: statusPublished,
blog: r.Context().Value(blogKey).(string), blog: r.Context().Value(blogKey).(string),
withoutParameters: true, withoutParameters: true,

View File

@ -16,7 +16,7 @@ const telegramBaseURL = "https://api.telegram.org/bot"
func (a *goBlog) initTelegram() { func (a *goBlog) initTelegram() {
a.pPostHooks = append(a.pPostHooks, func(p *post) { a.pPostHooks = append(a.pPostHooks, func(p *post) {
if tg := a.cfg.Blogs[p.Blog].Telegram; tg.enabled() && p.isPublishedSectionPost() { if tg := a.cfg.Blogs[p.Blog].Telegram; tg.enabled() && p.isPublishedSectionPost() {
if html := tg.generateHTML(a.renderMdTitle(p.Title()), a.fullPostURL(p), a.shortPostURL(p)); html != "" { if html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p)); html != "" {
if err := a.send(tg, html, "HTML"); err != nil { if err := a.send(tg, html, "HTML"); err != nil {
log.Printf("Failed to send post to Telegram: %v", err) log.Printf("Failed to send post to Telegram: %v", err)
} }

View File

@ -139,14 +139,12 @@ func Test_telegram(t *testing.T) {
app.initTelegram() app.initTelegram()
p := &post{ p := &post{
Path: "/test", Path: "/test",
Parameters: map[string][]string{ RenderedTitle: "Title",
"title": {"Title"}, Published: time.Now().String(),
}, Section: "test",
Published: time.Now().String(), Blog: "en",
Section: "test", Status: statusPublished,
Blog: "en",
Status: statusPublished,
} }
app.pPostHooks[0](p) app.pPostHooks[0](p)

View File

@ -1,10 +1,10 @@
{{ define "photosummary" }} {{ define "photosummary" }}
<article class="h-entry border-bottom"> <article class="h-entry border-bottom">
{{ if gt .Data.Priority 0 }}<p>📌 {{ string .Blog.Lang "pinned" }}</p>{{ end }} {{ if gt .Data.Priority 0 }}<p>📌 {{ string .Blog.Lang "pinned" }}</p>{{ end }}
{{ if .Data.Title }} {{ if .Data.RenderedTitle }}
<h2 class="p-name"> <h2 class="p-name">
<a class="u-url" href="{{ .Data.Path }}"> <a class="u-url" href="{{ .Data.Path }}">
{{ mdtitle .Data.Title }} {{ .Data.RenderedTitle }}
</a> </a>
</h2> </h2>
{{ end }} {{ end }}

View File

@ -1,5 +1,5 @@
{{ define "title" }} {{ define "title" }}
<title>{{ with .Data.Title }}{{ mdtitle . }} - {{end}}{{ mdtitle .Blog.Title }}</title> <title>{{ with .Data.RenderedTitle }}{{ . }} - {{end}}{{ mdtitle .Blog.Title }}</title>
{{ include "postheadmeta" . }} {{ include "postheadmeta" . }}
{{ with shorturl .Data }}<link rel="shortlink" href="{{ . }}">{{ end }} {{ with shorturl .Data }}<link rel="shortlink" href="{{ . }}">{{ end }}
{{ end }} {{ end }}
@ -8,7 +8,7 @@
<main class=h-entry> <main class=h-entry>
<article> <article>
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data> <data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
{{ with .Data.Title }}<h1 class=p-name>{{ mdtitle . }}</h1>{{ end }} {{ with .Data.RenderedTitle }}<h1 class=p-name>{{ . }}</h1>{{ end }}
{{ include "postmeta" . }} {{ include "postmeta" . }}
{{ include "postactions" . }} {{ include "postactions" . }}
{{ if .Data.Content }} {{ if .Data.Content }}

View File

@ -1,6 +1,6 @@
{{ define "postactions" }} {{ define "postactions" }}
<div class="p flex" id="post-actions"> <div class="p flex" id="post-actions">
<a href="https://www.addtoany.com/share#url={{ absolute .Data.Path }}{{ with .Data.Title }}&title={{ mdtitle . }}{{ end }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "share" }}</a>&nbsp; <a href="https://www.addtoany.com/share#url={{ absolute .Data.Path }}{{ with .Data.RenderedTitle }}&title={{ . }}{{ end }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "share" }}</a>&nbsp;
<a id="translateBtn" href="https://translate.google.com/translate?u={{ absolute .Data.Path }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "translate" }}</a>&nbsp; <a id="translateBtn" href="https://translate.google.com/translate?u={{ absolute .Data.Path }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "translate" }}</a>&nbsp;
<script defer src="{{ asset "js/translate.js" }}"></script> <script defer src="{{ asset "js/translate.js" }}"></script>
<button id="speakBtn" class="hide" data-speak="{{ string .Blog.Lang "speak" }}" data-stopspeak="{{ string .Blog.Lang "stopspeak" }}"></button> <button id="speakBtn" class="hide" data-speak="{{ string .Blog.Lang "speak" }}" data-stopspeak="{{ string .Blog.Lang "stopspeak" }}"></button>

View File

@ -3,9 +3,9 @@
<meta property="og:url" content="{{ . }}"> <meta property="og:url" content="{{ . }}">
<meta property="twitter:url" content="{{ . }}"> <meta property="twitter:url" content="{{ . }}">
{{ end }} {{ end }}
{{ with .Data.Title }} {{ with .Data.RenderedTitle }}
<meta property="og:title" content="{{ mdtitle . }}"> <meta property="og:title" content="{{ . }}">
<meta property="twitter:title" content="{{ mdtitle . }}"> <meta property="twitter:title" content="{{ . }}">
{{ end }} {{ end }}
{{ with summary .Data }} {{ with summary .Data }}
<meta name="description" content="{{ . }}"> <meta name="description" content="{{ . }}">

View File

@ -3,7 +3,7 @@
{{ include "summaryandpostmeta" . }} {{ include "summaryandpostmeta" . }}
{{ $translations := (translations .Data) }} {{ $translations := (translations .Data) }}
{{ if gt (len $translations) 0 }} {{ if gt (len $translations) 0 }}
<div>{{ string .Blog.Lang "translations" }}: {{ $delimiter := "" }}{{ range $i, $t := $translations }}{{ $delimiter }}<a href="{{ $t.Path }}" translate="no">{{ mdtitle $t.Title }}</a>{{ $delimiter = ", " }}{{ end }}</div> <div>{{ string .Blog.Lang "translations" }}: {{ $delimiter := "" }}{{ range $i, $t := $translations }}{{ $delimiter }}<a href="{{ $t.Path }}" translate="no">{{ $t.RenderedTitle }}</a>{{ $delimiter = ", " }}{{ end }}</div>
{{ end }} {{ end }}
{{ $short := shorturl .Data }} {{ $short := shorturl .Data }}
{{ if $short }}<div>{{ string .Blog.Lang "shorturl" }} <a href="{{ $short }}" rel="shortlink">{{ $short }}</a></div>{{ end }} {{ if $short }}<div>{{ string .Blog.Lang "shorturl" }} <a href="{{ $short }}" rel="shortlink">{{ $short }}</a></div>{{ end }}

View File

@ -1,10 +1,10 @@
{{ define "summary" }} {{ define "summary" }}
<article class="h-entry border-bottom"> <article class="h-entry border-bottom">
{{ if gt .Data.Priority 0 }}<p>📌 {{ string .Blog.Lang "pinned" }}</p>{{ end }} {{ if gt .Data.Priority 0 }}<p>📌 {{ string .Blog.Lang "pinned" }}</p>{{ end }}
{{ if .Data.Title }} {{ if .Data.RenderedTitle }}
<h2 class="p-name"> <h2 class="p-name">
<a class="u-url" href="{{ .Data.Path }}"> <a class="u-url" href="{{ .Data.Path }}">
{{ mdtitle .Data.Title }} {{ .Data.RenderedTitle }}
</a> </a>
</h2> </h2>
{{ end }} {{ end }}