@okjavis/nodebb-theme-javis 2.1.0 → 2.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 +1 -1
- package/scss/_topic.scss +518 -62
- package/static/lib/theme.js +319 -0
- package/templates/partials/topic/post-parent.tpl +9 -0
package/package.json
CHANGED
package/scss/_topic.scss
CHANGED
|
@@ -33,15 +33,15 @@ body.template-topic {
|
|
|
33
33
|
margin-bottom: 0;
|
|
34
34
|
display: flex;
|
|
35
35
|
align-items: flex-start;
|
|
36
|
-
gap: $jv-space-
|
|
36
|
+
gap: $jv-space-3;
|
|
37
37
|
|
|
38
38
|
h1,
|
|
39
39
|
[component="topic/title"],
|
|
40
40
|
.topic-title {
|
|
41
|
-
font-size:
|
|
42
|
-
font-weight:
|
|
43
|
-
line-height:
|
|
44
|
-
letter-spacing: -0.
|
|
41
|
+
font-size: $jv-font-size-xl;
|
|
42
|
+
font-weight: 700;
|
|
43
|
+
line-height: $jv-line-height-tight;
|
|
44
|
+
letter-spacing: -0.02em;
|
|
45
45
|
color: $jv-text-main;
|
|
46
46
|
margin-bottom: 0;
|
|
47
47
|
flex: 1;
|
|
@@ -49,10 +49,10 @@ body.template-topic {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
h1.fs-3 {
|
|
52
|
-
font-size:
|
|
53
|
-
font-weight:
|
|
54
|
-
line-height:
|
|
55
|
-
letter-spacing: -0.
|
|
52
|
+
font-size: $jv-font-size-xl !important;
|
|
53
|
+
font-weight: 700 !important;
|
|
54
|
+
line-height: $jv-line-height-tight !important;
|
|
55
|
+
letter-spacing: -0.02em !important;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// ===========================================================
|
|
@@ -111,23 +111,75 @@ body.template-topic {
|
|
|
111
111
|
width: 100%;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Remove timeline start/end dots from posts list
|
|
115
|
+
.posts.timeline::before,
|
|
116
|
+
.posts.timeline::after {
|
|
117
|
+
display: none !important;
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
[component="post"] {
|
|
115
|
-
background:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
border: 1px solid $jv-border-subtle;
|
|
121
|
+
background: transparent;
|
|
122
|
+
margin-bottom: $jv-space-3;
|
|
123
|
+
border: none;
|
|
119
124
|
box-shadow: none;
|
|
120
125
|
|
|
126
|
+
// Remove timeline vertical line from Harmony theme
|
|
127
|
+
border-left: none !important;
|
|
128
|
+
|
|
129
|
+
// Remove timeline end dot
|
|
130
|
+
&:last-child::after {
|
|
131
|
+
display: none !important;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Remove all timeline pseudo-elements (dots at start/end)
|
|
135
|
+
&::before,
|
|
136
|
+
&::after {
|
|
137
|
+
display: none !important;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Hide user status indicator (green/grey dot below avatar)
|
|
141
|
+
[component="user/status"] {
|
|
142
|
+
display: none !important;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Card styling on the parent container that includes avatar
|
|
146
|
+
.post-container-parent {
|
|
147
|
+
background: $jv-surface;
|
|
148
|
+
border-radius: $jv-radius-md;
|
|
149
|
+
border: 1px solid $jv-border-subtle;
|
|
150
|
+
padding: $jv-space-3;
|
|
151
|
+
gap: $jv-space-2 !important; // Tighter gap
|
|
152
|
+
|
|
153
|
+
// Reduce avatar size from 48px to 36px
|
|
154
|
+
> .bg-body {
|
|
155
|
+
// Align avatar container with the post header (username/timestamp line)
|
|
156
|
+
align-self: flex-start;
|
|
157
|
+
margin-top: 2px; // Fine-tune to vertically center with text
|
|
158
|
+
|
|
159
|
+
[component="user/picture"],
|
|
160
|
+
.avatar {
|
|
161
|
+
width: 36px !important;
|
|
162
|
+
height: 36px !important;
|
|
163
|
+
font-size: $jv-font-size-sm !important;
|
|
164
|
+
line-height: 36px !important;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Align the post header row with avatar (extends base .post-header)
|
|
170
|
+
.post-header {
|
|
171
|
+
min-height: 36px; // Match avatar height for alignment
|
|
172
|
+
}
|
|
173
|
+
|
|
121
174
|
.post-container {
|
|
122
|
-
padding:
|
|
175
|
+
padding: 0;
|
|
123
176
|
padding-top: 0 !important;
|
|
124
177
|
border: none;
|
|
125
178
|
background: transparent;
|
|
126
179
|
}
|
|
127
180
|
|
|
128
|
-
&.selected .post-container {
|
|
181
|
+
&.selected .post-container-parent {
|
|
129
182
|
background: $jv-selected-bg;
|
|
130
|
-
border-radius: $jv-radius-md;
|
|
131
183
|
}
|
|
132
184
|
}
|
|
133
185
|
}
|
|
@@ -137,14 +189,14 @@ body.template-topic {
|
|
|
137
189
|
// ===========================================================
|
|
138
190
|
.post-header {
|
|
139
191
|
display: flex;
|
|
140
|
-
align-items: center;
|
|
141
|
-
gap: $jv-space-
|
|
142
|
-
margin-bottom:
|
|
143
|
-
font-size: $jv-font-size-
|
|
192
|
+
align-items: center !important;
|
|
193
|
+
gap: $jv-space-2;
|
|
194
|
+
margin-bottom: 0 !important;
|
|
195
|
+
font-size: $jv-font-size-xs;
|
|
144
196
|
|
|
145
197
|
.avatar {
|
|
146
|
-
width:
|
|
147
|
-
height:
|
|
198
|
+
width: 32px;
|
|
199
|
+
height: 32px;
|
|
148
200
|
border-radius: 50%;
|
|
149
201
|
object-fit: cover;
|
|
150
202
|
flex-shrink: 0;
|
|
@@ -153,12 +205,13 @@ body.template-topic {
|
|
|
153
205
|
.user-info {
|
|
154
206
|
display: flex;
|
|
155
207
|
flex-direction: column;
|
|
156
|
-
gap:
|
|
208
|
+
gap: $jv-space-1;
|
|
157
209
|
}
|
|
158
210
|
|
|
159
211
|
.username,
|
|
160
212
|
[component="post/header/username"] {
|
|
161
213
|
font-weight: 600;
|
|
214
|
+
font-size: $jv-font-size-sm;
|
|
162
215
|
color: $jv-text-main;
|
|
163
216
|
text-decoration: none;
|
|
164
217
|
|
|
@@ -195,20 +248,20 @@ body.template-topic {
|
|
|
195
248
|
color: $jv-text-main;
|
|
196
249
|
|
|
197
250
|
p {
|
|
198
|
-
margin-bottom: $jv-space-
|
|
251
|
+
margin-bottom: $jv-space-3;
|
|
199
252
|
&:last-child { margin-bottom: 0; }
|
|
200
253
|
}
|
|
201
254
|
|
|
202
255
|
pre, code {
|
|
203
256
|
background: rgba(0, 0, 0, 0.04);
|
|
204
257
|
border-radius: $jv-radius-sm;
|
|
205
|
-
font-size: $jv-font-size-
|
|
258
|
+
font-size: $jv-font-size-xs;
|
|
206
259
|
}
|
|
207
260
|
|
|
208
|
-
code { padding: 2px
|
|
261
|
+
code { padding: 2px 5px; }
|
|
209
262
|
|
|
210
263
|
pre {
|
|
211
|
-
padding: $jv-space-
|
|
264
|
+
padding: $jv-space-3;
|
|
212
265
|
overflow-x: auto;
|
|
213
266
|
code {
|
|
214
267
|
padding: 0;
|
|
@@ -229,6 +282,95 @@ body.template-topic {
|
|
|
229
282
|
border-radius: $jv-radius-sm;
|
|
230
283
|
}
|
|
231
284
|
|
|
285
|
+
// ===========================================================
|
|
286
|
+
// POST IMAGE CAROUSEL
|
|
287
|
+
// ===========================================================
|
|
288
|
+
.post-image-carousel {
|
|
289
|
+
border-radius: $jv-radius-md;
|
|
290
|
+
overflow: hidden;
|
|
291
|
+
margin: $jv-space-4 0;
|
|
292
|
+
background: rgba(0, 0, 0, 0.03);
|
|
293
|
+
|
|
294
|
+
.carousel-inner {
|
|
295
|
+
border-radius: $jv-radius-md;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.carousel-item {
|
|
299
|
+
// Fixed height container to prevent jumping
|
|
300
|
+
height: 400px;
|
|
301
|
+
|
|
302
|
+
img {
|
|
303
|
+
width: 100%;
|
|
304
|
+
height: 100%;
|
|
305
|
+
object-fit: contain;
|
|
306
|
+
background: rgba(0, 0, 0, 0.02);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Navigation arrows
|
|
311
|
+
.carousel-control-prev,
|
|
312
|
+
.carousel-control-next {
|
|
313
|
+
width: 48px;
|
|
314
|
+
height: 48px;
|
|
315
|
+
top: 50%;
|
|
316
|
+
transform: translateY(-50%);
|
|
317
|
+
background: rgba(0, 0, 0, 0.5);
|
|
318
|
+
border-radius: 50%;
|
|
319
|
+
opacity: 0;
|
|
320
|
+
transition: opacity $jv-transition-fast;
|
|
321
|
+
|
|
322
|
+
&:hover {
|
|
323
|
+
background: rgba(0, 0, 0, 0.7);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.carousel-control-prev-icon,
|
|
327
|
+
.carousel-control-next-icon {
|
|
328
|
+
width: 20px;
|
|
329
|
+
height: 20px;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.carousel-control-prev {
|
|
334
|
+
left: $jv-space-3;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.carousel-control-next {
|
|
338
|
+
right: $jv-space-3;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
&:hover {
|
|
342
|
+
.carousel-control-prev,
|
|
343
|
+
.carousel-control-next {
|
|
344
|
+
opacity: 1;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Indicator dots
|
|
349
|
+
.carousel-indicators {
|
|
350
|
+
margin-bottom: $jv-space-3;
|
|
351
|
+
gap: $jv-space-2;
|
|
352
|
+
|
|
353
|
+
button {
|
|
354
|
+
width: 8px;
|
|
355
|
+
height: 8px;
|
|
356
|
+
border-radius: 50%;
|
|
357
|
+
background: rgba(255, 255, 255, 0.5);
|
|
358
|
+
border: none;
|
|
359
|
+
opacity: 1;
|
|
360
|
+
transition: background-color $jv-transition-fast, transform $jv-transition-fast;
|
|
361
|
+
|
|
362
|
+
&.active {
|
|
363
|
+
background: #fff;
|
|
364
|
+
transform: scale(1.2);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
&:hover {
|
|
368
|
+
background: rgba(255, 255, 255, 0.8);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
232
374
|
a {
|
|
233
375
|
color: $jv-primary;
|
|
234
376
|
&:hover { text-decoration: underline; }
|
|
@@ -300,10 +442,32 @@ body.template-topic {
|
|
|
300
442
|
}
|
|
301
443
|
}
|
|
302
444
|
|
|
303
|
-
//
|
|
304
|
-
|
|
445
|
+
// Post actions - show on hover (Reddit/HN style)
|
|
446
|
+
// Uses JS-managed .post-hovered class to prevent bubble to parent posts
|
|
447
|
+
.topic .posts [component="post"] {
|
|
448
|
+
[component="post/actions"] {
|
|
449
|
+
opacity: 0;
|
|
450
|
+
transition: opacity $jv-transition-fast;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Show actions only on the directly hovered post (JS adds .post-hovered class)
|
|
454
|
+
> .post-container-parent.post-hovered [component="post/actions"] {
|
|
455
|
+
opacity: 1;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Always show if user has interacted (voted, bookmarked)
|
|
459
|
+
[component="post/actions"] {
|
|
460
|
+
.upvoted,
|
|
461
|
+
.downvoted,
|
|
462
|
+
.bookmarked {
|
|
463
|
+
opacity: 1;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Keep vote count always visible when there are votes
|
|
469
|
+
.topic .posts [component="post/vote-count"]:not(:empty) {
|
|
305
470
|
opacity: 1 !important;
|
|
306
|
-
visibility: visible !important;
|
|
307
471
|
}
|
|
308
472
|
|
|
309
473
|
// ===========================================================
|
|
@@ -448,9 +612,21 @@ body.template-topic {
|
|
|
448
612
|
.dropdown-toggle { display: none; }
|
|
449
613
|
}
|
|
450
614
|
|
|
451
|
-
//
|
|
615
|
+
// Sidebar action buttons - full-width, left-aligned (extends base .btn-ghost)
|
|
452
616
|
.btn-ghost,
|
|
453
617
|
.bottom-sheet > .btn,
|
|
618
|
+
.thread-tools > .dropdown-toggle {
|
|
619
|
+
text-align: left;
|
|
620
|
+
width: 100%;
|
|
621
|
+
justify-content: flex-start;
|
|
622
|
+
|
|
623
|
+
i {
|
|
624
|
+
width: 16px; // Fixed width for icon alignment
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Non-ghost buttons need base ghost styling too
|
|
629
|
+
.bottom-sheet > .btn,
|
|
454
630
|
.thread-tools > .dropdown-toggle {
|
|
455
631
|
background: transparent !important;
|
|
456
632
|
border: 1px solid transparent !important;
|
|
@@ -459,15 +635,10 @@ body.template-topic {
|
|
|
459
635
|
border-radius: $jv-radius-sm;
|
|
460
636
|
font-size: $jv-font-size-sm;
|
|
461
637
|
font-weight: 500;
|
|
462
|
-
text-align: left;
|
|
463
|
-
width: 100%;
|
|
464
|
-
justify-content: flex-start;
|
|
465
638
|
transition: background-color $jv-transition-fast, color $jv-transition-fast;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
color: $jv-primary;
|
|
470
|
-
}
|
|
639
|
+
display: inline-flex;
|
|
640
|
+
align-items: center;
|
|
641
|
+
gap: $jv-space-2;
|
|
471
642
|
|
|
472
643
|
&:hover {
|
|
473
644
|
background: $jv-hover-bg !important;
|
|
@@ -593,42 +764,310 @@ body.template-topic {
|
|
|
593
764
|
}
|
|
594
765
|
|
|
595
766
|
// ===========================================================
|
|
596
|
-
//
|
|
767
|
+
// PARENT POST CONTEXT (shown when viewing a reply)
|
|
768
|
+
// Compact inline style - visually distinct from main posts
|
|
597
769
|
// ===========================================================
|
|
598
|
-
[component="post/
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
border-left: 2px solid $jv-border-
|
|
770
|
+
[component="post/parent"] {
|
|
771
|
+
background: transparent !important;
|
|
772
|
+
border: none !important;
|
|
773
|
+
border-left: 2px solid $jv-border-strong !important;
|
|
774
|
+
border-radius: 0 !important;
|
|
775
|
+
padding: $jv-space-1 0 $jv-space-1 $jv-space-3 !important;
|
|
776
|
+
margin-bottom: $jv-space-2 !important;
|
|
777
|
+
margin-left: $jv-space-1 !important;
|
|
778
|
+
transition: border-color $jv-transition-fast;
|
|
779
|
+
cursor: pointer;
|
|
780
|
+
display: flex !important;
|
|
781
|
+
align-items: center !important;
|
|
782
|
+
gap: $jv-space-2 !important;
|
|
783
|
+
flex-direction: row !important;
|
|
602
784
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
785
|
+
&:hover {
|
|
786
|
+
border-left-color: $jv-primary !important;
|
|
787
|
+
|
|
788
|
+
.fw-semibold {
|
|
789
|
+
color: $jv-primary !important;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Avatar
|
|
794
|
+
.avatar,
|
|
795
|
+
[component="user/picture"] {
|
|
796
|
+
width: 16px !important;
|
|
797
|
+
height: 16px !important;
|
|
798
|
+
border-radius: 50%;
|
|
799
|
+
flex-shrink: 0;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Username
|
|
803
|
+
.fw-semibold {
|
|
804
|
+
font-size: $jv-font-size-xs !important;
|
|
805
|
+
font-weight: 600;
|
|
806
|
+
color: $jv-text-muted;
|
|
807
|
+
transition: color $jv-transition-fast;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Parent content preview - inline, truncated
|
|
811
|
+
[component="post/parent/content"] {
|
|
812
|
+
font-size: $jv-font-size-xs !important;
|
|
813
|
+
color: $jv-text-soft !important;
|
|
814
|
+
line-height: 1.4;
|
|
815
|
+
white-space: nowrap;
|
|
816
|
+
overflow: hidden;
|
|
817
|
+
text-overflow: ellipsis;
|
|
818
|
+
flex: 1;
|
|
819
|
+
min-width: 0;
|
|
820
|
+
|
|
821
|
+
p {
|
|
822
|
+
margin: 0;
|
|
823
|
+
display: inline;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Hide images in preview
|
|
827
|
+
img {
|
|
828
|
+
display: none;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
608
831
|
|
|
609
|
-
|
|
832
|
+
// Hide the nested flex container structure, flatten it
|
|
833
|
+
> .d-flex {
|
|
834
|
+
display: contents !important;
|
|
610
835
|
}
|
|
611
836
|
}
|
|
612
837
|
|
|
613
838
|
// ===========================================================
|
|
614
|
-
//
|
|
839
|
+
// POST HIGHLIGHT ANIMATION (when navigating to a post)
|
|
840
|
+
// ===========================================================
|
|
841
|
+
.post-highlight-flash {
|
|
842
|
+
animation: postHighlightFlash 1.5s ease-out;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
@keyframes postHighlightFlash {
|
|
846
|
+
0% {
|
|
847
|
+
background-color: rgba($jv-primary, 0.15);
|
|
848
|
+
box-shadow: 0 0 0 4px rgba($jv-primary, 0.1);
|
|
849
|
+
}
|
|
850
|
+
100% {
|
|
851
|
+
background-color: $jv-surface;
|
|
852
|
+
box-shadow: none;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// ===========================================================
|
|
857
|
+
// REPLY COUNT BUTTON (Reddit/Discord style - no border, text link)
|
|
615
858
|
// ===========================================================
|
|
616
859
|
[component="post/reply-count"] {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
860
|
+
display: inline-flex;
|
|
861
|
+
align-items: center;
|
|
862
|
+
gap: $jv-space-2;
|
|
863
|
+
background: transparent !important;
|
|
864
|
+
border: none !important;
|
|
865
|
+
border-radius: $jv-radius-sm !important;
|
|
866
|
+
padding: $jv-space-1 $jv-space-2 !important;
|
|
867
|
+
font-size: $jv-font-size-xs !important;
|
|
868
|
+
color: $jv-text-muted !important;
|
|
621
869
|
cursor: pointer;
|
|
622
|
-
transition:
|
|
870
|
+
transition: all $jv-transition-fast;
|
|
871
|
+
text-decoration: none !important;
|
|
872
|
+
|
|
873
|
+
// Respect the hidden class when there are no replies
|
|
874
|
+
&.hidden {
|
|
875
|
+
display: none !important;
|
|
876
|
+
}
|
|
623
877
|
|
|
624
878
|
&:hover {
|
|
625
|
-
background: $jv-hover-bg;
|
|
626
|
-
color: $jv-
|
|
879
|
+
background: $jv-hover-bg !important;
|
|
880
|
+
color: $jv-primary !important;
|
|
881
|
+
|
|
882
|
+
[component="post/reply-count/text"],
|
|
883
|
+
.replies-count {
|
|
884
|
+
color: $jv-primary !important;
|
|
885
|
+
}
|
|
627
886
|
}
|
|
628
887
|
|
|
629
|
-
|
|
630
|
-
|
|
888
|
+
// Hide avatars - cleaner look like Reddit
|
|
889
|
+
[component="post/reply-count/avatars"] {
|
|
890
|
+
display: none !important;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Reply count text - simple and clean
|
|
894
|
+
.replies-count,
|
|
895
|
+
[component="post/reply-count/text"] {
|
|
631
896
|
font-weight: 600;
|
|
897
|
+
font-size: $jv-font-size-xs;
|
|
898
|
+
color: $jv-text-muted;
|
|
899
|
+
transition: color $jv-transition-fast;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Hide last reply time - too verbose
|
|
903
|
+
.replies-last {
|
|
904
|
+
display: none !important;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Chevron icon
|
|
908
|
+
.fa-chevron-down {
|
|
909
|
+
font-size: 10px;
|
|
910
|
+
color: $jv-text-soft;
|
|
911
|
+
transition: transform $jv-transition-fast;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Expanded state
|
|
915
|
+
&[aria-expanded="true"] {
|
|
916
|
+
color: $jv-primary !important;
|
|
917
|
+
|
|
918
|
+
.fa-chevron-down {
|
|
919
|
+
transform: rotate(180deg);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
[component="post/reply-count/text"],
|
|
923
|
+
.replies-count {
|
|
924
|
+
color: $jv-primary !important;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// ===========================================================
|
|
930
|
+
// NESTED REPLIES CONTAINER (Clean threaded style)
|
|
931
|
+
// Inspired by Reddit/GitHub - compact, minimal
|
|
932
|
+
// ===========================================================
|
|
933
|
+
[component="post/replies/container"] {
|
|
934
|
+
margin-top: $jv-space-3 !important;
|
|
935
|
+
margin-bottom: 0 !important;
|
|
936
|
+
margin-left: $jv-space-4;
|
|
937
|
+
padding: 0 !important;
|
|
938
|
+
background: transparent !important;
|
|
939
|
+
border: none !important;
|
|
940
|
+
border-radius: 0 !important;
|
|
941
|
+
position: relative;
|
|
942
|
+
|
|
943
|
+
// Simple thread line
|
|
944
|
+
&::before {
|
|
945
|
+
content: "";
|
|
946
|
+
position: absolute;
|
|
947
|
+
left: -$jv-space-2;
|
|
948
|
+
top: 0;
|
|
949
|
+
bottom: 0;
|
|
950
|
+
width: 2px;
|
|
951
|
+
background: $jv-border-subtle;
|
|
952
|
+
border-radius: 1px;
|
|
953
|
+
transition: background $jv-transition-fast;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Hover effect on thread line
|
|
957
|
+
&:hover::before {
|
|
958
|
+
background: $jv-border-strong;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Replies list - compact spacing
|
|
962
|
+
[component="post/replies"],
|
|
963
|
+
ul.list-unstyled {
|
|
964
|
+
margin: 0;
|
|
965
|
+
padding: 0;
|
|
966
|
+
display: flex;
|
|
967
|
+
flex-direction: column;
|
|
968
|
+
gap: $jv-space-2;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Individual reply posts - clean, compact
|
|
972
|
+
[component="post"] {
|
|
973
|
+
margin: 0 !important;
|
|
974
|
+
padding: 0 !important;
|
|
975
|
+
background: transparent !important;
|
|
976
|
+
position: relative;
|
|
977
|
+
|
|
978
|
+
// No connector dots
|
|
979
|
+
&::before {
|
|
980
|
+
display: none !important;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Reply container - compact padding
|
|
984
|
+
.post-container-parent {
|
|
985
|
+
background: transparent !important;
|
|
986
|
+
border: none !important;
|
|
987
|
+
border-radius: $jv-radius-sm !important;
|
|
988
|
+
padding: $jv-space-2 $jv-space-3 !important;
|
|
989
|
+
margin-left: 0;
|
|
990
|
+
margin-right: 0;
|
|
991
|
+
gap: $jv-space-2 !important;
|
|
992
|
+
transition: background $jv-transition-fast;
|
|
993
|
+
|
|
994
|
+
&:hover {
|
|
995
|
+
background: $jv-hover-bg !important;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Smaller avatar for replies
|
|
999
|
+
> .bg-body,
|
|
1000
|
+
> div:first-child {
|
|
1001
|
+
.avatar,
|
|
1002
|
+
[component="user/picture"] {
|
|
1003
|
+
width: 20px !important;
|
|
1004
|
+
height: 20px !important;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Post content area
|
|
1010
|
+
.post-container {
|
|
1011
|
+
padding: 0 !important;
|
|
1012
|
+
background: transparent !important;
|
|
1013
|
+
border: none !important;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Reply header - inline, compact
|
|
1017
|
+
.post-header {
|
|
1018
|
+
font-size: $jv-font-size-xs !important;
|
|
1019
|
+
min-height: auto !important;
|
|
1020
|
+
margin-bottom: 0 !important;
|
|
1021
|
+
display: flex;
|
|
1022
|
+
align-items: center;
|
|
1023
|
+
gap: $jv-space-2;
|
|
1024
|
+
|
|
1025
|
+
.fw-bold,
|
|
1026
|
+
a[data-username] {
|
|
1027
|
+
font-size: $jv-font-size-xs !important;
|
|
1028
|
+
font-weight: 600;
|
|
1029
|
+
color: $jv-text-main;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
.text-muted {
|
|
1033
|
+
font-size: $jv-font-size-xs !important;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Hide post index in replies
|
|
1037
|
+
.post-index {
|
|
1038
|
+
display: none !important;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Reply content - tighter
|
|
1043
|
+
[component="post/content"] {
|
|
1044
|
+
font-size: $jv-font-size-sm !important;
|
|
1045
|
+
line-height: $jv-line-height-base;
|
|
1046
|
+
color: $jv-text-main;
|
|
1047
|
+
margin-top: $jv-space-1;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Reply footer - override main post styles (no border, tighter spacing)
|
|
1051
|
+
[component="post/footer"],
|
|
1052
|
+
.post-footer {
|
|
1053
|
+
margin-top: $jv-space-1 !important;
|
|
1054
|
+
border-top: none !important;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Smaller action buttons for compact reply layout (inherits opacity/hover behavior from main rule)
|
|
1058
|
+
[component="post/actions"] {
|
|
1059
|
+
gap: $jv-space-1 !important;
|
|
1060
|
+
|
|
1061
|
+
.btn,
|
|
1062
|
+
.btn-ghost {
|
|
1063
|
+
padding: $jv-space-1 $jv-space-2 !important;
|
|
1064
|
+
font-size: $jv-font-size-xs !important;
|
|
1065
|
+
|
|
1066
|
+
i {
|
|
1067
|
+
font-size: 12px !important;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
632
1071
|
}
|
|
633
1072
|
}
|
|
634
1073
|
}
|
|
@@ -666,7 +1105,7 @@ body.template-topic {
|
|
|
666
1105
|
body.template-topic {
|
|
667
1106
|
.topic-header h1,
|
|
668
1107
|
.topic-header [component="topic/title"] {
|
|
669
|
-
font-size:
|
|
1108
|
+
font-size: $jv-font-size-xl;
|
|
670
1109
|
}
|
|
671
1110
|
|
|
672
1111
|
.quick-reply,
|
|
@@ -674,8 +1113,25 @@ body.template-topic {
|
|
|
674
1113
|
padding: $jv-space-4;
|
|
675
1114
|
}
|
|
676
1115
|
|
|
1116
|
+
// Mobile adjustments for threaded replies
|
|
677
1117
|
[component="post/replies/container"] {
|
|
678
|
-
|
|
1118
|
+
margin-left: $jv-space-3;
|
|
1119
|
+
|
|
1120
|
+
&::before {
|
|
1121
|
+
left: -$jv-space-2;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
[component="post"] .post-container-parent {
|
|
1125
|
+
padding: $jv-space-2 $jv-space-3 !important;
|
|
1126
|
+
margin-left: 0;
|
|
1127
|
+
margin-right: 0;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// Mobile adjustments for parent context
|
|
1132
|
+
[component="post/parent"] {
|
|
1133
|
+
padding: $jv-space-1 0 $jv-space-1 $jv-space-2 !important;
|
|
1134
|
+
margin-left: 0 !important;
|
|
679
1135
|
}
|
|
680
1136
|
}
|
|
681
1137
|
}
|
package/static/lib/theme.js
CHANGED
|
@@ -6,14 +6,194 @@
|
|
|
6
6
|
(function() {
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
+
var carouselCounter = 0;
|
|
10
|
+
|
|
9
11
|
// Theme initialization
|
|
10
12
|
$(document).ready(function() {
|
|
11
13
|
console.log('JAVIS Community Theme initialized');
|
|
12
14
|
|
|
13
15
|
// Ensure sidebar toggle works (reinitialize if Harmony's handler isn't loaded)
|
|
14
16
|
initSidebarToggle();
|
|
17
|
+
|
|
18
|
+
// Initialize image carousels for posts with multiple images
|
|
19
|
+
initPostImageCarousels();
|
|
20
|
+
|
|
21
|
+
// Fix bookmark alert - only show when bookmark is meaningfully ahead
|
|
22
|
+
fixBookmarkAlert();
|
|
23
|
+
|
|
24
|
+
// Initialize parent post click navigation with smooth scroll
|
|
25
|
+
initParentPostNavigation();
|
|
26
|
+
|
|
27
|
+
// Initialize post hover actions (show actions only on directly hovered post)
|
|
28
|
+
initPostHoverActions();
|
|
29
|
+
|
|
30
|
+
// Re-initialize carousels when new posts are loaded (infinite scroll, etc.)
|
|
31
|
+
// Also handle post edits by clearing the processed flag
|
|
32
|
+
$(window).on('action:posts.loaded action:topic.loaded action:ajaxify.end', function() {
|
|
33
|
+
initPostImageCarousels();
|
|
34
|
+
initParentPostNavigation();
|
|
35
|
+
initPostHoverActions();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Handle post edits - need to re-process the edited post
|
|
39
|
+
$(window).on('action:posts.edited', function(ev, data) {
|
|
40
|
+
if (data && data.post && data.post.pid) {
|
|
41
|
+
// Find the edited post and remove the processed flag so it gets re-scanned
|
|
42
|
+
var $post = $('[data-pid="' + data.post.pid + '"]');
|
|
43
|
+
var $content = $post.find('[component="post/content"]');
|
|
44
|
+
$content.removeAttr('data-carousel-processed');
|
|
45
|
+
// Remove any existing carousel in this post
|
|
46
|
+
$content.find('.post-image-carousel').remove();
|
|
47
|
+
// Re-initialize
|
|
48
|
+
initPostImageCarousels();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
15
51
|
});
|
|
16
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Convert multiple images in post content to Bootstrap carousels
|
|
55
|
+
*/
|
|
56
|
+
function initPostImageCarousels() {
|
|
57
|
+
// Find all post content areas that haven't been processed
|
|
58
|
+
$('[component="post/content"]:not([data-carousel-processed])').each(function() {
|
|
59
|
+
var $content = $(this);
|
|
60
|
+
$content.attr('data-carousel-processed', 'true');
|
|
61
|
+
|
|
62
|
+
// Find all images in post content - including those in links (lightbox) and paragraphs
|
|
63
|
+
var $images = $content.find('img').filter(function() {
|
|
64
|
+
var $img = $(this);
|
|
65
|
+
|
|
66
|
+
// Exclude images already in a carousel
|
|
67
|
+
if ($img.closest('.carousel, .post-image-carousel').length) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Exclude emojis and small icons by class
|
|
72
|
+
if ($img.hasClass('emoji') || $img.hasClass('emoji-img') || $img.hasClass('icon') || $img.hasClass('not-responsive')) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check the image source to determine if it's a content image
|
|
77
|
+
var src = $img.attr('src') || '';
|
|
78
|
+
|
|
79
|
+
// Include images from uploads folder or with image extensions
|
|
80
|
+
var isContentImage = src.indexOf('/assets/uploads/') !== -1 ||
|
|
81
|
+
src.indexOf('/files/') !== -1 ||
|
|
82
|
+
src.match(/\.(jpg|jpeg|png|gif|webp)(\?|$)/i);
|
|
83
|
+
|
|
84
|
+
if (!isContentImage) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Exclude tiny images by checking width/height attributes if present
|
|
89
|
+
var width = $img.attr('width');
|
|
90
|
+
var height = $img.attr('height');
|
|
91
|
+
if ((width && parseInt(width, 10) < 50) || (height && parseInt(height, 10) < 50)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return true;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Only create carousel if 2+ images
|
|
99
|
+
if ($images.length >= 2) {
|
|
100
|
+
createCarousel($content, $images);
|
|
101
|
+
console.log('JAVIS: Found ' + $images.length + ' images, creating carousel');
|
|
102
|
+
} else if ($images.length > 0) {
|
|
103
|
+
console.log('JAVIS: Found ' + $images.length + ' image(s), not enough for carousel');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a Bootstrap 5 carousel from a set of images
|
|
110
|
+
*/
|
|
111
|
+
function createCarousel($content, $images) {
|
|
112
|
+
var carouselId = 'post-carousel-' + (++carouselCounter);
|
|
113
|
+
|
|
114
|
+
// Build carousel HTML
|
|
115
|
+
var carouselHtml = '<div id="' + carouselId + '" class="carousel slide post-image-carousel" data-bs-ride="false">';
|
|
116
|
+
|
|
117
|
+
// Indicators (dots)
|
|
118
|
+
carouselHtml += '<div class="carousel-indicators">';
|
|
119
|
+
$images.each(function(index) {
|
|
120
|
+
var activeClass = index === 0 ? 'active' : '';
|
|
121
|
+
var ariaCurrent = index === 0 ? 'aria-current="true"' : '';
|
|
122
|
+
carouselHtml += '<button type="button" data-bs-target="#' + carouselId + '" data-bs-slide-to="' + index + '" class="' + activeClass + '" ' + ariaCurrent + ' aria-label="Slide ' + (index + 1) + '"></button>';
|
|
123
|
+
});
|
|
124
|
+
carouselHtml += '</div>';
|
|
125
|
+
|
|
126
|
+
// Carousel inner (slides)
|
|
127
|
+
carouselHtml += '<div class="carousel-inner">';
|
|
128
|
+
$images.each(function(index) {
|
|
129
|
+
var $img = $(this);
|
|
130
|
+
var src = $img.attr('src');
|
|
131
|
+
var alt = $img.attr('alt') || 'Image ' + (index + 1);
|
|
132
|
+
var activeClass = index === 0 ? 'active' : '';
|
|
133
|
+
carouselHtml += '<div class="carousel-item ' + activeClass + '">';
|
|
134
|
+
carouselHtml += '<img src="' + src + '" class="d-block w-100" alt="' + alt + '" loading="lazy">';
|
|
135
|
+
carouselHtml += '</div>';
|
|
136
|
+
});
|
|
137
|
+
carouselHtml += '</div>';
|
|
138
|
+
|
|
139
|
+
// Navigation arrows
|
|
140
|
+
carouselHtml += '<button class="carousel-control-prev" type="button" data-bs-target="#' + carouselId + '" data-bs-slide="prev">';
|
|
141
|
+
carouselHtml += '<span class="carousel-control-prev-icon" aria-hidden="true"></span>';
|
|
142
|
+
carouselHtml += '<span class="visually-hidden">Previous</span>';
|
|
143
|
+
carouselHtml += '</button>';
|
|
144
|
+
carouselHtml += '<button class="carousel-control-next" type="button" data-bs-target="#' + carouselId + '" data-bs-slide="next">';
|
|
145
|
+
carouselHtml += '<span class="carousel-control-next-icon" aria-hidden="true"></span>';
|
|
146
|
+
carouselHtml += '<span class="visually-hidden">Next</span>';
|
|
147
|
+
carouselHtml += '</button>';
|
|
148
|
+
|
|
149
|
+
carouselHtml += '</div>';
|
|
150
|
+
|
|
151
|
+
// Find the topmost element to insert carousel before
|
|
152
|
+
// Images can be in: <p><img></p>, <p><a><img></a></p>, <a><img></a>, or just <img>
|
|
153
|
+
var $firstImg = $images.first();
|
|
154
|
+
var $insertBefore = $firstImg;
|
|
155
|
+
|
|
156
|
+
// Walk up to find the direct child of $content
|
|
157
|
+
while ($insertBefore.parent().length && !$insertBefore.parent().is($content)) {
|
|
158
|
+
$insertBefore = $insertBefore.parent();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Insert carousel before the first image's container
|
|
162
|
+
$insertBefore.before(carouselHtml);
|
|
163
|
+
|
|
164
|
+
// Collect elements to remove (images and their empty containers)
|
|
165
|
+
var elementsToRemove = [];
|
|
166
|
+
$images.each(function() {
|
|
167
|
+
var $img = $(this);
|
|
168
|
+
var $element = $img;
|
|
169
|
+
|
|
170
|
+
// Walk up to find the direct child of $content
|
|
171
|
+
while ($element.parent().length && !$element.parent().is($content)) {
|
|
172
|
+
$element = $element.parent();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Mark for removal if not already marked
|
|
176
|
+
if (elementsToRemove.indexOf($element[0]) === -1) {
|
|
177
|
+
elementsToRemove.push($element[0]);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Remove the original image containers
|
|
182
|
+
$(elementsToRemove).remove();
|
|
183
|
+
|
|
184
|
+
// Initialize Bootstrap carousel
|
|
185
|
+
var carouselEl = document.getElementById(carouselId);
|
|
186
|
+
if (carouselEl && typeof bootstrap !== 'undefined') {
|
|
187
|
+
new bootstrap.Carousel(carouselEl, {
|
|
188
|
+
interval: false, // Don't auto-slide
|
|
189
|
+
touch: true,
|
|
190
|
+
wrap: true
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log('JAVIS: Created carousel with ' + $images.length + ' images');
|
|
195
|
+
}
|
|
196
|
+
|
|
17
197
|
function initSidebarToggle() {
|
|
18
198
|
// Check if the toggle element exists
|
|
19
199
|
var toggleEl = $('[component="sidebar/toggle"]');
|
|
@@ -51,4 +231,143 @@
|
|
|
51
231
|
console.log('JAVIS: Sidebar toggle initialized');
|
|
52
232
|
}
|
|
53
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Fix the bookmark alert bug - NodeBB shows "Click here to return to last read post"
|
|
236
|
+
* even when the bookmark position isn't meaningfully ahead of the current position.
|
|
237
|
+
*
|
|
238
|
+
* The bug: NodeBB checks if bookmark exists and postcount > threshold, but doesn't
|
|
239
|
+
* check if the bookmark is actually ahead of where the user currently is.
|
|
240
|
+
*
|
|
241
|
+
* This fix removes the bookmark alert if:
|
|
242
|
+
* 1. The bookmark is at position 1 or 2 (meaningless to "return" to the start)
|
|
243
|
+
* 2. The bookmark is at or behind the current post index
|
|
244
|
+
*/
|
|
245
|
+
function fixBookmarkAlert() {
|
|
246
|
+
$(window).on('action:topic.loaded', function() {
|
|
247
|
+
// Small delay to let NodeBB's handleBookmark run first
|
|
248
|
+
setTimeout(function() {
|
|
249
|
+
if (typeof ajaxify === 'undefined' || !ajaxify.data || !ajaxify.data.template || !ajaxify.data.template.topic) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
require(['storage', 'alerts'], function(storage, alerts) {
|
|
254
|
+
var tid = ajaxify.data.tid;
|
|
255
|
+
var bookmark = ajaxify.data.bookmark || storage.getItem('topic:' + tid + ':bookmark');
|
|
256
|
+
var postIndex = ajaxify.data.postIndex || 1;
|
|
257
|
+
var bookmarkInt = parseInt(bookmark, 10) || 0;
|
|
258
|
+
var postIndexInt = parseInt(postIndex, 10) || 1;
|
|
259
|
+
|
|
260
|
+
// Remove bookmark alert if:
|
|
261
|
+
// 1. No meaningful bookmark (position 1 or 2 - essentially the start)
|
|
262
|
+
// 2. Bookmark is at or behind current position (nothing to "return" to)
|
|
263
|
+
// 3. Bookmark is only 1-2 posts ahead (not worth showing notification)
|
|
264
|
+
var shouldRemoveAlert = bookmarkInt <= 2 ||
|
|
265
|
+
bookmarkInt <= postIndexInt ||
|
|
266
|
+
(bookmarkInt - postIndexInt) <= 2;
|
|
267
|
+
|
|
268
|
+
if (shouldRemoveAlert) {
|
|
269
|
+
alerts.remove('bookmark');
|
|
270
|
+
console.log('JAVIS: Removed unnecessary bookmark alert (bookmark: ' + bookmarkInt + ', current: ' + postIndexInt + ')');
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}, 100);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Initialize parent post click navigation with smooth scroll
|
|
279
|
+
* Clicking the "Replying to" component scrolls to the parent post
|
|
280
|
+
*/
|
|
281
|
+
function initParentPostNavigation() {
|
|
282
|
+
// Use event delegation on the topic container
|
|
283
|
+
$('[component="topic"]').off('click.javis-parent').on('click.javis-parent', '[component="post/parent"]', function(e) {
|
|
284
|
+
// Don't navigate if clicking on a link (username, etc.)
|
|
285
|
+
if ($(e.target).closest('a').length) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
e.stopPropagation();
|
|
291
|
+
|
|
292
|
+
var parentPid = $(this).attr('data-parent-pid');
|
|
293
|
+
if (!parentPid) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Find the parent post element
|
|
298
|
+
var $parentPost = $('[component="topic"] > [component="post"][data-pid="' + parentPid + '"]');
|
|
299
|
+
|
|
300
|
+
if ($parentPost.length) {
|
|
301
|
+
// Post is on the current page - smooth scroll to it
|
|
302
|
+
smoothScrollToPost($parentPost);
|
|
303
|
+
} else {
|
|
304
|
+
// Post is on a different page - navigate via URL
|
|
305
|
+
window.location.href = config.relative_path + '/post/' + parentPid;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Smooth scroll to a post element with highlight effect
|
|
312
|
+
*/
|
|
313
|
+
function smoothScrollToPost($postElement) {
|
|
314
|
+
if (!$postElement.length) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Calculate scroll position (with offset for header)
|
|
319
|
+
var headerHeight = $('header').outerHeight() || 60;
|
|
320
|
+
var scrollTop = $postElement.offset().top - headerHeight - 20;
|
|
321
|
+
|
|
322
|
+
// Smooth scroll
|
|
323
|
+
$('html, body').animate({
|
|
324
|
+
scrollTop: scrollTop
|
|
325
|
+
}, 400, 'swing', function() {
|
|
326
|
+
// Add highlight effect after scroll completes
|
|
327
|
+
highlightPost($postElement);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Briefly highlight a post to draw attention
|
|
333
|
+
*/
|
|
334
|
+
function highlightPost($postElement) {
|
|
335
|
+
var $container = $postElement.find('.post-container-parent');
|
|
336
|
+
if (!$container.length) {
|
|
337
|
+
$container = $postElement;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Add highlight class
|
|
341
|
+
$container.addClass('post-highlight-flash');
|
|
342
|
+
|
|
343
|
+
// Remove after animation
|
|
344
|
+
setTimeout(function() {
|
|
345
|
+
$container.removeClass('post-highlight-flash');
|
|
346
|
+
}, 1500);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Initialize post hover actions
|
|
351
|
+
* Adds 'post-hovered' class only to the directly hovered post container
|
|
352
|
+
* This prevents CSS hover from bubbling up to parent posts
|
|
353
|
+
*/
|
|
354
|
+
function initPostHoverActions() {
|
|
355
|
+
// Use event delegation for all post containers
|
|
356
|
+
$(document).off('mouseenter.javis-hover mouseleave.javis-hover', '.post-container-parent');
|
|
357
|
+
|
|
358
|
+
$(document).on('mouseenter.javis-hover', '.post-container-parent', function(e) {
|
|
359
|
+
// Stop propagation to prevent parent containers from getting the event
|
|
360
|
+
e.stopPropagation();
|
|
361
|
+
// Remove class from all other containers first
|
|
362
|
+
$('.post-container-parent.post-hovered').removeClass('post-hovered');
|
|
363
|
+
// Add class only to this specific container
|
|
364
|
+
$(this).addClass('post-hovered');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
$(document).on('mouseleave.javis-hover', '.post-container-parent', function(e) {
|
|
368
|
+
e.stopPropagation();
|
|
369
|
+
$(this).removeClass('post-hovered');
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
54
373
|
})();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<div component="post/parent" data-parent-pid="{./parent.pid}" data-uid="{./parent.uid}" class="btn btn-ghost btn-sm d-flex gap-2 text-start flex-row mb-2 post-parent-clickable" style="font-size: 13px;">
|
|
2
|
+
<div class="d-flex gap-2 text-nowrap">
|
|
3
|
+
<div class="d-flex flex-nowrap gap-1 align-items-center">
|
|
4
|
+
<a href="{config.relative_path}/user/{./parent.user.userslug}" class="text-decoration-none lh-1" onclick="event.stopPropagation();">{buildAvatar(./parent.user, "16px", true, "not-responsive align-middle")}</a>
|
|
5
|
+
<a class="fw-semibold text-truncate" style="max-width: 150px;" href="{config.relative_path}/user/{./parent.user.userslug}" onclick="event.stopPropagation();">{./parent.user.displayname}</a>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
<div component="post/parent/content" class="text-muted line-clamp-1 text-break w-100">{./parent.content}</div>
|
|
9
|
+
</div>
|