@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.
@@ -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
@@ -10,8 +10,45 @@
10
10
  $(document).ready(function() {
11
11
  console.log('JAVIS Community Theme initialized');
12
12
 
13
- // Add any custom client-side interactions here
14
- // Example: smooth scrolling, animations, custom widgets, etc.
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,7 @@
1
+ <!-- Right sidebar hidden - user profile moved to left sidebar -->
2
+ <!-- Widget area preserved for any sidebar footer widgets if needed -->
3
+ <div class="d-none" data-widget-area="sidebar-footer">
4
+ {{{each widgets.sidebar-footer}}}
5
+ {{./html}}
6
+ {{{end}}}
7
+ </div>
@@ -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 custom widget areas for JAVIS theme
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: 'JAVIS Right Sidebar',
15
- template: 'categories.tpl',
16
- location: 'sidebar-right',
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: 'JAVIS Left Sidebar',
20
- template: 'categories.tpl',
21
- location: 'sidebar-left',
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
- }