mirror of
https://github.com/dnote/dnote
synced 2026-03-14 22:45:50 +01:00
Remove unused templates
This commit is contained in:
parent
0a5728faf3
commit
6ba558de96
13 changed files with 0 additions and 601 deletions
|
|
@ -1,103 +0,0 @@
|
|||
/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors
|
||||
*
|
||||
* This file is part of Dnote.
|
||||
*
|
||||
* Dnote is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Dnote is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tmpl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// routes
|
||||
var notesPathRegex = regexp.MustCompile("^/notes/([^/]+)$")
|
||||
|
||||
// template names
|
||||
var templateIndex = "index"
|
||||
var templateNoteMetaTags = "note_metatags"
|
||||
|
||||
// AppShell represents the application in HTML
|
||||
type AppShell struct {
|
||||
DB *gorm.DB
|
||||
T *template.Template
|
||||
}
|
||||
|
||||
// ErrNotFound is an error indicating that a resource was not found
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
// NewAppShell parses the templates for the application
|
||||
func NewAppShell(db *gorm.DB, content []byte) (AppShell, error) {
|
||||
t, err := template.New(templateIndex).Parse(string(content))
|
||||
if err != nil {
|
||||
return AppShell{}, errors.Wrap(err, "parsing the index template")
|
||||
}
|
||||
|
||||
_, err = t.New(templateNoteMetaTags).Parse(noteMetaTags)
|
||||
if err != nil {
|
||||
return AppShell{}, errors.Wrap(err, "parsing the note meta tags template")
|
||||
}
|
||||
|
||||
return AppShell{DB: db, T: t}, nil
|
||||
}
|
||||
|
||||
// Execute executes the index template
|
||||
func (a AppShell) Execute(r *http.Request) ([]byte, error) {
|
||||
data, err := a.getData(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting data")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := a.T.ExecuteTemplate(&buf, templateIndex, data); err != nil {
|
||||
return nil, errors.Wrap(err, "executing template")
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a AppShell) getData(r *http.Request) (tmplData, error) {
|
||||
path := r.URL.Path
|
||||
|
||||
if ok, params := matchPath(path, notesPathRegex); ok {
|
||||
p, err := a.newNotePage(r, params[0])
|
||||
if err != nil {
|
||||
return tmplData{}, errors.Wrap(err, "instantiating note page")
|
||||
}
|
||||
|
||||
return p.getData()
|
||||
}
|
||||
|
||||
p := defaultPage{}
|
||||
return p.getData(), nil
|
||||
}
|
||||
|
||||
// matchPath checks if the given path matches the given regular expressions
|
||||
// and returns a boolean as well as any parameters from regex capture groups.
|
||||
func matchPath(p string, reg *regexp.Regexp) (bool, []string) {
|
||||
match := notesPathRegex.FindStringSubmatch(p)
|
||||
|
||||
if len(match) > 0 {
|
||||
return true, match[1:]
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors
|
||||
*
|
||||
* This file is part of Dnote.
|
||||
*
|
||||
* Dnote is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Dnote is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tmpl
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/dnote/dnote/pkg/assert"
|
||||
"github.com/dnote/dnote/pkg/server/testutils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestAppShellExecute(t *testing.T) {
|
||||
t.Run("home", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
a, err := NewAppShell(db, []byte("<head><title>{{ .Title }}</title>{{ .MetaTags }}</head>"))
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "preparing app shell"))
|
||||
}
|
||||
|
||||
r, err := http.NewRequest("GET", "http://mock.url/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "preparing request"))
|
||||
}
|
||||
|
||||
b, err := a.Execute(r)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "executing"))
|
||||
}
|
||||
|
||||
assert.Equal(t, string(b), "<head><title>Dnote</title></head>", "result mismatch")
|
||||
})
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors
|
||||
*
|
||||
* This file is part of Dnote.
|
||||
*
|
||||
* Dnote is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Dnote is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tmpl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dnote/dnote/pkg/server/database"
|
||||
"github.com/dnote/dnote/pkg/server/middleware"
|
||||
"github.com/dnote/dnote/pkg/server/operations"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var newlineRegexp = regexp.MustCompile(`\r?\n`)
|
||||
|
||||
// tmplData is the data to be passed to the app shell template
|
||||
type tmplData struct {
|
||||
Title string
|
||||
MetaTags template.HTML
|
||||
}
|
||||
|
||||
type noteMetaTagsData struct {
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
type notePage struct {
|
||||
Note database.Note
|
||||
T *template.Template
|
||||
}
|
||||
|
||||
func (a AppShell) newNotePage(r *http.Request, noteUUID string) (notePage, error) {
|
||||
user, _, err := middleware.AuthWithSession(a.DB, r)
|
||||
if err != nil {
|
||||
return notePage{}, errors.Wrap(err, "authenticating with session")
|
||||
}
|
||||
|
||||
note, ok, err := operations.GetNote(a.DB, noteUUID, &user)
|
||||
|
||||
if !ok {
|
||||
return notePage{}, ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return notePage{}, errors.Wrap(err, "getting note")
|
||||
}
|
||||
|
||||
return notePage{note, a.T}, nil
|
||||
}
|
||||
|
||||
func (p notePage) getTitle() string {
|
||||
note := p.Note
|
||||
date := time.Unix(0, note.AddedOn).Format("Jan 2 2006")
|
||||
|
||||
return fmt.Sprintf("Note: %s (%s)", note.Book.Label, date)
|
||||
}
|
||||
|
||||
func excerpt(s string, maxLen int) string {
|
||||
if len(s) > maxLen {
|
||||
|
||||
var lastIdx int
|
||||
if maxLen > 3 {
|
||||
lastIdx = maxLen - 3
|
||||
} else {
|
||||
lastIdx = maxLen
|
||||
}
|
||||
|
||||
return s[:lastIdx] + "..."
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func formatMetaDescContent(s string) string {
|
||||
desc := excerpt(s, 200)
|
||||
desc = strings.Trim(desc, " ")
|
||||
|
||||
return newlineRegexp.ReplaceAllString(desc, " ")
|
||||
}
|
||||
|
||||
func (p notePage) getMetaTags() (template.HTML, error) {
|
||||
title := p.getTitle()
|
||||
desc := formatMetaDescContent(p.Note.Body)
|
||||
|
||||
data := noteMetaTagsData{
|
||||
Title: title,
|
||||
Description: desc,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := p.T.ExecuteTemplate(&buf, templateNoteMetaTags, data); err != nil {
|
||||
return "", errors.Wrap(err, "executing template")
|
||||
}
|
||||
|
||||
return template.HTML(buf.String()), nil
|
||||
}
|
||||
|
||||
func (p notePage) getData() (tmplData, error) {
|
||||
mt, err := p.getMetaTags()
|
||||
if err != nil {
|
||||
return tmplData{}, errors.Wrap(err, "getting meta tags")
|
||||
}
|
||||
|
||||
dat := tmplData{
|
||||
Title: p.getTitle(),
|
||||
MetaTags: mt,
|
||||
}
|
||||
|
||||
return dat, nil
|
||||
}
|
||||
|
||||
type defaultPage struct {
|
||||
}
|
||||
|
||||
func (p defaultPage) getData() tmplData {
|
||||
return tmplData{
|
||||
Title: "Dnote",
|
||||
MetaTags: "",
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors
|
||||
*
|
||||
* This file is part of Dnote.
|
||||
*
|
||||
* Dnote is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Dnote is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tmpl
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dnote/dnote/pkg/assert"
|
||||
"github.com/dnote/dnote/pkg/server/database"
|
||||
"github.com/dnote/dnote/pkg/server/testutils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestDefaultPageGetData(t *testing.T) {
|
||||
p := defaultPage{}
|
||||
|
||||
result := p.getData()
|
||||
|
||||
assert.Equal(t, result.MetaTags, template.HTML(""), "MetaTags mismatch")
|
||||
assert.Equal(t, result.Title, "Dnote", "Title mismatch")
|
||||
}
|
||||
|
||||
func TestNotePageGetData(t *testing.T) {
|
||||
// Set time.Local to UTC for deterministic test
|
||||
time.Local = time.UTC
|
||||
|
||||
db := testutils.InitMemoryDB(t)
|
||||
a, err := NewAppShell(db, nil)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "preparing app shell"))
|
||||
}
|
||||
|
||||
p := notePage{
|
||||
Note: database.Note{
|
||||
Book: database.Book{
|
||||
Label: "vocabulary",
|
||||
},
|
||||
AddedOn: time.Date(2019, time.January, 2, 0, 0, 0, 0, time.UTC).UnixNano(),
|
||||
},
|
||||
T: a.T,
|
||||
}
|
||||
|
||||
result, err := p.getData()
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "executing"))
|
||||
}
|
||||
|
||||
assert.NotEqual(t, result.MetaTags, template.HTML(""), "MetaTags should not be empty")
|
||||
assert.Equal(t, result.Title, "Note: vocabulary (Jan 2 2019)", "Title mismatch")
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors
|
||||
*
|
||||
* This file is part of Dnote.
|
||||
*
|
||||
* Dnote is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Dnote is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tmpl
|
||||
|
||||
var noteMetaTags = `
|
||||
<meta name="description" content="{{ .Description }}" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content="{{ .Title }}" />
|
||||
<meta name="twitter:description" content="{{ .Description }}" />
|
||||
<meta name="twitter:image" content="https://dnote-asset.s3.amazonaws.com/images/logo-text-vertical.png" />
|
||||
<meta name="og:image" content="https://dnote-asset.s3.amazonaws.com/images/logo-text-vertical.png" />
|
||||
<meta name="og:title" content="{{ .Title }}" />
|
||||
<meta name="og:description" content="{{ .Description }}" />`
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{{define "yield"}}
|
||||
<div class="container page page-mobile-full books-page mobile-fw">
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-heading">Books</h1>
|
||||
</div>
|
||||
|
||||
<div class="frame books-content">
|
||||
<ul>
|
||||
{{range .Books}}
|
||||
<li>
|
||||
<a href="/?book={{.Label}}">
|
||||
{{ .Label }}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{{define "yield"}}
|
||||
content
|
||||
{{ .Note.Body }}
|
||||
{{end}}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{{define "book"}}
|
||||
<svg
|
||||
width="20px"
|
||||
height="20px"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<title>Book</title>
|
||||
<desc>Icon depicting a book</desc>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M26.5 32H5.5C3.56712 32 2 30.209 2 28V4C2 1.791 3.56712 0 5.5 0H26.5C28.4329 0 30 1.791 30 4V28C30 30.209 28.4329 32 26.5 32ZM9 4H6.20088C5.81238 4 5.5 4.357 5.5 4.8V27.199C5.5 27.642 5.81238 28 6.20088 28H9V4ZM26.5 4.8C26.5 4.357 26.1876 4 25.7991 4H23V13.594C23 14.033 22.7778 14.139 22.5048 13.827L19.9961 10.959C19.7231 10.647 19.2786 10.647 19.0048 10.959L16.4961 13.827C16.2222 14.139 16 14.033 16 13.594V4H10.75V28H25.7991C26.1876 28 26.5 27.642 26.5 27.199V4.8Z"
|
||||
fill="{{ .fill }}"
|
||||
/>
|
||||
</svg>
|
||||
{{end}}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{{define "caret"}}
|
||||
<svg
|
||||
width="12px"
|
||||
height="12px"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
class="icon--caret-{{ .direction }}"
|
||||
>
|
||||
<line
|
||||
y1="-1"
|
||||
x2="17.2395"
|
||||
y2="-1"
|
||||
transform="matrix(0.769979 0.638069 -0.653774 0.75669 3 11.5779)"
|
||||
stroke="{{ .stroke }}"
|
||||
stroke-width="4"
|
||||
/>
|
||||
<line
|
||||
y1="-1"
|
||||
x2="17.2045"
|
||||
y2="-1"
|
||||
transform="matrix(0.739684 -0.672954 0.688196 0.725524 16.2741 22.5779)"
|
||||
stroke="{{ .stroke }}"
|
||||
stroke-width="4"
|
||||
/>
|
||||
</svg>
|
||||
{{end}}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
{{define "yield"}}
|
||||
<div id="T-home-page" class="container page page-mobile-full home-page">
|
||||
<h1 class="sr-only">Notes</h1>
|
||||
|
||||
{{template "pageToolbar" dict "data" . "class" "toolbar"}}
|
||||
|
||||
<div class="note-group-list">
|
||||
{{if eq (len .NoteGroups) 0 }}
|
||||
<div class="note-group-list-empty">No notes found.</div>
|
||||
{{end}}
|
||||
|
||||
{{range .NoteGroups}}
|
||||
{{template "noteGroup" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "noteGroup"}}
|
||||
<section class="note-group">
|
||||
<header class="note-group-header">
|
||||
<h2 class="date">
|
||||
<time datetime="{{ toDateTime .Year .Month }}">
|
||||
{{ getFullMonthName .Month }} {{ .Year }}
|
||||
</time>
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<ul class="list-unstyled note-list">
|
||||
{{range .Data}}
|
||||
{{template "noteItem" .}}
|
||||
{{end}}
|
||||
</ul>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
{{define "noteItem"}}
|
||||
<li class="note-item">
|
||||
<a href="/notes/{{ .UUID }}" class="link">
|
||||
<div class="body">
|
||||
<div class="note-header">
|
||||
<h3 class="book-label">
|
||||
{{ .Book.Label }}
|
||||
</h3>
|
||||
|
||||
{{template "time" dict "value" .UpdatedAt "text" (timeAgo .UpdatedAt)}}
|
||||
</div>
|
||||
|
||||
<div class="note-content">
|
||||
{{ excerpt .Body 160 }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{define "pageToolbarContent"}}
|
||||
<nav class="paginator">
|
||||
<span class="paginator-info">
|
||||
<span class="paginator-label">{{ .CurrentPage }}</span> of
|
||||
<span class="paginator-label">{{ .MaxPage }}</span>
|
||||
</span>
|
||||
|
||||
{{template "pager" dict "disabled" (not .HasPrev) "direction" "left" "page" (add .CurrentPage -1)}}
|
||||
{{template "pager" dict "disabled" (not .HasNext) "direction" "right" "page" (add .CurrentPage 1)}}
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{define "pager"}}
|
||||
|
||||
{{$ariaLabel := ""}}
|
||||
{{if eq .direction "left"}}
|
||||
{{$ariaLabel = "Previous page"}}
|
||||
{{else}}
|
||||
{{$ariaLabel = "Next page"}}
|
||||
{{end}}
|
||||
|
||||
{{if .disabled}}
|
||||
<span class="paginator-link disabled">
|
||||
{{template "caret" dict "direction" .direction "stroke" "gray"}}
|
||||
</span>
|
||||
{{else}}
|
||||
<a
|
||||
href="/?page={{ .page }}"
|
||||
aria-label="{{ $ariaLabel }}"
|
||||
class="paginator-link"
|
||||
>
|
||||
{{template "caret" dict "direction" .direction "stroke" "black"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
{{define "yield"}}
|
||||
<div id="T-note-page" class="note-page">
|
||||
<div class="container mobile-nopadding page page-mobile-full">
|
||||
<article class="frame">
|
||||
<header class="header">
|
||||
<div class="header-left">
|
||||
{{template "book" dict "fill" "#000000"}}
|
||||
|
||||
<h1 class="book-label">
|
||||
<a href="/">
|
||||
{{ .Note.Book.Label }}
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
<section class="content-wrapper">
|
||||
<div class="markdown-body">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="ts">
|
||||
<span class="ts-head">Last edit: </span>
|
||||
{{ timeFormat .Note.UpdatedAt "January 02, 2006" }}
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{{define "pageToolbar"}}
|
||||
<div class="partial--page-toolbar{{if .class}} {{.class}}{{end}}">
|
||||
{{template "pageToolbarContent" .data}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{{define "time"}}
|
||||
|
||||
{{$mobileText := defaultValue .mobileText .text}}
|
||||
|
||||
<span class="partial--time">
|
||||
<time datetime="{{ toISOString .value }}">
|
||||
<span>
|
||||
<span class="text">{{ .text }}</span>
|
||||
<span class="mobile-text">{{ $mobileText }}</span>
|
||||
</span>
|
||||
</time>
|
||||
</span>
|
||||
{{end}}
|
||||
Loading…
Add table
Add a link
Reference in a new issue