@malto/sdk 0.1.2 → 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 +453 -130
- package/dist/index.d.cts +25 -5
- package/dist/index.d.ts +25 -5
- package/dist/index.js +453 -130
- package/dist/malto.umd.js +218 -17
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -179,7 +179,7 @@ function buildCss(opts) {
|
|
|
179
179
|
const primary = opts.primary;
|
|
180
180
|
const tones = derivePalette(primary);
|
|
181
181
|
const radiusScale = opts.radius === "sm" ? "12px" : opts.radius === "lg" ? "24px" : "20px";
|
|
182
|
-
const appearance = opts.appearance ?? "
|
|
182
|
+
const appearance = opts.appearance ?? "light";
|
|
183
183
|
const overrides = opts.cssVars ?? {};
|
|
184
184
|
return `
|
|
185
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');
|
|
@@ -302,7 +302,8 @@ function buildCss(opts) {
|
|
|
302
302
|
.malto-modal {
|
|
303
303
|
background: var(--malto-surface);
|
|
304
304
|
width: 100%;
|
|
305
|
-
max-width:
|
|
305
|
+
max-width: 1152px;
|
|
306
|
+
height: 720px;
|
|
306
307
|
max-height: 92vh;
|
|
307
308
|
border-radius: var(--malto-radius) var(--malto-radius) 0 0;
|
|
308
309
|
box-shadow: var(--malto-shadow-lg);
|
|
@@ -753,6 +754,206 @@ function buildCss(opts) {
|
|
|
753
754
|
background: var(--malto-surface-muted);
|
|
754
755
|
}
|
|
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);
|
|
955
|
+
}
|
|
956
|
+
|
|
756
957
|
/* \u2500\u2500\u2500\u2500\u2500\u2500 Animations \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
757
958
|
@keyframes malto-overlay-in {
|
|
758
959
|
from { opacity: 0; backdrop-filter: blur(0); }
|
|
@@ -842,6 +1043,11 @@ var MaltoWidget = class {
|
|
|
842
1043
|
this.container = null;
|
|
843
1044
|
this.trigger = null;
|
|
844
1045
|
this.board = null;
|
|
1046
|
+
this.shellEl = null;
|
|
1047
|
+
this.bodyEl = null;
|
|
1048
|
+
this.tabRefs = {};
|
|
1049
|
+
this.bannerEl = null;
|
|
1050
|
+
this.initialLoaded = false;
|
|
845
1051
|
if (!config.apiKey) throw new Error("Malto: apiKey is required");
|
|
846
1052
|
this.apiKeyPrefix = config.apiKey.slice(0, 16);
|
|
847
1053
|
this.config = {
|
|
@@ -854,7 +1060,7 @@ var MaltoWidget = class {
|
|
|
854
1060
|
cssVars: config.cssVars,
|
|
855
1061
|
customCss: config.customCss,
|
|
856
1062
|
radius: config.radius ?? "md",
|
|
857
|
-
appearance: config.appearance ?? "
|
|
1063
|
+
appearance: config.appearance ?? "light",
|
|
858
1064
|
zIndex: config.zIndex ?? 2147483600,
|
|
859
1065
|
views: config.views,
|
|
860
1066
|
identify: config.identify,
|
|
@@ -865,10 +1071,9 @@ var MaltoWidget = class {
|
|
|
865
1071
|
const session = readSession(this.apiKeyPrefix);
|
|
866
1072
|
this.state = {
|
|
867
1073
|
open: false,
|
|
868
|
-
view: "
|
|
869
|
-
loading:
|
|
1074
|
+
view: "roadmap",
|
|
1075
|
+
loading: true,
|
|
870
1076
|
error: null,
|
|
871
|
-
feedbacks: [],
|
|
872
1077
|
roadmap: {},
|
|
873
1078
|
releases: [],
|
|
874
1079
|
selectedFeedback: null,
|
|
@@ -901,17 +1106,28 @@ var MaltoWidget = class {
|
|
|
901
1106
|
await this.runIdentify(this.config.identify);
|
|
902
1107
|
}
|
|
903
1108
|
this.renderHost(this.resolvedMode);
|
|
1109
|
+
this.ensureInitialLoad();
|
|
904
1110
|
this.config.onReady?.();
|
|
905
1111
|
} catch (err) {
|
|
906
1112
|
this.config.onError?.(err);
|
|
907
1113
|
}
|
|
908
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
|
+
}
|
|
909
1121
|
unmount() {
|
|
910
1122
|
this.root?.parentElement?.removeChild(this.root);
|
|
911
1123
|
this.trigger?.parentElement?.removeChild(this.trigger);
|
|
912
1124
|
this.root = null;
|
|
913
1125
|
this.trigger = null;
|
|
914
1126
|
this.container = null;
|
|
1127
|
+
this.shellEl = null;
|
|
1128
|
+
this.bodyEl = null;
|
|
1129
|
+
this.tabRefs = {};
|
|
1130
|
+
this.bannerEl = null;
|
|
915
1131
|
}
|
|
916
1132
|
open() {
|
|
917
1133
|
this.state.open = true;
|
|
@@ -1020,24 +1236,39 @@ var MaltoWidget = class {
|
|
|
1020
1236
|
if (!this.root) return;
|
|
1021
1237
|
if (this.resolvedMode === "inline") {
|
|
1022
1238
|
if (!this.container) return;
|
|
1023
|
-
this.
|
|
1024
|
-
|
|
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();
|
|
1025
1245
|
return;
|
|
1026
1246
|
}
|
|
1027
|
-
this.
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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();
|
|
1039
1270
|
}
|
|
1040
|
-
|
|
1271
|
+
buildShell() {
|
|
1041
1272
|
const wrap = document.createElement("div");
|
|
1042
1273
|
wrap.style.display = "flex";
|
|
1043
1274
|
wrap.style.flexDirection = "column";
|
|
@@ -1047,23 +1278,39 @@ var MaltoWidget = class {
|
|
|
1047
1278
|
wrap.appendChild(this.buildTabs());
|
|
1048
1279
|
const body = document.createElement("div");
|
|
1049
1280
|
body.className = "malto-body";
|
|
1050
|
-
|
|
1281
|
+
this.bodyEl = body;
|
|
1051
1282
|
wrap.appendChild(body);
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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);
|
|
1065
1296
|
return wrap;
|
|
1066
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
|
+
}
|
|
1067
1314
|
buildHeader() {
|
|
1068
1315
|
const header = document.createElement("div");
|
|
1069
1316
|
header.className = "malto-header";
|
|
@@ -1100,14 +1347,14 @@ var MaltoWidget = class {
|
|
|
1100
1347
|
buildTabs() {
|
|
1101
1348
|
const tabs = document.createElement("div");
|
|
1102
1349
|
tabs.className = "malto-tabs";
|
|
1350
|
+
this.tabRefs = {};
|
|
1103
1351
|
const enabled = this.allowedViews();
|
|
1104
|
-
if (enabled.includes("list")) tabs.appendChild(this.tabBtn("Feed", "list"));
|
|
1105
|
-
if (enabled.includes("submit"))
|
|
1106
|
-
tabs.appendChild(this.tabBtn("New", "submit"));
|
|
1107
1352
|
if (enabled.includes("roadmap"))
|
|
1108
1353
|
tabs.appendChild(this.tabBtn("Roadmap", "roadmap"));
|
|
1109
1354
|
if (enabled.includes("changelog"))
|
|
1110
1355
|
tabs.appendChild(this.tabBtn("Updates", "changelog"));
|
|
1356
|
+
if (enabled.includes("submit"))
|
|
1357
|
+
tabs.appendChild(this.tabBtn("New", "submit"));
|
|
1111
1358
|
return tabs;
|
|
1112
1359
|
}
|
|
1113
1360
|
tabBtn(label, view) {
|
|
@@ -1119,13 +1366,16 @@ var MaltoWidget = class {
|
|
|
1119
1366
|
);
|
|
1120
1367
|
btn.textContent = label;
|
|
1121
1368
|
btn.addEventListener("click", () => this.go(view));
|
|
1369
|
+
this.tabRefs[view] = btn;
|
|
1122
1370
|
return btn;
|
|
1123
1371
|
}
|
|
1124
1372
|
go(view) {
|
|
1125
1373
|
this.state.view = view;
|
|
1126
1374
|
this.state.error = null;
|
|
1375
|
+
if (view === "roadmap" || view === "changelog" || view === "detail" && this.state.selectedFeedback) {
|
|
1376
|
+
this.state.loading = true;
|
|
1377
|
+
}
|
|
1127
1378
|
this.render();
|
|
1128
|
-
if (view === "list") void this.loadFeedbacks();
|
|
1129
1379
|
if (view === "roadmap") void this.loadRoadmap();
|
|
1130
1380
|
if (view === "changelog") void this.loadReleases();
|
|
1131
1381
|
if (view === "detail" && this.state.selectedFeedback) {
|
|
@@ -1133,21 +1383,12 @@ var MaltoWidget = class {
|
|
|
1133
1383
|
}
|
|
1134
1384
|
}
|
|
1135
1385
|
allowedViews() {
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
"changelog"
|
|
1143
|
-
];
|
|
1144
|
-
const requested = this.config.views ?? [
|
|
1145
|
-
"list",
|
|
1146
|
-
"submit",
|
|
1147
|
-
"roadmap",
|
|
1148
|
-
"changelog"
|
|
1149
|
-
];
|
|
1150
|
-
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
|
+
);
|
|
1151
1392
|
}
|
|
1152
1393
|
buildView() {
|
|
1153
1394
|
const wrap = document.createElement("div");
|
|
@@ -1158,12 +1399,6 @@ var MaltoWidget = class {
|
|
|
1158
1399
|
wrap.appendChild(e);
|
|
1159
1400
|
}
|
|
1160
1401
|
switch (this.state.view) {
|
|
1161
|
-
case "list":
|
|
1162
|
-
wrap.appendChild(this.viewList());
|
|
1163
|
-
if (this.state.feedbacks.length === 0 && !this.state.loading) {
|
|
1164
|
-
void this.loadFeedbacks();
|
|
1165
|
-
}
|
|
1166
|
-
break;
|
|
1167
1402
|
case "submit":
|
|
1168
1403
|
wrap.appendChild(this.viewSubmit());
|
|
1169
1404
|
break;
|
|
@@ -1172,15 +1407,9 @@ var MaltoWidget = class {
|
|
|
1172
1407
|
break;
|
|
1173
1408
|
case "roadmap":
|
|
1174
1409
|
wrap.appendChild(this.viewRoadmap());
|
|
1175
|
-
if (Object.keys(this.state.roadmap).length === 0 && !this.state.loading) {
|
|
1176
|
-
void this.loadRoadmap();
|
|
1177
|
-
}
|
|
1178
1410
|
break;
|
|
1179
1411
|
case "changelog":
|
|
1180
1412
|
wrap.appendChild(this.viewChangelog());
|
|
1181
|
-
if (this.state.releases.length === 0 && !this.state.loading) {
|
|
1182
|
-
void this.loadReleases();
|
|
1183
|
-
}
|
|
1184
1413
|
break;
|
|
1185
1414
|
case "detail":
|
|
1186
1415
|
wrap.appendChild(this.viewDetail());
|
|
@@ -1188,30 +1417,6 @@ var MaltoWidget = class {
|
|
|
1188
1417
|
}
|
|
1189
1418
|
return wrap;
|
|
1190
1419
|
}
|
|
1191
|
-
viewList() {
|
|
1192
|
-
const wrap = document.createElement("div");
|
|
1193
|
-
if (this.state.loading) {
|
|
1194
|
-
wrap.appendChild(this.skeletonList(4));
|
|
1195
|
-
return wrap;
|
|
1196
|
-
}
|
|
1197
|
-
if (this.state.feedbacks.length === 0) {
|
|
1198
|
-
wrap.appendChild(
|
|
1199
|
-
this.emptyState(
|
|
1200
|
-
"\u{1F4A1}",
|
|
1201
|
-
"No requests yet",
|
|
1202
|
-
"Be the first to share an idea or report something."
|
|
1203
|
-
)
|
|
1204
|
-
);
|
|
1205
|
-
return wrap;
|
|
1206
|
-
}
|
|
1207
|
-
const list = document.createElement("div");
|
|
1208
|
-
list.className = "malto-list";
|
|
1209
|
-
for (const fb of this.state.feedbacks) {
|
|
1210
|
-
list.appendChild(this.feedbackRow(fb));
|
|
1211
|
-
}
|
|
1212
|
-
wrap.appendChild(list);
|
|
1213
|
-
return wrap;
|
|
1214
|
-
}
|
|
1215
1420
|
feedbackRow(fb) {
|
|
1216
1421
|
const row = document.createElement("div");
|
|
1217
1422
|
row.className = "malto-item";
|
|
@@ -1291,8 +1496,7 @@ var MaltoWidget = class {
|
|
|
1291
1496
|
});
|
|
1292
1497
|
titleInput.value = "";
|
|
1293
1498
|
descInput.value = "";
|
|
1294
|
-
|
|
1295
|
-
this.go("list");
|
|
1499
|
+
this.go("roadmap");
|
|
1296
1500
|
} catch (err) {
|
|
1297
1501
|
this.state.error = err.message;
|
|
1298
1502
|
submit.removeAttribute("disabled");
|
|
@@ -1316,7 +1520,7 @@ var MaltoWidget = class {
|
|
|
1316
1520
|
clearSession(this.apiKeyPrefix);
|
|
1317
1521
|
this.state.email = null;
|
|
1318
1522
|
this.state.name = null;
|
|
1319
|
-
this.go("
|
|
1523
|
+
this.go("roadmap");
|
|
1320
1524
|
});
|
|
1321
1525
|
wrap.appendChild(p);
|
|
1322
1526
|
wrap.appendChild(out);
|
|
@@ -1351,7 +1555,7 @@ var MaltoWidget = class {
|
|
|
1351
1555
|
this.state.email = session.user.email;
|
|
1352
1556
|
this.state.name = session.user.name;
|
|
1353
1557
|
this.state.authStatus = "idle";
|
|
1354
|
-
this.go("
|
|
1558
|
+
this.go("roadmap");
|
|
1355
1559
|
} catch (err) {
|
|
1356
1560
|
this.state.error = err.message;
|
|
1357
1561
|
verifyBtn.removeAttribute("disabled");
|
|
@@ -1412,40 +1616,57 @@ var MaltoWidget = class {
|
|
|
1412
1616
|
}
|
|
1413
1617
|
viewRoadmap() {
|
|
1414
1618
|
const wrap = document.createElement("div");
|
|
1619
|
+
wrap.className = "malto-kanban";
|
|
1415
1620
|
if (this.state.loading) {
|
|
1416
|
-
|
|
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
|
+
}
|
|
1417
1631
|
return wrap;
|
|
1418
1632
|
}
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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
|
+
];
|
|
1424
1639
|
let any = false;
|
|
1425
|
-
for (const status
|
|
1640
|
+
for (const { status, title } of cols) {
|
|
1426
1641
|
const items = this.state.roadmap[status] ?? [];
|
|
1642
|
+
if (items.length > 0) any = true;
|
|
1427
1643
|
const col = document.createElement("div");
|
|
1428
|
-
col.className = "malto-
|
|
1644
|
+
col.className = "malto-kanban-col";
|
|
1645
|
+
const head = document.createElement("div");
|
|
1646
|
+
head.className = "malto-kanban-head";
|
|
1429
1647
|
const h = document.createElement("h5");
|
|
1430
|
-
h.
|
|
1648
|
+
h.className = "malto-kanban-title";
|
|
1649
|
+
h.textContent = title;
|
|
1431
1650
|
const c = document.createElement("span");
|
|
1432
|
-
c.className = "malto-
|
|
1433
|
-
c.textContent =
|
|
1434
|
-
|
|
1435
|
-
|
|
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";
|
|
1436
1658
|
if (items.length === 0) {
|
|
1437
1659
|
const empty = document.createElement("div");
|
|
1438
|
-
empty.className = "malto-empty
|
|
1439
|
-
empty.
|
|
1440
|
-
empty
|
|
1441
|
-
col.appendChild(empty);
|
|
1660
|
+
empty.className = "malto-kanban-empty";
|
|
1661
|
+
empty.textContent = "Nothing here.";
|
|
1662
|
+
list.appendChild(empty);
|
|
1442
1663
|
} else {
|
|
1443
|
-
|
|
1444
|
-
for (const fb of items) col.appendChild(this.feedbackRow(fb));
|
|
1664
|
+
for (const fb of items) list.appendChild(this.feedbackCard(fb));
|
|
1445
1665
|
}
|
|
1666
|
+
col.appendChild(list);
|
|
1446
1667
|
wrap.appendChild(col);
|
|
1447
1668
|
}
|
|
1448
|
-
if (!any
|
|
1669
|
+
if (!any) {
|
|
1449
1670
|
wrap.innerHTML = "";
|
|
1450
1671
|
wrap.appendChild(
|
|
1451
1672
|
this.emptyState(
|
|
@@ -1457,6 +1678,102 @@ var MaltoWidget = class {
|
|
|
1457
1678
|
}
|
|
1458
1679
|
return wrap;
|
|
1459
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
|
+
}
|
|
1460
1777
|
viewChangelog() {
|
|
1461
1778
|
const wrap = document.createElement("div");
|
|
1462
1779
|
if (this.state.loading) {
|
|
@@ -1493,18 +1810,20 @@ var MaltoWidget = class {
|
|
|
1493
1810
|
const fb = this.state.selectedFeedback;
|
|
1494
1811
|
if (!fb) {
|
|
1495
1812
|
wrap.appendChild(
|
|
1496
|
-
this.emptyState("\u{1F4ED}", "Nothing selected", "Pick a request from
|
|
1813
|
+
this.emptyState("\u{1F4ED}", "Nothing selected", "Pick a request from the roadmap.")
|
|
1497
1814
|
);
|
|
1498
1815
|
return wrap;
|
|
1499
1816
|
}
|
|
1500
1817
|
const back = document.createElement("button");
|
|
1501
1818
|
back.className = "malto-back";
|
|
1502
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';
|
|
1503
|
-
back.addEventListener("click", () => this.go("
|
|
1820
|
+
back.addEventListener("click", () => this.go("roadmap"));
|
|
1504
1821
|
wrap.appendChild(back);
|
|
1505
1822
|
wrap.appendChild(this.feedbackRow(fb));
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1823
|
+
const commentEnabled = this.board?.widget.enabledFeatures.includes(
|
|
1824
|
+
"comment"
|
|
1825
|
+
) ?? true;
|
|
1826
|
+
if (commentEnabled) {
|
|
1508
1827
|
const commentsHeader = document.createElement("h5");
|
|
1509
1828
|
commentsHeader.textContent = "Comments";
|
|
1510
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;";
|
|
@@ -1638,18 +1957,6 @@ var MaltoWidget = class {
|
|
|
1638
1957
|
wrap.appendChild(d);
|
|
1639
1958
|
return wrap;
|
|
1640
1959
|
}
|
|
1641
|
-
async loadFeedbacks() {
|
|
1642
|
-
this.state.loading = true;
|
|
1643
|
-
this.render();
|
|
1644
|
-
try {
|
|
1645
|
-
this.state.feedbacks = await this.client.listFeedbacks();
|
|
1646
|
-
} catch (err) {
|
|
1647
|
-
this.state.error = err.message;
|
|
1648
|
-
} finally {
|
|
1649
|
-
this.state.loading = false;
|
|
1650
|
-
this.render();
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
1960
|
async loadRoadmap() {
|
|
1654
1961
|
this.state.loading = true;
|
|
1655
1962
|
this.render();
|
|
@@ -1721,6 +2028,22 @@ function statusPill(status) {
|
|
|
1721
2028
|
pill.appendChild(txt);
|
|
1722
2029
|
return pill;
|
|
1723
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
|
+
}
|
|
1724
2047
|
function formatCount(n) {
|
|
1725
2048
|
if (n < 1e3) return String(n);
|
|
1726
2049
|
if (n < 1e4) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|