@okjavis/nodebb-theme-javis 1.0.1 → 1.3.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.
- package/package.json +6 -22
- package/plugin.json +4 -3
- package/scss/_base.scss +72 -0
- package/scss/_buttons.scss +410 -0
- package/scss/_cards.scss +434 -0
- package/{less/_categories.less → scss/_categories.scss} +10 -10
- package/scss/_feed.scss +610 -0
- package/scss/_forms.scss +502 -0
- package/scss/_sidebar-user.scss +150 -0
- package/scss/_sidebar.scss +815 -0
- package/scss/_variables.scss +54 -0
- package/scss/overrides.scss +39 -0
- package/static/images/logo-full.png +0 -0
- package/static/images/logo-icon.png +0 -0
- package/static/lib/theme.js +39 -2
- package/templates/partials/header/brand.tpl +11 -0
- package/templates/partials/posts_list_item.tpl +3 -7
- package/templates/partials/sidebar/user-menu-dropdown.tpl +99 -0
- package/templates/partials/sidebar-left.tpl +87 -0
- package/templates/partials/sidebar-right.tpl +7 -0
- package/templates/partials/topics_list.tpl +111 -0
- package/theme.js +49 -7
- package/theme.json +8 -0
- package/theme.scss +21 -0
- package/less/_base.less +0 -70
- package/less/_buttons.less +0 -90
- package/less/_cards.less +0 -118
- package/less/_forms.less +0 -104
- package/less/_sidebar.less +0 -110
- package/less/_variables.less +0 -54
- package/less/theme.less +0 -18
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// JAVIS Design System – Tokens
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
5
|
+
// Brand Colors
|
|
6
|
+
$jv-primary: #0051ff;
|
|
7
|
+
$jv-primary-hover: #0044dd;
|
|
8
|
+
$jv-primary-soft: rgba(0, 81, 255, 0.12);
|
|
9
|
+
|
|
10
|
+
// Neutrals
|
|
11
|
+
$jv-bg: #f5f6f8; // app background
|
|
12
|
+
$jv-surface: #ffffff; // cards, panels
|
|
13
|
+
$jv-border-subtle: rgba(0,0,0,0.06);
|
|
14
|
+
$jv-border-strong: rgba(0,0,0,0.12);
|
|
15
|
+
|
|
16
|
+
// Text
|
|
17
|
+
$jv-text-main: #111111;
|
|
18
|
+
$jv-text-muted: #6b7280;
|
|
19
|
+
$jv-text-soft: #9ca3af;
|
|
20
|
+
|
|
21
|
+
// Radii
|
|
22
|
+
$jv-radius-xs: 4px;
|
|
23
|
+
$jv-radius-sm: 8px;
|
|
24
|
+
$jv-radius-md: 12px;
|
|
25
|
+
$jv-radius-lg: 16px;
|
|
26
|
+
$jv-radius-pill: 999px;
|
|
27
|
+
|
|
28
|
+
// Shadows
|
|
29
|
+
$jv-shadow-soft: 0 2px 8px rgba(0,0,0,0.04);
|
|
30
|
+
$jv-shadow-card: 0 4px 10px rgba(0,0,0,0.05);
|
|
31
|
+
$jv-shadow-button: 0 4px 12px rgba(0, 81, 255, 0.25);
|
|
32
|
+
$jv-shadow-button-hover: 0 6px 16px rgba(0, 81, 255, 0.35);
|
|
33
|
+
|
|
34
|
+
// Spacing (8pt-ish scale)
|
|
35
|
+
$jv-space-2: 4px;
|
|
36
|
+
$jv-space-4: 8px;
|
|
37
|
+
$jv-space-6: 12px;
|
|
38
|
+
$jv-space-8: 16px;
|
|
39
|
+
$jv-space-10: 20px;
|
|
40
|
+
$jv-space-12: 24px;
|
|
41
|
+
|
|
42
|
+
// Typography
|
|
43
|
+
$jv-font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Helvetica Neue", Arial, sans-serif;
|
|
44
|
+
$jv-font-size-base: 15px;
|
|
45
|
+
$jv-font-size-sm: 13px;
|
|
46
|
+
$jv-font-size-xs: 12px;
|
|
47
|
+
$jv-font-size-lg: 18px;
|
|
48
|
+
$jv-font-size-xl: 22px;
|
|
49
|
+
$jv-font-size-xxl: 32px;
|
|
50
|
+
|
|
51
|
+
// Line Heights
|
|
52
|
+
$jv-line-height-base: 1.52;
|
|
53
|
+
$jv-line-height-tight: 1.4;
|
|
54
|
+
$jv-line-height-relaxed: 1.6;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// JAVIS Theme - Bootstrap Variable Overrides
|
|
3
|
+
// This file is imported BEFORE Bootstrap variables
|
|
4
|
+
// Use it to customize Bootstrap's defaults
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
// Import Harmony's overrides first (inherits their Bootstrap customizations)
|
|
8
|
+
@import "nodebb-theme-harmony/scss/overrides";
|
|
9
|
+
|
|
10
|
+
// JAVIS Brand Colors
|
|
11
|
+
$primary: #0051ff;
|
|
12
|
+
$link-color: #0051ff;
|
|
13
|
+
|
|
14
|
+
// Typography
|
|
15
|
+
$font-family-sans-serif: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Helvetica Neue", Arial, sans-serif;
|
|
16
|
+
|
|
17
|
+
// Body
|
|
18
|
+
$body-bg: #f5f6f8;
|
|
19
|
+
$body-color: #111111;
|
|
20
|
+
|
|
21
|
+
// Borders
|
|
22
|
+
$border-radius: 8px;
|
|
23
|
+
$border-radius-sm: 4px;
|
|
24
|
+
$border-radius-lg: 12px;
|
|
25
|
+
$border-radius-pill: 999px;
|
|
26
|
+
|
|
27
|
+
// Buttons
|
|
28
|
+
$btn-border-radius: 999px;
|
|
29
|
+
$btn-border-radius-sm: 999px;
|
|
30
|
+
$btn-border-radius-lg: 999px;
|
|
31
|
+
|
|
32
|
+
// Cards
|
|
33
|
+
$card-border-radius: 16px;
|
|
34
|
+
$card-border-color: rgba(0, 0, 0, 0.05);
|
|
35
|
+
|
|
36
|
+
// Inputs
|
|
37
|
+
$input-border-radius: 999px;
|
|
38
|
+
$input-focus-border-color: #0051ff;
|
|
39
|
+
$input-focus-box-shadow: 0 0 0 3px rgba(0, 81, 255, 0.12);
|
|
Binary file
|
|
Binary file
|
package/static/lib/theme.js
CHANGED
|
@@ -10,8 +10,45 @@
|
|
|
10
10
|
$(document).ready(function() {
|
|
11
11
|
console.log('JAVIS Community Theme initialized');
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
|
|
13
|
+
// Ensure sidebar toggle works (reinitialize if Harmony's handler isn't loaded)
|
|
14
|
+
initSidebarToggle();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
function initSidebarToggle() {
|
|
18
|
+
// Check if the toggle element exists
|
|
19
|
+
var toggleEl = $('[component="sidebar/toggle"]');
|
|
20
|
+
if (!toggleEl.length) {
|
|
21
|
+
console.log('JAVIS: Sidebar toggle element not found');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Remove any existing handlers to avoid duplicates, then add our handler
|
|
26
|
+
toggleEl.off('click.javis').on('click.javis', function(e) {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
e.stopPropagation();
|
|
29
|
+
|
|
30
|
+
var sidebarEl = $('.sidebar');
|
|
31
|
+
sidebarEl.toggleClass('open');
|
|
32
|
+
|
|
33
|
+
console.log('JAVIS: Sidebar toggled, open:', sidebarEl.hasClass('open'));
|
|
34
|
+
|
|
35
|
+
// Save preference if user is logged in
|
|
36
|
+
if (typeof app !== 'undefined' && app.user && app.user.uid) {
|
|
37
|
+
require(['api'], function(api) {
|
|
38
|
+
api.put('/users/' + app.user.uid + '/settings', {
|
|
39
|
+
settings: {
|
|
40
|
+
openSidebars: sidebarEl.hasClass('open') ? 'on' : 'off',
|
|
41
|
+
},
|
|
42
|
+
}).catch(function(err) {
|
|
43
|
+
console.warn('JAVIS: Could not save sidebar preference', err);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
$(window).trigger('action:sidebar.toggle');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log('JAVIS: Sidebar toggle initialized');
|
|
52
|
+
}
|
|
53
|
+
|
|
17
54
|
})();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{{{ if widgets.brand-header.length }}}
|
|
2
|
+
<div class="container-lg px-md-4 brand-container">
|
|
3
|
+
<div class="col-12 d-flex border-bottom pb-3 {{{ if config.theme.centerHeaderElements }}}justify-content-center{{{ end }}}">
|
|
4
|
+
<div data-widget-area="brand-header" class="flex-fill gap-3 p-2 align-self-center">
|
|
5
|
+
{{{each widgets.brand-header}}}
|
|
6
|
+
{{./html}}
|
|
7
|
+
{{{end}}}
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
{{{ end }}}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
<!--
|
|
2
2
|
JAVIS Community Theme - Post List Item
|
|
3
|
-
|
|
4
|
-
This template overrides Harmony's posts_list_item.tpl
|
|
3
|
+
Overrides Harmony's posts_list_item.tpl
|
|
5
4
|
Used on: Recent, Popular, Unread pages (feed view)
|
|
6
|
-
|
|
7
|
-
To customize: Edit the HTML structure below
|
|
8
|
-
Original: https://github.com/NodeBB/nodebb-theme-harmony/blob/master/templates/partials/posts_list_item.tpl
|
|
9
|
-
}}}
|
|
5
|
+
-->
|
|
10
6
|
<li component="post" class="posts-list-item {{{ if ./deleted }}} deleted{{{ else }}}{{{ if ./topic.deleted }}} deleted{{{ end }}}{{{ end }}}{{{ if ./topic.scheduled }}} scheduled{{{ end }}}" data-pid="{./pid}" data-uid="{./uid}">
|
|
11
7
|
|
|
12
8
|
<a class="topic-title fw-semibold fs-5 mb-2 text-reset text-break d-block" href="{config.relative_path}/post/{encodeURIComponent(./pid)}">
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<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">
|
|
2
|
+
<li>
|
|
3
|
+
<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]]">
|
|
4
|
+
<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>
|
|
5
|
+
<span class="fw-semibold" component="header/username">{user.username}</span>
|
|
6
|
+
</a>
|
|
7
|
+
</li>
|
|
8
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
9
|
+
<li><h6 class="dropdown-header text-xs">[[global:status]]</h6></li>
|
|
10
|
+
<li>
|
|
11
|
+
<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">
|
|
12
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status online"></span>
|
|
13
|
+
<span class="flex-grow-1">[[global:online]]</span>
|
|
14
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0" aria-label="[[global:selected]]"></i>
|
|
15
|
+
</a>
|
|
16
|
+
</li>
|
|
17
|
+
<li>
|
|
18
|
+
<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">
|
|
19
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status away"></span>
|
|
20
|
+
<span class="flex-grow-1">[[global:away]]</span>
|
|
21
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0"><span class="visually-hidden">[[global:selected]]</span></i>
|
|
22
|
+
</a>
|
|
23
|
+
</li>
|
|
24
|
+
<li>
|
|
25
|
+
<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">
|
|
26
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status dnd"></span>
|
|
27
|
+
<span class="flex-grow-1">[[global:dnd]]</span>
|
|
28
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0"></i>
|
|
29
|
+
</a>
|
|
30
|
+
</li>
|
|
31
|
+
<li>
|
|
32
|
+
<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">
|
|
33
|
+
<span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status offline"></span>
|
|
34
|
+
<span class="flex-grow-1">[[global:invisible]]</span>
|
|
35
|
+
<i class="fa-solid fa-check text-secondary flex-shrink-0"></i>
|
|
36
|
+
</a>
|
|
37
|
+
</li>
|
|
38
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
39
|
+
<li>
|
|
40
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/user/{user.userslug}/bookmarks" role="menuitem">
|
|
41
|
+
<i class="fa fa-fw fa-bookmark text-secondary"></i> <span>[[user:bookmarks]]</span>
|
|
42
|
+
</a>
|
|
43
|
+
</li>
|
|
44
|
+
<li>
|
|
45
|
+
<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">
|
|
46
|
+
<i class="fa fa-fw fa-edit text-secondary"></i> <span>[[user:edit-profile]]</span>
|
|
47
|
+
</a>
|
|
48
|
+
</li>
|
|
49
|
+
<li>
|
|
50
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="header/profilelink/settings" href="{relative_path}/user/{user.userslug}/settings" role="menuitem">
|
|
51
|
+
<i class="fa fa-fw fa-gear text-secondary"></i> <span>[[user:settings]]</span>
|
|
52
|
+
</a>
|
|
53
|
+
</li>
|
|
54
|
+
{{{ if showModMenu }}}
|
|
55
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
56
|
+
<li><h6 class="dropdown-header text-xs">[[pages:moderator-tools]]</h6></li>
|
|
57
|
+
<li>
|
|
58
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/flags" role="menuitem">
|
|
59
|
+
<i class="fa fa-fw fa-flag text-secondary"></i> <span>[[pages:flagged-content]]</span>
|
|
60
|
+
</a>
|
|
61
|
+
</li>
|
|
62
|
+
<li>
|
|
63
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/post-queue" role="menuitem">
|
|
64
|
+
<i class="fa fa-fw fa-list-alt text-secondary"></i> <span>[[pages:post-queue]]</span>
|
|
65
|
+
</a>
|
|
66
|
+
</li>
|
|
67
|
+
{{{ if registrationQueueEnabled }}}
|
|
68
|
+
<li>
|
|
69
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/registration-queue" role="menuitem">
|
|
70
|
+
<i class="fa fa-fw fa-list-alt text-secondary"></i> <span>[[pages:registration-queue]]</span>
|
|
71
|
+
</a>
|
|
72
|
+
</li>
|
|
73
|
+
{{{ end }}}
|
|
74
|
+
<li>
|
|
75
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/ip-blacklist" role="menuitem">
|
|
76
|
+
<i class="fa fa-fw fa-ban text-secondary"></i> <span>[[pages:ip-blacklist]]</span>
|
|
77
|
+
</a>
|
|
78
|
+
</li>
|
|
79
|
+
{{{ else }}}
|
|
80
|
+
{{{ if postQueueEnabled }}}
|
|
81
|
+
<li>
|
|
82
|
+
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="{relative_path}/post-queue" role="menuitem">
|
|
83
|
+
<i class="fa fa-fw fa-list-alt text-secondary"></i> <span>[[pages:post-queue]]</span>
|
|
84
|
+
</a>
|
|
85
|
+
</li>
|
|
86
|
+
{{{ end }}}
|
|
87
|
+
{{{ end }}}
|
|
88
|
+
|
|
89
|
+
<li role="presentation" class="dropdown-divider"></li>
|
|
90
|
+
<li component="user/logout">
|
|
91
|
+
<form method="post" action="{relative_path}/logout" role="menuitem">
|
|
92
|
+
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
93
|
+
<input type="hidden" name="noscript" value="true">
|
|
94
|
+
<button type="submit" class="dropdown-item rounded-1 d-flex align-items-center gap-2">
|
|
95
|
+
<i class="fa fa-fw fa-sign-out text-secondary"></i><span>[[global:logout]]</span>
|
|
96
|
+
</button>
|
|
97
|
+
</form>
|
|
98
|
+
</li>
|
|
99
|
+
</ul>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<nav component="sidebar/left" class="{{{ if config.theme.openSidebars}}}open{{{ end }}} text-dark bg-light sidebar sidebar-left start-0 border-end vh-100 d-none d-lg-flex flex-column justify-content-between sticky-top">
|
|
2
|
+
<!-- JAVIS Logo Section -->
|
|
3
|
+
<div class="javis-sidebar-logo">
|
|
4
|
+
<a href="{relative_path}/" class="javis-logo-link" title="JAVIS Community">
|
|
5
|
+
<!-- Icon logo (shown when collapsed) -->
|
|
6
|
+
<img src="{relative_path}/plugins/@okjavis/nodebb-theme-javis/static/images/logo-icon.png" alt="JAVIS" class="javis-logo-icon visible-closed" />
|
|
7
|
+
<!-- Full logo (shown when expanded) -->
|
|
8
|
+
<img src="{relative_path}/plugins/@okjavis/nodebb-theme-javis/static/images/logo-full.png" alt="JAVIS Community" class="javis-logo-full visible-open" />
|
|
9
|
+
</a>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<ul id="main-nav" class="list-unstyled d-flex flex-column w-100 overflow-y-auto">
|
|
13
|
+
{{{ each navigation }}}
|
|
14
|
+
{{{ if displayMenuItem(@root, @index) }}}
|
|
15
|
+
<li class="nav-item mx-2 {./class}{{{ if ./dropdown }}} dropend{{{ end }}}" title="{./title}">
|
|
16
|
+
<a class="nav-link navigation-link d-flex gap-2 justify-content-between align-items-center {{{ if ./dropdown }}}dropdown-toggle{{{ end }}}" {{{ if ./dropdown }}} href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" {{{ else }}} href="{./route}"{{{ end }}} {{{ if ./id }}}id="{./id}"{{{ end }}}{{{ if ./targetBlank }}} target="_blank"{{{ end }}} {{{ if ./text }}}aria-label="{./text}"{{{ end }}}>
|
|
17
|
+
<span class="d-flex gap-2 align-items-center text-nowrap truncate-open">
|
|
18
|
+
<span class="position-relative">
|
|
19
|
+
{{{ if ./iconClass }}}
|
|
20
|
+
<i class="fa fa-fw {./iconClass}" data-content="{./content}"></i>
|
|
21
|
+
<span component="navigation/count" class="visible-closed position-absolute top-0 start-100 translate-middle badge rounded-1 bg-primary {{{ if !./content }}}hidden{{{ end }}}">{./content}</span>
|
|
22
|
+
{{{ end }}}
|
|
23
|
+
</span>
|
|
24
|
+
{{{ if ./text }}}<span class="nav-text small visible-open fw-semibold text-truncate">{./text}</span>{{{ end }}}
|
|
25
|
+
</span>
|
|
26
|
+
<span component="navigation/count" class="visible-open badge rounded-1 bg-primary {{{ if !./content }}}hidden{{{ end }}}">{./content}</span>
|
|
27
|
+
</a>
|
|
28
|
+
{{{ if ./dropdown }}}
|
|
29
|
+
<ul class="dropdown-menu p-1 shadow" role="menu">
|
|
30
|
+
{./dropdownContent}
|
|
31
|
+
</ul>
|
|
32
|
+
{{{ end }}}
|
|
33
|
+
</li>
|
|
34
|
+
{{{ end }}}
|
|
35
|
+
{{{ end }}}
|
|
36
|
+
</ul>
|
|
37
|
+
<div class="sidebar-toggle-container align-self-start">
|
|
38
|
+
<!-- User Profile Section (only for logged-in users) -->
|
|
39
|
+
{{{ if config.loggedIn }}}
|
|
40
|
+
<div class="sidebar-user-section mx-2 mb-2">
|
|
41
|
+
<div class="nav-item dropend usermenu">
|
|
42
|
+
<a component="header/avatar" id="sidebar_user_dropdown" href="#" role="button"
|
|
43
|
+
class="nav-link d-flex gap-2 align-items-center text-truncate sidebar-user-trigger"
|
|
44
|
+
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
|
45
|
+
aria-label="[[user:user-menu]]">
|
|
46
|
+
<span class="sidebar-avatar-wrapper position-relative">
|
|
47
|
+
{buildAvatar(user, "32px", true)}
|
|
48
|
+
<span component="user/status" class="sidebar-status-dot position-absolute border border-white border-2 rounded-circle status {user.status}"></span>
|
|
49
|
+
</span>
|
|
50
|
+
<span class="nav-text visible-open fw-semibold text-truncate">{user.username}</span>
|
|
51
|
+
</a>
|
|
52
|
+
<!-- IMPORT partials/sidebar/user-menu-dropdown.tpl -->
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
{{{ else }}}
|
|
56
|
+
<!-- Login/Register for logged-out users -->
|
|
57
|
+
<div class="sidebar-auth-section mx-2 mb-2">
|
|
58
|
+
<div class="nav-item">
|
|
59
|
+
<a class="nav-link d-flex gap-2 align-items-center" href="{relative_path}/login">
|
|
60
|
+
<i class="fa fa-fw fa-sign-in"></i>
|
|
61
|
+
<span class="nav-text small visible-open fw-semibold">[[global:login]]</span>
|
|
62
|
+
</a>
|
|
63
|
+
</div>
|
|
64
|
+
{{{ if config.allowRegistration }}}
|
|
65
|
+
<div class="nav-item mt-1">
|
|
66
|
+
<a class="nav-link d-flex gap-2 align-items-center" href="{relative_path}/register">
|
|
67
|
+
<i class="fa fa-fw fa-user-plus"></i>
|
|
68
|
+
<span class="nav-text small visible-open fw-semibold">[[global:register]]</span>
|
|
69
|
+
</a>
|
|
70
|
+
</div>
|
|
71
|
+
{{{ end }}}
|
|
72
|
+
</div>
|
|
73
|
+
{{{ end }}}
|
|
74
|
+
|
|
75
|
+
{{{ if !config.disableCustomUserSkins }}}
|
|
76
|
+
<!-- IMPORT partials/skin-switcher.tpl -->
|
|
77
|
+
{{{ end }}}
|
|
78
|
+
|
|
79
|
+
<div class="sidebar-toggle m-2 d-none d-lg-block">
|
|
80
|
+
<a href="#" role="button" component="sidebar/toggle" class="nav-link d-flex gap-2 align-items-center p-2 pointer w-100 text-nowrap" title="[[themes/harmony:expand]]" aria-label="[[themes/harmony:sidebar-toggle]]">
|
|
81
|
+
<i class="fa fa-fw fa-angles-right"></i>
|
|
82
|
+
<i class="fa fa-fw fa-angles-left"></i>
|
|
83
|
+
<span class="nav-text visible-open fw-semibold small lh-1">[[themes/harmony:collapse]]</span>
|
|
84
|
+
</a>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</nav>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
JAVIS Community Theme - Topics List (Reddit-style)
|
|
3
|
+
Used on: Categories, Recent, Popular, Unread pages
|
|
4
|
+
-->
|
|
5
|
+
<ul component="category" class="topics-list list-unstyled" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}" data-set="{set}">
|
|
6
|
+
|
|
7
|
+
{{{ each topics }}}
|
|
8
|
+
<li component="category/topic" class="topic-card {function.generateTopicClass}" <!-- IMPORT partials/data/category.tpl -->>
|
|
9
|
+
<link itemprop="url" content="{config.relative_path}/topic/{./slug}" />
|
|
10
|
+
<meta itemprop="name" content="{function.stripTags, ./title}" />
|
|
11
|
+
<meta itemprop="itemListOrder" content="descending" />
|
|
12
|
+
<meta itemprop="position" content="{increment(./index, "1")}" />
|
|
13
|
+
<a id="{./index}" data-index="{./index}" component="topic/anchor"></a>
|
|
14
|
+
|
|
15
|
+
<!-- Vote Column (Reddit-style) -->
|
|
16
|
+
{{{ if !reputation:disabled }}}
|
|
17
|
+
<div class="vote-column">
|
|
18
|
+
<button class="vote-btn vote-up" title="[[topic:upvote]]">
|
|
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]]">
|
|
23
|
+
<i class="fa fa-chevron-down"></i>
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
{{{ end }}}
|
|
27
|
+
|
|
28
|
+
<!-- Main Content -->
|
|
29
|
+
<div class="topic-content">
|
|
30
|
+
<!-- Meta Row: Category + Author + Time -->
|
|
31
|
+
<div class="topic-meta">
|
|
32
|
+
{{{ if (!template.category || (cid != ./cid)) }}}
|
|
33
|
+
{buildCategoryLabel(./category, "a", "border")}
|
|
34
|
+
{{{ end }}}
|
|
35
|
+
<span class="meta-separator">•</span>
|
|
36
|
+
<span class="meta-author">
|
|
37
|
+
Posted by
|
|
38
|
+
<a href="{{{ if ./user.userslug }}}{config.relative_path}/user/{./user.userslug}{{{ else }}}#{{{ end }}}" class="author-link">{./user.displayname}</a>
|
|
39
|
+
</span>
|
|
40
|
+
<span class="meta-separator">•</span>
|
|
41
|
+
<span class="timeago meta-time" title="{./timestampISO}"></span>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Title -->
|
|
45
|
+
<h3 component="topic/header" class="topic-title">
|
|
46
|
+
<a href="{{{ if topics.noAnchor }}}#{{{ else }}}{config.relative_path}/topic/{./slug}{{{ if ./bookmark }}}/{./bookmark}{{{ end }}}{{{ end }}}">{./title}</a>
|
|
47
|
+
</h3>
|
|
48
|
+
|
|
49
|
+
<!-- Labels/Badges -->
|
|
50
|
+
<div component="topic/labels" class="topic-labels">
|
|
51
|
+
<span component="topic/pinned" class="topic-badge pinned {{{ if (./scheduled || !./pinned) }}}hidden{{{ end }}}">
|
|
52
|
+
<i class="fa fa-thumb-tack"></i> Pinned
|
|
53
|
+
</span>
|
|
54
|
+
<span component="topic/locked" class="topic-badge locked {{{ if !./locked }}}hidden{{{ end }}}">
|
|
55
|
+
<i class="fa fa-lock"></i> Locked
|
|
56
|
+
</span>
|
|
57
|
+
<span component="topic/scheduled" class="topic-badge scheduled {{{ if !./scheduled }}}hidden{{{ end }}}">
|
|
58
|
+
<i class="fa fa-clock-o"></i> Scheduled
|
|
59
|
+
</span>
|
|
60
|
+
{{{each ./icons}}}<span class="topic-badge">{@value}</span>{{{end}}}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- Tags -->
|
|
64
|
+
{{{ if ./tags.length }}}
|
|
65
|
+
<div data-tid="{./tid}" component="topic/tags" class="topic-tags">
|
|
66
|
+
{{{ each ./tags }}}
|
|
67
|
+
<a href="{config.relative_path}/tags/{./valueEncoded}" class="topic-tag">{./valueEscaped}</a>
|
|
68
|
+
{{{ end }}}
|
|
69
|
+
</div>
|
|
70
|
+
{{{ end }}}
|
|
71
|
+
|
|
72
|
+
<!-- Thumbnail Preview (if exists) -->
|
|
73
|
+
{{{ if ./thumbs.length }}}
|
|
74
|
+
<a class="topic-thumbnail" href="{config.relative_path}/topic/{./slug}{{{ if ./bookmark }}}/{./bookmark}{{{ end }}}">
|
|
75
|
+
<img src="{./thumbs.0.url}" alt="" loading="lazy" />
|
|
76
|
+
{{{ if ./thumbs.1 }}}
|
|
77
|
+
<span class="thumb-count">+{subtract(./thumbs.length, 1)}</span>
|
|
78
|
+
{{{ end }}}
|
|
79
|
+
</a>
|
|
80
|
+
{{{ end }}}
|
|
81
|
+
|
|
82
|
+
<!-- Teaser/Preview Content -->
|
|
83
|
+
{{{ if ./teaser.content }}}
|
|
84
|
+
<div class="topic-teaser">
|
|
85
|
+
{./teaser.content}
|
|
86
|
+
</div>
|
|
87
|
+
{{{ end }}}
|
|
88
|
+
|
|
89
|
+
<!-- Action Bar -->
|
|
90
|
+
<div class="topic-actions">
|
|
91
|
+
<a href="{config.relative_path}/topic/{./slug}" class="action-btn">
|
|
92
|
+
<i class="fa-regular fa-comment"></i>
|
|
93
|
+
<span>{humanReadableNumber(./postcount, 0)} Comments</span>
|
|
94
|
+
</a>
|
|
95
|
+
<button class="action-btn share-btn" data-url="{config.relative_path}/topic/{./slug}">
|
|
96
|
+
<i class="fa fa-share"></i>
|
|
97
|
+
<span>Share</span>
|
|
98
|
+
</button>
|
|
99
|
+
<button class="action-btn save-btn">
|
|
100
|
+
<i class="fa-regular fa-bookmark"></i>
|
|
101
|
+
<span>Save</span>
|
|
102
|
+
</button>
|
|
103
|
+
<span class="action-stat">
|
|
104
|
+
<i class="fa fa-eye"></i>
|
|
105
|
+
<span>{humanReadableNumber(./viewcount, 0)}</span>
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</li>
|
|
110
|
+
{{{end}}}
|
|
111
|
+
</ul>
|
package/theme.js
CHANGED
|
@@ -8,17 +8,59 @@
|
|
|
8
8
|
const theme = {};
|
|
9
9
|
|
|
10
10
|
theme.defineWidgetAreas = async (areas) => {
|
|
11
|
-
// Define
|
|
11
|
+
// Define widget areas like Harmony does
|
|
12
|
+
const locations = ['header', 'sidebar', 'footer'];
|
|
13
|
+
const templates = [
|
|
14
|
+
'categories.tpl', 'category.tpl', 'topic.tpl', 'users.tpl',
|
|
15
|
+
'unread.tpl', 'recent.tpl', 'popular.tpl', 'top.tpl', 'tags.tpl', 'tag.tpl',
|
|
16
|
+
'login.tpl', 'register.tpl', 'world.tpl',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function capitalizeFirst(str) {
|
|
20
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
templates.forEach((template) => {
|
|
24
|
+
locations.forEach((location) => {
|
|
25
|
+
areas.push({
|
|
26
|
+
name: `${capitalizeFirst(template.split('.')[0])} ${capitalizeFirst(location)}`,
|
|
27
|
+
template: template,
|
|
28
|
+
location: location,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Additional widget areas
|
|
12
34
|
areas = areas.concat([
|
|
13
35
|
{
|
|
14
|
-
name: '
|
|
15
|
-
template: '
|
|
16
|
-
location: '
|
|
36
|
+
name: 'Main post header',
|
|
37
|
+
template: 'topic.tpl',
|
|
38
|
+
location: 'mainpost-header',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Main post footer',
|
|
42
|
+
template: 'topic.tpl',
|
|
43
|
+
location: 'mainpost-footer',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Sidebar Footer',
|
|
47
|
+
template: 'global',
|
|
48
|
+
location: 'sidebar-footer',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'Brand Header',
|
|
52
|
+
template: 'global',
|
|
53
|
+
location: 'brand-header',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'About me (before)',
|
|
57
|
+
template: 'account/profile.tpl',
|
|
58
|
+
location: 'profile-aboutme-before',
|
|
17
59
|
},
|
|
18
60
|
{
|
|
19
|
-
name: '
|
|
20
|
-
template: '
|
|
21
|
-
location: '
|
|
61
|
+
name: 'About me (after)',
|
|
62
|
+
template: 'account/profile.tpl',
|
|
63
|
+
location: 'profile-aboutme-after',
|
|
22
64
|
},
|
|
23
65
|
]);
|
|
24
66
|
|
package/theme.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "@okjavis/nodebb-theme-javis",
|
|
3
|
+
"name": "JAVIS Community Theme",
|
|
4
|
+
"description": "Modern, premium NodeBB theme for JAVIS Community",
|
|
5
|
+
"url": "https://github.com/javis-admin/nodebb-community-theme",
|
|
6
|
+
"screenshot": "screenshot.png",
|
|
7
|
+
"baseTheme": "nodebb-theme-harmony"
|
|
8
|
+
}
|
package/theme.scss
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// JAVIS Community Theme
|
|
3
|
+
// Extends NodeBB Harmony Theme
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
// Note: When using baseTheme, NodeBB loads the parent theme (Harmony) first.
|
|
7
|
+
// This file contains JAVIS-specific style overrides that are applied on top.
|
|
8
|
+
|
|
9
|
+
// Import Harmony's theme styles (required for inheritance)
|
|
10
|
+
@import "nodebb-theme-harmony/scss/harmony";
|
|
11
|
+
|
|
12
|
+
// JAVIS Custom Styles
|
|
13
|
+
@import "./scss/variables";
|
|
14
|
+
@import "./scss/base";
|
|
15
|
+
@import "./scss/buttons";
|
|
16
|
+
@import "./scss/forms";
|
|
17
|
+
@import "./scss/cards";
|
|
18
|
+
@import "./scss/sidebar";
|
|
19
|
+
@import "./scss/sidebar-user";
|
|
20
|
+
@import "./scss/categories";
|
|
21
|
+
@import "./scss/feed";
|
package/less/_base.less
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// ============================================
|
|
2
|
-
// BASE STYLES – Global Reset & Typography
|
|
3
|
-
// ============================================
|
|
4
|
-
|
|
5
|
-
html, body {
|
|
6
|
-
font-family: @jv-font-sans;
|
|
7
|
-
font-size: @jv-font-size-base;
|
|
8
|
-
line-height: @jv-line-height-base;
|
|
9
|
-
background-color: @jv-bg;
|
|
10
|
-
color: @jv-text-main;
|
|
11
|
-
-webkit-font-smoothing: antialiased;
|
|
12
|
-
-moz-osx-font-smoothing: grayscale;
|
|
13
|
-
margin: 0;
|
|
14
|
-
padding: 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Paragraph
|
|
18
|
-
p {
|
|
19
|
-
margin: 0 0 @jv-space-6 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Headings
|
|
23
|
-
h1 {
|
|
24
|
-
font-size: @jv-font-size-xxl;
|
|
25
|
-
font-weight: 600;
|
|
26
|
-
letter-spacing: -0.02em;
|
|
27
|
-
margin-bottom: @jv-space-8;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
h2 {
|
|
31
|
-
font-size: @jv-font-size-xl;
|
|
32
|
-
font-weight: 600;
|
|
33
|
-
letter-spacing: -0.01em;
|
|
34
|
-
margin-bottom: @jv-space-6;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
h3 {
|
|
38
|
-
font-size: @jv-font-size-xl;
|
|
39
|
-
font-weight: 600;
|
|
40
|
-
margin-bottom: @jv-space-6;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
h4 {
|
|
44
|
-
font-size: 20px;
|
|
45
|
-
font-weight: 600;
|
|
46
|
-
margin-bottom: @jv-space-6;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
h5 {
|
|
50
|
-
font-size: 17px;
|
|
51
|
-
font-weight: 600;
|
|
52
|
-
margin-bottom: @jv-space-6;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
h6 {
|
|
56
|
-
font-size: @jv-font-size-base;
|
|
57
|
-
font-weight: 600;
|
|
58
|
-
margin-bottom: @jv-space-4;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Links
|
|
62
|
-
a {
|
|
63
|
-
color: @jv-primary;
|
|
64
|
-
text-decoration: none;
|
|
65
|
-
transition: color 0.15s ease;
|
|
66
|
-
|
|
67
|
-
&:hover {
|
|
68
|
-
color: @jv-primary-hover;
|
|
69
|
-
}
|
|
70
|
-
}
|