web-mojo 2.2.69 → 2.2.71
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/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.cjs.js.map +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/charts.es.js.map +1 -1
- package/dist/chunks/ChatView-Cwwb-A47.js +2 -0
- package/dist/chunks/ChatView-Cwwb-A47.js.map +1 -0
- package/dist/chunks/ChatView-DCW_0GFg.js +2 -0
- package/dist/chunks/ChatView-DCW_0GFg.js.map +1 -0
- package/dist/chunks/Collection-BUv4E9op.js +2 -0
- package/dist/chunks/Collection-BUv4E9op.js.map +1 -0
- package/dist/chunks/Collection-r1ACzUeh.js +2 -0
- package/dist/chunks/Collection-r1ACzUeh.js.map +1 -0
- package/dist/chunks/ContextMenu-BFxliZ03.js +2 -0
- package/dist/chunks/{ContextMenu-8vTiZZQV.js.map → ContextMenu-BFxliZ03.js.map} +1 -1
- package/dist/chunks/ContextMenu-BwJJ4QJE.js +2 -0
- package/dist/chunks/{ContextMenu-DBw0WMTO.js.map → ContextMenu-BwJJ4QJE.js.map} +1 -1
- package/dist/chunks/DataView-DMpNXerv.js +2 -0
- package/dist/chunks/{DataView-DyJKgOn3.js.map → DataView-DMpNXerv.js.map} +1 -1
- package/dist/chunks/DataView-_CACqzRt.js +2 -0
- package/dist/chunks/{DataView-BEovBggn.js.map → DataView-_CACqzRt.js.map} +1 -1
- package/dist/chunks/Dialog-CdIM9IyH.js +3 -0
- package/dist/chunks/Dialog-CdIM9IyH.js.map +1 -0
- package/dist/chunks/Dialog-DZ4cPzBO.js +3 -0
- package/dist/chunks/Dialog-DZ4cPzBO.js.map +1 -0
- package/dist/chunks/FormView-BG-huF2q.js +3 -0
- package/dist/chunks/{FormView-B1CXO2t8.js.map → FormView-BG-huF2q.js.map} +1 -1
- package/dist/chunks/FormView-BGkZo_M5.js +3 -0
- package/dist/chunks/{FormView-BRHAIawp.js.map → FormView-BGkZo_M5.js.map} +1 -1
- package/dist/chunks/ListView-2M4I8KHF.js +2 -0
- package/dist/chunks/{ListView-CMZpwyyC.js.map → ListView-2M4I8KHF.js.map} +1 -1
- package/dist/chunks/ListView-B0QbqSPv.js +2 -0
- package/dist/chunks/{ListView-BLFFK_Ir.js.map → ListView-B0QbqSPv.js.map} +1 -1
- package/dist/chunks/MetricsCountryMapView-C5kQA6xw.js +2 -0
- package/dist/chunks/{MetricsCountryMapView-B0kWK-Js.js.map → MetricsCountryMapView-C5kQA6xw.js.map} +1 -1
- package/dist/chunks/MetricsCountryMapView-D1WtxmEc.js +2 -0
- package/dist/chunks/{MetricsCountryMapView-DuBKO7gz.js.map → MetricsCountryMapView-D1WtxmEc.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-BVCILe9D.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-Dg1e6EQJ.js.map → MetricsMiniChartWidget-BVCILe9D.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-C56xbSvN.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-D1w608Jy.js.map → MetricsMiniChartWidget-C56xbSvN.js.map} +1 -1
- package/dist/chunks/Modal-BRy52L0q.js +2 -0
- package/dist/chunks/Modal-BRy52L0q.js.map +1 -0
- package/dist/chunks/Modal-Bk391x8U.js +2 -0
- package/dist/chunks/Modal-Bk391x8U.js.map +1 -0
- package/dist/chunks/PDFViewer-BOlzz1xw.js +2 -0
- package/dist/chunks/{PDFViewer-D_3V8QJe.js.map → PDFViewer-BOlzz1xw.js.map} +1 -1
- package/dist/chunks/PDFViewer-D86bl2rr.js +2 -0
- package/dist/chunks/{PDFViewer-CDeV9OBs.js.map → PDFViewer-D86bl2rr.js.map} +1 -1
- package/dist/chunks/Passkeys-BMlPoiNU.js +2 -0
- package/dist/chunks/Passkeys-BMlPoiNU.js.map +1 -0
- package/dist/chunks/Passkeys-BOgPdzUP.js +2 -0
- package/dist/chunks/Passkeys-BOgPdzUP.js.map +1 -0
- package/dist/chunks/TokenManager-Bt369_RQ.js +2 -0
- package/dist/chunks/{TokenManager-sZgt--C9.js.map → TokenManager-Bt369_RQ.js.map} +1 -1
- package/dist/chunks/TokenManager-CQz0jHA7.js +2 -0
- package/dist/chunks/{TokenManager-ien2XzwO.js.map → TokenManager-CQz0jHA7.js.map} +1 -1
- package/dist/chunks/UserProfileView-C-acS9nw.js +2 -0
- package/dist/chunks/UserProfileView-C-acS9nw.js.map +1 -0
- package/dist/chunks/UserProfileView-_PYRQ7e-.js +2 -0
- package/dist/chunks/UserProfileView-_PYRQ7e-.js.map +1 -0
- package/dist/chunks/WebApp-Bcm_5W7o.js +2 -0
- package/dist/chunks/WebApp-Bcm_5W7o.js.map +1 -0
- package/dist/chunks/WebApp-XII3ai1e.js +2 -0
- package/dist/chunks/WebApp-XII3ai1e.js.map +1 -0
- package/dist/chunks/WebSocketClient-Ibi7mLQu.js +2 -0
- package/dist/chunks/{WebSocketClient-Bh0Mmtje.js.map → WebSocketClient-Ibi7mLQu.js.map} +1 -1
- package/dist/chunks/WebSocketClient-QaCUN3EQ.js +2 -0
- package/dist/chunks/{WebSocketClient-CLgYPxWX.js.map → WebSocketClient-QaCUN3EQ.js.map} +1 -1
- package/dist/chunks/index-BlmLfc7i.js +2 -0
- package/dist/chunks/{index-Aq9ke4vg.js.map → index-BlmLfc7i.js.map} +1 -1
- package/dist/chunks/index-CR7lDflX.js +2 -0
- package/dist/chunks/{index-Da9sT-tE.js.map → index-CR7lDflX.js.map} +1 -1
- package/dist/chunks/{version-D8JjsPW0.js → version-COP2irOM.js} +2 -2
- package/dist/chunks/{version-D8JjsPW0.js.map → version-COP2irOM.js.map} +1 -1
- package/dist/chunks/{version-XmirKYWA.js → version-hqiyNraS.js} +2 -2
- package/dist/chunks/{version-XmirKYWA.js.map → version-hqiyNraS.js.map} +1 -1
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.cjs.js.map +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/docit.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.cjs.js.map +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/lightbox.es.js.map +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.cjs.js.map +1 -1
- package/dist/map.es.js +1 -1
- package/dist/map.es.js.map +1 -1
- package/dist/mojo-auth.es.js +101 -50
- package/dist/mojo-auth.umd.js +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.cjs.js.map +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/timeline.es.js.map +1 -1
- package/dist/toast.css +106 -74
- package/dist/user-profile.cjs.js +1 -1
- package/dist/user-profile.es.js +1 -1
- package/dist/web-mojo.lite.iife.js +18330 -18084
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +280 -238
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/ChatView-CZ3Key2k.js +0 -2
- package/dist/chunks/ChatView-CZ3Key2k.js.map +0 -1
- package/dist/chunks/ChatView-Dw-iVmht.js +0 -2
- package/dist/chunks/ChatView-Dw-iVmht.js.map +0 -1
- package/dist/chunks/Collection-BWKmydl5.js +0 -2
- package/dist/chunks/Collection-BWKmydl5.js.map +0 -1
- package/dist/chunks/Collection-CmjTsmrP.js +0 -2
- package/dist/chunks/Collection-CmjTsmrP.js.map +0 -1
- package/dist/chunks/ContextMenu-8vTiZZQV.js +0 -2
- package/dist/chunks/ContextMenu-DBw0WMTO.js +0 -2
- package/dist/chunks/DataView-BEovBggn.js +0 -2
- package/dist/chunks/DataView-DyJKgOn3.js +0 -2
- package/dist/chunks/Dialog-Dhqtd9Yz.js +0 -2
- package/dist/chunks/Dialog-Dhqtd9Yz.js.map +0 -1
- package/dist/chunks/Dialog-t_9l2Mou.js +0 -2
- package/dist/chunks/Dialog-t_9l2Mou.js.map +0 -1
- package/dist/chunks/Files-6eRT5k3r.js +0 -2
- package/dist/chunks/Files-6eRT5k3r.js.map +0 -1
- package/dist/chunks/Files-Dh_5PFBn.js +0 -2
- package/dist/chunks/Files-Dh_5PFBn.js.map +0 -1
- package/dist/chunks/FormView-B1CXO2t8.js +0 -3
- package/dist/chunks/FormView-BRHAIawp.js +0 -3
- package/dist/chunks/ListView-BLFFK_Ir.js +0 -2
- package/dist/chunks/ListView-CMZpwyyC.js +0 -2
- package/dist/chunks/MetricsCountryMapView-B0kWK-Js.js +0 -2
- package/dist/chunks/MetricsCountryMapView-DuBKO7gz.js +0 -2
- package/dist/chunks/MetricsMiniChartWidget-D1w608Jy.js +0 -2
- package/dist/chunks/MetricsMiniChartWidget-Dg1e6EQJ.js +0 -2
- package/dist/chunks/PDFViewer-CDeV9OBs.js +0 -2
- package/dist/chunks/PDFViewer-D_3V8QJe.js +0 -2
- package/dist/chunks/Rest-B1eUyLX5.js +0 -2
- package/dist/chunks/Rest-B1eUyLX5.js.map +0 -1
- package/dist/chunks/Rest-BJ3Mvx1L.js +0 -2
- package/dist/chunks/Rest-BJ3Mvx1L.js.map +0 -1
- package/dist/chunks/TableView-CI_7a-kD.js +0 -2
- package/dist/chunks/TableView-CI_7a-kD.js.map +0 -1
- package/dist/chunks/TableView-CWk5k4LQ.js +0 -2
- package/dist/chunks/TableView-CWk5k4LQ.js.map +0 -1
- package/dist/chunks/ToastService-C2tTooFn.js +0 -3
- package/dist/chunks/ToastService-C2tTooFn.js.map +0 -1
- package/dist/chunks/ToastService-nUaGVpSl.js +0 -3
- package/dist/chunks/ToastService-nUaGVpSl.js.map +0 -1
- package/dist/chunks/TokenManager-ien2XzwO.js +0 -2
- package/dist/chunks/TokenManager-sZgt--C9.js +0 -2
- package/dist/chunks/User-BL9M_PWB.js +0 -2
- package/dist/chunks/User-BL9M_PWB.js.map +0 -1
- package/dist/chunks/User-DqHG5Gr1.js +0 -2
- package/dist/chunks/User-DqHG5Gr1.js.map +0 -1
- package/dist/chunks/UserProfileView-DnVMHcLH.js +0 -2
- package/dist/chunks/UserProfileView-DnVMHcLH.js.map +0 -1
- package/dist/chunks/UserProfileView-kupeq2rN.js +0 -2
- package/dist/chunks/UserProfileView-kupeq2rN.js.map +0 -1
- package/dist/chunks/WebApp-Bti0Gqqo.js +0 -2
- package/dist/chunks/WebApp-Bti0Gqqo.js.map +0 -1
- package/dist/chunks/WebApp-CcVF73yg.js +0 -2
- package/dist/chunks/WebApp-CcVF73yg.js.map +0 -1
- package/dist/chunks/WebSocketClient-Bh0Mmtje.js +0 -2
- package/dist/chunks/WebSocketClient-CLgYPxWX.js +0 -2
- package/dist/chunks/index-Aq9ke4vg.js +0 -2
- package/dist/chunks/index-Da9sT-tE.js +0 -2
package/dist/mojo-auth.es.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
var
|
|
2
|
-
var y =
|
|
3
|
-
(function(
|
|
4
|
-
typeof define == "function" && define.amd ? define([],
|
|
1
|
+
var k = (g, u) => () => (u || g((u = { exports: {} }).exports, u), u.exports);
|
|
2
|
+
var y = k((S, p) => {
|
|
3
|
+
(function(g, u) {
|
|
4
|
+
typeof define == "function" && define.amd ? define([], u) : typeof p < "u" && p.exports ? p.exports = u() : g.MojoAuth = u();
|
|
5
5
|
})(typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : void 0, function() {
|
|
6
|
-
var
|
|
6
|
+
var g = "", u = {}, c = {
|
|
7
7
|
access: "access_token",
|
|
8
8
|
refresh: "refresh_token"
|
|
9
9
|
}, w = {
|
|
@@ -20,12 +20,12 @@ var y = v((S, d) => {
|
|
|
20
20
|
refreshToken: "/api/refresh_token"
|
|
21
21
|
};
|
|
22
22
|
function i(e, r) {
|
|
23
|
-
var t =
|
|
23
|
+
var t = u[e] || w[e] || "";
|
|
24
24
|
return r && Object.keys(r).forEach(function(n) {
|
|
25
25
|
t = t.replace("{" + n + "}", r[n]);
|
|
26
|
-
}),
|
|
26
|
+
}), g.replace(/\/$/, "") + t;
|
|
27
27
|
}
|
|
28
|
-
function
|
|
28
|
+
function s(e, r, t) {
|
|
29
29
|
var n = Object.assign({ "Content-Type": "application/json" }, {});
|
|
30
30
|
return fetch(e, {
|
|
31
31
|
method: "POST",
|
|
@@ -49,25 +49,25 @@ var y = v((S, d) => {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
function
|
|
52
|
+
function h(e) {
|
|
53
53
|
var r = e && e.data ? e.data : e;
|
|
54
54
|
if (!r || !r.access_token) throw new Error("No access_token in response");
|
|
55
55
|
return localStorage.setItem(c.access, r.access_token), r.refresh_token && localStorage.setItem(c.refresh, r.refresh_token), r;
|
|
56
56
|
}
|
|
57
|
-
function
|
|
57
|
+
function v(e) {
|
|
58
58
|
return e ? typeof e == "string" ? e : e.message || e.error || Array.isArray(e.errors) && e.errors[0] && e.errors[0].message || "An error occurred" : "An error occurred";
|
|
59
59
|
}
|
|
60
|
-
function
|
|
60
|
+
function d(e) {
|
|
61
61
|
for (var r = e.replace(/-/g, "+").replace(/_/g, "/"), t = atob(r), n = new Uint8Array(t.length), o = 0; o < t.length; o++)
|
|
62
62
|
n[o] = t.charCodeAt(o);
|
|
63
63
|
return n.buffer;
|
|
64
64
|
}
|
|
65
|
-
function
|
|
65
|
+
function l(e) {
|
|
66
66
|
for (var r = new Uint8Array(e), t = "", n = 0; n < r.byteLength; n++)
|
|
67
67
|
t += String.fromCharCode(r[n]);
|
|
68
68
|
return btoa(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
69
69
|
}
|
|
70
|
-
var
|
|
70
|
+
var f = {
|
|
71
71
|
/**
|
|
72
72
|
* Initialize the library. Must be called before any auth method.
|
|
73
73
|
* @param {object} config
|
|
@@ -76,7 +76,7 @@ var y = v((S, d) => {
|
|
|
76
76
|
*/
|
|
77
77
|
init: function(e) {
|
|
78
78
|
if (!e || !e.baseURL) throw new Error("MojoAuth.init: baseURL is required");
|
|
79
|
-
|
|
79
|
+
g = e.baseURL, u = Object.assign({}, e.endpoints || {});
|
|
80
80
|
},
|
|
81
81
|
// -----------------------------------------------------------------------
|
|
82
82
|
// JWT Login
|
|
@@ -92,9 +92,9 @@ var y = v((S, d) => {
|
|
|
92
92
|
* instead of tokens. Check result.mfa_required before assuming login is complete.
|
|
93
93
|
*/
|
|
94
94
|
login: function(e, r) {
|
|
95
|
-
return
|
|
95
|
+
return s(i("login"), { username: e, password: r }).then(function(t) {
|
|
96
96
|
var n = t.data || t;
|
|
97
|
-
return n.mfa_required ? n :
|
|
97
|
+
return n.mfa_required ? n : h(t);
|
|
98
98
|
});
|
|
99
99
|
},
|
|
100
100
|
// -----------------------------------------------------------------------
|
|
@@ -106,7 +106,7 @@ var y = v((S, d) => {
|
|
|
106
106
|
* @returns {Promise<object>}
|
|
107
107
|
*/
|
|
108
108
|
forgotPasswordCode: function(e) {
|
|
109
|
-
return
|
|
109
|
+
return s(i("forgotPassword"), { email: e, method: "code" });
|
|
110
110
|
},
|
|
111
111
|
/**
|
|
112
112
|
* Complete password reset using the emailed code.
|
|
@@ -117,11 +117,11 @@ var y = v((S, d) => {
|
|
|
117
117
|
* @returns {Promise<object>}
|
|
118
118
|
*/
|
|
119
119
|
resetWithCode: function(e, r, t) {
|
|
120
|
-
return
|
|
120
|
+
return s(i("resetWithCode"), {
|
|
121
121
|
email: e,
|
|
122
122
|
code: r,
|
|
123
123
|
new_password: t
|
|
124
|
-
}).then(
|
|
124
|
+
}).then(h);
|
|
125
125
|
},
|
|
126
126
|
// -----------------------------------------------------------------------
|
|
127
127
|
// Password Reset — Link / Token method
|
|
@@ -132,7 +132,7 @@ var y = v((S, d) => {
|
|
|
132
132
|
* @returns {Promise<object>}
|
|
133
133
|
*/
|
|
134
134
|
forgotPasswordLink: function(e) {
|
|
135
|
-
return
|
|
135
|
+
return s(i("forgotPassword"), { email: e, method: "link" });
|
|
136
136
|
},
|
|
137
137
|
/**
|
|
138
138
|
* Complete password reset using the token from the reset link email.
|
|
@@ -142,10 +142,10 @@ var y = v((S, d) => {
|
|
|
142
142
|
* @returns {Promise<object>}
|
|
143
143
|
*/
|
|
144
144
|
resetWithToken: function(e, r) {
|
|
145
|
-
return
|
|
145
|
+
return s(i("resetWithToken"), {
|
|
146
146
|
token: e,
|
|
147
147
|
new_password: r
|
|
148
|
-
}).then(
|
|
148
|
+
}).then(h);
|
|
149
149
|
},
|
|
150
150
|
// -----------------------------------------------------------------------
|
|
151
151
|
// Magic Login (passwordless email link)
|
|
@@ -156,7 +156,7 @@ var y = v((S, d) => {
|
|
|
156
156
|
* @returns {Promise<object>}
|
|
157
157
|
*/
|
|
158
158
|
sendMagicLink: function(e) {
|
|
159
|
-
return
|
|
159
|
+
return s(i("magicSend"), { email: e });
|
|
160
160
|
},
|
|
161
161
|
/**
|
|
162
162
|
* Complete login using the ml: token from the magic link URL.
|
|
@@ -165,7 +165,7 @@ var y = v((S, d) => {
|
|
|
165
165
|
* @returns {Promise<object>}
|
|
166
166
|
*/
|
|
167
167
|
loginWithMagicToken: function(e) {
|
|
168
|
-
return
|
|
168
|
+
return s(i("magicLogin"), { token: e }).then(h);
|
|
169
169
|
},
|
|
170
170
|
/**
|
|
171
171
|
* Convenience: read ?token= from current URL and login if it's a magic link token.
|
|
@@ -177,7 +177,7 @@ var y = v((S, d) => {
|
|
|
177
177
|
if (!r || r.indexOf("ml:") !== 0) return Promise.resolve(null);
|
|
178
178
|
e.delete("token");
|
|
179
179
|
var t = e.toString() ? window.location.pathname + "?" + e.toString() : window.location.pathname;
|
|
180
|
-
return window.history.replaceState({}, "", t),
|
|
180
|
+
return window.history.replaceState({}, "", t), f.loginWithMagicToken(r);
|
|
181
181
|
},
|
|
182
182
|
// -----------------------------------------------------------------------
|
|
183
183
|
// Passkey Login (WebAuthn)
|
|
@@ -189,6 +189,37 @@ var y = v((S, d) => {
|
|
|
189
189
|
isPasskeySupported: function() {
|
|
190
190
|
return typeof window < "u" && typeof window.PublicKeyCredential < "u" && typeof navigator.credentials < "u" && typeof navigator.credentials.get == "function";
|
|
191
191
|
},
|
|
192
|
+
/**
|
|
193
|
+
* Login with a passkey (WebAuthn) without requiring a username.
|
|
194
|
+
* Uses discoverable credentials — browser shows all passkeys for this domain.
|
|
195
|
+
* Handles the full begin → browser prompt → complete flow.
|
|
196
|
+
* Stores tokens on success.
|
|
197
|
+
* @returns {Promise<object>}
|
|
198
|
+
*/
|
|
199
|
+
loginWithPasskeyDiscoverable: function() {
|
|
200
|
+
return f.isPasskeySupported() ? s(i("passkeyLoginBegin"), {}).then(function(e) {
|
|
201
|
+
var r = e.data || e, t = r.challenge_id, n = r.publicKey;
|
|
202
|
+
return n.challenge = d(n.challenge), n.allowCredentials && (n.allowCredentials = n.allowCredentials.map(function(o) {
|
|
203
|
+
return Object.assign({}, o, { id: d(o.id) });
|
|
204
|
+
})), navigator.credentials.get({ publicKey: n }).then(function(o) {
|
|
205
|
+
if (!o) throw new Error("No credential received from authenticator");
|
|
206
|
+
return s(i("passkeyLoginComplete"), {
|
|
207
|
+
challenge_id: t,
|
|
208
|
+
credential: {
|
|
209
|
+
id: o.id,
|
|
210
|
+
rawId: l(o.rawId),
|
|
211
|
+
type: o.type,
|
|
212
|
+
response: {
|
|
213
|
+
clientDataJSON: l(o.response.clientDataJSON),
|
|
214
|
+
authenticatorData: l(o.response.authenticatorData),
|
|
215
|
+
signature: l(o.response.signature),
|
|
216
|
+
userHandle: o.response.userHandle ? l(o.response.userHandle) : null
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}).then(h) : Promise.reject(new Error("Passkeys are not supported in this browser"));
|
|
222
|
+
},
|
|
192
223
|
/**
|
|
193
224
|
* Login with a passkey (WebAuthn).
|
|
194
225
|
* Handles the full begin → browser prompt → complete flow.
|
|
@@ -197,28 +228,28 @@ var y = v((S, d) => {
|
|
|
197
228
|
* @returns {Promise<object>}
|
|
198
229
|
*/
|
|
199
230
|
loginWithPasskey: function(e) {
|
|
200
|
-
return
|
|
231
|
+
return f.isPasskeySupported() ? s(i("passkeyLoginBegin"), { username: e }).then(function(r) {
|
|
201
232
|
var t = r.data || r, n = t.challenge_id, o = t.publicKey;
|
|
202
|
-
return o.challenge =
|
|
203
|
-
return Object.assign({}, a, { id:
|
|
233
|
+
return o.challenge = d(o.challenge), o.allowCredentials && (o.allowCredentials = o.allowCredentials.map(function(a) {
|
|
234
|
+
return Object.assign({}, a, { id: d(a.id) });
|
|
204
235
|
})), navigator.credentials.get({ publicKey: o }).then(function(a) {
|
|
205
236
|
if (!a) throw new Error("No credential received from authenticator");
|
|
206
|
-
return
|
|
237
|
+
return s(i("passkeyLoginComplete"), {
|
|
207
238
|
challenge_id: n,
|
|
208
239
|
credential: {
|
|
209
240
|
id: a.id,
|
|
210
|
-
rawId:
|
|
241
|
+
rawId: l(a.rawId),
|
|
211
242
|
type: a.type,
|
|
212
243
|
response: {
|
|
213
|
-
clientDataJSON:
|
|
214
|
-
authenticatorData:
|
|
215
|
-
signature:
|
|
216
|
-
userHandle: a.response.userHandle ?
|
|
244
|
+
clientDataJSON: l(a.response.clientDataJSON),
|
|
245
|
+
authenticatorData: l(a.response.authenticatorData),
|
|
246
|
+
signature: l(a.response.signature),
|
|
247
|
+
userHandle: a.response.userHandle ? l(a.response.userHandle) : null
|
|
217
248
|
}
|
|
218
249
|
}
|
|
219
250
|
});
|
|
220
251
|
});
|
|
221
|
-
}).then(
|
|
252
|
+
}).then(h) : Promise.reject(new Error("Passkeys are not supported in this browser"));
|
|
222
253
|
},
|
|
223
254
|
// -----------------------------------------------------------------------
|
|
224
255
|
// Google OAuth
|
|
@@ -228,25 +259,45 @@ var y = v((S, d) => {
|
|
|
228
259
|
* Fetches the authorization URL from the backend and redirects the browser.
|
|
229
260
|
* @returns {Promise<void>}
|
|
230
261
|
*/
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Start an OAuth login flow for any provider.
|
|
264
|
+
* Stores the provider in sessionStorage so the callback page knows which provider to complete.
|
|
265
|
+
* @param {string} provider - e.g. 'google', 'apple'
|
|
266
|
+
* @param {string} [callbackUrl] - URL Google/Apple should redirect back to.
|
|
267
|
+
* Defaults to the current page URL (strip query/hash).
|
|
268
|
+
* Must be registered in the provider's console AND
|
|
269
|
+
* allowed by the backend (ALLOWED_REDIRECT_URLS or per-group).
|
|
270
|
+
* @returns {Promise<void>}
|
|
271
|
+
*/
|
|
272
|
+
startOAuthLogin: function(e, r) {
|
|
273
|
+
sessionStorage.setItem("oauth_provider", e);
|
|
274
|
+
var t = r || window.location.origin + window.location.pathname, n = i("oauthBegin", { provider: e }) + "?redirect_uri=" + encodeURIComponent(t);
|
|
275
|
+
return m(n).then(function(o) {
|
|
276
|
+
var a = o.data || o;
|
|
277
|
+
if (!a.auth_url) throw new Error("No auth_url in OAuth begin response");
|
|
278
|
+
window.location.href = a.auth_url;
|
|
236
279
|
});
|
|
237
280
|
},
|
|
281
|
+
startGoogleLogin: function(e) {
|
|
282
|
+
return f.startOAuthLogin("google", e);
|
|
283
|
+
},
|
|
238
284
|
/**
|
|
239
|
-
* Complete
|
|
285
|
+
* Complete OAuth login for any provider — call this on your OAuth callback page.
|
|
240
286
|
* Reads ?code and ?state from the current URL automatically.
|
|
241
287
|
* Stores tokens on success.
|
|
288
|
+
* @param {string} provider - e.g. 'google'
|
|
242
289
|
* @returns {Promise<object>}
|
|
243
290
|
*/
|
|
291
|
+
completeOAuthLogin: function(e) {
|
|
292
|
+
var r = new URLSearchParams(window.location.search), t = r.get("code"), n = r.get("state");
|
|
293
|
+
return t ? s(i("oauthComplete", { provider: e }), {
|
|
294
|
+
code: t,
|
|
295
|
+
state: n
|
|
296
|
+
}).then(h) : Promise.reject(new Error("No OAuth code in URL"));
|
|
297
|
+
},
|
|
298
|
+
/** @deprecated use completeOAuthLogin('google') */
|
|
244
299
|
completeGoogleLogin: function() {
|
|
245
|
-
|
|
246
|
-
return r ? u(i("oauthComplete", { provider: "google" }), {
|
|
247
|
-
code: r,
|
|
248
|
-
state: t
|
|
249
|
-
}).then(l) : Promise.reject(new Error("No OAuth code in URL"));
|
|
300
|
+
return f.completeOAuthLogin("google");
|
|
250
301
|
},
|
|
251
302
|
// -----------------------------------------------------------------------
|
|
252
303
|
// Session helpers
|
|
@@ -309,7 +360,7 @@ var y = v((S, d) => {
|
|
|
309
360
|
* @returns {boolean}
|
|
310
361
|
*/
|
|
311
362
|
isTokenExpired: function() {
|
|
312
|
-
var e =
|
|
363
|
+
var e = f.getTokenPayload();
|
|
313
364
|
return !e || !e.exp ? !0 : Math.floor(Date.now() / 1e3) >= e.exp;
|
|
314
365
|
},
|
|
315
366
|
/**
|
|
@@ -319,7 +370,7 @@ var y = v((S, d) => {
|
|
|
319
370
|
*/
|
|
320
371
|
refreshToken: function() {
|
|
321
372
|
var e = localStorage.getItem(c.refresh);
|
|
322
|
-
return e ?
|
|
373
|
+
return e ? s(i("refreshToken"), { refresh_token: e }).then(h) : Promise.reject(new Error("No refresh token stored"));
|
|
323
374
|
},
|
|
324
375
|
// -----------------------------------------------------------------------
|
|
325
376
|
// Error helper
|
|
@@ -330,9 +381,9 @@ var y = v((S, d) => {
|
|
|
330
381
|
* @param {any} err - the caught error (object, string, etc.)
|
|
331
382
|
* @returns {string}
|
|
332
383
|
*/
|
|
333
|
-
getError:
|
|
384
|
+
getError: v
|
|
334
385
|
};
|
|
335
|
-
return
|
|
386
|
+
return f;
|
|
336
387
|
});
|
|
337
388
|
});
|
|
338
389
|
export default y();
|
package/dist/mojo-auth.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(f){typeof define=="function"&&define.amd?define(f):f()})((function(){"use strict";(function(f,h){typeof define=="function"&&define.amd?define([],h):typeof module<"u"&&module.exports?module.exports=h():f.MojoAuth=h()})(typeof globalThis<"u"?globalThis:typeof window<"u"?window:void 0,function(){var f="",h={},u={access:"access_token",refresh:"refresh_token"},p={login:"/api/login",forgotPassword:"/api/auth/forgot",resetWithCode:"/api/auth/password/reset/code",resetWithToken:"/api/auth/password/reset/token",magicSend:"/api/auth/magic/send",magicLogin:"/api/auth/magic/login",passkeyLoginBegin:"/api/auth/passkeys/login/begin",passkeyLoginComplete:"/api/auth/passkeys/login/complete",oauthBegin:"/api/auth/oauth/{provider}/begin",oauthComplete:"/api/auth/oauth/{provider}/complete",refreshToken:"/api/refresh_token"};function i(e,n){var t=h[e]||p[e]||"";return n&&Object.keys(n).forEach(function(r){t=t.replace("{"+r+"}",n[r])}),f.replace(/\/$/,"")+t}function s(e,n,t){var r=Object.assign({"Content-Type":"application/json"},{});return fetch(e,{method:"POST",headers:r,body:JSON.stringify(n||{})}).then(function(o){return o.json().then(function(a){if(!o.ok)throw a;return a})})}function w(e,n){return fetch(e,{method:"GET",headers:Object.assign({"Content-Type":"application/json"},{})}).then(function(t){return t.json().then(function(r){if(!t.ok)throw r;return r})})}function l(e){var n=e&&e.data?e.data:e;if(!n||!n.access_token)throw new Error("No access_token in response");return localStorage.setItem(u.access,n.access_token),n.refresh_token&&localStorage.setItem(u.refresh,n.refresh_token),n}function m(e){return e?typeof e=="string"?e:e.message||e.error||Array.isArray(e.errors)&&e.errors[0]&&e.errors[0].message||"An error occurred":"An error occurred"}function g(e){for(var n=e.replace(/-/g,"+").replace(/_/g,"/"),t=atob(n),r=new Uint8Array(t.length),o=0;o<t.length;o++)r[o]=t.charCodeAt(o);return r.buffer}function c(e){for(var n=new Uint8Array(e),t="",r=0;r<n.byteLength;r++)t+=String.fromCharCode(n[r]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}var d={init:function(e){if(!e||!e.baseURL)throw new Error("MojoAuth.init: baseURL is required");f=e.baseURL,h=Object.assign({},e.endpoints||{})},login:function(e,n){return s(i("login"),{username:e,password:n}).then(function(t){var r=t.data||t;return r.mfa_required?r:l(t)})},forgotPasswordCode:function(e){return s(i("forgotPassword"),{email:e,method:"code"})},resetWithCode:function(e,n,t){return s(i("resetWithCode"),{email:e,code:n,new_password:t}).then(l)},forgotPasswordLink:function(e){return s(i("forgotPassword"),{email:e,method:"link"})},resetWithToken:function(e,n){return s(i("resetWithToken"),{token:e,new_password:n}).then(l)},sendMagicLink:function(e){return s(i("magicSend"),{email:e})},loginWithMagicToken:function(e){return s(i("magicLogin"),{token:e}).then(l)},handleMagicTokenFromURL:function(){var e=new URLSearchParams(window.location.search),n=e.get("token");if(!n||n.indexOf("ml:")!==0)return Promise.resolve(null);e.delete("token");var t=e.toString()?window.location.pathname+"?"+e.toString():window.location.pathname;return window.history.replaceState({},"",t),d.loginWithMagicToken(n)},isPasskeySupported:function(){return typeof window<"u"&&typeof window.PublicKeyCredential<"u"&&typeof navigator.credentials<"u"&&typeof navigator.credentials.get=="function"},loginWithPasskeyDiscoverable:function(){return d.isPasskeySupported()?s(i("passkeyLoginBegin"),{}).then(function(e){var n=e.data||e,t=n.challenge_id,r=n.publicKey;return r.challenge=g(r.challenge),r.allowCredentials&&(r.allowCredentials=r.allowCredentials.map(function(o){return Object.assign({},o,{id:g(o.id)})})),navigator.credentials.get({publicKey:r}).then(function(o){if(!o)throw new Error("No credential received from authenticator");return s(i("passkeyLoginComplete"),{challenge_id:t,credential:{id:o.id,rawId:c(o.rawId),type:o.type,response:{clientDataJSON:c(o.response.clientDataJSON),authenticatorData:c(o.response.authenticatorData),signature:c(o.response.signature),userHandle:o.response.userHandle?c(o.response.userHandle):null}}})})}).then(l):Promise.reject(new Error("Passkeys are not supported in this browser"))},loginWithPasskey:function(e){return d.isPasskeySupported()?s(i("passkeyLoginBegin"),{username:e}).then(function(n){var t=n.data||n,r=t.challenge_id,o=t.publicKey;return o.challenge=g(o.challenge),o.allowCredentials&&(o.allowCredentials=o.allowCredentials.map(function(a){return Object.assign({},a,{id:g(a.id)})})),navigator.credentials.get({publicKey:o}).then(function(a){if(!a)throw new Error("No credential received from authenticator");return s(i("passkeyLoginComplete"),{challenge_id:r,credential:{id:a.id,rawId:c(a.rawId),type:a.type,response:{clientDataJSON:c(a.response.clientDataJSON),authenticatorData:c(a.response.authenticatorData),signature:c(a.response.signature),userHandle:a.response.userHandle?c(a.response.userHandle):null}}})})}).then(l):Promise.reject(new Error("Passkeys are not supported in this browser"))},startOAuthLogin:function(e,n){sessionStorage.setItem("oauth_provider",e);var t=n||window.location.origin+window.location.pathname,r=i("oauthBegin",{provider:e})+"?redirect_uri="+encodeURIComponent(t);return w(r).then(function(o){var a=o.data||o;if(!a.auth_url)throw new Error("No auth_url in OAuth begin response");window.location.href=a.auth_url})},startGoogleLogin:function(e){return d.startOAuthLogin("google",e)},completeOAuthLogin:function(e){var n=new URLSearchParams(window.location.search),t=n.get("code"),r=n.get("state");return t?s(i("oauthComplete",{provider:e}),{code:t,state:r}).then(l):Promise.reject(new Error("No OAuth code in URL"))},completeGoogleLogin:function(){return d.completeOAuthLogin("google")},logout:function(){localStorage.removeItem(u.access),localStorage.removeItem(u.refresh)},isAuthenticated:function(){return!!localStorage.getItem(u.access)},getToken:function(){return localStorage.getItem(u.access)},getRefreshToken:function(){return localStorage.getItem(u.refresh)},getAuthHeader:function(){var e=localStorage.getItem(u.access);return e?"Bearer "+e:null},getTokenPayload:function(){var e=localStorage.getItem(u.access);if(!e)return null;try{var n=e.split(".");if(n.length!==3)return null;var t=n[1].replace(/-/g,"+").replace(/_/g,"/"),r=4-t.length%4;return r!==4&&(t+="====".slice(0,r)),JSON.parse(atob(t))}catch{return null}},isTokenExpired:function(){var e=d.getTokenPayload();return!e||!e.exp?!0:Math.floor(Date.now()/1e3)>=e.exp},refreshToken:function(){var e=localStorage.getItem(u.refresh);return e?s(i("refreshToken"),{refresh_token:e}).then(l):Promise.reject(new Error("No refresh token stored"))},getError:m};return d})}));
|
package/dist/timeline.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/ListView-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/ListView-2M4I8KHF.js"),e=require("./chunks/Collection-BUv4E9op.js");class TimelineViewItem extends t.ListViewItem{constructor(t={}){super({className:"timeline-item",...t}),this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.template||(this.template='\n <div class="timeline-marker timeline-marker-{{markerType}}">\n {{#hasIcon}}\n <i class="bi {{model.icon}} text-{{displayColor}}"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class="timeline-dot bg-{{displayColor}}"></div>\n {{/hasIcon}}\n </div>\n \n <div class="timeline-content">\n {{#showDate}}\n <div class="timeline-date text-muted small">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class="timeline-card">\n {{#model.title}}\n <h6 class="timeline-title mb-1">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class="timeline-description mb-0">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class="timeline-meta mt-2 text-muted small">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n ')}async onInit(){await super.onInit(),this.processItemData()}processItemData(){this.displayColor=this.model?.get?.("color")||this.model?.color||this.theme;const t=!(!this.model?.get?.("icon")&&!this.model?.icon)&&"icon"===this.dotStyle;this.hasIcon=t,this.markerType=t?"icon":this.dotStyle;const e=this.model?.get?.("date")||this.model?.date;this.formattedDate=this.formatDate(e)}formatDate(t){if(!t)return"";switch(this.dateFormat){case"datetime":return e.dataFormatter.pipe(t,"datetime");case"relative":return e.dataFormatter.pipe(t,"timeago");default:return e.dataFormatter.pipe(t,"date")}}async onActionSelect(t,e){t.stopPropagation(),this.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model})}}class TimelineView extends t.ListView{constructor(t={}){super({className:"timeline-view",itemClass:t.itemClass||TimelineViewItem,selectionMode:"none",emptyMessage:t.emptyMessage||"No timeline events to display",template:'\n <div class="timeline-container timeline-{{position}}">\n {{#loading}}\n <div class="timeline-loading text-center py-4">\n <div class="spinner-border spinner-border-sm" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <span class="ms-2 text-muted">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="timeline-empty text-center text-muted py-4">\n <i class="bi bi-clock-history fs-1 d-block mb-2"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class="timeline" data-container="items"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ',...t}),this.position=t.position||"left",this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.groupBy=t.groupBy||"none"}_createItemView(t,e){if(this.itemViews.has(t.id))return;const i=new this.itemClass({model:t,index:e,listView:this,template:this.itemTemplate,dateFormat:this.dateFormat,dotStyle:this.dotStyle,showDate:this.showDate,theme:this.theme});return this.itemViews.set(t.id,i),i.on("item:click",this._onItemClick.bind(this)),i}_onItemClick(t){this.emit("item:click",t)}setPosition(t){return"left"!==t&&"center"!==t?(console.warn('Invalid position. Use "left" or "center"'),this):(this.position=t,this.isMounted()&&this.render(),this)}setDateFormat(t){return this.dateFormat=t,this.forEachItem(e=>{e.dateFormat=t,e.processItemData(),e.isMounted()&&e.render()}),this}setDotStyle(t){return this.dotStyle=t,this.forEachItem(e=>{e.dotStyle=t,e.processItemData(),e.isMounted()&&e.render()}),this}toggleDates(t=null){return this.showDate=null!==t?t:!this.showDate,this.forEachItem(t=>{t.showDate=this.showDate,t.isMounted()&&t.render()}),this}}exports.ListView=t.ListView,exports.ListViewItem=t.ListViewItem,exports.Collection=e.Collection,exports.Model=e.Model,exports.View=e.View,exports.TimelineView=TimelineView,exports.TimelineViewItem=TimelineViewItem;
|
|
2
2
|
//# sourceMappingURL=timeline.cjs.js.map
|
package/dist/timeline.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.cjs.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n}\n\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-loading text-center py-4\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"timeline.cjs.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n}\n\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-loading text-center py-4\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"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"}
|
package/dist/timeline.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{L as t,a as e}from"./chunks/ListView-
|
|
1
|
+
import{L as t,a as e}from"./chunks/ListView-B0QbqSPv.js";import{d as i}from"./chunks/Collection-r1ACzUeh.js";import{C as s,M as n,V as o}from"./chunks/Collection-r1ACzUeh.js";class TimelineViewItem extends t{constructor(t={}){super({className:"timeline-item",...t}),this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.template||(this.template='\n <div class="timeline-marker timeline-marker-{{markerType}}">\n {{#hasIcon}}\n <i class="bi {{model.icon}} text-{{displayColor}}"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class="timeline-dot bg-{{displayColor}}"></div>\n {{/hasIcon}}\n </div>\n \n <div class="timeline-content">\n {{#showDate}}\n <div class="timeline-date text-muted small">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class="timeline-card">\n {{#model.title}}\n <h6 class="timeline-title mb-1">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class="timeline-description mb-0">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class="timeline-meta mt-2 text-muted small">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n ')}async onInit(){await super.onInit(),this.processItemData()}processItemData(){this.displayColor=this.model?.get?.("color")||this.model?.color||this.theme;const t=!(!this.model?.get?.("icon")&&!this.model?.icon)&&"icon"===this.dotStyle;this.hasIcon=t,this.markerType=t?"icon":this.dotStyle;const e=this.model?.get?.("date")||this.model?.date;this.formattedDate=this.formatDate(e)}formatDate(t){if(!t)return"";switch(this.dateFormat){case"datetime":return i.pipe(t,"datetime");case"relative":return i.pipe(t,"timeago");default:return i.pipe(t,"date")}}async onActionSelect(t,e){t.stopPropagation(),this.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model})}}class TimelineView extends e{constructor(t={}){super({className:"timeline-view",itemClass:t.itemClass||TimelineViewItem,selectionMode:"none",emptyMessage:t.emptyMessage||"No timeline events to display",template:'\n <div class="timeline-container timeline-{{position}}">\n {{#loading}}\n <div class="timeline-loading text-center py-4">\n <div class="spinner-border spinner-border-sm" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <span class="ms-2 text-muted">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="timeline-empty text-center text-muted py-4">\n <i class="bi bi-clock-history fs-1 d-block mb-2"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class="timeline" data-container="items"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ',...t}),this.position=t.position||"left",this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.groupBy=t.groupBy||"none"}_createItemView(t,e){if(this.itemViews.has(t.id))return;const i=new this.itemClass({model:t,index:e,listView:this,template:this.itemTemplate,dateFormat:this.dateFormat,dotStyle:this.dotStyle,showDate:this.showDate,theme:this.theme});return this.itemViews.set(t.id,i),i.on("item:click",this._onItemClick.bind(this)),i}_onItemClick(t){this.emit("item:click",t)}setPosition(t){return"left"!==t&&"center"!==t?(console.warn('Invalid position. Use "left" or "center"'),this):(this.position=t,this.isMounted()&&this.render(),this)}setDateFormat(t){return this.dateFormat=t,this.forEachItem(e=>{e.dateFormat=t,e.processItemData(),e.isMounted()&&e.render()}),this}setDotStyle(t){return this.dotStyle=t,this.forEachItem(e=>{e.dotStyle=t,e.processItemData(),e.isMounted()&&e.render()}),this}toggleDates(t=null){return this.showDate=null!==t?t:!this.showDate,this.forEachItem(t=>{t.showDate=this.showDate,t.isMounted()&&t.render()}),this}}export{s as Collection,e as ListView,t as ListViewItem,n as Model,TimelineView,TimelineViewItem,o as View};
|
|
2
2
|
//# sourceMappingURL=timeline.es.js.map
|
package/dist/timeline.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.es.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n}\n\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-loading text-center py-4\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"timeline.es.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n}\n\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-loading text-center py-4\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"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"}
|