@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.js
CHANGED
|
@@ -177,7 +177,7 @@ function buildCss(opts) {
|
|
|
177
177
|
const primary = opts.primary;
|
|
178
178
|
const tones = derivePalette(primary);
|
|
179
179
|
const radiusScale = opts.radius === "sm" ? "12px" : opts.radius === "lg" ? "24px" : "20px";
|
|
180
|
-
const appearance = opts.appearance ?? "
|
|
180
|
+
const appearance = opts.appearance ?? "light";
|
|
181
181
|
const overrides = opts.cssVars ?? {};
|
|
182
182
|
return `
|
|
183
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');
|
|
@@ -300,7 +300,8 @@ function buildCss(opts) {
|
|
|
300
300
|
.malto-modal {
|
|
301
301
|
background: var(--malto-surface);
|
|
302
302
|
width: 100%;
|
|
303
|
-
max-width:
|
|
303
|
+
max-width: 1152px;
|
|
304
|
+
height: 720px;
|
|
304
305
|
max-height: 92vh;
|
|
305
306
|
border-radius: var(--malto-radius) var(--malto-radius) 0 0;
|
|
306
307
|
box-shadow: var(--malto-shadow-lg);
|
|
@@ -751,6 +752,206 @@ function buildCss(opts) {
|
|
|
751
752
|
background: var(--malto-surface-muted);
|
|
752
753
|
}
|
|
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);
|
|
953
|
+
}
|
|
954
|
+
|
|
754
955
|
/* \u2500\u2500\u2500\u2500\u2500\u2500 Animations \u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
755
956
|
@keyframes malto-overlay-in {
|
|
756
957
|
from { opacity: 0; backdrop-filter: blur(0); }
|
|
@@ -840,6 +1041,11 @@ var MaltoWidget = class {
|
|
|
840
1041
|
this.container = null;
|
|
841
1042
|
this.trigger = null;
|
|
842
1043
|
this.board = null;
|
|
1044
|
+
this.shellEl = null;
|
|
1045
|
+
this.bodyEl = null;
|
|
1046
|
+
this.tabRefs = {};
|
|
1047
|
+
this.bannerEl = null;
|
|
1048
|
+
this.initialLoaded = false;
|
|
843
1049
|
if (!config.apiKey) throw new Error("Malto: apiKey is required");
|
|
844
1050
|
this.apiKeyPrefix = config.apiKey.slice(0, 16);
|
|
845
1051
|
this.config = {
|
|
@@ -852,7 +1058,7 @@ var MaltoWidget = class {
|
|
|
852
1058
|
cssVars: config.cssVars,
|
|
853
1059
|
customCss: config.customCss,
|
|
854
1060
|
radius: config.radius ?? "md",
|
|
855
|
-
appearance: config.appearance ?? "
|
|
1061
|
+
appearance: config.appearance ?? "light",
|
|
856
1062
|
zIndex: config.zIndex ?? 2147483600,
|
|
857
1063
|
views: config.views,
|
|
858
1064
|
identify: config.identify,
|
|
@@ -863,10 +1069,9 @@ var MaltoWidget = class {
|
|
|
863
1069
|
const session = readSession(this.apiKeyPrefix);
|
|
864
1070
|
this.state = {
|
|
865
1071
|
open: false,
|
|
866
|
-
view: "
|
|
867
|
-
loading:
|
|
1072
|
+
view: "roadmap",
|
|
1073
|
+
loading: true,
|
|
868
1074
|
error: null,
|
|
869
|
-
feedbacks: [],
|
|
870
1075
|
roadmap: {},
|
|
871
1076
|
releases: [],
|
|
872
1077
|
selectedFeedback: null,
|
|
@@ -899,17 +1104,28 @@ var MaltoWidget = class {
|
|
|
899
1104
|
await this.runIdentify(this.config.identify);
|
|
900
1105
|
}
|
|
901
1106
|
this.renderHost(this.resolvedMode);
|
|
1107
|
+
this.ensureInitialLoad();
|
|
902
1108
|
this.config.onReady?.();
|
|
903
1109
|
} catch (err) {
|
|
904
1110
|
this.config.onError?.(err);
|
|
905
1111
|
}
|
|
906
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
|
+
}
|
|
907
1119
|
unmount() {
|
|
908
1120
|
this.root?.parentElement?.removeChild(this.root);
|
|
909
1121
|
this.trigger?.parentElement?.removeChild(this.trigger);
|
|
910
1122
|
this.root = null;
|
|
911
1123
|
this.trigger = null;
|
|
912
1124
|
this.container = null;
|
|
1125
|
+
this.shellEl = null;
|
|
1126
|
+
this.bodyEl = null;
|
|
1127
|
+
this.tabRefs = {};
|
|
1128
|
+
this.bannerEl = null;
|
|
913
1129
|
}
|
|
914
1130
|
open() {
|
|
915
1131
|
this.state.open = true;
|
|
@@ -1018,24 +1234,39 @@ var MaltoWidget = class {
|
|
|
1018
1234
|
if (!this.root) return;
|
|
1019
1235
|
if (this.resolvedMode === "inline") {
|
|
1020
1236
|
if (!this.container) return;
|
|
1021
|
-
this.
|
|
1022
|
-
|
|
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();
|
|
1023
1243
|
return;
|
|
1024
1244
|
}
|
|
1025
|
-
this.
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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();
|
|
1037
1268
|
}
|
|
1038
|
-
|
|
1269
|
+
buildShell() {
|
|
1039
1270
|
const wrap = document.createElement("div");
|
|
1040
1271
|
wrap.style.display = "flex";
|
|
1041
1272
|
wrap.style.flexDirection = "column";
|
|
@@ -1045,23 +1276,39 @@ var MaltoWidget = class {
|
|
|
1045
1276
|
wrap.appendChild(this.buildTabs());
|
|
1046
1277
|
const body = document.createElement("div");
|
|
1047
1278
|
body.className = "malto-body";
|
|
1048
|
-
|
|
1279
|
+
this.bodyEl = body;
|
|
1049
1280
|
wrap.appendChild(body);
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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);
|
|
1063
1294
|
return wrap;
|
|
1064
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
|
+
}
|
|
1065
1312
|
buildHeader() {
|
|
1066
1313
|
const header = document.createElement("div");
|
|
1067
1314
|
header.className = "malto-header";
|
|
@@ -1098,14 +1345,14 @@ var MaltoWidget = class {
|
|
|
1098
1345
|
buildTabs() {
|
|
1099
1346
|
const tabs = document.createElement("div");
|
|
1100
1347
|
tabs.className = "malto-tabs";
|
|
1348
|
+
this.tabRefs = {};
|
|
1101
1349
|
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
1350
|
if (enabled.includes("roadmap"))
|
|
1106
1351
|
tabs.appendChild(this.tabBtn("Roadmap", "roadmap"));
|
|
1107
1352
|
if (enabled.includes("changelog"))
|
|
1108
1353
|
tabs.appendChild(this.tabBtn("Updates", "changelog"));
|
|
1354
|
+
if (enabled.includes("submit"))
|
|
1355
|
+
tabs.appendChild(this.tabBtn("New", "submit"));
|
|
1109
1356
|
return tabs;
|
|
1110
1357
|
}
|
|
1111
1358
|
tabBtn(label, view) {
|
|
@@ -1117,13 +1364,16 @@ var MaltoWidget = class {
|
|
|
1117
1364
|
);
|
|
1118
1365
|
btn.textContent = label;
|
|
1119
1366
|
btn.addEventListener("click", () => this.go(view));
|
|
1367
|
+
this.tabRefs[view] = btn;
|
|
1120
1368
|
return btn;
|
|
1121
1369
|
}
|
|
1122
1370
|
go(view) {
|
|
1123
1371
|
this.state.view = view;
|
|
1124
1372
|
this.state.error = null;
|
|
1373
|
+
if (view === "roadmap" || view === "changelog" || view === "detail" && this.state.selectedFeedback) {
|
|
1374
|
+
this.state.loading = true;
|
|
1375
|
+
}
|
|
1125
1376
|
this.render();
|
|
1126
|
-
if (view === "list") void this.loadFeedbacks();
|
|
1127
1377
|
if (view === "roadmap") void this.loadRoadmap();
|
|
1128
1378
|
if (view === "changelog") void this.loadReleases();
|
|
1129
1379
|
if (view === "detail" && this.state.selectedFeedback) {
|
|
@@ -1131,21 +1381,12 @@ var MaltoWidget = class {
|
|
|
1131
1381
|
}
|
|
1132
1382
|
}
|
|
1133
1383
|
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));
|
|
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
|
+
);
|
|
1149
1390
|
}
|
|
1150
1391
|
buildView() {
|
|
1151
1392
|
const wrap = document.createElement("div");
|
|
@@ -1156,12 +1397,6 @@ var MaltoWidget = class {
|
|
|
1156
1397
|
wrap.appendChild(e);
|
|
1157
1398
|
}
|
|
1158
1399
|
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
1400
|
case "submit":
|
|
1166
1401
|
wrap.appendChild(this.viewSubmit());
|
|
1167
1402
|
break;
|
|
@@ -1170,15 +1405,9 @@ var MaltoWidget = class {
|
|
|
1170
1405
|
break;
|
|
1171
1406
|
case "roadmap":
|
|
1172
1407
|
wrap.appendChild(this.viewRoadmap());
|
|
1173
|
-
if (Object.keys(this.state.roadmap).length === 0 && !this.state.loading) {
|
|
1174
|
-
void this.loadRoadmap();
|
|
1175
|
-
}
|
|
1176
1408
|
break;
|
|
1177
1409
|
case "changelog":
|
|
1178
1410
|
wrap.appendChild(this.viewChangelog());
|
|
1179
|
-
if (this.state.releases.length === 0 && !this.state.loading) {
|
|
1180
|
-
void this.loadReleases();
|
|
1181
|
-
}
|
|
1182
1411
|
break;
|
|
1183
1412
|
case "detail":
|
|
1184
1413
|
wrap.appendChild(this.viewDetail());
|
|
@@ -1186,30 +1415,6 @@ var MaltoWidget = class {
|
|
|
1186
1415
|
}
|
|
1187
1416
|
return wrap;
|
|
1188
1417
|
}
|
|
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
1418
|
feedbackRow(fb) {
|
|
1214
1419
|
const row = document.createElement("div");
|
|
1215
1420
|
row.className = "malto-item";
|
|
@@ -1289,8 +1494,7 @@ var MaltoWidget = class {
|
|
|
1289
1494
|
});
|
|
1290
1495
|
titleInput.value = "";
|
|
1291
1496
|
descInput.value = "";
|
|
1292
|
-
|
|
1293
|
-
this.go("list");
|
|
1497
|
+
this.go("roadmap");
|
|
1294
1498
|
} catch (err) {
|
|
1295
1499
|
this.state.error = err.message;
|
|
1296
1500
|
submit.removeAttribute("disabled");
|
|
@@ -1314,7 +1518,7 @@ var MaltoWidget = class {
|
|
|
1314
1518
|
clearSession(this.apiKeyPrefix);
|
|
1315
1519
|
this.state.email = null;
|
|
1316
1520
|
this.state.name = null;
|
|
1317
|
-
this.go("
|
|
1521
|
+
this.go("roadmap");
|
|
1318
1522
|
});
|
|
1319
1523
|
wrap.appendChild(p);
|
|
1320
1524
|
wrap.appendChild(out);
|
|
@@ -1349,7 +1553,7 @@ var MaltoWidget = class {
|
|
|
1349
1553
|
this.state.email = session.user.email;
|
|
1350
1554
|
this.state.name = session.user.name;
|
|
1351
1555
|
this.state.authStatus = "idle";
|
|
1352
|
-
this.go("
|
|
1556
|
+
this.go("roadmap");
|
|
1353
1557
|
} catch (err) {
|
|
1354
1558
|
this.state.error = err.message;
|
|
1355
1559
|
verifyBtn.removeAttribute("disabled");
|
|
@@ -1410,40 +1614,57 @@ var MaltoWidget = class {
|
|
|
1410
1614
|
}
|
|
1411
1615
|
viewRoadmap() {
|
|
1412
1616
|
const wrap = document.createElement("div");
|
|
1617
|
+
wrap.className = "malto-kanban";
|
|
1413
1618
|
if (this.state.loading) {
|
|
1414
|
-
|
|
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
|
+
}
|
|
1415
1629
|
return wrap;
|
|
1416
1630
|
}
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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
|
+
];
|
|
1422
1637
|
let any = false;
|
|
1423
|
-
for (const status
|
|
1638
|
+
for (const { status, title } of cols) {
|
|
1424
1639
|
const items = this.state.roadmap[status] ?? [];
|
|
1640
|
+
if (items.length > 0) any = true;
|
|
1425
1641
|
const col = document.createElement("div");
|
|
1426
|
-
col.className = "malto-
|
|
1642
|
+
col.className = "malto-kanban-col";
|
|
1643
|
+
const head = document.createElement("div");
|
|
1644
|
+
head.className = "malto-kanban-head";
|
|
1427
1645
|
const h = document.createElement("h5");
|
|
1428
|
-
h.
|
|
1646
|
+
h.className = "malto-kanban-title";
|
|
1647
|
+
h.textContent = title;
|
|
1429
1648
|
const c = document.createElement("span");
|
|
1430
|
-
c.className = "malto-
|
|
1431
|
-
c.textContent =
|
|
1432
|
-
|
|
1433
|
-
|
|
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";
|
|
1434
1656
|
if (items.length === 0) {
|
|
1435
1657
|
const empty = document.createElement("div");
|
|
1436
|
-
empty.className = "malto-empty
|
|
1437
|
-
empty.
|
|
1438
|
-
empty
|
|
1439
|
-
col.appendChild(empty);
|
|
1658
|
+
empty.className = "malto-kanban-empty";
|
|
1659
|
+
empty.textContent = "Nothing here.";
|
|
1660
|
+
list.appendChild(empty);
|
|
1440
1661
|
} else {
|
|
1441
|
-
|
|
1442
|
-
for (const fb of items) col.appendChild(this.feedbackRow(fb));
|
|
1662
|
+
for (const fb of items) list.appendChild(this.feedbackCard(fb));
|
|
1443
1663
|
}
|
|
1664
|
+
col.appendChild(list);
|
|
1444
1665
|
wrap.appendChild(col);
|
|
1445
1666
|
}
|
|
1446
|
-
if (!any
|
|
1667
|
+
if (!any) {
|
|
1447
1668
|
wrap.innerHTML = "";
|
|
1448
1669
|
wrap.appendChild(
|
|
1449
1670
|
this.emptyState(
|
|
@@ -1455,6 +1676,102 @@ var MaltoWidget = class {
|
|
|
1455
1676
|
}
|
|
1456
1677
|
return wrap;
|
|
1457
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
|
+
}
|
|
1458
1775
|
viewChangelog() {
|
|
1459
1776
|
const wrap = document.createElement("div");
|
|
1460
1777
|
if (this.state.loading) {
|
|
@@ -1491,18 +1808,20 @@ var MaltoWidget = class {
|
|
|
1491
1808
|
const fb = this.state.selectedFeedback;
|
|
1492
1809
|
if (!fb) {
|
|
1493
1810
|
wrap.appendChild(
|
|
1494
|
-
this.emptyState("\u{1F4ED}", "Nothing selected", "Pick a request from
|
|
1811
|
+
this.emptyState("\u{1F4ED}", "Nothing selected", "Pick a request from the roadmap.")
|
|
1495
1812
|
);
|
|
1496
1813
|
return wrap;
|
|
1497
1814
|
}
|
|
1498
1815
|
const back = document.createElement("button");
|
|
1499
1816
|
back.className = "malto-back";
|
|
1500
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';
|
|
1501
|
-
back.addEventListener("click", () => this.go("
|
|
1818
|
+
back.addEventListener("click", () => this.go("roadmap"));
|
|
1502
1819
|
wrap.appendChild(back);
|
|
1503
1820
|
wrap.appendChild(this.feedbackRow(fb));
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1821
|
+
const commentEnabled = this.board?.widget.enabledFeatures.includes(
|
|
1822
|
+
"comment"
|
|
1823
|
+
) ?? true;
|
|
1824
|
+
if (commentEnabled) {
|
|
1506
1825
|
const commentsHeader = document.createElement("h5");
|
|
1507
1826
|
commentsHeader.textContent = "Comments";
|
|
1508
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;";
|
|
@@ -1636,18 +1955,6 @@ var MaltoWidget = class {
|
|
|
1636
1955
|
wrap.appendChild(d);
|
|
1637
1956
|
return wrap;
|
|
1638
1957
|
}
|
|
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
1958
|
async loadRoadmap() {
|
|
1652
1959
|
this.state.loading = true;
|
|
1653
1960
|
this.render();
|
|
@@ -1719,6 +2026,22 @@ function statusPill(status) {
|
|
|
1719
2026
|
pill.appendChild(txt);
|
|
1720
2027
|
return pill;
|
|
1721
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
|
+
}
|
|
1722
2045
|
function formatCount(n) {
|
|
1723
2046
|
if (n < 1e3) return String(n);
|
|
1724
2047
|
if (n < 1e4) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|