nodebb-theme-harmony 3.0.0-beta.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +2 -2
  2. package/public/harmony.js +10 -12
  3. package/scss/harmony.scss +0 -2
  4. package/templates/account/categories.tpl +1 -1
  5. package/templates/account/consent.tpl +3 -1
  6. package/templates/account/info.tpl +2 -2
  7. package/templates/account/posts.tpl +1 -1
  8. package/templates/account/profile.tpl +2 -2
  9. package/templates/account/settings.tpl +3 -3
  10. package/templates/account/shares.tpl +1 -1
  11. package/templates/account/tags.tpl +1 -1
  12. package/templates/account/topics.tpl +1 -1
  13. package/templates/account/uploads.tpl +1 -1
  14. package/templates/category.tpl +1 -1
  15. package/templates/notifications.tpl +1 -1
  16. package/templates/partials/account/header.tpl +1 -1
  17. package/templates/partials/account/sidebar-left.tpl +1 -1
  18. package/templates/partials/categories/lastpost.tpl +1 -1
  19. package/templates/partials/category/tags.tpl +1 -1
  20. package/templates/partials/cookie-consent.tpl +2 -2
  21. package/templates/partials/mobile-nav.tpl +2 -2
  22. package/templates/partials/notifications_list.tpl +2 -2
  23. package/templates/partials/posts_list_item.tpl +2 -2
  24. package/templates/partials/search-filters.tpl +9 -9
  25. package/templates/partials/search-results.tpl +2 -3
  26. package/templates/partials/sidebar/drafts.tpl +8 -3
  27. package/templates/partials/skin-switcher.tpl +2 -2
  28. package/templates/partials/tags_list.tpl +3 -3
  29. package/templates/partials/topic/icon.tpl +4 -0
  30. package/templates/partials/topic/post.tpl +9 -12
  31. package/templates/partials/topic/tag.tpl +1 -1
  32. package/templates/partials/topic-list-bar.tpl +1 -1
  33. package/templates/partials/topics_list.tpl +5 -5
  34. package/templates/topic.tpl +1 -1
  35. package/templates/world.tpl +1 -1
  36. package/scss/groups.scss +0 -20
  37. package/scss/modules/cover.scss +0 -104
  38. package/templates/groups/details.tpl +0 -92
  39. package/templates/groups/list.tpl +0 -58
  40. package/templates/groups/members.tpl +0 -10
  41. package/templates/partials/groups/admin.tpl +0 -90
  42. package/templates/partials/groups/badge.tpl +0 -1
  43. package/templates/partials/groups/invited.tpl +0 -33
  44. package/templates/partials/groups/list.tpl +0 -17
  45. package/templates/partials/groups/memberlist.tpl +0 -47
  46. package/templates/partials/groups/pending.tpl +0 -29
  47. package/templates/partials/groups/sidebar-left.tpl +0 -27
  48. package/templates/partials/toast.tpl +0 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-theme-harmony",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0",
4
4
  "nbbpm": {
5
5
  "compatibility": "^4.14.0"
6
6
  },
@@ -45,7 +45,7 @@
45
45
  "@fontsource/poppins": "5.2.7"
46
46
  },
47
47
  "devDependencies": {
48
- "eslint": "10.4.1",
48
+ "eslint": "10.5.0",
49
49
  "eslint-config-nodebb": "^2.0.2"
50
50
  }
51
51
  }
package/public/harmony.js CHANGED
@@ -141,7 +141,7 @@ $(document).ready(function () {
141
141
  }
142
142
 
143
143
  function setupDrafts() {
144
- require(['composer/drafts', 'bootbox'], function (drafts, bootbox) {
144
+ require(['composer/drafts', 'modals', 'api'], function (drafts, modals, api) {
145
145
  const draftsEl = $('[component="sidebar/drafts"]');
146
146
  const bottomBarDraftsEl = $('[component="bottombar"] [component="sidebar/drafts"]');
147
147
  function updateBadgeCount() {
@@ -159,17 +159,15 @@ $(document).ready(function () {
159
159
  draftListEl.find('.draft-item-container').html('');
160
160
  return;
161
161
  }
162
- draftItems.reverse().forEach((draft) => {
163
- if (draft) {
164
- if (draft.title) {
165
- draft.title = utils.escapeHTML(String(draft.title));
166
- }
167
- draft.text = utils.escapeHTML(
168
- draft.text
169
- ).replace(/(?:\r\n|\r|\n)/g, '<br>');
162
+ draftItems.reverse();
163
+ await Promise.all(draftItems.map(async (item) => {
164
+ const cid = String(item.cid);
165
+ if (item && item.action === 'topics.post' && cid !== '0') {
166
+ const categoryUrl = cid !== '-1' ?
167
+ `/api/category/${encodeURIComponent(cid)}` : `/api/world`;
168
+ item.category = await api.get(categoryUrl, {});
170
169
  }
171
- });
172
-
170
+ }));
173
171
  const html = await app.parseAndTranslate('partials/sidebar/drafts', 'drafts', { drafts: draftItems });
174
172
  draftListEl.find('.no-drafts').addClass('hidden');
175
173
  draftListEl.find('.placeholder-wave').addClass('hidden');
@@ -185,7 +183,7 @@ $(document).ready(function () {
185
183
 
186
184
  draftsEl.on('click', '[component="drafts/delete"]', function () {
187
185
  const save_id = $(this).attr('data-save-id');
188
- bootbox.confirm({
186
+ modals.confirm({
189
187
  title: '[[modules:bootbox.confirm]]',
190
188
  message: '[[modules:composer.discard-draft-confirm]]',
191
189
  callback: function (ok) {
package/scss/harmony.scss CHANGED
@@ -10,7 +10,6 @@
10
10
  @import "mobilebar";
11
11
  @import "status";
12
12
  @import "account";
13
- @import "groups";
14
13
  @import "world";
15
14
  @import "modals";
16
15
 
@@ -19,7 +18,6 @@
19
18
  @import "modules/user-menu";
20
19
  @import "modules/topic-navigator";
21
20
  @import "modules/topics-list";
22
- @import "modules/cover";
23
21
  @import "modules/nprogress";
24
22
  @import "modules/paginator";
25
23
  @import "modules/filters";
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div class="d-flex justify-content-between align-items-center mb-3">
4
4
  <div class="d-flex gap-1">
5
- <h3 class="fw-semibold fs-5 mb-0">{title}</h3>
5
+ <h3 class="fw-semibold fs-5 mb-0">{tx(title)}</h3>
6
6
  </div>
7
7
 
8
8
  <div class="d-flex gap-1">
@@ -54,7 +54,8 @@
54
54
 
55
55
  <hr />
56
56
 
57
- <div class="btn-group-vertical d-grid">
57
+ {{{ if canExport }}}
58
+ <div class="d-grid gap-2">
58
59
  <a data-action="export-profile" class="btn btn-outline-secondary">
59
60
  <i class="fa fa-download"></i> [[user:consent.export-profile]]
60
61
  </a>
@@ -65,6 +66,7 @@
65
66
  <i class="fa fa-download"></i> [[user:consent.export-uploads]]
66
67
  </a>
67
68
  </div>
69
+ {{{ end }}}
68
70
  </div>
69
71
  </div>
70
72
  </div>
@@ -151,7 +151,7 @@
151
151
  {{{ end }}}
152
152
  </div>
153
153
  <p class="mb-1">
154
- <span class="reason">[[user:info.banned-reason-label]]: <strong>{./reason}</strong></span>
154
+ <span class="reason">[[user:info.banned-reason-label]]: <strong>{tx(./reason)}</strong></span>
155
155
  </p>
156
156
  <p>
157
157
  {{{ if ./until }}}
@@ -205,7 +205,7 @@
205
205
  {{{ end }}}
206
206
  </div>
207
207
  <p class="mb-1">
208
- <span class="reason">[[user:info.banned-reason-label]]: <strong>{./reason}</strong></span>
208
+ <span class="reason">[[user:info.banned-reason-label]]: <strong>{tx(./reason)}</strong></span>
209
209
  </p>
210
210
  <p>
211
211
  {{{ if ./until }}}
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
 
23
23
  {{{ if !posts.length }}}
24
- <div class="alert alert-warning text-center">{noItemsFoundKey}</div>
24
+ <div class="alert alert-warning text-center">{tx(noItemsFoundKey)}</div>
25
25
  {{{ end }}}
26
26
 
27
27
  <div>
@@ -10,7 +10,7 @@
10
10
 
11
11
  {{{ if aboutme }}}
12
12
  <div component="aboutme" class="text-sm text-break">
13
- {{aboutmeParsed}}
13
+ {{txEscape(aboutmeParsed)}}
14
14
  </div>
15
15
  {{{ end }}}
16
16
 
@@ -76,7 +76,7 @@
76
76
  {{{ if ./value }}}
77
77
  <div class="stat">
78
78
  <div class="align-items-center justify-content-center card card-header p-3 border-0 rounded-1 h-100 gap-2">
79
- <span class="stat-label text-xs fw-semibold"><span><i class="text-muted {./icon}"></i> {./name}</span></span>
79
+ <span class="stat-label text-xs fw-semibold"><span><i class="text-muted {./icon}"></i> {tx(./name)}</span></span>
80
80
  {{{ if (./type == "input-link") }}}
81
81
  <a class="text-center text-break w-100 px-2 ff-secondary text-underline text-reset" href="{./value}" rel="nofollow noreferrer">{./linkValue}</a>
82
82
  {{{ else }}}
@@ -10,7 +10,7 @@
10
10
  <label for="bootswatchSkin" class="form-label fw-bold">[[user:select-skin]]</label>
11
11
  <select class="form-select form-select-sm" id="bootswatchSkin" data-property="bootswatchSkin">
12
12
  {{{each bootswatchSkinOptions}}}
13
- <option value="{bootswatchSkinOptions.value}" {{{ if bootswatchSkinOptions.selected }}}selected{{{ end }}}>{bootswatchSkinOptions.name}</option>
13
+ <option value="{bootswatchSkinOptions.value}" {{{ if bootswatchSkinOptions.selected }}}selected{{{ end }}}>{tx(bootswatchSkinOptions.name)}</option>
14
14
  {{{end}}}
15
15
  </select>
16
16
 
@@ -177,7 +177,7 @@
177
177
  <label class="form-label text-sm" for="dailyDigestFreq">[[user:digest-label]]</label>
178
178
  <select class="form-select form-select-sm" id="dailyDigestFreq" data-property="dailyDigestFreq" autocomplete="off">
179
179
  {{{each dailyDigestFreqOptions}}}
180
- <option value="{./value}" {{{ if ./selected }}}selected="1"{{{ end }}}>{./name}</option>
180
+ <option value="{./value}" {{{ if ./selected }}}selected="1"{{{ end }}}>{tx(./name)}</option>
181
181
  {{{end}}}
182
182
  </select>
183
183
  <p class="form-text text-xs">[[user:digest-description]]</p>
@@ -271,7 +271,7 @@
271
271
  <tr component="notification/setting" class="align-middle">
272
272
  <td style="width:100%;">
273
273
  <div class="align-items-center">
274
- <label class="text-sm tracking-tight" for="{./name}">{./label}</label>
274
+ <label class="text-sm tracking-tight" for="{./name}">{tx(./label)}</label>
275
275
  <input type="hidden" data-property="{./name}" value="{./value}">
276
276
  </div>
277
277
  </td>
@@ -7,7 +7,7 @@
7
7
  </div>
8
8
 
9
9
  {{{ if !topics.length }}}
10
- <div class="alert alert-warning text-center">{noItemsFoundKey}</div>
10
+ <div class="alert alert-warning text-center">{tx(noItemsFoundKey)}</div>
11
11
  {{{ end }}}
12
12
 
13
13
  <div class="category">
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div class="d-flex justify-content-between align-items-center mb-3">
4
4
  <div class="d-flex gap-1">
5
- <h3 class="fw-semibold fs-5 mb-0">{title}</h3>
5
+ <h3 class="fw-semibold fs-5 mb-0">{tx(title)}</h3>
6
6
  </div>
7
7
  </div>
8
8
 
@@ -32,7 +32,7 @@
32
32
 
33
33
 
34
34
  {{{ if !topics.length }}}
35
- <div class="alert alert-warning text-center">{noItemsFoundKey}</div>
35
+ <div class="alert alert-warning text-center">{tx(noItemsFoundKey)}</div>
36
36
  {{{ end }}}
37
37
 
38
38
  <div class="category">
@@ -1,6 +1,6 @@
1
1
  <!-- IMPORT partials/account/header.tpl -->
2
2
 
3
- <h3 class="fw-semibold fs-5">{title}</h3>
3
+ <h3 class="fw-semibold fs-5">{tx(title)}</h3>
4
4
 
5
5
  <div class="alert alert-info text-center">
6
6
  {{{ if privateUploads }}}[[uploads:private-uploads-info]]{{{ else }}}[[uploads:public-uploads-info]]{{{ end }}}
@@ -18,7 +18,7 @@
18
18
  <span class="text-lowercase fw-normal">[[global:posts]]</span>
19
19
  </span>
20
20
  {{{ if !isNumber(cid) }}}
21
- <a href="{escape(./url)}" class="badge text-body border border-gray-300 text-xs" data-ajaxify="false">
21
+ <a href="{./url}" class="badge text-body border border-gray-300 text-xs" data-ajaxify="false">
22
22
  <span class="fw-normal">View Original</span>
23
23
  <i class="fa fa-external-link"></i>
24
24
  </a>
@@ -10,7 +10,7 @@
10
10
  <hr/>
11
11
  {{{ else }}}
12
12
  <a class="btn btn-ghost d-flex gap-2 text-start align-items-baseline text-sm ff-secondary fw-semibold {{{ if ./selected }}}active{{{ end }}}" href="{config.relative_path}/notifications?filter={./filter}">
13
- <div class="flex-grow-1">{filters.name}</div>
13
+ <div class="flex-grow-1">{tx(filters.name)}</div>
14
14
  {{{ if ./filter }}}
15
15
  <span class="flex-shrink-0 text-xs" title="{./count}">{humanReadableNumber(./count)}</span>
16
16
  {{{ end }}}
@@ -1,5 +1,5 @@
1
1
  <div class="account w-100 mx-auto">
2
- <div class="cover position-absolute start-0 top-0 w-100" component="account/cover" style="background-image: url({escape(cover:url)}); background-position: {cover:position};">
2
+ <div class="cover position-absolute start-0 top-0 w-100" component="account/cover" style="background-image: url({cover:url}); background-position: {cover:position};">
3
3
  <div class="container">
4
4
  {{{ if (allowCoverPicture && canEdit) }}}
5
5
  <div class="controls text-center">
@@ -119,7 +119,7 @@
119
119
 
120
120
  {{{ each profile_links }}}
121
121
  <a href="{config.relative_path}/user/{userslug}/{./route}" class="btn btn-ghost btn-sm ff-secondary text-xs text-start plugin-link {{{ if ./public }}}public{{{ else }}}private{{{ end }}} {{{ if (url == ./url) }}}active{{{ end }}}" id="{./id}">
122
- <div class="flex-grow-1">{./name}</div>
122
+ <div class="flex-grow-1">{tx(./name)}</div>
123
123
  </a>
124
124
  {{{end}}}
125
125
  </div>
@@ -8,7 +8,7 @@
8
8
  </div>
9
9
  <div class="post-content text-xs text-break line-clamp-sm-2 lh-sm position-relative flex-fill">
10
10
  <a class="stretched-link" tabindex="-1" href="{config.relative_path}/topic/{./topic.slug}{{{ if ./index }}}/{./index}{{{ end }}}" aria-label="[[global:lastpost]]"></a>
11
- {{./content}}
11
+ {{txEscape(./content)}}
12
12
  </div>
13
13
  </div>
14
14
  {{{ end }}}
@@ -1,3 +1,3 @@
1
1
  {{{ each tags }}}
2
- <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 fw-normal tag tag-class-{./class}" data-tag="{./value}">{./valueEscaped}</span></a>
2
+ <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 fw-normal tag tag-class-{./class}" data-tag="{./value}">{./value}</span></a>
3
3
  {{{ end }}}
@@ -1,6 +1,6 @@
1
1
  <div class="position-fixed bottom-0 w-100 px-3 px-md-0 pb-5 mb-5 pb-lg-0 mb-lg-0" style="z-index: 2;">
2
2
  <div class="cookie-consent d-flex flex-column flex-lg-row gap-2 text-bg-info col-12 col-sm-8 col-lg-6 p-3 mx-auto rounded mb-5 mb-md-3 justify-content-between align-items-center">
3
- <span>{message} <a class="fw-bold" target="_blank" rel="noopener" href="{link_url}">{link}</a></span>
4
- <button class="btn btn-primary text-nowrap">{dismiss}</button>
3
+ <span>{tx(message)} <a class="fw-bold" target="_blank" rel="noopener" href="{link_url}">{tx(link)}</a></span>
4
+ <button class="btn btn-primary text-nowrap">{tx(dismiss)}</button>
5
5
  </div>
6
6
  </div>
@@ -10,14 +10,14 @@
10
10
  <ul class="navigation-dropdown dropdown-menu" role="menu">
11
11
  {{{ each navigation }}}
12
12
  {{{ if displayMenuItem(@root, @index) }}}
13
- <li class="nav-item {./class}{{{ if ./dropdown }}} dropend{{{ end }}}" title="{./title}">
13
+ <li class="nav-item {./class}{{{ if ./dropdown }}} dropend{{{ end }}}" title="{tx(./title)}">
14
14
  <a class="nav-link navigation-link px-3 py-2 {{{ if ./dropdown }}}dropdown-toggle{{{ end }}}" {{{ if ./dropdown }}} href="#" role="button" data-bs-toggle="collapse" data-bs-target="#collapse-target-{@index}" onclick="event.stopPropagation();" {{{ else }}} href="{./route}"{{{ end }}} {{{ if ./id }}}id="{./id}"{{{ end }}}{{{ if ./targetBlank }}} target="_blank"{{{ end }}}>
15
15
  <span class="d-inline-flex justify-content-between align-items-center w-100">
16
16
  <span class="text-nowrap">
17
17
  {{{ if ./iconClass }}}
18
18
  <i class="fa fa-fw {./iconClass}" data-content="{./content}"></i>
19
19
  {{{ end }}}
20
- {{{ if ./text }}}<span class="nav-text px-2 fw-semibold">{./text}</span>{{{ end }}}
20
+ {{{ if ./text }}}<span class="nav-text px-2 fw-semibold">{tx(./text)}</span>{{{ end }}}
21
21
  </span>
22
22
  <span component="navigation/count" class="badge rounded-1 bg-primary {{{ if !./content }}}hidden{{{ end }}}">{./content}</span>
23
23
  </span>
@@ -23,11 +23,11 @@
23
23
 
24
24
  <div class="d-flex flex-grow-1 flex-column gap-1 align-items-start position-relative">
25
25
  <a href="{./path}" class="text-decoration-none d-inline-block text-reset text-break text-sm ff-sans stretched-link" component="notifications/item/link">
26
- {./bodyShort}
26
+ {{./bodyShort}}
27
27
  </a>
28
28
  {{{ if ./bodyLong}}}
29
29
  <div class="text-secondary text-sm line-clamp-2 text-contain hidden-blockquote hidden-pre hidden-first-child-br">
30
- {./bodyLong}
30
+ {{./bodyLong}}
31
31
  </div>
32
32
  {{{ end }}}
33
33
  <div class="text-xs text-muted">{{{ if ./timeagoLong }}}{./timeagoLong}{{{ else }}}<span class="timeago" title="{./datetimeISO}"></span>{{{ end }}}</div>
@@ -14,14 +14,14 @@
14
14
  </div>
15
15
 
16
16
  <div component="post/content" class="content text-sm text-break">
17
- {{./content}}
17
+ {{txEscape(./content)}}
18
18
  </div>
19
19
  </div>
20
20
  <div class="mb-3 d-flex flex-wrap gap-1 w-100">
21
21
  {{buildCategoryLabel(./category, "a", "border")}}
22
22
  <span data-tid="{./topic.tid}" component="topic/tags" class="lh-1 tag-list d-flex flex-wrap gap-1 {{{ if !./topic.tags.length }}}hidden{{{ end }}}">
23
23
  {{{ each ./topic.tags }}}
24
- <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 fw-normal tag tag-class-{./class}" data-tag="{./value}">{./valueEscaped}</span></a>
24
+ <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 fw-normal tag tag-class-{./class}" data-tag="{./value}">{./value}</span></a>
25
25
  {{{ end }}}
26
26
  </span>
27
27
  </div>
@@ -3,7 +3,7 @@
3
3
  <div class="post-search-item">
4
4
  <div component="category/filter" class="dropdown" data-filter-name="category">
5
5
  <a component="category/filter/button" class="filter-btn btn btn-light btn-sm border {{{ if filters.categories.active }}}active-filter{{{ end }}} dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-haspopup="true" aria-expanded="false">
6
- <span class="filter-label">{{{ if filters.categories.active }}}{filters.categories.label}{{{ else }}}[[search:categories]]{{{ end }}}</span>
6
+ <span class="filter-label">{{{ if filters.categories.active }}}{tx(filters.categories.label)}{{{ else }}}[[search:categories]]{{{ end }}}</span>
7
7
  <span class="caret text-primary opacity-75"></span>
8
8
  </a>
9
9
 
@@ -29,7 +29,7 @@
29
29
  <div class="post-search-item">
30
30
  <div component="tag/filter" class="dropdown" data-filter-name="tag">
31
31
  <a component="tag/filter/button" class="filter-btn btn btn-light btn-sm border {{{ if filters.tags.active }}}active-filter{{{ end }}} dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-haspopup="true" aria-expanded="false">
32
- <span class="filter-label">{{{ if filters.tags.active }}}{filters.tags.label}{{{ else }}}[[search:tags]]{{{ end }}}</span>
32
+ <span class="filter-label">{{{ if filters.tags.active }}}{{tx(filters.tags.label)}}{{{ else }}}[[search:tags]]{{{ end }}}</span>
33
33
  <span class="caret text-primary opacity-75"></span>
34
34
  </a>
35
35
 
@@ -39,15 +39,15 @@
39
39
  <div component="tag/filter/selected" class="d-flex flex-wrap gap-2">
40
40
  {{{ each tagFilterSelected }}}
41
41
  <div class="d-flex px-2 py-1 rounded-1 text-bg-primary gap-2 align-items-center text-sm">
42
- <div>{./valueEscaped}</div>
43
- <button component="tag/filter/delete" data-tag="{./valueEscaped}" class="btn btn-primary btn-sm py-0"><i class="fa fa-times fa-xs"></i></button>
42
+ <div>{./value}</div>
43
+ <button component="tag/filter/delete" data-tag="{./value}" class="btn btn-primary btn-sm py-0"><i class="fa fa-times fa-xs"></i></button>
44
44
  </div>
45
45
  {{{ end }}}
46
46
  </div>
47
47
  <hr class="my-2"/>
48
48
  <div component="tag/filter/results" class="d-flex flex-wrap gap-2">
49
49
  {{{ each tagFilterResults }}}
50
- <button class="btn btn-light btn-sm border" data-tag="{./valueEscaped}">{./valueEscaped}</button>
50
+ <button class="btn btn-light btn-sm border" data-tag="{./value}">{./value}</button>
51
51
  {{{ end }}}
52
52
  </div>
53
53
  </li>
@@ -59,7 +59,7 @@
59
59
  <div class="post-search-item">
60
60
  <div component="user/filter" class="dropdown" data-filter-name="user">
61
61
  <a component="user/filter/button" class="filter-btn btn btn-light btn-sm border {{{ if filters.users.active }}}active-filter{{{ end }}} dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-haspopup="true" aria-expanded="false">
62
- <span class="filter-label">{{{ if filters.users.active }}}{filters.users.label}{{{ else }}}[[search:posted-by]]{{{ end }}}</span>
62
+ <span class="filter-label">{{{ if filters.users.active }}}{{tx(filters.users.label)}}{{{ else }}}[[search:posted-by]]{{{ end }}}</span>
63
63
  <span class="caret text-primary opacity-75"></span>
64
64
  </a>
65
65
 
@@ -90,7 +90,7 @@
90
90
  <div class="post-search-item">
91
91
  <div class="dropdown" data-filter-name="replies">
92
92
  <a id="reply-count-button" class="filter-btn btn btn-light btn-sm border {{{ if filters.replies.active }}}active-filter{{{ end }}} dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
93
- <span class="filter-label">{{{ if filters.replies.active }}}{filters.replies.label}{{{ else }}}[[search:replies]]{{{ end }}}</span>
93
+ <span class="filter-label">{{{ if filters.replies.active }}}{tx(filters.replies.label)}{{{ else }}}[[search:replies]]{{{ end }}}</span>
94
94
  <span class="caret text-primary opacity-75"></span>
95
95
  </a>
96
96
 
@@ -110,7 +110,7 @@
110
110
  <div class="post-search-item">
111
111
  <div class="dropdown" data-filter-name="time">
112
112
  <a id="post-time-button" class="filter-btn btn btn-light btn-sm border {{{ if filters.time.active }}}active-filter{{{ end }}} dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
113
- <span class="filter-label">{{{ if filters.time.active }}}{filters.time.label}{{{ else }}}[[search:time]]{{{ end }}}</span>
113
+ <span class="filter-label">{{{ if filters.time.active }}}{tx(filters.time.label)}{{{ else }}}[[search:time]]{{{ end }}}</span>
114
114
  <span class="caret text-primary opacity-75"></span>
115
115
  </a>
116
116
 
@@ -139,7 +139,7 @@
139
139
  <div class="post-search-item">
140
140
  <div class="dropdown" data-filter-name="sort">
141
141
  <a id="sort-by-button" class="filter-btn btn btn-light btn-sm border {{{ if filters.sort.active }}}active-filter{{{ end }}} dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
142
- <span class="filter-label">{{{ if filters.sort.active }}}{filters.sort.label}{{{ else }}}[[search:sort]]{{{ end }}}</span>
142
+ <span class="filter-label">{{{ if filters.sort.active }}}{tx(filters.sort.label)}{{{ else }}}[[search:sort]]{{{ end }}}</span>
143
143
  <span class="caret text-primary opacity-75"></span>
144
144
  </a>
145
145
 
@@ -1,6 +1,5 @@
1
1
  {{{ if matchCount }}}
2
- <div class="card card-header text-xs px-2 py-1 fw-semibold border-0 align-self-start">[[search:results-matching, {matchCount}, {txEscape(search_query)}, {time}]]
3
- </div>
2
+ <div class="card card-header text-xs px-2 py-1 fw-semibold border-0 align-self-start">{{tx("search:results-matching", matchCount, txEscape(search_query), time)}}</div>
4
3
  {{{ else }}}
5
4
  {{{ if search_query }}}
6
5
  <div class="badge text-bg-warning align-self-start">[[search:no-matches]]</div>
@@ -25,7 +24,7 @@
25
24
  <div class="d-flex gap-3 post-info">
26
25
  <div class="post-author d-flex gap-1">
27
26
  <a class="lh-1 text-decoration-none" href="{config.relative_path}/user/{./user.userslug}">{{buildAvatar(./user, "16px", true, "not-responsive")}}</a>
28
- <a class="fw-semibold text-sm" href="{config.relative_path}/user/{./user.userslug}">{../user.displayname}</a>
27
+ <a class="fw-semibold text-sm" href="{config.relative_path}/user/{./user.userslug}">{./user.displayname}</a>
29
28
  </div>
30
29
  <span class="timeago text-sm text-muted" title="{./timestampISO}"></span>
31
30
  </div>
@@ -35,17 +35,22 @@
35
35
  {{{ end }}}
36
36
 
37
37
  {{{ if (./action == "posts.reply") }}}
38
- <div class="text text-xs fw-semibold line-clamp-2 text-break">[[topic:composer.replying-to, "{txEscape(./title)}"]]</div>
38
+ <div class="text text-xs fw-semibold line-clamp-2 text-break">{{tx("topic:composer.replying-to", txEscape(quote(./title)))}}</div>
39
39
  {{{ end }}}
40
40
 
41
41
  {{{ if (./action == "posts.edit") }}}
42
- <div class="text text-xs fw-semibold line-clamp-2">[[topic:composer.editing-in, "{txEscape(./title)}"]]</div>
42
+ <div class="text text-xs fw-semibold line-clamp-2">{{tx("topic:composer.editing-in", txEscape(quote(./title)))}}</div>
43
43
  {{{ end }}}
44
44
 
45
45
  {{{ if ./text }}}
46
46
  <div class="text text-sm line-clamp-3 text-break">{./text}</div>
47
47
  {{{ end }}}
48
- <div class="timeago text-xs text-muted" title="{./timestampISO}"></div>
48
+ <div class="d-flex align-items-baseline gap-2">
49
+ {{{ if (./action == "topics.post") }}}
50
+ {{buildCategoryLabel(./category, "span", "border")}}
51
+ {{{ end }}}
52
+ <div class="timeago text-xs text-muted" title="{./timestampISO}"></div>
53
+ </div>
49
54
  </a>
50
55
  <div>
51
56
  <button component="drafts/delete" data-save-id="{./save_id}" class="btn btn-light btn-sm">
@@ -37,12 +37,12 @@
37
37
  <div class="d-grid" style="grid-template-columns: 1fr 1fr;">
38
38
  {{{ each bootswatchSkinOptions.default }}}
39
39
  <li>
40
- <a href="#" class="dropdown-item rounded-1" data-value="{./value}" role="menuitem">{./name} <i class="fa fa-fw fa-check {{{ if !./selected }}} invisible {{{ end }}}"></i></a>
40
+ <a href="#" class="dropdown-item rounded-1" data-value="{./value}" role="menuitem">{tx(./name)} <i class="fa fa-fw fa-check {{{ if !./selected }}} invisible {{{ end }}}"></i></a>
41
41
  </li>
42
42
  {{{ end }}}
43
43
  {{{ each bootswatchSkinOptions.custom }}}
44
44
  <li>
45
- <a href="#" class="dropdown-item rounded-1" data-value="{./value}" role="menuitem">{./name} <i class="fa fa-fw fa-check {{{ if !./selected }}} invisible {{{ end }}}"></i></a>
45
+ <a href="#" class="dropdown-item rounded-1" data-value="{./value}" role="menuitem">{tx(./name)} <i class="fa fa-fw fa-check {{{ if !./selected }}} invisible {{{ end }}}"></i></a>
46
46
  </li>
47
47
  {{{ end }}}
48
48
  </div>
@@ -1,8 +1,8 @@
1
1
  {{{each tags}}}
2
2
  <div>
3
- <a href="{config.relative_path}/tags/{./valueEncoded}" data-tag="{./valueEscaped}" class="btn btn-ghost ff-base d-flex flex-column gap-1 align-items-start justify-content-start text-truncate p-2">
4
- <div class="fw-semibold text-nowrap tag-item w-100 text-start text-truncate">{./valueEscaped}</div>
5
- <div class="text-xs text-muted text-nowrap tag-topic-count">[[global:x-topics, {txEscape(formattedNumber(./score))}]]</div>
3
+ <a href="{config.relative_path}/tags/{./valueEncoded}" data-tag="{./value}" class="btn btn-ghost ff-base d-flex flex-column gap-1 align-items-start justify-content-start text-truncate p-2">
4
+ <div class="fw-semibold text-nowrap tag-item w-100 text-start text-truncate">{./value}</div>
5
+ <div class="text-xs text-muted text-nowrap tag-topic-count">{tx("global:x-topics", formattedNumber(./score))}</div>
6
6
  </a>
7
7
  </div>
8
8
  {{{end}}}
@@ -0,0 +1,4 @@
1
+ <span class="badge border {./classes}">
2
+ {{{ if ./icon }}}<i class="fa {./icon}"></i>{{{ end }}}
3
+ {{{ if ./label }}}<span> {tx(./label)}</span>{{{ end }}}
4
+ </span>
@@ -17,7 +17,7 @@
17
17
  {{{ else }}}
18
18
  <span component="user/locality" class="position-absolute top-100 start-100 lh-1 border border-white border-2 rounded-circle small" title="[[global:remote-user]]">
19
19
  <span class="visually-hidden">[[global:remote-user]]</span>
20
- <i class="fa fa-globe"></i>
20
+ <i class="fa fa-globe fa-width-auto"></i>
21
21
  </span>
22
22
  {{{ end }}}
23
23
  </a>
@@ -37,7 +37,7 @@
37
37
  {{{ else }}}
38
38
  <span component="user/locality" class="position-absolute top-100 start-100 lh-1 border border-white border-2 rounded-circle small" title="[[global:remote-user]]">
39
39
  <span class="visually-hidden">[[global:remote-user]]</span>
40
- <i class="fa fa-globe"></i>
40
+ <i class="fa fa-globe fa-width-auto"></i>
41
41
  </span>
42
42
  {{{ end }}}
43
43
  </a>
@@ -64,13 +64,10 @@
64
64
  </div>
65
65
 
66
66
  {{{ if posts.user.custom_profile_info.length }}}
67
- <div>
68
- <span>
69
-
70
- {{{ each posts.user.custom_profile_info }}}
71
- {{posts.user.custom_profile_info.content}}
72
- {{{ end }}}
73
- </span>
67
+ <div class="d-flex gap-1 align-items-center">
68
+ {{{ each posts.user.custom_profile_info }}}
69
+ {{posts.user.custom_profile_info.content}}
70
+ {{{ end }}}
74
71
  </div>
75
72
  {{{ end }}}
76
73
  </div>
@@ -81,12 +78,12 @@
81
78
  </div>
82
79
 
83
80
  <div class="content text-break" component="post/content" itemprop="text">
84
- {{posts.content}}
81
+ {{txEscape(posts.content)}}
85
82
  </div>
86
83
 
87
84
  <div component="post/footer" class="post-footer border-bottom pb-2">
88
85
  {{{ if posts.user.signature }}}
89
- <div component="post/signature" data-uid="{posts.user.uid}" class="text-xs text-muted mt-2">{{posts.user.signature}}</div>
86
+ <div component="post/signature" data-uid="{posts.user.uid}" class="text-xs text-muted mt-2">{{txEscape(posts.user.signature)}}</div>
90
87
  {{{ end }}}
91
88
 
92
89
  <div class="d-flex flex-wrap-reverse gap-2 {{{ if (hideReplies || !posts.replies.count) }}}justify-content-end{{{ else }}}justify-content-between{{{ end }}}">
@@ -101,7 +98,7 @@
101
98
  {{{ end }}}
102
99
  </span>
103
100
 
104
- <span class="ms-2 replies-count fw-semibold text-nowrap" component="post/reply-count/text" data-replies="{posts.replies.count}">{posts.replies.text}</span>
101
+ <span class="ms-2 replies-count fw-semibold text-nowrap" component="post/reply-count/text" data-replies="{posts.replies.count}">{tx(posts.replies.text)}</span>
105
102
  <span class="ms-2 replies-last hidden-xs fw-semibold">[[topic:last-reply-time]] <span class="timeago" title="{posts.replies.timestampISO}"></span></span>
106
103
 
107
104
  <i class="fa fa-fw fa-chevron-down" component="post/replies/open"></i>
@@ -1 +1 @@
1
- <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 text-xs tag tag-class-{./class}" data-tag="{./value}">{./valueEscaped}</span></a>
1
+ <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 text-xs tag tag-class-{./class}" data-tag="{./value}">{./value}</span></a>
@@ -54,7 +54,7 @@
54
54
 
55
55
  <!-- IMPORT partials/category/tools-dropdown-left.tpl -->
56
56
 
57
- <a href="{{{ if (template.category || template.world) }}}{escape(url)}{{{ else }}}{config.relative_path}/{selectedFilter.url}{querystring}{{{ end }}}" class="btn btn-secondary fw-semibold position-absolute top-100 translate-middle-x start-50 mt-1 hide" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;" id="new-topics-alert">
57
+ <a href="{{{ if (template.category || template.world) }}}{url}{{{ else }}}{config.relative_path}/{selectedFilter.url}{querystring}{{{ end }}}" class="btn btn-secondary fw-semibold position-absolute top-100 translate-middle-x start-50 mt-1 hide" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;" id="new-topics-alert">
58
58
  <i class="fa fa-fw fa-arrow-up"></i> [[recent:load-new-posts]]
59
59
  </a>
60
60
  </div>
@@ -3,7 +3,7 @@
3
3
  {{{ each topics }}}
4
4
  <li component="category/topic" class="category-item hover-parent border-bottom py-3 py-lg-4 d-flex flex-column flex-lg-row align-items-start {generateTopicClass(@value)}" <!-- IMPORT partials/data/category.tpl -->>
5
5
  <link itemprop="url" content="{config.relative_path}/topic/{./slug}" />
6
- <meta itemprop="name" content="{{generateTopicTitle(@value)}}" />
6
+ <meta itemprop="name" content="{./title}" />
7
7
  <meta itemprop="itemListOrder" content="descending" />
8
8
  <meta itemprop="position" content="{increment(./index, "1")}" />
9
9
  <a id="{./index}" data-index="{./index}" component="topic/anchor"></a>
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
  <div class="flex-grow-1 d-flex flex-wrap gap-1 position-relative">
23
23
  <h3 component="topic/header" class="title text-break text-wrap-pretty fs-5 fw-semibold m-0 tracking-tight w-100 {{{ if showSelect }}}me-4 me-lg-0{{{ end }}}">
24
- <a class="text-reset" href="{{{ if topics.noAnchor }}}#{{{ else }}}{config.relative_path}/topic/{./slug}{{{ if ./bookmark }}}/{./bookmark}{{{ end }}}{{{ end }}}">{{generateTopicTitle(@value)}}</a>
24
+ <a class="text-reset" href="{{{ if topics.noAnchor }}}#{{{ else }}}{config.relative_path}/topic/{./slug}{{{ if ./bookmark }}}/{./bookmark}{{{ end }}}{{{ end }}}">{./title}</a>
25
25
  </h3>
26
26
  <span component="topic/labels" class="d-flex flex-wrap gap-1 w-100">
27
27
  <span component="topic/watched" class="badge border border-gray-300 text-body {{{ if !./followed }}}hidden{{{ end }}}">
@@ -48,7 +48,7 @@
48
48
  <i class="fa fa-arrow-circle-right"></i>
49
49
  <span>[[topic:moved]]</span>
50
50
  </span>
51
- {{{each ./icons}}}<span class="lh-1">{@value}</span>{{{end}}}
51
+ {{{ each ./icons }}}<!-- IMPORT partials/topic/icon.tpl -->{{{ end }}}
52
52
 
53
53
  {{{ if (!template.category || (cid != ./cid)) }}}
54
54
  {{buildCategoryLabel(./category, "a", "border")}}
@@ -56,7 +56,7 @@
56
56
 
57
57
  <span data-tid="{./tid}" component="topic/tags" class="lh-1 tag-list d-flex flex-wrap gap-1 {{{ if !./tags.length }}}hidden{{{ end }}}">
58
58
  {{{ each ./tags }}}
59
- <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 fw-normal tag tag-class-{./class}" data-tag="{./value}">{./valueEscaped}</span></a>
59
+ <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 fw-normal tag tag-class-{./class}" data-tag="{./value}">{./value}</span></a>
60
60
  {{{ end }}}
61
61
  </span>
62
62
 
@@ -119,7 +119,7 @@
119
119
  </div>
120
120
  <div class="post-content text-xs ps-2 line-clamp-sm-2 lh-sm text-break position-relative flex-fill">
121
121
  <a class="stretched-link" tabindex="-1" href="{config.relative_path}/topic/{./slug}/{./teaser.index}" aria-label="[[global:lastpost]]"></a>
122
- {{./teaser.content}}
122
+ {{txEscape(./teaser.content)}}
123
123
  </div>
124
124
  {{{ end }}}
125
125
  {{{ end }}}