node-red-contrib-notification-better 0.0.0-stage → 1.1.0
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/LICENSE +21 -0
- package/README.md +82 -2
- package/locales/en-US/ui_notification_better.html +50 -0
- package/locales/en-US/ui_notification_better.json +63 -0
- package/locales/ja/ui_notification_better.json +63 -0
- package/locales/zh-CN/ui_notification_better.json +58 -0
- package/locales/zh-TW/ui_notification_better.html +50 -0
- package/locales/zh-TW/ui_notification_better.json +63 -0
- package/package.json +35 -4
- package/ui_notification.html +283 -0
- package/ui_notification.js +297 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Your Name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,83 @@
|
|
|
1
|
-
#
|
|
1
|
+
# node-red-contrib-notification-better
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一個為 [Node-RED Dashboard](https://flows.nodered.org/node/node-red-dashboard) 設計的進階通知(Notification / Toast)與互動對話框(Dialog / Prompt)節點。
|
|
4
|
+
|
|
5
|
+
相比原生的通知節點,此節點徹底解決了按鈕被強制大寫的問題,並提供多語言介面、視覺主題等級、提示音效、倒數進度條等豐富功能!
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ 核心特色 (Key Features)
|
|
10
|
+
|
|
11
|
+
- 🔤 **忠實保留按鈕原始大小寫 (Preserve Button Case)**:
|
|
12
|
+
- 徹底解除 Angular Material 強制全大寫(`text-transform: uppercase`)的限制。
|
|
13
|
+
- 輸入 `Ok` 顯示 `Ok`、輸入 `Cancel` 顯示 `Cancel`、輸入 `確定` 或 `Close Now` 皆忠實依字樣呈現!
|
|
14
|
+
- 🌐 **多國語言介面 (Multi-Language i18n)**:
|
|
15
|
+
- 支援 **繁體中文 (zh-TW)**、**English (en-US)**、**日本語 (ja)**、**簡體中文 (zh-CN)**,根據 Node-RED 系統語言自動切換。
|
|
16
|
+
- 🎯 **通知等級預設 (Notification Levels)**:
|
|
17
|
+
- 內建 **Info (資訊)**、**Success (成功)**、**Warning (警告)**、**Error (錯誤)** 與 **Default (預設)**。
|
|
18
|
+
- 自動套用專屬主題色彩與外框線,亦可透過 `msg.notificationType` 動態指定。
|
|
19
|
+
- 🔔 **圖示支援 (Icon Support)**:
|
|
20
|
+
- 自動根據通知等級配對 Font Awesome 圖示,亦可手動填寫或透過 `msg.icon` 自訂圖示。
|
|
21
|
+
- 🔊 **音效提示 (Audio Chime Alert)**:
|
|
22
|
+
- 內建 Web Audio API 音效合成技術,無須外掛任何音訊檔案,即可發出清脆悅耳的提示聲。
|
|
23
|
+
- ⏳ **倒數進度條 (Countdown Progress Bar)**:
|
|
24
|
+
- 在卡片底部以視覺化動態進度條呈現剩餘時間,何時關閉一目了然。
|
|
25
|
+
- 💬 **互動式對話框與輸入框 (Dialog & Prompt)**:
|
|
26
|
+
- 支援確認 / 取消對話框,以及帶輸入框的 Prompt 模式,支援自訂預設值與 Placeholder。
|
|
27
|
+
- 🚀 **原生 HTML 與 JavaScript 支援**:
|
|
28
|
+
- 可勾選開啟,直接在通知內容中渲染自訂 HTML、動畫或樣式。
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📦 安裝方式 (Installation)
|
|
33
|
+
|
|
34
|
+
### 依賴需求 (Prerequisites)
|
|
35
|
+
此節點搭配 **`node-red-dashboard`** 使用,請先確保已安裝 Dashboard 套件:
|
|
36
|
+
```bash
|
|
37
|
+
npm install node-red-dashboard
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 透過 Node-RED 調色盤管理員安裝 (推薦)
|
|
41
|
+
1. 開啟 Node-RED 編輯器。
|
|
42
|
+
2. 點擊右上角漢堡選單 ➔ **節點管理 (Manage palette)** ➔ **安裝 (Install)**。
|
|
43
|
+
3. 搜尋 `node-red-contrib-notification-better` 並點選安裝。
|
|
44
|
+
|
|
45
|
+
### 透過 npm 命令列安裝
|
|
46
|
+
進入您的 Node-RED 使用者目錄(通常為 `~/.node-red`):
|
|
47
|
+
```bash
|
|
48
|
+
cd ~/.node-red
|
|
49
|
+
npm install node-red-contrib-notification-better
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🛠️ 使用與訊息屬性說明 (Message Properties)
|
|
55
|
+
|
|
56
|
+
安裝完成後,會在左側節點列表(Palette)的 **dashboard** 分類中顯示 **`進階通知 (notification better)`**。
|
|
57
|
+
|
|
58
|
+
### 支援的輸入訊息屬性 (Inputs)
|
|
59
|
+
|
|
60
|
+
| 屬性名稱 | 型別 | 預設來源 | 說明 |
|
|
61
|
+
| :--- | :--- | :--- | :--- |
|
|
62
|
+
| `msg.payload` | `string` \| `object` | 必填 | 通知的內文訊息。若開啟原始 HTML 模式,將忠實解析標籤。 |
|
|
63
|
+
| `msg.topic` / `msg.title` | `string` | 可選 | 通知的頂部標題。 |
|
|
64
|
+
| `msg.notificationType` | `string` | 可選 | 通知等級:`info`、`success`、`warning`、`error` 或 `default`。 |
|
|
65
|
+
| `msg.timeout` | `number` | 可選 | 顯示秒數(例如 `5` 代表 5 秒;設為 `0` 則立即消失)。 |
|
|
66
|
+
| `msg.ok` | `string` | 可選 | 確認按鈕標籤文字(大小寫忠實保留)。 |
|
|
67
|
+
| `msg.cancel` | `string` | 可選 | 取消按鈕標籤文字(大小寫忠實保留)。 |
|
|
68
|
+
| `msg.highlight` / `msg.border` | `string` | 可選 | 外框高亮顏色(例如 `#4CAF50` 或 `red`)。 |
|
|
69
|
+
| `msg.icon` | `string` | 可選 | 自訂 Font Awesome 圖示名稱(例如 `fa-bell` 或 `fa-check`)。 |
|
|
70
|
+
| `msg.playSound` | `boolean` | 可選 | 是否在此次通知觸發時播放提示音效。 |
|
|
71
|
+
| `msg.position` | `string` | 可選 | 覆蓋顯示位置(如 `top right`、`dialog`、`prompt` 等)。 |
|
|
72
|
+
| `msg.className` | `string` | 可選 | 附加自訂 CSS 類別名稱。 |
|
|
73
|
+
| `msg.socketid` | `string` | 可選 | 當關閉廣播所有連線時,指定特定使用者之 Socket ID。 |
|
|
74
|
+
|
|
75
|
+
### 輸出訊息 (Outputs)
|
|
76
|
+
當佈局選擇 **Dialog (對話框)** 或 **Prompt (文字輸入框)** 時:
|
|
77
|
+
- 使用者點擊按鈕或送出輸入文字後,結果將由節點輸出點送出(確認按鈕輸出 `msg.ok` 或輸入文字;取消按鈕輸出 `msg.cancel`)。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 📄 授權 (License)
|
|
82
|
+
|
|
83
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script type="text/html" data-help-name="ui_notification_better">
|
|
2
|
+
<p>An enhanced notification and dialog node designed for <b>Node-RED Dashboard</b>.</p>
|
|
3
|
+
|
|
4
|
+
<h3>Features</h3>
|
|
5
|
+
<ul>
|
|
6
|
+
<li><b>Exact Button Case</b>: Disables Angular Material's forced uppercase styling, displaying button text exactly as typed.</li>
|
|
7
|
+
<li><b>Levels & Types</b>: Built-in presets for Info, Success, Warning, and Error with automatic styling and icons.</li>
|
|
8
|
+
<li><b>Audio Alerts</b>: Built-in sound chime support via Web Audio API.</li>
|
|
9
|
+
<li><b>Rich Customization</b>: Raw HTML/JS formatting, custom icons, border highlight, and countdown bar.</li>
|
|
10
|
+
</ul>
|
|
11
|
+
|
|
12
|
+
<h3>Inputs</h3>
|
|
13
|
+
<dl class="message-properties">
|
|
14
|
+
<dt>payload <span class="property-type">string | object</span></dt>
|
|
15
|
+
<dd>The message body to display. If <i>Accept raw HTML/JS</i> is enabled, raw HTML tags will be rendered directly.</dd>
|
|
16
|
+
|
|
17
|
+
<dt class="optional">topic <span class="property-type">string</span></dt>
|
|
18
|
+
<dd>The notification or dialog title.</dd>
|
|
19
|
+
|
|
20
|
+
<dt class="optional">timeout <span class="property-type">number</span></dt>
|
|
21
|
+
<dd>Duration in seconds before auto-dismissal (defaults to 3s). Set to 0 to dismiss immediately.</dd>
|
|
22
|
+
|
|
23
|
+
<dt class="optional">notificationType <span class="property-type">string</span></dt>
|
|
24
|
+
<dd>One of <code>default</code>, <code>info</code>, <code>success</code>, <code>warning</code>, or <code>error</code>.</dd>
|
|
25
|
+
|
|
26
|
+
<dt class="optional">ok <span class="property-type">string</span></dt>
|
|
27
|
+
<dd>Custom text for the primary confirmation button (casing preserved).</dd>
|
|
28
|
+
|
|
29
|
+
<dt class="optional">cancel <span class="property-type">string</span></dt>
|
|
30
|
+
<dd>Custom text for the cancel button (casing preserved).</dd>
|
|
31
|
+
|
|
32
|
+
<dt class="optional">highlight <span class="property-type">string</span></dt>
|
|
33
|
+
<dd>Border highlight color (e.g. <code>#2ecc71</code> or <code>green</code>).</dd>
|
|
34
|
+
|
|
35
|
+
<dt class="optional">icon <span class="property-type">string</span></dt>
|
|
36
|
+
<dd>Font Awesome icon class name (e.g. <code>fa-bell</code>).</dd>
|
|
37
|
+
|
|
38
|
+
<dt class="optional">playSound <span class="property-type">boolean</span></dt>
|
|
39
|
+
<dd>Whether to play an audible chime when triggering the alert.</dd>
|
|
40
|
+
|
|
41
|
+
<dt class="optional">socketid <span class="property-type">string</span></dt>
|
|
42
|
+
<dd>When <i>Send to all browser sessions</i> is disabled, targets this specific client session.</dd>
|
|
43
|
+
</dl>
|
|
44
|
+
|
|
45
|
+
<h3>Outputs</h3>
|
|
46
|
+
<dl class="message-properties">
|
|
47
|
+
<dt>payload <span class="property-type">string</span></dt>
|
|
48
|
+
<dd>When layout is set to <b>Dialog</b> or <b>Prompt</b>, emits the user's action or entered text when closed.</dd>
|
|
49
|
+
</dl>
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ui_notification_better": {
|
|
3
|
+
"paletteLabel": "notification better",
|
|
4
|
+
"label": {
|
|
5
|
+
"position": "Layout",
|
|
6
|
+
"notificationType": "Type / Level",
|
|
7
|
+
"displayTime": "Timeout (sec)",
|
|
8
|
+
"highlight": "Border Color",
|
|
9
|
+
"showIcon": "Show Icon",
|
|
10
|
+
"customIcon": "Custom Icon",
|
|
11
|
+
"playSound": "Play Sound Alert",
|
|
12
|
+
"showCountdown": "Countdown Progress Bar",
|
|
13
|
+
"preserveCase": "Preserve Button Casing (No Force Uppercase)",
|
|
14
|
+
"sendall": "Send to all browser sessions",
|
|
15
|
+
"ok": "Default Action Button",
|
|
16
|
+
"cancel": "Secondary Action Button",
|
|
17
|
+
"promptDefault": "Default Input Text",
|
|
18
|
+
"promptPlaceholder": "Input Placeholder",
|
|
19
|
+
"raw": "Accept raw HTML / JavaScript in msg.payload",
|
|
20
|
+
"className": "Custom CSS Class",
|
|
21
|
+
"topic": "Topic / Title",
|
|
22
|
+
"name": "Name",
|
|
23
|
+
"showDialog": "show dialog",
|
|
24
|
+
"showNotification": "show notification"
|
|
25
|
+
},
|
|
26
|
+
"option": {
|
|
27
|
+
"topRight": "Top Right",
|
|
28
|
+
"bottomRight": "Bottom Right",
|
|
29
|
+
"topLeft": "Top Left",
|
|
30
|
+
"bottomLeft": "Bottom Left",
|
|
31
|
+
"dialog": "OK / Cancel Confirmation Dialog",
|
|
32
|
+
"prompt": "Dialog with Text Input (Prompt)",
|
|
33
|
+
"typeDefault": "Default (Neutral)",
|
|
34
|
+
"typeInfo": "ℹ Info (Blue)",
|
|
35
|
+
"typeSuccess": "✓ Success (Green)",
|
|
36
|
+
"typeWarning": "⚠ Warning (Amber)",
|
|
37
|
+
"typeError": "✕ Error (Red)"
|
|
38
|
+
},
|
|
39
|
+
"placeholder": {
|
|
40
|
+
"timeout": "e.g. 3 or [msg.timeout]",
|
|
41
|
+
"highlight": "e.g. #ff0000 or red",
|
|
42
|
+
"customIcon": "e.g. fa-bell, fa-check",
|
|
43
|
+
"ok": "Button label, e.g. Ok, Confirm, 確定",
|
|
44
|
+
"cancel": "Optional label, e.g. Cancel, 取消",
|
|
45
|
+
"promptDefault": "Optional default input value",
|
|
46
|
+
"promptPlaceholder": "Optional placeholder inside text box",
|
|
47
|
+
"className": "[msg.className]",
|
|
48
|
+
"topic": "[msg.topic]",
|
|
49
|
+
"name": "Node name"
|
|
50
|
+
},
|
|
51
|
+
"section": {
|
|
52
|
+
"basic": "General Settings",
|
|
53
|
+
"appearance": "Appearance & Styling",
|
|
54
|
+
"dialog": "Dialog Configuration",
|
|
55
|
+
"advanced": "Behavior & Advanced"
|
|
56
|
+
},
|
|
57
|
+
"tip": {
|
|
58
|
+
"raw": "<b>Security Notice</b>: Allowing raw HTML/JavaScript can enable code execution. Only enable this if the message payload originates from a trusted source.",
|
|
59
|
+
"icon": "Enter Font Awesome 4 icon name (e.g. fa-bell, fa-shield). If enabled without custom icon, an icon matching the notification type will automatically be displayed.",
|
|
60
|
+
"preserveCase": "When checked, button text will exactly match what you typed instead of being forced into uppercase by Angular Material."
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ui_notification_better": {
|
|
3
|
+
"paletteLabel": "通知(拡張版)",
|
|
4
|
+
"label": {
|
|
5
|
+
"position": "表示位置",
|
|
6
|
+
"notificationType": "通知タイプ / レベル",
|
|
7
|
+
"displayTime": "表示時間 (秒)",
|
|
8
|
+
"highlight": "枠線の色",
|
|
9
|
+
"showIcon": "アイコンを表示",
|
|
10
|
+
"customIcon": "カスタムアイコン",
|
|
11
|
+
"playSound": "通知音を再生",
|
|
12
|
+
"showCountdown": "カウントダウンバーを表示",
|
|
13
|
+
"preserveCase": "ボタンの大文字強制を無効化 (入力通り表示)",
|
|
14
|
+
"sendall": "全ブラウザセッションに送信",
|
|
15
|
+
"ok": "デフォルトボタン",
|
|
16
|
+
"cancel": "キャンセルボタン",
|
|
17
|
+
"promptDefault": "入力欄の初期値",
|
|
18
|
+
"promptPlaceholder": "入力欄のプレースホルダー",
|
|
19
|
+
"raw": "msg.payload の生 HTML / JS を許可",
|
|
20
|
+
"className": "カスタム CSS クラス",
|
|
21
|
+
"topic": "トピック / タイトル",
|
|
22
|
+
"name": "ノード名",
|
|
23
|
+
"showDialog": "ダイアログを表示",
|
|
24
|
+
"showNotification": "通知を表示"
|
|
25
|
+
},
|
|
26
|
+
"option": {
|
|
27
|
+
"topRight": "右上 (Top Right)",
|
|
28
|
+
"bottomRight": "右下 (Bottom Right)",
|
|
29
|
+
"topLeft": "左上 (Top Left)",
|
|
30
|
+
"bottomLeft": "左下 (Bottom Left)",
|
|
31
|
+
"dialog": "OK / キャンセル 確認ダイアログ",
|
|
32
|
+
"prompt": "テキスト入力付きダイアログ",
|
|
33
|
+
"typeDefault": "デフォルト (標準)",
|
|
34
|
+
"typeInfo": "ℹ 情報 (青)",
|
|
35
|
+
"typeSuccess": "✓ 成功 (緑)",
|
|
36
|
+
"typeWarning": "⚠ 警告 (黄)",
|
|
37
|
+
"typeError": "✕ エラー (赤)"
|
|
38
|
+
},
|
|
39
|
+
"placeholder": {
|
|
40
|
+
"timeout": "例: 3 または [msg.timeout]",
|
|
41
|
+
"highlight": "例: #ff0000 または red",
|
|
42
|
+
"customIcon": "例: fa-bell, fa-check",
|
|
43
|
+
"ok": "ボタンのラベル (例: Ok, 了解)",
|
|
44
|
+
"cancel": "キャンセルのラベル (例: Cancel, 中止)",
|
|
45
|
+
"promptDefault": "初期入力値(任意)",
|
|
46
|
+
"promptPlaceholder": "プレースホルダー(任意)",
|
|
47
|
+
"className": "[msg.className]",
|
|
48
|
+
"topic": "[msg.topic]",
|
|
49
|
+
"name": "ノード名"
|
|
50
|
+
},
|
|
51
|
+
"section": {
|
|
52
|
+
"basic": "基本設定",
|
|
53
|
+
"appearance": "外観とスタイル",
|
|
54
|
+
"dialog": "ダイアログ設定",
|
|
55
|
+
"advanced": "動作と詳細設定"
|
|
56
|
+
},
|
|
57
|
+
"tip": {
|
|
58
|
+
"raw": "<b>セキュリティの注意</b>:生の HTML/JS を許可するとスクリプト実行のリスクがあります。信頼できる入力の場合のみ有効にしてください。",
|
|
59
|
+
"icon": "Font Awesome 4 のアイコン名を入力してください (例: fa-bell)。空欄の場合は通知タイプに応じたアイコンが表示されます。",
|
|
60
|
+
"preserveCase": "チェックを入れると、ボタン文字が Angular Material により強制的に大文字化されるのを防ぎ、入力した通りの大文字・小文字で表示されます。"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ui_notification_better": {
|
|
3
|
+
"paletteLabel": "高級通知",
|
|
4
|
+
"label": {
|
|
5
|
+
"position": "佈局位置",
|
|
6
|
+
"displayTime": "逾時秒數 (秒)",
|
|
7
|
+
"highlight": "邊框顏色",
|
|
8
|
+
"sendall": "發送至所有瀏覽器會話",
|
|
9
|
+
"ok": "默認操作標籤",
|
|
10
|
+
"cancel": "次要操作標籤",
|
|
11
|
+
"raw": "接受 msg.payload 中的原始 HTML/JS",
|
|
12
|
+
"className": "CSS 類名",
|
|
13
|
+
"topic": "主題",
|
|
14
|
+
"name": "名稱",
|
|
15
|
+
"notificationType": "通知類型",
|
|
16
|
+
"showIcon": "顯示圖標",
|
|
17
|
+
"customIcon": "自定義圖標",
|
|
18
|
+
"showCountdown": "顯示倒計時進度條",
|
|
19
|
+
"playSound": "播放音效",
|
|
20
|
+
"preserveCase": "保留按鈕大小寫(不強制全大寫)",
|
|
21
|
+
"showDialog": "顯示對話框",
|
|
22
|
+
"showNotification": "顯示通知"
|
|
23
|
+
},
|
|
24
|
+
"option": {
|
|
25
|
+
"topRight": "右上角",
|
|
26
|
+
"bottomRight": "右下角",
|
|
27
|
+
"topLeft": "左上角",
|
|
28
|
+
"bottomLeft": "左下角",
|
|
29
|
+
"dialog": "確認 / 取消 對話框",
|
|
30
|
+
"prompt": "帶文本輸入的對話框",
|
|
31
|
+
"default": "默認",
|
|
32
|
+
"info": "ℹ 信息",
|
|
33
|
+
"success": "✓ 成功",
|
|
34
|
+
"warning": "⚠ 警告",
|
|
35
|
+
"error": "✕ 錯誤"
|
|
36
|
+
},
|
|
37
|
+
"placeholder": {
|
|
38
|
+
"timeout": "[msg.timeout]",
|
|
39
|
+
"highlight": "(可選)例如 #ff0000 或 red",
|
|
40
|
+
"ok": "確認按鈕標籤",
|
|
41
|
+
"cancel": "(可選)取消按鈕標籤",
|
|
42
|
+
"className": "[msg.className]",
|
|
43
|
+
"topic": "[msg.topic]",
|
|
44
|
+
"name": "名稱",
|
|
45
|
+
"customIcon": "例如 fa-bell、fa-check"
|
|
46
|
+
},
|
|
47
|
+
"section": {
|
|
48
|
+
"appearance": "外觀設置",
|
|
49
|
+
"behavior": "行為設置",
|
|
50
|
+
"dialog": "對話框設置",
|
|
51
|
+
"advanced": "高級設置"
|
|
52
|
+
},
|
|
53
|
+
"tip": {
|
|
54
|
+
"raw": "<b>警告</b>:啟用 <i>接受原始 HTML/JS</i> 可能導致代碼注入風險,請確保輸入來源可信。",
|
|
55
|
+
"icon": "請使用 Font Awesome 4 圖標類名(例如 fa-bell、fa-check)"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script type="text/html" data-help-name="ui_notification_better">
|
|
2
|
+
<p>專為 <b>Node-RED Dashboard</b> 量身打造的強化版通知 (Notification / Toast) 與互動式對話框 (Dialog) 節點。</p>
|
|
3
|
+
|
|
4
|
+
<h3>核心特色</h3>
|
|
5
|
+
<ul>
|
|
6
|
+
<li><b>忠實保留按鈕大小寫</b>:徹底解除 Angular Material 強制大寫的限制,輸入「Ok」即顯示「Ok」,大小寫百分之百精確呈現。</li>
|
|
7
|
+
<li><b>通知等級預設</b>:內建 Info(資訊)、Success(成功)、Warning(警告)、Error(錯誤)四種視覺主題與自動圖示。</li>
|
|
8
|
+
<li><b>音效提醒</b>:內建 Web Audio API 音效支援,可發出清脆的提示聲。</li>
|
|
9
|
+
<li><b>豐富的自訂性</b>:支援原生 HTML / JavaScript、倒數進度條、自訂外框顏色與圖示。</li>
|
|
10
|
+
</ul>
|
|
11
|
+
|
|
12
|
+
<h3>輸入屬性 (Inputs)</h3>
|
|
13
|
+
<dl class="message-properties">
|
|
14
|
+
<dt>payload <span class="property-type">字串 | 物件</span></dt>
|
|
15
|
+
<dd>顯示在彈出框中的主要內容。若啟用「允許原始 HTML / JS」,將忠實渲染 HTML 與動畫標籤。</dd>
|
|
16
|
+
|
|
17
|
+
<dt class="optional">topic <span class="property-type">字串</span></dt>
|
|
18
|
+
<dd>通知或對話框的頂部標題。</dd>
|
|
19
|
+
|
|
20
|
+
<dt class="optional">timeout <span class="property-type">數值</span></dt>
|
|
21
|
+
<dd>自動關閉的顯示秒數(預設 3 秒),設為 0 會立即關閉。</dd>
|
|
22
|
+
|
|
23
|
+
<dt class="optional">notificationType <span class="property-type">字串</span></dt>
|
|
24
|
+
<dd>通知等級,可指定為 <code>default</code>、<code>info</code>、<code>success</code>、<code>warning</code> 或 <code>error</code>。</dd>
|
|
25
|
+
|
|
26
|
+
<dt class="optional">ok <span class="property-type">字串</span></dt>
|
|
27
|
+
<dd>確認按鈕文字(完全保留原始大小寫)。</dd>
|
|
28
|
+
|
|
29
|
+
<dt class="optional">cancel <span class="property-type">字串</span></dt>
|
|
30
|
+
<dd>取消按鈕文字(完全保留原始大小寫)。</dd>
|
|
31
|
+
|
|
32
|
+
<dt class="optional">highlight <span class="property-type">字串</span></dt>
|
|
33
|
+
<dd>外框高亮顏色(例如 <code>#2ecc71</code> 或 <code>green</code>)。</dd>
|
|
34
|
+
|
|
35
|
+
<dt class="optional">icon <span class="property-type">字串</span></dt>
|
|
36
|
+
<dd>自訂 Font Awesome 圖示名稱(例如 <code>fa-bell</code>)。</dd>
|
|
37
|
+
|
|
38
|
+
<dt class="optional">playSound <span class="property-type">布林值</span></dt>
|
|
39
|
+
<dd>是否在彈出通知時播放提示音效。</dd>
|
|
40
|
+
|
|
41
|
+
<dt class="optional">socketid <span class="property-type">字串</span></dt>
|
|
42
|
+
<dd>若未勾選廣播至所有工作階段,可指定單一瀏覽器連線的 Session ID。</dd>
|
|
43
|
+
</dl>
|
|
44
|
+
|
|
45
|
+
<h3>輸出屬性 (Outputs)</h3>
|
|
46
|
+
<dl class="message-properties">
|
|
47
|
+
<dt>payload <span class="property-type">字串</span></dt>
|
|
48
|
+
<dd>當佈局設為 <b>Dialog</b>(確認對話框)或 <b>Prompt</b>(文字輸入對話框)時,使用者點擊或輸入確認後的結果將由輸出點發送。</dd>
|
|
49
|
+
</dl>
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ui_notification_better": {
|
|
3
|
+
"paletteLabel": "進階通知",
|
|
4
|
+
"label": {
|
|
5
|
+
"position": "佈局位置",
|
|
6
|
+
"notificationType": "通知類型 / 等級",
|
|
7
|
+
"displayTime": "顯示時間 (秒)",
|
|
8
|
+
"highlight": "外框顏色",
|
|
9
|
+
"showIcon": "顯示圖示",
|
|
10
|
+
"customIcon": "自訂圖示",
|
|
11
|
+
"playSound": "播放提示音效",
|
|
12
|
+
"showCountdown": "顯示倒數進度條",
|
|
13
|
+
"preserveCase": "保留按鈕原始大小寫 (不強制大寫)",
|
|
14
|
+
"sendall": "發送至所有瀏覽器工作階段",
|
|
15
|
+
"ok": "預設動作按鈕",
|
|
16
|
+
"cancel": "次要動作按鈕",
|
|
17
|
+
"promptDefault": "輸入框預設文字",
|
|
18
|
+
"promptPlaceholder": "輸入框提示文字 (Placeholder)",
|
|
19
|
+
"raw": "允許 msg.payload 中的原始 HTML / JavaScript",
|
|
20
|
+
"className": "自訂 CSS 類別",
|
|
21
|
+
"topic": "主題 / 標題",
|
|
22
|
+
"name": "節點名稱",
|
|
23
|
+
"showDialog": "顯示對話框",
|
|
24
|
+
"showNotification": "顯示通知"
|
|
25
|
+
},
|
|
26
|
+
"option": {
|
|
27
|
+
"topRight": "右上角 (Top Right)",
|
|
28
|
+
"bottomRight": "右下角 (Bottom Right)",
|
|
29
|
+
"topLeft": "左上角 (Top Left)",
|
|
30
|
+
"bottomLeft": "左下角 (Bottom Left)",
|
|
31
|
+
"dialog": "確認 / 取消 對話框 (Dialog)",
|
|
32
|
+
"prompt": "帶文字輸入框的對話框 (Prompt)",
|
|
33
|
+
"typeDefault": "預設 (中性)",
|
|
34
|
+
"typeInfo": "ℹ 資訊 (藍色)",
|
|
35
|
+
"typeSuccess": "✓ 成功 (綠色)",
|
|
36
|
+
"typeWarning": "⚠ 警告 (琥珀黃)",
|
|
37
|
+
"typeError": "✕ 錯誤 (紅色)"
|
|
38
|
+
},
|
|
39
|
+
"placeholder": {
|
|
40
|
+
"timeout": "例如 3 或 [msg.timeout]",
|
|
41
|
+
"highlight": "例如 #ff0000 或 red",
|
|
42
|
+
"customIcon": "例如 fa-bell、fa-check",
|
|
43
|
+
"ok": "按鈕文字,例如 Ok、Confirm、確定",
|
|
44
|
+
"cancel": "可選按鈕文字,例如 Cancel、取消",
|
|
45
|
+
"promptDefault": "可選預設輸入值",
|
|
46
|
+
"promptPlaceholder": "文字框內的提示字",
|
|
47
|
+
"className": "[msg.className]",
|
|
48
|
+
"topic": "[msg.topic]",
|
|
49
|
+
"name": "節點名稱"
|
|
50
|
+
},
|
|
51
|
+
"section": {
|
|
52
|
+
"basic": "基本設定",
|
|
53
|
+
"appearance": "外觀與樣式",
|
|
54
|
+
"dialog": "對話框設定",
|
|
55
|
+
"advanced": "行為與進階設定"
|
|
56
|
+
},
|
|
57
|
+
"tip": {
|
|
58
|
+
"raw": "<b>安全提醒</b>:啟用原始 HTML/JavaScript 可能允許執行任意腳本。僅在訊息內容來自可信來源時開啟此選項。",
|
|
59
|
+
"icon": "請輸入 Font Awesome 4 圖示名稱(例如 fa-bell、fa-shield)。若未自訂圖示,系統將依據通知類型自動呈現對應圖示。",
|
|
60
|
+
"preserveCase": "勾選後,按鈕文字將完全依據您所輸入的內容呈現,不再被 Angular Material 強制轉換為全大寫。"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-notification-better",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "An enhanced notification and dialog node for Node-RED Dashboard supporting exact button casing, preset notification levels, audio alerts, countdown progress bar, and multi-language UI.",
|
|
5
|
+
"main": "ui_notification.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 0"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"node-red",
|
|
11
|
+
"node-red-dashboard",
|
|
12
|
+
"notification",
|
|
13
|
+
"toast",
|
|
14
|
+
"dialog",
|
|
15
|
+
"prompt",
|
|
16
|
+
"alert",
|
|
17
|
+
"ui"
|
|
18
|
+
],
|
|
19
|
+
"node-red": {
|
|
20
|
+
"version": ">=1.0.0",
|
|
21
|
+
"nodes": {
|
|
22
|
+
"ui_notification_better": "ui_notification.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"files": [
|
|
28
|
+
"ui_notification.js",
|
|
29
|
+
"ui_notification.html",
|
|
30
|
+
"locales",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"node-red-dashboard": ">=2.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('ui_notification_better', {
|
|
3
|
+
category: (RED._("node-red-dashboard/ui_base:ui_base.label.category") || 'dashboard'),
|
|
4
|
+
color: 'rgb(119, 198, 204)',
|
|
5
|
+
defaults: {
|
|
6
|
+
position: {value: 'top right'},
|
|
7
|
+
notificationType: {value: 'default'},
|
|
8
|
+
displayTime: {value: '3'},
|
|
9
|
+
highlight: {value: ''},
|
|
10
|
+
showIcon: {value: true},
|
|
11
|
+
customIcon: {value: ''},
|
|
12
|
+
playSound: {value: false},
|
|
13
|
+
showCountdown: {value: false},
|
|
14
|
+
preserveCase: {value: true},
|
|
15
|
+
sendall: {value: true},
|
|
16
|
+
outputs: {value: 0},
|
|
17
|
+
ok: {value: 'Ok', required: true},
|
|
18
|
+
cancel: {value: ''},
|
|
19
|
+
promptDefault: {value: ''},
|
|
20
|
+
promptPlaceholder: {value: ''},
|
|
21
|
+
raw: {value: false},
|
|
22
|
+
className: {value: ''},
|
|
23
|
+
topic: {value: ''},
|
|
24
|
+
name: {value: ''}
|
|
25
|
+
},
|
|
26
|
+
inputs: 1,
|
|
27
|
+
outputs: 0,
|
|
28
|
+
align: "right",
|
|
29
|
+
icon: "font-awesome/fa-bell",
|
|
30
|
+
paletteLabel: function() {
|
|
31
|
+
return this._("ui_notification_better.paletteLabel");
|
|
32
|
+
},
|
|
33
|
+
label: function() {
|
|
34
|
+
return this.name || (this.position === "prompt" || this.position === "dialog" ? this._("ui_notification_better.label.showDialog") : this._("ui_notification_better.label.showNotification"));
|
|
35
|
+
},
|
|
36
|
+
labelStyle: function() {
|
|
37
|
+
return this.name ? "node_label_italic" : "";
|
|
38
|
+
},
|
|
39
|
+
oneditprepare: function() {
|
|
40
|
+
var node = this;
|
|
41
|
+
|
|
42
|
+
function updateFormVisibility() {
|
|
43
|
+
var pos = $("#node-input-position").val();
|
|
44
|
+
var isDialog = (pos === 'dialog' || pos === 'prompt');
|
|
45
|
+
var isPrompt = (pos === 'prompt');
|
|
46
|
+
|
|
47
|
+
if (isDialog) {
|
|
48
|
+
// 對話框模式
|
|
49
|
+
$("#section-toast-settings").slideUp(150);
|
|
50
|
+
$("#section-dialog-settings").slideDown(200);
|
|
51
|
+
|
|
52
|
+
if (isPrompt) {
|
|
53
|
+
$("#node-dialog-prompt-fields").slideDown(150);
|
|
54
|
+
if (!$("#node-input-cancel").val()) {
|
|
55
|
+
$("#node-input-cancel").val("Cancel");
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
$("#node-dialog-prompt-fields").slideUp(150);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// 浮動通知模式
|
|
62
|
+
$("#section-toast-settings").slideDown(200);
|
|
63
|
+
$("#section-dialog-settings").slideUp(150);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
$("#node-input-position").on("change", updateFormVisibility);
|
|
68
|
+
updateFormVisibility();
|
|
69
|
+
|
|
70
|
+
// 通知等級切換時的顏色提示輔助
|
|
71
|
+
$("#node-input-notificationType").on("change", function() {
|
|
72
|
+
var t = $(this).val();
|
|
73
|
+
var colors = {
|
|
74
|
+
"info": "#2196F3",
|
|
75
|
+
"success": "#4CAF50",
|
|
76
|
+
"warning": "#FF9800",
|
|
77
|
+
"error": "#F44336",
|
|
78
|
+
"default": ""
|
|
79
|
+
};
|
|
80
|
+
var c = colors[t];
|
|
81
|
+
if (c && !$("#node-input-highlight").val()) {
|
|
82
|
+
$("#node-input-highlight").attr("placeholder", c + " (" + t + ")");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 圖示勾選聯動
|
|
87
|
+
$("#node-input-showIcon").on("change", function() {
|
|
88
|
+
if ($(this).is(":checked")) {
|
|
89
|
+
$("#node-row-customIcon").slideDown(150);
|
|
90
|
+
} else {
|
|
91
|
+
$("#node-row-customIcon").slideUp(150);
|
|
92
|
+
}
|
|
93
|
+
}).trigger("change");
|
|
94
|
+
|
|
95
|
+
// 安全提示警告聯動
|
|
96
|
+
$("#node-input-raw").on("change", function() {
|
|
97
|
+
if ($(this).is(":checked")) {
|
|
98
|
+
$("#node-tip-raw-warning").slideDown(200);
|
|
99
|
+
} else {
|
|
100
|
+
$("#node-tip-raw-warning").slideUp(200);
|
|
101
|
+
}
|
|
102
|
+
}).trigger("change");
|
|
103
|
+
},
|
|
104
|
+
oneditsave: function() {
|
|
105
|
+
var pos = $("#node-input-position").val();
|
|
106
|
+
if (pos === 'dialog' || pos === 'prompt') {
|
|
107
|
+
this.outputs = 1;
|
|
108
|
+
} else {
|
|
109
|
+
this.outputs = 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<style>
|
|
116
|
+
.nr-notification-section-header {
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
padding: 6px 10px;
|
|
119
|
+
margin-top: 14px;
|
|
120
|
+
margin-bottom: 12px;
|
|
121
|
+
background: #f7f9fa;
|
|
122
|
+
border-left: 4px solid #77c6cc;
|
|
123
|
+
border-radius: 2px;
|
|
124
|
+
color: #333;
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 8px;
|
|
128
|
+
}
|
|
129
|
+
.nr-notification-tip-box {
|
|
130
|
+
margin: 10px 0;
|
|
131
|
+
padding: 10px 14px;
|
|
132
|
+
background-color: #fff9e6;
|
|
133
|
+
border-left: 4px solid #ffb800;
|
|
134
|
+
border-radius: 4px;
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
line-height: 1.5;
|
|
137
|
+
color: #665200;
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
140
|
+
|
|
141
|
+
<script type="text/html" data-template-name="ui_notification_better">
|
|
142
|
+
<!-- 基本設定區塊 -->
|
|
143
|
+
<div class="nr-notification-section-header">
|
|
144
|
+
<i class="fa fa-sliders"></i> <span data-i18n="ui_notification_better.section.basic"></span>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div class="form-row">
|
|
148
|
+
<label for="node-input-position"><i class="fa fa-th-large"></i> <span data-i18n="ui_notification_better.label.position"></span></label>
|
|
149
|
+
<select id="node-input-position" style="width:70%;">
|
|
150
|
+
<option value="top right" data-i18n="ui_notification_better.option.topRight"></option>
|
|
151
|
+
<option value="bottom right" data-i18n="ui_notification_better.option.bottomRight"></option>
|
|
152
|
+
<option value="top left" data-i18n="ui_notification_better.option.topLeft"></option>
|
|
153
|
+
<option value="bottom left" data-i18n="ui_notification_better.option.bottomLeft"></option>
|
|
154
|
+
<option value="dialog" data-i18n="ui_notification_better.option.dialog"></option>
|
|
155
|
+
<option value="prompt" data-i18n="ui_notification_better.option.prompt"></option>
|
|
156
|
+
</select>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="form-row">
|
|
160
|
+
<label for="node-input-notificationType"><i class="fa fa-tag"></i> <span data-i18n="ui_notification_better.label.notificationType"></span></label>
|
|
161
|
+
<select id="node-input-notificationType" style="width:70%;">
|
|
162
|
+
<option value="default" data-i18n="ui_notification_better.option.typeDefault"></option>
|
|
163
|
+
<option value="info" data-i18n="ui_notification_better.option.typeInfo"></option>
|
|
164
|
+
<option value="success" data-i18n="ui_notification_better.option.typeSuccess"></option>
|
|
165
|
+
<option value="warning" data-i18n="ui_notification_better.option.typeWarning"></option>
|
|
166
|
+
<option value="error" data-i18n="ui_notification_better.option.typeError"></option>
|
|
167
|
+
</select>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div class="form-row">
|
|
171
|
+
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="ui_notification_better.label.topic"></span></label>
|
|
172
|
+
<input type="text" id="node-input-topic" data-i18n="[placeholder]ui_notification_better.placeholder.topic">
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<!-- 浮動通知專用設定 (Timeout, 進度條等) -->
|
|
176
|
+
<div id="section-toast-settings">
|
|
177
|
+
<div class="form-row">
|
|
178
|
+
<label for="node-input-displayTime"><i class="fa fa-clock-o"></i> <span data-i18n="ui_notification_better.label.displayTime"></span></label>
|
|
179
|
+
<input type="text" id="node-input-displayTime" data-i18n="[placeholder]ui_notification_better.placeholder.timeout">
|
|
180
|
+
</div>
|
|
181
|
+
<div class="form-row">
|
|
182
|
+
<label style="width:auto;" for="node-input-showCountdown">
|
|
183
|
+
<input type="checkbox" id="node-input-showCountdown" style="display:inline-block; width:auto; vertical-align:baseline; margin-right:6px;">
|
|
184
|
+
<span data-i18n="ui_notification_better.label.showCountdown"></span>
|
|
185
|
+
</label>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<!-- 外觀與圖示設定區塊 -->
|
|
190
|
+
<div class="nr-notification-section-header">
|
|
191
|
+
<i class="fa fa-paint-brush"></i> <span data-i18n="ui_notification_better.section.appearance"></span>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div class="form-row">
|
|
195
|
+
<label for="node-input-highlight"><i class="fa fa-square-o"></i> <span data-i18n="ui_notification_better.label.highlight"></span></label>
|
|
196
|
+
<input type="text" id="node-input-highlight" data-i18n="[placeholder]ui_notification_better.placeholder.highlight">
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="form-row">
|
|
200
|
+
<label style="width:auto;" for="node-input-showIcon">
|
|
201
|
+
<input type="checkbox" id="node-input-showIcon" style="display:inline-block; width:auto; vertical-align:baseline; margin-right:6px;">
|
|
202
|
+
<span data-i18n="ui_notification_better.label.showIcon"></span>
|
|
203
|
+
</label>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="form-row" id="node-row-customIcon">
|
|
207
|
+
<label for="node-input-customIcon"><i class="fa fa-font-awesome"></i> <span data-i18n="ui_notification_better.label.customIcon"></span></label>
|
|
208
|
+
<input type="text" id="node-input-customIcon" data-i18n="[placeholder]ui_notification_better.placeholder.customIcon">
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div class="form-row">
|
|
212
|
+
<label for="node-input-className"><i class="fa fa-code"></i> <span data-i18n="ui_notification_better.label.className"></span></label>
|
|
213
|
+
<input type="text" id="node-input-className" data-i18n="[placeholder]ui_notification_better.placeholder.className">
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<!-- 對話框專用設定 (按鈕與大小寫) -->
|
|
217
|
+
<div id="section-dialog-settings" style="display:none;">
|
|
218
|
+
<div class="nr-notification-section-header">
|
|
219
|
+
<i class="fa fa-commenting-o"></i> <span data-i18n="ui_notification_better.section.dialog"></span>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div class="form-row">
|
|
223
|
+
<label for="node-input-ok"><i class="fa fa-check"></i> <span data-i18n="ui_notification_better.label.ok"></span></label>
|
|
224
|
+
<input type="text" id="node-input-ok" data-i18n="[placeholder]ui_notification_better.placeholder.ok">
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="form-row">
|
|
228
|
+
<label for="node-input-cancel"><i class="fa fa-times"></i> <span data-i18n="ui_notification_better.label.cancel"></span></label>
|
|
229
|
+
<input type="text" id="node-input-cancel" data-i18n="[placeholder]ui_notification_better.placeholder.cancel">
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div id="node-dialog-prompt-fields" style="display:none;">
|
|
233
|
+
<div class="form-row">
|
|
234
|
+
<label for="node-input-promptDefault"><i class="fa fa-pencil-square-o"></i> <span data-i18n="ui_notification_better.label.promptDefault"></span></label>
|
|
235
|
+
<input type="text" id="node-input-promptDefault" data-i18n="[placeholder]ui_notification_better.placeholder.promptDefault">
|
|
236
|
+
</div>
|
|
237
|
+
<div class="form-row">
|
|
238
|
+
<label for="node-input-promptPlaceholder"><i class="fa fa-i-cursor"></i> <span data-i18n="ui_notification_better.label.promptPlaceholder"></span></label>
|
|
239
|
+
<input type="text" id="node-input-promptPlaceholder" data-i18n="[placeholder]ui_notification_better.placeholder.promptPlaceholder">
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<div class="form-row">
|
|
244
|
+
<label style="width:auto;" for="node-input-preserveCase">
|
|
245
|
+
<input type="checkbox" id="node-input-preserveCase" style="display:inline-block; width:auto; vertical-align:baseline; margin-right:6px;">
|
|
246
|
+
<span data-i18n="ui_notification_better.label.preserveCase"></span>
|
|
247
|
+
</label>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<!-- 行為與進階設定區塊 -->
|
|
252
|
+
<div class="nr-notification-section-header">
|
|
253
|
+
<i class="fa fa-cogs"></i> <span data-i18n="ui_notification_better.section.advanced"></span>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div class="form-row">
|
|
257
|
+
<label style="width:auto;" for="node-input-playSound">
|
|
258
|
+
<input type="checkbox" id="node-input-playSound" style="display:inline-block; width:auto; vertical-align:baseline; margin-right:6px;">
|
|
259
|
+
<span data-i18n="ui_notification_better.label.playSound"></span>
|
|
260
|
+
</label>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div class="form-row">
|
|
264
|
+
<label style="width:auto;" for="node-input-sendall">
|
|
265
|
+
<input type="checkbox" id="node-input-sendall" style="display:inline-block; width:auto; vertical-align:baseline; margin-right:6px;">
|
|
266
|
+
<span data-i18n="ui_notification_better.label.sendall"></span>
|
|
267
|
+
</label>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<div class="form-row">
|
|
271
|
+
<label style="width:auto;" for="node-input-raw">
|
|
272
|
+
<input type="checkbox" id="node-input-raw" style="display:inline-block; width:auto; vertical-align:baseline; margin-right:6px;">
|
|
273
|
+
<span data-i18n="ui_notification_better.label.raw"></span>
|
|
274
|
+
</label>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div id="node-tip-raw-warning" class="nr-notification-tip-box" style="display:none;" data-i18n="[html]ui_notification_better.tip.raw"></div>
|
|
278
|
+
|
|
279
|
+
<div class="form-row" style="margin-top:14px;">
|
|
280
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="ui_notification_better.label.name"></span></label>
|
|
281
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]ui_notification_better.placeholder.name">
|
|
282
|
+
</div>
|
|
283
|
+
</script>
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* node-red-contrib-notification-better
|
|
3
|
+
* 為 Node-RED Dashboard 提供更強大、客製化度更高的通知與對話框節點
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = function(RED) {
|
|
7
|
+
var ui;
|
|
8
|
+
try {
|
|
9
|
+
ui = RED.require("node-red-dashboard")(RED);
|
|
10
|
+
} catch (e) {
|
|
11
|
+
try {
|
|
12
|
+
ui = require("node-red-dashboard")(RED);
|
|
13
|
+
} catch (e2) {
|
|
14
|
+
RED.log.warn("node-red-contrib-notification-better: 找不到 node-red-dashboard,請確保已安裝該套件。");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 全域 CSS 注入:解除 Angular Material 強制大寫,並提供進階視覺美化與主題樣式
|
|
19
|
+
var globalStyleInjected = false;
|
|
20
|
+
var betterNotificationCSS = `
|
|
21
|
+
<style id="nr-notification-better-styles">
|
|
22
|
+
/* 徹底解除對話框與通知按鈕強制大寫的限制,忠實保留原始大小寫 */
|
|
23
|
+
.nr-dashboard-dialog .md-button,
|
|
24
|
+
.nr-dashboard-dialog .md-confirm-button,
|
|
25
|
+
.nr-dashboard-dialog .md-cancel-button,
|
|
26
|
+
.nr-dashboard-dialog button,
|
|
27
|
+
.nr-better-notification .md-button,
|
|
28
|
+
.nr-better-dialog .md-button,
|
|
29
|
+
.nr-preserve-case .md-button,
|
|
30
|
+
md-dialog.nr-better-notification .md-button,
|
|
31
|
+
md-dialog.nr-preserve-case .md-button,
|
|
32
|
+
md-toast.nr-better-notification .md-button {
|
|
33
|
+
text-transform: none !important;
|
|
34
|
+
font-weight: 500 !important;
|
|
35
|
+
letter-spacing: 0.3px !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* 現代化通知卡片外觀增強 */
|
|
39
|
+
md-toast.nr-better-notification .md-toast-content {
|
|
40
|
+
border-radius: 8px !important;
|
|
41
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25) !important;
|
|
42
|
+
padding: 12px 18px !important;
|
|
43
|
+
font-size: 14px !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* 通知等級外框與主題色彩 */
|
|
47
|
+
.nr-type-info {
|
|
48
|
+
border-left: 6px solid #2196F3 !important;
|
|
49
|
+
}
|
|
50
|
+
.nr-type-success {
|
|
51
|
+
border-left: 6px solid #4CAF50 !important;
|
|
52
|
+
}
|
|
53
|
+
.nr-type-warning {
|
|
54
|
+
border-left: 6px solid #FF9800 !important;
|
|
55
|
+
}
|
|
56
|
+
.nr-type-error {
|
|
57
|
+
border-left: 6px solid #F44336 !important;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* 倒數進度條動畫 */
|
|
61
|
+
.nr-toast-progress-wrap {
|
|
62
|
+
position: absolute;
|
|
63
|
+
left: 0;
|
|
64
|
+
bottom: 0;
|
|
65
|
+
width: 100%;
|
|
66
|
+
height: 4px;
|
|
67
|
+
background: rgba(255, 255, 255, 0.2);
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
border-bottom-left-radius: 8px;
|
|
70
|
+
border-bottom-right-radius: 8px;
|
|
71
|
+
}
|
|
72
|
+
.nr-toast-progress-bar {
|
|
73
|
+
height: 100%;
|
|
74
|
+
background: rgba(255, 255, 255, 0.85);
|
|
75
|
+
width: 100%;
|
|
76
|
+
animation: nrProgressLinear linear forwards;
|
|
77
|
+
}
|
|
78
|
+
@keyframes nrProgressLinear {
|
|
79
|
+
from { width: 100%; }
|
|
80
|
+
to { width: 0%; }
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
// 注入全域樣式至 Dashboard
|
|
86
|
+
function injectGlobalStyles() {
|
|
87
|
+
if (!ui || globalStyleInjected) { return; }
|
|
88
|
+
try {
|
|
89
|
+
ui.emit("update-value", {
|
|
90
|
+
id: "nr_notification_better_global_style",
|
|
91
|
+
msg: {
|
|
92
|
+
templateScope: "global",
|
|
93
|
+
template: betterNotificationCSS
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
globalStyleInjected = true;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
// 若此時 socket 尚未就緒則在每次 input 時嘗試重試
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ToastNode(config) {
|
|
103
|
+
RED.nodes.createNode(this, config);
|
|
104
|
+
var node = this;
|
|
105
|
+
|
|
106
|
+
if (!ui) {
|
|
107
|
+
node.error("node-red-contrib-notification-better 需要依賴 node-red-dashboard,請先在 Palette 管理員安裝 node-red-dashboard。");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 基本與進階設定解析
|
|
112
|
+
if (config.hasOwnProperty("displayTime") && (config.displayTime.length > 0)) {
|
|
113
|
+
try {
|
|
114
|
+
this.displayTime = parseFloat(config.displayTime) * 1000;
|
|
115
|
+
} catch(e) {
|
|
116
|
+
this.displayTime = 3000;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
this.position = config.position || "top right";
|
|
120
|
+
this.notificationType = config.notificationType || "default";
|
|
121
|
+
this.highlight = config.highlight || "";
|
|
122
|
+
this.showIcon = config.showIcon !== undefined ? config.showIcon : true;
|
|
123
|
+
this.customIcon = config.customIcon || "";
|
|
124
|
+
this.playSound = config.playSound || false;
|
|
125
|
+
this.showCountdown = config.showCountdown !== undefined ? config.showCountdown : false;
|
|
126
|
+
this.preserveCase = config.preserveCase !== undefined ? config.preserveCase : true;
|
|
127
|
+
this.sendall = config.sendall === undefined ? true : config.sendall;
|
|
128
|
+
|
|
129
|
+
// 對話框屬性
|
|
130
|
+
this.ok = config.ok || "Ok";
|
|
131
|
+
this.cancel = config.cancel || "";
|
|
132
|
+
this.promptDefault = config.promptDefault || "";
|
|
133
|
+
this.promptPlaceholder = config.promptPlaceholder || "";
|
|
134
|
+
|
|
135
|
+
// 進階屬性
|
|
136
|
+
this.raw = config.raw || false;
|
|
137
|
+
this.className = config.className || "";
|
|
138
|
+
this.topic = config.topic || "";
|
|
139
|
+
|
|
140
|
+
// 嘗試在節點初始化時注入全域樣式
|
|
141
|
+
injectGlobalStyles();
|
|
142
|
+
|
|
143
|
+
var done = ui.add({
|
|
144
|
+
node: node,
|
|
145
|
+
control: {},
|
|
146
|
+
storeFrontEndInputAsState: false,
|
|
147
|
+
forwardInputMessages: false,
|
|
148
|
+
beforeSend: function (msg) {
|
|
149
|
+
var m = msg.payload.msg;
|
|
150
|
+
m.topic = node.topic || m.topic;
|
|
151
|
+
return m;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 預設等級顏色與圖示映射表
|
|
156
|
+
var typePresets = {
|
|
157
|
+
"info": {
|
|
158
|
+
color: "#2196F3",
|
|
159
|
+
icon: "fa-info-circle",
|
|
160
|
+
className: "nr-type-info"
|
|
161
|
+
},
|
|
162
|
+
"success": {
|
|
163
|
+
color: "#4CAF50",
|
|
164
|
+
icon: "fa-check-circle",
|
|
165
|
+
className: "nr-type-success"
|
|
166
|
+
},
|
|
167
|
+
"warning": {
|
|
168
|
+
color: "#FF9800",
|
|
169
|
+
icon: "fa-exclamation-triangle",
|
|
170
|
+
className: "nr-type-warning"
|
|
171
|
+
},
|
|
172
|
+
"error": {
|
|
173
|
+
color: "#F44336",
|
|
174
|
+
icon: "fa-times-circle",
|
|
175
|
+
className: "nr-type-error"
|
|
176
|
+
},
|
|
177
|
+
"default": {
|
|
178
|
+
color: "",
|
|
179
|
+
icon: "fa-bell",
|
|
180
|
+
className: ""
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
node.on('input', function(msg) {
|
|
185
|
+
// 每次處理 input 時再次確保全域樣式已送達
|
|
186
|
+
injectGlobalStyles();
|
|
187
|
+
|
|
188
|
+
if (node.sendall === true && !msg.socketid) {
|
|
189
|
+
delete msg.socketid;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 動態逾時秒數計算
|
|
193
|
+
var dt = node.displayTime || 3000;
|
|
194
|
+
if (msg.hasOwnProperty("timeout") && !isNaN(parseFloat(msg.timeout))) {
|
|
195
|
+
dt = parseFloat(msg.timeout) * 1000;
|
|
196
|
+
} else if (msg.hasOwnProperty("displayTime") && !isNaN(parseFloat(msg.displayTime))) {
|
|
197
|
+
dt = parseFloat(msg.displayTime) * 1000;
|
|
198
|
+
}
|
|
199
|
+
if (dt <= 0) {
|
|
200
|
+
dt = 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 動態判定通知等級
|
|
204
|
+
var currentType = (msg.notificationType || msg.status || msg.level || node.notificationType || "default").toLowerCase();
|
|
205
|
+
var preset = typePresets[currentType] || typePresets["default"];
|
|
206
|
+
|
|
207
|
+
// 邊框高亮顏色(支援節點設定與動態傳入)
|
|
208
|
+
var currentHighlight = msg.highlight || msg.border || node.highlight || preset.color;
|
|
209
|
+
|
|
210
|
+
// 圖示判定
|
|
211
|
+
var currentIcon = msg.icon || node.customIcon || (node.showIcon ? preset.icon : "");
|
|
212
|
+
if (currentIcon && currentIcon.indexOf("fa-") === -1) {
|
|
213
|
+
currentIcon = "fa-" + currentIcon;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 佈局判定
|
|
217
|
+
var currentPosition = msg.position || node.position;
|
|
218
|
+
var isDialog = (currentPosition === "dialog" || currentPosition === "prompt");
|
|
219
|
+
var isPrompt = (currentPosition === "prompt");
|
|
220
|
+
|
|
221
|
+
// 按鈕文字判定(忠實保留原始大小寫)
|
|
222
|
+
var okLabel = msg.ok !== undefined ? msg.ok : node.ok;
|
|
223
|
+
var cancelLabel = msg.cancel !== undefined ? msg.cancel : node.cancel;
|
|
224
|
+
|
|
225
|
+
// 標題判定
|
|
226
|
+
var currentTitle = msg.topic !== undefined ? msg.topic : (msg.title !== undefined ? msg.title : node.topic);
|
|
227
|
+
|
|
228
|
+
// 訊息內容判定
|
|
229
|
+
var currentMessage = msg.payload !== undefined ? msg.payload : "";
|
|
230
|
+
if (typeof currentMessage === "object") {
|
|
231
|
+
try {
|
|
232
|
+
currentMessage = JSON.stringify(currentMessage, null, 2);
|
|
233
|
+
} catch(e) {
|
|
234
|
+
currentMessage = String(currentMessage);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var isRaw = msg.hasOwnProperty("raw") ? Boolean(msg.raw) : Boolean(node.raw);
|
|
239
|
+
|
|
240
|
+
// 音效判定
|
|
241
|
+
var playAudio = msg.hasOwnProperty("playSound") ? Boolean(msg.playSound) : Boolean(node.playSound);
|
|
242
|
+
var soundScript = "";
|
|
243
|
+
if (playAudio) {
|
|
244
|
+
// 利用瀏覽器內建 Web Audio API 播放提示音,無須依賴外部音訊檔案
|
|
245
|
+
soundScript = `<script>(function(){try{var A=window.AudioContext||window.webkitAudioContext;if(A){var c=new A(),o=c.createOscillator(),g=c.createGain();o.type='sine';o.frequency.setValueAtTime(${currentType==='error'?330:587},c.currentTime);o.frequency.exponentialRampToValueAtTime(${currentType==='error'?220:880},c.currentTime+0.2);g.gain.setValueAtTime(0.15,c.currentTime);g.gain.exponentialRampToValueAtTime(0.01,c.currentTime+0.25);o.connect(g);g.connect(c.destination);o.start();o.stop(c.currentTime+0.25);}}catch(e){}})();</script>`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 當啟用 raw 時,組合圖示、音效與倒數進度條
|
|
249
|
+
if (isRaw) {
|
|
250
|
+
var iconHtml = (node.showIcon && currentIcon) ? `<i class="fa ${currentIcon}" style="margin-right:8px; vertical-align:middle;"></i> ` : "";
|
|
251
|
+
var progressHtml = (!isDialog && node.showCountdown) ? `<div class="nr-toast-progress-wrap"><div class="nr-toast-progress-bar" style="animation-duration:${dt}ms;"></div></div>` : "";
|
|
252
|
+
currentMessage = `${soundScript}${iconHtml}${currentMessage}${progressHtml}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 組合 CSS 類別
|
|
256
|
+
var classList = ["nr-better-notification"];
|
|
257
|
+
if (node.preserveCase) {
|
|
258
|
+
classList.push("nr-preserve-case");
|
|
259
|
+
}
|
|
260
|
+
if (isDialog) {
|
|
261
|
+
classList.push("nr-better-dialog");
|
|
262
|
+
}
|
|
263
|
+
if (preset.className) {
|
|
264
|
+
classList.push(preset.className);
|
|
265
|
+
}
|
|
266
|
+
if (node.className) {
|
|
267
|
+
classList.push(node.className);
|
|
268
|
+
}
|
|
269
|
+
if (msg.className) {
|
|
270
|
+
classList.push(msg.className);
|
|
271
|
+
}
|
|
272
|
+
var finalToastClass = classList.join(" ").trim();
|
|
273
|
+
|
|
274
|
+
// 發送至前端 socket
|
|
275
|
+
ui.emitSocket('show-toast', {
|
|
276
|
+
title: currentTitle,
|
|
277
|
+
toastClass: finalToastClass,
|
|
278
|
+
message: currentMessage,
|
|
279
|
+
highlight: currentHighlight,
|
|
280
|
+
displayTime: dt,
|
|
281
|
+
position: currentPosition,
|
|
282
|
+
id: node.id,
|
|
283
|
+
dialog: isDialog,
|
|
284
|
+
prompt: isPrompt,
|
|
285
|
+
ok: okLabel,
|
|
286
|
+
cancel: cancelLabel,
|
|
287
|
+
socketid: msg.socketid,
|
|
288
|
+
raw: isRaw,
|
|
289
|
+
msg: msg
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
node.on("close", done);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
RED.nodes.registerType("ui_notification_better", ToastNode);
|
|
297
|
+
};
|