@malto/sdk 0.1.1 → 0.1.3
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/dist/index.cjs +498 -173
- package/dist/index.d.cts +25 -5
- package/dist/index.d.ts +25 -5
- package/dist/index.js +498 -173
- package/dist/malto.umd.js +252 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -176,10 +176,12 @@ function upsert(id, css) {
|
|
|
176
176
|
function buildCss(opts) {
|
|
177
177
|
const primary = opts.primary;
|
|
178
178
|
const tones = derivePalette(primary);
|
|
179
|
-
const radiusScale = opts.radius === "sm" ? "
|
|
180
|
-
const appearance = opts.appearance ?? "
|
|
179
|
+
const radiusScale = opts.radius === "sm" ? "12px" : opts.radius === "lg" ? "24px" : "20px";
|
|
180
|
+
const appearance = opts.appearance ?? "light";
|
|
181
181
|
const overrides = opts.cssVars ?? {};
|
|
182
182
|
return `
|
|
183
|
+
@import url('https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&display=swap');
|
|
184
|
+
|
|
183
185
|
:where(.malto-root, .malto-root *) {
|
|
184
186
|
box-sizing: border-box;
|
|
185
187
|
}
|
|
@@ -192,21 +194,21 @@ function buildCss(opts) {
|
|
|
192
194
|
--malto-primary-600: ${tones.shade600};
|
|
193
195
|
--malto-primary-rgb: ${tones.rgb};
|
|
194
196
|
--malto-surface: ${overrides.surface ?? "#ffffff"};
|
|
195
|
-
--malto-surface-muted: ${overrides.surfaceMuted ?? "#
|
|
196
|
-
--malto-surface-elevated: rgba(255,255,255,0.
|
|
197
|
-
--malto-border: ${overrides.border ?? "
|
|
198
|
-
--malto-border-strong:
|
|
199
|
-
--malto-text: ${overrides.text ?? "#
|
|
197
|
+
--malto-surface-muted: ${overrides.surfaceMuted ?? "#f2eeff"};
|
|
198
|
+
--malto-surface-elevated: rgba(255,255,255,0.92);
|
|
199
|
+
--malto-border: ${overrides.border ?? "#e2e8f0"};
|
|
200
|
+
--malto-border-strong: #cbd1de;
|
|
201
|
+
--malto-text: ${overrides.text ?? "#0a0a0a"};
|
|
200
202
|
--malto-text-muted: ${overrides.textMuted ?? "#5b5871"};
|
|
201
|
-
--malto-text-subtle: rgba(
|
|
203
|
+
--malto-text-subtle: rgba(10, 10, 10, 0.5);
|
|
202
204
|
--malto-radius: ${overrides.radius ?? radiusScale};
|
|
203
|
-
--malto-radius-sm:
|
|
205
|
+
--malto-radius-sm: 12px;
|
|
204
206
|
--malto-radius-pill: 999px;
|
|
205
|
-
--malto-shadow-sm: ${overrides.shadow ?? "0 4px 12px -
|
|
206
|
-
--malto-shadow-md: 0
|
|
207
|
-
--malto-shadow-lg: 0 30px 60px -20px rgba(
|
|
208
|
-
--malto-shadow-glow: 0
|
|
209
|
-
--malto-font: ${overrides.font ?? "
|
|
207
|
+
--malto-shadow-sm: ${overrides.shadow ?? "0 4px 12px -6px rgba(25, 24, 56, 0.12)"};
|
|
208
|
+
--malto-shadow-md: 0 10px 30px -10px rgba(25, 24, 56, 0.15);
|
|
209
|
+
--malto-shadow-lg: 0 30px 60px -20px rgba(25, 24, 56, 0.25);
|
|
210
|
+
--malto-shadow-glow: 0 8px 20px -6px rgba(var(--malto-primary-rgb), 0.45);
|
|
211
|
+
--malto-font: ${overrides.font ?? "'Google Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif"};
|
|
210
212
|
--malto-ease-spring: cubic-bezier(0.34, 1.4, 0.5, 1);
|
|
211
213
|
--malto-ease-smooth: cubic-bezier(0.32, 0.72, 0, 1);
|
|
212
214
|
}
|
|
@@ -267,12 +269,12 @@ function buildCss(opts) {
|
|
|
267
269
|
|
|
268
270
|
.malto-trigger {
|
|
269
271
|
display: inline-flex; align-items: center; gap: 8px;
|
|
270
|
-
padding:
|
|
272
|
+
padding: 10px 20px;
|
|
271
273
|
border-radius: var(--malto-radius-pill);
|
|
272
274
|
background: var(--malto-primary);
|
|
273
275
|
color: var(--malto-primary-contrast);
|
|
274
276
|
border: none; cursor: pointer;
|
|
275
|
-
font-size:
|
|
277
|
+
font-size: 14px; font-weight: 600; letter-spacing: -0.01em;
|
|
276
278
|
box-shadow: var(--malto-shadow-glow);
|
|
277
279
|
transition: transform .25s var(--malto-ease-spring), box-shadow .2s ease;
|
|
278
280
|
}
|
|
@@ -298,7 +300,8 @@ function buildCss(opts) {
|
|
|
298
300
|
.malto-modal {
|
|
299
301
|
background: var(--malto-surface);
|
|
300
302
|
width: 100%;
|
|
301
|
-
max-width:
|
|
303
|
+
max-width: 1152px;
|
|
304
|
+
height: 720px;
|
|
302
305
|
max-height: 92vh;
|
|
303
306
|
border-radius: var(--malto-radius) var(--malto-radius) 0 0;
|
|
304
307
|
box-shadow: var(--malto-shadow-lg);
|
|
@@ -326,23 +329,23 @@ function buildCss(opts) {
|
|
|
326
329
|
/* \u2500\u2500\u2500\u2500\u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
327
330
|
.malto-header {
|
|
328
331
|
display: flex; align-items: center; justify-content: space-between;
|
|
329
|
-
padding:
|
|
332
|
+
padding: 22px 24px 16px;
|
|
330
333
|
position: relative;
|
|
331
334
|
flex: none;
|
|
332
335
|
}
|
|
333
336
|
.malto-header h3 {
|
|
334
337
|
margin: 0;
|
|
335
|
-
font-size:
|
|
338
|
+
font-size: 17px; font-weight: 600;
|
|
336
339
|
color: var(--malto-text);
|
|
337
|
-
letter-spacing: -0.
|
|
340
|
+
letter-spacing: -0.02em;
|
|
338
341
|
}
|
|
339
342
|
.malto-header-meta {
|
|
340
343
|
display: flex; align-items: center; gap: 10px;
|
|
341
344
|
}
|
|
342
345
|
.malto-board-logo {
|
|
343
346
|
width: 28px; height: 28px;
|
|
344
|
-
border-radius:
|
|
345
|
-
background: var(--malto-
|
|
347
|
+
border-radius: 10px;
|
|
348
|
+
background: var(--malto-surface-muted);
|
|
346
349
|
display: flex; align-items: center; justify-content: center;
|
|
347
350
|
color: var(--malto-primary);
|
|
348
351
|
font-size: 12px; font-weight: 700;
|
|
@@ -360,7 +363,7 @@ function buildCss(opts) {
|
|
|
360
363
|
transition: background .15s ease, color .15s ease;
|
|
361
364
|
}
|
|
362
365
|
.malto-close:hover {
|
|
363
|
-
background: var(--malto-
|
|
366
|
+
background: var(--malto-surface-muted);
|
|
364
367
|
color: var(--malto-text);
|
|
365
368
|
}
|
|
366
369
|
|
|
@@ -368,8 +371,8 @@ function buildCss(opts) {
|
|
|
368
371
|
.malto-tabs {
|
|
369
372
|
display: flex;
|
|
370
373
|
padding: 4px;
|
|
371
|
-
margin: 0
|
|
372
|
-
background: var(--malto-
|
|
374
|
+
margin: 0 24px 16px;
|
|
375
|
+
background: var(--malto-surface-muted);
|
|
373
376
|
border-radius: var(--malto-radius-sm);
|
|
374
377
|
flex: none;
|
|
375
378
|
}
|
|
@@ -395,7 +398,7 @@ function buildCss(opts) {
|
|
|
395
398
|
.malto-body {
|
|
396
399
|
flex: 1;
|
|
397
400
|
overflow-y: auto;
|
|
398
|
-
padding: 4px
|
|
401
|
+
padding: 4px 24px 22px;
|
|
399
402
|
background: var(--malto-surface);
|
|
400
403
|
scroll-behavior: smooth;
|
|
401
404
|
}
|
|
@@ -410,7 +413,7 @@ function buildCss(opts) {
|
|
|
410
413
|
/* \u2500\u2500\u2500\u2500\u2500\u2500 Feedback row \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
411
414
|
.malto-item {
|
|
412
415
|
display: flex; gap: 12px;
|
|
413
|
-
padding:
|
|
416
|
+
padding: 14px;
|
|
414
417
|
border-radius: 12px;
|
|
415
418
|
background: var(--malto-surface);
|
|
416
419
|
border: 1px solid var(--malto-border);
|
|
@@ -421,15 +424,15 @@ function buildCss(opts) {
|
|
|
421
424
|
.malto-item:hover {
|
|
422
425
|
border-color: var(--malto-border-strong);
|
|
423
426
|
transform: translateY(-1px);
|
|
424
|
-
box-shadow:
|
|
427
|
+
box-shadow: 0 6px 16px -8px rgba(25, 24, 56, 0.12);
|
|
425
428
|
}
|
|
426
429
|
.malto-vote {
|
|
427
430
|
display: flex; flex-direction: column;
|
|
428
431
|
align-items: center; justify-content: center;
|
|
429
432
|
min-width: 44px; padding: 6px 4px;
|
|
430
|
-
border-radius:
|
|
433
|
+
border-radius: 12px;
|
|
431
434
|
border: 1px solid var(--malto-border);
|
|
432
|
-
background: var(--malto-surface
|
|
435
|
+
background: var(--malto-surface);
|
|
433
436
|
cursor: pointer;
|
|
434
437
|
font-size: 11px; color: var(--malto-text-muted);
|
|
435
438
|
transition: all .2s var(--malto-ease-spring);
|
|
@@ -516,10 +519,10 @@ function buildCss(opts) {
|
|
|
516
519
|
}
|
|
517
520
|
.malto-input, .malto-textarea {
|
|
518
521
|
width: 100%;
|
|
519
|
-
padding:
|
|
520
|
-
border: 1px solid var(--malto-border
|
|
522
|
+
padding: 12px 14px;
|
|
523
|
+
border: 1px solid var(--malto-border);
|
|
521
524
|
border-radius: 12px;
|
|
522
|
-
font-size:
|
|
525
|
+
font-size: 14px;
|
|
523
526
|
color: var(--malto-text);
|
|
524
527
|
background: var(--malto-surface);
|
|
525
528
|
outline: none;
|
|
@@ -531,7 +534,7 @@ function buildCss(opts) {
|
|
|
531
534
|
}
|
|
532
535
|
.malto-input:focus, .malto-textarea:focus {
|
|
533
536
|
border-color: var(--malto-primary);
|
|
534
|
-
box-shadow: 0 0 0 4px rgba(var(--malto-primary-rgb), 0.
|
|
537
|
+
box-shadow: 0 0 0 4px rgba(var(--malto-primary-rgb), 0.12);
|
|
535
538
|
}
|
|
536
539
|
.malto-textarea {
|
|
537
540
|
min-height: 110px;
|
|
@@ -539,13 +542,13 @@ function buildCss(opts) {
|
|
|
539
542
|
line-height: 1.5;
|
|
540
543
|
}
|
|
541
544
|
.malto-button {
|
|
542
|
-
padding:
|
|
545
|
+
padding: 12px 20px;
|
|
543
546
|
border-radius: 12px;
|
|
544
547
|
background: var(--malto-primary);
|
|
545
548
|
color: var(--malto-primary-contrast);
|
|
546
549
|
border: none; cursor: pointer;
|
|
547
|
-
font-size:
|
|
548
|
-
letter-spacing: -0.
|
|
550
|
+
font-size: 14px; font-weight: 600;
|
|
551
|
+
letter-spacing: -0.01em;
|
|
549
552
|
box-shadow: var(--malto-shadow-glow);
|
|
550
553
|
transition: transform .2s var(--malto-ease-spring), box-shadow .2s ease, opacity .2s ease;
|
|
551
554
|
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
@@ -554,13 +557,13 @@ function buildCss(opts) {
|
|
|
554
557
|
.malto-button:active { transform: scale(0.97); }
|
|
555
558
|
.malto-button[disabled] { opacity: .5; cursor: not-allowed; transform: none; }
|
|
556
559
|
.malto-secondary {
|
|
557
|
-
padding:
|
|
558
|
-
border-radius:
|
|
560
|
+
padding: 10px 16px;
|
|
561
|
+
border-radius: 12px;
|
|
559
562
|
background: transparent;
|
|
560
563
|
color: var(--malto-text);
|
|
561
|
-
border: 1px solid var(--malto-border
|
|
564
|
+
border: 1px solid var(--malto-border);
|
|
562
565
|
cursor: pointer;
|
|
563
|
-
font-size:
|
|
566
|
+
font-size: 13.5px; font-weight: 500;
|
|
564
567
|
transition: all .2s ease;
|
|
565
568
|
}
|
|
566
569
|
.malto-secondary:hover {
|
|
@@ -653,7 +656,7 @@ function buildCss(opts) {
|
|
|
653
656
|
|
|
654
657
|
/* \u2500\u2500\u2500\u2500\u2500\u2500 Releases / changelog \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
655
658
|
.malto-release {
|
|
656
|
-
padding:
|
|
659
|
+
padding: 16px;
|
|
657
660
|
border: 1px solid var(--malto-border);
|
|
658
661
|
border-radius: 12px;
|
|
659
662
|
margin-bottom: 8px;
|
|
@@ -723,10 +726,10 @@ function buildCss(opts) {
|
|
|
723
726
|
/* \u2500\u2500\u2500\u2500\u2500\u2500 Footer banner (sign-in CTA) \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
724
727
|
.malto-footer-banner {
|
|
725
728
|
flex: none;
|
|
726
|
-
padding:
|
|
729
|
+
padding: 14px 24px;
|
|
727
730
|
background: var(--malto-surface-muted);
|
|
728
731
|
border-top: 1px solid var(--malto-border);
|
|
729
|
-
font-size:
|
|
732
|
+
font-size: 12.5px;
|
|
730
733
|
color: var(--malto-text-muted);
|
|
731
734
|
display: flex; align-items: center; justify-content: space-between;
|
|
732
735
|
gap: 10px;
|
|
@@ -746,7 +749,207 @@ function buildCss(opts) {
|
|
|
746
749
|
}
|
|
747
750
|
.malto-back:hover {
|
|
748
751
|
color: var(--malto-text);
|
|
749
|
-
background: var(--malto-
|
|
752
|
+
background: var(--malto-surface-muted);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/* \u2500\u2500\u2500\u2500\u2500\u2500 Kanban (roadmap) \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
756
|
+
.malto-kanban {
|
|
757
|
+
display: grid;
|
|
758
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
759
|
+
gap: 16px;
|
|
760
|
+
align-items: start;
|
|
761
|
+
}
|
|
762
|
+
@media (max-width: 960px) {
|
|
763
|
+
.malto-kanban { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
764
|
+
}
|
|
765
|
+
@media (max-width: 560px) {
|
|
766
|
+
.malto-kanban { grid-template-columns: 1fr; }
|
|
767
|
+
}
|
|
768
|
+
.malto-kanban-col {
|
|
769
|
+
display: flex; flex-direction: column;
|
|
770
|
+
gap: 12px;
|
|
771
|
+
min-width: 0;
|
|
772
|
+
}
|
|
773
|
+
.malto-kanban-head {
|
|
774
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
775
|
+
padding: 0 4px;
|
|
776
|
+
}
|
|
777
|
+
.malto-kanban-title {
|
|
778
|
+
margin: 0;
|
|
779
|
+
font-size: 11px;
|
|
780
|
+
font-weight: 600;
|
|
781
|
+
text-transform: uppercase;
|
|
782
|
+
letter-spacing: 0.06em;
|
|
783
|
+
color: var(--malto-text-muted);
|
|
784
|
+
}
|
|
785
|
+
.malto-kanban-count {
|
|
786
|
+
font-size: 11px;
|
|
787
|
+
font-weight: 700;
|
|
788
|
+
color: var(--malto-text-subtle);
|
|
789
|
+
letter-spacing: 0.04em;
|
|
790
|
+
}
|
|
791
|
+
.malto-kanban-list {
|
|
792
|
+
display: flex; flex-direction: column;
|
|
793
|
+
gap: 12px;
|
|
794
|
+
}
|
|
795
|
+
.malto-kanban-empty {
|
|
796
|
+
padding: 14px 12px;
|
|
797
|
+
font-size: 12px;
|
|
798
|
+
color: var(--malto-text-subtle);
|
|
799
|
+
border: 1px dashed var(--malto-border);
|
|
800
|
+
border-radius: 12px;
|
|
801
|
+
text-align: center;
|
|
802
|
+
background: var(--malto-surface-muted);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* Card \u2014 mirrors malto-web FeedbackCard */
|
|
806
|
+
.malto-card {
|
|
807
|
+
display: block;
|
|
808
|
+
text-decoration: none;
|
|
809
|
+
color: inherit;
|
|
810
|
+
position: relative;
|
|
811
|
+
padding: 16px;
|
|
812
|
+
background: var(--malto-surface);
|
|
813
|
+
border: 1px solid rgba(200, 196, 216, 0.18);
|
|
814
|
+
border-radius: 12px;
|
|
815
|
+
box-shadow: 0 0 40px rgba(var(--malto-primary-rgb), 0.06),
|
|
816
|
+
0 0 80px rgba(var(--malto-primary-rgb), 0.02);
|
|
817
|
+
transition: transform .2s var(--malto-ease-spring),
|
|
818
|
+
box-shadow .2s ease, border-color .2s ease;
|
|
819
|
+
cursor: pointer;
|
|
820
|
+
}
|
|
821
|
+
.malto-card:hover {
|
|
822
|
+
transform: translateY(-2px);
|
|
823
|
+
box-shadow: 0 0 50px rgba(var(--malto-primary-rgb), 0.14),
|
|
824
|
+
0 0 90px rgba(var(--malto-primary-rgb), 0.04);
|
|
825
|
+
border-color: rgba(var(--malto-primary-rgb), 0.2);
|
|
826
|
+
}
|
|
827
|
+
.malto-card-top {
|
|
828
|
+
display: flex; align-items: flex-start; justify-content: space-between;
|
|
829
|
+
gap: 8px;
|
|
830
|
+
}
|
|
831
|
+
.malto-card-cat {
|
|
832
|
+
display: inline-flex; align-items: center;
|
|
833
|
+
padding: 3px 9px;
|
|
834
|
+
border-radius: 999px;
|
|
835
|
+
background: rgba(var(--malto-primary-rgb), 0.1);
|
|
836
|
+
color: var(--malto-primary-600);
|
|
837
|
+
font-size: 10px;
|
|
838
|
+
font-weight: 600;
|
|
839
|
+
text-transform: uppercase;
|
|
840
|
+
letter-spacing: 0.05em;
|
|
841
|
+
line-height: 1.4;
|
|
842
|
+
}
|
|
843
|
+
.malto-card-votes {
|
|
844
|
+
display: inline-flex; flex-direction: column;
|
|
845
|
+
align-items: center;
|
|
846
|
+
padding: 4px 8px;
|
|
847
|
+
border: 1px solid var(--malto-border);
|
|
848
|
+
border-radius: 8px;
|
|
849
|
+
background: var(--malto-surface);
|
|
850
|
+
color: var(--malto-text);
|
|
851
|
+
font-size: 12px;
|
|
852
|
+
font-weight: 700;
|
|
853
|
+
line-height: 1;
|
|
854
|
+
transition: border-color .2s ease, background .2s ease;
|
|
855
|
+
}
|
|
856
|
+
.malto-card:hover .malto-card-votes {
|
|
857
|
+
border-color: rgba(var(--malto-primary-rgb), 0.3);
|
|
858
|
+
background: rgba(var(--malto-primary-rgb), 0.05);
|
|
859
|
+
}
|
|
860
|
+
.malto-card-votes svg {
|
|
861
|
+
color: var(--malto-text-muted);
|
|
862
|
+
margin-bottom: 2px;
|
|
863
|
+
}
|
|
864
|
+
.malto-card-title {
|
|
865
|
+
margin: 12px 0 0;
|
|
866
|
+
font-size: 14.5px;
|
|
867
|
+
font-weight: 600;
|
|
868
|
+
letter-spacing: -0.01em;
|
|
869
|
+
line-height: 1.35;
|
|
870
|
+
color: var(--malto-text);
|
|
871
|
+
display: -webkit-box;
|
|
872
|
+
-webkit-line-clamp: 2;
|
|
873
|
+
-webkit-box-orient: vertical;
|
|
874
|
+
overflow: hidden;
|
|
875
|
+
}
|
|
876
|
+
.malto-card-desc {
|
|
877
|
+
margin: 6px 0 0;
|
|
878
|
+
font-size: 12.5px;
|
|
879
|
+
line-height: 1.45;
|
|
880
|
+
color: var(--malto-text-muted);
|
|
881
|
+
display: -webkit-box;
|
|
882
|
+
-webkit-line-clamp: 2;
|
|
883
|
+
-webkit-box-orient: vertical;
|
|
884
|
+
overflow: hidden;
|
|
885
|
+
}
|
|
886
|
+
.malto-card-foot {
|
|
887
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
888
|
+
gap: 8px;
|
|
889
|
+
margin-top: 14px;
|
|
890
|
+
padding-top: 12px;
|
|
891
|
+
border-top: 1px solid rgba(var(--malto-primary-rgb), 0.08);
|
|
892
|
+
font-size: 11px;
|
|
893
|
+
color: var(--malto-text-subtle);
|
|
894
|
+
}
|
|
895
|
+
.malto-card-who {
|
|
896
|
+
display: flex; align-items: center; gap: 6px;
|
|
897
|
+
min-width: 0;
|
|
898
|
+
flex: 1;
|
|
899
|
+
}
|
|
900
|
+
.malto-card-avatar {
|
|
901
|
+
flex: none;
|
|
902
|
+
width: 20px; height: 20px;
|
|
903
|
+
border-radius: 999px;
|
|
904
|
+
display: flex; align-items: center; justify-content: center;
|
|
905
|
+
color: #ffffff;
|
|
906
|
+
font-size: 9px;
|
|
907
|
+
font-weight: 700;
|
|
908
|
+
overflow: hidden;
|
|
909
|
+
}
|
|
910
|
+
.malto-card-avatar img {
|
|
911
|
+
width: 100%; height: 100%;
|
|
912
|
+
object-fit: cover;
|
|
913
|
+
}
|
|
914
|
+
.malto-avatar-bg-0 { background: #5f4ff8; }
|
|
915
|
+
.malto-avatar-bg-1 { background: #ec4899; }
|
|
916
|
+
.malto-avatar-bg-2 { background: #0ea5e9; }
|
|
917
|
+
.malto-avatar-bg-3 { background: #10b981; }
|
|
918
|
+
.malto-avatar-bg-4 { background: #f59e0b; }
|
|
919
|
+
.malto-avatar-bg-5 { background: #7e2eaa; }
|
|
920
|
+
.malto-avatar-bg-6 { background: #ef4444; }
|
|
921
|
+
.malto-avatar-bg-7 { background: #06b6d4; }
|
|
922
|
+
.malto-card-author {
|
|
923
|
+
font-size: 11px;
|
|
924
|
+
font-weight: 500;
|
|
925
|
+
color: var(--malto-text-muted);
|
|
926
|
+
min-width: 0;
|
|
927
|
+
overflow: hidden;
|
|
928
|
+
text-overflow: ellipsis;
|
|
929
|
+
white-space: nowrap;
|
|
930
|
+
}
|
|
931
|
+
.malto-card-dot {
|
|
932
|
+
color: var(--malto-border-strong);
|
|
933
|
+
flex: none;
|
|
934
|
+
}
|
|
935
|
+
.malto-card-time {
|
|
936
|
+
color: var(--malto-text-muted);
|
|
937
|
+
flex: none;
|
|
938
|
+
}
|
|
939
|
+
.malto-card-comments {
|
|
940
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
941
|
+
color: var(--malto-text-muted);
|
|
942
|
+
font-weight: 500;
|
|
943
|
+
flex: none;
|
|
944
|
+
}
|
|
945
|
+
.malto-card-skeleton {
|
|
946
|
+
cursor: default;
|
|
947
|
+
box-shadow: none;
|
|
948
|
+
}
|
|
949
|
+
.malto-card-skeleton:hover {
|
|
950
|
+
transform: none;
|
|
951
|
+
box-shadow: none;
|
|
952
|
+
border-color: rgba(200, 196, 216, 0.18);
|
|
750
953
|
}
|
|
751
954
|
|
|
752
955
|
/* \u2500\u2500\u2500\u2500\u2500\u2500 Animations \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -838,6 +1041,11 @@ var MaltoWidget = class {
|
|
|
838
1041
|
this.container = null;
|
|
839
1042
|
this.trigger = null;
|
|
840
1043
|
this.board = null;
|
|
1044
|
+
this.shellEl = null;
|
|
1045
|
+
this.bodyEl = null;
|
|
1046
|
+
this.tabRefs = {};
|
|
1047
|
+
this.bannerEl = null;
|
|
1048
|
+
this.initialLoaded = false;
|
|
841
1049
|
if (!config.apiKey) throw new Error("Malto: apiKey is required");
|
|
842
1050
|
this.apiKeyPrefix = config.apiKey.slice(0, 16);
|
|
843
1051
|
this.config = {
|
|
@@ -850,7 +1058,7 @@ var MaltoWidget = class {
|
|
|
850
1058
|
cssVars: config.cssVars,
|
|
851
1059
|
customCss: config.customCss,
|
|
852
1060
|
radius: config.radius ?? "md",
|
|
853
|
-
appearance: config.appearance ?? "
|
|
1061
|
+
appearance: config.appearance ?? "light",
|
|
854
1062
|
zIndex: config.zIndex ?? 2147483600,
|
|
855
1063
|
views: config.views,
|
|
856
1064
|
identify: config.identify,
|
|
@@ -861,10 +1069,9 @@ var MaltoWidget = class {
|
|
|
861
1069
|
const session = readSession(this.apiKeyPrefix);
|
|
862
1070
|
this.state = {
|
|
863
1071
|
open: false,
|
|
864
|
-
view: "
|
|
865
|
-
loading:
|
|
1072
|
+
view: "roadmap",
|
|
1073
|
+
loading: true,
|
|
866
1074
|
error: null,
|
|
867
|
-
feedbacks: [],
|
|
868
1075
|
roadmap: {},
|
|
869
1076
|
releases: [],
|
|
870
1077
|
selectedFeedback: null,
|
|
@@ -897,17 +1104,28 @@ var MaltoWidget = class {
|
|
|
897
1104
|
await this.runIdentify(this.config.identify);
|
|
898
1105
|
}
|
|
899
1106
|
this.renderHost(this.resolvedMode);
|
|
1107
|
+
this.ensureInitialLoad();
|
|
900
1108
|
this.config.onReady?.();
|
|
901
1109
|
} catch (err) {
|
|
902
1110
|
this.config.onError?.(err);
|
|
903
1111
|
}
|
|
904
1112
|
}
|
|
1113
|
+
ensureInitialLoad() {
|
|
1114
|
+
if (this.initialLoaded) return;
|
|
1115
|
+
this.initialLoaded = true;
|
|
1116
|
+
if (this.state.view === "roadmap") void this.loadRoadmap();
|
|
1117
|
+
else if (this.state.view === "changelog") void this.loadReleases();
|
|
1118
|
+
}
|
|
905
1119
|
unmount() {
|
|
906
1120
|
this.root?.parentElement?.removeChild(this.root);
|
|
907
1121
|
this.trigger?.parentElement?.removeChild(this.trigger);
|
|
908
1122
|
this.root = null;
|
|
909
1123
|
this.trigger = null;
|
|
910
1124
|
this.container = null;
|
|
1125
|
+
this.shellEl = null;
|
|
1126
|
+
this.bodyEl = null;
|
|
1127
|
+
this.tabRefs = {};
|
|
1128
|
+
this.bannerEl = null;
|
|
911
1129
|
}
|
|
912
1130
|
open() {
|
|
913
1131
|
this.state.open = true;
|
|
@@ -1016,24 +1234,39 @@ var MaltoWidget = class {
|
|
|
1016
1234
|
if (!this.root) return;
|
|
1017
1235
|
if (this.resolvedMode === "inline") {
|
|
1018
1236
|
if (!this.container) return;
|
|
1019
|
-
this.
|
|
1020
|
-
|
|
1237
|
+
if (!this.shellEl || this.shellEl.parentElement !== this.container) {
|
|
1238
|
+
this.container.innerHTML = "";
|
|
1239
|
+
this.shellEl = this.buildShell();
|
|
1240
|
+
this.container.appendChild(this.shellEl);
|
|
1241
|
+
}
|
|
1242
|
+
this.refreshShell();
|
|
1021
1243
|
return;
|
|
1022
1244
|
}
|
|
1023
|
-
this.
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1245
|
+
if (!this.state.open) {
|
|
1246
|
+
this.root.innerHTML = "";
|
|
1247
|
+
this.shellEl = null;
|
|
1248
|
+
this.bodyEl = null;
|
|
1249
|
+
this.tabRefs = {};
|
|
1250
|
+
this.bannerEl = null;
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (!this.shellEl || this.shellEl.parentElement === null) {
|
|
1254
|
+
this.root.innerHTML = "";
|
|
1255
|
+
const overlay = document.createElement("div");
|
|
1256
|
+
overlay.className = "malto-overlay";
|
|
1257
|
+
overlay.addEventListener("click", (e) => {
|
|
1258
|
+
if (e.target === overlay) this.close();
|
|
1259
|
+
});
|
|
1260
|
+
const modal = document.createElement("div");
|
|
1261
|
+
modal.className = "malto-modal";
|
|
1262
|
+
this.shellEl = this.buildShell();
|
|
1263
|
+
modal.appendChild(this.shellEl);
|
|
1264
|
+
overlay.appendChild(modal);
|
|
1265
|
+
this.root.appendChild(overlay);
|
|
1266
|
+
}
|
|
1267
|
+
this.refreshShell();
|
|
1035
1268
|
}
|
|
1036
|
-
|
|
1269
|
+
buildShell() {
|
|
1037
1270
|
const wrap = document.createElement("div");
|
|
1038
1271
|
wrap.style.display = "flex";
|
|
1039
1272
|
wrap.style.flexDirection = "column";
|
|
@@ -1043,23 +1276,39 @@ var MaltoWidget = class {
|
|
|
1043
1276
|
wrap.appendChild(this.buildTabs());
|
|
1044
1277
|
const body = document.createElement("div");
|
|
1045
1278
|
body.className = "malto-body";
|
|
1046
|
-
|
|
1279
|
+
this.bodyEl = body;
|
|
1047
1280
|
wrap.appendChild(body);
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1281
|
+
const banner = document.createElement("div");
|
|
1282
|
+
banner.className = "malto-footer-banner";
|
|
1283
|
+
banner.style.display = "none";
|
|
1284
|
+
const txt = document.createElement("span");
|
|
1285
|
+
txt.innerHTML = `Sign in to vote, comment, or submit ideas.`;
|
|
1286
|
+
const link = document.createElement("button");
|
|
1287
|
+
link.className = "malto-link";
|
|
1288
|
+
link.textContent = "Sign in";
|
|
1289
|
+
link.addEventListener("click", () => this.go("auth"));
|
|
1290
|
+
banner.appendChild(txt);
|
|
1291
|
+
banner.appendChild(link);
|
|
1292
|
+
this.bannerEl = banner;
|
|
1293
|
+
wrap.appendChild(banner);
|
|
1061
1294
|
return wrap;
|
|
1062
1295
|
}
|
|
1296
|
+
refreshShell() {
|
|
1297
|
+
for (const [name, btn] of Object.entries(this.tabRefs)) {
|
|
1298
|
+
btn?.setAttribute(
|
|
1299
|
+
"aria-selected",
|
|
1300
|
+
name === this.state.view ? "true" : "false"
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
if (this.bodyEl) {
|
|
1304
|
+
this.bodyEl.innerHTML = "";
|
|
1305
|
+
this.bodyEl.appendChild(this.buildView());
|
|
1306
|
+
}
|
|
1307
|
+
if (this.bannerEl) {
|
|
1308
|
+
const showBanner = this.state.view !== "auth" && !this.state.email;
|
|
1309
|
+
this.bannerEl.style.display = showBanner ? "" : "none";
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1063
1312
|
buildHeader() {
|
|
1064
1313
|
const header = document.createElement("div");
|
|
1065
1314
|
header.className = "malto-header";
|
|
@@ -1096,14 +1345,14 @@ var MaltoWidget = class {
|
|
|
1096
1345
|
buildTabs() {
|
|
1097
1346
|
const tabs = document.createElement("div");
|
|
1098
1347
|
tabs.className = "malto-tabs";
|
|
1348
|
+
this.tabRefs = {};
|
|
1099
1349
|
const enabled = this.allowedViews();
|
|
1100
|
-
if (enabled.includes("list")) tabs.appendChild(this.tabBtn("Feed", "list"));
|
|
1101
|
-
if (enabled.includes("submit"))
|
|
1102
|
-
tabs.appendChild(this.tabBtn("New", "submit"));
|
|
1103
1350
|
if (enabled.includes("roadmap"))
|
|
1104
1351
|
tabs.appendChild(this.tabBtn("Roadmap", "roadmap"));
|
|
1105
1352
|
if (enabled.includes("changelog"))
|
|
1106
1353
|
tabs.appendChild(this.tabBtn("Updates", "changelog"));
|
|
1354
|
+
if (enabled.includes("submit"))
|
|
1355
|
+
tabs.appendChild(this.tabBtn("New", "submit"));
|
|
1107
1356
|
return tabs;
|
|
1108
1357
|
}
|
|
1109
1358
|
tabBtn(label, view) {
|
|
@@ -1115,13 +1364,16 @@ var MaltoWidget = class {
|
|
|
1115
1364
|
);
|
|
1116
1365
|
btn.textContent = label;
|
|
1117
1366
|
btn.addEventListener("click", () => this.go(view));
|
|
1367
|
+
this.tabRefs[view] = btn;
|
|
1118
1368
|
return btn;
|
|
1119
1369
|
}
|
|
1120
1370
|
go(view) {
|
|
1121
1371
|
this.state.view = view;
|
|
1122
1372
|
this.state.error = null;
|
|
1373
|
+
if (view === "roadmap" || view === "changelog" || view === "detail" && this.state.selectedFeedback) {
|
|
1374
|
+
this.state.loading = true;
|
|
1375
|
+
}
|
|
1123
1376
|
this.render();
|
|
1124
|
-
if (view === "list") void this.loadFeedbacks();
|
|
1125
1377
|
if (view === "roadmap") void this.loadRoadmap();
|
|
1126
1378
|
if (view === "changelog") void this.loadReleases();
|
|
1127
1379
|
if (view === "detail" && this.state.selectedFeedback) {
|
|
@@ -1129,21 +1381,12 @@ var MaltoWidget = class {
|
|
|
1129
1381
|
}
|
|
1130
1382
|
}
|
|
1131
1383
|
allowedViews() {
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
"changelog"
|
|
1139
|
-
];
|
|
1140
|
-
const requested = this.config.views ?? [
|
|
1141
|
-
"list",
|
|
1142
|
-
"submit",
|
|
1143
|
-
"roadmap",
|
|
1144
|
-
"changelog"
|
|
1145
|
-
];
|
|
1146
|
-
return requested.filter((v) => settings.includes(v));
|
|
1384
|
+
const TABS = ["roadmap", "submit", "changelog"];
|
|
1385
|
+
const settings = this.board?.widget.enabledFeatures ?? TABS;
|
|
1386
|
+
const requested = this.config.views ?? TABS;
|
|
1387
|
+
return TABS.filter(
|
|
1388
|
+
(v) => settings.includes(v) && requested.includes(v)
|
|
1389
|
+
);
|
|
1147
1390
|
}
|
|
1148
1391
|
buildView() {
|
|
1149
1392
|
const wrap = document.createElement("div");
|
|
@@ -1154,12 +1397,6 @@ var MaltoWidget = class {
|
|
|
1154
1397
|
wrap.appendChild(e);
|
|
1155
1398
|
}
|
|
1156
1399
|
switch (this.state.view) {
|
|
1157
|
-
case "list":
|
|
1158
|
-
wrap.appendChild(this.viewList());
|
|
1159
|
-
if (this.state.feedbacks.length === 0 && !this.state.loading) {
|
|
1160
|
-
void this.loadFeedbacks();
|
|
1161
|
-
}
|
|
1162
|
-
break;
|
|
1163
1400
|
case "submit":
|
|
1164
1401
|
wrap.appendChild(this.viewSubmit());
|
|
1165
1402
|
break;
|
|
@@ -1168,15 +1405,9 @@ var MaltoWidget = class {
|
|
|
1168
1405
|
break;
|
|
1169
1406
|
case "roadmap":
|
|
1170
1407
|
wrap.appendChild(this.viewRoadmap());
|
|
1171
|
-
if (Object.keys(this.state.roadmap).length === 0 && !this.state.loading) {
|
|
1172
|
-
void this.loadRoadmap();
|
|
1173
|
-
}
|
|
1174
1408
|
break;
|
|
1175
1409
|
case "changelog":
|
|
1176
1410
|
wrap.appendChild(this.viewChangelog());
|
|
1177
|
-
if (this.state.releases.length === 0 && !this.state.loading) {
|
|
1178
|
-
void this.loadReleases();
|
|
1179
|
-
}
|
|
1180
1411
|
break;
|
|
1181
1412
|
case "detail":
|
|
1182
1413
|
wrap.appendChild(this.viewDetail());
|
|
@@ -1184,30 +1415,6 @@ var MaltoWidget = class {
|
|
|
1184
1415
|
}
|
|
1185
1416
|
return wrap;
|
|
1186
1417
|
}
|
|
1187
|
-
viewList() {
|
|
1188
|
-
const wrap = document.createElement("div");
|
|
1189
|
-
if (this.state.loading) {
|
|
1190
|
-
wrap.appendChild(this.skeletonList(4));
|
|
1191
|
-
return wrap;
|
|
1192
|
-
}
|
|
1193
|
-
if (this.state.feedbacks.length === 0) {
|
|
1194
|
-
wrap.appendChild(
|
|
1195
|
-
this.emptyState(
|
|
1196
|
-
"\u{1F4A1}",
|
|
1197
|
-
"No requests yet",
|
|
1198
|
-
"Be the first to share an idea or report something."
|
|
1199
|
-
)
|
|
1200
|
-
);
|
|
1201
|
-
return wrap;
|
|
1202
|
-
}
|
|
1203
|
-
const list = document.createElement("div");
|
|
1204
|
-
list.className = "malto-list";
|
|
1205
|
-
for (const fb of this.state.feedbacks) {
|
|
1206
|
-
list.appendChild(this.feedbackRow(fb));
|
|
1207
|
-
}
|
|
1208
|
-
wrap.appendChild(list);
|
|
1209
|
-
return wrap;
|
|
1210
|
-
}
|
|
1211
1418
|
feedbackRow(fb) {
|
|
1212
1419
|
const row = document.createElement("div");
|
|
1213
1420
|
row.className = "malto-item";
|
|
@@ -1287,8 +1494,7 @@ var MaltoWidget = class {
|
|
|
1287
1494
|
});
|
|
1288
1495
|
titleInput.value = "";
|
|
1289
1496
|
descInput.value = "";
|
|
1290
|
-
|
|
1291
|
-
this.go("list");
|
|
1497
|
+
this.go("roadmap");
|
|
1292
1498
|
} catch (err) {
|
|
1293
1499
|
this.state.error = err.message;
|
|
1294
1500
|
submit.removeAttribute("disabled");
|
|
@@ -1312,7 +1518,7 @@ var MaltoWidget = class {
|
|
|
1312
1518
|
clearSession(this.apiKeyPrefix);
|
|
1313
1519
|
this.state.email = null;
|
|
1314
1520
|
this.state.name = null;
|
|
1315
|
-
this.go("
|
|
1521
|
+
this.go("roadmap");
|
|
1316
1522
|
});
|
|
1317
1523
|
wrap.appendChild(p);
|
|
1318
1524
|
wrap.appendChild(out);
|
|
@@ -1347,7 +1553,7 @@ var MaltoWidget = class {
|
|
|
1347
1553
|
this.state.email = session.user.email;
|
|
1348
1554
|
this.state.name = session.user.name;
|
|
1349
1555
|
this.state.authStatus = "idle";
|
|
1350
|
-
this.go("
|
|
1556
|
+
this.go("roadmap");
|
|
1351
1557
|
} catch (err) {
|
|
1352
1558
|
this.state.error = err.message;
|
|
1353
1559
|
verifyBtn.removeAttribute("disabled");
|
|
@@ -1408,40 +1614,57 @@ var MaltoWidget = class {
|
|
|
1408
1614
|
}
|
|
1409
1615
|
viewRoadmap() {
|
|
1410
1616
|
const wrap = document.createElement("div");
|
|
1617
|
+
wrap.className = "malto-kanban";
|
|
1411
1618
|
if (this.state.loading) {
|
|
1412
|
-
|
|
1619
|
+
for (let i = 0; i < 4; i++) {
|
|
1620
|
+
const col = document.createElement("div");
|
|
1621
|
+
col.className = "malto-kanban-col";
|
|
1622
|
+
const h = document.createElement("h5");
|
|
1623
|
+
h.className = "malto-kanban-title";
|
|
1624
|
+
h.textContent = "Loading";
|
|
1625
|
+
col.appendChild(h);
|
|
1626
|
+
col.appendChild(this.skeletonCards(2));
|
|
1627
|
+
wrap.appendChild(col);
|
|
1628
|
+
}
|
|
1413
1629
|
return wrap;
|
|
1414
1630
|
}
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1631
|
+
const cols = [
|
|
1632
|
+
{ status: "BACKLOG", title: "Backlog" },
|
|
1633
|
+
{ status: "PLANNED", title: "Planned" },
|
|
1634
|
+
{ status: "IN_PROGRESS", title: "In Progress" },
|
|
1635
|
+
{ status: "UNDER_REVIEW", title: "Under Review" }
|
|
1636
|
+
];
|
|
1420
1637
|
let any = false;
|
|
1421
|
-
for (const status
|
|
1638
|
+
for (const { status, title } of cols) {
|
|
1422
1639
|
const items = this.state.roadmap[status] ?? [];
|
|
1640
|
+
if (items.length > 0) any = true;
|
|
1423
1641
|
const col = document.createElement("div");
|
|
1424
|
-
col.className = "malto-
|
|
1642
|
+
col.className = "malto-kanban-col";
|
|
1643
|
+
const head = document.createElement("div");
|
|
1644
|
+
head.className = "malto-kanban-head";
|
|
1425
1645
|
const h = document.createElement("h5");
|
|
1426
|
-
h.
|
|
1646
|
+
h.className = "malto-kanban-title";
|
|
1647
|
+
h.textContent = title;
|
|
1427
1648
|
const c = document.createElement("span");
|
|
1428
|
-
c.className = "malto-
|
|
1429
|
-
c.textContent =
|
|
1430
|
-
|
|
1431
|
-
|
|
1649
|
+
c.className = "malto-kanban-count";
|
|
1650
|
+
c.textContent = pad2(items.length);
|
|
1651
|
+
head.appendChild(h);
|
|
1652
|
+
head.appendChild(c);
|
|
1653
|
+
col.appendChild(head);
|
|
1654
|
+
const list = document.createElement("div");
|
|
1655
|
+
list.className = "malto-kanban-list";
|
|
1432
1656
|
if (items.length === 0) {
|
|
1433
1657
|
const empty = document.createElement("div");
|
|
1434
|
-
empty.className = "malto-empty
|
|
1435
|
-
empty.
|
|
1436
|
-
empty
|
|
1437
|
-
col.appendChild(empty);
|
|
1658
|
+
empty.className = "malto-kanban-empty";
|
|
1659
|
+
empty.textContent = "Nothing here.";
|
|
1660
|
+
list.appendChild(empty);
|
|
1438
1661
|
} else {
|
|
1439
|
-
|
|
1440
|
-
for (const fb of items) col.appendChild(this.feedbackRow(fb));
|
|
1662
|
+
for (const fb of items) list.appendChild(this.feedbackCard(fb));
|
|
1441
1663
|
}
|
|
1664
|
+
col.appendChild(list);
|
|
1442
1665
|
wrap.appendChild(col);
|
|
1443
1666
|
}
|
|
1444
|
-
if (!any
|
|
1667
|
+
if (!any) {
|
|
1445
1668
|
wrap.innerHTML = "";
|
|
1446
1669
|
wrap.appendChild(
|
|
1447
1670
|
this.emptyState(
|
|
@@ -1453,6 +1676,102 @@ var MaltoWidget = class {
|
|
|
1453
1676
|
}
|
|
1454
1677
|
return wrap;
|
|
1455
1678
|
}
|
|
1679
|
+
feedbackCard(fb) {
|
|
1680
|
+
const url = fb.url ?? "";
|
|
1681
|
+
const card = document.createElement(url ? "a" : "div");
|
|
1682
|
+
card.className = "malto-card";
|
|
1683
|
+
if (url) {
|
|
1684
|
+
card.href = url;
|
|
1685
|
+
card.target = "_blank";
|
|
1686
|
+
card.rel = "noopener noreferrer";
|
|
1687
|
+
}
|
|
1688
|
+
const top = document.createElement("div");
|
|
1689
|
+
top.className = "malto-card-top";
|
|
1690
|
+
const cat = document.createElement("span");
|
|
1691
|
+
cat.className = "malto-card-cat";
|
|
1692
|
+
cat.textContent = fb.category ?? "General";
|
|
1693
|
+
const votes = document.createElement("span");
|
|
1694
|
+
votes.className = "malto-card-votes";
|
|
1695
|
+
votes.innerHTML = `<svg viewBox="0 0 12 12" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 7 6 4 9 7"/></svg><span>${formatCount(fb.votes ?? 0)}</span>`;
|
|
1696
|
+
top.appendChild(cat);
|
|
1697
|
+
top.appendChild(votes);
|
|
1698
|
+
card.appendChild(top);
|
|
1699
|
+
const title = document.createElement("h4");
|
|
1700
|
+
title.className = "malto-card-title";
|
|
1701
|
+
title.textContent = fb.title;
|
|
1702
|
+
card.appendChild(title);
|
|
1703
|
+
if (fb.description) {
|
|
1704
|
+
const desc = document.createElement("p");
|
|
1705
|
+
desc.className = "malto-card-desc";
|
|
1706
|
+
desc.textContent = fb.description;
|
|
1707
|
+
card.appendChild(desc);
|
|
1708
|
+
}
|
|
1709
|
+
const foot = document.createElement("div");
|
|
1710
|
+
foot.className = "malto-card-foot";
|
|
1711
|
+
const who = document.createElement("div");
|
|
1712
|
+
who.className = "malto-card-who";
|
|
1713
|
+
const avatarUrl = safeImageUrl(fb.author?.avatarUrl);
|
|
1714
|
+
const avatar = document.createElement("span");
|
|
1715
|
+
avatar.className = "malto-card-avatar";
|
|
1716
|
+
if (avatarUrl) {
|
|
1717
|
+
const img = document.createElement("img");
|
|
1718
|
+
img.src = avatarUrl;
|
|
1719
|
+
img.alt = "";
|
|
1720
|
+
img.referrerPolicy = "no-referrer";
|
|
1721
|
+
img.crossOrigin = "anonymous";
|
|
1722
|
+
avatar.appendChild(img);
|
|
1723
|
+
} else {
|
|
1724
|
+
avatar.classList.add(`malto-avatar-bg-${avatarBgIndex(fb.author?.name ?? fb.title)}`);
|
|
1725
|
+
avatar.textContent = initialsOf(fb.author?.name);
|
|
1726
|
+
}
|
|
1727
|
+
who.appendChild(avatar);
|
|
1728
|
+
const name = document.createElement("span");
|
|
1729
|
+
name.className = "malto-card-author";
|
|
1730
|
+
name.textContent = fb.author?.name ?? "Anon";
|
|
1731
|
+
who.appendChild(name);
|
|
1732
|
+
const dot = document.createElement("span");
|
|
1733
|
+
dot.className = "malto-card-dot";
|
|
1734
|
+
dot.textContent = "\xB7";
|
|
1735
|
+
who.appendChild(dot);
|
|
1736
|
+
const time = document.createElement("span");
|
|
1737
|
+
time.className = "malto-card-time";
|
|
1738
|
+
time.textContent = relativeTime(fb.createdAt);
|
|
1739
|
+
who.appendChild(time);
|
|
1740
|
+
const comments = document.createElement("span");
|
|
1741
|
+
comments.className = "malto-card-comments";
|
|
1742
|
+
comments.innerHTML = `<svg viewBox="0 0 14 14" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9a1.5 1.5 0 0 1-1.5 1.5H5l-3 3v-9A1.5 1.5 0 0 1 3.5 3h7A1.5 1.5 0 0 1 12 4.5z"/></svg><span>${fb.commentCount ?? 0}</span>`;
|
|
1743
|
+
foot.appendChild(who);
|
|
1744
|
+
foot.appendChild(comments);
|
|
1745
|
+
card.appendChild(foot);
|
|
1746
|
+
return card;
|
|
1747
|
+
}
|
|
1748
|
+
skeletonCards(count) {
|
|
1749
|
+
const wrap = document.createElement("div");
|
|
1750
|
+
wrap.className = "malto-kanban-list";
|
|
1751
|
+
for (let i = 0; i < count; i++) {
|
|
1752
|
+
const card = document.createElement("div");
|
|
1753
|
+
card.className = "malto-card malto-card-skeleton";
|
|
1754
|
+
const top = document.createElement("div");
|
|
1755
|
+
top.className = "malto-skeleton";
|
|
1756
|
+
top.style.height = "14px";
|
|
1757
|
+
top.style.width = "60px";
|
|
1758
|
+
const t = document.createElement("div");
|
|
1759
|
+
t.className = "malto-skeleton";
|
|
1760
|
+
t.style.height = "12px";
|
|
1761
|
+
t.style.width = "85%";
|
|
1762
|
+
t.style.marginTop = "10px";
|
|
1763
|
+
const d = document.createElement("div");
|
|
1764
|
+
d.className = "malto-skeleton";
|
|
1765
|
+
d.style.height = "10px";
|
|
1766
|
+
d.style.width = "70%";
|
|
1767
|
+
d.style.marginTop = "6px";
|
|
1768
|
+
card.appendChild(top);
|
|
1769
|
+
card.appendChild(t);
|
|
1770
|
+
card.appendChild(d);
|
|
1771
|
+
wrap.appendChild(card);
|
|
1772
|
+
}
|
|
1773
|
+
return wrap;
|
|
1774
|
+
}
|
|
1456
1775
|
viewChangelog() {
|
|
1457
1776
|
const wrap = document.createElement("div");
|
|
1458
1777
|
if (this.state.loading) {
|
|
@@ -1489,18 +1808,20 @@ var MaltoWidget = class {
|
|
|
1489
1808
|
const fb = this.state.selectedFeedback;
|
|
1490
1809
|
if (!fb) {
|
|
1491
1810
|
wrap.appendChild(
|
|
1492
|
-
this.emptyState("\u{1F4ED}", "Nothing selected", "Pick a request from
|
|
1811
|
+
this.emptyState("\u{1F4ED}", "Nothing selected", "Pick a request from the roadmap.")
|
|
1493
1812
|
);
|
|
1494
1813
|
return wrap;
|
|
1495
1814
|
}
|
|
1496
1815
|
const back = document.createElement("button");
|
|
1497
1816
|
back.className = "malto-back";
|
|
1498
1817
|
back.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg> Back';
|
|
1499
|
-
back.addEventListener("click", () => this.go("
|
|
1818
|
+
back.addEventListener("click", () => this.go("roadmap"));
|
|
1500
1819
|
wrap.appendChild(back);
|
|
1501
1820
|
wrap.appendChild(this.feedbackRow(fb));
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1821
|
+
const commentEnabled = this.board?.widget.enabledFeatures.includes(
|
|
1822
|
+
"comment"
|
|
1823
|
+
) ?? true;
|
|
1824
|
+
if (commentEnabled) {
|
|
1504
1825
|
const commentsHeader = document.createElement("h5");
|
|
1505
1826
|
commentsHeader.textContent = "Comments";
|
|
1506
1827
|
commentsHeader.style.cssText = "margin: 16px 0 8px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--malto-text-muted); font-weight: 600;";
|
|
@@ -1634,18 +1955,6 @@ var MaltoWidget = class {
|
|
|
1634
1955
|
wrap.appendChild(d);
|
|
1635
1956
|
return wrap;
|
|
1636
1957
|
}
|
|
1637
|
-
async loadFeedbacks() {
|
|
1638
|
-
this.state.loading = true;
|
|
1639
|
-
this.render();
|
|
1640
|
-
try {
|
|
1641
|
-
this.state.feedbacks = await this.client.listFeedbacks();
|
|
1642
|
-
} catch (err) {
|
|
1643
|
-
this.state.error = err.message;
|
|
1644
|
-
} finally {
|
|
1645
|
-
this.state.loading = false;
|
|
1646
|
-
this.render();
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
1958
|
async loadRoadmap() {
|
|
1650
1959
|
this.state.loading = true;
|
|
1651
1960
|
this.render();
|
|
@@ -1717,6 +2026,22 @@ function statusPill(status) {
|
|
|
1717
2026
|
pill.appendChild(txt);
|
|
1718
2027
|
return pill;
|
|
1719
2028
|
}
|
|
2029
|
+
function initialsOf(name) {
|
|
2030
|
+
if (!name) return "?";
|
|
2031
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
2032
|
+
if (parts.length === 0) return "?";
|
|
2033
|
+
const first = parts[0]?.[0] ?? "";
|
|
2034
|
+
const last = parts.length > 1 ? parts[parts.length - 1]?.[0] ?? "" : "";
|
|
2035
|
+
return (first + last).toUpperCase() || "?";
|
|
2036
|
+
}
|
|
2037
|
+
function avatarBgIndex(seedStr) {
|
|
2038
|
+
let h = 0;
|
|
2039
|
+
for (let i = 0; i < seedStr.length; i++) h = h * 31 + seedStr.charCodeAt(i) >>> 0;
|
|
2040
|
+
return h % 8;
|
|
2041
|
+
}
|
|
2042
|
+
function pad2(n) {
|
|
2043
|
+
return n < 10 ? `0${n}` : String(n);
|
|
2044
|
+
}
|
|
1720
2045
|
function formatCount(n) {
|
|
1721
2046
|
if (n < 1e3) return String(n);
|
|
1722
2047
|
if (n < 1e4) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|