1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge pull request #2493 from node-red/groups

Grouping Nodes
This commit is contained in:
Nick O'Leary 2020-03-30 23:43:27 +01:00 committed by GitHub
commit 222ece2533
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
155 changed files with 7807 additions and 1707 deletions

View File

@ -1,6 +1,6 @@
---
name: Bug report
about: Reproducable software issues in the core of Node-RED
about: Reproducible software issues in the core of Node-RED
title: ''
labels: ''
assignees: ''

View File

@ -29,6 +29,6 @@ the [forum](https://discourse.nodered.org) or
<!-- Put an `x` in the boxes that apply -->
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team.
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [ ] I have run `grunt` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality

File diff suppressed because it is too large Load Diff

View File

@ -151,6 +151,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
@ -177,6 +178,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/group.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
@ -193,7 +195,8 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js",
"node_modules/marked/marked.min.js",
"node_modules/dompurify/dist/purify.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js",
"node_modules/jsonata/jsonata-es5.min.js",

View File

@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "1.0.3",
"version": "1.0.4",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@ -24,7 +24,7 @@
}
],
"dependencies": {
"ajv": "6.10.2",
"ajv": "6.12.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
@ -34,47 +34,49 @@
"cookie": "0.4.0",
"cookie-parser": "1.4.4",
"cors": "2.8.5",
"cron": "1.7.2",
"cron": "1.8.2",
"denque": "1.4.1",
"express": "4.17.1",
"express-session": "1.17.0",
"fs-extra": "8.1.0",
"fs.notify": "0.0.4",
"hash-sum": "2.0.0",
"https-proxy-agent": "2.2.4",
"https-proxy-agent": "5.0.0",
"i18next": "15.1.2",
"iconv-lite": "0.5.0",
"iconv-lite": "0.5.1",
"is-utf8": "0.2.1",
"js-yaml": "3.13.1",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.0",
"jsonata": "1.8.1",
"media-typer": "1.1.0",
"memorystore": "1.6.1",
"memorystore": "1.6.2",
"mime": "2.4.4",
"mqtt": "2.18.8",
"multer": "1.4.2",
"mustache": "3.0.2",
"mustache": "4.0.0",
"node-red-node-rbe": "^0.2.6",
"node-red-node-sentiment": "^0.1.6",
"node-red-node-tail": "^0.1.0",
"nopt": "4.0.1",
"oauth2orize": "1.11.0",
"on-headers": "1.0.2",
"passport": "0.4.0",
"passport": "0.4.1",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.4.1",
"request": "2.88.0",
"semver": "6.3.0",
"uglify-js": "3.6.9",
"uglify-js": "3.8.0",
"when": "3.7.8",
"ws": "6.2.1",
"xml2js": "0.4.22"
"xml2js": "0.4.23"
},
"optionalDependencies": {
"bcrypt": "3.0.6"
},
"devDependencies": {
"marked": "0.8.0",
"dompurify": "2.0.8",
"grunt": "~1.0.4",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.2",
@ -102,7 +104,7 @@
"mocha": "^5.2.0",
"mosca": "^2.8.3",
"node-red-node-test-helper": "^0.2.3",
"node-sass": "^4.13.0",
"node-sass": "^4.13.1",
"should": "^8.4.0",
"sinon": "1.17.7",
"stoppable": "^1.1.0",

View File

@ -101,7 +101,10 @@ function login(req,res) {
}
} else if (mergedAdminAuth.type === "strategy") {
var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot;
var urlPrefix = (settings.httpAdminRoot||"").replace(/\/$/,"");
if (urlPrefix.length > 0) {
urlPrefix += "/";
}
response = {
"type":"strategy",
"prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "1.0.3",
"version": "1.0.4",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,21 +16,21 @@
}
],
"dependencies": {
"@node-red/util": "1.0.3",
"@node-red/editor-client": "1.0.3",
"@node-red/util": "1.0.4",
"@node-red/editor-client": "1.0.4",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
"clone": "2.1.2",
"cors": "2.8.5",
"express-session": "1.17.0",
"express": "4.17.1",
"memorystore": "1.6.1",
"memorystore": "1.6.2",
"mime": "2.4.4",
"mustache": "3.0.2",
"mustache": "4.0.0",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"passport": "0.4.0",
"passport": "0.4.1",
"when": "3.7.8",
"ws": "6.2.1"
},

View File

@ -34,11 +34,11 @@
"view" : "Ansicht",
"grid" : "Gitter",
"showGrid" : "Raster anzeigen",
"snapGrid" : "Einrasten am Raster",
"snapGrid" : "Am Raster ausrichten",
"gridSize" : "Rastergröße",
"textDir" : "Textrichtung",
"defaultDir" : "Standard",
"ltr" : "Links-nach-rechts",
"ltr" : "Von links nach rechts",
"rtl" : "Von rechts nach links",
"auto" : "Kontextuell"
},
@ -53,15 +53,15 @@
"import" : "Import",
"export" : "Exportieren",
"search" : "Flows durchsuchen",
"searchInput" : "durchsuchen Sie Ihre Flows",
"searchInput" : "Flows durchsuchen",
"subflows" : "Subflow",
"createSubflow" : "Subflow erstellen",
"selectionToSubflow" : "Auswahl für Subflow",
"selectionToSubflow" : "Auswahl zu Subflow",
"flows" : "Flows",
"add" : "Hinzufügen",
"rename" : "Umbenennen",
"delete" : "Löschen",
"keyboardShortcuts" : "Tastaturkurzbefehle",
"keyboardShortcuts" : "Tastenkürzel",
"login" : "Anmelden",
"logout" : "Abmelden",
"editPalette" : "Palette verwalten",
@ -217,7 +217,7 @@
"remote" : "Ferne Änderungen",
"reviewChanges" : "Änderungen prüfen",
"noBinaryFileShowed" : "Der Inhalt der Binärdatei kann nicht angezeigt",
"viewCommitDiff" : "Änderungen festschreiben",
"viewCommitDiff" : "Änderungen committen",
"compareChanges" : "Änderungen vergleichen",
"saveConflict" : "Konfliktlösung speichern",
"conflictHeader" : "<span> __resolved__ </span> von <span> __unresolved__ </span> -Konflikten behoben",
@ -226,8 +226,8 @@
"newVersionError" : "Neue Version enthält keine gültige JSON-Datei:"
},
"subflow" : {
"editSubflow" : "Flowschablone bearbeiten: __name__",
"edit" : "Flowsschablone bearbeiten",
"editSubflow" : "Subflow bearbeiten: __name__",
"edit" : "Subflow bearbeiten",
"subflowInstances" : "Es ist __count__ Instanz dieser Subflow-Vorlage vorhanden.",
"subflowInstances_plural" : "Es gibt __count__ Instanzen dieser Subflow-Vorlage.",
"editSubflowProperties" : "Eigenschaften bearbeiten",
@ -266,7 +266,7 @@
}
},
"keyboard" : {
"title" : "Tastaturkurzbefehle",
"title" : "Tastenkürzel",
"keyboard" : "Tastatur",
"filterActions" : "Filteraktionen",
"shortcut" : "Direktaufruf",
@ -283,7 +283,7 @@
"exportNode" : "Node exportieren",
"nudgeNode" : "Ausgewählte Nodes verschieben (1px)",
"moveNode" : "Ausgewählte Nodes verschieben (20px)",
"toggleSidebar" : "Seitenleiste ein-/ausschalten",
"toggleSidebar" : "Seitenleiste ein-/ausblenden",
"copyNode" : "Ausgewählte Nodes kopieren",
"cutNode" : "Ausgewählte Nodes ausschneiden",
"pasteNode" : "Node einfügen",
@ -308,7 +308,7 @@
},
"palette" : {
"noInfo" : "Keine Informationen verfügbar",
"filter" : "Filter Nodes",
"filter" : "Nodes filtern",
"search" : "Suchmodule",
"addCategory" : "Neu hinzufügen ...",
"label" : {
@ -366,11 +366,11 @@
"remove" : "entfernen",
"update" : "Update auf __version__",
"updated" : "aktualisiert",
"install" : "installieren",
"installed" : "installiert",
"install" : "Installieren",
"installed" : "Installiert",
"loading" : "Kataloge werden geladen ...",
"tab-nodes" : "Nodes",
"tab-install" : "installieren",
"tab-install" : "Installieren",
"sort" : "Sortierung:",
"sortAZ" : "a-z",
"sortRecent" : "kürzlich",
@ -452,7 +452,7 @@
"name" : "Kontextdaten",
"label" : "Kontext",
"none" : "keine ausgewählt",
"refresh" : "Aktualisierung zum Laden",
"refresh" : "Zum Aktualisieren neu laden",
"empty" : "leer",
"node" : "Node",
"flow" : "Flow",
@ -477,7 +477,7 @@
"none" : "Keine",
"install" : "installieren",
"removeFromProject" : "Aus Projekt entfernen",
"addToProject" : "zu Projekt hinzufügen",
"addToProject" : "Zu Projekt hinzufügen",
"files" : "Dateien",
"flow" : "Flow",
"credentials" : "Berechtigungsnachweis",
@ -510,7 +510,7 @@
},
"userSettings" : {
"committerDetail" : "Committer-Details",
"committerTip" : "Leer Wert für Systemstandardwert belassen",
"committerTip" : "Leer lassen für Systemstandard",
"userName" : "Benutzername",
"email" : "E-Mail",
"sshKeys" : "SSH-Schlüssel",
@ -544,7 +544,7 @@
"revertChanges" : "Änderungen zurücksetzen",
"localChanges" : "Lokale Änderungen",
"none" : "Keine",
"conflictResolve" : "Alle Konflikte wurden aufgelöst. Festschreiben der Änderungen, um den Mischvorgang abzuschließen.",
"conflictResolve" : "Alle Konflikte wurden aufgelöst. Committe die Änderungen, um den Merge Request abzuschließen.",
"localFiles" : "Lokale Dateien",
"all" : "alle",
"unmergedChanges" : "Nicht zusammengeführte Änderungen",

View File

@ -14,7 +14,11 @@
"back": "Back",
"next": "Next",
"clone": "Clone project",
"cont": "Continue"
"cont": "Continue",
"line": "Outline",
"fill": "Fill",
"color": "Color",
"position": "Position"
},
"type": {
"string": "string",
@ -91,7 +95,12 @@
"projects-new": "New",
"projects-open": "Open",
"projects-settings": "Project Settings",
"showNodeLabelDefault": "Show label of newly added nodes"
"showNodeLabelDefault": "Show label of newly added nodes",
"groups": "Groups",
"groupSelection": "Group selection",
"ungroupSelection": "Ungroup selection",
"groupMergeSelection": "Merge selection",
"groupRemoveSelection": "Remove from group"
}
},
"actions": {
@ -171,6 +180,8 @@
"node_plural": "__count__ nodes",
"configNode": "__count__ configuration node",
"configNode_plural": "__count__ configuration nodes",
"group": "__count__ group",
"group_plural": "__count__ groups",
"flow": "__count__ flow",
"flow_plural": "__count__ flows",
"subflow": "__count__ subflow",
@ -186,6 +197,9 @@
"nodesImported": "Imported:",
"nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied",
"groupCopied": "__count__ group copied",
"groupCopied_plural": "__count__ groups copied",
"groupStyleCopied": "Group style copied",
"invalidFlow": "Invalid flow: __message__",
"export": {
"selected":"selected nodes",
@ -308,6 +322,13 @@
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
}
},
"group": {
"editGroup": "Edit group: __name__",
"errors": {
"cannotCreateDiffGroups": "Cannot create group using nodes from different groups",
"cannotAddSubflowPorts": "Cannot add subflow ports to a group"
}
},
"editor": {
"configEdit": "Edit",
"configAdd": "Add",
@ -539,6 +560,7 @@
"label": "info",
"node": "Node",
"type": "Type",
"group": "Group",
"module": "Module",
"id": "ID",
"status": "Status",
@ -978,7 +1000,8 @@
"passphrase": "Passphrase",
"retry": "Retry",
"update-failed": "Failed to update auth",
"unhandled": "Unhandled error response"
"unhandled": "Unhandled error response",
"host-key-verify-failed": "<p>Host key verification failed.</p><p>The repository host key could not be verified. Please update your <code>known_hosts</code> file and try again."
},
"create-branch-list": {
"invalid": "Invalid branch",

View File

@ -24,7 +24,7 @@
"buffer": "buffer",
"object": "对象",
"jsonString": "JSON字符串",
"undefined": "定义",
"undefined": "定义",
"null": "空"
}
},
@ -1008,6 +1008,7 @@
"en-US": "英文",
"ja": "日语",
"ko": "韩文",
"zh-CN": "简体中文"
"zh-CN": "简体中文",
"zh-TW": "繁体中文"
}
}

View File

@ -262,5 +262,9 @@
"$distinct": {
"args": "array",
"desc": "返回一个数组,其中重复的值已从`数组`中删除"
},
"$type": {
"args": "value",
"desc": "以字符串形式返回`值`的类型。 如果该`值`未定义,则将返回`未定义`"
}
}

View File

@ -15,6 +15,17 @@
"next": "下一步",
"clone": "複製專案",
"cont": "Continue"
},
"type": {
"string": "字符串",
"number": "數值",
"boolean": "布林",
"array": "數組",
"buffer": "buffer",
"object": "對象",
"jsonString": "JSON字符串",
"undefined": "未定義",
"null": "空"
}
},
"workspace": {
@ -29,8 +40,7 @@
"enabled": "有效",
"disabled": "無效",
"info": "詳細描述",
"selectNodes": "點擊節點用於選擇",
"tip": "詳細描述支援Markdown羽量級標記語言並將出現在資訊標籤中。"
"selectNodes": "點擊節點用於選擇"
},
"menu": {
"label": {
@ -45,14 +55,14 @@
"ltr": "從左到右",
"rtl": "從右到左",
"auto": "上下文",
"language": "Language",
"browserDefault": "Browser default"
"language": "語言",
"browserDefault": "瀏覽器默認"
},
"sidebar": {
"show": "顯示側邊欄"
},
"palette": {
"show": "Show palette"
"show": "顯示控制板"
},
"settings": "設置",
"userSettings": "使用者設置",
@ -81,10 +91,7 @@
"projects-new": "新專案",
"projects-open": "開啟專案",
"projects-settings": "專案設定",
"showNodeLabelDefault": "顯示新添加節點的標籤",
"clipboard": "剪貼簿",
"library": "庫",
"examples": "範例"
"showNodeLabelDefault": "顯示新添加節點的標籤"
}
},
"actions": {
@ -204,8 +211,7 @@
},
"copyMessagePath": "已複製路徑",
"copyMessageValue": "已複製數值",
"copyMessageValue_truncated": "已複製捨棄的數值",
"selectNodes": "選擇上面的文本並複製到剪貼簿"
"copyMessageValue_truncated": "已複製捨棄的數值"
},
"deploy": {
"deploy": "部署",
@ -237,7 +243,7 @@
"undeployedChanges": "您有未部署的更改。\n\n離開此頁面將丟失這些更改。",
"improperlyConfigured": "工作區包含一些未正確配置的節點:",
"unknown": "工作區包含一些未知的節點類型:",
"confirm": "確定要部署嗎?",
"confirm": "確定要部署嗎?",
"doNotWarn": "不要再對此發出警告",
"conflict": "伺服器正在運行較新的一組流程。",
"backgroundUpdate": "伺服器上的流程已更新。",
@ -300,8 +306,7 @@
"errors": {
"noNodesSelected": "<strong>無法創建子流程</strong>: 未選擇節點",
"multipleInputsToSelection": "<strong>無法創建子流程</strong>: 多個輸入到了選擇"
},
"format": "標記格式"
}
},
"editor": {
"configEdit": "編輯",
@ -316,17 +321,53 @@
"addNewType": "添加新的__type__節點",
"nodeProperties": "節點屬性",
"label": "Label",
"color": "顏色",
"portLabels": "埠標籤",
"labelInputs": "輸入",
"labelOutputs": "輸出",
"settingIcon": "Icon",
"default": "默認",
"noDefaultLabel": "無",
"defaultLabel": "使用默認標籤",
"searchIcons": "搜尋 icons",
"searchIcons": "搜尋圖標",
"useDefault": "使用默認",
"description": "描述",
"show": "顯示",
"hide": "隱藏",
"locale": "選擇界面語言",
"icon": "圖標",
"inputType": "輸入類型",
"inputs": {
"input": "輸入",
"select": "選擇",
"checkbox": "復選框",
"spinner": "微調器",
"none": "空",
"hidden": "隱藏屬性"
},
"types": {
"str": "字符串",
"num": "數字",
"bool": "布爾",
"json": "JSON",
"bin": "buffer",
"env": "環境變量"
},
"menu": {
"input": "輸入",
"select": "選擇",
"checkbox": "復選框",
"spinner": "微調器",
"hidden": "僅標簽"
},
"select": {
"label": "標簽",
"value": "值"
},
"spinner": {
"min": "最小值",
"max": "最大值"
},
"errors": {
"scopeChange": "更改範圍將使其他流程中的節點無法使用",
"invalidProperties": "無效的屬性:"
@ -356,8 +397,9 @@
"cutNode": "剪切所選節點",
"pasteNode": "粘貼節點",
"undoChange": "撤銷上次執行的更改",
"searchBox": "打開搜索框",
"managePalette": "管理面板"
"searchBox": "打開搜尋框",
"managePalette": "管理面板",
"actionList": "動作列表"
},
"library": {
"library": "庫",
@ -371,28 +413,27 @@
"savedNodes": "保存的節點",
"savedType": "已保存__type__",
"saveFailed": "保存失敗: __message__",
"newFolder": "新文件夾",
"types": {
"local": "本地",
"examples": "例子"
},
"exportToLibrary": "將節點匯出到庫",
"filename": "檔案名",
"folder": "資料夾",
"filenamePlaceholder": "文件",
"fullFilenamePlaceholder": "a/b/文件",
"folderPlaceholder": "a/b",
"breadcrumb": "庫"
"exportToLibrary": "將節點匯出到庫"
},
"palette": {
"noInfo": "無可用資訊",
"filter": "過濾節點",
"search": "搜模組",
"search": "搜尋模組",
"addCategory": "添加新的...",
"label": {
"subflows": "子流程",
"network": "網絡",
"common": "共通",
"input": "輸入",
"output": "輸出",
"function": "功能",
"sequence": "序列",
"parser": "解析",
"social": "社交",
"storage": "存儲",
"analysis": "分析",
@ -459,7 +500,7 @@
"sortRecent": "日期順序",
"more": "增加__count__個",
"errors": {
"catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制瞭解更多資訊",
"catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制瞭解更多資訊",
"installFailed": "無法安裝: __module__<br>__message__<br>查看日誌瞭解更多資訊",
"removeFailed": "無法刪除: __module__<br>__message__<br>查看日誌瞭解更多資訊",
"updateFailed": "無法更新: __module__<br>__message__<br>查看日誌瞭解更多資訊",
@ -529,8 +570,10 @@
"none": "無",
"subflows": "子流程",
"flows": "流程",
"filterUnused": "未使用",
"filterAll": "所有",
"showAllConfigNodes": "顯示所有配置節點",
"filterUnused": "未使用",
"showAllUnusedConfigNodes": "顯示所有未使用的配置節點",
"filtered": "__count__ 個隱藏"
},
"context": {
@ -543,7 +586,9 @@
"flow": "流程",
"global": "全局的",
"deleteConfirm": "你確定要刪除這個項目嗎?",
"autoRefresh": "自動刷新"
"autoRefresh": "自動刷新",
"refrsh": "刷新",
"delete": "刪除"
},
"palette": {
"name": "節點管理",
@ -558,6 +603,7 @@
"noSummaryAvailable": "無可用摘要",
"editDescription": "編輯專案描述",
"editDependencies": "編輯項目依賴",
"noDescriptionAvailable": "沒有可用的描述",
"editReadme": "Edit README.md",
"showProjectSettings": "顯示項目設置",
"projectSettings": {
@ -657,15 +703,15 @@
"moreCommits": "更多提交",
"changeLocalBranch": "變更當地分支",
"createBranchPlaceholder": "查找或創建分支",
"upstream": "上的",
"localOverwrite": "您有可通过切换分支覆盖的本地更改。您必须先提交或撤销那些更改。",
"upstream": "上的",
"localOverwrite": "您有可通過切換分支覆蓋的本地更改。您必須先提交或撤銷那些更改。",
"manageRemoteBranch": "管理遠程分支",
"unableToAccess": "無法訪問遠程存儲庫",
"retry": "重試",
"setUpstreamBranch": "設置為上分支",
"setUpstreamBranch": "設置為上分支",
"createRemoteBranchPlaceholder": "查找或創建遠程分支",
"trackedUpstreamBranch": "創建的分支將被設置為跟踪的上游分支。",
"selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟踪的上游分支。",
"trackedUpstreamBranch": "創建的分支將被設置為跟蹤的上遊分支。",
"selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟蹤的上遊分支。",
"pushFailed": "Push失敗因為遠程具有更多的最新提交。請先進行pull與merge然後再嘗試push。",
"push": "push",
"pull": "pull",
@ -683,7 +729,7 @@
"minsAgo": "__count__分鐘前",
"minsAgo_plural": "__count__分鐘前",
"secondsAgo": "秒前",
"notTracking": "您的本地分支當前未跟遠程分支。",
"notTracking": "您的本地分支當前未跟遠程分支。",
"statusUnmergedChanged": "您的存儲庫中有未合併的更改。您需要解決衝突並提交結果。",
"repositoryUpToDate": "您的存儲庫是最新的。",
"commitsAhead": "您的倉庫領先遠程倉庫__count__次提交。您現在可以push這些提交。",
@ -748,10 +794,23 @@
},
"jsonEditor": {
"title": "JSON編輯器",
"format": "格式化JSON"
"format": "格式化JSON",
"rawMode": "編輯 JSON",
"uiMode": "Visual編輯器",
"insertAbove": "在上方插入",
"insertBelow": "在下方插入",
"addItem": "添加項目",
"copyPath": "復制路徑到項目",
"expandItems": "展開項目",
"collapseItems": "收合項目",
"duplicate": "重復",
"error": {
"invalidJSON": "無效的JSON: "
}
},
"markdownEditor": {
"title": "Markdown 編輯器",
"expand": "展開",
"format": "F使用markdown格式化",
"heading1": "Heading 1",
"heading2": "Heading 2",
@ -786,7 +845,7 @@
},
"git-config": {
"setup": "設置您的版本控制客戶端",
"desc0": "Node-RED使用開源工具Git進行版本控制。 它跟對項目文件的更改,並允許您將其推送到遠程存儲庫。",
"desc0": "Node-RED使用開源工具Git進行版本控制。 它跟對項目文件的更改,並允許您將其推送到遠程存儲庫。",
"desc1": "提交一組更改時Git會使用用戶名和電子郵件地址記錄誰進行了更改。 用戶名可以是您想要的任何名稱-不必是您的真實姓名。",
"desc2": "您的Git客戶端已經配置了以下詳細信息。",
"desc3": "您可以稍後在設置對話框的“ Git config”標籤下更改這些設置。",
@ -905,7 +964,7 @@
"confirm": "您確定要刪除此項目嗎?"
},
"create-project-list": {
"search": "搜您的項目",
"search": "搜您的項目",
"current": "當前的"
},
"require-clean": {
@ -938,8 +997,19 @@
},
"editor-tab": {
"properties": "屬性",
"envProperties": "環境變量",
"description": "描述",
"appearance": "外觀",
"preview": "UI預覽",
"defaultValue": "默認值",
"env": "環境變量"
},
"languages": {
"de": "德語",
"en-US": "英語",
"ja": "日語",
"ko": "韓語",
"zh-CN": "簡體中文",
"zh-TW": "繁體中文"
}
}

View File

@ -214,5 +214,57 @@
"$toMillis": {
"args": "timestamp",
"desc": "將ISO 8601格式的字串`timestamp`轉換為從UNIX時間 (1970年1月1日 UTC/GMT的午夜開始到現在的毫秒數。如果該字串的格式不正確則拋出錯誤。"
},
"$env": {
"args": "arg",
"desc": "返回環境變量的值。\n\n這是Node-RED定義的函數。"
},
"$eval": {
"args": "expr [, context]",
"desc": "使用當前上下文來作為評估依據,分析並評估字符串`expr`其中包含文字JSON或JSONata表達式。"
},
"$formatInteger": {
"args": "number, picture",
"desc": "將“數字”轉換為字符串並將其格式化為“圖片”字符串指定的整數表示形式。圖片字符串參數定義了數字的格式並具有與XPath F&O 3.1 規範中的fnformat-integer相同的語法。"
},
"$parseInteger": {
"args": "string, picture",
"desc": "使用“圖片”字符串指定的格式將“字符串”參數的內容解析為整數作為JSON數字。圖片字符串參數與$formatInteger格式相同。."
},
"$error": {
"args": "[str]",
"desc": "引發錯誤並顯示一條消息。 可選的`str`將替代$error()函數評估的默認消息。"
},
"$assert": {
"args": "arg, str",
"desc": "如果`arg`為真,則該函數返回。 如果arg為假則拋出帶有str的異常作為異常消息。"
},
"$single": {
"args": "array, function",
"desc": "返回滿足參數function謂語的array參數中的唯一值 (比如傳遞值時函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數`functionvalue [index [array []]]`其中value是數組的每個輸入index是該值的位置整個數組作為第三個參數傳遞。"
},
"$encodeUrl": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL組件進行編碼。\n\n示例`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL進行編碼。\n\n示例 `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "解碼以前由encodeUrlComponent創建的統一資源定位器URL組件。 \n\n示例 `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "解碼先前由encodeUrl創建的統一資源定位符URL。 \n\n示例 `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "返回一個數組,其中重復的值已從`數組`中刪除"
},
"$type": {
"args": "value",
"desc": "以字符串形式返回`值`的類型。 如果該`值`未定義,則將返回`未定義`"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "1.0.3",
"version": "1.0.4",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@ -21,6 +21,7 @@ RED.history = (function() {
var i;
var len;
var node;
var group;
var subflow;
var modifiedTabs = {};
var inverseEv;
@ -74,6 +75,15 @@ RED.history = (function() {
RED.nodes.removeLink(ev.links[i]);
}
}
if (ev.groups) {
inverseEv.groups = [];
for (i=0;i<ev.groups.length;i++) {
group = ev.groups[i];
modifiedTabs[group.z] = true;
inverseEv.groups.push(group);
RED.nodes.removeGroup(group);
}
}
if (ev.workspaces) {
inverseEv.workspaces = [];
for (i=0;i<ev.workspaces.length;i++) {
@ -193,12 +203,35 @@ RED.history = (function() {
n.dirty = true;
});
}
if (ev.groups) {
inverseEv.groups = [];
var groupsToAdd = new Set(ev.groups.map(function(g) { return g.id }));
for (i=0;i<ev.groups.length;i++) {
RED.nodes.addGroup(ev.groups[i])
modifiedTabs[ev.groups[i].z] = true;
inverseEv.groups.push(ev.groups[i]);
if (ev.groups[i].g && !groupsToAdd.has(ev.groups[i].g)) {
group = RED.nodes.group(ev.groups[i].g);
if (group.nodes.indexOf(ev.groups[i]) === -1) {
group.nodes.push(ev.groups[i]);
}
RED.group.markDirty(ev.groups[i])
}
}
}
if (ev.nodes) {
inverseEv.nodes = [];
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.add(ev.nodes[i]);
modifiedTabs[ev.nodes[i].z] = true;
inverseEv.nodes.push(ev.nodes[i].id);
if (ev.nodes[i].g) {
group = RED.nodes.group(ev.nodes[i].g);
if (group.nodes.indexOf(ev.nodes[i]) === -1) {
group.nodes.push(ev.nodes[i]);
}
RED.group.markDirty(group)
}
}
}
if (ev.links) {
@ -260,6 +293,13 @@ RED.history = (function() {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
if (ev.addToGroup) {
RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n }),true);
inverseEv.removeFromGroup = ev.addToGroup;
} else if (ev.removeFromGroup) {
RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n }));
inverseEv.addToGroup = ev.removeFromGroup;
}
} else if (ev.t == "edit") {
inverseEv = {
t: "edit",
@ -370,7 +410,9 @@ RED.history = (function() {
if (ev.nodes) {
inverseEv.movedNodes = [];
var z = ev.activeWorkspace;
RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) {
var fullNodeList = RED.nodes.filterNodes({z:ev.subflow.subflow.id});
fullNodeList = fullNodeList.concat(RED.nodes.groups(ev.subflow.subflow.id))
fullNodeList.forEach(function(n) {
n.x += ev.subflow.offsetX;
n.y += ev.subflow.offsetY;
n.dirty = true;
@ -411,6 +453,9 @@ RED.history = (function() {
if (ev.subflow) {
RED.nodes.addSubflow(ev.subflow.subflow);
inverseEv.subflow = ev.subflow;
if (ev.subflow.subflow.g) {
RED.group.addToGroup(RED.nodes.group(ev.subflow.subflow.g),ev.subflow.subflow);
}
}
if (ev.subflows) {
inverseEv.nodes = [];
@ -422,6 +467,9 @@ RED.history = (function() {
if (ev.movedNodes) {
ev.movedNodes.forEach(function(nid) {
nn = RED.nodes.node(nid);
if (!nn) {
nn = RED.nodes.group(nid);
}
nn.x -= ev.subflow.offsetX;
nn.y -= ev.subflow.offsetY;
nn.dirty = true;
@ -450,6 +498,55 @@ RED.history = (function() {
if (ev.order) {
RED.workspaces.order(ev.order);
}
} else if (ev.t == "createGroup") {
inverseEv = {
t: "ungroup",
dirty: RED.nodes.dirty(),
groups: []
}
if (ev.groups) {
for (i=0;i<ev.groups.length;i++) {
inverseEv.groups.push(ev.groups[i]);
RED.group.ungroup(ev.groups[i]);
}
}
} else if (ev.t == "ungroup") {
inverseEv = {
t: "createGroup",
dirty: RED.nodes.dirty(),
groups: []
}
if (ev.groups) {
for (i=0;i<ev.groups.length;i++) {
inverseEv.groups.push(ev.groups[i]);
var nodes = ev.groups[i].nodes.slice();
ev.groups[i].nodes = [];
RED.nodes.addGroup(ev.groups[i]);
RED.group.addToGroup(ev.groups[i],nodes);
}
}
} else if (ev.t == "addToGroup") {
inverseEv = {
t: "removeFromGroup",
dirty: RED.nodes.dirty(),
group: ev.group,
nodes: ev.nodes,
reparent: ev.reparent
}
if (ev.nodes) {
RED.group.removeFromGroup(ev.group,ev.nodes,(ev.hasOwnProperty('reparent')&&ev.hasOwnProperty('reparent')!==undefined)?ev.reparent:true);
}
} else if (ev.t == "removeFromGroup") {
inverseEv = {
t: "addToGroup",
dirty: RED.nodes.dirty(),
group: ev.group,
nodes: ev.nodes,
reparent: ev.reparent
}
if (ev.nodes) {
RED.group.addToGroup(ev.group,ev.nodes);
}
}
Object.keys(modifiedTabs).forEach(function(id) {
@ -460,8 +557,8 @@ RED.history = (function() {
});
RED.nodes.dirty(ev.dirty);
RED.view.updateActive();
RED.view.select(null);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
@ -482,6 +579,9 @@ RED.history = (function() {
list: function() {
return undoHistory;
},
listRedo: function() {
return redoHistory;
},
depth: function() {
return undoHistory.length;
},

View File

@ -61,6 +61,10 @@
"shift-down": "core:step-selection-down",
"shift-left": "core:step-selection-left",
"ctrl-shift-j": "core:show-previous-tab",
"ctrl-shift-k": "core:show-next-tab"
"ctrl-shift-k": "core:show-next-tab",
"ctrl-shift-g": "core:group-selection",
"ctrl-shift-u": "core:ungroup-selection",
"ctrl-shift-c": "core:copy-group-style",
"ctrl-shift-v": "core:paste-group-style"
}
}

View File

@ -27,6 +27,9 @@ RED.nodes = (function() {
var subflows = {};
var loadedFlowVersion = null;
var groups = {};
var groupsByZ = {};
var initialLoad;
var dirty = false;
@ -302,6 +305,10 @@ RED.nodes = (function() {
}
function moveNodeToTab(node, z) {
if (node.type === "group") {
moveGroupToTab(node,z);
return;
}
if (nodeTabMap[node.z]) {
delete nodeTabMap[node.z][node.id];
}
@ -311,6 +318,13 @@ RED.nodes = (function() {
nodeTabMap[z][node.id] = node;
node.z = z;
}
function moveGroupToTab(group, z) {
var index = groupsByZ[group.z].indexOf(group);
groupsByZ[group.z].splice(index,1);
groupsByZ[z] = groupsByZ[z] || [];
groupsByZ[z].push(group);
group.z = z;
}
function removeLink(l) {
var index = links.indexOf(l);
@ -340,6 +354,7 @@ RED.nodes = (function() {
var removedNodes = [];
var removedLinks = [];
var removedGroups = [];
var n;
var node;
for (n=0;n<nodes.length;n++) {
@ -356,11 +371,17 @@ RED.nodes = (function() {
}
}
}
removedGroups = groupsByZ[id] || [];
removedGroups.forEach(function(g) {
delete groups[g.id]
})
delete groupsByZ[id];
for (n=0;n<removedNodes.length;n++) {
var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links);
}
return {nodes:removedNodes,links:removedLinks};
return {nodes:removedNodes,links:removedLinks, groups: removedGroups};
}
function addSubflow(sf, createNewIds) {
@ -497,6 +518,9 @@ RED.nodes = (function() {
if (n.d === true) {
node.d = true;
}
if (n.g) {
node.g = n.g;
}
if (node.type == "unknown") {
for (var p in n._orig) {
if (n._orig.hasOwnProperty(p)) {
@ -544,6 +568,13 @@ RED.nodes = (function() {
}
}
}
if (n.type === "group") {
node.x = n.x;
node.y = n.y;
node.w = n.w;
node.h = n.h;
node.nodes = node.nodes.map(function(n) { return n.id });
}
if (n._def.category != "config") {
node.x = n.x;
node.y = n.y;
@ -669,8 +700,18 @@ RED.nodes = (function() {
/**
* Converts the current node selection to an exportable JSON Object
**/
function createExportableNodeSet(set, exportedSubflows, exportedConfigNodes) {
function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) {
var nns = [];
exportedIds = exportedIds || {};
set = set.filter(function(n) {
if (exportedIds[n.id]) {
return false;
}
exportedIds[n.id] = true;
return true;
})
exportedConfigNodes = exportedConfigNodes || {};
exportedSubflows = exportedSubflows || {};
for (var n=0;n<set.length;n++) {
@ -686,11 +727,11 @@ RED.nodes = (function() {
subflowSet.push(n);
}
});
var exportableSubflow = createExportableNodeSet(subflowSet, exportedSubflows, exportedConfigNodes);
var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes);
nns = exportableSubflow.concat(nns);
}
}
if (node.type != "subflow") {
if (node.type !== "subflow") {
var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) {
if (node._def.defaults[d].type && node[d] in configNodes) {
@ -707,6 +748,9 @@ RED.nodes = (function() {
}
}
nns.push(convertedNode);
if (node.type === "group") {
nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
}
} else {
var convertedSubflow = convertSubflow(node);
nns.push(convertedSubflow);
@ -732,6 +776,11 @@ RED.nodes = (function() {
nns.push(convertSubflow(subflows[i], exportCredentials));
}
}
for (i in groups) {
if (groups.hasOwnProperty(i)) {
nns.push(convertNode(groups[i]));
}
}
for (i in configNodes) {
if (configNodes.hasOwnProperty(i)) {
nns.push(convertNode(configNodes[i], exportCredentials));
@ -858,6 +907,7 @@ RED.nodes = (function() {
if (n.type != "workspace" &&
n.type != "tab" &&
n.type != "subflow" &&
n.type != "group" &&
!registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) {
@ -907,6 +957,7 @@ RED.nodes = (function() {
var node_map = {};
var new_nodes = [];
var new_links = [];
var new_groups = [];
var nid;
var def;
var configNode;
@ -1074,20 +1125,25 @@ RED.nodes = (function() {
y:parseFloat(n.y || 0),
z:n.z,
type:0,
wires:n.wires||[],
inputLabels: n.inputLabels,
outputLabels: n.outputLabels,
icon: n.icon,
info: n.info,
changed:false,
_config:{}
};
}
if (n.type !== "group") {
node.wires = n.wires||[];
node.inputLabels = n.inputLabels;
node.outputLabels = n.outputLabels;
node.icon = n.icon;
}
if (n.hasOwnProperty('l')) {
node.l = n.l;
}
if (n.hasOwnProperty('d')) {
node.d = n.d;
}
if (n.hasOwnProperty('g')) {
node.g = n.g;
}
if (createNewIds) {
if (subflow_blacklist[n.z]) {
continue;
@ -1124,7 +1180,17 @@ RED.nodes = (function() {
}
node.type = n.type;
node._def = def;
if (n.type.substring(0,7) === "subflow") {
if (node.type === "group") {
node._def = RED.group.def;
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
node[d] = n[d];
node._config[d] = JSON.stringify(n[d]);
}
}
node._config.x = node.x;
node._config.y = node.y;
} else if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) {
@ -1214,13 +1280,19 @@ RED.nodes = (function() {
}
}
}
if (node.type !== "group") {
addNode(node);
RED.editor.validateNode(node);
} else {
addGroup(node);
}
node_map[n.id] = node;
// If an 'unknown' config node, it will not have been caught by the
// proper config node handling, so needs adding to new_nodes here
if (node.type === "unknown" || node._def.category !== "config") {
new_nodes.push(node);
} else if (node.type === "group") {
new_groups.push(node);
}
}
}
@ -1255,6 +1327,11 @@ RED.nodes = (function() {
}
delete n.wires;
}
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
delete n.g
}
for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type && node_map[n[d3]]) {
@ -1323,9 +1400,20 @@ RED.nodes = (function() {
delete n.status.wires;
}
}
for (i=0;i<new_groups.length;i++) {
n = new_groups[i];
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
delete n.g;
}
n.nodes = n.nodes.map(function(id) {
return node_map[id];
})
}
RED.workspaces.refresh();
return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace];
return [new_nodes,new_links,new_groups,new_workspaces,new_subflows,missingWorkspace];
}
// TODO: supports filter.z|type
@ -1416,6 +1504,9 @@ RED.nodes = (function() {
nodeTabMap = {};
configNodes = {};
workspacesOrder = [];
groups = {};
groupsByZ = {};
var subflowIds = Object.keys(subflows);
subflowIds.forEach(function(id) {
RED.subflow.removeSubflow(id)
@ -1444,6 +1535,27 @@ RED.nodes = (function() {
// var loadedFlowVersion = null;
}
function addGroup(group) {
groupsByZ[group.z] = groupsByZ[group.z] || [];
groupsByZ[group.z].push(group);
groups[group.id] = group;
}
function removeGroup(group) {
var i = groupsByZ[group.z].indexOf(group);
groupsByZ[group.z].splice(i,1);
if (group.g) {
if (groups[group.g]) {
var index = groups[group.g].nodes.indexOf(group);
groups[group.g].nodes.splice(index,1);
}
}
RED.group.markDirty(group);
delete groups[group.id];
}
return {
init: function() {
RED.events.on("registry:node-type-added",function(type) {
@ -1539,6 +1651,11 @@ RED.nodes = (function() {
subflow: getSubflow,
subflowContains: subflowContains,
addGroup: addGroup,
removeGroup: removeGroup,
group: function(id) { return groups[id] },
groups: function(z) { return groupsByZ[z]||[] },
eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) {
if (cb(nodes[n]) === false) {

View File

@ -431,7 +431,7 @@ var RED = (function() {
'<img width="50px" src="red/images/node-red-icon.svg" />'+
'</div>';
RED.sidebar.info.set(aboutHeader+marked(data));
RED.sidebar.info.set(aboutHeader+RED.utils.renderMarkdown(data));
RED.sidebar.info.show();
});
}
@ -472,6 +472,14 @@ var RED = (function() {
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"},
]});
menuOptions.push({id:"menu-item-group",label:RED._("menu.label.groups"), options: [
{id:"menu-item-group-group",label:RED._("menu.label.groupSelection"),disabled:true,onselect:"core:group-selection"},
{id:"menu-item-group-ungroup",label:RED._("menu.label.ungroupSelection"),disabled:true,onselect:"core:ungroup-selection"},
null,
{id:"menu-item-group-merge",label:RED._("menu.label.groupMergeSelection"),disabled:true,onselect:"core:merge-selection-to-group"},
{id:"menu-item-group-remove",label:RED._("menu.label.groupRemoveSelection"),disabled:true,onselect:"core:remove-selection-from-group"}
]});
menuOptions.push(null);
if (RED.settings.theme('palette.editable') !== false) {
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
@ -524,6 +532,7 @@ var RED = (function() {
}
RED.subflow.init();
RED.group.init();
RED.clipboard.init();
RED.search.init();
RED.actionList.init();

View File

@ -583,6 +583,7 @@ RED.clipboard = (function() {
nodes = [];
selection.forEach(function(n) {
nodes.push(n);
nodes = nodes.concat(RED.nodes.groups(n.id));
nodes = nodes.concat(RED.nodes.filterNodes({z:n.id}));
});
} else {
@ -592,7 +593,8 @@ RED.clipboard = (function() {
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}));
} else if (type === 'red-ui-clipboard-dialog-export-rng-flow') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace});
nodes = RED.nodes.groups(activeWorkspace);
nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace}));
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);

View File

@ -0,0 +1,223 @@
RED.colorPicker = (function() {
function getDarkerColor(c) {
var r,g,b;
if (/^#[a-f0-9]{6}$/i.test(c)) {
r = parseInt(c.substring(1, 3), 16);
g = parseInt(c.substring(3, 5), 16);
b = parseInt(c.substring(5, 7), 16);
} else if (/^#[a-f0-9]{3}$/i.test(c)) {
r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16);
g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16);
b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16);
} else {
return c;
}
var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ;
r = Math.max(0,r-50);
g = Math.max(0,g-50);
b = Math.max(0,b-50);
return '#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0')
}
function create(options) {
var color = options.value;
var id = options.id;
var colorPalette = options.palette || [];
var width = options.cellWidth || 30;
var height = options.cellHeight || 30;
var margin = options.cellMargin || 2;
var perRow = options.cellPerRow || 6;
var container = $("<div>",{style:"display:inline-block"});
var colorHiddenInput = $("<input/>", { id: id, type: "hidden", value: color }).appendTo(container);
var opacityHiddenInput = $("<input/>", { id: id+"-opacity", type: "hidden", value: options.hasOwnProperty('opacity')?options.opacity:"1" }).appendTo(container);
var colorButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(container);
$('<i class="fa fa-caret-down"></i>').appendTo(colorButton);
var colorDispContainer = $('<div>',{class:"red-ui-search-result-node"}).appendTo(colorButton);
$('<div>',{class:"red-ui-color-picker-cell-none"}).appendTo(colorDispContainer);
var colorDisp = $('<div>',{class:"red-ui-color-picker-swatch"}).appendTo(colorDispContainer);
var refreshDisplay = function(color) {
if (color === "none") {
colorDisp.addClass('red-ui-color-picker-cell-none').css({
"background-color": "",
opacity: 1
});
colorDispContainer.css({
"border-color":""
})
} else {
var opacity = parseFloat(opacityHiddenInput.val())
colorDisp.removeClass('red-ui-color-picker-cell-none').css({
"background-color": color,
"opacity": opacity
});
var border = getDarkerColor(color);
if (border[0] === '#') {
border += Math.round(255*Math.floor(opacity*100)/100).toString(16);
} else {
border = "";
}
colorDispContainer.css({
"border-color": border
})
}
if (options.hasOwnProperty('opacity')) {
$(".red-ui-color-picker-opacity-slider-overlay").css({
"background-image": "linear-gradient(90deg, transparent 0%, "+color+" 100%)"
})
}
}
colorButton.on("click", function (e) {
var numColors = colorPalette.length;
var picker = $("<div/>", {
class: "red-ui-color-picker"
}).css({
width: ((width+margin+margin)*perRow)+"px",
height: Math.ceil(numColors/perRow)*(height+margin+margin)+"+px"
});
var count = 0;
var row = null;
row = $("<div/>").appendTo(picker);
var colorInput = $('<input>',{
type:"text",
value:colorHiddenInput.val()
}).appendTo(row);
colorInput.on("change", function (e) {
var color = colorInput.val();
colorHiddenInput.val(color).trigger('change');
refreshDisplay(color);
});
// if (options.hasOwnProperty('opacity')) {
// var sliderContainer = $("<div>",{class:"red-ui-color-picker-opacity-slider"
// }
if (options.none) {
row = $("<div/>").appendTo(picker);
var button = $("<button/>", {
class:"red-ui-color-picker-cell red-ui-color-picker-cell-none"
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px"
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
colorInput.val("none");
colorInput.trigger("change");
});
}
colorPalette.forEach(function (col) {
if ((count % perRow) == 0) {
row = $("<div/>").appendTo(picker);
}
var button = $("<button/>", {
class:"red-ui-color-picker-cell"
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px",
backgroundColor: col,
"border-color": getDarkerColor(col)
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
// colorPanel.hide();
colorInput.val(col);
colorInput.trigger("change");
});
count++;
});
if (options.none || options.hasOwnProperty('opacity')) {
row = $("<div/>").appendTo(picker);
// if (options.none) {
// var button = $("<button/>", {
// class:"red-ui-color-picker-cell red-ui-color-picker-cell-none"
// }).css({
// width: width+"px",
// height: height+"px",
// margin: margin+"px"
// }).appendTo(row);
// button.on("click", function (e) {
// e.preventDefault();
// colorPanel.hide();
// selector.val("none");
// selector.trigger("change");
// });
// }
if (options.hasOwnProperty('opacity')) {
var sliderContainer = $("<div>",{class:"red-ui-color-picker-opacity-slider"}).appendTo(row);
sliderContainer.on("mousedown", function(evt) {
if (evt.target === sliderHandle[0]) {
return;
}
var v = evt.offsetX/sliderContainer.width();
sliderHandle.css({
left: ( v*(sliderContainer.width() - sliderHandle.outerWidth()))+"px"
});
v = Math.floor(100*v)
opacityHiddenInput.val(v/100)
opacityLabel.text(v+"%");
refreshDisplay(colorHiddenInput.val());
})
$("<div>",{class:"red-ui-color-picker-opacity-slider-overlay"}).appendTo(sliderContainer);
var sliderHandle = $("<div>",{class:"red-ui-color-picker-opacity-slider-handle red-ui-button red-ui-button-small"}).appendTo(sliderContainer).draggable({
containment: "parent",
axis: "x",
drag: function( event, ui ) {
var v = Math.max(0,ui.position.left/($(this).parent().width()-$(this).outerWidth()));
// Odd bug that if it is loaded with a non-0 value, the first time
// it is dragged it ranges -1 to 99. But every other time, its 0 to 100.
// The Math.max above makes the -1 disappear. The follow hack ensures
// it always maxes out at a 100, at the cost of not allowing 99% exactly.
v = Math.floor(100*v)
if ( v === 99 ) {
v = 100;
}
// console.log("uip",ui.position.left);
opacityHiddenInput.val(v/100)
opacityLabel.text(v+"%");
refreshDisplay(colorHiddenInput.val());
}
});
var opacityLabel = $('<small></small>').appendTo(row);
setTimeout(function() {
sliderHandle.css({
left: (parseFloat(opacityHiddenInput.val())*(sliderContainer.width() - sliderHandle.outerWidth()))+"px"
})
opacityLabel.text(Math.floor(opacityHiddenInput.val()*100)+"%");
},50);
}
}
var colorPanel = RED.popover.panel(picker);
setTimeout(function() {
refreshDisplay(colorHiddenInput.val())
},50);
colorPanel.show({
target: colorButton
})
});
setTimeout(function() {
refreshDisplay(colorHiddenInput.val())
},50);
return container;
}
return {
create: create
}
})();

View File

@ -38,7 +38,10 @@
this.element.addClass("red-ui-searchBox-input");
this.uiContainer = this.element.wrap("<div>").parent();
this.uiContainer.addClass("red-ui-searchBox-container");
if (this.element.parents("form").length === 0) {
var form = this.element.wrap("<form>").parent();
form.addClass("red-ui-searchBox-form");
}
$('<i class="fa fa-search"></i>').prependTo(this.uiContainer);
this.clearButton = $('<a href="#"><i class="fa fa-times"></i></a>').appendTo(this.uiContainer);
this.clearButton.on("click",function(e) {

View File

@ -514,7 +514,9 @@ RED.editor = (function() {
for (var i=editStack.length-1;i<editStack.length;i++) {
var node = editStack[i];
label = node.type;
if (node.type === '_expression') {
if (node.type === 'group') {
label = RED._("group.editGroup",{name:RED.utils.sanitize(node.name||node.id)});
} else if (node.type === '_expression') {
label = RED._("expressionEditor.title");
} else if (node.type === '_js') {
label = RED._("jsEditor.title");
@ -588,8 +590,8 @@ RED.editor = (function() {
// cases, and also prevent browser auto-fill of password
// - the elements cannot be hidden otherwise Chrome will ignore them.
// - the elements need to have id's that imply password/username
$('<div style="position: absolute; top: -2000px;"><input id="red-ui-trap-password" type="password"/></div>').prependTo(dialogForm);
$('<div style="position: absolute; top: -2000px;"><input id="red-ui-trap-username" type="text"/></div>').prependTo(dialogForm);
$('<span style="position: absolute; top: -2000px;"><input id="red-ui-trap-password" type="password"/></span>').prependTo(dialogForm);
$('<span style="position: absolute; top: -2000px;"><input id="red-ui-trap-username" type="text"/></span>').prependTo(dialogForm);
dialogForm.on("submit", function(e) { e.preventDefault();});
dialogForm.find('input').attr("autocomplete","off");
return dialogForm;
@ -823,99 +825,6 @@ RED.editor = (function() {
searchInput.trigger("focus");
}
function createColorPicker(colorRow, color) {
var colorButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(colorRow);
$('<i class="fa fa-caret-down"></i>').appendTo(colorButton);
var colorDisp = $('<div>',{class:"red-ui-search-result-node"}).appendTo(colorButton);
var selector = $("<input/>", {
id: "red-ui-editor-node-color",
type: "text",
value: color
}).css({
marginLeft: "10px",
width: "150px",
}).appendTo(colorRow);
selector.on("change", function (e) {
var color = selector.val();
$(".red-ui-editor-node-appearance-button .red-ui-search-result-node").css({
"background-color": color
});
});
selector.trigger("change");
colorButton.on("click", function (e) {
var recommendedColors = [
"#DDAA99",
"#3FADB5", "#87A980", "#A6BBCF",
"#AAAA66", "#C0C0C0", "#C0DEED",
"#C7E9C0", "#D7D7A0", "#D8BFD8",
"#DAC4B4", "#DEB887", "#DEBD5C",
"#E2D96E", "#E6E0F8", "#E7E7AE",
"#E9967A", "#F3B567", "#FDD0A2",
"#FDF0C2", "#FFAAAA", "#FFCC66",
"#FFF0F0", "#FFFFFF"
].map(function(c) {
var r = parseInt(c.substring(1, 3), 16) / 255;
var g = parseInt(c.substring(3, 5), 16) / 255;
var b = parseInt(c.substring(5, 7), 16) / 255;
return {
hex: c,
r: r,
g: g,
b: b,
l: 0.3 * r + 0.59 * g + 0.11 * b
}
});
// Sort by luminosity.
recommendedColors.sort(function (a, b) {
return a.l - b.l;
});
var numColors = recommendedColors.length;
var width = 30;
var height = 30;
var margin = 2;
var perRow = 6;
var picker = $("<div/>", {
class: "red-ui-color-picker"
}).css({
width: ((width+margin+margin)*perRow)+"px",
height: Math.ceil(numColors/perRow)*(height+margin+margin)+"+px"
});
var count = 0;
var row = null;
recommendedColors.forEach(function (col) {
if ((count % perRow) == 0) {
row = $("<div/>").appendTo(picker);
}
var button = $("<button/>", {
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px",
backgroundColor: col.hex,
"border-style": "solid",
"border-width": "1px",
"border-color": col.luma<0.92?col.hex:'#ccc'
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
colorPanel.hide();
selector.val(col.hex);
selector.trigger("change");
});
count++;
});
var colorPanel = RED.popover.panel(picker);
colorPanel.show({
target: colorButton
})
});
}
function buildAppearanceForm(container,node) {
var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
@ -1001,7 +910,30 @@ RED.editor = (function() {
class: "form-row"
}).appendTo(dialogForm);
$("<label/>").text(RED._("editor.color")).appendTo(colorRow);
createColorPicker(colorRow, color);
var recommendedColors = [
"#DDAA99",
"#3FADB5", "#87A980", "#A6BBCF",
"#AAAA66", "#C0C0C0", "#C0DEED",
"#C7E9C0", "#D7D7A0", "#D8BFD8",
"#DAC4B4", "#DEB887", "#DEBD5C",
"#E2D96E", "#E6E0F8", "#E7E7AE",
"#E9967A", "#F3B567", "#FDD0A2",
"#FDF0C2", "#FFAAAA", "#FFCC66",
"#FFF0F0", "#FFFFFF"
]
RED.colorPicker.create({
id: "red-ui-editor-node-color",
value: color,
palette: recommendedColors,
sortPalette: function (a, b) {return a.l - b.l;}
}).appendTo(colorRow);
$("#red-ui-editor-node-color").on('change', function(ev) {
// Horribly out of scope...
nodeDiv.css('backgroundColor',$(this).val());
})
}
@ -2454,6 +2386,249 @@ RED.editor = (function() {
RED.tray.show(trayOptions);
}
function showEditGroupDialog(group) {
var editing_node = group;
editStack.push(group);
RED.view.state(RED.state.EDITING);
var nodeInfoEditor;
var finishedBuilding = false;
var trayOptions = {
title: getEditStackTitle(),
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
class: "primary",
text: RED._("common.label.done"),
click: function() {
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var d;
var outputMap;
if (editing_node._def.oneditsave) {
var oldValues = {};
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
}
try {
var rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
changed = true;
}
} catch(err) {
console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d];
changed = true;
}
} else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d];
changed = true;
}
}
}
}
}
var newValue;
if (editing_node._def.defaults) {
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var input = $("#node-input-"+d);
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") {
// An empty select-multiple box returns null.
// Need to treat that as an empty array.
newValue = input.val();
if (newValue == null) {
newValue = [];
}
} else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
newValue = input.text();
} else {
newValue = input.val();
}
if (newValue != null) {
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
}
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
// Change to a related config node
var configNode = RED.nodes.node(editing_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_node),1);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_node);
}
}
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
}
}
}
}
}
var oldInfo = editing_node.info;
if (nodeInfoEditor) {
var newInfo = nodeInfoEditor.getValue();
if (!!oldInfo) {
// Has existing info property
if (newInfo.trim() === "") {
// New value is blank - remove the property
changed = true;
changes.info = oldInfo;
delete editing_node.info;
} else if (newInfo !== oldInfo) {
// New value is different
changed = true;
changes.info = oldInfo;
editing_node.info = newInfo;
}
} else {
// No existing info
if (newInfo.trim() !== "") {
// New value is not blank
changed = true;
changes.info = undefined;
editing_node.info = newInfo;
}
}
}
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.tray.close();
RED.view.redraw(true);
}
}
],
resize: function(size) {
editTrayWidthCache['group'] = size.width;
$(".red-ui-tray-content").height(size.height - 50);
// var form = $(".red-ui-tray-content form").height(dimensions.height - 50 - 40);
// if (editing_node && editing_node._def.oneditresize) {
// try {
// editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
// } catch(err) {
// console.log("oneditresize",editing_node.id,editing_node.type,err.toString());
// }
// }
},
open: function(tray, done) {
var trayFooter = tray.find(".red-ui-tray-footer");
var trayFooterLeft = $("<div/>", {
class: "red-ui-tray-footer-left"
}).appendTo(trayFooter)
var trayBody = tray.find('.red-ui-tray-body');
trayBody.parent().css('overflow','hidden');
var editorTabEl = $('<ul></ul>').appendTo(trayBody);
var editorContent = $('<div></div>').appendTo(trayBody);
var editorTabs = RED.tabs.create({
element:editorTabEl,
onchange:function(tab) {
editorContent.children().hide();
if (tab.onchange) {
tab.onchange.call(tab);
}
tab.content.show();
if (finishedBuilding) {
RED.tray.resize();
}
},
collapsible: true,
menu: false
});
var nodePropertiesTab = {
id: "editor-tab-properties",
label: RED._("editor-tab.properties"),
name: RED._("editor-tab.properties"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog"
};
buildEditForm(nodePropertiesTab.content,"dialog-form","group","node-red",group);
editorTabs.addTab(nodePropertiesTab);
var descriptionTab = {
id: "editor-tab-description",
label: RED._("editor-tab.description"),
name: RED._("editor-tab.description"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-file-text-o",
onchange: function() {
nodeInfoEditor.focus();
}
};
editorTabs.addTab(descriptionTab);
nodeInfoEditor = buildDescriptionForm(descriptionTab.content,editing_node);
prepareEditDialog(group,group._def,"node-input", function() {
trayBody.i18n();
finishedBuilding = true;
done();
});
},
close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
nodeInfoEditor.destroy();
nodeInfoEditor = null;
editStack.pop();
editing_node = null;
},
show: function() {
}
}
if (editTrayWidthCache.hasOwnProperty('group')) {
trayOptions.width = editTrayWidthCache['group'];
}
RED.tray.show(trayOptions);
}
function showTypeEditor(type, options) {
if (customEditTypes.hasOwnProperty(type)) {
if (editStack.length > 0) {
@ -2577,6 +2752,7 @@ RED.editor = (function() {
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
editGroup: showEditGroupDialog,
editJavaScript: function(options) { showTypeEditor("_js",options) },
editExpression: function(options) { showTypeEditor("_expression", options) },
editJSON: function(options) { showTypeEditor("_json", options) },

View File

@ -102,7 +102,7 @@
var f = $(this).val();
var args = RED._('jsonata:'+f+".args",{defaultValue:''});
var title = "<h5>"+f+"("+args+")</h5>";
var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
var body = RED.utils.renderMarkdown(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
$("#red-ui-editor-type-expression-help").html(title+"<p>"+body+"</p>");
})

View File

@ -107,7 +107,7 @@
clearTimeout(changeTimer);
changeTimer = setTimeout(function() {
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
$(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
},200);
})
@ -116,7 +116,7 @@
}
if (value) {
$(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
}
panels = RED.panels.create({
id:"red-ui-editor-type-markdown-panels",

View File

@ -0,0 +1,633 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.group = (function() {
var _groupEditTemplate = '<script type="text/x-red" data-template-name="group">'+
'<div class="form-row">'+
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
'</div>'+
// '<div class="node-input-group-style-tools"><span class="button-group"><button class="red-ui-button red-ui-button-small">Use default style</button><button class="red-ui-button red-ui-button-small">Set as default style</button></span></div>'+
'<div class="form-row" id="node-input-row-style-stroke">'+
'<label>Style</label>'+
'<label style="width: 70px;margin-right:10px" for="node-input-style-stroke" data-i18n="editor:common.label.line"></label>'+
'</div>'+
'<div class="form-row" style="padding-left: 100px;" id="node-input-row-style-fill">'+
'<label style="width: 70px;margin-right: 10px " for="node-input-style-fill" data-i18n="editor:common.label.fill"></label>'+
'</div>'+
'<div class="form-row">'+
'<label for="node-input-style-label">Label</label>'+
'<input type="checkbox" id="node-input-style-label"/>'+
'</div>'+
'<div class="form-row" id="node-input-row-style-label-options">'+
'<div style="margin-left: 100px; display: inline-block">'+
'<div class="form-row">'+
'<span style="display: inline-block; min-width: 140px" id="node-input-row-style-label-color">'+
'<label style="width: 70px;margin-right: 10px" for="node-input-style-fill" data-i18n="editor:common.label.color"></label>'+
'</span>'+
'</div>'+
'<div class="form-row">'+
'<span style="display: inline-block; min-width: 140px;" id="node-input-row-style-label-position">'+
'<label style="width: 70px;margin-right: 10px " for="node-input-style-label-position" data-i18n="editor:common.label.position"></label>'+
'</span>'+
'</div>'+
'</div>'+
'</div>'+
'</script>';
var colorPalette = [
"#ff0000",
"#ffC000",
"#ffff00",
"#92d04f",
"#0070c0",
"#001f60",
"#6f2fa0",
"#000000",
"#777777"
]
var colorSteps = 3;
var colorCount = colorPalette.length;
for (var i=0,len=colorPalette.length*colorSteps;i<len;i++) {
var ci = i%colorCount;
var j = Math.floor(i/colorCount)+1;
var c = colorPalette[ci];
var r = parseInt(c.substring(1, 3), 16);
var g = parseInt(c.substring(3, 5), 16);
var b = parseInt(c.substring(5, 7), 16);
var dr = (255-r)/(colorSteps+((ci===colorCount-1) ?0:1));
var dg = (255-g)/(colorSteps+((ci===colorCount-1) ?0:1));
var db = (255-b)/(colorSteps+((ci===colorCount-1) ?0:1));
r = Math.min(255,Math.floor(r+j*dr));
g = Math.min(255,Math.floor(g+j*dg));
b = Math.min(255,Math.floor(b+j*db));
colorPalette.push('#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0'));
}
var defaultGroupStyle = {};
var groupDef = {
defaults:{
name:{value:""},
style:{value:{}},
nodes:{value:[]}
},
category: "config",
oneditprepare: function() {
var style = this.style || {};
RED.colorPicker.create({
id:"node-input-style-stroke",
value: style.stroke || "#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['stroke-opacity'] || 1.0
}).appendTo("#node-input-row-style-stroke");
RED.colorPicker.create({
id:"node-input-style-fill",
value: style.fill || "none",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['fill-opacity'] || 1.0
}).appendTo("#node-input-row-style-fill");
createLayoutPicker({
id:"node-input-style-label-position",
value:style["label-position"] || "nw"
}).appendTo("#node-input-row-style-label-position");
RED.colorPicker.create({
id:"node-input-style-color",
value: style.color || "#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3
}).appendTo("#node-input-row-style-label-color");
$("#node-input-style-label").toggleButton({
enabledLabel: RED._("editor.show"),
disabledLabel: RED._("editor.hide")
})
$("#node-input-style-label").on("change", function(evt) {
$("#node-input-row-style-label-options").toggle($(this).prop("checked"));
})
$("#node-input-style-label").prop("checked", this.style.label)
$("#node-input-style-label").trigger("change");
},
oneditresize: function(size) {
},
oneditsave: function() {
this.style.stroke = $("#node-input-style-stroke").val();
this.style.fill = $("#node-input-style-fill").val();
this.style["stroke-opacity"] = $("#node-input-style-stroke-opacity").val();
this.style["fill-opacity"] = $("#node-input-style-fill-opacity").val();
this.style.label = $("#node-input-style-label").prop("checked");
if (this.style.label) {
this.style["label-position"] = $("#node-input-style-label-position").val();
this.style.color = $("#node-input-style-color").val();
} else {
delete this.style["label-position"];
delete this.style.color;
}
if (this.style["stroke-opacity"] === "1") {
delete this.style["stroke-opacity"]
}
if (this.style["fill-opacity"] === "1") {
delete this.style["fill-opacity"]
}
this.resize = true;
},
set:{
module: "node-red"
}
}
function init() {
RED.events.on("view:selection-changed",function(selection) {
RED.menu.setDisabled("menu-item-group-group",!!!selection.nodes);
RED.menu.setDisabled("menu-item-group-ungroup",!!!selection.nodes || selection.nodes.filter(function(n) { return n.type==='group'}).length === 0);
RED.menu.setDisabled("menu-item-group-merge",!!!selection.nodes);
RED.menu.setDisabled("menu-item-group-remove",!!!selection.nodes || selection.nodes.filter(function(n) { return !!n.g }).length === 0);
});
RED.actions.add("core:group-selection", function() { groupSelection() })
RED.actions.add("core:ungroup-selection", function() { ungroupSelection() })
RED.actions.add("core:merge-selection-to-group", function() { mergeSelection() })
RED.actions.add("core:remove-selection-from-group", function() { removeSelection() })
RED.actions.add("core:copy-group-style", function() { copyGroupStyle() });
RED.actions.add("core:paste-group-style", function() { pasteGroupStyle() });
$(_groupEditTemplate).appendTo("#red-ui-editor-node-configs");
var groupStyleDiv = $("<div>",{
class:"red-ui-flow-group-body",
style: "position: absolute; top: -1000px;"
}).appendTo(document.body);
var groupStyle = getComputedStyle(groupStyleDiv[0]);
defaultGroupStyle = {
stroke: convertColorToHex(groupStyle.stroke),
"stroke-opacity": groupStyle.strokeOpacity,
fill: convertColorToHex(groupStyle.fill),
"fill-opacity": groupStyle.fillOpacity
}
groupStyleDiv.remove();
}
function convertColorToHex(c) {
var m = /^rgb\((\d+), (\d+), (\d+)\)$/.exec(c);
if (m) {
return "#"+(((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16).padStart(6,'0'))
}
return c;
}
var groupStyleClipboard;
function copyGroupStyle() {
var selection = RED.view.selection();
if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].type === 'group') {
groupStyleClipboard = JSON.parse(JSON.stringify(selection.nodes[0].style));
RED.notify(RED._("clipboard.groupStyleCopied"),{id:"clipboard"})
}
}
function pasteGroupStyle() {
if (groupStyleClipboard) {
var selection = RED.view.selection();
if (selection.nodes) {
var historyEvent = {
t:'multi',
events:[],
dirty: RED.nodes.dirty()
}
selection.nodes.forEach(function(n) {
if (n.type === 'group') {
historyEvent.events.push({
t: "edit",
node: n,
changes: {
style: JSON.parse(JSON.stringify(n.style))
},
dirty: RED.nodes.dirty()
});
n.style = JSON.parse(JSON.stringify(groupStyleClipboard));
n.dirty = true;
}
})
if (historyEvent.events.length > 0) {
RED.history.push(historyEvent);
RED.nodes.dirty(true);
RED.view.redraw();
}
}
}
}
function groupSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var group = createGroup(selection.nodes);
if (group) {
var historyEvent = {
t:"createGroup",
groups: [ group ],
dirty: RED.nodes.dirty()
}
RED.history.push(historyEvent);
RED.view.select({nodes:[group]});
RED.nodes.dirty(true);
}
}
}
function ungroupSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var newSelection = [];
groups = selection.nodes.filter(function(n) { return n.type === "group" });
var historyEvent = {
t:"ungroup",
groups: [ ],
dirty: RED.nodes.dirty()
}
RED.history.push(historyEvent);
groups.forEach(function(g) {
newSelection = newSelection.concat(ungroup(g))
historyEvent.groups.push(g);
})
RED.history.push(historyEvent);
RED.view.select({nodes:newSelection})
RED.nodes.dirty(true);
}
}
function ungroup(g) {
var nodes = [];
var parentGroup = RED.nodes.group(g.g);
g.nodes.forEach(function(n) {
nodes.push(n);
if (parentGroup) {
// Move nodes to parent group
n.g = parentGroup.id;
parentGroup.nodes.push(n);
parentGroup.dirty = true;
n.dirty = true;
} else {
delete n.g;
}
})
RED.nodes.removeGroup(g);
return nodes;
}
function mergeSelection() {
// TODO: this currently creates an entirely new group. Need to merge properties
// of any existing group
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];
var historyEvent = {
t: "multi",
events: []
}
var ungroupHistoryEvent = {
t: "ungroup",
groups: []
}
var n;
var parentGroup;
// First pass, check they are all in the same parent
// TODO: DRY mergeSelection,removeSelection,...
for (var i=0; i<selection.nodes.length; i++) {
n = selection.nodes[i];
if (i === 0) {
parentGroup = n.g;
} else if (n.g !== parentGroup) {
RED.notify(RED._("group.errors.cannotCreateDiffGroups"),"error");
return;
}
}
// Second pass, ungroup any groups in the selection and add their contents
// to the selection
for (var i=0; i<selection.nodes.length; i++) {
n = selection.nodes[i];
if (n.type === "group") {
ungroupHistoryEvent.groups.push(n);
nodes = nodes.concat(ungroup(n));
} else {
nodes.push(n);
}
n.dirty = true;
}
if (ungroupHistoryEvent.groups.length > 0) {
historyEvent.events.push(ungroupHistoryEvent);
}
// Finally, create the new group
var group = createGroup(nodes);
if (group) {
RED.view.select({nodes:[group]})
}
historyEvent.events.push({
t:"createGroup",
groups: [ group ],
dirty: RED.nodes.dirty()
});
RED.history.push(historyEvent);
RED.nodes.dirty(true);
}
}
function removeSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];
var n;
var parentGroup = RED.nodes.group(selection.nodes[0].g);
if (parentGroup) {
try {
removeFromGroup(parentGroup,selection.nodes,true);
var historyEvent = {
t: "removeFromGroup",
dirty: RED.nodes.dirty(),
group: parentGroup,
nodes: selection.nodes
}
RED.history.push(historyEvent);
RED.nodes.dirty(true);
} catch(err) {
RED.notify(err,"error");
return;
}
}
RED.view.select({nodes:selection.nodes})
}
}
function createGroup(nodes) {
if (nodes.length === 0) {
return;
}
if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) {
RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
return;
}
// nodes is an array
// each node must be on the same tab (z)
var group = {
id: RED.nodes.id(),
type: 'group',
nodes: [],
style: JSON.parse(JSON.stringify(defaultGroupStyle)),
x: Number.POSITIVE_INFINITY,
y: Number.POSITIVE_INFINITY,
w: 0,
h: 0,
_def: RED.group.def
}
try {
addToGroup(group,nodes);
} catch(err) {
RED.notify(err,"error");
return;
}
group.z = nodes[0].z;
RED.nodes.addGroup(group);
return group;
}
function addToGroup(group,nodes) {
if (!Array.isArray(nodes)) {
nodes = [nodes];
}
var i,n,z;
var g;
// First pass - validate we can safely add these nodes to the group
for (i=0;i<nodes.length;i++) {
n = nodes[i]
if (!n.z) {
throw new Error("Cannot add node without a z property to a group")
}
if (!z) {
z = n.z;
} else if (z !== n.z) {
throw new Error("Cannot add nooes with different z properties")
}
if (n.g) {
// This is already in a group.
// - check they are all in the same group
if (!g) {
if (i!==0) {
// TODO: this might be ok when merging groups
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
}
g = n.g
}
}
if (g !== n.g) {
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
}
}
// The nodes are already in a group. The assumption is they should be
// wrapped in the newly provided group, and that group added to in their
// place to the existing containing group.
if (g) {
g = RED.nodes.group(g);
g.nodes.push(group);
g.dirty = true;
group.g = g.id;
}
// Second pass - add them to the group
for (i=0;i<nodes.length;i++) {
n = nodes[i];
if (n.type !== "subflow") {
if (g && n.g === g.id) {
var ni = g.nodes.indexOf(n);
if (ni > -1) {
g.nodes.splice(ni,1)
}
}
n.g = group.id;
n.dirty = true;
group.nodes.push(n);
group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0));
group.y = Math.min(group.y,n.y-n.h/2-25);
group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x);
group.h = Math.max(group.h,n.y+n.h/2+25-group.y);
}
}
markDirty(group);
}
function removeFromGroup(group, nodes, reparent) {
if (!Array.isArray(nodes)) {
nodes = [nodes];
}
var n;
// First pass, check they are all in the same parent
// TODO: DRY mergeSelection,removeSelection,...
for (var i=0; i<nodes.length; i++) {
if (nodes[i].g !== group.id) {
return;
}
}
var parentGroup = RED.nodes.group(group.g);
for (var i=0; i<nodes.length; i++) {
n = nodes[i];
n.dirty = true;
var index = group.nodes.indexOf(n);
group.nodes.splice(index,1);
if (reparent && group.g) {
n.g = group.g
parentGroup.nodes.push(n);
} else {
delete n.g;
}
}
markDirty(group);
}
function getNodes(group,recursive) {
var nodes = [];
group.nodes.forEach(function(n) {
nodes.push(n);
if (recursive && n.type === 'group') {
nodes = nodes.concat(getNodes(n,recursive))
}
})
return nodes;
}
function groupContains(group,item) {
if (item.g === group.id) {
return true;
}
for (var i=0;i<group.nodes.length;i++) {
if (group.nodes[i].type === "group") {
if (groupContains(group.nodes[i],item)) {
return true;
}
}
}
return false;
}
function getRootGroup(group) {
if (!group.g) {
return group;
}
return getRootGroup(RED.nodes.group(group.g))
}
function createLayoutPicker(options) {
var container = $("<div>",{style:"display:inline-block"});
var layoutHiddenInput = $("<input/>", { id: options.id, type: "hidden", value: options.value }).appendTo(container);
var layoutButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(container);
$('<i class="fa fa-caret-down"></i>').appendTo(layoutButton);
var layoutDispContainer = $('<div>',{class:"red-ui-search-result-node"}).appendTo(layoutButton);
var layoutDisp = $('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"}).appendTo(layoutDispContainer);
var refreshDisplay = function() {
var val = layoutHiddenInput.val();
layoutDisp.removeClass().addClass("red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val)
}
layoutButton.on("click", function(e) {
var picker = $("<div/>", {
class: "red-ui-group-layout-picker"
}).css({
width: "126px"
});
var row = null;
row = $("<div/>").appendTo(picker);
for (var y=0;y<2;y++) { //red-ui-group-layout-text-pos
var yComponent= "ns"[y];
row = $("<div/>").appendTo(picker);
for (var x=0;x<3;x++) {
var xComponent = ["w","","e"][x];
var val = yComponent+xComponent;
var button = $("<button/>", { class:"red-ui-search-result-node","data-pos":val }).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
layoutHiddenInput.val($(this).data("pos"));
layoutPanel.hide()
refreshDisplay();
});
$('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val}).appendTo(button);
}
}
refreshDisplay();
var layoutPanel = RED.popover.panel(picker);
layoutPanel.show({
target: layoutButton
})
})
refreshDisplay();
return container;
}
function markDirty(group) {
group.dirty = true;
while(group) {
group.dirty = true;
group = RED.nodes.group(group.g);
}
}
return {
def: groupDef,
init: init,
createGroup: createGroup,
ungroup: ungroup,
addToGroup: addToGroup,
removeFromGroup: removeFromGroup,
getNodes: getNodes,
contains: groupContains,
markDirty: markDirty
}
})();

View File

@ -524,12 +524,12 @@ RED.keyboard = (function() {
var pane = $('<div id="red-ui-settings-tab-keyboard"></div>');
$('<div class="keyboard-shortcut-entry keyboard-shortcut-list-header">'+
'<div class="keyboard-shortcut-entry-key keyboard-shortcut-entry-text"><input id="red-ui-settings-tab-keyboard-filter" type="text" data-i18n="[placeholder]keyboard.filterActions"></div>'+
'<div class="keyboard-shortcut-entry-key keyboard-shortcut-entry-text"><input autocomplete="off" name="keyboard-filter" id="red-ui-settings-tab-keyboard-filter" type="text" data-i18n="[placeholder]keyboard.filterActions"></div>'+
'<div class="keyboard-shortcut-entry-key" data-i18n="keyboard.shortcut"></div>'+
'<div class="keyboard-shortcut-entry-scope" data-i18n="keyboard.scope"></div>'+
'</div>').appendTo(pane);
pane.find("input").searchBox({
pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({
delay: 100,
change: function() {
var filterValue = $(this).val().trim();

View File

@ -384,6 +384,7 @@ RED.palette.editor = (function() {
handleCatalogResponse(null,catalog,index,v);
refreshNodeModuleList();
}).fail(function(jqxhr, textStatus, error) {
console.warn("Error loading catalog",catalog,":",error);
handleCatalogResponse(jqxhr,catalog,index);
}).always(function() {
handled++;

View File

@ -269,7 +269,7 @@ RED.palette = (function() {
RED.view.focus();
var helpText;
if (nt.indexOf("subflow:") === 0) {
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
} else {
helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
}
@ -284,6 +284,9 @@ RED.palette = (function() {
var mouseX;
var mouseY;
var spliceTimer;
var groupTimer;
var activeGroup;
var hoverGroup;
var paletteWidth;
var paletteTop;
$(d).draggable({
@ -295,16 +298,51 @@ RED.palette = (function() {
start: function() {
paletteWidth = $("#red-ui-palette").width();
paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
hoverGroup = null;
activeGroup = RED.view.getActiveGroup();
if (activeGroup) {
document.getElementById(activeGroup.id).classList.add("red-ui-flow-group-active-hovered");
}
RED.view.focus();
},
stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}},
stop: function() {
d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
if (hoverGroup) {
document.getElementById(hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
}
if (activeGroup) {
document.getElementById(activeGroup.id).classList.remove("red-ui-flow-group-active-hovered");
}
if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
},
drag: function(e,ui) {
var paletteNode = getPaletteNode(nt);
ui.originalPosition.left = paletteNode.offset().left;
if (def.inputs > 0 && def.outputs > 0) {
mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop();
if (!groupTimer) {
groupTimer = setTimeout(function() {
var group = RED.view.getGroupAtPoint(mouseX,mouseY);
if (group !== hoverGroup) {
if (hoverGroup) {
document.getElementById(hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
}
if (group) {
document.getElementById(group.id).classList.add("red-ui-flow-group-hovered");
}
hoverGroup = group;
if (hoverGroup) {
$(ui.helper).data('group',hoverGroup);
} else {
$(ui.helper).removeData('group');
}
}
groupTimer = null;
},200)
}
if (def.inputs > 0 && def.outputs > 0) {
if (!spliceTimer) {
spliceTimer = setTimeout(function() {
var nodes = [];
@ -370,7 +408,7 @@ RED.palette = (function() {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
});
nodeInfo = marked(def.info||"");
nodeInfo = RED.utils.renderMarkdown(def.info||"");
}
setLabel(nt,d,label,nodeInfo);
@ -440,7 +478,7 @@ RED.palette = (function() {
} else if (portOutput.length !== 0 && sf.out.length === 0) {
portOutput.remove();
}
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||""));
setIcon(paletteNode,sf);
var currentCategory = paletteNode.data('category');

View File

@ -158,7 +158,7 @@ RED.projects.settings = (function() {
container.empty();
var desc;
if (activeProject.description) {
desc = marked(activeProject.description);
desc = RED.utils.renderMarkdown(activeProject.description);
} else {
desc = '<span class="red-ui-help-info-none">' + RED._("sidebar.project.noDescriptionAvailable") + '</span>';
}

View File

@ -30,13 +30,13 @@ RED.projects.userSettings = (function() {
$('<div class="red-ui-settings-section-description"></div>').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip"));
var row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text">').appendTo(row);
$('<label for="user-settings-gitconfig-username"></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text" id="user-settings-gitconfig-username">').appendTo(row);
gitUsernameInput.val(currentGitSettings.user.name||"");
row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text">').appendTo(row);
$('<label for="user-settings-gitconfig-email"></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text" id="user-settings-gitconfig-email">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||"");
}

View File

@ -1939,13 +1939,15 @@ RED.projects = (function() {
}
}).fail(function(xhr,textStatus,err) {
var responses;
if (options.responses && options.responses[xhr.status]) {
responses = options.responses[xhr.status];
if (typeof responses === 'function') {
resultCallback = responses;
resultCallbackArgs = {error:responses.statusText};
return;
} else if (options.handleAuthFail !== false && xhr.responseJSON.code === 'git_auth_failed') {
} else if (options.handleAuthFail !== false && (xhr.responseJSON.code === 'git_auth_failed' || xhr.responseJSON.code === 'git_host_key_verification_failed')) {
if (xhr.responseJSON.code === 'git_auth_failed') {
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
var message = $('<div>'+
@ -2033,6 +2035,25 @@ RED.projects = (function() {
]
});
return;
} else if (xhr.responseJSON.code === 'git_host_key_verification_failed') {
var message = $('<div>'+
'<div class="form-row">'+RED._("projects.send-req.host-key-verify-failed")+'</div>'+
'</div>');
var notification = RED.notify(message,{
type:"error",
fixed: true,
modal: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
}
]
});
return;
}
} else if (responses[xhr.responseJSON.code]) {
resultCallback = responses[xhr.responseJSON.code];
resultCallbackArgs = xhr.responseJSON;

View File

@ -25,5 +25,7 @@ RED.state = {
IMPORT_DRAGGING: 8,
QUICK_JOINING: 9,
PANNING: 10,
SELECTING_NODE: 11
SELECTING_NODE: 11,
GROUP_DRAGGING: 12,
GROUP_RESIZE: 13
}

View File

@ -567,6 +567,34 @@ RED.subflow = (function() {
return;
}
var i,n;
var nodeList = new Set();
var tmplist = selection.nodes.slice();
var includedGroups = new Set();
while(tmplist.length > 0) {
n = tmplist.shift();
if (n.type === "group") {
includedGroups.add(n.id);
tmplist = tmplist.concat(n.nodes);
}
nodeList.add(n);
}
nodeList = Array.from(nodeList);
var containingGroup = nodeList[0].g;
var nodesMovedFromGroup = [];
for (i=0; i<nodeList.length;i++) {
if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) {
if (containingGroup !== nodeList[i].g) {
RED.notify("Cannot create subflow across multiple groups","error");
return;
}
}
}
if (containingGroup) {
containingGroup = RED.nodes.group(containingGroup);
}
var nodes = {};
var new_links = [];
var removedLinks = [];
@ -575,13 +603,13 @@ RED.subflow = (function() {
var candidateOutputs = [];
var candidateInputNodes = {};
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
selection.nodes[0].x,
selection.nodes[0].y];
var boundingBox = [nodeList[0].x,
nodeList[0].y,
nodeList[0].x,
nodeList[0].y];
for (i=0;i<selection.nodes.length;i++) {
n = selection.nodes[i];
for (i=0;i<nodeList.length;i++) {
n = nodeList[i];
nodes[n.id] = {n:n,outputs:{}};
boundingBox = [
Math.min(boundingBox[0],n.x),
@ -690,6 +718,20 @@ RED.subflow = (function() {
RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance);
if (containingGroup) {
RED.group.addToGroup(containingGroup, subflowInstance);
nodeList.forEach(function(nl) {
if (nl.g === containingGroup.id) {
delete nl.g;
var index = containingGroup.nodes.indexOf(nl);
containingGroup.nodes.splice(index,1);
nodesMovedFromGroup.push(nl);
}
})
containingGroup.dirty = true;
}
candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link);
@ -723,8 +765,8 @@ RED.subflow = (function() {
RED.nodes.removeLink(removedLinks[i]);
}
for (i=0;i<selection.nodes.length;i++) {
n = selection.nodes[i];
for (i=0;i<nodeList.length;i++) {
n = nodeList[i];
if (/^link /.test(n.type)) {
n.links = n.links.filter(function(id) {
var isLocalLink = nodes.hasOwnProperty(id);
@ -745,7 +787,8 @@ RED.subflow = (function() {
RED.nodes.moveNodeToTab(n, subflow.id);
}
RED.history.push({
var historyEvent = {
t:'createSubflow',
nodes:[subflowInstance.id],
links:new_links,
@ -759,11 +802,29 @@ RED.subflow = (function() {
removedLinks: removedLinks,
dirty:RED.nodes.dirty()
});
RED.view.select(null);
}
if (containingGroup) {
historyEvent = {
t:'multi',
events: [ historyEvent ]
}
historyEvent.events.push({
t:'addToGroup',
group: containingGroup,
nodes: [subflowInstance]
})
historyEvent.events.push({
t:'removeFromGroup',
group: containingGroup,
nodes: nodesMovedFromGroup,
reparent: false
})
}
RED.history.push(historyEvent);
RED.editor.validateNode(subflow);
RED.nodes.dirty(true);
RED.view.redraw(true);
RED.view.updateActive();
RED.view.select(null);
}

View File

@ -231,7 +231,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: data.format,
sourceId: id+"."+k,
tools: tools
tools: tools,
path: ""
}).appendTo(propRow.children()[1]);
}
})
@ -275,7 +276,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: data.format,
sourceId: id+"."+k,
tools: tools
tools: tools,
path: ""
}).appendTo(propRow.children()[1]);
}
});
@ -295,7 +297,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: v.format,
sourceId: id+"."+k,
tools: tools
tools: tools,
path: ""
}).appendTo(propRow.children()[1]);
if (contextStores.length > 1) {
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))

View File

@ -15,17 +15,6 @@
**/
RED.sidebar.info = (function() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var content;
var sections;
var propertiesSection;
@ -163,7 +152,8 @@ RED.sidebar.info = (function() {
var types = {
nodes:0,
flows:0,
subflows:0
subflows:0,
groups: 0
}
node.forEach(function(n) {
if (n.type === 'tab') {
@ -171,6 +161,8 @@ RED.sidebar.info = (function() {
types.nodes += RED.nodes.filterNodes({z:n.id}).length;
} else if (n.type === 'subflow') {
types.subflows++;
} else if (n.type === 'group') {
types.groups++;
} else {
types.nodes++;
}
@ -190,6 +182,9 @@ RED.sidebar.info = (function() {
if (types.nodes > 0) {
$('<div>').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts);
}
if (types.groups > 0) {
$('<div>').text(RED._("clipboard.group",{count:types.groups})).appendTo(counts);
}
} else {
// A single 'thing' selected.
@ -220,6 +215,36 @@ RED.sidebar.info = (function() {
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled"))
}
} else if (node.type === "group") {
// An actual node is selected in the editor - build up its properties table
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.group")+"</td><td></td></tr>").appendTo(tableBody);
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
if (node.name) {
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("common.label.name")+'</td><td></td></tr>').appendTo(tableBody);
$('<span class="red-ui-text-bidi-aware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'"></span>').text(node.name).appendTo(propRow.children()[1]);
}
propRow = $('<tr class="red-ui-help-info-row"><td>&nbsp;</td><td></td></tr>').appendTo(tableBody);
var typeCounts = {
nodes:0,
groups: 0
}
var allNodes = RED.group.getNodes(node,true);
allNodes.forEach(function(n) {
if (n.type === "group") {
typeCounts.groups++;
} else {
typeCounts.nodes++
}
});
var counts = $('<div>').appendTo($(propRow.children()[1]));
if (typeCounts.nodes > 0) {
$('<div>').text(RED._("clipboard.node",{count:typeCounts.nodes})).appendTo(counts);
}
if (typeCounts.groups > 0) {
$('<div>').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts);
}
} else {
// An actual node is selected in the editor - build up its properties table
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody);
@ -236,7 +261,7 @@ RED.sidebar.info = (function() {
}
}
var count = 0;
if (!m && node.type != "subflow") {
if (!m && node.type != "subflow" && node.type != "group") {
var defaults;
if (node.type === 'unknown') {
defaults = {};
@ -314,7 +339,7 @@ RED.sidebar.info = (function() {
if (subflowNode && node.type !== "subflow") {
// Selected a subflow instance node.
// - The subflow template info goes into help
helpText = (marked(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'));
helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'));
} else {
helpText = $("script[data-help-name='"+node.type+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
}
@ -326,10 +351,10 @@ RED.sidebar.info = (function() {
if (node._def && node._def.info) {
var info = node._def.info;
var textInfo = (typeof info === "function" ? info.call(node) : info);
infoText = infoText + marked(textInfo);
infoText = infoText + RED.utils.renderMarkdown(textInfo);
}
if (node.info) {
infoText = infoText + marked(node.info || "")
infoText = infoText + RED.utils.renderMarkdown(node.info || "")
}
setInfoText(infoText, infoSection.content);

View File

@ -16,6 +16,28 @@
RED.utils = (function() {
window._marked = window.marked;
window.marked = function(txt) {
console.warn("Use of 'marked()' is deprecated. Use RED.utils.renderMarkdown() instead");
return renderMarkdown(txt);
}
_marked.setOptions({
renderer: new _marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
smartLists: true,
smartypants: false
});
function renderMarkdown(txt) {
var rendered = _marked(txt);
var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true})
return cleaned;
}
function formatString(str) {
return str.replace(/\r?\n/g,"&crarr;").replace(/\t/g,"&rarr;");
}
@ -1053,6 +1075,7 @@ RED.utils = (function() {
decodeObject: decodeObject,
parseContextKey: parseContextKey,
createIconElement: createIconElement,
sanitize: sanitize
sanitize: sanitize,
renderMarkdown: renderMarkdown
}
})();

View File

@ -67,9 +67,16 @@ RED.view.tools = (function() {
function moveSelection(dx,dy) {
if (moving_set === null) {
moving_set = [];
var selection = RED.view.selection();
if (selection.nodes) {
moving_set = selection.nodes.map(function(n) { return {n:n}});
while (selection.nodes.length > 0) {
var n = selection.nodes.shift();
moving_set.push({n:n});
if (n.type === "group") {
selection.nodes = selection.nodes.concat(n.nodes);
}
}
}
}
if (moving_set && moving_set.length > 0) {
@ -93,6 +100,9 @@ RED.view.tools = (function() {
node.n.x += dx;
node.n.y += dy;
node.n.dirty = true;
if (node.n.type === "group") {
RED.group.markDirty(node.n);
}
minX = Math.min(node.n.x-node.n.w/2-5,minX);
minY = Math.min(node.n.y-node.n.h/2-5,minY);
}

File diff suppressed because it is too large Load Diff

View File

@ -284,3 +284,8 @@ $debug-message-border: #eee;
$debug-message-border-hover: #999;
$debug-message-border-warning: #ffdf9d;
$debug-message-border-error: #f99;
$group-default-fill: none;
$group-default-fill-opacity: 1;
$group-default-stroke: #999;
$group-default-stroke-opacity: 1;

View File

@ -411,6 +411,133 @@ button.red-ui-button.red-ui-editor-node-appearance-button {
}
}
.red-ui-group-layout-picker {
padding: 5px;
background: $primary-background;
}
.red-ui-group-layout-picker-cell-text {
position: absolute;
width: 14px;
height: 2px;
border-top: 2px solid $secondary-text-color;
border-bottom: 2px solid $secondary-text-color;
margin: 2px;
&.red-ui-group-layout-text-pos-nw { top: 0; left: 0; }
&.red-ui-group-layout-text-pos-n { top: 0; left: calc(50% - 9px); }
&.red-ui-group-layout-text-pos-ne { top: 0; right: 0; }
&.red-ui-group-layout-text-pos-sw { bottom: 0; left: 0; }
&.red-ui-group-layout-text-pos-s { bottom: 0; left: calc(50% - 9px); }
&.red-ui-group-layout-text-pos-se { bottom: 0; right: 0; }
&.red-ui-group-layout-text-pos- {
width: 100%;
height: 100%;
border-radius: 5px;
margin: 0;
background-color: #FFF;
background-size: 100% 100%;
background-position: 0 0, 50% 50%;
background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent);
border: none;
}
}
.red-ui-group-layout-picker button.red-ui-search-result-node {
float: none;
position: relative;
padding: 0;
margin: 5px;
width: 32px;
height: 27px;
}
button.red-ui-group-layout-picker-none {
width: 100%;
}
.red-ui-color-picker {
input[type="text"] {
border-radius:0;
width: 100%;
margin-bottom: 0;
border: none;
border-bottom: 1px solid $form-input-border-color;
}
small {
color: $secondary-text-color;
margin-left: 5px;
margin-right: 4px;
display: inline-block;
min-width: 35px;
text-align: right;
}
background: $primary-background;
}
.red-ui-editor-node-appearance-button {
.red-ui-search-result-node {
overflow: hidden
}
}
.red-ui-color-picker-cell {
padding: 0;
border-style: solid;
border-width: 1px;
border-color: $secondary-border-color;
}
.red-ui-color-picker-swatch {
position: absolute;
top:-1px;right:-1px;left:-1px;bottom:-1px;
border-radius: 4px;
}
.red-ui-color-picker-cell-none {
height: 100%;
background-color: #FFF;
background-size: 100% 100%;
background-position: 0 0, 50% 50%;
background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent)
}
.red-ui-search-result-node .red-ui-color-picker-cell-none {
border-radius: 4px;
background-size: 50% 50%;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
}
.red-ui-color-picker-opacity-slider {
position:relative;
vertical-align: middle;
display: inline-block;
width: calc(100% - 50px);
height: 14px;
margin: 6px 3px 8px;
box-sizing: border-box;
background-color: white;
background-image:
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%),
linear-gradient(-45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%);
background-size: 6px 6px;
}
.red-ui-color-picker-opacity-slider-overlay {
position: absolute;
top:0;right:0;left:0;bottom:0;
background-image:linear-gradient(90deg, transparent 0%, #f00 100%);
background-size: 100% 100%;
border: 1px solid $primary-border-color;
}
div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
z-Index: 10;
top: -4px;
cursor: pointer;
min-width: 0;
width: 10px;
height: 22px;
padding: 0;
border: 1px solid $primary-border-color;
border-radius: 1px;
background: $secondary-background;
box-sizing: border-box;
}
.red-ui-icon-picker {
select {
box-sizing: border-box;

View File

@ -71,6 +71,48 @@
}
}
.red-ui-flow-group {
&.red-ui-flow-group-hovered {
.red-ui-flow-group-outline-select {
stroke-opacity: 0.8 !important;
stroke-dasharray: 10 4 !important;
}
}
&.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) {
.red-ui-flow-group-outline-select {
stroke: $link-link-color;
}
}
}
.red-ui-flow-group-outline {
fill: none;
stroke: $node-selected-color;
stroke-opacity: 0;
stroke-width: 12;
pointer-events: stroke;
}
.red-ui-flow-group-outline-select {
fill: none;
stroke: $node-selected-color;
pointer-events: stroke;
stroke-opacity: 0;
stroke-width: 3;
}
.red-ui-flow-group-body {
pointer-events: none;
fill: $group-default-fill;
fill-opacity: $group-default-fill-opacity;
stroke-width: 2;
stroke: $group-default-stroke;
stroke-opacity: $group-default-stroke-opacity;
}
.red-ui-flow-group-label {
@include disable-selection;
}
.red-ui-flow-node-unknown {
stroke-dasharray:10,4;
stroke: $node-border-unknown;
@ -248,6 +290,7 @@ g.red-ui-flow-node-selected {
.red-ui-flow-link-outline {
stroke: $view-background;
stroke-opacity: 0.4;
stroke-width: 5;
cursor: crosshair;
fill: none;

View File

@ -32,6 +32,9 @@
right: 5px;
top: 9px;
}
form.red-ui-searchBox-form {
margin: 0;
}
input.red-ui-searchBox-input {
border-radius: 0;
border: none;

View File

@ -11,6 +11,7 @@
var length = str.length;
var start = 0;
var inString = false;
var inRegex = false;
var inBox = false;
var quoteChar;
var list = [];
@ -24,8 +25,13 @@
}
for (var i=0;i<length;i++) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
if (!inString && !inRegex) {
if (c === "/") {
inRegex = true;
frame = {type:"regex",pos:i};
list.push(frame);
stack.push(frame);
} else if (c === "'" || c === '"') {
inString = true;
quoteChar = c;
frame = {type:"string",pos:i};
@ -37,6 +43,9 @@
} else if (c === ",") {
frame = {type:",",pos:i};
list.push(frame);
} else if (c === "&") {
frame = {type:"&",pos:i};
list.push(frame);
} else if (/[\(\[\{]/.test(c)) {
frame = {type:"open-block",char:c,pos:i};
list.push(frame);
@ -45,6 +54,7 @@
var oldFrame = stack.pop();
if (matchingBrackets[oldFrame.char] !== c) {
// console.log("Stack frame mismatch",c,"at",i,"expected",matchingBrackets[oldFrame.char],"from",oldFrame.pos);
// console.log(list);
return str;
}
//console.log("Closing",c,"at",i,"compare",oldFrame.type,oldFrame.pos);
@ -53,19 +63,32 @@
list.push(frame);
}
} else {
if (c === "\\") {
// an escaped char - stay in current mode and skip the next char
i++;
}
if (inString) {
if (c === quoteChar) {
// Next char must be a ]
inString = false;
stack.pop();
var f = stack.pop();
f.end = i;
}
} else if (inRegex) {
if (c === "/") {
inRegex = false;
var f = stack.pop();
f.end = i;
}
}
}
}
// console.log("list",list);
}
// console.log(stack);
var result = str;
var indent = 0;
var offset = 0;
var pre,post,indented;
var pre,post,indented,hasNewline;
var longStack = [];
list.forEach(function(f) {
if (f.type === ";" || f.type === ",") {
@ -73,30 +96,52 @@
pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
hasNewline = /\n$/.test(pre);
// console.log("A§"+pre+"§\n§"+indented+"§",hasNewline);
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
}
} else if (f.type === "&") {
pre = result.substring(0,offset+f.pos+1);
var lastLineBreak = pre.lastIndexOf("\n");
var lineLength = pre.length - lastLineBreak;
if (lineLength > 70) {
post = result.substring(offset+f.pos+1);
if (!/^\n/.test(post)) {
indented = indentLine(post,indent);
hasNewline = /\n$/.test(pre);
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
}
}
} else if (f.type === "open-block") {
if (f.width > 30) {
if (f.width > 40) {
longStack.push(true);
indent += 4;
pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1);
hasNewline = /\n$/.test(pre);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
} else {
longStack.push(false);
}
} else if (f.type === "close-block") {
if (f.width > 30) {
if (f.width > 40) {
indent -= 4;
pre = result.substring(0,offset+f.pos);
post = result.substring(offset+f.pos);
indented = indentLine(post,indent);
hasNewline = /\n *$/.test(pre);
if (hasNewline) {
result = pre + post;
} else {
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
}
}
longStack.pop();
}
})

View File

@ -28,6 +28,11 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
}, "identifier");
this.$rules = {
"start" : [
{
token: "string.regexp",
regex: "\\/",
next: "regex"
},
{
token : "string",
regex : "'(?=.)",
@ -46,7 +51,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "constant.numeric", // float
regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
},
{ token: "keyword",
{
token: "keyword",
regex: /λ/
},
{
@ -86,7 +92,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "string",
regex : '"|$',
next : "start"
}, {
},
{
defaultToken: "string"
}
],
@ -95,9 +102,24 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "string",
regex : "'|$",
next : "start"
}, {
},
{
defaultToken: "string"
}
],
"regex" : [
{
token: "string.regexp",
regex: "\\\\/"
},
{
token: "string.regexp",
regex: "/[sxngimy]*",
next: "start"
},
{
defaultToken: "string.regexp"
}
]
};
};

File diff suppressed because one or more lines are too long

View File

@ -457,7 +457,7 @@ RED.debug = (function() {
var metaRow = $('<div class="red-ui-debug-msg-meta"></div>').appendTo(msg);
$('<span class="red-ui-debug-msg-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
if (sourceNode) {
$('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+sanitize(o.name||sourceNode.name||sourceNode.id))
$('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id))
.appendTo(metaRow)
.on("click", function(evt) {
evt.preventDefault();

View File

@ -109,9 +109,11 @@
$(".node-type-duration").hide();
}
else if ($(this).val() == "loop") {
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
$(".node-type-wait").hide();
$(".node-type-duration").show();
} else {
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
$(".node-type-wait").show();
$(".node-type-duration").show();
}
@ -176,8 +178,6 @@
if ($("#node-then-type").val() == "loop") {
$("#node-input-duration").val($("#node-input-duration").val() * -1);
}
}
});
</script>

View File

@ -76,6 +76,7 @@ module.exports = function(RED) {
var node = this;
node.topics = {};
var npay = {};
var pendingMessages = [];
var activeMessagePromise = null;
var processMessageQueue = function(msg) {
@ -124,6 +125,7 @@ module.exports = function(RED) {
node.status({});
}
else {
if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); }
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
promise = Promise.resolve();
if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
@ -188,7 +190,14 @@ module.exports = function(RED) {
});
}
promise.then(() => {
if (node.op2type === "payl") {
node.send(npay[topic]);
delete npay[topic];
}
else {
msg2.payload = node.topics[topic].m2;
node.send(msg2);
}
delete node.topics[topic];
if (node.op2type === "payl") { node.send(npay); }
else { node.send(msg2); }

View File

@ -145,7 +145,7 @@ module.exports = function(RED) {
if (error.signal) { msg3.payload.signal = error.signal; }
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
node.log('error:' + error);
if (RED.settings.verbose) { node.log('error:' + error); }
}
else if (node.oldrc === "false") {
msg3 = RED.util.cloneMessage(msg);

View File

@ -153,7 +153,12 @@ module.exports = function(RED) {
this.brokerurl="mqtt://";
}
if (this.broker !== "") {
//Check for an IPv6 address
if (/(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)/.test(this.broker)) {
this.brokerurl = this.brokerurl+"["+this.broker+"]:";
} else {
this.brokerurl = this.brokerurl+this.broker+":";
}
// port now defaults to 1883 if unset.
if (!this.port){
this.brokerurl = this.brokerurl+"1883";
@ -464,6 +469,7 @@ module.exports = function(RED) {
this.broker = n.broker;
this.brokerConn = RED.nodes.getNode(this.broker);
var node = this;
var chk = /[\+#]/;
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
@ -482,6 +488,7 @@ module.exports = function(RED) {
}
if ( msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
if (chk.test(msg.topic)) { node.warn(RED._("mqtt.errors.invalid-topic")); }
this.brokerConn.publish(msg, done); // send the message
} else {
node.warn(RED._("mqtt.errors.invalid-topic"));

View File

@ -163,7 +163,7 @@
if (root === "") {
$("#node-config-ws-tip").hide();
} else {
$("#node-config-ws-path").html(root);
$("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root }));
$("#node-config-ws-tip").show();
}
}
@ -235,7 +235,7 @@
</div>
<div class="form-tips">
<span data-i18n="[html]websocket.tip.path1"></span>
<p id="node-config-ws-tip"><span data-i18n="[html]websocket.tip.path2"></span><code><span id="node-config-ws-path"></span></code>.</p>
<p id="node-config-ws-tip"><span id="node-config-ws-path"></span></p>
</div>
</script>

View File

@ -292,7 +292,6 @@ module.exports = function(RED) {
reduceMessageGroup(node,msgs,exp,fixup,count,result,done);
}
});
}
function reduceAndSendGroup(node, group, done) {
var is_right = node.reduce_right;
@ -361,7 +360,6 @@ module.exports = function(RED) {
}
return done();
}
if (msgs.length === group.count) {
delete pending[gid];
pending_count -= msgs.length;
@ -408,7 +406,7 @@ module.exports = function(RED) {
if (this.joinerType === "str") {
this.joiner = this.joiner.replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
} else if (this.joinerType === "bin") {
var joinArray = JSON.parse(n.joiner)
var joinArray = JSON.parse(n.joiner || "[]");
if (Array.isArray(joinArray)) {
this.joiner = Buffer.from(joinArray);
} else {
@ -429,7 +427,7 @@ module.exports = function(RED) {
var completeSend = function(partId) {
var group = inflight[partId];
clearTimeout(group.timeout);
if (group.timeout) { clearTimeout(group.timeout); }
if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; }
if (group.type === 'array' && group.arrayLen > 1) {
var newArray = [];
@ -448,6 +446,9 @@ module.exports = function(RED) {
buffers.push(joinBuffer);
bufferLen += joinBuffer.length;
}
if (!Buffer.isBuffer(group.payload[i])) {
group.payload[i] = Buffer.from(group.payload[i]);
}
buffers.push(group.payload[i]);
bufferLen += group.payload[i].length;
}
@ -629,8 +630,14 @@ module.exports = function(RED) {
var group = inflight[partId];
if (payloadType === 'buffer') {
if (property !== undefined) {
if (Buffer.isBuffer(property) || (typeof property === "string") || Array.isArray(property)) {
inflight[partId].bufferLen += property.length;
}
else {
node.error(RED._("join.errors.invalid-type",{error:(typeof property)}),msg);
return;
}
}
}
if (payloadType === 'object') {
group.payload[propertyKey] = property;

View File

@ -397,7 +397,7 @@
"message" : "gesamte Nachricht",
"tip" : {
"path1" : "Standardmäßig enthält <code> Nutzdaten </code> die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Listener kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt.",
"path2" : "Dieser Pfad ist relativ zu ",
"path2" : "Dieser Pfad ist relativ zu <code>__path__</code>.",
"url1" : "URL sollte ws: &#47; & #47; oder wss: &#47; & #47; Schema verwenden und auf einen vorhandenen Websocket-Listener verweisen.",
"url2" : "Standardmäßig enthält <code> Nutzdaten </code> die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt."
},

View File

@ -37,30 +37,6 @@
<p>Dieser Konfigurations-Node erstellt einen WebSocket Server-Endpunkt unter Verwendung des angegebenen Pfades.</p>
</script>
<!-- WebSocket Client configuration node -->
<script type="text/html" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input id="node-config-input-path" type="text" placeholder="ws://example.com/ws">
</div>
<div class="form-row node-config-row-tls hide">
<label for="node-config-input-tls" data-i18n="httpin.tls-config"></label>
<input type="text" id="node-config-input-tls">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false" data-i18n="websocket.payload"></option>
<option value="true" data-i18n="websocket.message"></option>
</select>
</div>
<div class="form-tips">
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
<span data-i18n="[html]websocket.tip.url2"></span>
</div>
</script>
<script type="text/html" data-help-name="websocket-client">
<p>Dieser Konfigurations-Node verbindet einen WebSocket-Client mit der angegebenen URL.</p>
</script>

View File

@ -455,7 +455,7 @@
"message": "entire message",
"tip": {
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
"path2": "This path will be relative to ",
"path2": "This path will be relative to <code>__path__</code>.",
"url1": "URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.",
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
},

View File

@ -41,5 +41,5 @@
wait a fixed timeout from first reply and then return, sit and wait for data, or send then close the connection
immediately, without waiting for a reply.</p>
<p>The response will be output in <code>msg.payload</code> as a buffer, so you may want to .toString() it.</p>
<p>If you leave tcp host or port blank they must be set by using the <code>msg.host</code> and <code>msg.port</code> properties.</p>
<p>If you leave tcp host or port blank they must be set by using the <code>msg.host</code> and <code>msg.port</code> properties in every message sent to the node.</p>
</script>

View File

@ -91,7 +91,8 @@
</ul>
</dd>
<dt class="optional">complete</dt>
<dd>If set, the node will send its output message in its current state.</dd>
<dd>If set, the node will append the payload, and then send the output message in its current state.
If you don't wish to append the payload, delete it from the msg.</dd>
</dl>
<h3>Details</h3>

View File

@ -49,12 +49,6 @@
<dd>The contents of the file as either a string or binary buffer.</dd>
<dt class="optional">filename <span class="property-type">string</span></dt>
<dd>If not configured in the node, this optional property sets the name of the file to be read.</dd>
<dt class="optional">error <span class="property-type">object</span></dt>
<dd><i>deprecated</i>: If enabled in the node, when the node hits an error
reading the file, it will send a message with no <code>payload</code>
and this <code>error</code> property set to the error details. This
mode of behaviour is deprecated and not enabled by default for new
instances of the node. See below for more information.</dd>
</dl>
<h3>Details</h3>
<p>The filename should be an absolute path, otherwise it will be relative to
@ -65,11 +59,5 @@
<p>When split into multiple messages, each message will have a <code>parts</code>
property set, forming a complete message sequence.</p>
<p>Encoding of input data can be specified from list of encodings if output format is string.</p>
<h4>Legacy error handling</h4>
<p>Before Node-RED 0.17, if this node hit an error whilst reading the file, it would
send a message with no <code>msg.payload</code> and <code>msg.error</code> set to the
details of the error. This is a deprecated mode of behaviour for the node that new
instances will not do. If required, this mode can be re-enabled within the node
configuration.</p>
<p>Errors should be caught and handled using a Catch node.</p>
</script>

View File

@ -455,7 +455,7 @@
"message": "メッセージ全体を送信/受信",
"tip": {
"path1": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
"path2": "This path will be relative to ",
"path2": "このパスは <code>__path__</code> の相対パスになります。",
"url1": "URLには ws:&#47;&#47; または wss:&#47;&#47; スキーマを使用して、存在するwebsocketリスナを設定してください。",
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。"
},
@ -892,7 +892,8 @@
"fixup": "最終調整式"
},
"errors": {
"invalid-expr": "JSONata式が不正: __error__"
"invalid-expr": "JSONata式が不正: __error__",
"invalid-type": "__error__ をバッファに連結できません"
}
},
"sort": {

View File

@ -79,7 +79,7 @@
</ul>
</dd>
<dt class="optional">complete</dt>
<dd>設定されている場合、保持しているメッセージを結合して送信します。</dd>
<dd>設定されている場合、本ノードはペイロードを追加し、保持しているメッセージを送信します。ペイロードを追加したくない場合は、msgから削除してください</dd>
</dl>
<h3>詳細</h3>

View File

@ -44,8 +44,6 @@
<dd>ファイルの内容を文字列もしくはバッファで表現します</dd>
<dt class="optional">filename <span class="property-type">文字列</span></dt>
<dd>読み出し対象のファイル名をノードに設定していない場合、このプロパティでファイルを指定します</dd>
<dt class="optional">error <span class="property-type">オブジェクト</span></dt>
<dd><i>非推奨</i>: 設定で有効にした場合、ファイルの読み込み時にエラーが発生すると<code>payload</code>を持たず<code>error</code>プロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。詳細については、以下を参照してください。</dd>
</dl>
<h3>詳細</h3>
<p>ファイルネームは絶対パスでの指定を推奨します。絶対パスを指定しない場合は、Node-REDプロセスのワーキングディレクトリからの相対パスとして扱います。</p>
@ -53,7 +51,5 @@
<p>テキストファイルの場合、行毎に分割して各々メッセージを送信することができます。また、バイナリファイルの場合、小さな塊のバッファに分割して送信できます。バッファの分割単位はオペレーティングシステム依存ですが、一般に64k(Linux/Mac)もしくは41k(Windows)です。</p>
<p>複数のメッセージに分割する場合、各メッセージには<code>parts</code>プロパティが設定され、メッセージ列を構成します。</p>
<p>出力形式が文字列の場合、入力データのエンコーディングをエンコーディングリストから選択できます。</p>
<h4>旧式のエラー処理</h4>
<p>Node-RED 0.17より前の版では、ファイルの読み込み時にエラーが発生すると<code>payload</code>を持たず<code>error</code>プロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。ノードの設定により、必要に応じてこのモードを有効にできます。</p>
<p>エラーはcatchードで補足して処理することを推奨します。</p>
</script>

View File

@ -446,7 +446,7 @@
"message": "메세지 전체를 송신/수신",
"tip": {
"path1": "표준으로는 <code>payload</code> 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
"path2": "This path will be relative to ",
"path2": "This path will be relative to <code>__path__</code>.",
"url1": "URL에는 ws:&#47;&#47; 또는 wss:&#47;&#47; 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.",
"url2": "표준으로는 <code>payload</code> 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다."
},

View File

@ -455,7 +455,7 @@
"message": "完整信息",
"tip": {
"path1": "默认情况下,<code>payload</code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
"path2": "这条路径将相对于 ",
"path2": "这条路径将相对于 <code>__path__</code>.",
"url1": "URL 应该使用ws:&#47;&#47;或者wss:&#47;&#47;方案并指向现有的websocket侦听器.",
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
},
@ -698,7 +698,7 @@
"output": "输出",
"includerow": "包含列名行",
"newline": "换行符",
"usestrings": "parse numerical values"
"usestrings": "解析数值"
},
"placeholder": {
"columns": "用逗号分割列名"
@ -898,7 +898,7 @@
}
},
"sort" : {
"sort": "sort",
"sort": "排序",
"target" : "排序属性",
"seq" : "信息队列",
"key" : "键值",
@ -907,9 +907,9 @@
"ascending" : "升序",
"descending" : "降序",
"as-number" : "作为数值",
"invalid-exp" : "sort节点中存在无效的JSONata表达式",
"too-many" : "sort节点中有太多待定信息",
"clear" : "清空sort节点中的待定信息"
"invalid-exp" : "排序节点中存在无效的JSONata表达式",
"too-many" : "排序节点中有太多待定信息",
"clear" : "清空排序节点中的待定信息"
},
"batch" : {
"batch": "batch",

View File

@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-help-name="file">
<script type="text/html" data-help-name="file">
<p><code>msg.payload</code>写入文件,添加到末尾或替换现有内容。或者,它也可以删除文件。</p>
<h3>输入</h3>
<dl class="message-properties">
@ -31,7 +31,7 @@
<p>您可以将此节点配置为删除文件。</p>
</script>
<script type="text/x-red" data-help-name="file in">
<script type="text/html" data-help-name="file in">
<p>以字符串或二进制缓冲区的形式读取文件的内容。</p>
<h3>输入</h3>
<dl class="message-properties">
@ -44,8 +44,6 @@
<dd>文件的内容可以是字符串也可以是二进制的buffer。</dd>
<dt class="optional">filename <span class="property-type">字符串</span></dt>
<dd>如果未在节点配置中设置,该属性可以选择要读取的文件名。</dd>
<dt class="optional">error <span class="property-type">object</span></dt>
<dd><i>已不推荐使用</i>: 如果在节点中启用,则当节点在读取文件时遇到错误时,它将发送一条没有<code>有效荷载</code>的消息,且将消息的<code>error</code>属性设置为错误的详细信息。在默认情况下,此行为模式已弃用且未启用。 请参阅下面的详细信息。</dd>
</dl>
<h3>详细</h3>
<p>文件名应该是绝对路径否则将相对于Node-RED进程的工作目录。</p>
@ -53,7 +51,5 @@
<p>可以选择将文本文件拆分为几行每行输出一条消息或者将二进制文件拆分为较小的buffer块-块大小取决于操作系统但通常为64kLinux/Mac或41kWindows</p>
<p>当拆分为多条消息时,每条消息将具有<code>parts</code>属性集,从而形成完整的消息序列。</p>
<p>如果输出格式为字符串,则可以从编码列表中指定输入数据的编码。</p>
<h4>旧版的错误处理</h4>
<p>在Node-RED 0.17之前,如果此节点在读取文件时遇到错误,它将发送一条不包含<code>msg.payload</code>,但包含<code>msg.error</code>的消息。在<code>msg.error</code>中记录详细的错误内容。但这是模式已被弃用,默认未启用。如有需要,您可以在节点配置中重新启用该模式。</p>
<p>应该使用Catch节点来捕获并处理错误。</p>
</script>

View File

@ -0,0 +1,35 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="inject">
<p>手動或定期得將消息注入流程中。消息的有效荷載可以為多種類型包括字符串JavaScript對象或當前時間。</p>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">various</span></dt>
<dd>指定的消息的有效荷載。</dd>
<dt class="optional">topic <span class="property-type">字符串</span></dt>
<dd>可以在節點中配置的可選屬性。</dd>
</dl>
<h3>詳細</h3>
<p>通過使用特定的有效荷載注入節點可以啟動流程。默認有效荷載是當前時間的時間戳以毫秒為單位自1970年1月1日起</p>
<p>該節點還支持注入字符串數字布林值JavaScript對象或流程/全局上下文值。</p>
<p>默認情況下,節點可以通過在編輯器中單擊節點按鈕來手動觸發。同時也可以被設置為定期或按計劃注入。</p>
<p>另一個可選的設置是在每次啟動流程時注入一次。</p>
<p>可以指定的最大<i>間隔</i>約為596小時/24天。 但是如果對於間隔超過一天的那些間隔建議您使用scheduler節點來應對斷電或重啟。</p>
<p><b>注意</b>:選項<i>“時間間隔” </i><i>“特定時間” </i>使用了標準cron系統。這意味著因此“20分鐘”並不表示在此之後20分鐘而是每小時的20分鐘40分鐘。如果您希望設定為從現在開始的每20分鐘那麽請使用<i>“間隔” </i>選項。</p>
<p><b>注意</b>: 如果您想在字符串中包含換行符,必須使用“功能”節點創建有效荷載。</p>
</script>

View File

@ -0,0 +1,25 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="debug">
<p>在“調試”側邊欄選項卡和運行時日志中顯示選定的消息屬性。 默認情況下,它會顯示<code>msg.payload</code>的值但您也可以將其設置成顯示任意屬性完整消息或JSONata表達式的結果。</p>
<h3>詳細</h3>
<p>調試側邊欄會提供已發消息的結構化視圖,方便您查詢消息的結構。</p>
<p>JavaScript對象和數組可以根據需要來折疊或擴展。緩衝區對象可以顯示爲原始數據也可以顯示爲字符串。</p>
<p>對任意條消息調試側邊欄還會顯示接收消息的時間發送消息的節點以及消息類型等信息。單擊源節點ID將在工作區中顯示該節點。</p>
<p>節點上的按鈕可用于啓用或禁用其輸出。建議禁用或刪除所有未使用的調試節點。</p>
<p>還可以通過配置節點將所有消息發送到運行時的日志或將簡短的數據32個字符內在調試節點下的狀態文本上顯示。</p>
</script>

View File

@ -0,0 +1,24 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="complete">
<p>當另一個節點完成對消息的處理時觸發流程。</p>
<h3>詳細</h3>
<p>如果一個節點通知運行時它已完成消息的處理,該節點可用于觸發第二個流程。</p>
<p>這個節點可以與沒有輸出端口的節點一起使用,例如在使用電子郵件發送節點來發送郵件後觸發一個流程。</p>
<p>此節點只能被設置爲處理流程中某個所選節點的事件。與Catch節點不同您不能指定“所有節點”模式並以流程中的所有節點爲目標。</p>
<p>並非所有節點都會觸發此事件。這取決于它們是否支持于Node-RED 1.0中引入的此功能。</p>
</script>

View File

@ -0,0 +1,36 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="catch">
<p>捕獲由同一標簽頁上的節點引發的錯誤。</p>
<h3>輸出</h3>
<dl class="message-properties">
<dt>error.message <span class="property-type">字符串</span></dt>
<dd>錯誤消息。</dd>
<dt>error.source.id <span class="property-type">字符串</span></dt>
<dd>引發錯誤的節點的ID。</dd>
<dt>error.source.type <span class="property-type">字符串</span></dt>
<dd>引發錯誤的節點的類型。</dd>
<dt>error.source.name <span class="property-type">字符串</span></dt>
<dd>引發錯誤的節點的名稱。(如果已設置)</dd>
</dl>
<h3>詳細</h3>
<p>如果節點在處理消息時抛出錯誤,則流程通常會停止。該節點可用于捕獲那些錯誤並通過專用流程進行處理。</p>
<p>默認情況下,該節點將捕獲同一標簽頁上任何節點抛出的錯誤。或者,它可以針對特定節點,或配置爲僅捕獲另一個“目標”捕獲節點尚未捕獲的錯誤。</p>
<p>當錯誤發生時所有匹配的catch節點都會收到錯誤消息。</p>
<p>如果在子流程中發送了錯誤,則該錯誤將由子流程中的任意捕獲節點處理。如果子流程中不存在捕獲節點,則那錯誤將被傳播到子流程實例所在的標簽頁。</p>
<p>如果消息已經具有<code>error</code>屬性,則將該<code>error</code>複制爲<code>_error</code></p>
</script>

View File

@ -0,0 +1,33 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="status">
<p>獲取在同一標簽頁上的其他節點的狀態消息。</p>
<h3>輸出</h3>
<dl class="message-properties">
<dt>status.text <span class="property-type">字符串</span></dt>
<dd>狀態文本。</dd>
<dt>status.source.type <span class="property-type">字符串</span></dt>
<dd>報告狀態的節點的類型。</dd>
<dt>status.source.id <span class="property-type">字符串</span></dt>
<dd>報告狀態的節點的ID。</dd>
<dt>status.source.name <span class="property-type">字符串</span></dt>
<dd>報告狀態的節點的名稱(如果已設置)。</dd>
</dl>
<h3>詳細</h3>
<p>該節點不包含<code>有效荷載</code></p>
<p>默認情況下,節點會獲取同一工作空間標簽頁上報告所有節點的狀態。可以通過配置來設定目標節點。</p>
</script>

View File

@ -0,0 +1,31 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="link in">
<p>在流程之間創建虛擬連線。</p>
<h3>詳細</h3>
<p>該節點可以連接到任何標簽頁上存在的任何<code>link out</code>節點。連接後,它們的行爲就像連接在一起。</p>
<p>僅當選擇鏈接節點時,才會顯示鏈接節點之間的鏈接。如果有指向另一個選項卡的鏈接,則顯示一個虛擬節點。單擊該虛擬節點將帶您到相應的選項卡。</p>
<p><b>注意:</b>無法創建進入或離開子流程的鏈接。</p>
</script>
<script type="text/x-red" data-help-name="link out">
<p>在流程之間創建虛擬連線。</p>
<h3>詳細</h3>
<p>該節點可以連接到任何標簽頁上存在的任何<code>link in</code>節點。連接後,它們的行爲就像連接在一起。</p>
<p>僅當選擇鏈接節點時,才會顯示鏈接節點之間的鏈接。如果有指向另一個選項卡的鏈接,則顯示一個虛擬節點。單擊該虛擬節點將帶您到相應的選項卡。</p>
<p><b>注意:</b>無法創建進入或離開子流程的鏈接。</p>
</script>

View File

@ -0,0 +1,21 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="comment">
<p>可用于向流程添加注釋的節點。</p>
<h3>詳細</h3>
<p>編輯面板接受Markdown語法。輸入的文本將在信息側面板中顯示。</p>
</script>

View File

@ -0,0 +1,24 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="unknown">
<p>您安裝的Node-RED無法識別該節點的類型。</p>
<h3>詳細</h3>
<p><i>如果在此狀態下部署節點,其配置會被保存。但是在安裝缺少的類型之前,流程不會開始。</i></p>
<p>使用<code> Menu-Manage Palette </code>選項來搜索並安裝節點,或者使用<b>npm install &lt;module&gt;</b>來安裝所有缺少的節點並重新啓動Node-Red來導入這些節點。</p>
<p>另一種可能是您已經安裝了此節點類型但是缺少必須的依賴項。您應檢查Node-RED的啓動日志中是否有與缺少節點有關的錯誤消息。</p>
<p>以上方法都不適用時,您可以聯系該流程的作者以獲取缺少的節點類型的副本。</p>
</script>

View File

@ -0,0 +1,51 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="function">
<p>定義對接收到的消息進行處理的JavaScript代碼函數的主體</p>
<p>輸入消息在名爲<code>msg</code>的JavaScript對象中傳遞。</p>
<p>通常,<code>msg</code>對象將消息正文保留在<code>msg.payload</code>屬性中。</p>
<p>該函數一般會返回一個消息對象(或多個消息對象),但也可以爲了停止流程而什麽都不返回。</p>
<h3>詳細</h3>
<p>請參見<a target="_blank" href="http://nodered.org/docs/writing-functions.html">在線文檔</a>來獲得更多有關編寫函數的信息。</p>
<h4>傳送消息</h4>
<p>要將消息傳遞到流程中的下一個節點,請返回消息或調用<code>node.send(messages)</code></p>
<p>它將返回/send:</p>
<ul>
<li>單個消息對象 - 傳遞給連接到第一個輸出的節點</li>
<li>消息對象數組,傳遞給連接到相應輸出的節點</li>
</ul>
<p>如果數組元素是數組,則將多個消息發送到相應的輸出。</p>
<p>無論return方法是單個值還是數組元素如果返回值爲null則不會發送任何消息。</p>
<h4>日志輸出和錯誤處理</h4>
<p>使用以下功能輸出日志信息和輸出錯誤:</p>
<ul>
<li><code>node.log("Log message")</code></li>
<li><code>node.warn("Warning")</code></li>
<li><code>node.error("Error")</code></li>
</ul>
</p>
<p>使用catch節點可以進行錯誤處理。 要由catch節點處理請將<code>msg</code>作爲<code>node.error</code>的第二個參數傳遞:</p>
<pre>node.error("Error",msg);</pre>
<h4>訪問節點信息</h4>
<p>您可以使用以下屬性來在代碼中引用節點ID和名稱</p>
<ul>
<li><code>node.id</code> - 節點的ID</li>
<li><code>node.name</code> - 節點的名稱</li>
</ul>
<h4>使用環境變量</h4>
<p>環境變量可以通過<code>env.get("MY_ENV_VAR")</code>來進行訪問。</p>
</script>

View File

@ -0,0 +1,37 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="switch">
<p>按屬性值來分配消息的傳送路線。</p>
<h3>詳細</h3>
<p>根據接收到的消息評估指定的規則,然後將消息發送到與匹配的規則相對應的輸出端口。</p>
<p>可以將節點設置爲一旦發現一個匹配的規則,則停止後續的匹配。</p>
<p>對于評估規則,可以使用消息屬性,流程上下文/全局上下文屬性環境變量和JSONata表達式的評估結果。</p>
<h4>規則</h4>
<p>有四種規則:</p>
<ol>
<li><b></b>根據配置的屬性評估規則</li>
<li><b>順序</b>可用于消息序列的規則,例如由“拆分”節點生成的規則</li>
<li><b>JSONata表達式</b>評估整個消息,如果結果爲真,則匹配。</li>
<li><b>其他</b>上述規則都不匹配時適用</li>
</ol>
<h4>注釋</h4>
<p><code>is true/false</code><code>is null</code> 規則將對類型進行嚴格的匹配。匹配之前的類型轉化不會發生。</p>
<p><code>is empty</code>規則與零字節的字符串,數組,緩衝區或沒有屬性的對象相匹配。與<code>null</code>或者<code>undefined</code>等不匹配。</p>
<h4>處理消息序列</h4>
<p>默認情況下,節點不會修改<code>msg.parts</code>屬性。</p>
<p>可以啓用<b>重建消息序列</b>選項來爲每條匹配的規則生成新的消息序列。在這種模式下,節點將在發送新序列之前對整個傳入序列進行緩存。運行時的設定<code>nodeMessageBufferMaxLength</code>可以用來限制可緩存的消息數目。</p>
</script>

View File

@ -0,0 +1,33 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="change">
<p>設置,更改,刪除或移動消息,流程上下文或全局上下文的屬性。</p>
<p>如果指定了多個規則,則將按定義的順序來應用它們。</p>
<h3>詳細</h3>
<p>可用的操作有:</p>
<dl class="message-properties">
<dt>設置</dt>
<dd>設置一個屬性。該值可以是多種不同類型,也可以從現有消息或上下文屬性中獲取。</dd>
<dt>置換</dt>
<dd>搜索並替換屬性。 如果啓用了正則表達式則可以爲“replace with”屬性指定捕獲組例如<code>$1</code>。 在替換過程中,僅當規則完全匹配時才能更改屬性類型。</dd>
<dt>刪除</dt>
<dd>刪除一個屬性</dd>
<dt>移動</dt>
<dd>移動或者重命名一個屬性</dd>
</dl>
<p>類型"expression"使用<a href="http://jsonata.org/" target="_new">JSONata</a>語言。</p>
</script>

View File

@ -0,0 +1,40 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="range">
<p>將數值映射爲另一個區間的數值</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">數值</span></dt>
<dd>有效荷載<i>一定</i>得是一個數值. 否則則會映射失敗。</dd>
</dl>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">數值</span></dt>
<dd>被映射到新區間的數值。</dd>
</dl>
<h3>詳細</h3>
<p>該節點將線性縮放所接收到的數值。在默認情況下,結果不限于節點中定義的範圍。</p>
<p><i>縮放並限制到目標範圍</i>表示結果永遠不會超出目標範圍內指定的範圍。</p>
<p><i>在目標範圍內縮放並折疊</i>表示結果將會被限制(折疊)在目標範圍內。</p>
<p>例如輸入0-10映射到0-100。</p>
<table style="outline-width:#888 solid thin">
<tr><th width="80px">模式</th><th width="80px">輸入</th><th width="80px">輸出</th></tr>
<tr><td><center>scale</center></td><td><center>12</center></td><td><center>120</center></td></tr>
<tr><td><center>limit</center></td><td><center>12</center></td><td><center>100</center></td></tr>
<tr><td><center>wrap</center></td><td><center>12</center></td><td><center>20</center></td></tr>
</table>
</script>

View File

@ -0,0 +1,46 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="template">
<p>根據提供的模板設置屬性。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>msg <span class="property-type">object</span></dt>
<dd>一個msg對象其中包含著用于填充模板的信息。</dd>
<dt class="optional">template <span class="property-type">string</span></dt>
<dd><code>msg.payload</code>填充的模板。如果未在編輯面板中配置則可以將設爲msg的屬性。</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>msg <span class="property-type">object</span></dt>
<dd>由來自傳入msg的屬性來填充已配置的模板後輸出的帶有屬性的msg。</dd>
</dl>
<h3>詳細</h3>
<p>默認情況下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式。如有需要也可以切換其他格式。</p>
<p>例如:
<pre>Hello {{payload.name}}. Today is {{date}}</pre>
<p>receives a message containing:
<pre>{
date: "Monday",
payload: {
name: "Fred"
}
}</pre>
<p>輸出的消息將會是:
<pre>Hello Fred. Today is Monday</pre>
<p>也可以使用流程上下文或全局上下文中的屬性:<code>{{flow.name}}</code>或者<code>{{global.name}}</code>,或者爲了持久儲存<code>store</code>,可以使用<code>{{flow[store].name}}</code><code>{{global[store].name}}</code>
<p><b>注意:</b>默認情況下,<i>mustache</i>將在其替換的值中轉義任何非字母數字或HTML實體。爲了防止這種情況請使用<code>{{{triple}}}</code>大括號。
</script>

View File

@ -0,0 +1,32 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="delay">
<p>對通過節點的消息進行延遲發送或限制。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt class="optional">delay <span class="property-type">數值</span></dt>
<dd>設置要應用于消息的延遲(以毫秒爲單位)。僅當節點配置爲允許消息去覆蓋配置的默認延遲間隔時,此選項才適用。</dd>
<dt class="optional">reset</dt>
<dd>如果接收到的消息將此屬性設置爲任何值,則將清空該節點保留的所有的未發送消息。</dd>
<dt class="optional">flush</dt>
<dd>如果接收到的消息的此屬性設置爲任何值,則將立即發送該節點保留的所有未發送消息。</dd>
</dl>
<h3>詳細</h3>
<p>當配置爲延遲發送消息時,延遲間隔可以是一個固定值,一個範圍內的隨機值或爲每個消息動態設置。</p>
<p>當配置爲對消息進行限制時,它們的傳遞將分散在配置的時間段內。狀態顯示隊列中當前的消息數。可以選擇在中間消息到達時丟棄它們。</p>
<p>速率限制可以應用于所有消息,也可以根據<code>msg.topic</code>的值來進行分組。分組時,中間消息將會被自動刪除。在每個時間間隔,節點可以釋放所有主題的最新消息,或釋放下一個主題的最新消息。</p>
</script>

View File

@ -0,0 +1,33 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="trigger">
<p>触发后,将会发送一条消息。如果被拓展或重置,则可以选择发送第二条消息。</p>
<h3>输入</h3>
<dl class="message-properties">
<dt class="optional">reset</dt>
<dd>如果收到带有此属性的消息,则将清除当前正在进行的任何超时或重复,且不会触发任何消息。</dd>
</dl>
<h3>详细</h3>
<p>该节点可用于在流程中创建一个超时。 默认情况下,当它收到一条消息时,它将发送一条带有<code>1</code>的有效荷载的消息。然后它将等待250毫秒再发送第二条消息其有效荷载为<code>0</code>。这可以用于使连接到Raspberry Pi GPIO引脚的LED闪烁等例子上。</p>
<p>可以将发送的每个消息的有效荷载配置为各种值,包括不发送任何内容的选项。例如,将初始消息设置为<i>nothing</i>,然后选择将计时器与每个收到的消息一起扩展的选项,则该节点将充当看门狗计时器;仅在设置的间隔内未收到任何消息时才发送消息。</p>
<p>如果设置为<i>字符串</i>类型,则该节点支持<i>mustache</i>模板语法。</p>
<p>如果节点收到具有<code>reset</code>属性或与节点中配置的匹配的<code>有效荷载</code>的消息,则将清除当前正在进行的任何超时或重复,并且不会触发任何消息。</p>
<p>可以将节点配置为以固定的时间间隔重新发送消息,直到被收到的消息重置为止。</p>
<p>(可选)可以将节点配置为将带有<code>msg.topic</code>的消息视为独立的流程。</p>
</script>

View File

@ -0,0 +1,74 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="exec">
<p>運行系統命令並返回其輸出。</p>
<p>可以將節點配置爲等待命令完成,或者在命令生成時發送其輸出。</p>
<p>運行的命令可以在節點中配置,也可以由收到的消息提供。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt class="optional">payload <span class="property-type">字符串</span></dt>
<dd>如果這樣配置,則將被附加到執行命令中。</dd>
<dt class="optional">kill <span class="property-type">字符串</span></dt>
<dd>指定發送到現有的exec節點進程的kill信號類型。</dd>
<dt class="optional">pid <span class="property-type">數值|字符串</span></dt>
<dd>要殺死的現有exec節點進程的進程ID</dd>
</dl>
<h3>輸出</h3>
<ol class="node-ports">
<li>標准輸出(stdout)
<dl class="message-properties">
<dt>payload <span class="property-type">字符串</span></dt>
<dd>命令的標准輸出。</dd>
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">object</span></dt>
<dd>僅執行模式一個返回代碼對象的副本在端口3上也可用</dd>
</dl>
</li>
<li>標准error輸出(stderr)
<dl class="message-properties">
<dt>payload <span class="property-type">字符串</span></dt>
<dd>命令的標准錯誤輸出。</dd>
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">object</span></dt>
<dd>僅執行模式一個返回代碼對象的副本在端口3上也可用</dd>
</dl>
</li>
<li>返回代碼
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>一個包含返回代碼以及<code>message</code><code>signal</code>屬性的對象。</dd>
</dl>
</li>
</ol>
<h3>詳細</h3>
<p>默認情況下,使用<code>exec</code>系統調用來調用命令,等待命令完成,然後返回輸出。例如,成功的命令的返回碼應爲<code>{code0}</code></p>
<p>(可選)可以選擇使用<code>spawn</code>代替它會在命令運行時從stdout和stderr返回輸出通常一次一行。完成後它將在第三個端口上返回一個對象。例如成功的命令應返回<code>{code0}</code></p>
<p>錯誤可能會在第三個端口<code>msg.payload</code>上返回額外的信息,例如<code>message</code>字符串,<code>signal</code>字符串。</p>
<p>運行的命令是在節點內定義的,帶有附加<code>msg.payload</code>的選項和另外一組參數。</p>
<p>帶空格的命令或參數應該用引號引起來:<code>“這是一個參數”</code></p>
<p>返回的<code>有效荷載</code>通常是<i>字符串</i>類型除非檢測到非UTF8字符在這種情況下它會是<i>buffer</i>類型。</p>
<p>節點處于活動狀態時該節點的狀態圖標和PID將可見。對此更改可以通過<code>Status</code>節點讀取。</p>
<h4>殺死進程</h4>
<p>發送<code>msg.kill</code>將殺死一個活動進程。<code>msg.kill</code>應該是包含要發送的信號類型的字符串,例如<code>SIGINT</code><code>SIGQUIT</code><code>SIGHUP</code>。如果設置爲空字符串,則默認爲<code>SIGTERM</code></p>
<p>如果節點有多個進程在運行,則還必須設置<code>msg.pid</code>並設置要殺死的PID的值。</p>
<p>如果<code>超時</code>字段提供了一個值,則如果在指定的秒數過去後進程尚未完成,則該進程將自動終止。</p>
<p>提示如果運行Python應用程序則可能需要使用<code>-u</code>參數來停止對輸出進行緩存。</p>
</script>

View File

@ -6,7 +6,9 @@
"name": "名稱",
"username": "使用者名稱",
"password": "密碼",
"property": "屬性"
"property": "屬性",
"selectNodes": "選擇節點...",
"expand": "擴展"
},
"status": {
"connected": "已連接",
@ -35,7 +37,22 @@
"stopped": "停止",
"failed": "注入失敗: __error__",
"label": {
"repeat": "重複"
"repeat": "重複",
"flow": "流上下午",
"global": "全局上下文",
"str": "字符串",
"num": "數值",
"bool": "布爾值",
"json": "JSON對象",
"bin": "buffer",
"date": "時間戳",
"env": "環境變量",
"object": "對象",
"string": "字符串",
"boolean": "布爾值",
"number": "數值",
"Array": "數組",
"invalid": "無效的JSON對象"
},
"timestamp": "時間戳記",
"none": "無",
@ -72,13 +89,11 @@
"catch": {
"catch": "監測所有節點",
"catchNodes": "監測__number__個節點",
"catchUncaught": "捕獲:未捕獲",
"label": {
"source": "監測範圍",
"node": "節點",
"type": "類型",
"selectAll": "全選",
"sortByLabel": "按名稱排序",
"sortByType": "按類型排序"
"uncaught": "忽略其他捕獲節點處理的錯誤"
},
"scope": {
"all": "所有節點",
@ -90,10 +105,6 @@
"statusNodes": "報告__number__個節點狀態",
"label": {
"source": "報告狀態範圍",
"node": "節點",
"type": "類型",
"selectAll": "全選",
"sortByLabel": "按名稱排序",
"sortByType": "按類型排序"
},
"scope": {
@ -101,8 +112,13 @@
"selected": "指定節點"
}
},
"complete": {
"completeNodes": "完成: __number__個節點"
},
"debug": {
"output": "輸出",
"none": "None",
"invalid-exp": "無效的JSONata表達式: __error__",
"msgprop": "資訊屬性",
"msgobj": "完整資訊",
"to": "目標",
@ -124,7 +140,11 @@
"filterCurrent": "當前流程",
"debugNodes": "除錯節點",
"clearLog": "清空日誌",
"openWindow": "在新視窗打開"
"filterLog": "過濾日誌",
"openWindow": "在新視窗打開",
"copyPath": "復制路徑",
"copyPayload": "復制值",
"pinPath": "固定展開"
},
"messageMenu": {
"collapseAll": "折疊所有路徑",
@ -146,26 +166,33 @@
"key": "私密金鑰",
"passphrase": "密碼",
"ca": "CA證書",
"verify-server-cert":"驗證伺服器憑證"
"verify-server-cert": "驗證伺服器憑證",
"servername": "服務器名"
},
"placeholder": {
"cert": "憑證路徑 (PEM 格式)",
"key": "私密金鑰路徑 (PEM 格式)",
"ca": "CA憑證路徑 (PEM 格式)",
"passphrase":"私密金鑰密碼 (可選)"
"passphrase": "私密金鑰密碼 (可選)",
"servername": "用於SNI"
},
"error": {
"missing-file": "未提供證書/金鑰檔案"
}
},
"exec": {
"exec": "exec",
"spawn": "spawn",
"label": {
"command": "命令",
"append": "追加",
"timeout": "超時",
"timeoutplace": "可選填",
"return": "輸出",
"seconds": "秒"
"seconds": "秒",
"stdout": "標準輸出",
"stderr": "標準錯誤輸出",
"retcode": "返回碼"
},
"placeholder": {
"extraparams": "額外的輸入參數"
@ -177,6 +204,7 @@
"oldrc": "使用舊式輸出 (相容模式)"
},
"function": {
"function": "函數",
"label": {
"function": "函數",
"outputs": "輸出"
@ -187,6 +215,7 @@
}
},
"template": {
"template": "模板",
"label": {
"template": "模版",
"property": "屬性",
@ -272,6 +301,9 @@
"wait-reset": "等待被重置",
"wait-for": "等待",
"wait-loop": "週期性重發",
"for": "處理",
"bytopics": "每個msg.topic",
"alltopics": "所有消息",
"duration": {
"ms": "毫秒",
"s": "秒",
@ -290,6 +322,7 @@
}
},
"comment": {
"comment": "注釋"
},
"unknown": {
"label": {
@ -303,6 +336,7 @@
"example": "e.g. localhost",
"output": "輸出",
"qos": "QoS",
"retain": "保持",
"clientid": "使用者端ID",
"port": "埠",
"keepalive": "Keepalive計時(秒)",
@ -312,17 +346,22 @@
"verify-server-cert": "驗證伺服器憑證",
"compatmode": "使用舊式MQTT 3.1支援"
},
"sections-label": {
"birth-message": "連接時發送的消息(出生消息)",
"will-message": "意外斷開連接時的發送消息Will消息",
"close-message": "斷開連接前發送的消息(關閉消息)"
},
"tabs-label": {
"connection": "連接",
"security": "安全",
"will": "Will信息",
"birth": "Birth信息"
"messages": "消息"
},
"placeholder": {
"clientid": "留白則自動隨機生成",
"clientid-nonclean": "如非新會話必須設置使用者端ID",
"will-topic": "留白將禁止Will資訊",
"birth-topic": "留白將禁止Birth資訊"
"birth-topic": "留白將禁止Birth資訊",
"close-topic": "留白以禁用關閉消息"
},
"state": {
"connected": "已連接到服務端: __broker__",
@ -333,7 +372,9 @@
"output": {
"buffer": "Buffer",
"string": "字串",
"base64": "Base64編碼字串"
"base64": "Base64編碼字串",
"auto": "自動檢測 (字符串或buffer)",
"json": "解析的JSON對象"
},
"true": "是",
"false": "否",
@ -342,7 +383,9 @@
"not-defined": "主題未設置",
"missing-config": "未設置服務端",
"invalid-topic": "主題無效",
"nonclean-missingclientid": "使用者端ID未設定使用新會話"
"nonclean-missingclientid": "使用者端ID未設定使用新會話",
"invalid-json-string": "無效的JSON字符串",
"invalid-json-parse": "無法解析JSON字符串"
}
},
"httpin": {
@ -354,12 +397,26 @@
"upload": "接受檔案上傳?",
"status": "狀態碼",
"headers": "Header",
"other": "其他"
"other": "其他",
"paytoqs": "將msg.payload附加為查詢字符串參數",
"utf8String": "UTF8格式的字符串",
"binaryBuffer": "二進制buffer",
"jsonObject": "解析的JSON對象",
"authType": "類型",
"bearerToken": "Token"
},
"setby": "- 用 msg.method 設定 -",
"basicauth": "基本認證",
"use-tls": "使用安全連接 (SSL/TLS) ",
"tls-config": "TLS 設置",
"basic": "基本認證",
"digest": "摘要認證",
"bearer": "bearer認證",
"use-proxy": "使用代理服務器",
"persist": "對連接啟用keep-alive",
"proxy-config": "代理服務器設置",
"use-proxyauth": "使用代理身份驗證",
"noproxy-hosts": "代理例外",
"utf8": "UTF-8 字串",
"binary": "二進位資料",
"json": "JSON對象",
@ -376,7 +433,10 @@
"json-error": "JSON 解析錯誤",
"no-url": "未設定 URL",
"deprecated-call": "__method__方法已棄用",
"invalid-transport":"非HTTP傳輸請求"
"invalid-transport": "非HTTP傳輸請求",
"timeout-isnan": "超時值不是有效數字,忽略",
"timeout-isnegative": "超時值為負,忽略",
"invalid-payload": "無效的有效載荷"
},
"status": {
"requesting": "請求中"
@ -395,17 +455,23 @@
"message": "完整資訊",
"tip": {
"path1": "預設情況下,<code>payload</code>將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.",
"path2": "這條路徑將相對於 ",
"path2": "這條路徑將相對於 <code>__path__</code>.",
"url1": "URL 應該使用ws:&#47;&#47;或者wss:&#47;&#47;方案並指向現有的websocket監聽器.",
"url2": "預設情況下,<code>payload</code> 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件."
},
"status": {
"connected": "連接數 __count__",
"connected_plural": "連接數 __count__"
},
"errors": {
"connect-error": "ws連接發生了錯誤: ",
"send-error": "發送時發生了錯誤: ",
"missing-conf": "未設置伺服器"
"missing-conf": "未設置伺服器",
"duplicate-path": "同一路徑上不能有兩個WebSocket偵聽器: __path__"
}
},
"watch": {
"watch": "watch",
"label": {
"files": "文件",
"recursive": "遞迴所有子資料夾"
@ -458,14 +524,12 @@
"connection-closed": "連接已關閉 __host__:__port__",
"connections": "__count__ 個連接",
"connections_plural": "__count__ 個連接"
},
"errors": {
"connection-lost": "連接中斷 __host__:__port__",
"timeout": "超時關閉通訊端連接,埠 __port__",
"cannot-listen": "無法監聽埠 __port__, 錯誤: __error__",
"error": "錯誤: __error__",
"socket-error": "通訊端連接錯誤來自 __host__:__port__",
"no-host": "主機位址或埠未設定",
"connect-timeout": "連接逾時",
@ -480,14 +544,15 @@
"output": "輸出",
"group": "組",
"interface": "本地IP",
"interfaceprompt": "(可選)本地 IP 綁定到",
"send": "發送一個",
"toport": "到埠",
"address": "地址",
"decode-base64": "是否解碼Base64編碼的資訊?"
"decode-base64": "是否解碼Base64編碼的資訊?",
"interfaceprompt": "(可選)本地 IP 綁定到"
},
"placeholder": {
"interface": "可選eth0的IP地址",
"interfaceprompt": "(可選) 要綁定的本地接口或地址",
"address": "目標IP位址"
},
"udpmsgs": "udp信息",
@ -529,15 +594,18 @@
"ip-notset": "udp: IP地址未設定",
"port-notset": "udp: 埠未設定",
"port-invalid": "udp: 無效埠號碼",
"alreadyused": "udp: 埠已被佔用"
"alreadyused": "udp: 埠已被佔用",
"ifnotfound": "udp: 接口 __iface__ 未發現"
}
},
"switch": {
"switch": "switch",
"label": {
"property": "屬性",
"rule": "規則",
"repair": "重建資訊佇列"
},
"previous": "先前值",
"and": "與",
"checkall": "全選所有規則",
"stopfirst": "接受第一條匹配資訊後停止",
@ -550,11 +618,15 @@
"false": "為假",
"null": "為空",
"nnull": "非空",
"istype": "類型是",
"empty": "為空",
"nempty": "非空",
"head": "head",
"tail": "tail",
"index": "index between",
"exp": "JSONata運算式",
"else":"除此以外"
"else": "除此以外",
"hask": "擁有鍵"
},
"errors": {
"invalid-expr": "無效的JSONata運算式: __error__",
@ -588,6 +660,7 @@
}
},
"range": {
"range": "range",
"label": {
"action": "操作",
"inputrange": "映射輸入資料",
@ -623,7 +696,8 @@
"firstrow": "第一行包含列名",
"output": "輸出",
"includerow": "包含列名行",
"newline": "分行符號"
"newline": "分行符號",
"usestrings": "解析數值"
},
"placeholder": {
"columns": "用逗號分割列名"
@ -654,7 +728,8 @@
"html": {
"label": {
"select": "選取項",
"output": "輸出"
"output": "輸出",
"in": "in"
},
"output": {
"html": "選定元素的html內容",
@ -670,7 +745,9 @@
"errors": {
"dropped-object": "忽略非物件格式的有效負載",
"dropped": "忽略不支援格式的有效負載類型",
"dropped-error": "轉換有效負載失敗"
"dropped-error": "轉換有效負載失敗",
"schema-error": "JSON架構錯誤",
"schema-error-compile": "JSON架構錯誤: 未能編譯架構"
},
"label": {
"o2j": "對象至JSON",
@ -702,76 +779,6 @@
"xml_js": "此節點僅處理XML字串或JS物件."
}
},
"rpi-gpio": {
"label": {
"gpiopin": "GPIO",
"selectpin": "選擇引腳",
"resistor": "電阻?",
"readinitial": "在部署/重啟時讀取引腳的初始狀態?",
"type": "類型",
"initpin": "初始化引腳狀態?",
"debounce": "去抖動",
"freq": "頻率",
"button": "按鈕",
"pimouse": "Pi滑鼠",
"pikeyboard": "Pi鍵盤",
"left": "左",
"right": "右",
"middle": "中"
},
"resistor": {
"none": "無",
"pullup": "上拉電阻",
"pulldown": "下拉電阻"
},
"digout": "數位輸出",
"pwmout": "PWM輸出",
"servo": "伺服輸出",
"initpin0": "初始引腳電平 - 低(0)",
"initpin1": "初始引腳電平 - 高(1)",
"left": "左",
"right": "右",
"middle": "中",
"any": "任何",
"pinname": "引腳",
"alreadyuse": "已被使用",
"alreadyset": "已被設為",
"tip": {
"pin": "<b>正在使用引腳</b>: ",
"in": "提示: 僅接受數位輸入 - 輸出必須為0或1.",
"dig": "提示: 如用數位輸出 - 輸入必須為0或1.",
"pwm": "提示: 如用PWM輸出 - 輸入必須為0至100之間; 如用高頻率可能會比預期佔用更多CPU資源.",
"ser": "<b>提示</b>: 如用伺服輸出 - 輸入必須為0至100之間. 50為中間值."
},
"types": {
"digout": "數位輸出",
"input": "輸入",
"pullup": "含有上拉電阻的輸入",
"pulldown": "含有下拉電阻的輸入",
"pwmout": "PWM輸出",
"servo": "伺服輸出"
},
"status": {
"stopped": "已停止",
"closed": "已關閉",
"not-running": "不運行"
},
"errors": {
"ignorenode": "忽略樹莓派的特定節點",
"version": "版本命令失敗",
"sawpitype": "查看Pi類型",
"libnotfound": "找不到樹莓派RPi.GPIO的python庫",
"alreadyset": "GPIO引腳 __pin__ 已經被設定為類型: __type__",
"invalidpin": "無效GPIO引腳",
"invalidinput": "無效輸入",
"needtobeexecutable": "__command__須為可運行命令",
"mustbeexecutable": "nrgpio須為可運行",
"commandnotfound": "nrgpio命令不存在",
"commandnotexecutable": "nrgpio命令不可運行",
"error": "錯誤: __error__",
"pythoncommandnotfound": "nrpgio python命令未處於運行狀態"
}
},
"file": {
"label": {
"filename": "檔案名",
@ -783,7 +790,10 @@
"breaklines": "分拆成行",
"filelabel": "文件",
"sendError": "發生錯誤時發送消息(傳統模式)",
"deletelabel": "刪除 __file__"
"deletelabel": "刪除 __file__",
"encoding": "編碼",
"utf8String": "UTF8字符串",
"binaryBuffer": "二進制buffer"
},
"action": {
"append": "追加至文件",
@ -801,6 +811,21 @@
"deletedfile": "刪除檔: __file__",
"appendedfile": "追加至文件: __file__"
},
"encoding": {
"none": "默認",
"native": "Native",
"unicode": "Unicode",
"japanese": "日本",
"chinese": "中國",
"korean": "韓國",
"taiwan": "臺灣/香港",
"windows": "Windows代碼頁",
"iso": "ISO代碼頁",
"ibm": "IBM代碼頁",
"mac": "Mac代碼頁",
"koi8": "KOI8代碼頁",
"misc": "其它"
},
"errors": {
"nofilename": "未指定檔案名",
"invaliddelete": "警告:無效刪除。請在配置對話方塊中使用特定的刪除選項",
@ -812,6 +837,7 @@
"tip": "提示: 檔案名應該是絕對路徑否則它將相對於Node-RED進程的工作目錄。"
},
"split": {
"split": "split",
"intro": "基於以下類型拆分<code>msg.payload</code>:",
"object": "<b>對象</b>",
"objectSend": "每個鍵值對作為單個消息發送",
@ -823,6 +849,7 @@
"addname": " 複製鍵到 "
},
"join": {
"join": "join",
"mode": {
"mode": "模式",
"auto": "自動",
@ -831,6 +858,7 @@
"custom": "手動"
},
"combine": "合併每個",
"completeMessage": "完整的消息",
"create": "輸出為",
"type": {
"string": "字串",
@ -846,7 +874,7 @@
"afterCount": "達到一定數量的資訊時",
"count": "數量",
"subsequent": "和每個後續的消息",
"afterTimeout":"第一條消息的若干時間後",
"afterTimeout": "第一條消息的若幹時間後",
"seconds": "秒",
"complete": "在收到存在<code>msg.complete</code>的消息後",
"tip": "此模式假定此節點與<i>split</i>相連, 或者接收到的消息有正確配置的<code>msg.parts</code>屬性.",
@ -869,6 +897,7 @@
}
},
"sort": {
"sort": "排序",
"target": "排序屬性",
"seq": "資訊佇列",
"key": "鍵值",
@ -877,11 +906,12 @@
"ascending": "昇冪",
"descending": "降冪",
"as-number": "作為數值",
"invalid-exp" : "sort節點中存在無效的JSONata運算式",
"too-many" : "sort節點中有太多待定信息",
"clear" : "清空sort節點中的待定資訊"
"invalid-exp": "排序節點中存在無效的JSONata運算式",
"too-many": "排序節點中有太多待定信息",
"clear": "清空排序節點中的待定資訊"
},
"batch": {
"batch": "batch",
"mode": {
"label": "模式",
"num-msgs": "按指定數量分組",

View File

@ -0,0 +1,19 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="tls-config">
<p>TLS連接的配置選項。</p>
</script>

View File

@ -0,0 +1,22 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="http proxy">
<p>HTTP代理的配置選項。</p>
<h3>詳細</h3>
<p>訪問代理例外列表中的主機時,將不使用任何代理。</p>
</script>

View File

@ -0,0 +1,70 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="mqtt in">
<p>連接到MQTT代理並訂閱來自指定主題的消息。</p>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">字符串 | buffer</span></dt>
<dd>如果不是二進制buffer的話就是字符串</dd>
<dt>topic <span class="property-type">字符串</span></dt>
<dd>MQTT主題使用<code>/</code>作爲層次結構分隔符。</dd>
<dt>qos <span class="property-type">數值</span> </dt>
<dd>QoS服務質量0, 最多一次; 1, 最少一次; 2, 只一次。</dd>
<dt>retain <span class="property-type">布爾值</span></dt>
<dd>值爲true時表示消息已保留且可能是舊的。</dd>
</dl>
<h3>詳細</h3>
<p>訂閱主題可以包括MQTT通配符+:一個級別,#:多個級別)。</p>
<p>使用該節點您首先需要建立與MQTT代理的連接。通過單擊鉛筆圖標來進行配置。</p>
<p>如有需要幾個MQTT節點輸入或輸出可以共享相同的代理連接。</p>
</script>
<script type="text/x-red" data-help-name="mqtt out">
<p>連接到MQTT代理並發布消息。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">字符串 | buffer</span></dt>
<dd>要發布的有效負載。如果未設置此屬性,則不會發送任何消息。要發送空白消息,請將此屬性設置爲空字符串。</dd>
<dt class="optional">topic <span class="property-type">字符串</span></dt>
<dd>要發布的MQTT主題。</dd>
<dt class="optional">qos <span class="property-type">number</span></dt>
<dd>QoS服務質量0, 最多一次; 1, 最少一次; 2, 只一次。默認值爲0。</dd>
<dt class="optional">retain <span class="property-type">布爾值</span></dt>
<dd>設置爲<code>true</code>來將消息保留在代理上。默認值爲<code>false</code></dd>
</dl>
<h3>詳細</h3>
<p><code>msg.payload</code>用作已發布消息的有效載荷。如果包含Object則會在發送之前將其轉換爲JSON字符串。如果它包含二進制buffer則消息將按原樣發布。</p>
<p>可以在節點中配置所使用的主題,或者如果留爲空白,則可以通過<code>msg.topic</code>進行設置。</p>
<p>同樣可以在節點中配置QoS和保留值或者如果保留空白則分別由<code>msg.qos</code><code>msg.retain</code>設置。要清除先前存儲在代理中的主題,請設置保留標志並向該主題發布空消息。</p>
<p>該節點需要與要配置的MQTT代理的連接。通過單擊鉛筆圖標進行配置。</p>
<p>如果需要幾個MQTT節點輸入或輸出可以共享相同的代理連接。</p>
</script>
<script type="text/x-red" data-help-name="mqtt-broker">
<p>與MQTT代理的連接設置。</p>
<p>創建與代理的連接設置。可以在<code>MQTT In</code><code>MQTT Out</code>節點中重複利用這些設置。</p>
<p>如果未爲該節點設置客戶端ID並且設置了會話初始化則將生成一個隨機客戶端ID。設置客戶端ID時請確保它對于連接目標處的代理是唯一的。</p>
<h4>Birth Message</h4>
<p>建立連接後發布在以配置主題中的消息。</p>
<h4>Close Message</h4>
<p>在連接正常結束之前重新部署或者關閉了節點時,發布在以配置主題中的消息。</p>
<h4>Will Message</h4>
<p>當節點意外丟失連接時由代理發布的消息</p>
<h4>WebSockets</h4>
<p>可以將節點配置成使用WebSocket連接。使用WebSocket時請在服務器字段中以完整格式描述連接目標的URI。 例如:</p>
<pre>ws://example.com:4000/mqtt</pre>
</script>

View File

@ -0,0 +1,81 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="http in">
<p>創建用于創建Web服務的HTTP端點。</p>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload</dt>
<dd>GET請求包含任何查詢字符串參數的對象。或者包含HTTP請求正文。</dd>
<dt>req<span class="property-type">object</span></dt>
<dd>HTTP請求對象。該對象包含有關請求信息的多個屬性。
<ul>
<li><code>body</code> - 傳入請求的正文。格式將取決于請求。</li>
<li><code>headers</code> - 包含HTTP請求標頭的對象。</li>
<li><code>query</code> - 包含任何查詢字符串參數的對象。</li>
<li><code>params</code> - 包含任何路由參數的對象。</li>
<li><code>cookies</code> - 包含請求cookie的對象。</li>
<li><code>files</code> - 如果節點啓用了文件上傳,則爲包含了上傳的文件的對象。</li>
</ul>
</dd>
<dt>res<span class="property-type">object</span></dt>
<dd>HTTP響應對象。此屬性不應直接使用<code>HTTP Response</code>節點記錄了如何響應請求。該屬性必須保留在傳遞給響應節點的消息上。</dd>
</dl>
<h3>詳細</h3>
<p>節點將在配置的路徑上監聽特定類型的請求。路徑可以完全指定,例如<code>/user</code>,或包括可以接受任何值的命名參數,例如<code>/user/:name</code>。 使用命名參數時,可以在<code>msg.req.params</code>下訪問其在請求中的實際值。</p>
<p>對于包含正文的請求例如POST或PUT請求的內容將作爲<code>msg.payload</code>提供。</p>
<p>如果可以確定請求的內容類型,則正文將被解析爲任何適當的類型。例如,<code>application/json</code>將被解析爲其JavaScript對象表示。</p>
<p><b>注意:</b>該節點不發送對請求的任何響應。該流程必須包含HTTP響應節點才能完成請求。</p>
</script>
<script type="text/x-red" data-help-name="http response">
<p>將響應發送回從HTTP輸入節點接收的請求。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt>
<dd>響應的正文。</dd>
<dt class="optional">statusCode <span class="property-type">數值</span></dt>
<dd>如果設置則用作響應狀態代碼。默認值200。</dd>
<dt class="optional">headers <span class="property-type">object</span></dt>
<dd>如果設置則提供HTTP頭以包含在響應中。</dd>
<dt class="optional">cookies <span class="property-type">object</span></dt>
<dd>如果設置則可用于設置或刪除cookie。</dd>
</dl>
<h3>詳細</h3>
<p>還可以在節點本身內設置<code>statusCode</code><code>headers</code>。如果在節點內設置了屬性則不能被相應的message屬性覆蓋。</p>
<h4>Cookie處理</h4>
<p><code>cookies</code>屬性必須是名稱/值對的對象。該值可以是使用默認選項設置cookie值的字符串也可以是options對象。<p>
<p>下面的示例設置兩個cookie-一個名爲<code>name</code>的值爲<code>nick</code>,另一個名爲<code>session</code>的值爲<code>1234</code>並且有效期設置爲15分鍾。</p>
<pre>
msg.cookies = {
name: 'nick',
session: {
value: '1234',
maxAge: 900000
}
}</pre>
<p>有效選項包括:</p>
<ul>
<li><code>domain</code> - (字符串) Cookie的域名</li>
<li><code>expires</code> - (日期) GMT標准時間的到期日。如果未指定或設置爲0則創建會話cookie</li>
<li><code>maxAge</code> - (字符串) 相對于當前時間的到期日期(以毫秒爲單位)</li>
<li><code>path</code> - (字符串) Cookie的路徑。默認爲/</li>
<li><code>value</code> - (字符串) Cookie使用的值</li>
</ul>
<p>要刪除Cookie請將其<code>value</code>設置爲<code>null</code></p>
</script>

View File

@ -0,0 +1,78 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="http request">
<p>發送HTTP請求並返回響應。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt class="optional">url <span class="property-type">字符串</span></dt>
<dd>如果未在節點中配置則此可選屬性設置請求的url。</dd>
<dt class="optional">method <span class="property-type">字符串</span></dt>
<dd>如果未在節點中配置則此可選屬性設置請求的HTTP方法。必須是<code>GET</code>,<code>PUT</code>,<code>POST</code>,<code>PATCH</code><code>DELETE</code>之一。</dd>
<dt class="optional">headers <span class="property-type">object</span></dt>
<dd>設置請求的HTTP頭。</dd>
<dt class="optional">cookies <span class="property-type">object</span></dt>
<dd>如果設置則可用于發送帶有請求的cookie。</dd>
<dt class="optional">payload</dt>
<dd>發送爲請求的正文。</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd>如果設置爲<code>false</code>則允許對使用自簽名證書的https站點進行請求。</dd>
<dt class="optional">followRedirects</dt>
<dd>如果設置爲<code>false</code>則阻止遵循重定向HTTP 301。默認情況下爲<code>true</code></dd>
<dt class="optional">requestTimeout</dt>
<dd>如果設置爲正數毫秒,將覆蓋全局設置的<code>httpRequestTimeout</code>參數。</dd>
</dl>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">字符串 | object | buffer</span></dt>
<dd>響應的正文。可以將節點配置爲以字符串形式返回主體嘗試將其解析爲JSON字符串或將其保留爲二進制buffer。</dd>
<dt>statusCode <span class="property-type">數值</span></dt>
<dd>響應的狀態碼,如果請求無法完成,則返回錯誤碼。</dd>
<dt>headers <span class="property-type">object</span></dt>
<dd>包含響應頭的對象。</dd>
<dt>responseUrl <span class="property-type">字符串</span></dt>
<dd>如果在處理請求時發生任何重定向則此屬性爲最終重定向的URL。否則則爲原始請求的URL。</dd>
<dt>responseCookies <span class="property-type">object</span></dt>
<dd>如果響應包含cookie則此屬性是每個cookie的名稱/值’鍵值對的對象。</dd>
<dt>redirectList <span class="property-type">數組</span></dt>
<dd>如果請求被重定向了一次或多次則累積的信息將被添加到此屬性。“location”是下一個重定向目標。cookie是從重定向源返回的cookie。</dd>
</dl>
<h3>詳細</h3>
<p>在節點內配置後URL屬性可以包含<a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache樣式</a>標簽。 這些標簽允許使用傳入消息的值來構造url。例如如果url設置爲<code>example.com/{{{{topic}}}</code>,它將自動插入<code>msg.topic</code>的值。使用{{{...}}}可以防止mustache轉義/ &等字符。</p>
<p>節點可以選擇自動將<code>msg.payload</code>編碼爲GET請求的查詢字符串參數在這種情況下<code>msg.payload</code>必須是一個對象。</p>
<p><b>注意:</b>如果使用了代理,則應設置<code>http_proxy=...</code>環境變量並重新啓動Node-RED或使用“代理配置”。如果設置了代理配置則配置優先于環境變量。</p>
<h4>使用多個HTTP請求節點</h4>
<p>爲了在一個流程中多次使用該節點,必須要注意<code>msg.headers</code>屬性的處理。通常在第一個節點在響應頭中設置此屬性,而不期望在下一個節點的請求頭中使用此屬性。如果節點之間的<code>msg.headers</code>屬性保持不變,則第二個節點將忽略它。要設置自定義標題,首先應刪除<code>msg.headers</code>或將其重置爲空對象:<code>{}</code></p>
<h4>Cookie處理</h4>
<p>傳遞給節點的<code>cookies</code>屬性必須是‘名稱/值鍵值對的對象。該值可以是設置cookie值的字符串也可以是具有單個<code>value</code>屬性的對象。</p>
<p>請求返回的所有cookie都將在<code>responseCookies</code>屬性下傳遞回去。</p>
<h4>內容類型處理</h4>
<p>如果<code>msg.payload</code>是一個對象,則節點將自動將請求的內容類型設置爲<code>application/json</code>並對其進行編碼。</p>
<p>要將請求編碼爲表單數據,應將<code>msg.headers[“content-type”]</code>設置爲<code>application/x-www-form-urlencoded</code></p>
<h4>文件上傳</h4>
<p>要執行文件上傳,應將<code>msg.headers["content-type"]</code>設置爲<code>multipart/form-data</code><code>msg.payload</code>傳遞給節點的必須是具有以下結構的對象:</p>
<pre><code>{
"KEY": {
"value": FILE_CONTENTS,
"options": {
"filename": "FILENAME"
}
}
}</code></pre>
<p><code>KEY</code>,<code>FILE_CONTENTS</code><code>FILENAME</code>的值應設置爲適當的值。</p>
</script>

View File

@ -0,0 +1,35 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="websocket in">
<p>WebSocket輸入節點。</p>
<p>默認情況下從WebSocket接收的數據將位于<code>msg.payload</code>中。可以將套接字配置爲期望格式正確的JSON字符串在這種情況下它將解析JSON並將結果對象作爲整個消息發送。</p>
</script>
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket輸出節點。</p>
<p>默認情況下,<code>msg.payload</code>將通過WebSocket發送。可以將套接字配置爲將整個<code>msg</code>對象編碼爲JSON字符串然後通過WebSocket發送。</p>
<p>如果到達此節點的消息是從WebSocket In節點開始的則該消息將發送回觸發流程的客戶端。否則消息將廣播給所有連接的客戶端。</p>
<p>如果要廣播從“WebSocket輸入”節點開始的消息則可以應該刪除流中的<code>msg._session</code>屬性。</p>
</script>
<script type="text/x-red" data-help-name="websocket-listener">
<p>此配置節點使用指定的路徑創建WebSocket服務器端點。</p>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>此配置節點將WebSocket客戶端連接到指定的URL。</p>
</script>

View File

@ -0,0 +1,35 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="tcp in">
<p>提供TCP輸入選擇。可以連接到遠程TCP端口或接受傳入連接。</p>
<p><b>注意:</b>在某些系統上您可能需要root或管理員權限來訪問低于1024的端口。</p>
</script>
<script type="text/x-red" data-help-name="tcp out">
<p>提供TCP輸出的選擇。可以連接到遠程TCP端口接受傳入的連接或回複從TCP In節點收到的消息。</p>
<p>僅發送<code>msg.payload</code></p>
<p>如果<code>msg.payload</code>是包含二進制數據的Base64編碼的字符串則Base64解碼選項將導致它在發送之前先轉換回二進制。</p>
<p>如果不存在<code>msg._session</code>,則有效負載將發送到<b>所有</b>連接的客戶端。</p>
<p><b>注意:</b>在某些系統上您可能需要root或管理員權限來訪問低于1024的端口。</p>
</script>
<script type="text/x-red" data-help-name="tcp request">
<p>一個簡單的TCP請求節點。將<code>msg.payload</code>發送到服務器tcp端口並期望得到響應。</p>
<p>連接到服務器,發送“請求”並接收“響應”。 可以從固定數量的字符,與指定字符匹配的字符中選擇操作,從第一個答複到達起等待指定的時間,等待數據到達,發送數據並立即取消連接而無需等待答複等操作中進行選擇。</p>
<p>響應將在<code>msg.payload</code>中作爲buffer輸出因此您可能需要對它進行<code>.toString()</code>操作。</p>
<p>如果將tcp主機或端口留空則必須使用<code>msg.host</code><code>msg.port</code>屬性進行設置。</p>
</script>

View File

@ -0,0 +1,28 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="udp in">
<p>UDP輸入節點。在<code>msg.payload</code>中生成Buffer字符串或Base64編碼的字符串。支持組播。</p>
<p><code>msg.ip</code><code>msg.port</code>中設置接收到的消息的IP地址和端口。</p>
<p><b>注意:</b>在某些系統上您可能需要root或管理員權限才能使用低于1024的端口或廣播。</p>
</script>
<script type="text/x-red" data-help-name="udp out">
<p>該節點將<code>msg.payload</code>發送到指定的UDP主機和端口。支持組播。</p>
<p>您也可以使用<code>msg.ip</code><code>msg.port</code>設置目標值,但是靜態配置的值具有優先權。</p>
<p>如果選擇廣播則將地址設置爲本地廣播IP地址。或者也可以嘗試使用全局廣播地址255.255.255.255。</p>
<p><b>注意:</b>在某些系統上您可能需要root或管理員權限才能使用低于1024的端口或廣播。</p>
</script>

View File

@ -0,0 +1,43 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="csv">
<p>在CSV格式的字符串及其JavaScript對象表示形式之間進行相互轉換。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 數組 | 字符串</span></dt>
<dd>JavaScript對象數組或CSV字符串。</dd>
</dl>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 數組 | 字符串</span></dt>
<dd>
<ul>
<li>如果輸入是字符串它將嘗試將其解析爲CSV並爲每行創建鍵/值對的JavaScript對象。然後該節點將爲每行發送一條消息或者發送一條包含對象數組的消息。</li>
<li>如果輸入是JavaScript對象它將嘗試構建CSV字符串。</li>
<li>如果輸入是簡單值的數組則將構建單行CSV字符串。</li>
<li>如果輸入是數組數組或對象數組則會創建多行CSV字符串。</li>
</ul>
</dd>
</dl>
<h3>詳細</h3>
<p>列模板可以包含列名稱的有序列表。將CSV轉換爲對象時列名將用作屬性名稱。或者也可以從CSV的第一行中獲取列名稱。</p>
<p>轉換爲CSV時列模板用于標識從對象中提取的屬性以及提取的順序。</p>
<p>如果輸入是數組,則列模板僅用于有選擇地生成一行列標題。</p>
<p>只要正確設置<code>parts</code>屬性,該節點就可以接受多部分輸入。</p>
<p>如果輸出多個消息,則將設置其<code>parts</code>屬性並形成完整的消息序列。</p>
<p><b>注意:</b>列模板必須用逗號分隔,即使數據中已有了其他分隔符。</p>
</script>

View File

@ -0,0 +1,33 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="html">
<p>使用CSS選擇器從<code>msg.payload</code>中保存的html文檔中提取元素。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">字符串</span></dt>
<dd>從中提取元素的html字符串。</dd>
<dt class="optional">select <span class="property-type">字符串</span></dt>
<dd>如果未在編輯面板中配置則可以將選擇器設置爲msg的屬性。</dd>
</dl>
<h3>Output</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">數組 | 字符串</span></dt>
<dd>結果可以是有效載荷中包含匹配元素的數組的單個消息;也可以是多條消息,每條消息都包含匹配元素。發送多條消息時,需要爲消息設置<code>parts</code></dd>
</dl>
<h3>詳細</h3>
<p>該節點支持CSS和jQuery選擇器的組合。查看<a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_blank">css-select documentation</a> 來獲得更多信息。</p>
</script>

View File

@ -0,0 +1,43 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="json">
<p>在JSON字符串及其JavaScript對象表示形式之間相互轉換。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 字符串</span></dt>
<dd>JavaScript對象或JSON字符串。</dd>
<dt>schema<span class="property-type">object</span></dt>
<dd>可選的JSON Schema對象用于驗證有效負載。在將<code>msg</code>發送到下一個節點之前,將刪除該屬性。</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 字符串</span></dt>
<dd>
<ul>
<li>如果輸入是JSON字符串它將嘗試將其解析爲JavaScript對象。</li>
<li>如果輸入是JavaScript對象它將創建一個JSON字符串。並可以選擇對此JSON字符串進行整形。</li>
</ul>
</dd>
<dt>schemaError<span class="property-type">數組</span></dt>
<dd>如果JSON模式驗證失敗則catch節點將具有包含錯誤數組的<code>schemaError</code>屬性。</dd>
</dl>
<h3>詳細</h3>
<p>默認的轉換目標是<code>msg.payload</code>,但是也可以轉換消息的其它屬性。</p>
<p>您可以將其設置爲僅執行特定的轉換,而不是自動選擇雙向轉換。例如,即使對<code>HTTP In</code>節點的請求未正確設置content-type也可以使用它來確保JSON節點的轉換結果是JavaScript對象</p>
<p>如果指定了轉換爲JSON字符串則不會對收到的字符串進行進一步的檢查。也就是說即使指定了格式化選項它也不會檢查字符串是否正確爲JSON或對JSON執行整形。</p>
<p>有關JSON模式的更多詳細信息請查閱<a href="http://json-schema.org/latest/json-schema-validation.html">規範</a>.</p>
</script>

View File

@ -0,0 +1,48 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="xml">
<p>在XML字符串及其JavaScript對象表示形式之間進行相互轉換。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 字符串</span></dt>
<dd>JavaScript對象或XML字符串。</dd>
</dl>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 字符串</span></dt>
<dd>
<ul>
<li>如果輸入是字符串它將嘗試將其解析爲XML並創建一個JavaScript對象。</li>
<li>如果輸入是JavaScript對象它將嘗試構建XML字符串。</li>
</ul>
</dd>
<dt class="optional">options <span class="property-type">object</span></dt>
<dd>可以將選項傳遞給內部使用的XML轉換庫。請參見<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options" target="_blank"> xml2js文檔</a> 來獲取更多信息。</dd>
</dl>
<h3>詳細</h3>
<p>在XML和對象之間進行轉換時默認情況下XML屬性會添加到名爲<code>$</code>的屬性中。將文本內容添加到名爲<code>_</code>的屬性中。這些屬性名稱可以在節點設置中更改。</p>
<p>例如將如下所示轉換以下XML</p>
<pre>&lt;p class="tag"&gt;Hello World&lt;/p&gt;</pre>
<pre>{
"p": {
"$": {
"class": "tag"
},
"_": "Hello World"
}
}</pre>
</script>

View File

@ -0,0 +1,34 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="yaml">
<p>在YAML格式的字符串及其JavaScript對象表示形式之間相互轉換。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 字符串</span></dt>
<dd>JavaScript對象或YAML字符串。</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 字符串</span></dt>
<dd>
<ul>
<li>如果輸入是YAML字符串它將嘗試將其解析爲JavaScript對象。</li>
<li>如果輸入是JavaScript對象它將創建一個YAML字符串。</li>
</ul>
</dd>
</dl>
</script>

View File

@ -0,0 +1,133 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="split">
<p>將一條消息拆分爲一系列消息。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">object | 字符串 | 數組 | buffer</span></dt>
<dd>節點的行爲由<code>msg.payload</code>的類型決定:
<ul>
<li><b>字符串</b>/<b>buffer</b> - 使用指定的字符(默認值:<code>\n</code>),緩衝區序列或固定長度將消息拆分。</li>
<li><b>數組</b> - 消息被拆分爲單個數組元素或固定長度的數組。</li>
<li><b>object</b> - 將爲對象的每個鍵/值對發送一條消息。</li>
</ul>
</dd>
</dl>
<h3>輸出</h3>
<dl class="message-properties">
<dt>parts<span class="property-type">object</span></dt>
<dd>此屬性包含有關如何將消息與原始消息分開的信息。如果傳遞給<b>join</b>節點,則可以將序列重組爲單個消息。該屬性具有以下屬性:
<ul>
<li><code>id</code> - 一組消息的標識符</li>
<li><code>index</code> - 組中的位置</li>
<li><code>count</code> - 如果已知組中的郵件總數。請參閱下面的“流媒體模式”</li>
<li><code>type</code> - 消息的類型-字符串/數組/對象/buffer</li>
<li><code>ch</code> - 對于字符串或buffer用于將消息拆分爲字符串或字節數組的數據</li>
<li><code>key</code> - 對于對象,創建此消息的屬性的鍵。可以將節點配置爲也將此值複制到另一個消息屬性,例如<code>msg.topic</code></li>
<li><code>len</code> - 使用固定長度值拆分消息時,每段子消息的長度</li>
</ul>
</dd>
</dl>
<h3>詳細</h3>
<p>在使用<b>join</b>節點將序列重新組合爲單個消息之前,推薦使用此節點來輕松地創建跨消息序列,執行通用操作的流。</p>
<p>它使用<code>msg.parts</code>屬性跟蹤序列的各個部分。</p>
<h4>流媒體模式</h4>
<p>該節點還可以用于重排消息流。例如,發送換行符終止命令的串行設備可能會傳遞一條消息,並在其末尾帶有部分命令。 在“流模式”下,此節點將拆分一條消息並發送每個完整的段。如果末尾有部分片段,則該節點將保留該片段,並將其添加到收到的下一條消息之前。</p>
<p>在此模式下運行時,該節點將不會設置<code>msg.parts.count</code>屬性,因爲流中期望的消息數還是未知的。這意味著它不能在自動模式下與<b>join</b>節點一起使用。</p>
</script>
<script type="text/x-red" data-help-name="join">
<p>將消息序列合並爲一條消息.</p>
<p>共有三種模式:</p>
<dl>
<dt>自動模式</dt>
<dd><b>split</b>節點配對時,它將自動將已被拆分的消息進行合並。</dd>
<dt>手動模式</dt>
<dd>手動地以各種方式合並消息序列。</dd>
<dt>列聚合模式</dt>
<dd>對消息列中的所有消息應用表達式以將其簡化爲單個消息。</dd>
</dl>
<h3>輸入</h3>
<dl class="message-properties">
<dt class="optional">parts<span class="property-type">object</span></dt>
<dd>使用自動模式時,所有的消息都應包含此屬性。<b>split</b>節點會生成此屬性,但也可以手動進行設置。該屬性具有以下屬性:
<ul>
<li><code>id</code> - 消息組的標識符</li>
<li><code>index</code> - 組中的位置</li>
<li><code>count</code> - 如果已知組中的郵件總數。請參閱下面的“流媒體模式”</li>
<li><code>type</code> - 消息的類型-字符串/數組/對象/buffer</li>
<li><code>ch</code> - 對于字符串或buffer用于將消息拆分爲字符串或字節數組的數據</li>
<li><code>key</code> - 對于對象,創建此消息的屬性的鍵。可以將節點配置爲也將此值複制到另一個消息屬性,例如<code>msg.topic</code>/li>
<li><code>len</code> - 使用固定長度值拆分消息時,每段子消息的長度</li>
</ul>
</dd>
<dt class="optional">complete</dt>
<dd>如果設置,則節點將以其當前狀態發送其輸出消息。</dd>
</dl>
<h3>詳細</h3>
<h4>自動模式</h4>
<p>自動模式使用傳入消息的<code>parts</code>屬性來確定應如何連接序列。這使它可以自動逆轉<b>split</b>節點的操作。</p>
<h4>手動模式</h4>
<p>設置爲以手動模式時,該節點能以各種不同的方法來處理消息:</p>
<ul>
<li><b>字符串</b><b>緩衝區</b>-通過將每條消息的選定屬性與指定的連接字符或緩衝區連接起來。</li>
<li><b>數組</b> - 通過將每個選定的屬性或整個消息添加到輸出數組</li>
<li><b>鍵/值對象</b> - 通過使用每個消息的屬性來確定存儲所需值的鍵。</li>
<li><b>merged object</b> - 通過將每個消息的屬性合並到一個對象下。</li>
</ul>
<p>輸出消息的其他屬性都取自發送結果前的最後一條消息。</p>
<p>可以用<i>計數</i>來確定應接收多少條消息來進行合並。對于對象輸出,可以設置爲達到此計數後的每條後續消息都發送一條輸出。</p>
<p>可以用<i>超時</i>來設置發送新消息之前的等待時間。</p>
<p>如果收到設置了<b>msg.complete</b>屬性的消息時發送輸出消息並重置消息列數。</p>
<p>如果收到設置了<b>msg.reset</b>屬性的消息,則部分收到的消息將被刪除而不發送,同時重置消息列數。</p>
<h4>列聚合模式</h4>
<p>選擇列聚合模式時,將表達式應用于組成消息列的每條消息,並使用聚合值組成一條消息。</p>
<dl class="message-properties">
<dt>初始值</dt>
<dd>累積值的初始值(<code>$A</code>)。</dd>
<dt>聚合表達式</dt>
<dd>序列中的每個消息調用的JSONata表達式。結果將作爲累加值傳遞到表達式的下一個調用。在表達式中可以使用以下特殊變量
<ul>
<li><code>$A</code>: 累計值 </li>
<li><code>$I</code>: 消息在序列中的索引</li>
<li><code>$N</code>: 序列中的消息數</li>
</ul>
</dd>
<dt>最終調整式子</dt>
<dd>可選的JSONata表達式在將聚合表達式應用于序列中的所有消息之後應用。在表達式中可以使用以下特殊變量
<ul>
<li><code>$A</code>: 累計值</li>
<li><code>$N</code>: 消息在序列中的索引</li>
</ul>
</dd>
<p>默認情況下,按順序從序列的第一條消息到最後一條消息應用聚合表達式。也可以選擇以相反的順序應用聚合表達式。</p>
</dl>
<p><b>例子:</b>給定一系列數字值,以下設置將計算平均值:
<ul>
<li><b>聚合表達式</b>: <code>$A+payload</code></li>
<li><b>初始值</b>: <code>0</code></li>
<li><b>最終調整式</b>: <code>$A/$N</code></li>
</ul>
</p>
<h4>儲存訊息</h4>
<p>該節點將在內部緩存消息,以便跨序列工作。運行時設置<code>nodeMessageBufferMaxLength</code>可用于設定緩存的消息數。</p>
</script>

View File

@ -0,0 +1,41 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="sort">
<p>對消息屬性或消息序列進行排序的函數。</p>
<p>當配置爲對消息屬性進行排序時,節點將對指定消息屬性所指向的數組數據進行排序。</p>
<p>當配置爲對消息序列排序時,它將對消息重新排序。</p>
<p>排序順序可以是:</p>
<ul>
<li><b>升序</b></li>
<li><b>降序</b></li>
</ul>
<p>對于數字,可以通過複選框指定數字順序。</p>
<p>排序鍵可以是元素值也可以是JSONata表達式來對屬性值進行排序還可以是message屬性或JSONata表達式來對消息序列進行排序。<p>
<p>在對消息序列進行排序時,排序節點依賴于接收到的消息來設置<code>msg.parts</code>。拆分節點將生成此屬性,但也可以手動創建。它具有以下屬性:</p>
<p>
<ul>
<li><code>id</code> - 消息組的標識符</li>
<li><code>index</code> - 組中的位置</li>
<li><code>count</code> - 群組中的郵件總數</li>
</ul>
</p>
<p><b>注意:</b>在此節點的處理中,消息在內部存儲。通過指定要累積的最大消息數,可以防止意外的高內存使用。默認設置是不限制消息數量。
<ul>
<li><code>nodeMessageBufferMaxLength</code>屬性在<b>settings.js</b>中設置。</li>
</ul>
</p>
</script>

View File

@ -0,0 +1,34 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="batch">
<p>根據各種規則創建消息序列。</p>
<h3>詳細</h3>
<p>有三種創建消息序列的模式:</p>
<dl>
<dt>訊息數</dt>
<dd>將消息分組爲給定長度的序列。 <b>overlap</b>(重疊)選項指定在一個序列的末尾應重複多少消息。</dd>
<dt>時間間隔</dt>
<dd>對在指定時間間隔內到達的郵件進行分組。如果在該時間間隔內沒有消息到達,則該節點可以選擇發送空消息。</dd>
<dt>串聯序列</dt>
<dd>通過串聯輸入序列來創建消息序列。每條消息必須具有<code>msg.topic</code>屬性和標識其序列的<code>msg.parts</code>屬性。該節點配置有<code>topic</code>值列表,以標識所連接的順序序列。
</dd>
</dl>
<h4>儲存訊息</h4>
<p>該節點將在內部緩衝消息,以便跨序列工作。運行時設置<code>nodeMessageBufferMaxLength</code>可用于限制節點將緩存多少消息。</p>
</script>

View File

@ -0,0 +1,55 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="file">
<p><code>msg.payload</code>寫入文件,添加到末尾或替換現有內容。或者,它也可以刪除文件。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt class="optional">filename<span class="property-type">字符串</span></dt>
<dd>如果未在節點中配置,則此可選屬性可以設置文件名。</dd>
</dl>
<h3>輸出</h3>
<p>寫入完成後,輸入消息將發送到輸出端口。</p>
<h3>詳細</h3>
<p>每個消息的有效荷載將添加到文件的末尾,可以選擇在每個消息之間添加一個換行符(\n</p>
<p>如果使用<code>msg.filename</code>,則每次寫入後文件都會關閉。爲了獲得最佳體驗,請使用固定的文件名。</p>
<p>可以將其配置爲覆蓋整個文件,而不是在文件後添加段落。例如,在將二進制數據寫入文件(例如圖像)時,應使用此選項,並且應禁用添加換行符的選項。</p>
<p>可以從編碼列表中指定寫入文件的數據的編碼。</p>
<p>您可以將此節點配置爲刪除文件。</p>
</script>
<script type="text/html" data-help-name="file in">
<p>以字符串或二進制緩衝區的形式讀取文件的內容。</p>
<h3>輸入</h3>
<dl class="message-properties">
<dt class="optional">filename<span class="property-type">字符串</span></dt>
<dd>如果未在節點配置中設置,該屬性可以選擇要讀取的文件名。</dd>
</dl>
<h3>輸出</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">字符串 | buffer</span></dt>
<dd>文件的內容可以是字符串也可以是二進制的buffer。</dd>
<dt class="optional">filename <span class="property-type">字符串</span></dt>
<dd>如果未在節點配置中設置,該屬性可以選擇要讀取的文件名。</dd>
</dl>
<h3>詳細</h3>
<p>文件名應該是絕對路徑否則將相對于Node-RED進程的工作目錄。</p>
<p>在Windows上可能需要使用轉義路徑分隔符例如<code>\\Users\\myUser</code></p>
<p>可以選擇將文本文件拆分爲幾行每行輸出一條消息或者將二進制文件拆分爲較小的buffer塊-塊大小取決于操作系統但通常爲64kLinux/Mac或41kWindows</p>
<p>當拆分爲多條消息時,每條消息將具有<code>parts</code>屬性集,從而形成完整的消息序列。</p>
<p>如果輸出格式爲字符串,則可以從編碼列表中指定輸入數據的編碼。</p>
<p>應該使用Catch節點來捕獲並處理錯誤。</p>
</script>

View File

@ -0,0 +1,25 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-help-name="watch">
<p>監視目錄或文件中的更改。</p>
<p>您可以輸入用逗號分隔的目錄和/或文件的列表。您需要在所有帶有空格的地方加上引號“...”。</p>
<p>在Windows上必須在任何目錄名稱中使用雙反斜杠<code>\\</code></p>
<p>實際更改的文件的完整文件名將放入<code>msg.payload</code><code>msg.filename</code>中,而監視列表的字符串化版本將在<code>msg.topic</code>中返回。</p>
<p><code>msg.file</code>僅包含已更改文件的短文件名。<code>msg.type</code>更改了事物的類型,通常是<i>file</i><i>directory</i>,而<code>msg.size</code>保留了文件的大小(以字節爲單位)。</p>
<p>當然在Linux中<i>everything</i>也是一個文件,因此可以監視</p>
<p><b>注意:</b>該目錄或文件必須存在才能被監視。如果文件或目錄被刪除,即使重新創建它也可能不再被監視。</p>
</script>

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "1.0.3",
"version": "1.0.4",
"license": "Apache-2.0",
"repository": {
"type": "git",
@ -15,30 +15,30 @@
}
],
"dependencies": {
"ajv": "6.10.2",
"ajv": "6.12.0",
"body-parser": "1.19.0",
"cheerio": "0.22.0",
"content-type": "1.0.4",
"cookie-parser": "1.4.4",
"cookie": "0.4.0",
"cors": "2.8.5",
"cron": "1.7.2",
"cron": "1.8.2",
"denque": "1.4.1",
"fs-extra": "8.1.0",
"fs.notify": "0.0.4",
"hash-sum": "2.0.0",
"https-proxy-agent": "2.2.4",
"https-proxy-agent": "5.0.0",
"is-utf8": "0.2.1",
"js-yaml": "3.13.1",
"media-typer": "1.1.0",
"mqtt": "2.18.8",
"multer": "1.4.2",
"mustache": "3.0.2",
"mustache": "4.0.0",
"on-headers": "1.0.2",
"raw-body": "2.4.1",
"request": "2.88.0",
"ws": "6.2.1",
"xml2js": "0.4.22",
"iconv-lite": "0.5.0"
"xml2js": "0.4.23",
"iconv-lite": "0.5.1"
}
}

Some files were not shown because too many files have changed in this diff Show More