@okjavis/nodebb-theme-javis 2.4.0 → 2.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okjavis/nodebb-theme-javis",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "Modern, premium NodeBB theme for JAVIS Community - Extends Harmony with custom styling",
5
5
  "main": "theme.js",
6
6
  "scripts": {
package/scss/_base.scss CHANGED
@@ -28,6 +28,13 @@ nav[component="sidebar/left"] {
28
28
  }
29
29
  }
30
30
 
31
+ // Sidebar toggle link padding
32
+ .sidebar-toggle {
33
+ [component="sidebar/toggle"] {
34
+ padding: $jv-space-4 $jv-space-5 !important; // 16px vertical, 20px horizontal
35
+ }
36
+ }
37
+
31
38
  // Reddit-style layout: Equal spacing and wider feed
32
39
  .feed > .row {
33
40
  max-width: 1400px; // Reddit's approximate max-width
@@ -74,6 +74,14 @@ html.composing .composer .mobile-navbar {
74
74
  display: none !important;
75
75
  }
76
76
 
77
+ // ===========================================================
78
+ // HIDE PREVIEW AND HELP BUTTONS
79
+ // ===========================================================
80
+ html.composing .composer [data-action="preview"],
81
+ html.composing .composer [data-action="help"] {
82
+ display: none !important;
83
+ }
84
+
77
85
  // ===========================================================
78
86
  // HEADER
79
87
  // ===========================================================
package/scss/_feed.scss CHANGED
@@ -24,6 +24,18 @@
24
24
  display: none !important;
25
25
  }
26
26
  }
27
+
28
+ // Hide the original "New Topic" button via CSS to prevent flicker
29
+ // The composer prompt card will replace it via JS
30
+ #new_topic {
31
+ display: none !important;
32
+ }
33
+
34
+ // Hide the controls row (category dropdown, options)
35
+ // The composer prompt card replaces this functionality
36
+ > .row > div > .d-flex.justify-content-between.py-2.mb-2 {
37
+ display: none !important;
38
+ }
27
39
  }
28
40
 
29
41
  // ===========================================================
@@ -33,7 +45,8 @@
33
45
  background: $jv-surface;
34
46
  border: 1px solid $jv-border-subtle;
35
47
  border-radius: $jv-radius-lg;
36
- padding: $jv-space-4;
48
+ padding: $jv-space-4 $jv-space-6; // 16px top/bottom, 24px left/right
49
+ margin-top: $jv-space-4; // Align with trending tags padding-top
37
50
  margin-bottom: $jv-space-4;
38
51
  box-shadow: $jv-shadow-sm;
39
52
  transition: box-shadow $jv-transition-fast, border-color $jv-transition-fast;
@@ -358,7 +371,7 @@
358
371
  font-weight: 700 !important; // Bolder for better hierarchy
359
372
  line-height: 1.4 !important; // Better readability
360
373
  color: $jv-text-main !important;
361
- margin-bottom: $jv-space-3; // 6px - more breathing room
374
+ margin-bottom: 0;
362
375
  letter-spacing: -0.02em; // Tighter for modern feel
363
376
 
364
377
  &:hover {
@@ -375,27 +375,65 @@
375
375
  }
376
376
 
377
377
  /**
378
- * Initialize click handler for composer prompt card on feed page
379
- * The card HTML is rendered server-side in feed.tpl for instant display (no flicker)
380
- * This function only sets up the click handlers
378
+ * Initialize LinkedIn-style composer prompt card on feed page
379
+ * Injects the card HTML and sets up click handlers
380
+ * The original "New Topic" button is hidden via CSS to prevent flicker
381
381
  */
382
382
  function initFeedComposerPromptHandler() {
383
- // Only run on feed page with composer card present
384
- var $card = $('.composer-prompt-card');
385
- if (!$card.length) {
383
+ // Only run on feed page
384
+ if (!$('.feed').length) {
386
385
  return;
387
386
  }
388
387
 
389
- // Skip if already initialized
390
- if ($card.data('handler-initialized')) {
388
+ // Don't re-inject if already present
389
+ if ($('.composer-prompt-card').length) {
391
390
  return;
392
391
  }
393
- $card.data('handler-initialized', true);
394
392
 
395
- var isLoggedIn = typeof app !== 'undefined' && app.user && app.user.uid;
393
+ // Find the existing controls row (contains New Topic button)
394
+ var $controlsRow = $('.feed .d-flex.justify-content-between.py-2.mb-2').first();
395
+ if (!$controlsRow.length) {
396
+ return;
397
+ }
398
+
399
+ // Get user info for avatar - clone from header avatar which is already correctly rendered
400
+ var isLoggedIn = false;
401
+ var avatarHtml = '';
402
+
403
+ if (typeof app !== 'undefined' && app.user && app.user.uid) {
404
+ isLoggedIn = true;
405
+ // Clone the avatar from the header - it's already correctly rendered by NodeBB
406
+ var $headerAvatar = $('[component="header/avatar"] img, [component="header/avatar"] [component="avatar/picture"]').first();
407
+ if ($headerAvatar.length) {
408
+ avatarHtml = '<img src="' + $headerAvatar.attr('src') + '" alt="Avatar" class="avatar avatar-rounded" />';
409
+ } else {
410
+ // Fallback to icon-based avatar from header
411
+ var $headerIcon = $('[component="header/avatar"] [component="avatar/icon"]').first();
412
+ if ($headerIcon.length) {
413
+ avatarHtml = $headerIcon.clone().addClass('avatar avatar-rounded').get(0).outerHTML;
414
+ } else {
415
+ // Last resort: use placeholder
416
+ avatarHtml = '<div class="avatar avatar-rounded avatar-placeholder"><i class="fa fa-user"></i></div>';
417
+ }
418
+ }
419
+ } else {
420
+ avatarHtml = '<div class="avatar avatar-rounded avatar-placeholder"><i class="fa fa-user"></i></div>';
421
+ }
422
+
423
+ var cardHtml = '<div class="composer-prompt-card' + (isLoggedIn ? '' : ' composer-prompt-guest') + '">' +
424
+ '<div class="composer-prompt-inner">' +
425
+ '<div class="composer-prompt-avatar">' + avatarHtml + '</div>' +
426
+ '<button class="composer-prompt-input" type="button" id="composer-prompt-btn">' +
427
+ '<span class="composer-prompt-placeholder">' + (isLoggedIn ? 'Start a discussion...' : 'Sign in to start a discussion...') + '</span>' +
428
+ '</button>' +
429
+ '</div>' +
430
+ '</div>';
431
+
432
+ // Insert the card before the controls row
433
+ $controlsRow.before(cardHtml);
396
434
 
397
435
  // Bind click handler to open composer
398
- $card.on('click', '.composer-prompt-input, .composer-prompt-action', function(e) {
436
+ $('.composer-prompt-card').on('click', '.composer-prompt-input, .composer-prompt-action', function(e) {
399
437
  e.preventDefault();
400
438
 
401
439
  if (!isLoggedIn) {
@@ -1,163 +0,0 @@
1
- <div data-widget-area="header">
2
- {{{each widgets.header}}}
3
- {{widgets.header.html}}
4
- {{{end}}}
5
- </div>
6
- <style>.feed .post-body .content > p:last-child { margin-bottom: 0px; }</style>
7
- <div class="feed">
8
- <div class="row">
9
- <div data-widget-area="left" class="col-lg-3 col-sm-12 {{{ if !widgets.left.length }}}hidden{{{ end }}}">
10
- {{{each widgets.left}}}
11
- {{widgets.left.html}}
12
- {{{end}}}
13
- </div>
14
- {{{ if ((widgets.left.length && widgets.right.length) || (!widgets.left.length && !widgets.right.length))}}}
15
- <div class="col-lg-6 col-sm-12 mx-auto">
16
- {{{ end }}}
17
- {{{ if (widgets.left.length && !widgets.right.length) }}}
18
- <div class="col-lg-6 col-sm-12 me-auto">
19
- {{{ end }}}
20
- {{{ if (!widgets.left.length && widgets.right.length) }}}
21
- <div class="col-lg-6 col-sm-12 ms-auto">
22
- {{{ end }}}
23
-
24
- <!-- JAVIS: LinkedIn-style Composer Prompt Card -->
25
- <div class="composer-prompt-card{{{ if !loggedIn }}} composer-prompt-guest{{{ end }}}">
26
- <div class="composer-prompt-inner">
27
- <div class="composer-prompt-avatar">
28
- {{{ if loggedIn }}}
29
- {buildAvatar(loggedInUser, "48px", true, "avatar avatar-rounded")}
30
- {{{ else }}}
31
- <div class="avatar avatar-rounded avatar-placeholder"><i class="fa fa-user"></i></div>
32
- {{{ end }}}
33
- </div>
34
- <button class="composer-prompt-input" type="button" id="composer-prompt-btn">
35
- <span class="composer-prompt-placeholder">{{{ if loggedIn }}}Start a discussion...{{{ else }}}Sign in to start a discussion...{{{ end }}}</span>
36
- </button>
37
- {{{ if loggedIn }}}
38
- <div class="composer-prompt-actions">
39
- <button class="composer-prompt-action" type="button" title="Add image"><i class="fa fa-image"></i></button>
40
- <button class="composer-prompt-action" type="button" title="Add link"><i class="fa fa-link"></i></button>
41
- </div>
42
- {{{ end }}}
43
- </div>
44
- </div>
45
-
46
- <!-- Hidden controls row - New Topic button kept for JS functionality -->
47
- <div class="d-flex justify-content-between py-2 mb-2 gap-1 feed-controls-row">
48
- {{{ if canPost }}}
49
- <button id="new_topic" class="btn btn-primary btn-sm d-none">[[category:new-topic-button]]</button>
50
- {{{ end }}}
51
- {{{ if (!loggedIn && !canPost) }}}
52
- <a href="{config.relative_path}/login" class="btn btn-primary btn-sm d-none">[[category:guest-login-post]]</a>
53
- {{{ end }}}
54
-
55
- <div class="d-flex justify-content-end gap-1 ms-auto">
56
- <!-- IMPORT partials/category/filter-dropdown-right.tpl -->
57
-
58
- <div id="options-dropdown" class="btn-group dropdown dropdown-right bottom-sheet">
59
- <button type="button" class="btn btn-ghost btn-sm d-flex align-items-center gap-2 ff-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
60
- <i class="fa fa-fw fa-gear text-primary"></i>
61
- </button>
62
- <ul class="dropdown-menu p-1 text-sm" role="menu">
63
- <li class="py-1 px-3">
64
- <div class="form-check form-switch d-flex px-0 align-items-center justify-content-between gap-3">
65
- <label class="form-check-label text-nowrap" for="showAllPosts">[[feed:show-all-posts]]</label>
66
- <input class="form-check-input float-none m-0 pointer" type="checkbox" role="switch" id="showAllPosts" {{{ if showAllPosts }}}checked{{{ end }}}>
67
- </div>
68
- </li>
69
- {{{ if loggedIn }}}
70
- <li class="py-1 px-3">
71
- <div class="form-check form-switch d-flex px-0 align-items-center justify-content-between gap-3">
72
- <label class="form-check-label text-nowrap" for="showFollowedUsers">[[feed:followed-users-only]]</label>
73
- <input class="form-check-input float-none m-0 pointer" type="checkbox" role="switch" id="showFollowedUsers" {{{ if showFollowed }}}checked{{{ end }}}>
74
- </div>
75
- </li>
76
- {{{ end }}}
77
- </ul>
78
- </div>
79
- </div>
80
- </div>
81
-
82
- {{{ if !posts.length }}}
83
- <div class="alert alert-warning text-center">[[feed:no-posts-found]] {{{ if !following.length }}}[[feed:are-you-following-anyone]] {{{ end }}}</div>
84
- {{{ end }}}
85
-
86
- <ul component="posts" class="list-unstyled" data-nextstart="{nextStart}">
87
- {{{ each posts }}}
88
- <li component="post" class="shadow-sm mb-3 rounded-2 border posts-list-item {{{ if ./deleted }}} deleted{{{ else }}}{{{ if ./topic.deleted }}} deleted{{{ end }}}{{{ end }}}{{{ if ./topic.scheduled }}} scheduled{{{ end }}}" data-pid="{./pid}" data-uid="{./uid}">
89
-
90
- {{{ if (showThumbs && ./topic.thumbs.length)}}}
91
- <div class="p-1 position-relative">
92
- <div class="overflow-hidden rounded-1" style="max-height: 300px;">
93
- <a href="{config.relative_path}/topic/{./topic.slug}">
94
- <img class="w-100" src="{./topic.thumbs.0.url}">
95
- </a>
96
- </div>
97
-
98
- <div class="position-absolute end-0 bottom-0 p-3 d-flex gap-2 align-items-center pe-none">
99
- {{{ each ./topic.thumbs }}}
100
- {{{ if (@index != 0) }}}
101
- <img class="rounded-1" style="max-height: 64px; object-fit: contain;" src="{./url}">
102
- {{{ end }}}
103
- {{{ end }}}
104
- </div>
105
- </div>
106
- {{{ end }}}
107
-
108
- <div class="d-flex gap-2 p-3">
109
- <div class="d-none d-lg-block">
110
- <a class="lh-1 text-decoration-none" href="{config.relative_path}/user/{./user.userslug}">{buildAvatar(./user, "40px", true, "not-responsive")}</a>
111
- </div>
112
- <div class="post-body d-flex flex-column gap-2 flex-grow-1 hover-parent" style="min-width: 0px;">
113
- {{{ if ./isMainPost }}}
114
- <a class="lh-1 topic-title fw-semibold fs-5 text-reset text-break d-block" href="{config.relative_path}/topic/{./topic.slug}">
115
- {./topic.title}
116
- </a>
117
- {{{ end }}}
118
-
119
- <div class="d-flex gap-1 post-info text-sm align-items-center">
120
- <div class="post-author d-flex align-items-center gap-1">
121
- <a class="d-inline d-lg-none lh-1 text-decoration-none" href="{config.relative_path}/user/{./user.userslug}">{buildAvatar(./user, "16px", true, "not-responsive")}</a>
122
- <a class="lh-normal fw-semibold text-nowrap" href="{config.relative_path}/user/{./user.userslug}">{./user.displayname}</a>
123
- </div>
124
- {{{ if !./isMainPost}}}{./repliedString}{{{ else }}}<span class="timeago text-muted lh-normal" title="{./timestampISO}"></span>{{{ end}}}
125
- </div>
126
-
127
- <div component="post/content" class="content text-sm text-break position-relative truncate-post-content">
128
- <a href="{config.relative_path}/post/{./pid}" class="stretched-link"></a>
129
- {./content}
130
- </div>
131
- <div class="position-relative hover-visible">
132
- <button component="show/more" class="btn btn-light btn-sm rounded-pill position-absolute start-50 translate-middle-x bottom-0 z-1 hidden ff-secondary">[[feed:see-more]]</button>
133
- </div>
134
- <hr class="my-2"/>
135
- <div class="d-flex justify-content-between">
136
- <a href="{config.relative_path}/post/{{{ if ./topic.teaserPid }}}{./topic.teaserPid}{{{ else }}}{./pid}{{{ end }}}" class="btn btn-link btn-sm text-body {{{ if !./isMainPost }}}invisible{{{ end }}}"><i class="fa-fw fa-regular fa-message text-muted"></i> {humanReadableNumber(./topic.postcount)}</a>
137
-
138
- <a href="#" data-pid="{./pid}" data-action="bookmark" data-bookmarked="{./bookmarked}" data-bookmarks="{./bookmarks}" class="btn btn-link btn-sm text-body"><i class="fa-fw fa-bookmark {{{ if ./bookmarked }}}fa text-primary{{{ else }}}fa-regular text-muted{{{ end }}}"></i> <span component="bookmark-count">{humanReadableNumber(./bookmarks)}</span></a>
139
-
140
- <a href="#" data-pid="{./pid}" data-action="upvote" data-upvoted="{./upvoted}" data-upvotes="{./upvotes}" class="btn btn-link btn-sm text-body"><i class="fa-fw fa-heart {{{ if ./upvoted }}}fa text-danger{{{ else }}}fa-regular text-muted{{{ end }}}"></i> <span component="upvote-count">{humanReadableNumber(./upvotes)}</span></a>
141
-
142
- <a href="#" data-pid="{./pid}" data-is-main="{./isMainPost}" data-tid="{./tid}" data-action="reply" class="btn btn-link btn-sm text-body"><i class="fa-fw fa fa-reply text-muted"></i> [[topic:reply]]</a>
143
- </div>
144
- </div>
145
- </div>
146
- </li>
147
- {{{ end }}}
148
- </ul>
149
- </div>
150
-
151
- <div data-widget-area="right" class="col-lg-3 col-sm-12 {{{ if !widgets.right.length }}}hidden{{{ end }}}">
152
- {{{each widgets.right}}}
153
- {{widgets.right.html}}
154
- {{{end}}}
155
- </div>
156
- </div>
157
- </div>
158
-
159
- <div data-widget-area="footer">
160
- {{{each widgets.footer}}}
161
- {{widgets.footer.html}}
162
- {{{end}}}
163
- </div>