@okjavis/nodebb-theme-javis 5.0.0 → 5.0.2
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
package/plugin.json
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"static/lib/theme.js"
|
|
19
19
|
],
|
|
20
20
|
"modules": {
|
|
21
|
-
"../admin/plugins/javis.js": "public/admin.js"
|
|
21
|
+
"../admin/plugins/javis.js": "public/admin.js",
|
|
22
|
+
"forum/search": "public/src/client/search.js"
|
|
22
23
|
},
|
|
23
24
|
"templates": "templates",
|
|
24
25
|
"screenshot": "screenshot.png",
|
|
@@ -257,12 +257,14 @@ define('forum/search', [
|
|
|
257
257
|
categoryFilter.init(dropdownEl, {
|
|
258
258
|
selectedCids: _selectedCids,
|
|
259
259
|
updateButton: false, // prevent categoryFilter module from updating the button
|
|
260
|
+
states: [], // FIX: Disable watch states to remove "Watched categories" from dropdown
|
|
261
|
+
showLinks: true, // FIX: Show regular category links
|
|
260
262
|
onSelect: function (data) {
|
|
261
|
-
// Update selectedCids immediately when a category is clicked
|
|
263
|
+
// FIX: Update selectedCids immediately when a category is clicked
|
|
262
264
|
ajaxify.data.selectedCids = data.selectedCids;
|
|
263
265
|
selectedCids = data.selectedCids;
|
|
264
266
|
|
|
265
|
-
// Trigger search immediately
|
|
267
|
+
// FIX: Trigger search immediately without requiring additional clicks
|
|
266
268
|
const searchFiltersNew = getSearchDataFromDOM();
|
|
267
269
|
if (JSON.stringify(searchFilters) !== JSON.stringify(searchFiltersNew)) {
|
|
268
270
|
searchFilters = searchFiltersNew;
|
package/static/lib/theme.js
CHANGED
|
@@ -8,10 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
var carouselCounter = 0;
|
|
10
10
|
|
|
11
|
+
// Patch search module to fix category filter
|
|
12
|
+
$(window).on('action:ajaxify.end', function(ev, data) {
|
|
13
|
+
if (data.url && data.url.startsWith('search')) {
|
|
14
|
+
patchSearchCategoryFilter();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
11
18
|
// Theme initialization
|
|
12
19
|
$(document).ready(function() {
|
|
13
20
|
console.log('JAVIS Community Theme initialized');
|
|
14
21
|
|
|
22
|
+
// Patch search if we're already on the search page
|
|
23
|
+
if (window.location.pathname.includes('/search')) {
|
|
24
|
+
setTimeout(patchSearchCategoryFilter, 500);
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
// Initialize sidebar toggle handler
|
|
16
28
|
initSidebarToggle();
|
|
17
29
|
|
|
@@ -658,6 +670,64 @@
|
|
|
658
670
|
});
|
|
659
671
|
}
|
|
660
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Patch search page category filter to:
|
|
675
|
+
* 1. Remove "Watched categories" from dropdown
|
|
676
|
+
* 2. Auto-apply category filter on selection (no additional click needed)
|
|
677
|
+
*/
|
|
678
|
+
function patchSearchCategoryFilter() {
|
|
679
|
+
var $categoryFilter = $('[component="search/filters"] [component="category/filter"]');
|
|
680
|
+
if (!$categoryFilter.length || $categoryFilter.attr('data-javis-patched')) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
$categoryFilter.attr('data-javis-patched', 'true');
|
|
684
|
+
|
|
685
|
+
console.log('JAVIS: Patching search category filter');
|
|
686
|
+
|
|
687
|
+
// Function to remove "Watched categories" from the dropdown
|
|
688
|
+
function removeWatchedCategories() {
|
|
689
|
+
// Method 1: Remove by data-cid="watched"
|
|
690
|
+
var $watchedItem = $categoryFilter.find('[component="category/list"] li[data-cid="watched"]');
|
|
691
|
+
if ($watchedItem.length) {
|
|
692
|
+
$watchedItem.remove();
|
|
693
|
+
console.log('JAVIS: Removed "Watched categories" by data-cid');
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Method 2: Remove by text content (fallback)
|
|
698
|
+
$categoryFilter.find('[component="category/list"] li').each(function() {
|
|
699
|
+
var $li = $(this);
|
|
700
|
+
var text = $li.text().toLowerCase().trim();
|
|
701
|
+
if (text.includes('watched') && text.includes('categor')) {
|
|
702
|
+
$li.remove();
|
|
703
|
+
console.log('JAVIS: Removed "Watched categories" by text match');
|
|
704
|
+
return false; // break the loop
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Remove immediately
|
|
710
|
+
removeWatchedCategories();
|
|
711
|
+
|
|
712
|
+
// Also remove when dropdown opens (in case categories are loaded dynamically)
|
|
713
|
+
$categoryFilter.on('shown.bs.dropdown', function() {
|
|
714
|
+
setTimeout(removeWatchedCategories, 100);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
// Add immediate search trigger on category selection
|
|
718
|
+
$categoryFilter.on('click', '[component="category/list"] [data-cid]', function(e) {
|
|
719
|
+
var $item = $(this);
|
|
720
|
+
var cid = $item.attr('data-cid');
|
|
721
|
+
|
|
722
|
+
// Let the original handler update the selection state first
|
|
723
|
+
setTimeout(function() {
|
|
724
|
+
// Trigger the search by closing the dropdown (which triggers the existing onHidden handler)
|
|
725
|
+
$categoryFilter.find('.dropdown-toggle').dropdown('hide');
|
|
726
|
+
console.log('JAVIS: Auto-triggering search for category:', cid);
|
|
727
|
+
}, 50);
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
661
731
|
/**
|
|
662
732
|
* Initialize immediate category filter navigation on feed page
|
|
663
733
|
* When a category is selected, navigate immediately instead of waiting for dropdown close
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<div data-widget-area="header">
|
|
2
|
+
{{{each widgets.header}}}
|
|
3
|
+
{{widgets.header.html}}
|
|
4
|
+
{{{end}}}
|
|
5
|
+
</div>
|
|
6
|
+
<div class="row login flex-fill align-items-center justify-content-center" style="min-height: calc(100vh - 200px);">
|
|
7
|
+
<div class="d-flex flex-column gap-2 {{{ if widgets.sidebar.length }}}col-lg-9 col-sm-12{{{ else }}}col-lg-12{{{ end }}}">
|
|
8
|
+
<div class="row justify-content-center align-items-center">
|
|
9
|
+
<!-- LinkedIn Primary Login (hidden when ?admin=true) -->
|
|
10
|
+
{{{ if alternate_logins }}}
|
|
11
|
+
<div class="col-12 col-md-6 col-lg-4 px-md-0 linkedin-section">
|
|
12
|
+
<div class="linkedin-login-block d-flex flex-column align-items-center gap-4 py-5">
|
|
13
|
+
<h2 class="tracking-tight fw-semibold text-center mb-2">Welcome to JAVIS Community</h2>
|
|
14
|
+
<p class="text-muted text-center mb-4">Sign in with your LinkedIn account to continue</p>
|
|
15
|
+
|
|
16
|
+
<ul class="alt-logins list-unstyled w-100" style="max-width: 320px;">
|
|
17
|
+
{{{ each authentication }}}
|
|
18
|
+
<li class="{./name} mb-2">
|
|
19
|
+
<a class="btn linkedin-sso-btn d-flex align-items-center justify-content-center gap-3 w-100 py-3" rel="nofollow noopener noreferrer" target="_top" href="{config.relative_path}{./url}">
|
|
20
|
+
<i class="fa-brands fa-linkedin fa-lg"></i>
|
|
21
|
+
<span class="fw-semibold">Sign in with LinkedIn</span>
|
|
22
|
+
</a>
|
|
23
|
+
</li>
|
|
24
|
+
{{{ end }}}
|
|
25
|
+
</ul>
|
|
26
|
+
|
|
27
|
+
<p class="text-muted text-center text-sm mt-4">
|
|
28
|
+
By signing in, you agree to our Terms of Service and Privacy Policy
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
{{{ end }}}
|
|
33
|
+
|
|
34
|
+
<!-- Admin Login Form -->
|
|
35
|
+
{{{ if allowLocalLogin }}}
|
|
36
|
+
<div class="col-12 col-md-6 col-lg-4 px-md-0 admin-login-section">
|
|
37
|
+
<div class="admin-login-block d-flex flex-column gap-3 py-5">
|
|
38
|
+
<h2 class="tracking-tight fw-semibold text-center mb-2">Admin Login</h2>
|
|
39
|
+
<p class="text-muted text-center mb-3">Sign in with your administrator credentials</p>
|
|
40
|
+
<form class="d-flex flex-column gap-3" role="form" method="post" id="login-form">
|
|
41
|
+
<div class="mb-2 d-flex flex-column gap-2">
|
|
42
|
+
<label for="username">{allowLoginWith}</label>
|
|
43
|
+
<input class="form-control" type="text" placeholder="{allowLoginWith}" name="username" id="username" autocorrect="off" autocapitalize="off" autocomplete="nickname" value="{username}" aria-required="true"/>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="mb-2 d-flex flex-column gap-2">
|
|
47
|
+
<label for="password">[[user:password]]</label>
|
|
48
|
+
<div>
|
|
49
|
+
<input class="form-control" type="password" placeholder="[[user:password]]" name="password" id="password" autocomplete="current-password" autocapitalize="off" aria-required="true"/>
|
|
50
|
+
<p id="caps-lock-warning" class="text-danger hidden text-sm mb-0 form-text" aria-live="polite" role="alert" aria-atomic="true">
|
|
51
|
+
<i class="fa fa-exclamation-triangle"></i> [[login:caps-lock-enabled]]
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
{{{ if allowPasswordReset }}}
|
|
55
|
+
<div>
|
|
56
|
+
<a id="reset-link" class="text-sm text-reset text-decoration-underline" href="{config.relative_path}/reset">[[login:forgot-password]]</a>
|
|
57
|
+
</div>
|
|
58
|
+
{{{ end }}}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{{{ each loginFormEntry }}}
|
|
62
|
+
<div class="mb-2 loginFormEntry d-flex flex-column gap-2 {./styleName}">
|
|
63
|
+
<label for="{./inputId}">{./label}</label>
|
|
64
|
+
<div>{{./html}}</div>
|
|
65
|
+
</div>
|
|
66
|
+
{{{ end }}}
|
|
67
|
+
|
|
68
|
+
<input type="hidden" name="_csrf" value="{config.csrf_token}" />
|
|
69
|
+
<input type="hidden" name="noscript" id="noscript" value="true" />
|
|
70
|
+
|
|
71
|
+
<button class="btn btn-primary" id="login" type="submit">[[global:login]]</button>
|
|
72
|
+
|
|
73
|
+
<div class="form-check mb-2">
|
|
74
|
+
<input class="form-check-input" type="checkbox" name="remember" id="remember" checked />
|
|
75
|
+
<label class="form-check-label" for="remember">[[login:remember-me]]</label>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="alert alert-danger {{{ if !error }}} hidden{{{ end }}}" id="login-error-notify" role="alert" aria-atomic="true">
|
|
79
|
+
<strong>[[login:failed-login-attempt]]</strong>
|
|
80
|
+
<p class="mb-0">{error}</p>
|
|
81
|
+
</div>
|
|
82
|
+
</form>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
{{{ end }}}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div data-widget-area="sidebar" class="col-lg-3 col-sm-12 {{{ if !widgets.sidebar.length }}}hidden{{{ end }}}">
|
|
89
|
+
{{{each widgets.sidebar}}}
|
|
90
|
+
{{widgets.sidebar.html}}
|
|
91
|
+
{{{end}}}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div data-widget-area="footer">
|
|
95
|
+
{{{each widgets.footer}}}
|
|
96
|
+
{{widgets.footer.html}}
|
|
97
|
+
{{{end}}}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<script>
|
|
101
|
+
// Control visibility based on ?admin=true parameter
|
|
102
|
+
(function() {
|
|
103
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
104
|
+
const isAdminMode = urlParams.get('admin') === 'true';
|
|
105
|
+
const adminSection = document.querySelector('.admin-login-section');
|
|
106
|
+
const linkedinSection = document.querySelector('.linkedin-section');
|
|
107
|
+
|
|
108
|
+
if (isAdminMode) {
|
|
109
|
+
// Admin mode: show admin form, hide LinkedIn
|
|
110
|
+
if (adminSection) {
|
|
111
|
+
adminSection.style.display = 'block';
|
|
112
|
+
}
|
|
113
|
+
if (linkedinSection) {
|
|
114
|
+
linkedinSection.style.display = 'none';
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// Normal mode: show LinkedIn (if exists), hide admin form
|
|
118
|
+
if (linkedinSection) {
|
|
119
|
+
linkedinSection.style.display = 'block';
|
|
120
|
+
if (adminSection) {
|
|
121
|
+
adminSection.style.display = 'none';
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// No LinkedIn configured - show admin form as fallback
|
|
125
|
+
if (adminSection) {
|
|
126
|
+
adminSection.style.display = 'block';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
</script>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<a component="header/avatar" id="user_dropdown" href="#" role="button" class="nav-link d-flex gap-2 align-items-center text-truncate" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="[[user:user-menu]]">
|
|
2
|
+
{buildAvatar(user, "20px", true)}
|
|
3
|
+
<span id="user-header-name" class="nav-text small visible-open fw-semibold">{user.username}</span>
|
|
4
|
+
</a>
|
|
5
|
+
<ul id="user-control-list" component="header/usercontrol" class="overscroll-behavior-contain user-dropdown dropdown-menu shadow p-1 text-sm ff-base" role="menu">
|
|
6
|
+
<li>
|
|
7
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="header/profilelink" href="{relative_path}/user/{user.userslug}" role="menuitem" aria-label="[[user:profile]]">
|
|
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
|
+
<span class="fw-semibold" component="header/username">{user.username}</span>
|
|
10
|
+
</a>
|
|
11
|
+
</li>
|
|
12
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
13
|
+
<li><h6 class="dropdown-header text-xs">[[global:status]]</h6></li>
|
|
14
|
+
<li>
|
|
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" role="menuitem">
|
|
16
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status online"></span>
|
|
17
|
+
<span class="flex-grow-1">[[global:online]]</span>
|
|
18
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0" aria-label="[[global:selected]]"></i>
|
|
19
|
+
</a>
|
|
20
|
+
</li>
|
|
21
|
+
<li>
|
|
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" role="menuitem">
|
|
23
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status away"></span>
|
|
24
|
+
<span class="flex-grow-1">[[global:away]]</span>
|
|
25
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0"><span class="visually-hidden">[[global:selected]]</span></i>
|
|
26
|
+
</a>
|
|
27
|
+
</li>
|
|
28
|
+
<li>
|
|
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" role="menuitem">
|
|
30
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status dnd"></span>
|
|
31
|
+
<span class="flex-grow-1">[[global:dnd]]</span>
|
|
32
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0"></i>
|
|
33
|
+
</a>
|
|
34
|
+
</li>
|
|
35
|
+
<li>
|
|
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" role="menuitem">
|
|
37
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status offline"></span>
|
|
38
|
+
<span class="flex-grow-1">[[global:invisible]]</span>
|
|
39
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0"></i>
|
|
40
|
+
</a>
|
|
41
|
+
</li>
|
|
42
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
43
|
+
<li>
|
|
44
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/user/{user.userslug}/bookmarks" role="menuitem">
|
|
45
|
+
<i class="fa fa-fw fa-bookmark text-secondary"></i> <span>[[user:bookmarks]]</span>
|
|
46
|
+
</a>
|
|
47
|
+
</li>
|
|
48
|
+
<li>
|
|
49
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="header/profilelink/edit" href="{relative_path}/user/{user.userslug}/edit" role="menuitem">
|
|
50
|
+
<i class="fa fa-fw fa-edit text-secondary"></i> <span>[[user:edit-profile]]</span>
|
|
51
|
+
</a>
|
|
52
|
+
</li>
|
|
53
|
+
{{{ if showModMenu }}}
|
|
54
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
55
|
+
<li><h6 class="dropdown-header text-xs">[[pages:moderator-tools]]</h6></li>
|
|
56
|
+
<li>
|
|
57
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/flags" role="menuitem">
|
|
58
|
+
<i class="fa fa-fw fa-flag text-secondary"></i> <span>[[pages:flagged-content]]</span>
|
|
59
|
+
</a>
|
|
60
|
+
</li>
|
|
61
|
+
<li>
|
|
62
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/post-queue" role="menuitem">
|
|
63
|
+
<i class="fa fa-fw fa-list-alt text-secondary"></i> <span>[[pages:post-queue]]</span>
|
|
64
|
+
</a>
|
|
65
|
+
</li>
|
|
66
|
+
{{{ if registrationQueueEnabled }}}
|
|
67
|
+
<li>
|
|
68
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/registration-queue" role="menuitem">
|
|
69
|
+
<i class="fa fa-fw fa-list-alt text-secondary"></i> <span>[[pages:registration-queue]]</span>
|
|
70
|
+
</a>
|
|
71
|
+
</li>
|
|
72
|
+
{{{ end }}}
|
|
73
|
+
<li>
|
|
74
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/ip-blacklist" role="menuitem">
|
|
75
|
+
<i class="fa fa-fw fa-ban text-secondary"></i> <span>[[pages:ip-blacklist]]</span>
|
|
76
|
+
</a>
|
|
77
|
+
</li>
|
|
78
|
+
{{{ else }}}
|
|
79
|
+
{{{ if postQueueEnabled }}}
|
|
80
|
+
<li>
|
|
81
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/post-queue" role="menuitem">
|
|
82
|
+
<i class="fa fa-fw fa-list-alt text-secondary"></i> <span>[[pages:post-queue]]</span>
|
|
83
|
+
</a>
|
|
84
|
+
</li>
|
|
85
|
+
{{{ end }}}
|
|
86
|
+
{{{ end }}}
|
|
87
|
+
|
|
88
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
89
|
+
<li component="user/logout">
|
|
90
|
+
<form method="post" action="{relative_path}/logout" role="menuitem">
|
|
91
|
+
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
92
|
+
<input type="hidden" name="noscript" value="true">
|
|
93
|
+
<button type="submit" class="dropdown-item rounded-1 d-flex align-items-center gap-2">
|
|
94
|
+
<i class="fa fa-fw fa-sign-out text-secondary"></i><span>[[global:logout]]</span>
|
|
95
|
+
</button>
|
|
96
|
+
</form>
|
|
97
|
+
</li>
|
|
98
|
+
</ul>
|