jupyterlab_notifications_extension 1.2.20 → 1.2.22
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/lib/index.js +66 -3
- package/lib/utils.d.ts +2 -2
- package/lib/utils.js +3 -6
- package/package.json +1 -1
- package/src/__tests__/utils.spec.ts +4 -8
- package/src/index.ts +70 -3
- package/src/utils.ts +3 -6
package/lib/index.js
CHANGED
|
@@ -136,12 +136,67 @@ function injectTimeAgoIntoCenter(center) {
|
|
|
136
136
|
}
|
|
137
137
|
}, 10000);
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Inject time-ago into a single toast element using Notification.manager
|
|
141
|
+
* timestamps. Used by the MutationObserver for toasts NOT created by our
|
|
142
|
+
* extension (e.g. JupyterLab culler, file upload, kernel notifications).
|
|
143
|
+
*/
|
|
144
|
+
function injectTimeAgoIntoToast(toast) {
|
|
145
|
+
const msgEl = toast.querySelector('.jp-toast-message');
|
|
146
|
+
if (!msgEl) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const parent = msgEl.parentElement;
|
|
150
|
+
if (msgEl.querySelector('.jp-toast-time-ago') ||
|
|
151
|
+
(parent && parent.querySelector('.jp-toast-time-ago'))) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const msg = normalizeMsg(msgEl.innerText || '');
|
|
155
|
+
// Check server-side map first, then fall back to manager timestamps
|
|
156
|
+
const serverTs = serverCreatedAtMap.get(msg);
|
|
157
|
+
let createdAt = null;
|
|
158
|
+
if (serverTs && serverTs.length > 0) {
|
|
159
|
+
createdAt = serverTs[0];
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
for (const n of Notification.manager.notifications) {
|
|
163
|
+
if (normalizeMsg(n.message) === msg) {
|
|
164
|
+
createdAt = n.createdAt;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (createdAt === null) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const timeEl = createTimeAgoElement(createdAt);
|
|
173
|
+
const bar = parent ? parent.querySelector('.jp-toast-buttonBar') : null;
|
|
174
|
+
if (bar) {
|
|
175
|
+
timeEl.style.marginTop = '0';
|
|
176
|
+
bar.insertBefore(timeEl, bar.firstChild);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
msgEl.appendChild(timeEl);
|
|
180
|
+
}
|
|
181
|
+
// Refresh while the toast is in the DOM
|
|
182
|
+
const refreshInterval = setInterval(() => {
|
|
183
|
+
if (!document.body.contains(timeEl)) {
|
|
184
|
+
clearInterval(refreshInterval);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const ts = Number(timeEl.dataset.createdAt);
|
|
188
|
+
if (ts) {
|
|
189
|
+
timeEl.textContent = formatTimeAgo(ts);
|
|
190
|
+
}
|
|
191
|
+
}, 10000);
|
|
192
|
+
}
|
|
139
193
|
/**
|
|
140
194
|
* Set up a MutationObserver to watch for the Notification Center
|
|
141
|
-
* opening and
|
|
195
|
+
* opening and for any toast popup appearing, injecting time-ago
|
|
196
|
+
* indicators into both.
|
|
142
197
|
*
|
|
143
|
-
* Uses subtree observation to catch
|
|
144
|
-
*
|
|
198
|
+
* Uses subtree observation to catch the center being added,
|
|
199
|
+
* its list items being populated, and individual toast popups.
|
|
145
200
|
*/
|
|
146
201
|
function observeNotificationCenter() {
|
|
147
202
|
const observer = new MutationObserver(mutations => {
|
|
@@ -163,6 +218,14 @@ function observeNotificationCenter() {
|
|
|
163
218
|
const existingCenter = node.closest('.jp-Notification-Center');
|
|
164
219
|
if (existingCenter) {
|
|
165
220
|
setTimeout(() => injectTimeAgoIntoCenter(existingCenter), 100);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// Catch toast popups (from any source, not just our extension)
|
|
224
|
+
const toast = node.classList.contains('Toastify__toast')
|
|
225
|
+
? node
|
|
226
|
+
: node.querySelector('.Toastify__toast');
|
|
227
|
+
if (toast instanceof HTMLElement) {
|
|
228
|
+
setTimeout(() => injectTimeAgoIntoToast(toast), 100);
|
|
166
229
|
}
|
|
167
230
|
}
|
|
168
231
|
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Format a Unix timestamp (ms) as a relative time string.
|
|
3
3
|
*
|
|
4
|
-
* Returns a compact label such as "just now", "
|
|
5
|
-
* "
|
|
4
|
+
* Returns a compact label such as "just now", "5m ago", "2h ago",
|
|
5
|
+
* or "3d ago". Anything under 60 seconds is shown as "just now".
|
|
6
6
|
*/
|
|
7
7
|
export declare function formatTimeAgo(createdAt: number): string;
|
|
8
8
|
/**
|
package/lib/utils.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Format a Unix timestamp (ms) as a relative time string.
|
|
3
3
|
*
|
|
4
|
-
* Returns a compact label such as "just now", "
|
|
5
|
-
* "
|
|
4
|
+
* Returns a compact label such as "just now", "5m ago", "2h ago",
|
|
5
|
+
* or "3d ago". Anything under 60 seconds is shown as "just now".
|
|
6
6
|
*/
|
|
7
7
|
export function formatTimeAgo(createdAt) {
|
|
8
8
|
const delta = Math.max(0, Date.now() - createdAt);
|
|
9
9
|
const seconds = Math.floor(delta / 1000);
|
|
10
|
-
if (seconds < 5) {
|
|
11
|
-
return 'just now';
|
|
12
|
-
}
|
|
13
10
|
if (seconds < 60) {
|
|
14
|
-
return
|
|
11
|
+
return 'just now';
|
|
15
12
|
}
|
|
16
13
|
const minutes = Math.floor(seconds / 60);
|
|
17
14
|
if (minutes < 60) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_notifications_extension",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.22",
|
|
4
4
|
"description": "Jupyterlab extension to receive and display notifications in the main panel. Those can be from the jupyterjub administrator or from other places.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { formatTimeAgo, appendTimeAgo } from '../utils';
|
|
2
2
|
|
|
3
3
|
describe('formatTimeAgo', () => {
|
|
4
|
-
it('returns "just now" for timestamps less than
|
|
4
|
+
it('returns "just now" for timestamps less than 60 seconds old', () => {
|
|
5
5
|
expect(formatTimeAgo(Date.now())).toBe('just now');
|
|
6
|
-
expect(formatTimeAgo(Date.now() -
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
it('returns seconds for 5-59 seconds', () => {
|
|
10
|
-
expect(formatTimeAgo(Date.now() - 5000)).toBe('5s ago');
|
|
11
|
-
expect(formatTimeAgo(Date.now() - 30000)).toBe('30s ago');
|
|
12
|
-
expect(formatTimeAgo(Date.now() - 59000)).toBe('59s ago');
|
|
6
|
+
expect(formatTimeAgo(Date.now() - 5000)).toBe('just now');
|
|
7
|
+
expect(formatTimeAgo(Date.now() - 30000)).toBe('just now');
|
|
8
|
+
expect(formatTimeAgo(Date.now() - 59000)).toBe('just now');
|
|
13
9
|
});
|
|
14
10
|
|
|
15
11
|
it('returns minutes for 1-59 minutes', () => {
|
package/src/index.ts
CHANGED
|
@@ -192,12 +192,71 @@ function injectTimeAgoIntoCenter(center: Element): void {
|
|
|
192
192
|
}, 10000);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Inject time-ago into a single toast element using Notification.manager
|
|
197
|
+
* timestamps. Used by the MutationObserver for toasts NOT created by our
|
|
198
|
+
* extension (e.g. JupyterLab culler, file upload, kernel notifications).
|
|
199
|
+
*/
|
|
200
|
+
function injectTimeAgoIntoToast(toast: HTMLElement): void {
|
|
201
|
+
const msgEl = toast.querySelector('.jp-toast-message') as HTMLElement | null;
|
|
202
|
+
if (!msgEl) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const parent = msgEl.parentElement;
|
|
206
|
+
if (
|
|
207
|
+
msgEl.querySelector('.jp-toast-time-ago') ||
|
|
208
|
+
(parent && parent.querySelector('.jp-toast-time-ago'))
|
|
209
|
+
) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const msg = normalizeMsg(msgEl.innerText || '');
|
|
213
|
+
|
|
214
|
+
// Check server-side map first, then fall back to manager timestamps
|
|
215
|
+
const serverTs = serverCreatedAtMap.get(msg);
|
|
216
|
+
let createdAt: number | null = null;
|
|
217
|
+
if (serverTs && serverTs.length > 0) {
|
|
218
|
+
createdAt = serverTs[0];
|
|
219
|
+
} else {
|
|
220
|
+
for (const n of Notification.manager.notifications) {
|
|
221
|
+
if (normalizeMsg(n.message) === msg) {
|
|
222
|
+
createdAt = n.createdAt;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (createdAt === null) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const timeEl = createTimeAgoElement(createdAt);
|
|
232
|
+
const bar = parent ? parent.querySelector('.jp-toast-buttonBar') : null;
|
|
233
|
+
if (bar) {
|
|
234
|
+
timeEl.style.marginTop = '0';
|
|
235
|
+
bar.insertBefore(timeEl, bar.firstChild);
|
|
236
|
+
} else {
|
|
237
|
+
msgEl.appendChild(timeEl);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Refresh while the toast is in the DOM
|
|
241
|
+
const refreshInterval = setInterval(() => {
|
|
242
|
+
if (!document.body.contains(timeEl)) {
|
|
243
|
+
clearInterval(refreshInterval);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const ts = Number(timeEl.dataset.createdAt);
|
|
247
|
+
if (ts) {
|
|
248
|
+
timeEl.textContent = formatTimeAgo(ts);
|
|
249
|
+
}
|
|
250
|
+
}, 10000);
|
|
251
|
+
}
|
|
252
|
+
|
|
195
253
|
/**
|
|
196
254
|
* Set up a MutationObserver to watch for the Notification Center
|
|
197
|
-
* opening and
|
|
255
|
+
* opening and for any toast popup appearing, injecting time-ago
|
|
256
|
+
* indicators into both.
|
|
198
257
|
*
|
|
199
|
-
* Uses subtree observation to catch
|
|
200
|
-
*
|
|
258
|
+
* Uses subtree observation to catch the center being added,
|
|
259
|
+
* its list items being populated, and individual toast popups.
|
|
201
260
|
*/
|
|
202
261
|
function observeNotificationCenter(): void {
|
|
203
262
|
const observer = new MutationObserver(mutations => {
|
|
@@ -219,6 +278,14 @@ function observeNotificationCenter(): void {
|
|
|
219
278
|
const existingCenter = node.closest('.jp-Notification-Center');
|
|
220
279
|
if (existingCenter) {
|
|
221
280
|
setTimeout(() => injectTimeAgoIntoCenter(existingCenter), 100);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
// Catch toast popups (from any source, not just our extension)
|
|
284
|
+
const toast = node.classList.contains('Toastify__toast')
|
|
285
|
+
? node
|
|
286
|
+
: node.querySelector('.Toastify__toast');
|
|
287
|
+
if (toast instanceof HTMLElement) {
|
|
288
|
+
setTimeout(() => injectTimeAgoIntoToast(toast), 100);
|
|
222
289
|
}
|
|
223
290
|
}
|
|
224
291
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Format a Unix timestamp (ms) as a relative time string.
|
|
3
3
|
*
|
|
4
|
-
* Returns a compact label such as "just now", "
|
|
5
|
-
* "
|
|
4
|
+
* Returns a compact label such as "just now", "5m ago", "2h ago",
|
|
5
|
+
* or "3d ago". Anything under 60 seconds is shown as "just now".
|
|
6
6
|
*/
|
|
7
7
|
export function formatTimeAgo(createdAt: number): string {
|
|
8
8
|
const delta = Math.max(0, Date.now() - createdAt);
|
|
9
9
|
const seconds = Math.floor(delta / 1000);
|
|
10
10
|
|
|
11
|
-
if (seconds < 5) {
|
|
12
|
-
return 'just now';
|
|
13
|
-
}
|
|
14
11
|
if (seconds < 60) {
|
|
15
|
-
return
|
|
12
|
+
return 'just now';
|
|
16
13
|
}
|
|
17
14
|
const minutes = Math.floor(seconds / 60);
|
|
18
15
|
if (minutes < 60) {
|