@okjavis/nodebb-theme-javis 3.0.6 → 3.0.7

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": "3.0.6",
3
+ "version": "3.0.7",
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/_cards.scss CHANGED
@@ -61,25 +61,36 @@
61
61
  cursor: pointer;
62
62
  border-radius: $jv-radius-xs;
63
63
  transition: color 0.15s ease, background-color 0.15s ease;
64
+ text-decoration: none;
64
65
 
65
66
  &:hover {
66
67
  background: rgba(0, 0, 0, 0.06);
68
+ text-decoration: none;
67
69
  }
68
70
 
69
- &.vote-up:hover {
71
+ // Upvote - orange/red on hover and when active
72
+ &.vote-up:hover,
73
+ &.vote-up.upvoted {
70
74
  color: #ff4500; // Reddit orange-red
75
+ background: rgba(255, 69, 0, 0.1);
71
76
  }
72
77
 
73
- &.vote-down:hover {
74
- color: #7193ff; // Reddit periwinkle
78
+ // Downvote - blue/periwinkle on hover and when active
79
+ &.vote-down:hover,
80
+ &.vote-down.downvoted {
81
+ color: #7193ff; // Reddit periwinkle/blue
82
+ background: rgba(113, 147, 255, 0.1);
75
83
  }
76
84
 
85
+ // Legacy active class support
77
86
  &.active.vote-up {
78
87
  color: #ff4500;
88
+ background: rgba(255, 69, 0, 0.1);
79
89
  }
80
90
 
81
91
  &.active.vote-down {
82
92
  color: #7193ff;
93
+ background: rgba(113, 147, 255, 0.1);
83
94
  }
84
95
 
85
96
  i {
@@ -33,6 +33,12 @@
33
33
  // Initialize click handler for composer prompt card (rendered server-side in feed.tpl)
34
34
  initFeedComposerPromptHandler();
35
35
 
36
+ // Initialize voting handlers for category/topics listing pages
37
+ initTopicListVoting();
38
+
39
+ // Initialize immediate category filter navigation on feed page
40
+ initFeedCategoryFilter();
41
+
36
42
  // Re-initialize carousels when new posts are loaded (infinite scroll, etc.)
37
43
  // Also handle post edits by clearing the processed flag
38
44
  $(window).on('action:posts.loaded action:topic.loaded action:ajaxify.end', function() {
@@ -40,6 +46,8 @@
40
46
  initParentPostNavigation();
41
47
  initPostHoverActions();
42
48
  initFeedComposerPromptHandler();
49
+ initTopicListVoting();
50
+ initFeedCategoryFilter();
43
51
  });
44
52
 
45
53
  // Handle post edits - need to re-process the edited post
@@ -497,4 +505,169 @@
497
505
  });
498
506
  }
499
507
 
508
+ /**
509
+ * Initialize voting handlers for topic listing pages (category, recent, etc.)
510
+ * NodeBB's native voting only works on topic detail pages, so we add custom handlers here
511
+ */
512
+ function initTopicListVoting() {
513
+ // Only run on pages with topic listings (not on topic detail page)
514
+ if ($('[component="topic"]').length) {
515
+ // We're on a topic detail page, NodeBB handles voting natively
516
+ return;
517
+ }
518
+
519
+ // Find vote columns that haven't been initialized
520
+ $('.vote-column:not([data-vote-initialized])').each(function() {
521
+ var $voteColumn = $(this);
522
+ $voteColumn.attr('data-vote-initialized', 'true');
523
+
524
+ var pid = $voteColumn.attr('data-pid');
525
+ if (!pid) return;
526
+
527
+ var $upvoteBtn = $voteColumn.find('[component="post/upvote"]');
528
+ var $downvoteBtn = $voteColumn.find('[component="post/downvote"]');
529
+ var $voteCount = $voteColumn.find('[component="post/vote-count"]');
530
+
531
+ // Upvote click handler
532
+ $upvoteBtn.on('click', function(e) {
533
+ e.preventDefault();
534
+ e.stopPropagation();
535
+
536
+ if (!config.loggedIn) {
537
+ window.location.href = config.relative_path + '/login';
538
+ return;
539
+ }
540
+
541
+ var isUpvoted = $upvoteBtn.hasClass('upvoted');
542
+ var method = isUpvoted ? 'del' : 'put';
543
+
544
+ $.ajax({
545
+ url: config.relative_path + '/api/v3/posts/' + pid + '/vote',
546
+ method: method,
547
+ data: JSON.stringify({ delta: 1 }),
548
+ contentType: 'application/json',
549
+ headers: {
550
+ 'x-csrf-token': config.csrf_token
551
+ },
552
+ success: function(response) {
553
+ // Toggle upvoted state
554
+ $upvoteBtn.toggleClass('upvoted');
555
+ // Remove downvoted if it was set
556
+ $downvoteBtn.removeClass('downvoted');
557
+
558
+ // Update vote count
559
+ if (response && response.response) {
560
+ var votes = response.response.post ? response.response.post.votes : response.response.votes;
561
+ if (votes !== undefined) {
562
+ $voteCount.text(votes);
563
+ $voteCount.attr('data-votes', votes);
564
+ $voteCount.attr('title', votes);
565
+ }
566
+ }
567
+ },
568
+ error: function(xhr) {
569
+ var msg = xhr.responseJSON && xhr.responseJSON.status && xhr.responseJSON.status.message;
570
+ if (msg) {
571
+ alert(msg);
572
+ }
573
+ }
574
+ });
575
+ });
576
+
577
+ // Downvote click handler
578
+ $downvoteBtn.on('click', function(e) {
579
+ e.preventDefault();
580
+ e.stopPropagation();
581
+
582
+ if (!config.loggedIn) {
583
+ window.location.href = config.relative_path + '/login';
584
+ return;
585
+ }
586
+
587
+ var isDownvoted = $downvoteBtn.hasClass('downvoted');
588
+ var method = isDownvoted ? 'del' : 'put';
589
+
590
+ $.ajax({
591
+ url: config.relative_path + '/api/v3/posts/' + pid + '/vote',
592
+ method: method,
593
+ data: JSON.stringify({ delta: -1 }),
594
+ contentType: 'application/json',
595
+ headers: {
596
+ 'x-csrf-token': config.csrf_token
597
+ },
598
+ success: function(response) {
599
+ // Toggle downvoted state
600
+ $downvoteBtn.toggleClass('downvoted');
601
+ // Remove upvoted if it was set
602
+ $upvoteBtn.removeClass('upvoted');
603
+
604
+ // Update vote count
605
+ if (response && response.response) {
606
+ var votes = response.response.post ? response.response.post.votes : response.response.votes;
607
+ if (votes !== undefined) {
608
+ $voteCount.text(votes);
609
+ $voteCount.attr('data-votes', votes);
610
+ $voteCount.attr('title', votes);
611
+ }
612
+ }
613
+ },
614
+ error: function(xhr) {
615
+ var msg = xhr.responseJSON && xhr.responseJSON.status && xhr.responseJSON.status.message;
616
+ if (msg) {
617
+ alert(msg);
618
+ }
619
+ }
620
+ });
621
+ });
622
+ });
623
+ }
624
+
625
+ /**
626
+ * Initialize immediate category filter navigation on feed page
627
+ * When a category is selected, navigate immediately instead of waiting for dropdown close
628
+ */
629
+ function initFeedCategoryFilter() {
630
+ // Only run on feed page
631
+ if (!$('.feed').length) {
632
+ return;
633
+ }
634
+
635
+ var $categoryDropdown = $('.feed-category-filter [component="category/dropdown"]');
636
+ if (!$categoryDropdown.length || $categoryDropdown.attr('data-filter-initialized')) {
637
+ return;
638
+ }
639
+ $categoryDropdown.attr('data-filter-initialized', 'true');
640
+
641
+ // Handle category selection - navigate immediately
642
+ $categoryDropdown.on('click', '[component="category/list"] [data-cid]', function(e) {
643
+ e.preventDefault();
644
+ e.stopPropagation();
645
+
646
+ var $item = $(this);
647
+ var cid = $item.attr('data-cid');
648
+ var currentParams = utils.params();
649
+
650
+ // Build the new URL
651
+ if (cid === 'all') {
652
+ delete currentParams.cid;
653
+ } else {
654
+ currentParams.cid = cid;
655
+ }
656
+
657
+ // Remove page parameter to start from first page
658
+ delete currentParams.page;
659
+
660
+ var url = '/feed';
661
+ if (Object.keys(currentParams).length) {
662
+ url += '?' + $.param(currentParams);
663
+ }
664
+
665
+ // Close the dropdown
666
+ $categoryDropdown.find('.dropdown-toggle').dropdown('hide');
667
+
668
+ // Navigate to the filtered feed
669
+ ajaxify.go(url);
670
+ });
671
+ }
672
+
500
673
  })();
@@ -14,14 +14,14 @@
14
14
 
15
15
  <!-- Vote Column (Reddit-style) -->
16
16
  {{{ if !reputation:disabled }}}
17
- <div class="vote-column">
18
- <button class="vote-btn vote-up" title="[[topic:upvote]]">
17
+ <div class="vote-column" data-pid="{./mainPid}">
18
+ <a component="post/upvote" href="#" class="vote-btn vote-up{{{ if ./upvoted }}} upvoted{{{ end }}}" title="[[topic:upvote]]">
19
19
  <i class="fa fa-chevron-up"></i>
20
- </button>
21
- <span class="vote-count" title="{./votes}">{humanReadableNumber(./votes, 0)}</span>
22
- <button class="vote-btn vote-down" title="[[topic:downvote]]">
20
+ </a>
21
+ <span component="post/vote-count" class="vote-count" data-votes="{./votes}" title="{./votes}">{humanReadableNumber(./votes, 0)}</span>
22
+ <a component="post/downvote" href="#" class="vote-btn vote-down{{{ if ./downvoted }}} downvoted{{{ end }}}" title="[[topic:downvote]]">
23
23
  <i class="fa fa-chevron-down"></i>
24
- </button>
24
+ </a>
25
25
  </div>
26
26
  {{{ end }}}
27
27