nodebb-theme-harmony 1.0.0-beta.99 → 1.0.1

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 (33) hide show
  1. package/languages/en-GB/harmony.json +1 -0
  2. package/library.js +32 -1
  3. package/package.json +1 -1
  4. package/public/harmony.js +18 -13
  5. package/scss/chats.scss +22 -0
  6. package/scss/modals.scss +3 -6
  7. package/scss/modules/tags.scss +2 -2
  8. package/scss/modules/topic-navigator.scss +3 -1
  9. package/scss/topic.scss +6 -0
  10. package/templates/account/profile.tpl +17 -0
  11. package/templates/account/theme.tpl +5 -0
  12. package/templates/admin/plugins/harmony.tpl +4 -0
  13. package/templates/chats.tpl +1 -1
  14. package/templates/flags/list.tpl +0 -1
  15. package/templates/partials/categories/item.tpl +3 -3
  16. package/templates/partials/categories/lastpost.tpl +1 -1
  17. package/templates/partials/chats/message-window.tpl +4 -2
  18. package/templates/partials/chats/message.tpl +4 -4
  19. package/templates/partials/chats/recent_room.tpl +3 -3
  20. package/templates/partials/flags/bulk-actions.tpl +2 -2
  21. package/templates/partials/flags/filters.tpl +2 -0
  22. package/templates/partials/groups/invited.tpl +1 -1
  23. package/templates/partials/groups/memberlist.tpl +1 -1
  24. package/templates/partials/notifications_list.tpl +4 -4
  25. package/templates/partials/sidebar/notifications.tpl +1 -1
  26. package/templates/partials/sidebar/user-menu.tpl +5 -5
  27. package/templates/partials/topic/navigator.tpl +16 -14
  28. package/templates/partials/topic/post-menu-list.tpl +2 -2
  29. package/templates/partials/topic/post.tpl +2 -2
  30. package/templates/partials/topic/quickreply.tpl +1 -1
  31. package/templates/partials/topic/reply-button.tpl +4 -4
  32. package/templates/partials/topic-list-bar.tpl +1 -1
  33. package/templates/partials/topics_list.tpl +11 -8
@@ -6,6 +6,7 @@
6
6
  "settings.title": "Theme settings",
7
7
  "settings.enableQuickReply": "Enable quick reply",
8
8
  "settings.centerHeaderElements": "Center header elements",
9
+ "settings.mobileTopicTeasers": "Show topic teasers on mobile",
9
10
  "settings.stickyToolbar": "Sticky toolbar",
10
11
  "settings.stickyToolbar.help": "The toolbar on topic and category pages will stick to the top of the page",
11
12
  "settings.autohideBottombar": "Auto hide bottom bar",
package/library.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const nconf = require.main.require('nconf');
3
4
  const meta = require.main.require('./src/meta');
4
5
  const _ = require.main.require('lodash');
5
6
  const user = require.main.require('./src/user');
@@ -11,6 +12,7 @@ const library = module.exports;
11
12
  const defaults = {
12
13
  enableQuickReply: 'on',
13
14
  centerHeaderElements: 'off',
15
+ mobileTopicTeasers: 'off',
14
16
  stickyToolbar: 'on',
15
17
  autohideBottombar: 'off',
16
18
  openSidebars: 'off',
@@ -28,8 +30,26 @@ library.init = async function (params) {
28
30
  middleware.canViewUsers,
29
31
  middleware.checkAccountPermissions,
30
32
  ], controllers.renderThemeSettings);
33
+
34
+ if (nconf.get('isPrimary') && process.env.NODE_ENV === 'production') {
35
+ setTimeout(buildSkins, 0);
36
+ }
31
37
  };
32
38
 
39
+ async function buildSkins() {
40
+ try {
41
+ const plugins = require.main.require('./src/plugins');
42
+ await plugins.prepareForBuild(['client side styles']);
43
+ for (const skin of meta.css.supportedSkins) {
44
+ // eslint-disable-next-line no-await-in-loop
45
+ await meta.css.buildBundle(`client-${skin}`, true);
46
+ }
47
+ require.main.require('./src/meta/minifier').killAll();
48
+ } catch (err) {
49
+ console.error(err.stack);
50
+ }
51
+ }
52
+
33
53
  library.addAdminNavigation = async function (header) {
34
54
  header.plugins.push({
35
55
  route: '/plugins/harmony',
@@ -98,6 +118,16 @@ library.defineWidgetAreas = async function (areas) {
98
118
  template: 'global',
99
119
  location: 'brand-header',
100
120
  },
121
+ {
122
+ name: 'About me (before)',
123
+ template: 'account/profile.tpl',
124
+ location: 'profile-aboutme-before',
125
+ },
126
+ {
127
+ name: 'About me (after)',
128
+ template: 'account/profile.tpl',
129
+ location: 'profile-aboutme-after',
130
+ },
101
131
  ]);
102
132
 
103
133
  return areas;
@@ -112,6 +142,7 @@ async function loadThemeConfig(uid) {
112
142
  const config = { ...defaults, ...themeConfig, ...(_.pick(userConfig, Object.keys(defaults))) };
113
143
  config.enableQuickReply = config.enableQuickReply === 'on';
114
144
  config.centerHeaderElements = config.centerHeaderElements === 'on';
145
+ config.mobileTopicTeasers = config.mobileTopicTeasers === 'on';
115
146
  config.stickyToolbar = config.stickyToolbar === 'on';
116
147
  config.autohideBottombar = config.autohideBottombar === 'on';
117
148
  config.openSidebars = config.openSidebars === 'on';
@@ -119,7 +150,7 @@ async function loadThemeConfig(uid) {
119
150
  }
120
151
 
121
152
  library.getThemeConfig = async function (config) {
122
- config.theme = await loadThemeConfig(config.uid);;
153
+ config.theme = await loadThemeConfig(config.uid);
123
154
  config.openDraftsOnPageLoad = false;
124
155
  return config;
125
156
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-theme-harmony",
3
- "version": "1.0.0-beta.99",
3
+ "version": "1.0.1",
4
4
  "nbbpm": {
5
5
  "compatibility": "^3.0.0"
6
6
  },
package/public/harmony.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  $(document).ready(function () {
4
+ setupSkinSwitcher();
4
5
  setupNProgress();
5
6
  setupMobileMenu();
6
7
  setupSearch();
@@ -9,21 +10,22 @@ $(document).ready(function () {
9
10
  setupNavTooltips();
10
11
  fixPlaceholders();
11
12
 
12
- $('[component="skinSwitcher"]').on('click', '.dropdown-item', function () {
13
- const skin = $(this).attr('data-value');
14
- $('[component="skinSwitcher"] .dropdown-item .fa-check').addClass('invisible');
15
- $(this).find('.fa-check').removeClass('invisible');
16
- require(['forum/account/settings'], function (accountSettings) {
17
- $('[component="skinSwitcher"] [component="skinSwitcher/icon"]').addClass('fa-fade');
18
- accountSettings.changeSkin(skin);
13
+ function setupSkinSwitcher() {
14
+ $('[component="skinSwitcher"]').on('click', '.dropdown-item', function () {
15
+ const skin = $(this).attr('data-value');
16
+ $('[component="skinSwitcher"] .dropdown-item .fa-check').addClass('invisible');
17
+ $(this).find('.fa-check').removeClass('invisible');
18
+ require(['forum/account/settings', 'hooks'], function (accountSettings, hooks) {
19
+ hooks.one('action:skin.change', function () {
20
+ $('[component="skinSwitcher"] [component="skinSwitcher/icon"]').removeClass('fa-fade');
21
+ });
22
+ $('[component="skinSwitcher"] [component="skinSwitcher/icon"]').addClass('fa-fade');
23
+ accountSettings.changeSkin(skin);
24
+ });
19
25
  });
20
- });
26
+ }
21
27
 
22
28
  require(['hooks'], function (hooks) {
23
- hooks.on('action:skin.change', function () {
24
- $('[component="skinSwitcher"] [component="skinSwitcher/icon"]').removeClass('fa-fade');
25
- });
26
-
27
29
  $(window).on('action:composer.resize action:sidebar.toggle', function () {
28
30
  $('[component="composer"]').css({
29
31
  left: $('.sidebar-left').outerWidth(true),
@@ -65,6 +67,9 @@ $(document).ready(function () {
65
67
  $body.on('hidden.bs.dropdown', '.sticky-tools', function () {
66
68
  bottomBar.removeClass('hidden');
67
69
  });
70
+ function isSearchVisible() {
71
+ return !!$('[component="bottombar"] [component="sidebar/search"] .search-dropdown.show').length;
72
+ }
68
73
 
69
74
  let lastScrollTop = 0;
70
75
  let newPostsLoaded = false;
@@ -76,7 +81,7 @@ $(document).ready(function () {
76
81
  lastScrollTop = st;
77
82
  return;
78
83
  }
79
- if (st !== lastScrollTop && !navigator.scrollActive) {
84
+ if (st !== lastScrollTop && !navigator.scrollActive && !isSearchVisible()) {
80
85
  const diff = Math.abs(st - lastScrollTop);
81
86
  const scrolledDown = st > lastScrollTop;
82
87
  const scrolledUp = st < lastScrollTop;
package/scss/chats.scss CHANGED
@@ -4,6 +4,19 @@
4
4
  max-width: 100%;
5
5
  }
6
6
 
7
+ .stacked-avatars {
8
+ width: 32px;
9
+ height: 32px;
10
+ span:first-child {
11
+ top: 0;
12
+ left: 8px;
13
+ }
14
+ span:last-child {
15
+ left: 0;
16
+ top: 8px;
17
+ }
18
+ }
19
+
7
20
  body.page-user-chats {
8
21
  overflow: hidden;
9
22
  [data-widget-area="footer"] {
@@ -66,7 +79,16 @@ body.page-user-chats {
66
79
  }
67
80
 
68
81
  /* Mobile handling of chat page */
82
+ @include media-breakpoint-down(lg) {
83
+ .page-user-chats.chat-loaded {
84
+ padding-bottom: 4.75rem;
85
+ }
86
+ }
87
+
69
88
  @include media-breakpoint-down(md) {
89
+ .page-user-chats.chat-loaded {
90
+ padding-bottom: initial;
91
+ }
70
92
  [component="chat/nav-wrapper"] {
71
93
  width: 100%;
72
94
  }
package/scss/modals.scss CHANGED
@@ -1,9 +1,6 @@
1
1
  .tool-modal {
2
- bottom: $spacer * 3;
3
- right: $spacer * 4;
4
-
5
- @include media-breakpoint-down(md) {
6
- right: $spacer;
7
- left: $spacer;
2
+ @include media-breakpoint-up(md) {
3
+ bottom: $spacer * 3;
4
+ right: $spacer * 4;
8
5
  }
9
6
  }
@@ -1,6 +1,6 @@
1
1
  .tag-list {
2
2
  .tag {
3
- background-color: $gray-200;
4
- color: $gray-700;
3
+ background-color: $gray-200!important;
4
+ color: $gray-700!important;
5
5
  }
6
6
  }
@@ -1,5 +1,7 @@
1
1
  .topic .pagination-block {
2
- min-width: 150px;
2
+ .scroller-content {
3
+ min-width: 150px;
4
+ }
3
5
  .scroller-container {
4
6
  left: 10px;
5
7
  height: 300px;
package/scss/topic.scss CHANGED
@@ -40,6 +40,9 @@
40
40
  }
41
41
 
42
42
  [component="post"] {
43
+ &.selected .post-container {
44
+ background-color: mix($body-bg, $body-color, 90%);
45
+ }
43
46
  &.deleted .post-container {
44
47
  opacity: .5;
45
48
  }
@@ -50,6 +53,9 @@
50
53
  pre {
51
54
  max-height: calc($font-size-base * 24);
52
55
  }
56
+ table { // text-break breaks table formatting
57
+ word-break:initial!important;
58
+ }
53
59
  }
54
60
 
55
61
  > [component="post/footer"] {
@@ -1,10 +1,27 @@
1
1
  <!-- IMPORT partials/account/header.tpl -->
2
+
3
+ {{{ if widgets.profile-aboutme-before.length }}}
4
+ <div data-widget-area="profile-aboutme-before">
5
+ {{{each widgets.profile-aboutme-before}}}
6
+ {./html}
7
+ {{{end}}}
8
+ </div>
9
+ {{{ end }}}
10
+
2
11
  {{{ if aboutme }}}
3
12
  <div component="aboutme" class="text-sm text-break">
4
13
  {aboutmeParsed}
5
14
  </div>
6
15
  {{{ end }}}
7
16
 
17
+ {{{ if widgets.profile-aboutme-after.length }}}
18
+ <div data-widget-area="profile-aboutme-after">
19
+ {{{each widgets.profile-aboutme-after}}}
20
+ {./html}
21
+ {{{end}}}
22
+ </div>
23
+ {{{ end }}}
24
+
8
25
  <div class="account-stats container">
9
26
  <div class="row row-cols-2 row-cols-xl-3 row-cols-xxl-4 g-2">
10
27
  {{{ if !reputation:disabled }}}
@@ -17,6 +17,11 @@
17
17
  <label class="form-check-label" for="centerHeaderElements">[[harmony:settings.centerHeaderElements]]</label>
18
18
  </div>
19
19
 
20
+ <div class="form-check mb-3">
21
+ <input class="form-check-input" type="checkbox" id="mobileTopicTeasers" name="mobileTopicTeasers" {{{ if config.theme.mobileTopicTeasers }}}checked{{{ end }}}>
22
+ <label class="form-check-label" for="mobileTopicTeasers">[[harmony:settings.mobileTopicTeasers]]</label>
23
+ </div>
24
+
20
25
  <div class="form-check mb-3">
21
26
  <input class="form-check-input" type="checkbox" id="stickyToolbar" name="stickyToolbar" {{{ if config.theme.stickyToolbar }}}checked{{{ end }}}>
22
27
  <label class="form-check-label" for="stickyToolbar">
@@ -10,6 +10,10 @@
10
10
  <input type="checkbox" class="form-check-input" id="centerHeaderElements" name="centerHeaderElements" />
11
11
  <label for="centerHeaderElements" class="form-check-label">[[harmony:settings.centerHeaderElements]]</label>
12
12
  </div>
13
+ <div class="form-check form-switch">
14
+ <input type="checkbox" class="form-check-input" id="mobileTopicTeasers" name="mobileTopicTeasers" />
15
+ <label for="mobileTopicTeasers" class="form-check-label">[[harmony:settings.mobileTopicTeasers]]</label>
16
+ </div>
13
17
  <div class="form-check form-switch">
14
18
  <input type="checkbox" class="form-check-input" id="stickyToolbar" name="stickyToolbar" />
15
19
  <div for="stickyToolbar" class="form-check-label">
@@ -20,7 +20,7 @@
20
20
  {{{end}}}
21
21
  </div>
22
22
  </div>
23
- <div class="flex-grow-1 ms-md-2 ps-md-2 border-1 border-start-md h-100" component="chat/main-wrapper">
23
+ <div class="flex-grow-1 ms-md-2 ps-md-2 border-1 border-start-md h-100" component="chat/main-wrapper" style="min-width: 0;">
24
24
  <!-- IMPORT partials/chats/message-window.tpl -->
25
25
  </div>
26
26
  <div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>
@@ -2,6 +2,5 @@
2
2
 
3
3
  <div class="d-flex flex-column gap-3">
4
4
  <!-- IMPORT partials/flags/filters.tpl -->
5
- <!-- IMPORT partials/flags/bulk-actions.tpl -->
6
5
  <!-- IMPORT partials/flags/results.tpl -->
7
6
  </div>
@@ -1,6 +1,6 @@
1
1
  <li component="categories/category" data-cid="{./cid}" class="w-100 border-bottom py-3 py-lg-4 gap-1 d-flex flex-column flex-lg-row align-items-start category-{./cid}">
2
2
  <meta itemprop="name" content="{./name}">
3
-
3
+
4
4
  <div class="d-flex col-lg-7 gap-2 gap-lg-3">
5
5
  <div class="flex-shrink-0">
6
6
  {buildCategoryIcon(@value, "40px", "rounded-1")}
@@ -10,7 +10,7 @@
10
10
  <!-- IMPORT partials/categories/link.tpl -->
11
11
  </h2>
12
12
  {{{ if ./descriptionParsed }}}
13
- <div class="description text-muted text-xs w-100">
13
+ <div class="description text-muted text-sm w-100">
14
14
  {./descriptionParsed}
15
15
  </div>
16
16
  {{{ end }}}
@@ -54,7 +54,7 @@
54
54
  </div>
55
55
  </div>
56
56
  {{{ if !config.hideCategoryLastPost }}}
57
- <div component="topic/teaser" class="teaser col-lg-6">
57
+ <div component="topic/teaser" class="teaser col-lg-6 {{{ if !config.theme.mobileTopicTeasers }}}d-none d-lg-block{{{ end }}}">
58
58
  <!-- IMPORT partials/categories/lastpost.tpl -->
59
59
  </div>
60
60
  {{{ end }}}
@@ -9,7 +9,7 @@
9
9
  <span class="timeago text-xs" title="{../timestampISO}"></span>
10
10
  </a>
11
11
  </div>
12
- <div class="post-content text-xs line-clamp-2 lh-sm">
12
+ <div class="post-content text-xs text-break line-clamp-sm-2 lh-sm">
13
13
  {./content}
14
14
  </div>
15
15
  </div>
@@ -24,8 +24,10 @@
24
24
  <div component="chat/composer" class="d-flex flex-column flex-md-row gap-2 border-top pt-2 align-items-start align-items-md-end">
25
25
  <div class="w-100 flex-grow-1 position-relative input-group">
26
26
  <button component="chat/upload/button" class="btn btn-outline-primary btn-sm align-self-stretch px-3 px-md-2" type="button"><i class="fa fa-fw fa-upload"></i></button>
27
- <textarea component="chat/input" placeholder="[[modules:chat.placeholder.mobile]]" class="form-control chat-input mousetrap" style="height:0;resize:none;"></textarea>
28
- <span component="chat/message/remaining" class="text-xs text-muted position-absolute me-5 mb-1 end-0 bottom-0">{maximumChatMessageLength}</span>
27
+ <div class="flex-grow-1 position-relative">
28
+ <textarea component="chat/input" placeholder="[[modules:chat.placeholder.mobile]]" class="form-control chat-input mousetrap rounded-0" style="height:0;max-height:30vh;resize:none;"></textarea>
29
+ <span component="chat/message/remaining" class="text-xs text-muted position-absolute me-1 mb-1 end-0 bottom-0">{maximumChatMessageLength}</span>
30
+ </div>
29
31
  <button class="btn btn-primary btn-sm align-self-stretch px-3 px-md-2" type="button" data-action="send"><i class="fa fa-fw fa-paper-plane"></i></button>
30
32
  </div>
31
33
  <form class="hidden" component="chat/upload" method="post" enctype="multipart/form-data">
@@ -1,11 +1,11 @@
1
- <li component="chat/message" class="chat-message mx-2 pe-2 clear{{{ if ./deleted }}} deleted{{{ end }}} {{{ if messages.newSet }}}border-top pt-3{{{ end }}}" data-index="{messages.index}" data-mid="{messages.messageId}" data-uid="{messages.fromuid}" data-self="{messages.self}" data-break="{messages.newSet}" data-timestamp="{messages.timestamp}">
1
+ <li component="chat/message" class="chat-message mx-2 pe-2 clear{{{ if messages.deleted }}} deleted{{{ end }}} {{{ if messages.newSet }}}border-top pt-3{{{ end }}}" data-index="{messages.index}" data-mid="{messages.messageId}" data-uid="{messages.fromuid}" data-self="{messages.self}" data-break="{messages.newSet}" data-timestamp="{messages.timestamp}">
2
2
  <div class="message-header lh-1 d-flex align-items-center gap-2 text-sm {{{ if !messages.newSet }}}hidden{{{ end }}} pb-2">
3
3
  <a href="{config.relative_path}/user/{messages.fromUser.userslug}" class="text-decoration-none">{buildAvatar(messages.fromUser, "18px", true, "not-responsive")}</a>
4
4
  <span class="chat-user fw-semibold"><a href="{config.relative_path}/user/{messages.fromUser.userslug}">{messages.fromUser.displayname}</a></span>
5
- {{{ if ./fromUser.banned }}}
5
+ {{{ if messages.fromUser.banned }}}
6
6
  <span class="badge bg-danger">[[user:banned]]</span>
7
7
  {{{ end }}}
8
- {{{ if ./fromUser.deleted }}}
8
+ {{{ if messages.fromUser.deleted }}}
9
9
  <span class="badge bg-danger">[[user:deleted]]</span>
10
10
  {{{ end }}}
11
11
  <span class="chat-timestamp text-muted ms-2 timeago" title="{messages.timestampISO}"></span>
@@ -18,7 +18,7 @@
18
18
  {messages.content}
19
19
  </div>
20
20
 
21
- {{{ if (!config.disableChatMessageEditing && ./self ) }}}
21
+ {{{ if (!config.disableChatMessageEditing && messages.self ) }}}
22
22
  <div class="position-relative">
23
23
  <div class="btn-group border shadow-sm controls position-absolute small hover-d-block" style="bottom:5px; right:30px; display:none;">
24
24
  <button class="btn btn-sm btn-link" data-action="edit"><i class="fa fa-pencil"></i></button>
@@ -5,9 +5,9 @@
5
5
  <a class="stretched-link" href="{config.relative_path}/me/chats/{./roomId}"></a>
6
6
  {{{ if ./users.length }}}
7
7
  {{{ if ./groupChat}}}
8
- <div class="position-relative" style="width:32px; height:32px;">
9
- <span class="text-decoration-none position-absolute top-0" style="left: 8px;" href="{config.relative_path}/user/{./users.1.userslug}">{buildAvatar(./users.1, "24px", true)}</span>
10
- <span class="text-decoration-none position-absolute start-0" style="top: 8px;" href="{config.relative_path}/user/{./users.0.userslug}" >{buildAvatar(./users.0, "24px", true)}</span>
8
+ <div class="position-relative stacked-avatars">
9
+ <span class="text-decoration-none position-absolute" href="{config.relative_path}/user/{./users.1.userslug}">{buildAvatar(./users.1, "24px", true)}</span>
10
+ <span class="text-decoration-none position-absolute" href="{config.relative_path}/user/{./users.0.userslug}" >{buildAvatar(./users.0, "24px", true)}</span>
11
11
  </div>
12
12
  {{{ else }}}
13
13
  <span href="{config.relative_path}/user/{./users.0.userslug}" class="text-decoration-none">{buildAvatar(./users.0, "32px", true)}</span>
@@ -1,8 +1,8 @@
1
- <div class="btn-group float-end" component="flags/bulk-actions">
1
+ <div class="dropdown" component="flags/bulk-actions">
2
2
  <button class="filter-btn btn btn-light btn-sm border" data-bs-toggle="dropdown" autocomplete="off" aria-haspopup="true" aria-expanded="false" disabled="disabled">
3
3
  <span class="filter-label">[[flags:bulk-actions]]</span>
4
4
  </button>
5
- <ul class="dropdown-menu p-1 text-sm">
5
+ <ul class="dropdown-menu dropdown-menu-end p-1 text-sm">
6
6
  <li><a href="#" class="dropdown-item rounded-1" data-action="bulk-assign">[[flags:assign-to-me]]</a></li>
7
7
  <li><a href="#" class="dropdown-item rounded-1" data-action="bulk-mark-resolved">[[flags:bulk-resolve]]</a></li>
8
8
  </ul>
@@ -176,6 +176,8 @@
176
176
  </a>
177
177
  </div>
178
178
 
179
+ <!-- IMPORT partials/flags/bulk-actions.tpl -->
180
+
179
181
  <form role="form">
180
182
  <input type="hidden" name="sort" value="{./sort}" />
181
183
  <input type="hidden" name="state" value="{./filters.state}" />
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
 
12
12
  <div class="mb-2 clearfix">
13
- <button class="btn btn-primary btn-sm float-end" component="groups/members/bulk-invite-button">[[groups:bulk-invite]]</button>
13
+ <button type="button" class="btn btn-primary btn-sm float-end" component="groups/members/bulk-invite-button">[[groups:bulk-invite]]</button>
14
14
  </div>
15
15
 
16
16
  <table component="groups/invited" class="table table-hover">
@@ -1,5 +1,5 @@
1
1
  <div class="d-flex {{{ if group.isOwner }}}justify-content-between{{{ else }}}justify-content-end{{{ end }}} mb-3">
2
- {{{ if group.isOwner }}}
2
+ {{{ if isAdmin }}}
3
3
  <div class="flex-shrink-0">
4
4
  <button component="groups/members/add" type="button" class="btn btn-primary btn-sm me-3" title="[[groups:details.add-member]]"><i class="fa fa-user-plus"></i> [[groups:details.add-member]]</button>
5
5
  </div>
@@ -8,16 +8,16 @@
8
8
  {{{ each notifications }}}
9
9
  <li class="{./readClass} mb-2 p-1" data-nid="{./nid}" data-path="{./path}" {{{ if ./pid }}}data-pid="{./pid}"{{{ end }}}{{{ if ./tid }}}data-tid="{./tid}"{{{ end }}}>
10
10
  <div class="d-flex gap-1 justify-content-between">
11
- <div class="d-flex gap-0 flex-grow-1 align-items-start">
11
+ <div class="btn-ghost-sm d-flex gap-2 flex-grow-1 align-items-start">
12
12
  {{{ if ./image }}}
13
13
  {{{ if ./from }}}
14
- <a class="btn-ghost-sm p-1 flex-grow-0 flex-shrink-0" href="{config.relative_path}/user/{./user.userslug}"><img class="avatar avatar-rounded" style="--avatar-size: 32px;" src="{./image}" /></a>
14
+ <a class="flex-grow-0 flex-shrink-0" href="{config.relative_path}/user/{./user.userslug}"><img class="avatar avatar-rounded" style="--avatar-size: 32px;" src="{./image}" /></a>
15
15
  {{{ end }}}
16
16
  {{{ else }}}
17
- <a class="btn-ghost-sm p-1 flex-grow-0 flex-shrink-0" href="{config.relative_path}/user/{./user.userslug}"><div class="avatar avatar-rounded" style="--avatar-size: 32px; background-color: {./user.icon:bgColor};">{./user.icon:text}</div></a>
17
+ <a class="flex-grow-0 flex-shrink-0" href="{config.relative_path}/user/{./user.userslug}"><div class="avatar avatar-rounded" style="--avatar-size: 32px; background-color: {./user.icon:bgColor};">{./user.icon:text}</div></a>
18
18
  {{{ end }}}
19
19
  <div class="d-flex flex-grow-1 flex-column align-items-start position-relative">
20
- <a href="{./path}" class="btn-ghost-sm d-inline-block text-reset text-break text-sm ff-sans stretched-link">
20
+ <a href="{./path}" class="text-decoration-none d-inline-block text-reset text-break text-sm ff-sans stretched-link">
21
21
  {./bodyShort}
22
22
  </a>
23
23
  <div class="text-xs text-muted">{{{ if ./timeagoLong }}}{./timeagoLong}{{{ else }}}<span class="timeago" title="{./datetimeISO}"></span>{{{ end }}}</div>
@@ -10,7 +10,7 @@
10
10
  </a>
11
11
  <ul class="notifications-dropdown dropdown-menu p-1 shadow">
12
12
  <li>
13
- <ul component="notifications/list" class="list-container notification-list list-unstyled overscroll-behavior-contain ff-base">
13
+ <ul component="notifications/list" class="list-container notification-list list-unstyled overscroll-behavior-contain pe-1 ff-base">
14
14
  <li class="mb-2 p-1">
15
15
  <div class="d-flex gap-1 justify-content-between">
16
16
  <div class="d-flex gap-2 flex-grow-1 placeholder-wave">
@@ -5,7 +5,7 @@
5
5
  <ul id="user-control-list" component="header/usercontrol" class="overscroll-behavior-contain user-dropdown dropdown-menu shadow p-1 text-sm ff-base" aria-labelledby="user_dropdown">
6
6
  <li>
7
7
  <a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="header/profilelink" href="{relative_path}/user/{user.userslug}">
8
- <span component="user/status" class="flex-shrink-0 badge border border-white border-2 rounded-circle status {user.status}"><span class="visually-hidden">[[global:{user.status}]]</span></span>
8
+ <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status {user.status}"><span class="visually-hidden">[[global:{user.status}]]</span></span>
9
9
  <span class="fw-semibold" component="header/username">{user.username}</span>
10
10
  </a>
11
11
  </li>
@@ -13,28 +13,28 @@
13
13
  <li><h6 class="dropdown-header text-xs">[[global:status]]</h6></li>
14
14
  <li>
15
15
  <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.online }}}selected{{{ end }}}" data-status="online">
16
- <span component="user/status" class="flex-shrink-0 badge border border-white border-2 rounded-circle status online"><span class="visually-hidden">[[global:online]]</span></span>
16
+ <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status online"><span class="visually-hidden">[[global:online]]</span></span>
17
17
  <span class="flex-grow-1">[[global:online]]</span>
18
18
  <i class="fa-solid fa-check text-muted flex-shrink-0"></i>
19
19
  </a>
20
20
  </li>
21
21
  <li>
22
22
  <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.away }}}selected{{{ end }}}" data-status="away">
23
- <span component="user/status" class="flex-shrink-0 badge border border-white border-2 rounded-circle status away"><span class="visually-hidden">[[global:away]]</span></span>
23
+ <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status away"><span class="visually-hidden">[[global:away]]</span></span>
24
24
  <span class="flex-grow-1">[[global:away]]</span>
25
25
  <i class="fa-solid fa-check text-muted flex-shrink-0"></i>
26
26
  </a>
27
27
  </li>
28
28
  <li>
29
29
  <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.dnd }}}selected{{{ end }}}" data-status="dnd">
30
- <span component="user/status" class="flex-shrink-0 badge border border-white border-2 rounded-circle status dnd"><span class="visually-hidden">[[global:dnd]]</span></span>
30
+ <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status dnd"><span class="visually-hidden">[[global:dnd]]</span></span>
31
31
  <span class="flex-grow-1">[[global:dnd]]</span>
32
32
  <i class="fa-solid fa-check text-muted flex-shrink-0"></i>
33
33
  </a>
34
34
  </li>
35
35
  <li>
36
36
  <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.offline }}}selected{{{ end }}}" data-status="offline">
37
- <span component="user/status" class="flex-shrink-0 badge border border-white border-2 rounded-circle status offline"><span class="visually-hidden">[[global:invisible]]</span></span>
37
+ <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status offline"><span class="visually-hidden">[[global:invisible]]</span></span>
38
38
  <span class="flex-grow-1">[[global:invisible]]</span>
39
39
  <i class="fa-solid fa-check text-muted flex-shrink-0"></i>
40
40
  </a>
@@ -1,23 +1,25 @@
1
1
  <div class="pagination-block d-none d-lg-block flex-grow-1 mb-2">
2
- <div class="ps-1 ps-md-0 d-inline-block sticky-top" style="top:6rem;z-index:1;">
3
- <div class="scroller-content d-flex gap-2 flex-column align-items-start">
4
- <div class="pointer pagetop btn-ghost-sm d-inline-flex" style="padding: 4px;"><i class="fa fa-chevron-up"></i> <span class="timeago text-xs text-muted" title="{./timestampISO}"></span></div>
5
- <div class="scroller-container position-relative">
6
- <div class="scroller-thumb d-flex gap-2 text-nowrap position-relative" style="height: 40px;">
7
- <div class="scroller-thumb-icon bg-primary rounded d-inline-block" style="width:9px; height: 40px;"></div>
8
- <div>
9
- <p class="small thumb-text d-none d-md-inline-block ff-secondary fw-semibold user-select-none mb-0"></p>
10
- <p class="meta thumb-timestamp timeago text-xs text-muted ff-secondary fw-semibold mb-0 user-select-none"></p>
2
+ <div class="d-flex justify-content-end sticky-top mt-4" style="top:6rem;z-index:1;">
3
+ <div class="ps-1 ps-md-0 d-inline-block">
4
+ <div class="scroller-content d-flex gap-2 flex-column align-items-start">
5
+ <div class="pointer pagetop btn-ghost-sm d-inline-flex" style="padding: 4px;"><i class="fa fa-chevron-up"></i> <span class="timeago text-xs text-muted text-nowrap" title="{./timestampISO}"></span></div>
6
+ <div class="scroller-container position-relative">
7
+ <div class="scroller-thumb d-flex gap-2 text-nowrap position-relative" style="height: 40px;">
8
+ <div class="scroller-thumb-icon bg-primary rounded d-inline-block" style="width:9px; height: 40px;"></div>
9
+ <div>
10
+ <p class="small thumb-text d-none d-md-inline-block ff-secondary fw-semibold user-select-none mb-0"></p>
11
+ <p class="meta thumb-timestamp timeago text-xs text-muted ff-secondary fw-semibold mb-0 user-select-none"></p>
12
+ </div>
11
13
  </div>
12
- </div>
13
14
 
14
- <div class="unread d-inline-block position-absolute bottom-0">
15
- <div class="meta small position-absolute top-50 translate-middle-y text-nowrap fw-semibold ms-2">
16
- <a class="text-decoration-none" href="{url}"></a>
15
+ <div class="unread d-inline-block position-absolute bottom-0">
16
+ <div class="meta small position-absolute top-50 translate-middle-y text-nowrap fw-semibold ms-2">
17
+ <a class="text-decoration-none" href="{url}"></a>
18
+ </div>
17
19
  </div>
18
20
  </div>
21
+ <div class="pointer pagebottom btn-ghost-sm d-inline-flex" style="padding: 4px;"><i class="fa fa-chevron-down"></i> <span class="timeago text-xs text-muted text-nowrap" title="{./lastposttimeISO}"></span></div>
19
22
  </div>
20
- <div class="pointer pagebottom btn-ghost-sm d-inline-flex" style="padding: 4px;"><i class="fa fa-chevron-down"></i> <span class="timeago text-xs text-muted" title="{./lastposttimeISO}"></span></div>
21
23
  </div>
22
24
  </div>
23
25
  </div>
@@ -6,12 +6,12 @@
6
6
  </li>
7
7
  <li {{{ if posts.deleted }}}hidden{{{ end }}}>
8
8
  <a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="post/delete" role="menuitem" tabindex="-1" href="#" class="{{{ if posts.deleted }}}hidden{{{ end }}}">
9
- <span class="menu-icon"><i class="fa fa-fw text-muted fa-trash-o"></i><span> [[topic:delete]]
9
+ <span class="menu-icon"><i class="fa fa-fw text-muted fa-trash-o"></i></span> [[topic:delete]]
10
10
  </a>
11
11
  </li>
12
12
  <li {{{ if !posts.deleted }}}hidden{{{ end }}}>
13
13
  <a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="post/restore" role="menuitem" tabindex="-1" href="#" class="{{{ if !posts.deleted }}}hidden{{{ end }}}">
14
- <span class="menu-icon"><i class="fa fa-fw text-muted fa-history"></i><span> [[topic:restore]]
14
+ <span class="menu-icon"><i class="fa fa-fw text-muted fa-history"></i></span> [[topic:restore]]
15
15
  </a>
16
16
  </li>
17
17
  {{{ if posts.display_purge_tools }}}
@@ -10,7 +10,7 @@
10
10
  <div class="icon py-1 bg-body d-none d-sm-block">
11
11
  <a class="d-inline-block position-relative text-decoration-none" href="{{{ if ./user.userslug }}}{config.relative_path}/user/{./user.userslug}{{{ else }}}#{{{ end }}}">
12
12
  {buildAvatar(posts.user, "48px", true, "", "user/picture")}
13
- <span component="user/status" class="position-absolute translate-middle-y badge border border-white border-2 rounded-circle status {posts.user.status}"><span class="visually-hidden">[[global:{posts.user.status}]]</span></span>
13
+ <span component="user/status" class="position-absolute translate-middle-y border border-white border-2 rounded-circle status {posts.user.status}"><span class="visually-hidden">[[global:{posts.user.status}]]</span></span>
14
14
  </a>
15
15
  </div>
16
16
 
@@ -19,7 +19,7 @@
19
19
  <div class="icon bg-body d-sm-none">
20
20
  <a class="d-inline-block position-relative text-decoration-none" href="{{{ if ./user.userslug }}}{config.relative_path}/user/{./user.userslug}{{{ else }}}#{{{ end }}}">
21
21
  {buildAvatar(posts.user, "20px", true, "", "user/picture")}
22
- <span component="user/status" class="position-absolute translate-middle-y badge border border-white border-2 rounded-circle status {posts.user.status}"><span class="visually-hidden">[[global:{posts.user.status}]]</span></span>
22
+ <span component="user/status" class="position-absolute translate-middle-y border border-white border-2 rounded-circle status {posts.user.status}"><span class="visually-hidden">[[global:{posts.user.status}]]</span></span>
23
23
  </a>
24
24
  </div>
25
25
 
@@ -3,7 +3,7 @@
3
3
  <div class="icon hidden-xs">
4
4
  <a class="d-inline-block position-relative" href="{{{ if loggedInUser.userslug }}}{config.relative_path}/user/{loggedInUser.userslug}{{{ else }}}#{{{ end }}}">
5
5
  {buildAvatar(loggedInUser, "48px", true, "", "user/picture")}
6
- {{{ if loggedInUser.status }}}<span component="user/status" class="position-absolute translate-middle-y badge border border-white border-2 rounded-circle status {loggedInUser.status}"><span class="visually-hidden">[[global:{loggedInUser.status}]]</span></span>{{{ end }}}
6
+ {{{ if loggedInUser.status }}}<span component="user/status" class="position-absolute translate-middle-y border border-white border-2 rounded-circle status {loggedInUser.status}"><span class="visually-hidden">[[global:{loggedInUser.status}]]</span></span>{{{ end }}}
7
7
  </a>
8
8
  </div>
9
9
  <form class="flex-grow-1 d-flex flex-column gap-2" method="post" action="{config.relative_path}/compose">
@@ -1,5 +1,5 @@
1
1
  <div component="topic/reply/container" class="btn-group action-bar {{{ if !privileges.topics:reply }}}hidden{{{ end }}}">
2
- <a href="{config.relative_path}/compose?tid={tid}&title={title}" class="d-flex align-items-center btn btn-sm btn-primary px-3 fw-semibold " component="topic/reply" data-ajaxify="false" role="button"><i class="fa fa-reply d-sm-block d-md-none"></i><span class="d-none d-md-block"> [[topic:reply]]</span></a>
2
+ <a href="{config.relative_path}/compose?tid={tid}" class="d-flex align-items-center btn btn-sm btn-primary px-3 fw-semibold " component="topic/reply" data-ajaxify="false" role="button"><i class="fa fa-reply d-sm-block d-md-none"></i><span class="d-none d-md-block"> [[topic:reply]]</span></a>
3
3
  <button type="button" class="btn btn-sm btn-primary dropdown-toggle" data-bs-toggle="dropdown">
4
4
  <span class="caret"></span>
5
5
  </button>
@@ -11,15 +11,15 @@
11
11
  {{{ if loggedIn }}}
12
12
  {{{ if !privileges.topics:reply }}}
13
13
  {{{ if locked }}}
14
- <a component="topic/reply/locked" class="d-flex gap-2 align-items-center fw-semibold btn btn-primary disabled" disabled><i class="fa fa-lock"></i> [[topic:locked]]</a>
14
+ <a component="topic/reply/locked" class="d-flex gap-2 align-items-center fw-semibold btn btn-sm btn-primary disabled" disabled><i class="fa fa-lock"></i> [[topic:locked]]</a>
15
15
  {{{ end }}}
16
16
  {{{ end }}}
17
17
 
18
18
  {{{ if !locked }}}
19
- <a component="topic/reply/locked" class="d-flex gap-2 align-items-center fw-semibold btn btn-primary disabled hidden" disabled><i class="fa fa-lock"></i> [[topic:locked]]</a>
19
+ <a component="topic/reply/locked" class="d-flex gap-2 align-items-center fw-semibold btn btn-sm btn-primary disabled hidden" disabled><i class="fa fa-lock"></i> [[topic:locked]]</a>
20
20
  {{{ end }}}
21
21
  {{{ else }}}
22
22
  {{{ if !privileges.topics:reply }}}
23
- <a component="topic/reply/guest" href="{config.relative_path}/login" class="d-flex align-items-center fw-semibold btn btn-primary">[[topic:guest-login-reply]]</a>
23
+ <a component="topic/reply/guest" href="{config.relative_path}/login" class="d-flex align-items-center fw-semibold btn btn-sm btn-primary">[[topic:guest-login-reply]]</a>
24
24
  {{{ end }}}
25
25
  {{{ end }}}
@@ -46,7 +46,7 @@
46
46
  {{{ end }}}
47
47
  <!-- only show login button if not logged in and doesn't have any posting privilege -->
48
48
  {{{ if (!loggedIn && (!privileges.topics:create && !canPost))}}}
49
- <a component="category/post/guest" href="{config.relative_path}/login" class="btn btn-primary">[[category:guest-login-post]]</a>
49
+ <a component="category/post/guest" href="{config.relative_path}/login" class="btn btn-sm btn-primary">[[category:guest-login-post]]</a>
50
50
  {{{ end }}}
51
51
  </div>
52
52
  </div>
@@ -15,8 +15,8 @@
15
15
  {buildAvatar(./user, "40px", true, "avatar avatar-tooltip")}
16
16
  </a>
17
17
  {{{ if showSelect }}}
18
- <div class="checkbox position-absolute top-100 start-50 translate-middle-x p-1 m-0 d-none d-lg-flex" style="max-width:max-content">
19
- <i component="topic/select" class="fa text-muted pointer fa-square-o"></i>
18
+ <div class="checkbox position-absolute top-100 start-50 translate-middle-x pt-2 m-0 d-none d-lg-flex" style="max-width:max-content">
19
+ <i component="topic/select" class="fa text-muted pointer fa-square-o p-1"></i>
20
20
  </div>
21
21
  {{{ end }}}
22
22
  </div>
@@ -52,11 +52,14 @@
52
52
  {{{ if ./tags.length }}}
53
53
  <span class="lh-1 tag-list hidden-xs d-flex flex-wrap gap-1">
54
54
  {{{ each ./tags }}}
55
- <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 text-muted fw-normal tag tag-class-{./class}" data-tag="{./value}">{./valueEscaped}</span></a>
55
+ <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>
56
56
  {{{ end }}}
57
57
  </span>
58
58
  {{{ end }}}
59
- <span class="hidden-xs badge bg-transparent text-muted fw-normal timeago" title="{./timestampISO}"></span>
59
+ <a href="{config.relative_path}/topic/{./slug}" class="hidden-xs badge bg-transparent text-muted fw-normal timeago" title="{./timestampISO}"></a>
60
+ {{{ if !config.theme.mobileTopicTeasers}}}
61
+ <span class="visible-xs-inline badge bg-transparent text-muted fw-normal timeago" title="{{{ if ./teaser.timestampISO }}}{./teaser.timestampISO}{{{ else }}}{./timestampISO}{{{ end }}}"></span>
62
+ {{{ end }}}
60
63
  </span>
61
64
  </div>
62
65
  {{{ if ./thumbs.length }}}
@@ -87,7 +90,7 @@
87
90
  <i class="d-xl-none fa fa-fw text-xs text-muted opacity-75 fa-eye"></i>
88
91
  </div>
89
92
  </div>
90
- <div component="topic/teaser" class="meta teaser col-lg-6">
93
+ <div component="topic/teaser" class="meta teaser col-lg-6 {{{ if !config.theme.mobileTopicTeasers }}}d-none d-lg-block{{{ end }}}">
91
94
  <div class="lastpost background-link-container border-start border-2 lh-sm h-100" style="border-color: {./category.bgColor}!important;">
92
95
  <a class="background-link" href="{config.relative_path}/topic/{./slug}/{./teaser.index}"></a>
93
96
  {{{ if ./unreplied }}}
@@ -101,7 +104,7 @@
101
104
  <a class="permalink text-muted timeago text-xs" href="{config.relative_path}/topic/{./slug}/{./teaser.index}" title="{./teaser.timestampISO}">
102
105
  </a>
103
106
  </div>
104
- <div class="post-content text-xs ps-2 line-clamp-2 lh-sm text-break">
107
+ <div class="post-content text-xs ps-2 line-clamp-sm-2 lh-sm text-break">
105
108
  {./teaser.content}
106
109
  </div>
107
110
  {{{ end }}}
@@ -110,8 +113,8 @@
110
113
  </div>
111
114
  </div>
112
115
  {{{ if showSelect }}}
113
- <div class="checkbox position-absolute top-0 end-0 p-1 mt-3 m-0 d-flex d-lg-none" style="max-width:max-content">
114
- <i component="topic/select" class="fa fa-square-o text-muted pointer"></i>
116
+ <div class="checkbox position-absolute top-0 end-0 mt-3 m-0 d-flex d-lg-none" style="max-width:max-content">
117
+ <i component="topic/select" class="fa fa-square-o text-muted pointer p-1"></i>
115
118
  </div>
116
119
  {{{ end }}}
117
120
  </li>