jupyterlab_notifications_extension 1.0.20 → 1.1.10
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/README.md +52 -5
- package/lib/index.js +149 -1
- package/package.json +1 -1
- package/src/index.ts +173 -1
package/README.md
CHANGED
|
@@ -6,9 +6,30 @@
|
|
|
6
6
|
[](https://pepy.tech/project/jupyterlab-notifications-extension)
|
|
7
7
|
[](https://jupyterlab.readthedocs.io/en/stable/)
|
|
8
8
|
|
|
9
|
-
JupyterLab extension
|
|
9
|
+
JupyterLab extension for sending notifications using the native JupyterLab notification system. External systems and extensions send alerts and status updates that appear in JupyterLab's notification center.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Five notification types with distinct visual styling provide clear status communication:
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
Access via command palette for quick manual notification sending:
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
Interactive dialog with message input, type selection, auto-close timing, and action button options:
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
**Key Features:**
|
|
24
|
+
|
|
25
|
+
- REST API for external systems to POST notifications with authentication
|
|
26
|
+
- Command palette integration with interactive dialog
|
|
27
|
+
- Programmatic command API for extensions and automation
|
|
28
|
+
- Five notification types (info, success, warning, error, in-progress)
|
|
29
|
+
- Configurable auto-close with millisecond precision or manual dismiss
|
|
30
|
+
- Optional action buttons (currently dismiss only)
|
|
31
|
+
- Broadcast delivery via 30-second polling
|
|
32
|
+
- In-memory queue cleared after delivery
|
|
12
33
|
|
|
13
34
|
## Installation
|
|
14
35
|
|
|
@@ -22,7 +43,7 @@ pip install jupyterlab_notifications_extension
|
|
|
22
43
|
|
|
23
44
|
### POST /jupyterlab-notifications-extension/ingest
|
|
24
45
|
|
|
25
|
-
Send notifications to JupyterLab
|
|
46
|
+
Send notifications to JupyterLab. Requires authentication via `Authorization: token <TOKEN>` header or `?token=<TOKEN>` query parameter.
|
|
26
47
|
|
|
27
48
|
**Endpoint**: `POST /jupyterlab-notifications-extension/ingest`
|
|
28
49
|
|
|
@@ -47,7 +68,7 @@ Send notifications to JupyterLab users. Requires authentication via `Authorizati
|
|
|
47
68
|
|
|
48
69
|
| Field | Type | Required | Default | Description |
|
|
49
70
|
| ----------- | -------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
-
| `message` | string | Yes | - | Notification text
|
|
71
|
+
| `message` | string | Yes | - | Notification text (max 140 characters) |
|
|
51
72
|
| `type` | string | No | `"info"` | Visual style: `default`, `info`, `success`, `warning`, `error`, `in-progress` |
|
|
52
73
|
| `autoClose` | number/boolean | No | `5000` | Milliseconds before auto-dismiss. `false` = manual dismiss only. `0` = silent mode (notification center only, no toast) |
|
|
53
74
|
| `actions` | array | No | `[]` | Action buttons (see below) |
|
|
@@ -79,6 +100,32 @@ Note: Action buttons are purely visual. Clicking any button dismisses the notifi
|
|
|
79
100
|
|
|
80
101
|
## Usage Examples
|
|
81
102
|
|
|
103
|
+
### From JupyterLab Extensions
|
|
104
|
+
|
|
105
|
+
Send notifications programmatically from other extensions:
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// Basic notification
|
|
109
|
+
await app.commands.execute('jupyterlab-notifications:send', {
|
|
110
|
+
message: 'Operation complete'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Custom type and auto-close
|
|
114
|
+
await app.commands.execute('jupyterlab-notifications:send', {
|
|
115
|
+
message: 'Build finished successfully',
|
|
116
|
+
type: 'success',
|
|
117
|
+
autoClose: 3000
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// With action button
|
|
121
|
+
await app.commands.execute('jupyterlab-notifications:send', {
|
|
122
|
+
message: 'Error processing data',
|
|
123
|
+
type: 'error',
|
|
124
|
+
autoClose: false,
|
|
125
|
+
actions: [{ label: 'View Details', displayType: 'accent' }]
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
82
129
|
### Python Script
|
|
83
130
|
|
|
84
131
|
The included script auto-detects tokens from `JUPYTERHUB_API_TOKEN`, `JPY_API_TOKEN`, or `JUPYTER_TOKEN` environment variables:
|
|
@@ -121,7 +168,7 @@ curl -X POST http://localhost:8888/jupyterlab-notifications-extension/ingest \
|
|
|
121
168
|
|
|
122
169
|
## Architecture
|
|
123
170
|
|
|
124
|
-
Broadcast-only model - all notifications delivered to
|
|
171
|
+
Broadcast-only model - all notifications delivered to the JupyterLab server.
|
|
125
172
|
|
|
126
173
|
**Flow**: External system POSTs to `/jupyterlab-notifications-extension/ingest` -> Server queues in memory -> Frontend polls `/jupyterlab-notifications-extension/notifications` every 30 seconds -> Displays via JupyterLab notification manager -> Clears queue after fetch.
|
|
127
174
|
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ICommandPalette, Dialog } from '@jupyterlab/apputils';
|
|
2
|
+
import { Widget } from '@lumino/widgets';
|
|
1
3
|
import { requestAPI } from './request';
|
|
2
4
|
/**
|
|
3
5
|
* Poll interval in milliseconds (30 seconds)
|
|
@@ -55,8 +57,154 @@ const plugin = {
|
|
|
55
57
|
id: 'jupyterlab_notifications_extension:plugin',
|
|
56
58
|
description: 'Jupyterlab extension to receive and display notifications in the main panel. Those can be from the jupyterjub administrator or from other places.',
|
|
57
59
|
autoStart: true,
|
|
58
|
-
|
|
60
|
+
requires: [ICommandPalette],
|
|
61
|
+
activate: (app, palette) => {
|
|
59
62
|
console.log('JupyterLab extension jupyterlab_notifications_extension is activated!');
|
|
63
|
+
// Register command to send notifications
|
|
64
|
+
const commandId = 'jupyterlab-notifications:send';
|
|
65
|
+
app.commands.addCommand(commandId, {
|
|
66
|
+
label: 'Send Notification',
|
|
67
|
+
caption: 'Send a notification to all JupyterLab users',
|
|
68
|
+
execute: async (args) => {
|
|
69
|
+
let message = args.message;
|
|
70
|
+
let type = args.type || 'info';
|
|
71
|
+
let autoClose = args.autoClose !== undefined ? args.autoClose : 5000;
|
|
72
|
+
let actions = args.actions || [];
|
|
73
|
+
const data = args.data;
|
|
74
|
+
// If no message provided, show input dialog
|
|
75
|
+
if (!message) {
|
|
76
|
+
// Create dialog body with form elements
|
|
77
|
+
const body = document.createElement('div');
|
|
78
|
+
body.style.display = 'flex';
|
|
79
|
+
body.style.flexDirection = 'column';
|
|
80
|
+
body.style.gap = '10px';
|
|
81
|
+
// Message input
|
|
82
|
+
const messageLabel = document.createElement('label');
|
|
83
|
+
messageLabel.textContent = 'Message:';
|
|
84
|
+
const messageInput = document.createElement('input');
|
|
85
|
+
messageInput.type = 'text';
|
|
86
|
+
messageInput.placeholder = 'Enter notification message';
|
|
87
|
+
messageInput.style.width = '100%';
|
|
88
|
+
messageInput.style.padding = '5px';
|
|
89
|
+
// Type select
|
|
90
|
+
const typeLabel = document.createElement('label');
|
|
91
|
+
typeLabel.textContent = 'Type:';
|
|
92
|
+
const typeSelect = document.createElement('select');
|
|
93
|
+
typeSelect.style.width = '100%';
|
|
94
|
+
typeSelect.style.padding = '5px';
|
|
95
|
+
['info', 'success', 'warning', 'error', 'in-progress'].forEach(t => {
|
|
96
|
+
const option = document.createElement('option');
|
|
97
|
+
option.value = t;
|
|
98
|
+
option.textContent = t;
|
|
99
|
+
typeSelect.appendChild(option);
|
|
100
|
+
});
|
|
101
|
+
// Auto-close checkbox and seconds input
|
|
102
|
+
const autoCloseContainer = document.createElement('div');
|
|
103
|
+
autoCloseContainer.style.display = 'flex';
|
|
104
|
+
autoCloseContainer.style.alignItems = 'center';
|
|
105
|
+
autoCloseContainer.style.gap = '10px';
|
|
106
|
+
const autoCloseCheckbox = document.createElement('input');
|
|
107
|
+
autoCloseCheckbox.type = 'checkbox';
|
|
108
|
+
autoCloseCheckbox.id = 'autoCloseCheckbox';
|
|
109
|
+
autoCloseCheckbox.checked = true;
|
|
110
|
+
const autoCloseLabel = document.createElement('label');
|
|
111
|
+
autoCloseLabel.htmlFor = 'autoCloseCheckbox';
|
|
112
|
+
autoCloseLabel.textContent = 'Auto-close after';
|
|
113
|
+
autoCloseLabel.style.cursor = 'pointer';
|
|
114
|
+
const autoCloseInput = document.createElement('input');
|
|
115
|
+
autoCloseInput.type = 'number';
|
|
116
|
+
autoCloseInput.value = '5';
|
|
117
|
+
autoCloseInput.min = '1';
|
|
118
|
+
autoCloseInput.style.width = '60px';
|
|
119
|
+
autoCloseInput.style.padding = '3px';
|
|
120
|
+
const secondsLabel = document.createElement('span');
|
|
121
|
+
secondsLabel.textContent = 'seconds';
|
|
122
|
+
autoCloseContainer.appendChild(autoCloseCheckbox);
|
|
123
|
+
autoCloseContainer.appendChild(autoCloseLabel);
|
|
124
|
+
autoCloseContainer.appendChild(autoCloseInput);
|
|
125
|
+
autoCloseContainer.appendChild(secondsLabel);
|
|
126
|
+
// Disable/enable input based on checkbox
|
|
127
|
+
autoCloseCheckbox.addEventListener('change', () => {
|
|
128
|
+
autoCloseInput.disabled = !autoCloseCheckbox.checked;
|
|
129
|
+
});
|
|
130
|
+
// Dismiss button checkbox
|
|
131
|
+
const dismissCheckbox = document.createElement('input');
|
|
132
|
+
dismissCheckbox.type = 'checkbox';
|
|
133
|
+
dismissCheckbox.id = 'dismissCheckbox';
|
|
134
|
+
const dismissLabel = document.createElement('label');
|
|
135
|
+
dismissLabel.htmlFor = 'dismissCheckbox';
|
|
136
|
+
dismissLabel.textContent = ' Include dismiss button';
|
|
137
|
+
dismissLabel.style.display = 'flex';
|
|
138
|
+
dismissLabel.style.alignItems = 'center';
|
|
139
|
+
dismissLabel.style.gap = '5px';
|
|
140
|
+
dismissLabel.style.cursor = 'pointer';
|
|
141
|
+
dismissLabel.prepend(dismissCheckbox);
|
|
142
|
+
body.appendChild(messageLabel);
|
|
143
|
+
body.appendChild(messageInput);
|
|
144
|
+
body.appendChild(typeLabel);
|
|
145
|
+
body.appendChild(typeSelect);
|
|
146
|
+
body.appendChild(autoCloseContainer);
|
|
147
|
+
body.appendChild(dismissLabel);
|
|
148
|
+
const widget = new Widget({ node: body });
|
|
149
|
+
const dialog = new Dialog({
|
|
150
|
+
title: 'Send Notification',
|
|
151
|
+
body: widget,
|
|
152
|
+
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Send' })]
|
|
153
|
+
});
|
|
154
|
+
const result = await dialog.launch();
|
|
155
|
+
if (result.button.accept) {
|
|
156
|
+
message = messageInput.value;
|
|
157
|
+
if (!message) {
|
|
158
|
+
return; // No message entered
|
|
159
|
+
}
|
|
160
|
+
// Override with dialog values
|
|
161
|
+
type = typeSelect.value;
|
|
162
|
+
// Set autoClose based on checkbox and input
|
|
163
|
+
if (autoCloseCheckbox.checked) {
|
|
164
|
+
autoClose = parseInt(autoCloseInput.value) * 1000; // Convert to milliseconds
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
autoClose = false;
|
|
168
|
+
}
|
|
169
|
+
actions = dismissCheckbox.checked
|
|
170
|
+
? [
|
|
171
|
+
{
|
|
172
|
+
label: 'Dismiss',
|
|
173
|
+
caption: 'Close this notification',
|
|
174
|
+
displayType: 'default'
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
: [];
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
return; // User cancelled
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const payload = {
|
|
185
|
+
message,
|
|
186
|
+
type,
|
|
187
|
+
autoClose
|
|
188
|
+
};
|
|
189
|
+
if (actions.length > 0) {
|
|
190
|
+
payload.actions = actions;
|
|
191
|
+
}
|
|
192
|
+
if (data !== undefined) {
|
|
193
|
+
payload.data = data;
|
|
194
|
+
}
|
|
195
|
+
await requestAPI('ingest', {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
body: JSON.stringify(payload)
|
|
198
|
+
});
|
|
199
|
+
console.log('Notification sent successfully');
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.error('Failed to send notification:', error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
// Add command to palette
|
|
207
|
+
palette.addItem({ command: commandId, category: 'Notifications' });
|
|
60
208
|
// Fetch notifications immediately on startup
|
|
61
209
|
fetchAndDisplayNotifications(app);
|
|
62
210
|
// Set up periodic polling for new notifications
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_notifications_extension",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.10",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
JupyterFrontEndPlugin
|
|
4
4
|
} from '@jupyterlab/application';
|
|
5
5
|
|
|
6
|
+
import { ICommandPalette, Dialog } from '@jupyterlab/apputils';
|
|
7
|
+
import { Widget } from '@lumino/widgets';
|
|
8
|
+
|
|
6
9
|
import { requestAPI } from './request';
|
|
7
10
|
|
|
8
11
|
/**
|
|
@@ -91,11 +94,180 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
91
94
|
description:
|
|
92
95
|
'Jupyterlab extension to receive and display notifications in the main panel. Those can be from the jupyterjub administrator or from other places.',
|
|
93
96
|
autoStart: true,
|
|
94
|
-
|
|
97
|
+
requires: [ICommandPalette],
|
|
98
|
+
activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
|
|
95
99
|
console.log(
|
|
96
100
|
'JupyterLab extension jupyterlab_notifications_extension is activated!'
|
|
97
101
|
);
|
|
98
102
|
|
|
103
|
+
// Register command to send notifications
|
|
104
|
+
const commandId = 'jupyterlab-notifications:send';
|
|
105
|
+
app.commands.addCommand(commandId, {
|
|
106
|
+
label: 'Send Notification',
|
|
107
|
+
caption: 'Send a notification to all JupyterLab users',
|
|
108
|
+
execute: async (args: any) => {
|
|
109
|
+
let message = args.message as string;
|
|
110
|
+
let type = (args.type as string) || 'info';
|
|
111
|
+
let autoClose = args.autoClose !== undefined ? args.autoClose : 5000;
|
|
112
|
+
let actions = args.actions || [];
|
|
113
|
+
const data = args.data;
|
|
114
|
+
|
|
115
|
+
// If no message provided, show input dialog
|
|
116
|
+
if (!message) {
|
|
117
|
+
// Create dialog body with form elements
|
|
118
|
+
const body = document.createElement('div');
|
|
119
|
+
body.style.display = 'flex';
|
|
120
|
+
body.style.flexDirection = 'column';
|
|
121
|
+
body.style.gap = '10px';
|
|
122
|
+
|
|
123
|
+
// Message input
|
|
124
|
+
const messageLabel = document.createElement('label');
|
|
125
|
+
messageLabel.textContent = 'Message:';
|
|
126
|
+
const messageInput = document.createElement('input');
|
|
127
|
+
messageInput.type = 'text';
|
|
128
|
+
messageInput.placeholder = 'Enter notification message';
|
|
129
|
+
messageInput.style.width = '100%';
|
|
130
|
+
messageInput.style.padding = '5px';
|
|
131
|
+
|
|
132
|
+
// Type select
|
|
133
|
+
const typeLabel = document.createElement('label');
|
|
134
|
+
typeLabel.textContent = 'Type:';
|
|
135
|
+
const typeSelect = document.createElement('select');
|
|
136
|
+
typeSelect.style.width = '100%';
|
|
137
|
+
typeSelect.style.padding = '5px';
|
|
138
|
+
['info', 'success', 'warning', 'error', 'in-progress'].forEach(t => {
|
|
139
|
+
const option = document.createElement('option');
|
|
140
|
+
option.value = t;
|
|
141
|
+
option.textContent = t;
|
|
142
|
+
typeSelect.appendChild(option);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Auto-close checkbox and seconds input
|
|
146
|
+
const autoCloseContainer = document.createElement('div');
|
|
147
|
+
autoCloseContainer.style.display = 'flex';
|
|
148
|
+
autoCloseContainer.style.alignItems = 'center';
|
|
149
|
+
autoCloseContainer.style.gap = '10px';
|
|
150
|
+
|
|
151
|
+
const autoCloseCheckbox = document.createElement('input');
|
|
152
|
+
autoCloseCheckbox.type = 'checkbox';
|
|
153
|
+
autoCloseCheckbox.id = 'autoCloseCheckbox';
|
|
154
|
+
autoCloseCheckbox.checked = true;
|
|
155
|
+
|
|
156
|
+
const autoCloseLabel = document.createElement('label');
|
|
157
|
+
autoCloseLabel.htmlFor = 'autoCloseCheckbox';
|
|
158
|
+
autoCloseLabel.textContent = 'Auto-close after';
|
|
159
|
+
autoCloseLabel.style.cursor = 'pointer';
|
|
160
|
+
|
|
161
|
+
const autoCloseInput = document.createElement('input');
|
|
162
|
+
autoCloseInput.type = 'number';
|
|
163
|
+
autoCloseInput.value = '5';
|
|
164
|
+
autoCloseInput.min = '1';
|
|
165
|
+
autoCloseInput.style.width = '60px';
|
|
166
|
+
autoCloseInput.style.padding = '3px';
|
|
167
|
+
|
|
168
|
+
const secondsLabel = document.createElement('span');
|
|
169
|
+
secondsLabel.textContent = 'seconds';
|
|
170
|
+
|
|
171
|
+
autoCloseContainer.appendChild(autoCloseCheckbox);
|
|
172
|
+
autoCloseContainer.appendChild(autoCloseLabel);
|
|
173
|
+
autoCloseContainer.appendChild(autoCloseInput);
|
|
174
|
+
autoCloseContainer.appendChild(secondsLabel);
|
|
175
|
+
|
|
176
|
+
// Disable/enable input based on checkbox
|
|
177
|
+
autoCloseCheckbox.addEventListener('change', () => {
|
|
178
|
+
autoCloseInput.disabled = !autoCloseCheckbox.checked;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Dismiss button checkbox
|
|
182
|
+
const dismissCheckbox = document.createElement('input');
|
|
183
|
+
dismissCheckbox.type = 'checkbox';
|
|
184
|
+
dismissCheckbox.id = 'dismissCheckbox';
|
|
185
|
+
const dismissLabel = document.createElement('label');
|
|
186
|
+
dismissLabel.htmlFor = 'dismissCheckbox';
|
|
187
|
+
dismissLabel.textContent = ' Include dismiss button';
|
|
188
|
+
dismissLabel.style.display = 'flex';
|
|
189
|
+
dismissLabel.style.alignItems = 'center';
|
|
190
|
+
dismissLabel.style.gap = '5px';
|
|
191
|
+
dismissLabel.style.cursor = 'pointer';
|
|
192
|
+
dismissLabel.prepend(dismissCheckbox);
|
|
193
|
+
|
|
194
|
+
body.appendChild(messageLabel);
|
|
195
|
+
body.appendChild(messageInput);
|
|
196
|
+
body.appendChild(typeLabel);
|
|
197
|
+
body.appendChild(typeSelect);
|
|
198
|
+
body.appendChild(autoCloseContainer);
|
|
199
|
+
body.appendChild(dismissLabel);
|
|
200
|
+
|
|
201
|
+
const widget = new Widget({ node: body });
|
|
202
|
+
|
|
203
|
+
const dialog = new Dialog({
|
|
204
|
+
title: 'Send Notification',
|
|
205
|
+
body: widget,
|
|
206
|
+
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Send' })]
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const result = await dialog.launch();
|
|
210
|
+
|
|
211
|
+
if (result.button.accept) {
|
|
212
|
+
message = messageInput.value;
|
|
213
|
+
if (!message) {
|
|
214
|
+
return; // No message entered
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Override with dialog values
|
|
218
|
+
type = typeSelect.value;
|
|
219
|
+
|
|
220
|
+
// Set autoClose based on checkbox and input
|
|
221
|
+
if (autoCloseCheckbox.checked) {
|
|
222
|
+
autoClose = parseInt(autoCloseInput.value) * 1000; // Convert to milliseconds
|
|
223
|
+
} else {
|
|
224
|
+
autoClose = false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
actions = dismissCheckbox.checked
|
|
228
|
+
? [
|
|
229
|
+
{
|
|
230
|
+
label: 'Dismiss',
|
|
231
|
+
caption: 'Close this notification',
|
|
232
|
+
displayType: 'default'
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
: [];
|
|
236
|
+
} else {
|
|
237
|
+
return; // User cancelled
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const payload: any = {
|
|
243
|
+
message,
|
|
244
|
+
type,
|
|
245
|
+
autoClose
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (actions.length > 0) {
|
|
249
|
+
payload.actions = actions;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (data !== undefined) {
|
|
253
|
+
payload.data = data;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await requestAPI('ingest', {
|
|
257
|
+
method: 'POST',
|
|
258
|
+
body: JSON.stringify(payload)
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
console.log('Notification sent successfully');
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('Failed to send notification:', error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Add command to palette
|
|
269
|
+
palette.addItem({ command: commandId, category: 'Notifications' });
|
|
270
|
+
|
|
99
271
|
// Fetch notifications immediately on startup
|
|
100
272
|
fetchAndDisplayNotifications(app);
|
|
101
273
|
|