@okjavis/nodebb-theme-javis 1.6.0 → 2.0.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,547 @@
1
+ // ===========================================================
2
+ // TOPIC PAGE – Individual Topic/Thread View
3
+ // Clean, cohesive design matching platform theme
4
+ // ===========================================================
5
+
6
+ // ===========================================================
7
+ // BREADCRUMB – Softer, More Subtle
8
+ // ===========================================================
9
+ body.template-topic {
10
+ .breadcrumb {
11
+ background: transparent;
12
+ padding: $jv-space-2 0;
13
+ margin-bottom: $jv-space-4;
14
+ font-size: $jv-font-size-sm;
15
+
16
+ .breadcrumb-item {
17
+ color: $jv-text-soft;
18
+
19
+ a {
20
+ color: $jv-text-muted;
21
+ text-decoration: none;
22
+ transition: color $jv-transition-fast;
23
+
24
+ &:hover {
25
+ color: $jv-primary;
26
+ }
27
+ }
28
+
29
+ // Separator styling
30
+ + .breadcrumb-item::before {
31
+ color: $jv-text-soft;
32
+ content: "/";
33
+ padding: 0 $jv-space-2;
34
+ }
35
+
36
+ // Active/current item
37
+ &.active {
38
+ color: $jv-text-muted;
39
+ font-weight: 500;
40
+ }
41
+ }
42
+ }
43
+
44
+ // ===========================================================
45
+ // TOPIC HEADER – Title and Meta
46
+ // ===========================================================
47
+ .topic-header,
48
+ [component="topic/header"] {
49
+ margin-bottom: $jv-space-6;
50
+
51
+ // Topic title
52
+ h1,
53
+ [component="topic/title"] {
54
+ font-size: 28px;
55
+ font-weight: 700;
56
+ line-height: $jv-line-height-tight;
57
+ letter-spacing: -0.02em;
58
+ color: $jv-text-main;
59
+ margin-bottom: $jv-space-3;
60
+ }
61
+ }
62
+
63
+ // ===========================================================
64
+ // META PILLS – Category, Tags, Stats
65
+ // ===========================================================
66
+ .topic-tags,
67
+ [component="topic/tags"] {
68
+ display: flex;
69
+ flex-wrap: wrap;
70
+ gap: $jv-space-2;
71
+ margin-bottom: $jv-space-4;
72
+
73
+ .badge,
74
+ .tag {
75
+ font-size: $jv-font-size-xs;
76
+ font-weight: 500;
77
+ padding: $jv-space-1 $jv-space-3;
78
+ border-radius: $jv-radius-pill;
79
+ background: rgba(0, 0, 0, 0.05);
80
+ color: $jv-text-muted;
81
+ border: none;
82
+ transition: background-color $jv-transition-fast, color $jv-transition-fast;
83
+
84
+ &:hover {
85
+ background: $jv-primary-soft;
86
+ color: $jv-primary;
87
+ }
88
+ }
89
+ }
90
+
91
+ // Category badge in topic
92
+ .category-badge,
93
+ [component="category/link"] .badge {
94
+ background: rgba(0, 0, 0, 0.06);
95
+ color: $jv-text-muted;
96
+ font-weight: 500;
97
+ }
98
+
99
+ // ===========================================================
100
+ // POST CONTAINER – Clean, Borderless Design
101
+ // ===========================================================
102
+ .topic {
103
+ .posts-container {
104
+ max-width: 100%;
105
+ width: 100%;
106
+ }
107
+
108
+ // Individual post
109
+ [component="post"] {
110
+ background: $jv-surface;
111
+ border-radius: $jv-radius-md;
112
+ margin-bottom: $jv-space-4;
113
+ border: none; // Borderless as per theme
114
+ box-shadow: none; // No shadow for cleaner look
115
+
116
+ // Post container inner
117
+ .post-container {
118
+ padding: $jv-space-6;
119
+ border: none;
120
+ background: transparent;
121
+ }
122
+
123
+ // Selected state (when linked directly)
124
+ &.selected .post-container {
125
+ background: $jv-selected-bg;
126
+ border-radius: $jv-radius-md;
127
+ }
128
+ }
129
+ }
130
+
131
+ // ===========================================================
132
+ // POST HEADER – Avatar, Username, Time
133
+ // ===========================================================
134
+ .post-header {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: $jv-space-3;
138
+ margin-bottom: $jv-space-4;
139
+ font-size: $jv-font-size-sm;
140
+
141
+ // Avatar
142
+ .avatar {
143
+ width: 40px;
144
+ height: 40px;
145
+ border-radius: 50%;
146
+ object-fit: cover;
147
+ flex-shrink: 0;
148
+ }
149
+
150
+ // User info container
151
+ .user-info {
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: 2px;
155
+ }
156
+
157
+ // Username
158
+ .username,
159
+ [component="post/header/username"] {
160
+ font-weight: 600;
161
+ color: $jv-text-main;
162
+ text-decoration: none;
163
+
164
+ &:hover {
165
+ color: $jv-primary;
166
+ }
167
+ }
168
+
169
+ // Timestamp
170
+ .timeago,
171
+ [component="post/timestamp"] {
172
+ color: $jv-text-soft;
173
+ font-size: $jv-font-size-xs;
174
+ }
175
+
176
+ // Post index/number - Subtle
177
+ .post-index,
178
+ [component="post/header/index"] {
179
+ color: $jv-text-soft;
180
+ font-size: $jv-font-size-xs;
181
+ font-weight: 400;
182
+ opacity: 0.6;
183
+
184
+ &::before {
185
+ content: "#";
186
+ }
187
+ }
188
+
189
+ // Bookmarked indicator
190
+ .bookmarked {
191
+ color: $jv-warning;
192
+ }
193
+ }
194
+
195
+ // ===========================================================
196
+ // POST CONTENT – Typography
197
+ // ===========================================================
198
+ [component="post/content"] {
199
+ font-size: $jv-font-size-base;
200
+ line-height: $jv-line-height-relaxed;
201
+ color: $jv-text-main;
202
+
203
+ p {
204
+ margin-bottom: $jv-space-4;
205
+
206
+ &:last-child {
207
+ margin-bottom: 0;
208
+ }
209
+ }
210
+
211
+ // Code blocks
212
+ pre, code {
213
+ background: rgba(0, 0, 0, 0.04);
214
+ border-radius: $jv-radius-sm;
215
+ font-size: $jv-font-size-sm;
216
+ }
217
+
218
+ code {
219
+ padding: 2px 6px;
220
+ }
221
+
222
+ pre {
223
+ padding: $jv-space-4;
224
+ overflow-x: auto;
225
+
226
+ code {
227
+ padding: 0;
228
+ background: transparent;
229
+ }
230
+ }
231
+
232
+ // Blockquotes
233
+ blockquote {
234
+ border-left: 3px solid $jv-border-strong;
235
+ padding-left: $jv-space-4;
236
+ margin: $jv-space-4 0;
237
+ color: $jv-text-muted;
238
+ font-style: italic;
239
+ }
240
+
241
+ // Images
242
+ img {
243
+ max-width: 100%;
244
+ border-radius: $jv-radius-sm;
245
+ }
246
+
247
+ // Links
248
+ a {
249
+ color: $jv-primary;
250
+
251
+ &:hover {
252
+ text-decoration: underline;
253
+ }
254
+ }
255
+ }
256
+
257
+ // ===========================================================
258
+ // POST FOOTER – Action Bar
259
+ // ===========================================================
260
+ [component="post/footer"],
261
+ .post-footer {
262
+ padding-top: $jv-space-4;
263
+ margin-top: $jv-space-4;
264
+ border-top: 1px solid $jv-border-subtle;
265
+ }
266
+
267
+ // ===========================================================
268
+ // ACTION ICONS – Ghost Button Style
269
+ // ===========================================================
270
+ [component="post/actions"] {
271
+ display: flex;
272
+ align-items: center;
273
+ gap: $jv-space-2;
274
+ flex-wrap: wrap;
275
+
276
+ // All action buttons - Ghost style
277
+ .btn,
278
+ [component="post/reply"],
279
+ [component="post/quote"],
280
+ [component="post/bookmark"],
281
+ [component="post/upvote"],
282
+ [component="post/downvote"],
283
+ [component="post/tools"] {
284
+ background: transparent !important;
285
+ border: none !important;
286
+ color: $jv-text-muted;
287
+ padding: $jv-space-2 $jv-space-3;
288
+ border-radius: $jv-radius-sm;
289
+ font-size: $jv-font-size-sm;
290
+ font-weight: 500;
291
+ transition: background-color $jv-transition-fast, color $jv-transition-fast;
292
+ display: inline-flex;
293
+ align-items: center;
294
+ gap: $jv-space-2;
295
+
296
+ i {
297
+ font-size: 16px;
298
+ }
299
+
300
+ &:hover {
301
+ background: $jv-hover-bg !important;
302
+ color: $jv-text-main;
303
+ }
304
+
305
+ &:focus-visible {
306
+ outline: none;
307
+ box-shadow: $jv-focus-ring;
308
+ }
309
+
310
+ // Active states (voted, bookmarked)
311
+ &.active,
312
+ &.upvoted,
313
+ &.downvoted,
314
+ &.bookmarked {
315
+ background: $jv-primary-soft !important;
316
+ color: $jv-primary;
317
+ }
318
+ }
319
+
320
+ // Vote counts
321
+ [component="post/vote-count"] {
322
+ font-weight: 600;
323
+ color: $jv-text-muted;
324
+ min-width: 20px;
325
+ text-align: center;
326
+ }
327
+ }
328
+
329
+ // ===========================================================
330
+ // RIGHT SIDEBAR ACTIONS – Ghost Buttons
331
+ // ===========================================================
332
+ .topic-main-buttons,
333
+ [component="topic/actions"],
334
+ .sidebar [data-widget-area] {
335
+ .btn {
336
+ background: transparent !important;
337
+ border: 1px solid transparent !important;
338
+ color: $jv-text-muted;
339
+ padding: $jv-space-2 $jv-space-4;
340
+ border-radius: $jv-radius-sm;
341
+ font-size: $jv-font-size-sm;
342
+ font-weight: 500;
343
+ transition: background-color $jv-transition-fast, color $jv-transition-fast;
344
+ display: inline-flex;
345
+ align-items: center;
346
+ gap: $jv-space-2;
347
+ width: 100%;
348
+ justify-content: flex-start;
349
+ text-align: left;
350
+
351
+ i {
352
+ font-size: 16px;
353
+ width: 20px;
354
+ text-align: center;
355
+ }
356
+
357
+ &:hover {
358
+ background: $jv-hover-bg !important;
359
+ color: $jv-text-main;
360
+ }
361
+
362
+ &:focus-visible {
363
+ outline: none;
364
+ box-shadow: $jv-focus-ring;
365
+ }
366
+
367
+ // Primary action (Reply)
368
+ &.btn-primary {
369
+ background: $jv-primary !important;
370
+ color: #fff;
371
+
372
+ &:hover {
373
+ background: $jv-primary-hover !important;
374
+ color: #fff;
375
+ }
376
+ }
377
+ }
378
+ }
379
+
380
+ // Sidebar action list
381
+ .topic-actions-list,
382
+ .topic-tools {
383
+ display: flex;
384
+ flex-direction: column;
385
+ gap: $jv-space-1;
386
+
387
+ .dropdown-item,
388
+ .action-item {
389
+ padding: $jv-space-2 $jv-space-4;
390
+ border-radius: $jv-radius-sm;
391
+ color: $jv-text-muted;
392
+ font-size: $jv-font-size-sm;
393
+ transition: background-color $jv-transition-fast, color $jv-transition-fast;
394
+
395
+ &:hover {
396
+ background: $jv-hover-bg;
397
+ color: $jv-text-main;
398
+ }
399
+ }
400
+ }
401
+
402
+ // ===========================================================
403
+ // QUICK REPLY BOX
404
+ // ===========================================================
405
+ .quick-reply,
406
+ [component="topic/quickreply"] {
407
+ background: $jv-surface;
408
+ border-radius: $jv-radius-md;
409
+ padding: $jv-space-6;
410
+ margin-top: $jv-space-6;
411
+ border: 1px solid $jv-border-subtle;
412
+
413
+ // Quick reply header
414
+ .quick-reply-header {
415
+ margin-bottom: $jv-space-4;
416
+ font-weight: 600;
417
+ color: $jv-text-main;
418
+ }
419
+
420
+ // Textarea
421
+ textarea,
422
+ .form-control {
423
+ border: 1px solid $jv-border-subtle;
424
+ border-radius: $jv-radius-sm;
425
+ padding: $jv-space-4;
426
+ font-size: $jv-font-size-base;
427
+ line-height: $jv-line-height-base;
428
+ resize: vertical;
429
+ min-height: 120px;
430
+ transition: border-color $jv-transition-fast, box-shadow $jv-transition-fast;
431
+
432
+ &:focus {
433
+ outline: none;
434
+ border-color: $jv-primary;
435
+ box-shadow: $jv-focus-ring;
436
+ }
437
+
438
+ &::placeholder {
439
+ color: $jv-text-soft;
440
+ }
441
+ }
442
+
443
+ // Quick reply footer with buttons
444
+ .quick-reply-footer,
445
+ .composer-actions {
446
+ display: flex;
447
+ justify-content: flex-end;
448
+ gap: $jv-space-3;
449
+ margin-top: $jv-space-4;
450
+ }
451
+ }
452
+
453
+ // ===========================================================
454
+ // REPLY CONTAINER (Nested replies)
455
+ // ===========================================================
456
+ [component="post/replies/container"] {
457
+ margin-top: $jv-space-4;
458
+ padding-left: $jv-space-6;
459
+ border-left: 2px solid $jv-border-subtle;
460
+
461
+ [component="post"] {
462
+ margin-bottom: $jv-space-3;
463
+ padding: $jv-space-4;
464
+ background: rgba(0, 0, 0, 0.02);
465
+ border-radius: $jv-radius-sm;
466
+
467
+ .post-container {
468
+ padding: 0;
469
+ }
470
+ }
471
+ }
472
+
473
+ // ===========================================================
474
+ // TOPIC NAVIGATOR (Floating scroll helper)
475
+ // ===========================================================
476
+ [component="topic/navigator"] {
477
+ background: $jv-surface;
478
+ border-radius: $jv-radius-md;
479
+ box-shadow: $jv-shadow-lg;
480
+ border: 1px solid $jv-border-subtle;
481
+ padding: $jv-space-3;
482
+
483
+ .btn {
484
+ background: transparent;
485
+ border: none;
486
+ color: $jv-text-muted;
487
+ padding: $jv-space-2;
488
+ border-radius: $jv-radius-sm;
489
+
490
+ &:hover {
491
+ background: $jv-hover-bg;
492
+ color: $jv-text-main;
493
+ }
494
+ }
495
+ }
496
+ }
497
+
498
+ // ===========================================================
499
+ // RESPONSIVE ADJUSTMENTS
500
+ // ===========================================================
501
+ @media (max-width: 991px) {
502
+ body.template-topic {
503
+ .topic [component="post"] .post-container {
504
+ padding: $jv-space-4;
505
+ }
506
+
507
+ .post-header {
508
+ flex-wrap: wrap;
509
+ }
510
+
511
+ [component="post/actions"] {
512
+ gap: $jv-space-1;
513
+
514
+ .btn,
515
+ [component="post/reply"],
516
+ [component="post/quote"],
517
+ [component="post/bookmark"],
518
+ [component="post/upvote"],
519
+ [component="post/downvote"] {
520
+ padding: $jv-space-2;
521
+
522
+ // Hide text on smaller screens
523
+ span:not(.fa):not(.fas):not(.far):not(.fab) {
524
+ display: none;
525
+ }
526
+ }
527
+ }
528
+ }
529
+ }
530
+
531
+ @media (max-width: 767px) {
532
+ body.template-topic {
533
+ .topic-header h1,
534
+ .topic-header [component="topic/title"] {
535
+ font-size: 22px;
536
+ }
537
+
538
+ .quick-reply,
539
+ [component="topic/quickreply"] {
540
+ padding: $jv-space-4;
541
+ }
542
+
543
+ [component="post/replies/container"] {
544
+ padding-left: $jv-space-4;
545
+ }
546
+ }
547
+ }
@@ -87,3 +87,13 @@ $jv-focus-ring-offset: 0 0 0 2px $jv-surface, 0 0 0 4px $jv-primary;
87
87
  $jv-hover-bg: rgba(0, 0, 0, 0.04);
88
88
  $jv-active-bg: rgba(0, 0, 0, 0.08);
89
89
  $jv-selected-bg: rgba(0, 81, 255, 0.08);
90
+
91
+ // Semantic Colors
92
+ $jv-success: #10b981;
93
+ $jv-success-hover: #059669;
94
+ $jv-danger: #ef4444;
95
+ $jv-danger-hover: #dc2626;
96
+ $jv-warning: #f59e0b;
97
+ $jv-warning-hover: #d97706;
98
+ $jv-info: #3b82f6;
99
+ $jv-info-hover: #2563eb;
package/theme.js CHANGED
@@ -5,8 +5,64 @@
5
5
 
6
6
  'use strict';
7
7
 
8
+ const meta = require.main.require('./src/meta');
9
+ const user = require.main.require('./src/user');
10
+ const _ = require.main.require('lodash');
11
+
8
12
  const theme = {};
9
13
 
14
+ // Harmony's defaults - we override openSidebars to 'on'
15
+ const defaults = {
16
+ enableQuickReply: 'on',
17
+ enableBreadcrumbs: 'on',
18
+ centerHeaderElements: 'off',
19
+ mobileTopicTeasers: 'off',
20
+ stickyToolbar: 'on',
21
+ topicSidebarTools: 'on',
22
+ topMobilebar: 'off',
23
+ autohideBottombar: 'on',
24
+ openSidebars: 'on', // JAVIS override: sidebar open by default
25
+ chatModals: 'off',
26
+ };
27
+
28
+ /**
29
+ * Load theme config - replaces Harmony's loadThemeConfig
30
+ * Uses JAVIS defaults (with openSidebars: 'on')
31
+ */
32
+ async function loadThemeConfig(uid) {
33
+ const [themeConfig, userConfig] = await Promise.all([
34
+ meta.settings.get('harmony'),
35
+ user.getSettings(uid),
36
+ ]);
37
+
38
+ // 3-tier cascade: JAVIS defaults -> theme settings -> user settings
39
+ const config = { ...defaults, ...themeConfig, ...(_.pick(userConfig, Object.keys(defaults))) };
40
+
41
+ // Convert 'on'/'off' strings to boolean
42
+ config.enableQuickReply = config.enableQuickReply === 'on';
43
+ config.enableBreadcrumbs = config.enableBreadcrumbs === 'on';
44
+ config.centerHeaderElements = config.centerHeaderElements === 'on';
45
+ config.mobileTopicTeasers = config.mobileTopicTeasers === 'on';
46
+ config.stickyToolbar = config.stickyToolbar === 'on';
47
+ config.topicSidebarTools = config.topicSidebarTools === 'on';
48
+ config.autohideBottombar = config.autohideBottombar === 'on';
49
+ config.topMobilebar = config.topMobilebar === 'on';
50
+ config.openSidebars = config.openSidebars === 'on';
51
+ config.chatModals = config.chatModals === 'on';
52
+
53
+ return config;
54
+ }
55
+
56
+ /**
57
+ * Hook: filter:config.get
58
+ * Sets config.theme with JAVIS-specific defaults
59
+ */
60
+ theme.getThemeConfig = async function (config) {
61
+ config.theme = await loadThemeConfig(config.uid);
62
+ config.openDraftsOnPageLoad = false;
63
+ return config;
64
+ };
65
+
10
66
  theme.defineWidgetAreas = async (areas) => {
11
67
  // Define widget areas like Harmony does
12
68
  const locations = ['header', 'sidebar', 'footer'];
package/theme.scss CHANGED
@@ -12,6 +12,7 @@
12
12
  // JAVIS Custom Styles
13
13
  @import "./scss/variables";
14
14
  @import "./scss/base";
15
+ @import "./scss/header";
15
16
  @import "./scss/buttons";
16
17
  @import "./scss/forms";
17
18
  @import "./scss/cards";
@@ -19,3 +20,4 @@
19
20
  @import "./scss/sidebar-user";
20
21
  @import "./scss/categories";
21
22
  @import "./scss/feed";
23
+ @import "./scss/topic";