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