Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
841438793d | ||
|
|
e0ac565bab | ||
|
|
e5511ea86d | ||
|
|
b67df2c0ab |
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
|||||||
/packages/node_modules/** linguist-generated=false
|
|
||||||
61
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,61 +0,0 @@
|
|||||||
name: 🐞 Report a bug
|
|
||||||
description: File a bug/issue on the core of Node-RED
|
|
||||||
labels: [needs-triage]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
|
|
||||||
|
|
||||||
If your issue is:
|
|
||||||
- a general 'how-to' type question,
|
|
||||||
- a feature request or suggestion for a change,
|
|
||||||
- or problems with 3rd party (`node-red-contrib-`) nodes
|
|
||||||
|
|
||||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
|
||||||
|
|
||||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
|
|
||||||
|
|
||||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
|
|
||||||
|
|
||||||
To help us understand the issue, please fill-in as much of the following information as you can:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Current Behavior
|
|
||||||
description: A clear & concise description of what you're experiencing.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Expected Behavior
|
|
||||||
description: A clear & concise description of what you expected to happen.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Steps To Reproduce
|
|
||||||
description: Steps to reproduce the behavior.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Example flow
|
|
||||||
description: If you have a minimal example flow that demonstrates the issue, share it here.
|
|
||||||
value: |
|
|
||||||
```
|
|
||||||
paste your flow here
|
|
||||||
```
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Environment
|
|
||||||
description: Please tell us about your environment. Include any relevant information on how you are running Node-RED.
|
|
||||||
value: |
|
|
||||||
- Node-RED version:
|
|
||||||
- Node.js version:
|
|
||||||
- npm version:
|
|
||||||
- Platform/OS:
|
|
||||||
- Browser:
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +0,0 @@
|
|||||||
blank_issues_enabled: true
|
|
||||||
contact_links:
|
|
||||||
- name: ❓ Questions
|
|
||||||
url: https://discourse.nodered.org
|
|
||||||
about: Ask your question on the Node-RED forum
|
|
||||||
- name: ⭐️ Feature Request
|
|
||||||
url: https://discourse.nodered.org/c/development/feature-requests
|
|
||||||
about: Discuss your request with the community
|
|
||||||
- name: 🗂 Documentation
|
|
||||||
url: https://nodered.org/docs
|
|
||||||
about: Go straight to the documentation
|
|
||||||
- name: 💬 Slack
|
|
||||||
url: https://nodered.org/slack
|
|
||||||
about: Chat about the project on our slack team
|
|
||||||
34
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,34 +0,0 @@
|
|||||||
<!--
|
|
||||||
## Before you hit that Submit button....
|
|
||||||
|
|
||||||
Please read our [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
|
|
||||||
before submitting a pull-request.
|
|
||||||
|
|
||||||
## Types of changes
|
|
||||||
|
|
||||||
What types of changes does your code introduce?
|
|
||||||
Put an `x` in the boxes that apply
|
|
||||||
-->
|
|
||||||
|
|
||||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If you want to raise a pull-request with a new feature, or a refactoring
|
|
||||||
of existing code, it **may well get rejected** if it hasn't been discussed on
|
|
||||||
the [forum](https://discourse.nodered.org) or
|
|
||||||
[slack team](https://nodered.org/slack) first.
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Proposed changes
|
|
||||||
|
|
||||||
<!-- Describe the nature of this change. What problem does it address? -->
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
<!-- 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 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
|
|
||||||
29
.github/scripts/update-node-red-docker.js
vendored
@@ -1,29 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
const newVersion = require("../../package.json").version;
|
|
||||||
|
|
||||||
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
|
|
||||||
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
|
|
||||||
console.log(`Not updating for a non-stable release - ${newVersion}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentVersion = require("../../../node-red-docker/package.json").version;
|
|
||||||
|
|
||||||
console.log(`Update from ${currentVersion} to ${newVersion}`)
|
|
||||||
|
|
||||||
updateFile(__dirname+"/../../../node-red-docker/package.json", currentVersion, newVersion);
|
|
||||||
updateFile(__dirname+"/../../../node-red-docker/docker-custom/package.json", currentVersion, newVersion);
|
|
||||||
updateFile(__dirname+"/../../../node-red-docker/README.md", currentVersion, newVersion);
|
|
||||||
|
|
||||||
console.log(`::set-env name=newVersion::${newVersion}`);
|
|
||||||
|
|
||||||
function updateFile(path,from,to) {
|
|
||||||
let contents = fs.readFileSync(path,"utf8");
|
|
||||||
contents = contents.replace(new RegExp(from.replace(/\./g,"\\."),"g"), to);
|
|
||||||
fs.writeFileSync(path, contents);
|
|
||||||
}
|
|
||||||
18
.github/scripts/update-node-red-website.js
vendored
@@ -1,18 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
const newVersion = require("../../package.json").version;
|
|
||||||
|
|
||||||
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
|
|
||||||
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
|
|
||||||
console.log(`Not updating for a non-stable release - ${newVersion}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = __dirname+"/../../../node-red.github.io/index.html";
|
|
||||||
let contents = fs.readFileSync(path, "utf8");
|
|
||||||
contents = contents.replace(/<span class="node-red-latest-version">v\d+\.\d+\.\d+<\/span>/, `<span class="node-red-latest-version">v${newVersion}<\/span>` );
|
|
||||||
fs.writeFileSync(path, contents);
|
|
||||||
62
.github/workflows/release.yml
vendored
@@ -1,62 +0,0 @@
|
|||||||
name: Publish Release
|
|
||||||
env:
|
|
||||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
generate:
|
|
||||||
name: 'Update node-red-docker image'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out node-red repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
path: 'node-red'
|
|
||||||
- name: Check out node-red-docker repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: 'node-red/node-red-docker'
|
|
||||||
path: 'node-red-docker'
|
|
||||||
- name: Check out node-red.github.io repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: 'node-red/node-red.github.io'
|
|
||||||
path: 'node-red.github.io'
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: '16'
|
|
||||||
- run: node ./node-red/.github/scripts/update-node-red-docker.js
|
|
||||||
- name: Create Docker Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v2
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
|
||||||
committer: GitHub <noreply@github.com>
|
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
|
||||||
path: 'node-red-docker'
|
|
||||||
commit-message: 'Bump to ${{ env.newVersion }}'
|
|
||||||
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
|
|
||||||
body: |
|
|
||||||
Updates the Node-RED Docker repo for the ${{ env.newVersion }} release.
|
|
||||||
|
|
||||||
Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`.
|
|
||||||
|
|
||||||
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
|
|
||||||
- run: node ./node-red/.github/scripts/update-node-red-website.js
|
|
||||||
- name: Create Website Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v2
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
|
||||||
committer: GitHub <noreply@github.com>
|
|
||||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
|
||||||
path: 'node-red.github.io'
|
|
||||||
commit-message: 'Bump to ${{ env.newVersion }}'
|
|
||||||
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
|
|
||||||
body: |
|
|
||||||
Updates the Node-RED Website repo for the ${{ env.newVersion }} release.
|
|
||||||
|
|
||||||
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
|
|
||||||
36
.github/workflows/tests.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
name: Run tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master, dev ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ master, dev ]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
permissions:
|
|
||||||
checks: write # for coverallsapp/github-action to create new checks
|
|
||||||
contents: read # for actions/checkout to fetch code
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14, 16]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm install
|
|
||||||
- name: Run tests
|
|
||||||
run: |
|
|
||||||
npm run test
|
|
||||||
- name: Publish to coveralls.io
|
|
||||||
if: ${{ matrix.node-version == 14 }}
|
|
||||||
uses: coverallsapp/github-action@v1.1.2
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
10
.gitignore
vendored
@@ -7,9 +7,7 @@
|
|||||||
.sessions.json
|
.sessions.json
|
||||||
.settings
|
.settings
|
||||||
.tern-project
|
.tern-project
|
||||||
.i18n-editor-metadata
|
|
||||||
*.backup
|
*.backup
|
||||||
*.bak
|
|
||||||
*_cred*
|
*_cred*
|
||||||
coverage
|
coverage
|
||||||
credentials.json
|
credentials.json
|
||||||
@@ -19,11 +17,3 @@ node_modules
|
|||||||
public
|
public
|
||||||
locales/zz-ZZ
|
locales/zz-ZZ
|
||||||
nodes/core/locales/zz-ZZ
|
nodes/core/locales/zz-ZZ
|
||||||
!packages/node_modules
|
|
||||||
packages/node_modules/@node-red/editor-client/public
|
|
||||||
!test/**/node_modules
|
|
||||||
docs
|
|
||||||
!packages/node_modules/**/docs
|
|
||||||
.vscode
|
|
||||||
.nyc_output
|
|
||||||
sync.ffs_db
|
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
"asi": true, // allow missing semicolons
|
"asi": true, // allow missing semicolons
|
||||||
"curly": true, // require braces
|
"curly": true, // require braces
|
||||||
"eqnull": true, // ignore ==null
|
"eqnull": true, // ignore ==null
|
||||||
//"eqeqeq": true, // enforce ===
|
|
||||||
"freeze": true, // don't allow override
|
|
||||||
"indent": 4, // default indent of 4
|
|
||||||
"forin": true, // require property filtering in "for in" loops
|
"forin": true, // require property filtering in "for in" loops
|
||||||
"immed": true, // require immediate functions to be wrapped in ( )
|
"immed": true, // require immediate functions to be wrapped in ( )
|
||||||
"nonbsp": true, // warn on unexpected whitespace breaking chars
|
"nonbsp": true, // warn on unexpected whitespace breaking chars
|
||||||
@@ -12,8 +9,6 @@
|
|||||||
//"unused": true, // Check for unused functions and variables
|
//"unused": true, // Check for unused functions and variables
|
||||||
"loopfunc": true, // allow functions to be defined in loops
|
"loopfunc": true, // allow functions to be defined in loops
|
||||||
//"expr": true, // allow ternery operator syntax...
|
//"expr": true, // allow ternery operator syntax...
|
||||||
"shadow": true, // allow variable shadowing (re-use of names...)
|
|
||||||
"sub": true, // don't warn that foo['bar'] should be written as foo.bar
|
"sub": true, // don't warn that foo['bar'] should be written as foo.bar
|
||||||
"proto": true, // allow setting of __proto__ in node < v0.12,
|
"proto": true // allow setting of __proto__ in node < v0.12
|
||||||
"esversion": 11 // allow es11(ES2020)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/Gruntfile.js
|
/Gruntfile.js
|
||||||
/.git/*
|
/.git/*
|
||||||
|
/lib/*
|
||||||
*.backup
|
*.backup
|
||||||
/public/*
|
/public/*
|
||||||
|
|||||||
7
.npmignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.settings
|
||||||
|
.jshintignore
|
||||||
|
.jshintrc
|
||||||
|
.project
|
||||||
|
.tern-project
|
||||||
|
.travis.yml
|
||||||
|
.git
|
||||||
24
.travis.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
sudo: false
|
||||||
|
language: node_js
|
||||||
|
env:
|
||||||
|
- CXX="g++-4.8"
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-4.8
|
||||||
|
- gcc-4.8
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- node_js: "7"
|
||||||
|
node_js:
|
||||||
|
- "7"
|
||||||
|
- "6"
|
||||||
|
- "4"
|
||||||
|
- "0.10"
|
||||||
|
script:
|
||||||
|
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage
|
||||||
|
before_script:
|
||||||
|
- npm install -g istanbul
|
||||||
|
- npm install coveralls
|
||||||
19
API.md
@@ -1,19 +0,0 @@
|
|||||||
Node-RED consists of 6 node modules under the `@node-red` scope, which are pulled together
|
|
||||||
by the top-level `node-red` module. The typical scenario is where you are embedding Node-RED into your
|
|
||||||
own application, in which case you would use the `node-red` module rather than any of the
|
|
||||||
internal modules directly.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let RED = require("node-red");
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Module | Description
|
|
||||||
-------|-------
|
|
||||||
[node-red](node-red.html) | the main module that pulls together all of the internal modules and provides the executable version of Node-RED
|
|
||||||
[@node-red/editor-api](@node-red_editor-api.html) | an Express application that serves the Node-RED editor and provides the Admin HTTP API
|
|
||||||
[@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED
|
|
||||||
[@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules
|
|
||||||
[@node-red/registry](@node-red_registry.html) | the internal node registry
|
|
||||||
@node-red/nodes | the default set of core nodes. This module only contains the Node-RED nodes - it does not expose any APIs.
|
|
||||||
@node-red/editor-client | the client-side resources of the Node-RED editor application
|
|
||||||
793
CHANGELOG.md
@@ -1,353 +1,498 @@
|
|||||||
#### 3.1.0-beta.1: Beta Release
|
#### 0.15.1: Maintenance Release
|
||||||
|
|
||||||
|
- Update default palette catalogue to use https
|
||||||
|
- Disable palette editor if npm not found - and fix for Windows
|
||||||
|
- Searching package catalogue should be case-insensitive Fixes #1010
|
||||||
|
- contenteditable fields not handled in config nodes Fixes #1011
|
||||||
|
- Change html link refs from `_new` to `_blank` to be standards compliant
|
||||||
|
|
||||||
Editor
|
#### 0.15.0: Milestone Release
|
||||||
|
|
||||||
- NEW: Locking Flows (#3938) @knolleary
|
|
||||||
- NEW: Improve UX around hiding flows via context menu (#3930) @knolleary
|
|
||||||
- NEW: Add support for inline image in markdown editor by drag and drop of an image file (#4006) @HiroyasuNishiyama
|
|
||||||
- NEW: Add support for mermaid diagram to markdown editor (#4007) @HiroyasuNishiyama
|
|
||||||
- NEW: Support uri fragments for nodes and groups including edit support (#3870) @knolleary
|
|
||||||
- NEW: Add global environment variable feature (#3941) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
- Remember compact/pretty flow export user choice (#3974) @Steve-Mcl
|
|
||||||
- fix .red-ui-notification class (#4035) @xiaobinqt
|
|
||||||
- Fix border radius on Modules list header (#4038) @bonanitech
|
|
||||||
- fix workspace reference error in case of empty tabs (#4029) @HiroyasuNishiyama
|
|
||||||
- Disable delete tab menu when single tab exists (#4030) @HiroyasuNishiyama
|
|
||||||
- Disable hide all menu if all tabs hidden (#4031) @HiroyasuNishiyama
|
|
||||||
- fix hide subflow tooltip (#4033) @HiroyasuNishiyama
|
|
||||||
- Fix disabled menu items in project feature (#4027) @kazuhitoyokoi
|
|
||||||
- Let themes change radialMenu text colors (#3995) @bonanitech
|
|
||||||
- Add Japanese translations for v3.0.3 (#4012) @kazuhitoyokoi
|
|
||||||
- Add Japanese translation for v3.1.0-beta.0 (#3997) @kazuhitoyokoi
|
|
||||||
- Add Japanese translation for v3.1.0-beta.0 (#3916) @kazuhitoyokoi
|
|
||||||
- Hide subflow category after deleting subflow (#3980) @kazuhitoyokoi
|
|
||||||
- Prevent dbl-click opening node edit dialog with text selected (#3970) @knolleary
|
|
||||||
- Handle replacing unknown node inside group or subflow (#3921) @knolleary
|
|
||||||
- Fix #3939, red border red-ui-typedInput-container (#3949) @Steveorevo
|
|
||||||
- i18n item URL copy notification & add Japanese message (#3946) @HiroyasuNishiyama
|
|
||||||
- add Japanese message for item url copy actions (#3947) @HiroyasuNishiyama
|
|
||||||
- Fix autocomplete entry for responseUrl (#3884) @knolleary
|
|
||||||
- Fix Japanese translation for JSONata editor (#3872) @HiroyasuNishiyama
|
|
||||||
- Fix search type with spaces (#3841) @Steve-Mcl
|
|
||||||
- Fix error hanndling of JSONata expression editor for extended functions (#3871) @HiroyasuNishiyama
|
|
||||||
- Add button type to the adding SSH key button (#3866) @kazuhitoyokoi
|
|
||||||
- Check radio button as default in project dialog (#3879) @kazuhitoyokoi
|
|
||||||
- Add $clone as supported function (#3874) @HiroyasuNishiyama
|
|
||||||
- Env var jsonata (#3807) @HiroyasuNishiyama
|
|
||||||
- Add Japanese translation for v3.0.2 (#3852) @kazuhitoyokoi
|
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Force IPv4 name resolution to have priority (#4019) @dceejay
|
- Increase default apiMaxLength to 5mb and add to default settings Closes #1001
|
||||||
- Fix async loading of modules containing both nodes and plugins (#3999) @knolleary
|
- Add v2 /flows api and deploy-overwrite protection
|
||||||
- Use main branch as default in project feature (#4036) @kazuhitoyokoi
|
- Encrypt credentials by default
|
||||||
- Rename package var to avoid strict mode error (#4020) @knolleary
|
- Ensure errors thrown by RED.events handlers don't percolate up
|
||||||
- Fix typos in settings.js (#4013) @ypid
|
|
||||||
- Ensure credentials object is removed before returning node in getFlow request (#3971) @knolleary
|
|
||||||
- Ignore commit error in project feature (#3987) @kazuhitoyokoi
|
|
||||||
- Update dependencies (#3969) @knolleary
|
|
||||||
- Add check that node sends object rather than primitive type (#3909) @knolleary
|
|
||||||
- Ensure key_path is quoted in GIT_SSH_COMMAND in case of spaces in pathname (#3912) @knolleary
|
|
||||||
- Fix nodesDir scan when node package has js/html in sub dir to package.json (#3867) @Steve-Mcl
|
|
||||||
- Fix file permissions (#3917) @kazuhitoyokoi
|
|
||||||
- ci: add minimum GitHub token permissions for workflows (#3907) @boahc077
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Catch: fix typo in catch.html (#3965) @we11adam
|
|
||||||
- Change: Fix change node overwriting msg with itself (#3899) @dceejay
|
|
||||||
- Comment node: Clarify where the text will appear (#4004) @dirkjanfaber
|
|
||||||
- CSV: change replace to replaceAll (#3990) @dceejay
|
|
||||||
- CSV node: check header properties for ' and " (#3920) @dceejay
|
|
||||||
- CSV: Fix for CSV undefined property (#3906) @dceejay
|
|
||||||
- Delay: let delay node handle both flush then reset (#3898) @dceejay
|
|
||||||
- Function: Limit number of ports in function node (#3886) @kazuhitoyokoi
|
|
||||||
- Function: Remove dot from variable name for external module in function node (#3880) @kazuhitoyokoi
|
|
||||||
- Function: add function node monaco types util and promisify (#3868) @Steve-Mcl
|
|
||||||
- HTTP In: Ensure msg.req.headers is enumerable (#3908) @knolleary
|
|
||||||
- HTTP Request: Support form-data arrays (#3991) @hardillb
|
|
||||||
- HTTP Request: Fix httprequest tests to be more lenient on error message (#3922) @knolleary
|
|
||||||
- HTTP Request: Add missing property to node object HTTPRequest (#3842) @hardillb
|
|
||||||
- HTTP Request/Response: Support sortable list on property UI of http request and http response nodes (#3857) @kazuhitoyokoi
|
|
||||||
- HTTP Response: Ensure statusCode is a number (#3894) @hardillb
|
|
||||||
- Inject: Allow Inject node to work with async context stores (#4021) @knolleary
|
|
||||||
- Join/Batch: Add count to join and batch node labels (#4028) @dceejay
|
|
||||||
- MQTT: Fix birth topic handling in MQTT node (#3905) @Steve-Mcl
|
|
||||||
- MQTT: Fix pull-down menus of MQTT configuration node (#3890) @kazuhitoyokoi
|
|
||||||
- MQTT: Prevent invalid mqtt birth topic crashing node-red (#3869) @Steve-Mcl
|
|
||||||
- MQTT: ensure sessionExpiry(Interval) is applied (#3840) @Steve-Mcl
|
|
||||||
- MQTT: Fix mqtt nodes not reconnecting on modified-flows deploy (#3992) @knolleary
|
|
||||||
- MQTT: fix single subscription mqtt node status (#3966) @Steve-Mcl
|
|
||||||
- Range: Add drop mode to range node (#3935) @dceejay
|
|
||||||
- Remove done from describe (#3873) @HiroyasuNishiyama
|
|
||||||
- Split node: avoid duplicate done call for buffer split (#4000) @knolleary
|
|
||||||
- Status: Fix typo in 25-status.html (#3981) @kazuhitoyokoi
|
|
||||||
- TCP Node: ensure newline substitution applies to whole message (#4009) @dceejay
|
|
||||||
- Template: Add information about environment variable to template node (#3882) @kazuhitoyokoi
|
|
||||||
- Trigger: Hide trigger node repeat send option if sending nothing (#4023) @dceejay
|
|
||||||
- Watch: fix watch node test on MacOS/ARM (#3942) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
#### 3.0.2: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Fix workspace chart bottom property (#3812) @bonanitech
|
- Mark nodes as changed when they are moved
|
||||||
- Update german translation (#3802) @Dennis14e
|
- Added parent containment option for draggable. (#1006)
|
||||||
- Support color reset to the default in subflow and group (#3801) @kazuhitoyokoi
|
- Ignore bidi event handling on non-existent and non-Input elements Closes #999
|
||||||
- Allow generateNodeNames to handle names containing regex control chars (#3817) @knolleary
|
- Remove list of flows from menu
|
||||||
- Hide scrollbars until they're needed (#3808) @bonanitech
|
- Allow nodes to be imported with their credentials
|
||||||
- Include junctions/groups when exporting subflows plus related fixes (#3816) @knolleary
|
- Add workspace search option
|
||||||
- remove console.log (#3820) @Steve-Mcl
|
- Add scrollOnAdd option to editableList
|
||||||
|
- Add swift markup to editor for open whisk node
|
||||||
|
- Scrollable tabs 👍
|
||||||
|
- Allow linking to individual flow via url hash
|
||||||
|
- Avoid duplicating existing subflows on import
|
||||||
|
- Add import-to-new-tab option
|
||||||
|
- Add new options to export-nodes dialog
|
||||||
|
- Stop nodes being added beyond the outer bounds of the workspace
|
||||||
|
- Default config nodes to global scope unless in a subflow Closes #972
|
||||||
|
- Bidi support for Text Direction and Structured Text (#961)
|
||||||
|
- Fix jQuery selector, selecting more than one help pane/popover and displaying incorrectly. (#970)
|
||||||
|
- Fixes removeItem not passing row data to callback. (#965)
|
||||||
|
- Move common components and add searchBox
|
||||||
|
- Add initial palette sidebar
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Inject node label - show topic for timestamp mode if short
|
||||||
|
- Let change node set type if total match
|
||||||
|
- Clean up status on close for several core nodes.
|
||||||
|
- Change node: re-parse JSON set value each time to avoid pass-by-ref
|
||||||
|
- Better handle HTTP Request header capitalisation
|
||||||
|
- Enable ES6 parsing in Function editor by default Fixes #985
|
||||||
|
- Update debug sidebar to use RED.view.reveal to show debug nodes
|
||||||
|
- Add full path tip to file node, And tidy up Pi node tips
|
||||||
|
- Remove WebSocket node maxlistener warning
|
||||||
|
- Update mqtt-broker node to use fully name-space qualified status messages
|
||||||
|
- Let UDP node better share same port instance if required
|
||||||
|
- Add number of units to the delay node (rate) (#994)
|
||||||
|
- Allow http middleware to skip rawBodyParser
|
||||||
|
- Let change node move property to sub-property.
|
||||||
|
- Add info to exec warning about buffered output if using python
|
||||||
|
- TCP node: pass on latest input msg properties
|
||||||
|
- Make sure MQTT broker is really set
|
||||||
|
- Fix escape character catch in TCPGet + support 0x?? sequences
|
||||||
|
- Fix split character in TCP Request node
|
||||||
|
- Add CSS highlighting to the template node (#950)
|
||||||
|
- Only update switch previous value after all rules are run
|
||||||
|
|
||||||
|
Other
|
||||||
|
|
||||||
|
- Add npm build/test scripts Closes #946 #660
|
||||||
|
- Move travis to node 6 and 7 - drop 5 and 0.12
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.14.6: Maintenance Release
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
|
||||||
|
- Tell ace about Function node globals. Closes #927
|
||||||
|
- Tidy up mqtt nodes - linting and done handling. Closes #935
|
||||||
|
- Fix invalid html in TCP and HTML node edit templates
|
||||||
|
- Add proper help text to link nodes
|
||||||
|
- Handle importing old mqtt-broker configs that lack properties
|
||||||
|
- Update ace to 1.2.4
|
||||||
|
- Allow config nodes to provide a sort function for their select list
|
||||||
|
- Add log warning if node module required version cannot be satisfied
|
||||||
|
- Handle empty credentials file. Closes #937
|
||||||
|
- Add RPi.GPIO lib test for ArchLinux
|
||||||
|
|
||||||
|
#### 0.14.5: Maintenance Release
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
|
||||||
|
- Cannot clear cookies with http nodes
|
||||||
|
- let HTML parse node allow msg.select set select
|
||||||
|
- Validate nodes on import after any references have been remapped
|
||||||
|
- Debug node handles objects without constructor property Fixes #933
|
||||||
|
- Ensure 'false' property values are displayed in info panel Fixes #940
|
||||||
|
- Fix node enable/disable over restart - load configs after settings init
|
||||||
|
|
||||||
|
#### 0.14.4: Maintenance Release
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Update trigger node ui to use typedInputs
|
||||||
|
- Better handling of quotes in CSV node
|
||||||
|
- Clarify the MQTT node sends msg.payload - closes #929
|
||||||
|
- Inject node should reuse the message it is triggered with Closes #914
|
||||||
|
- Stop trigger node re-using old message
|
||||||
|
- Allow node.status text to be 'falsey' values
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
|
||||||
|
- Handle DOMException when embedded in an iframe of different origin Fixes #932
|
||||||
|
- Fix double firing of menu actions
|
||||||
|
- Fix select box handling in Safari - fixes #928
|
||||||
|
- Clear context in node test helper Fixes #858
|
||||||
|
- Allow node properties to be same as existing object functions Fixes #880
|
||||||
|
- Handle comms link closing whilst completing the initial connect
|
||||||
|
- Protect against node type names that clash with Object property names Fixes #917
|
||||||
|
- Clone default node properties to avoid reference leakage
|
||||||
|
- Strip tab node definition when exporting
|
||||||
|
- Check for null config properties in editor before over-writing them
|
||||||
|
- Add hasUsers flag to config nodes
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Add sql mode to ace editor
|
||||||
|
- Keyboard shortcuts dialog update (#923)
|
||||||
|
- Ensure importing link nodes to a subflow doesn't add outbound links Fixes #921
|
||||||
|
- Add updateConfigNodeUsers function to editor
|
||||||
|
- Scroll to bottom when item added to editableList
|
||||||
|
- Form input widths behave more consistently when resizing Fixes #919 #920
|
||||||
|
|
||||||
|
#### 0.14.3: Maintenance Release
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
|
||||||
|
- Create default setting.js in user-specified directory. Fixes #908
|
||||||
|
- MQTT In subscription qos not defaulting properly
|
||||||
|
- Let exec node handle 0 as well as "0"
|
||||||
|
|
||||||
|
#### 0.14.2: Maintenance Release
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
|
||||||
|
- Cannot add new twitter credentials. Fixes #913
|
||||||
|
- Support array references in Debug property field
|
||||||
|
|
||||||
|
#### 0.14.1: Maintenance Release
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
|
||||||
|
- Handle undefined property that led to missing wires in the editor
|
||||||
|
- Remove duplicate 'Delete' entry in keyboard shortcut window. Closes #911
|
||||||
|
- Add 'exec' to node-red-pi launch script. Closes #910
|
||||||
|
|
||||||
|
#### 0.14.0: Milestone Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Replace edit dialog with edit tray
|
||||||
|
- Enable shift-drag detach of just the selected link
|
||||||
|
- Allow workspace tabs to be re-ordered
|
||||||
|
- Scope keyboard shortcuts to dom elements
|
||||||
|
- Ensure parent nodes marked as changed due to child config node changes
|
||||||
|
- Validate all edit dialog inputs when one changes
|
||||||
|
- Add editableList widget and update Switch/Change nodes to use it
|
||||||
|
- Add option to filter Debug sidebar by flow and highlight subflow-emitting nodes
|
||||||
|
- Back off comms reconnect attempts after prolonged failures
|
||||||
|
- Prompt for login if comms reconnect fails authentication
|
||||||
|
- Change style of nodes in subflow template view
|
||||||
|
- Add CHANGELOG.md and make it accessible from menu
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Register subflow module instance node with parent flow (#3818) @knolleary
|
- Always log node warnings on start without requiring -v
|
||||||
|
- Add support for loading scoped node modules. Closes #885
|
||||||
|
- Add process.env.PORT to settings.js
|
||||||
|
- Clear node context on deploy. Closes #870
|
||||||
|
- Enable finer grained permissions in adminAuth
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
- HTTP Request: Allow HTTP Headers not in spec (#3776) @hardillb
|
- Enable config nodes to reference other config nodes
|
||||||
|
- Add Split/Join nodes
|
||||||
#### 3.0.1: Maintenance Release
|
- Add Link nodes
|
||||||
|
- Add support to HTTP In node for PATCH requests. Closes #904
|
||||||
Editor
|
- Add cookie handling to HTTP In and HTTP Response nodes
|
||||||
|
- Add repeat indicator to inject node label. Closes #887
|
||||||
- Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js (#3794) @Steve-Mcl
|
- Add javascript highlighter to template node
|
||||||
- Sys info (diagnostics report) amendments (#3793) @Steve-Mcl
|
- Add optional timeout to exec node
|
||||||
- Allow `mode` and `title` to be omitted in `options` argument for `createEditor` (#3791) @Steve-Mcl
|
- Add TLS node and update MQTT/HTTP nodes to use it
|
||||||
- Fix focus issues (#3789) @Steve-Mcl
|
- Let trigger node also send last payload to arrive
|
||||||
- Ensure all typedInput buttons have button type set (#3788) @knolleary
|
- Add timestamp as a default typedInput and update Inject and change nodes to match,
|
||||||
- Do not flag hasUsers=false nodes as unused in search (#3787) @knolleary
|
- Add QoS option to MQTT In node
|
||||||
- Properly position quick-add dialog in all cases (#3786) @knolleary
|
- Add status to exec spawn mode
|
||||||
- Ensure quick-add dialog does not obscure ghost node when shifted (#3785) @knolleary
|
- Add Move capability to Change node
|
||||||
- Remove use of Object.hasOwn (#3784) @knolleary
|
- Update Serial node to support custom baud rates
|
||||||
|
- Add support for array-syntax in typedInput msg properties
|
||||||
#### 3.0.0: Milestone Release
|
- Add RED.util to Function node sandbox
|
||||||
|
- Capture error stack on node.error. Closes #879
|
||||||
Editor
|
|
||||||
|
|
||||||
- Use theme page and header values if settings.js values are not present (#3767) @Steve-Mcl
|
|
||||||
- Focus editor for undo after some actions in menu (#3759) @kazuhitoyokoi
|
|
||||||
- Ensure node icon shade has properly rounded corners (#3763) @knolleary
|
|
||||||
- Fix storing subflow credential type when input has multiple types (#3762) @knolleary
|
|
||||||
- Ensure global-config and flow-config have info in the hierarchy popover (#3752) @Steve-Mcl
|
|
||||||
- Include dirty state in history event (#3748) @Steve-Mcl
|
|
||||||
- Fix display direction of context sub-menu (#3746) @knolleary
|
|
||||||
- Fix clear pinned paths of debug sidebar menu (#3745) @HiroyasuNishiyama
|
|
||||||
- prevent exception generating tooltip for deleted nodes (#3742) @Steve-Mcl
|
|
||||||
- Fix context menu issues ready for v3 beta.5 (#3741) @Steve-Mcl
|
|
||||||
- Do not generate new node-ids when pasting a cut flow (#3729) @knolleary
|
|
||||||
- Fix to prevent node from moving out of workspace (#3731) @HiroyasuNishiyama
|
|
||||||
- Don't let themes change disabled config node background color (#3736) @bonanitech
|
|
||||||
- Move colors left behind in #3692 to CSS variables (#3737) @bonanitech
|
|
||||||
- Fix handling of global debug message (#3733) @HiroyasuNishiyama
|
|
||||||
- Fix label overflow @ config-node palette (#3730) @ralphwetzel
|
|
||||||
- Fix defaulting to monaco if settings does not contain codeEditor (#3732) @knolleary
|
|
||||||
- Disable keyboard shortcut mapping when showing Edit[..]Dialog (#3700) @ralphwetzel
|
|
||||||
- Update add-junction menu to work in more cases (#3727) @knolleary
|
|
||||||
- Ensure importMap is not null when using import UI (#3723) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0-beta.4 (#3724) @kazuhitoyokoi
|
|
||||||
- Fix "split with" on virtual links (#3766) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Do not remove unknown credentials of Subflow Modules (#3728) @knolleary
|
|
||||||
- Add missing entries from beta.4 changelog (#3721) @knolleary
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Change: Fix change node, not handling from field properly when using context (#3754) @Fadoli
|
|
||||||
- Link Call: Fix linkcall registry bugs (#3751) @Steve-Mcl
|
|
||||||
- WebSocket: Fix close timeout of websocket node (#3734) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
#### 3.0.0-beta.4: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Move all colours to CSS variables (#3692) @bonanitech
|
|
||||||
- Fix clicking on node in workspace to hide context menu (#3696) @knolleary
|
|
||||||
- Fix credential type input item of subflow template (#3703) @HiroyasuNishiyama
|
|
||||||
- Add option flag `reimport` to `importNodes` (#3718) @Steve-Mcl
|
|
||||||
- Update german translation (#3691) @Dennis14e
|
|
||||||
- List welcome tours in help sidebar (#3717) @knolleary
|
|
||||||
- Ensure 'hidden flow' count doesn't include subflows (#3715) @knolleary
|
|
||||||
- Fix Chinese translate (#3706) @hotlong
|
|
||||||
- Fix use default button for node icon (#3714) @kazuhitoyokoi
|
|
||||||
- Fix select boxes vertical alignment (#3698) @bonanitech
|
|
||||||
- Ensure workspace clean after undoing dropped node (#3708) @Steve-Mcl
|
|
||||||
- Use solid colour as config node icon background to hide text overflow (#3710) @Steve-Mcl
|
|
||||||
- Increase quick-add height to reveal 2 most recent entries (#3711) @Steve-Mcl
|
|
||||||
- Set default editor to monaco in absence of user preference (#3702) @knolleary
|
|
||||||
- Add Japanese translations for v3.0-beta.3 (#3688) @kazuhitoyokoi
|
|
||||||
- Fix handling of spacebar inside JSON visual editor (#3687) @knolleary
|
|
||||||
- Fix menu padding to handle both icons and submenus (#3686) @knolleary
|
|
||||||
- Include scroll offset when positioning quick-add dialog (#3685) @knolleary
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- Allow flows to be stopped and started manually (#3719) @knolleary
|
|
||||||
- Import default export if node is a transpiled es module (#3669) @dschmidt
|
|
||||||
- Leave Monaco theme commented out by default (#3704) @bonanitech
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- CSV: Fix CSV node to handle when outputting text fields (#3716) @dceejay
|
|
||||||
- Delay: Fix delay rate limit last timing when empty (#3709) @dceejay
|
|
||||||
- Link: Ensure link-call cache is updated when link-in is modified (#3695) @Steve-Mcl
|
|
||||||
- Join: Join node in reduce mode doesn't keep existing msg properties (#3670) @dceejay
|
|
||||||
- Template: Add support for evalulating {{env.<var>}} within a template node (#3690) @cow0w
|
|
||||||
|
|
||||||
#### 3.0.0-beta.3: Beta Release
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Add Right-Click content menu (#3678) @knolleary
|
|
||||||
- Fix disable junction (#3671) @HiroyasuNishiyama
|
|
||||||
- Add Japanese translations for v2.2.3 (#3672) @kazuhitoyokoi
|
|
||||||
- Reset mouse state when switching tabs (#3643) @knolleary
|
|
||||||
- Fix uncorrect fix of junction to subflow conversion (#3666) @HiroyasuNishiyama
|
|
||||||
- Fix undoing junction to subflow (#3653) @HiroyasuNishiyama
|
|
||||||
- Fix conversion of junction to subflow (#3652) @HiroyasuNishiyama
|
|
||||||
- Fix to include junction to exported nodes (#3650) @HiroyasuNishiyama
|
|
||||||
- Fix z-index value for shade to cover nodes in palette (#3649) @kazuhitoyokoi
|
|
||||||
- Fix to extend escaped subflow category characters (#3647) @HiroyasuNishiyama
|
|
||||||
- Fix to sanitize tab name (#3646) @HiroyasuNishiyama
|
|
||||||
- Fix selector placement (#3644) @bonanitech
|
|
||||||
- Add Japanese translations for v3.0-beta.2 (#3622) @kazuhitoyokoi
|
|
||||||
- Fix new folder menu of save to library dialog (#3633) @HiroyasuNishiyama
|
|
||||||
- Fix layer of palette node (#3638) @HiroyasuNishiyama
|
|
||||||
- Fix to place a node dragged from palette within the workspace (#3637) @HiroyasuNishiyama
|
|
||||||
- Fix typo in CSS (#3628) @bonanitech
|
|
||||||
- Use the correct variable for the gutter text color (#3615) @bonanitech
|
|
||||||
|
|
||||||
|
|
||||||
Runtime
|
Fixes
|
||||||
|
|
||||||
- Support loading node modules from `nodesdir` (#3676) @Steve-Mcl
|
- Add error handling to all node definition api calls
|
||||||
- fix buffer parse error message of evaluateNodeProperty (#3624) @HiroyasuNishiyama
|
- Handle null return from Function node in array of messages
|
||||||
|
- Defer loading of token sessions until they are accessed. Fixes #895
|
||||||
Nodes
|
- set pi gpio pin status correctly if set on start
|
||||||
|
- Prevent parent window scrolling when view is focused. Fixes #635
|
||||||
- File: Further simplify file node filename entry UX (v3) (#3677) @Steve-Mcl
|
- Handle missing tab nodes in a loaded flow config
|
||||||
- Function: Fix initial cursor position of init/finalize tab of function node (#3674) @HiroyasuNishiyama
|
- Ensure typedInput dropdown doesn't fall off the page
|
||||||
- Function: Fix ESM module loading in Function node (#3645) @knolleary
|
- Protect against node types with reserved names such as toString. Fixes #880
|
||||||
- Inject: Fix JSONata evaluation of inject button (#3632) @HiroyasuNishiyama
|
- Do not rely on the HTML file to identify where nodes are registered from
|
||||||
- TCP: Dont delete TCP socket twice (#3630) @Steve-Mcl
|
- Preserve node properties on import
|
||||||
- MQTT Node: define noproxy variable (#3626) @Steve-Mcl
|
- Fix regression in delay node. topic based queue was emptying all the time instead of spreading out messages.
|
||||||
- Debug: i18n debug sidebar node label (#3623) @HiroyasuNishiyama
|
- Throw an error if a Function node adds an input event listener
|
||||||
|
- Fix hang on partial deploy with disconnected mqtt node
|
||||||
#### 3.0.0-beta.2: Beta Release
|
- TypedInput: preload type icons to ensure width calc correct
|
||||||
|
- Ensure tcp node creates a buffer of size 1 at least
|
||||||
**Migration from 2.x**
|
- Return editorTheme default if value is undefined
|
||||||
|
- Fix RED.util.compareObjects for Function created objects and Buffers
|
||||||
- The 'slice wires' action has changed from Ctrl-RightMouseButton to Alt-LeftMouseButton
|
- Ensure default settings copied to command-line specified userDir
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Rework Junctions to be more node like in their event handling (#3607) @knolleary
|
|
||||||
- Change slicing / slice-junction operations over to mouse button 0 (Left Mouse Button) (#3609) @Steve-Mcl
|
|
||||||
- Do not slice-junction link node wires (#3608) @knolleary
|
|
||||||
- Handle many-to-one slicing of wires (#3604) @knolleary
|
|
||||||
- Ensure ACE worker options are set (#3611) @Steve-Mcl
|
|
||||||
- Remove duplicate history add of ungroup event (#3605) @knolleary
|
|
||||||
- use text width instead of number of characters for deciding select fi… (#3603) @HiroyasuNishiyama
|
|
||||||
- Update Japanese info of link call node reflecting update of English info (#3600) @HiroyasuNishiyama
|
|
||||||
- Fix typedInput label not visible on themes (#3580) @bonanitech
|
|
||||||
- Fix project switching when junctions are present (#3595) @Steve-Mcl
|
|
||||||
- Fix junction: when wiring from a regular nodes INPUT, backwards to a junction (#3591) @Steve-Mcl
|
|
||||||
- Fix error initialising flow tab editor (#3585) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0-beta.1 (#3576) @kazuhitoyokoi
|
|
||||||
- Fix image paths where `red/image/typedInput/XXXX.png` should be `red/image/typedInput/XXXX.svg` (#3592) @kazuhitoyokoi
|
|
||||||
- Fix browser console error Uncaught TypeError when searching certain terms (#3584) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- fix error on system-info action (#3589) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- I18n switch rule selector (#3602) @HiroyasuNishiyama
|
|
||||||
- Handle removal of event handlers to allow mqtt client.end() to work (#3594) @PhilDay-CT
|
|
||||||
- update link-call node info according to current behavior (#3597) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
|
|
||||||
#### 3.0.0-beta.1: Beta Release
|
#### 0.13.4: Maintenance Release
|
||||||
|
|
||||||
**Migration from 2.x**
|
- Add timed release mode to delay node
|
||||||
|
- Enable link splicing for when import_dragging nodes. Closes #811
|
||||||
- Node-RED now requires Node.js 14.x or later.
|
- Fix uncaught exception on deploy whilst node sending messages
|
||||||
- New installs of Node-RED will default to the monaco editor.
|
- Deprecate old mqtt client and connection pool modules
|
||||||
|
- Change node: add bool/num types to change mode Closes #835
|
||||||
|
- Validate fields that are `$(env-vars)` Closes #825
|
||||||
|
- Handle missing config nodes when validating node properties
|
||||||
|
- Pi node - don't try to send data if closing
|
||||||
|
- Load node message catalog when added dynamically
|
||||||
|
- Split palette labels on spaces and hyphens when laying out
|
||||||
|
- Warn if editor routes are accessed but runtime not started Closes #816
|
||||||
|
- Better handling of zero-length flow files Closes #819
|
||||||
|
- Allow runtime calls to RED._ to specify other namespace
|
||||||
|
- Better right alignment of numerics in delay and trigger nodes
|
||||||
|
- Allow node modules to include example flows
|
||||||
|
- Create node_modules in userDir
|
||||||
|
- Ensure errors in node def functions don't break view rendering Fixes #815
|
||||||
|
- Updated Inject node info with instructions for flow and global options
|
||||||
|
|
||||||
|
|
||||||
Editor
|
|
||||||
|
|
||||||
- Add Junctions (#3462) @knolleary
|
#### 0.13.3: Maintenance Release
|
||||||
- Allow node name to be auto-generated when added (#3478, #3538) @knolleary
|
|
||||||
- Set monaco as default code editor as of v3.x (#3543) @Steve-Mcl
|
|
||||||
- Update Monaco to V0.33.0 (#3522) @Steve-Mcl
|
|
||||||
- Auto-complete Improvements (#3521) @Steve-Mcl
|
|
||||||
- Add a tooltip to debug sidebar messages to reveal full path to node (#3503) @knolleary
|
|
||||||
- Fix down arrow triggering menu in search box (#3507) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0 (#3512) @kazuhitoyokoi
|
|
||||||
- Add feature: Continuous search tools (search previous, search next) (#3405) @Steve-Mcl
|
|
||||||
- Add feature: split-wire-to-links (#3399, #3476) @Steve-Mcl
|
|
||||||
- Add copy button to node properties tables (#3390) @knolleary
|
|
||||||
- Add info-tab search options dropdown to the regular search (#3395) @Steve-Mcl
|
|
||||||
- New Feature: Add ability to find modified nodes/flows. (#3392) @Steve-Mcl
|
|
||||||
- Code editor ux improvements around remembering state of each code editor in a flow (#3553) @Steve-Mcl
|
|
||||||
- Make it easier to apply themes on SVG icons (#3515) @bonanitech
|
|
||||||
- Add support of property validation message (#3438) @HiroyasuNishiyama
|
|
||||||
- Ensure node validation tooltip is closed when field becomes valid (#3570) @knolleary
|
|
||||||
- Add "search for" buttons to notifications (#3567) @Steve-Mcl
|
|
||||||
- Don't let themes change node config colors (#3564) @bonanitech
|
|
||||||
- Fix gap between typedInput containers borders (#3560) @bonanitech
|
|
||||||
- Fix recording removed links in edit history (#3547) @knolleary
|
|
||||||
- Remove unused SASS vars (#3536) @bonanitech
|
|
||||||
- Add custom style for jQuery widgets borders (#3537) @bonanitech
|
|
||||||
- fix out of scope reference of hasUnusedConfig variable (#3535) @HiroyasuNishiyama
|
|
||||||
- correct "non string" check parenthesis (#3524) @Steve-Mcl
|
|
||||||
- Ensure i18n of scoped package name (#3516) @Steve-Mcl
|
|
||||||
- Prevent shortcut deploy when deploy button shaded (#3517) @Steve-Mcl
|
|
||||||
- Fix: Sidebar "Configuration" filter button tooltip (#3500) @ralphwetzel
|
|
||||||
- Add the ability to customize diff colors even more (#3499) @bonanitech
|
|
||||||
- Do JSON comparison of old value/new value in editor (#3481) @Steve-Mcl
|
|
||||||
- Fix nodes losing their wires when in an iframe (#3484) @zettca
|
|
||||||
- Improve scroll into view (#3468) @Steve-Mcl
|
|
||||||
- Do not show 1st tab if hidden when loading (#3464) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
- Fix crash on repeated inject of invalid json payload
|
||||||
|
- Add binary mode to tail node
|
||||||
|
- Revert Cheerio to somewhat smaller version
|
||||||
|
- Add os/platform info to default debug
|
||||||
|
|
||||||
- Fix importing external module from node-red module (#3541) @knolleary
|
|
||||||
- Add support for multiple static paths with optional static root (#3542) @Steve-Mcl
|
|
||||||
- Store external token when authenticating if provided (#3460) @ArFe
|
|
||||||
- Support OAuth/OpenID logout (#3388) @mw75
|
|
||||||
- Allow adminAuth to auto-login users when using passport strategy (#3519) @knolleary
|
|
||||||
- Add runtime diagnostics admin endpoint (#3511) @Steve-Mcl
|
|
||||||
- Don't start if user has no home directory (#3540) @hardillb
|
|
||||||
- Error on invalid encrypted credentials (#3498) @sammachin
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- Debug: Add message count option to Debug status (#3544 #3551) @rafaelmuynarsk @knolleary
|
#### 0.13.2: Maintenance Release
|
||||||
- File: Change basic Filename field to a typedInput (#3533) @Steve-Mcl
|
|
||||||
- HTTP Request: Add UI for Http Request node headers (#3488) @Steve-Mcl
|
|
||||||
- Inject: let inject optionally fire at start in only at time mode. (#3385) @dceejay
|
|
||||||
- Link Call: Dynamic link call (#3463) @Steve-Mcl
|
|
||||||
- Link Call: Display link targets of nodes in a regular flow, for Link Call nodes inside a subflow (#3528) @Steve-Mcl
|
|
||||||
- MQTT: MQTT payload auto parsing improvements (#3530) @Steve-Mcl
|
|
||||||
- MQTT: Add client and Runtime MQTT topic validation (#3563) @Steve-Mcl [dev]
|
|
||||||
- MQTT: save and restore v5 config user props (#3562) @Steve-Mcl
|
|
||||||
- MQTT: Fix incorrect MQTT status (#3552) @Steve-Mcl
|
|
||||||
- MQTT: fix reference error of msg.status in debug node (#3526) @HiroyasuNishiyama
|
|
||||||
- MQTT: Add unit tests for MQTT nodes (#3497) @Steve-Mcl
|
|
||||||
- MQTT: fix typo of will properties (#3502) @Steve-Mcl
|
|
||||||
- MQTT: ensure mqtt v5 props can be set false (#3472) @Steve-Mcl
|
|
||||||
- Switch: add check for NaN in is of type number to be false (#3409) @dceejay
|
|
||||||
- TCP: TCP node better split (#3465) @dceejay
|
|
||||||
- Watch: Update Watch node to use node-watch module (#3559 #3569) @knolleary
|
|
||||||
- WebSocket: call done after ws disconnects (#3531) @Steve-Mcl
|
|
||||||
|
|
||||||
#### Older Releases
|
- Don't force reconnect mqtt client if message arrives (fixes the MQTT connect/disconnect endless cycle)
|
||||||
|
- Add -p/--port option to override listening port
|
||||||
|
- Invert config node filter toggle button colours so state is more obvious
|
||||||
|
- Add timeout to httprequest node
|
||||||
|
- Tidy up of all node info content - make style consistent
|
||||||
|
- Make jquery spinner element css consistent with other inputs
|
||||||
|
- tcp node add reply (to all) capability
|
||||||
|
- Allow the template node to be treated as plain text
|
||||||
|
- Validate MQTT In topics Fixes #792
|
||||||
|
- httpNodeAuth should not block http options requests Fixes #793
|
||||||
|
- Disable perMessageDeflate on WS servers - fixes 'zlib binding closed' error
|
||||||
|
- Clear trigger status icon on re-deploy
|
||||||
|
- Don't default inject payload to blank string
|
||||||
|
- Trigger node, add configurable reset
|
||||||
|
- Allow function properties in settings Fixes #790 - fixes use of httpNodeMiddleware
|
||||||
|
- Fix order of config dialog calls to save/creds/validate
|
||||||
|
- Add debounce to Pi GPIO node
|
||||||
|
|
||||||
Change logs for older releases are available on GitHub: https://github.com/node-red/node-red/releases
|
|
||||||
|
|
||||||
|
#### 0.13.1: Maintenance Release
|
||||||
|
|
||||||
|
- Revert wrapping of http request object
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.13.0: Milestone Release
|
||||||
|
|
||||||
|
- Add 'previous value' option to Switch node
|
||||||
|
- Allow existing nodes to splice into links on drag
|
||||||
|
- CORS not properly configured on multiple http routes Fixes #783
|
||||||
|
- Restore shift-drag to snap/unsnap to grid
|
||||||
|
- Moving nodes with keyboard should flag workspace dirty
|
||||||
|
- Notifications flagged as fixed should not be click-closable
|
||||||
|
- Rework config sidebar and deploy warning
|
||||||
|
- Wrap http request object to match http response object
|
||||||
|
- Add 'view' menu and reorganise a few things
|
||||||
|
- Allow shift-click to detach existing wires
|
||||||
|
- Splice nodes dragged from palette into links
|
||||||
|
- try to trim imported/dragged flows to [ ]
|
||||||
|
- Move version number as title of NR logo
|
||||||
|
- Moving nodes mark workspace as dirty
|
||||||
|
- Ok/Cancel edit dialogs with Ctrl-Enter/Escape
|
||||||
|
- Handle OSX Meta key when selecting nodes
|
||||||
|
- Add grid-alignment options
|
||||||
|
- Add oneditresize function definition
|
||||||
|
- Rename propertySelect to typedInput and add boolean opt
|
||||||
|
- Add propertySelect to switch node
|
||||||
|
- Add propertySelect support to Change node
|
||||||
|
- Add context/flow/global support to Function node
|
||||||
|
- Add node context/flow/global
|
||||||
|
- Add propertySelect jquery widget
|
||||||
|
- Add add/update/delete flow apis
|
||||||
|
- Allow core nodes dir to be provided to runtime via settings
|
||||||
|
- Tidy up API passed to node modules
|
||||||
|
- Move locale files under api/runtime components
|
||||||
|
- Add flow reload admin api
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.12.5: Maintenance Release
|
||||||
|
|
||||||
|
- Add attribute capability to HTML parser node
|
||||||
|
- Add Pi Keyboard code node
|
||||||
|
- Fix for MQTT client connection cycling on partial deploy
|
||||||
|
- Fix for tcp node properly closing connections
|
||||||
|
- Update sentiment node dependencies
|
||||||
|
- Fix for file node handling of UTF8 extended characters
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.12.4: Maintenance Release
|
||||||
|
|
||||||
|
- Add readOnly setting to prevent file writes in localfilesystem storage
|
||||||
|
- Support bcrypt for httpNodeAuth
|
||||||
|
- Pi no longer needs root workaround to access gpio
|
||||||
|
- Fix: Input File node will not retain the file name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.12.3: Maintenance Release
|
||||||
|
|
||||||
|
- Fixes for TCP Get node reconnect handling
|
||||||
|
- Clear delay node status on re-deploy
|
||||||
|
- Update Font-Awesome to v4.5
|
||||||
|
- Fix trigger to block properly until reset
|
||||||
|
- Update example auth properties in settings.js
|
||||||
|
- Ensure httpNodeAuth doesn't get applied to admin routes
|
||||||
|
- TCP Get node not passing on existing msg properties
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.12.2: Maintenance Release
|
||||||
|
|
||||||
|
- Enable touch-menu for links so they can be deleted
|
||||||
|
- Allow nodes to be installed by path name
|
||||||
|
- Fix basic authentication on httpNode/Admin/Static
|
||||||
|
- Handle errors thrown in Function node setTimeout/Interval
|
||||||
|
- Fix mqtt node lifecycle with partial deployments
|
||||||
|
- Update tcp node status on reconnect after timeout
|
||||||
|
- Debug node not handling null messages
|
||||||
|
- Kill processes run with exec node when flows redeployed
|
||||||
|
- Inject time spinner incrementing value incorrectly
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.12.1: Maintenance Release
|
||||||
|
|
||||||
|
- Enable touch-menu for links so they can be deleted
|
||||||
|
- Allow nodes to be installed by path name
|
||||||
|
- Fix basic authentication on httpNode/Admin/Static
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.12.0: Milestone Release
|
||||||
|
|
||||||
|
- Change/Switch rules now resize with dialog width
|
||||||
|
- Support for node 4.x
|
||||||
|
- Move to Express 4.x
|
||||||
|
- Copy default settings file to user dir on start up
|
||||||
|
- Config nodes can be scoped to a particular subflow/tab
|
||||||
|
- Comms link tolerates <5 second breaks in connection before notifying user
|
||||||
|
- MQTT node overhaul - add will/tls/birth message support
|
||||||
|
- Status node - to report status events from other nodes
|
||||||
|
- Error node can be targeted to specific other nodes
|
||||||
|
- JSON node can encode Array types
|
||||||
|
- Switch node regular expression rule can now be set to be case-insensitive
|
||||||
|
- HTTP In node can accept non-UTF8 payloads - will return a Buffer when appropriate
|
||||||
|
- Exec node configuration consistent regardless of the spawn option
|
||||||
|
- Function node can now display status icon/text
|
||||||
|
- CSV node can now handle arrays
|
||||||
|
- setInterval/clearInterval add to Function node
|
||||||
|
- Function node automatically clears all timers (setInterval/setTimeout) when the node is stopped
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.11.2: Maintenance Release
|
||||||
|
|
||||||
|
- Allow XML parser options be set on the message
|
||||||
|
- Add 'mobile' category to the palette (no core nodes included)
|
||||||
|
- Allow a message catalog provide a partial translation
|
||||||
|
- Fix HTTP Node nls message id
|
||||||
|
- Remove delay spinner upper limit
|
||||||
|
- Update debug node output to include length of payload
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.11.1: Maintenance Release
|
||||||
|
|
||||||
|
- Fix exclusive config node check when type not registered (prevented HTTP In node from being editable unless the swagger node was also installed)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.11.0: Milestone Release
|
||||||
|
|
||||||
|
- Add Node 0.12 support
|
||||||
|
- Internationalization support
|
||||||
|
- Editor UI refresh
|
||||||
|
- Add RBE node
|
||||||
|
- File node optionally creates path to file
|
||||||
|
- Function node can access `clearTimeout`
|
||||||
|
- Fix: Unable to login with 'read' permission
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.10.10: Maintenance Release
|
||||||
|
|
||||||
|
- Fix permissions issue with packaged nrgpio script
|
||||||
|
- Add better help message if deprecated node missing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.10.9: Maintenance Release
|
||||||
|
|
||||||
|
Fix packaging of bin scripts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.10.8: Maintenance Release
|
||||||
|
|
||||||
|
- Nodes moved out of core
|
||||||
|
- still included as a dependency: twitter, serial, email, feedparser
|
||||||
|
- no longer included: mongo, arduino, irc, redis
|
||||||
|
- node icon defn can be a function
|
||||||
|
- http_proxy support
|
||||||
|
- httpNodeMiddleware setting
|
||||||
|
- Trigger node ui refresh
|
||||||
|
- editorTheme setting
|
||||||
|
- Warn on deploy of unused config nodes
|
||||||
|
- catch node prevents error loops
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.10.6: Maintenance Release
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Performance improvements in editor
|
||||||
|
- Palette appearance update
|
||||||
|
- Warn on navigation with undeployed changes
|
||||||
|
- Disable undeployed node action buttons
|
||||||
|
- Disable subflow node action buttons
|
||||||
|
- Add Catch node
|
||||||
|
- Add logging functions to Function node
|
||||||
|
- Add send function to Function node
|
||||||
|
- Update Change node to support multiple rules
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.10.4: Maintenance Release
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- http request node passes on request url as msg.url
|
||||||
|
- handle config nodes appearing out of order in flow file - don't assume they are always at the start
|
||||||
|
- move subflow palette category to the top, to make it more obvious
|
||||||
|
- fix labelling of Raspberry Pi pins
|
||||||
|
- allow email node to mark mail as read
|
||||||
|
- fix saving library content
|
||||||
|
- add node-red and node-red-pi start scripts
|
||||||
|
- use $HOME/.node-red for user data unless specified otherwise (or existing data is found in install dir)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.10.3: Maintenance Release
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- httpAdminAuth was too aggressively deprecated (ie removed); restoring with a console warning when used
|
||||||
|
- adds reporting of node.js version on start-up
|
||||||
|
- mongo node skip/limit options can be strings or numbers
|
||||||
|
- CSV parser passes through provided message object
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.10.2: Maintenance Release
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- subflow info sidebar more useful
|
||||||
|
- adds missing font-awesome file
|
||||||
|
- inject node day selection defaulted to invalid selection
|
||||||
|
- loading a flow with no tabs failed to add nodes to default tab
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ We welcome contributions, but request you follow these guidelines.
|
|||||||
|
|
||||||
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
||||||
By participating, you are expected to uphold this code. Please report unacceptable
|
By participating, you are expected to uphold this code. Please report unacceptable
|
||||||
behavior to the project's core team at team@nodered.org.
|
behavior to any of the [project's core team](https://github.com/orgs/node-red/teams/core).
|
||||||
|
|
||||||
## Raising issues
|
## Raising issues
|
||||||
|
|
||||||
@@ -26,30 +26,33 @@ relevant nodes, press Ctrl-E and copy the flow data from the Export dialog.
|
|||||||
At a minimum, please include:
|
At a minimum, please include:
|
||||||
|
|
||||||
- Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly.
|
- Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly.
|
||||||
- Version of Node.js - what does `node -v` say?
|
- Version of node.js - what does `node -v` say?
|
||||||
|
|
||||||
## Feature requests
|
## Feature requests
|
||||||
|
|
||||||
For feature requests, please raise them on the [forum](https://discourse.nodered.org).
|
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red).
|
||||||
|
|
||||||
## Pull-Requests
|
## Pull-Requests
|
||||||
|
|
||||||
If you want to raise a pull-request with a new feature, or a refactoring
|
If you want to raise a pull-request with a new feature, or a refactoring
|
||||||
of existing code, it may well get rejected if you haven't discussed it on
|
of existing code, it may well get rejected if you haven't discussed it on
|
||||||
the [forum](https://discourse.nodered.org) first.
|
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
|
||||||
|
|
||||||
All contributors need to sign the OpenJS Foundation's Contributor License Agreement.
|
### Contributor License Agreement
|
||||||
It is an online process and quick to do. If you raise a pull-request without
|
|
||||||
having signed the CLA, you will be prompted to do so automatically.
|
|
||||||
|
|
||||||
|
In order for us to accept pull-requests, the contributor must first complete
|
||||||
|
a Contributor License Agreement (CLA). This clarifies the intellectual
|
||||||
|
property license granted with any contribution. It is for your protection as a
|
||||||
|
Contributor as well as the protection of IBM and its customers; it does not
|
||||||
|
change your rights to use your own Contributions for any other purpose.
|
||||||
|
|
||||||
### Code Branches
|
You can download the CLAs here:
|
||||||
|
|
||||||
When raising a PR for a fix or a new feature, it is important to target the right branch.
|
- [individual](http://nodered.org/cla/node-red-cla-individual.pdf)
|
||||||
|
- [corporate](http://nodered.org/cla/node-red-cla-corporate.pdf)
|
||||||
|
|
||||||
- `master` - this is the main branch for the latest stable release of Node-RED. All bug fixes for that release should target this branch.
|
If you are an IBMer, please contact us directly as the contribution process is
|
||||||
- `v1.x` - this is the maintenance branch for the 1.x stream. If a fix *only* applies to 1.x, then it should target this branch. If it applies to the current stable release as well, target `master` first. We will then decide if it needs to be back ported to the 1.x stream.
|
slightly different.
|
||||||
- `dev` - this is the branch for new feature development targeting the next milestone release.
|
|
||||||
|
|
||||||
### Coding standards
|
### Coding standards
|
||||||
|
|
||||||
|
|||||||
613
Gruntfile.js
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
* Copyright 2013, 2016 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -15,35 +15,17 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var fs = require("fs-extra");
|
|
||||||
var sass = require("sass");
|
|
||||||
|
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
var nodemonArgs = ["-V"];
|
var nodemonArgs = ["-v"];
|
||||||
var flowFile = grunt.option('flowFile');
|
var flowFile = grunt.option('flowFile');
|
||||||
if (flowFile) {
|
if (flowFile) {
|
||||||
nodemonArgs.push(flowFile);
|
nodemonArgs.push(flowFile);
|
||||||
process.env.NODE_RED_ENABLE_PROJECTS=false;
|
|
||||||
}
|
|
||||||
var userDir = grunt.option('userDir');
|
|
||||||
if (userDir) {
|
|
||||||
nodemonArgs.push("-u");
|
|
||||||
nodemonArgs.push(userDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var browserstack = grunt.option('browserstack');
|
|
||||||
if (browserstack) {
|
|
||||||
process.env.BROWSERSTACK = true;
|
|
||||||
}
|
|
||||||
var nonHeadless = grunt.option('non-headless');
|
|
||||||
if (nonHeadless) {
|
|
||||||
process.env.NODE_RED_NON_HEADLESS = true;
|
|
||||||
}
|
|
||||||
const pkg = grunt.file.readJSON('package.json');
|
|
||||||
process.env.NODE_RED_PACKAGE_VERSION = pkg.version;
|
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
pkg: pkg,
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
paths: {
|
paths: {
|
||||||
dist: ".dist"
|
dist: ".dist"
|
||||||
},
|
},
|
||||||
@@ -55,29 +37,10 @@ module.exports = function(grunt) {
|
|||||||
ui: 'bdd',
|
ui: 'bdd',
|
||||||
reporter: 'spec'
|
reporter: 'spec'
|
||||||
},
|
},
|
||||||
all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] },
|
all: { src: ['test/**/*_spec.js'] },
|
||||||
core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]},
|
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
|
||||||
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||||
},
|
},
|
||||||
webdriver: {
|
|
||||||
all: {
|
|
||||||
configFile: 'test/editor/wdio.conf.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nyc: {
|
|
||||||
options: {
|
|
||||||
cwd: '.',
|
|
||||||
include: ['packages/node_modules/**'],
|
|
||||||
excludeNodeModules: false,
|
|
||||||
exclude: ['packages/node_modules/@node-red/editor-client/**'],
|
|
||||||
reporter: ['lcov', 'html','text-summary'],
|
|
||||||
reportDir: 'coverage',
|
|
||||||
all: true
|
|
||||||
},
|
|
||||||
all: { cmd: false, args: ['grunt', 'simplemocha:all'] },
|
|
||||||
core: { options: { exclude:['packages/node_modules/@node-red/editor-client/**', 'packages/node_modules/@node-red/nodes/**']},cmd: false, args: ['grunt', 'simplemocha:core'] },
|
|
||||||
nodes: { cmd: false, args: ['grunt', 'simplemocha:nodes'] }
|
|
||||||
},
|
|
||||||
jshint: {
|
jshint: {
|
||||||
options: {
|
options: {
|
||||||
jshintrc:true
|
jshintrc:true
|
||||||
@@ -92,20 +55,22 @@ module.exports = function(grunt) {
|
|||||||
//"loopfunc": true, // allow functions to be defined in loops
|
//"loopfunc": true, // allow functions to be defined in loops
|
||||||
//"sub": true // don't warn that foo['bar'] should be written as foo.bar
|
//"sub": true // don't warn that foo['bar'] should be written as foo.bar
|
||||||
},
|
},
|
||||||
// all: [
|
all: [
|
||||||
// 'Gruntfile.js',
|
'Gruntfile.js',
|
||||||
// 'red.js',
|
'red.js',
|
||||||
// 'packages/**/*.js'
|
'red/**/*.js',
|
||||||
// ],
|
'nodes/core/*/*.js',
|
||||||
// core: {
|
'editor/js/**/*.js'
|
||||||
// files: {
|
],
|
||||||
// src: [
|
core: {
|
||||||
// 'Gruntfile.js',
|
files: {
|
||||||
// 'red.js',
|
src: [
|
||||||
// 'packages/**/*.js',
|
'Gruntfile.js',
|
||||||
// ]
|
'red.js',
|
||||||
// }
|
'red/**/*.js'
|
||||||
// },
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
files: {
|
files: {
|
||||||
src: [ 'nodes/core/*/*.js' ]
|
src: [ 'nodes/core/*/*.js' ]
|
||||||
@@ -113,7 +78,7 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
editor: {
|
editor: {
|
||||||
files: {
|
files: {
|
||||||
src: [ 'packages/node_modules/@node-red/editor-client/src/js/**/*.js' ]
|
src: [ 'editor/js/**/*.js' ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tests: {
|
tests: {
|
||||||
@@ -133,111 +98,60 @@ module.exports = function(grunt) {
|
|||||||
src: [
|
src: [
|
||||||
// Ensure editor source files are concatenated in
|
// Ensure editor source files are concatenated in
|
||||||
// the right order
|
// the right order
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/polyfills.js",
|
"editor/js/main.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js",
|
"editor/js/events.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/red.js",
|
"editor/js/i18n.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/events.js",
|
"editor/js/settings.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/hooks.js",
|
"editor/js/user.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/i18n.js",
|
"editor/js/comms.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/settings.js",
|
"editor/js/bidi/bidi.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
"editor/js/bidi/base-text-dir.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
"editor/js/bidi/format.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
|
"editor/js/bidi/numeric-shaping.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
"editor/js/ui/state.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
"editor/js/nodes.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
"editor/js/history.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/plugins.js",
|
"editor/js/validators.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/nodes.js",
|
"editor/js/ui/common/editableList.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
|
"editor/js/ui/common/menu.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/history.js",
|
"editor/js/ui/common/popover.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/validators.js",
|
"editor/js/ui/common/searchBox.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js",
|
"editor/js/ui/common/tabs.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
|
"editor/js/ui/common/typedInput.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
|
"editor/js/ui/deploy.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
|
"editor/js/ui/keyboard.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/checkboxSet.js",
|
"editor/js/ui/workspaces.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js",
|
"editor/js/ui/view.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/panels.js",
|
"editor/js/ui/sidebar.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js",
|
"editor/js/ui/palette.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js",
|
"editor/js/ui/tab-info.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js",
|
"editor/js/ui/tab-config.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
|
"editor/js/ui/palette-editor.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
|
"editor/js/ui/editor.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js",
|
"editor/js/ui/tray.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js",
|
"editor/js/ui/clipboard.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
|
"editor/js/ui/library.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
|
"editor/js/ui/notifications.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/diagnostics.js",
|
"editor/js/ui/search.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
|
"editor/js/ui/subflow.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
|
"editor/js/ui/touch/radialMenu.js"
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/palette.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/*.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/*.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/library.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/search.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js",
|
|
||||||
"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",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
|
|
||||||
],
|
],
|
||||||
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
dest: "public/red/red.js"
|
||||||
},
|
},
|
||||||
vendor: {
|
vendor: {
|
||||||
files: {
|
files: {
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [
|
"public/vendor/vendor.js": [
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
|
"editor/vendor/jquery/js/jquery-1.11.3.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
|
"editor/vendor/bootstrap/js/bootstrap.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
|
"editor/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
||||||
"node_modules/marked/marked.min.js",
|
"editor/vendor/marked/marked.min.js",
|
||||||
"node_modules/dompurify/dist/purify.min.js",
|
"editor/vendor/d3/d3.v3.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
"editor/vendor/i18next/i18next.min.js"
|
||||||
"node_modules/i18next/i18next.min.js",
|
|
||||||
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
|
|
||||||
"node_modules/jquery-i18next/jquery-i18next.min.js",
|
|
||||||
"node_modules/jsonata/jsonata-es5.min.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
|
|
||||||
],
|
],
|
||||||
// "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
|
"public/vendor/vendor.css": [
|
||||||
// // TODO: resolve relative resource paths in
|
// TODO: resolve relative resource paths in
|
||||||
// // bootstrap/FA/jquery
|
// bootstrap/FA/jquery
|
||||||
// ],
|
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
|
|
||||||
"node_modules/jsonata/jsonata-es5.min.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
|
|
||||||
],
|
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js": [
|
|
||||||
"node_modules/mermaid/dist/mermaid.min.js"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,62 +159,54 @@ module.exports = function(grunt) {
|
|||||||
uglify: {
|
uglify: {
|
||||||
build: {
|
build: {
|
||||||
files: {
|
files: {
|
||||||
'packages/node_modules/@node-red/editor-client/public/red/red.min.js': 'packages/node_modules/@node-red/editor-client/public/red/red.js',
|
'public/red/red.min.js': 'public/red/red.js'
|
||||||
'packages/node_modules/@node-red/editor-client/public/red/main.min.js': 'packages/node_modules/@node-red/editor-client/public/red/main.js',
|
|
||||||
'packages/node_modules/@node-red/editor-client/public/vendor/ace/mode-jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js',
|
|
||||||
'packages/node_modules/@node-red/editor-client/public/vendor/ace/snippets/jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/snippets-jsonata.js'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sass: {
|
sass: {
|
||||||
build: {
|
build: {
|
||||||
options: {
|
options: {
|
||||||
implementation: sass,
|
|
||||||
outputStyle: 'compressed'
|
outputStyle: 'compressed'
|
||||||
},
|
},
|
||||||
files: [{
|
files: [{
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/style.min.css',
|
dest: 'public/red/style.min.css',
|
||||||
src: 'packages/node_modules/@node-red/editor-client/src/sass/style.scss'
|
src: 'editor/sass/style.scss'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dest: 'public/vendor/bootstrap/css/bootstrap.min.css',
|
||||||
|
src: 'editor/vendor/bootstrap/css/bootstrap.css'
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
jsonlint: {
|
jsonlint: {
|
||||||
messages: {
|
messages: {
|
||||||
src: [
|
src: [
|
||||||
'packages/node_modules/@node-red/nodes/locales/**/*.json',
|
'nodes/core/locales/en-US/messages.json',
|
||||||
'packages/node_modules/@node-red/editor-client/locales/**/*.json',
|
'red/api/locales/en-US/editor.json',
|
||||||
'packages/node_modules/@node-red/runtime/locales/**/*.json'
|
'red/runtime/locales/en-US/runtime.json'
|
||||||
]
|
|
||||||
},
|
|
||||||
keymaps: {
|
|
||||||
src: [
|
|
||||||
'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
attachCopyright: {
|
attachCopyright: {
|
||||||
js: {
|
js: {
|
||||||
src: [
|
src: [
|
||||||
'packages/node_modules/@node-red/editor-client/public/red/red.min.js',
|
'public/red/red.min.js'
|
||||||
'packages/node_modules/@node-red/editor-client/public/red/main.min.js'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
src: [
|
src: [
|
||||||
'packages/node_modules/@node-red/editor-client/public/red/style.min.css'
|
'public/red/style.min.css'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clean: {
|
clean: {
|
||||||
build: {
|
build: {
|
||||||
src: [
|
src: [
|
||||||
"packages/node_modules/@node-red/editor-client/public/red",
|
"public/red",
|
||||||
"packages/node_modules/@node-red/editor-client/public/index.html",
|
"public/index.html",
|
||||||
"packages/node_modules/@node-red/editor-client/public/favicon.ico",
|
"public/favicon.ico",
|
||||||
"packages/node_modules/@node-red/editor-client/public/icons",
|
"public/icons",
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor",
|
"public/vendor"
|
||||||
"packages/node_modules/@node-red/editor-client/public/types/node",
|
|
||||||
"packages/node_modules/@node-red/editor-client/public/types/node-red",
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
release: {
|
release: {
|
||||||
@@ -312,36 +218,24 @@ module.exports = function(grunt) {
|
|||||||
watch: {
|
watch: {
|
||||||
js: {
|
js: {
|
||||||
files: [
|
files: [
|
||||||
'packages/node_modules/@node-red/editor-client/src/js/**/*.js'
|
'editor/js/**/*.js'
|
||||||
],
|
],
|
||||||
tasks: ['copy:build','concat',/*'uglify',*/ 'attachCopyright:js']
|
tasks: ['concat','uglify','attachCopyright:js']
|
||||||
},
|
},
|
||||||
sass: {
|
sass: {
|
||||||
files: [
|
files: [
|
||||||
'packages/node_modules/@node-red/editor-client/src/sass/**/*.scss'
|
'editor/sass/**/*.scss'
|
||||||
],
|
],
|
||||||
tasks: ['sass','attachCopyright:css']
|
tasks: ['sass','attachCopyright:css']
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
files: [
|
files: [
|
||||||
'packages/node_modules/@node-red/nodes/locales/**/*.json',
|
'nodes/core/locales/en-US/messages.json',
|
||||||
'packages/node_modules/@node-red/editor-client/locales/**/*.json',
|
'red/api/locales/en-US/editor.json',
|
||||||
'packages/node_modules/@node-red/runtime/locales/**/*.json'
|
'red/runtime/locales/en-US/runtime.json'
|
||||||
],
|
],
|
||||||
tasks: ['jsonlint:messages']
|
tasks: ['jsonlint:messages']
|
||||||
},
|
},
|
||||||
keymaps: {
|
|
||||||
files: [
|
|
||||||
'packages/node_modules/@node-red/editor-client/src/js/keymap.json'
|
|
||||||
],
|
|
||||||
tasks: ['jsonlint:keymaps','copy:build']
|
|
||||||
},
|
|
||||||
tours: {
|
|
||||||
files: [
|
|
||||||
'packages/node_modules/@node-red/editor-client/src/tours/**/*.js'
|
|
||||||
],
|
|
||||||
tasks: ['copy:build']
|
|
||||||
},
|
|
||||||
misc: {
|
misc: {
|
||||||
files: [
|
files: [
|
||||||
'CHANGELOG.md'
|
'CHANGELOG.md'
|
||||||
@@ -353,13 +247,12 @@ module.exports = function(grunt) {
|
|||||||
nodemon: {
|
nodemon: {
|
||||||
/* uses .nodemonignore */
|
/* uses .nodemonignore */
|
||||||
dev: {
|
dev: {
|
||||||
script: 'packages/node_modules/node-red/red.js',
|
script: 'red.js',
|
||||||
options: {
|
options: {
|
||||||
args: nodemonArgs,
|
args: nodemonArgs,
|
||||||
ext: 'js,html,json',
|
ext: 'js,html,json',
|
||||||
watch: [
|
watch: [
|
||||||
'packages/node_modules',
|
'red','nodes'
|
||||||
'!packages/node_modules/@node-red/editor-client'
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,77 +269,62 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
copy: {
|
copy: {
|
||||||
build: {
|
build: {
|
||||||
files:[
|
files:[{
|
||||||
{
|
cwd: 'editor/images',
|
||||||
src: 'packages/node_modules/@node-red/editor-client/src/js/main.js',
|
src: '**',
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/main.js'
|
expand: true,
|
||||||
},
|
dest: 'public/red/images/'
|
||||||
{
|
},
|
||||||
src: 'packages/node_modules/@node-red/editor-client/src/js/keymap.json',
|
{
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/keymap.json'
|
cwd: 'editor/vendor',
|
||||||
},
|
src: [
|
||||||
{
|
'ace/**',
|
||||||
cwd: 'packages/node_modules/@node-red/editor-client/src/images',
|
//'bootstrap/css/**',
|
||||||
src: '**',
|
'bootstrap/img/**',
|
||||||
expand: true,
|
'jquery/css/**',
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/images/'
|
'font-awesome/**'
|
||||||
},
|
],
|
||||||
{
|
expand: true,
|
||||||
cwd: 'packages/node_modules/@node-red/editor-client/src/vendor',
|
dest: 'public/vendor/'
|
||||||
src: [
|
},
|
||||||
'ace/**',
|
{
|
||||||
'jquery/css/base/**',
|
cwd: 'editor/icons',
|
||||||
'font-awesome/**',
|
src: '**',
|
||||||
'monaco/dist/**',
|
expand: true,
|
||||||
'monaco/types/extraLibs.js',
|
dest: 'public/icons/'
|
||||||
'monaco/style.css',
|
},
|
||||||
'monaco/monaco-bootstrap.js'
|
{
|
||||||
],
|
expand: true,
|
||||||
expand: true,
|
src: ['editor/index.html','editor/favicon.ico'],
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/'
|
dest: 'public/',
|
||||||
},
|
flatten: true
|
||||||
{
|
},
|
||||||
cwd: 'packages/node_modules/@node-red/editor-client/src',
|
{
|
||||||
src: [
|
src: 'CHANGELOG.md',
|
||||||
'types/node/*.ts',
|
dest: 'public/red/about'
|
||||||
'types/node-red/*.ts',
|
}
|
||||||
],
|
|
||||||
expand: true,
|
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cwd: 'packages/node_modules/@node-red/editor-client/src/icons',
|
|
||||||
src: '**',
|
|
||||||
expand: true,
|
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/icons/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expand: true,
|
|
||||||
src: ['packages/node_modules/@node-red/editor-client/src/index.html','packages/node_modules/@node-red/editor-client/src/favicon.ico'],
|
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/',
|
|
||||||
flatten: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'CHANGELOG.md',
|
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/about'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'CHANGELOG.md',
|
|
||||||
dest: 'packages/node_modules/node-red/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cwd: 'packages/node_modules/@node-red/editor-client/src/ace/bin/',
|
|
||||||
src: '**',
|
|
||||||
expand: true,
|
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/ace/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cwd: 'packages/node_modules/@node-red/editor-client/src/tours',
|
|
||||||
src: '**',
|
|
||||||
expand: true,
|
|
||||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/tours/'
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
release: {
|
||||||
|
files: [{
|
||||||
|
mode: true,
|
||||||
|
expand: true,
|
||||||
|
src: [
|
||||||
|
'*.md',
|
||||||
|
'LICENSE',
|
||||||
|
'package.json',
|
||||||
|
'settings.js',
|
||||||
|
'red.js',
|
||||||
|
'lib/.gitignore',
|
||||||
|
'nodes/*.demo',
|
||||||
|
'nodes/core/**',
|
||||||
|
'red/**',
|
||||||
|
'public/**',
|
||||||
|
'editor/templates/**',
|
||||||
|
'bin/**'
|
||||||
|
],
|
||||||
|
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
chmod: {
|
chmod: {
|
||||||
@@ -454,96 +332,20 @@ module.exports = function(grunt) {
|
|||||||
mode: '755'
|
mode: '755'
|
||||||
},
|
},
|
||||||
release: {
|
release: {
|
||||||
|
// Target-specific file/dir lists and/or options go here.
|
||||||
src: [
|
src: [
|
||||||
"packages/node_modules/@node-red/nodes/core/hardware/nrgpio",
|
path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>/nodes/core/hardware/nrgpio*')
|
||||||
"packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-*sh"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'npm-command': {
|
|
||||||
options: {
|
|
||||||
cmd: "pack",
|
|
||||||
cwd: "<%= paths.dist %>/modules"
|
|
||||||
},
|
|
||||||
'node-red': { options: { args: [__dirname+'/packages/node_modules/node-red'] } },
|
|
||||||
'@node-red/editor-api': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-api'] } },
|
|
||||||
'@node-red/editor-client': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-client'] } },
|
|
||||||
'@node-red/nodes': { options: { args: [__dirname+'/packages/node_modules/@node-red/nodes'] } },
|
|
||||||
'@node-red/registry': { options: { args: [__dirname+'/packages/node_modules/@node-red/registry'] } },
|
|
||||||
'@node-red/runtime': { options: { args: [__dirname+'/packages/node_modules/@node-red/runtime'] } },
|
|
||||||
'@node-red/util': { options: { args: [__dirname+'/packages/node_modules/@node-red/util'] } }
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
mkdir: {
|
|
||||||
release: {
|
|
||||||
options: {
|
|
||||||
create: ['<%= paths.dist %>/modules']
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
compress: {
|
compress: {
|
||||||
release: {
|
release: {
|
||||||
options: {
|
options: {
|
||||||
archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip'
|
archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip'
|
||||||
},
|
},
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'packages/node_modules/',
|
cwd: '<%= paths.dist %>/',
|
||||||
src: [
|
src: ['node-red-<%= pkg.version %>/**']
|
||||||
'**',
|
|
||||||
'!@node-red/editor-client/src/**'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
jsdoc : {
|
|
||||||
modules: {
|
|
||||||
src: [
|
|
||||||
'API.md',
|
|
||||||
'packages/node_modules/node-red/lib/red.js',
|
|
||||||
'packages/node_modules/@node-red/runtime/lib/index.js',
|
|
||||||
'packages/node_modules/@node-red/runtime/lib/api/*.js',
|
|
||||||
'packages/node_modules/@node-red/runtime/lib/events.js',
|
|
||||||
'packages/node_modules/@node-red/runtime/lib/hooks.js',
|
|
||||||
'packages/node_modules/@node-red/util/**/*.js',
|
|
||||||
'packages/node_modules/@node-red/editor-api/lib/index.js',
|
|
||||||
'packages/node_modules/@node-red/editor-api/lib/auth/index.js',
|
|
||||||
'packages/node_modules/@node-red/registry/lib/index.js'
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
destination: 'docs',
|
|
||||||
configure: './jsdoc.json',
|
|
||||||
fred: "hi there"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_editor: {
|
|
||||||
src: [
|
|
||||||
'packages/node_modules/@node-red/editor-client/src/js'
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
destination: 'packages/node_modules/@node-red/editor-client/docs',
|
|
||||||
configure: './jsdoc.json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
jsdoc2md: {
|
|
||||||
runtimeAPI: {
|
|
||||||
options: {
|
|
||||||
separators: true
|
|
||||||
},
|
|
||||||
src: [
|
|
||||||
'packages/node_modules/@node-red/runtime/lib/index.js',
|
|
||||||
'packages/node_modules/@node-red/runtime/lib/api/*.js',
|
|
||||||
'packages/node_modules/@node-red/runtime/lib/events.js'
|
|
||||||
],
|
|
||||||
dest: 'packages/node_modules/@node-red/runtime/docs/api.md'
|
|
||||||
},
|
|
||||||
nodeREDUtil: {
|
|
||||||
options: {
|
|
||||||
separators: true
|
|
||||||
},
|
|
||||||
src: 'packages/node_modules/@node-red/util/**/*.js',
|
|
||||||
dest: 'packages/node_modules/@node-red/util/docs/api.md'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -556,42 +358,16 @@ module.exports = function(grunt) {
|
|||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-concurrent');
|
grunt.loadNpmTasks('grunt-concurrent');
|
||||||
grunt.loadNpmTasks('grunt-sass');
|
grunt.loadNpmTasks('grunt-sass');
|
||||||
|
grunt.loadNpmTasks('grunt-nodemon');
|
||||||
grunt.loadNpmTasks('grunt-contrib-compress');
|
grunt.loadNpmTasks('grunt-contrib-compress');
|
||||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||||
grunt.loadNpmTasks('grunt-chmod');
|
grunt.loadNpmTasks('grunt-chmod');
|
||||||
grunt.loadNpmTasks('grunt-jsonlint');
|
grunt.loadNpmTasks('grunt-jsonlint');
|
||||||
if (fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
|
|
||||||
grunt.loadNpmTasks('grunt-webdriver');
|
|
||||||
}
|
|
||||||
grunt.loadNpmTasks('grunt-jsdoc');
|
|
||||||
grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
|
|
||||||
grunt.loadNpmTasks('grunt-npm-command');
|
|
||||||
grunt.loadNpmTasks('grunt-mkdir');
|
|
||||||
grunt.loadNpmTasks('grunt-simple-nyc');
|
|
||||||
|
|
||||||
grunt.registerMultiTask('nodemon', 'Runs a nodemon monitor of your node.js server.', function () {
|
|
||||||
const nodemon = require('nodemon');
|
|
||||||
this.async();
|
|
||||||
const options = this.options();
|
|
||||||
options.script = this.data.script;
|
|
||||||
let callback;
|
|
||||||
if (options.callback) {
|
|
||||||
callback = options.callback;
|
|
||||||
delete options.callback;
|
|
||||||
} else {
|
|
||||||
callback = function(nodemonApp) {
|
|
||||||
nodemonApp.on('log', function (event) {
|
|
||||||
console.log(event.colour);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
callback(nodemon(options));
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.registerMultiTask('attachCopyright', function() {
|
grunt.registerMultiTask('attachCopyright', function() {
|
||||||
var files = this.data.src;
|
var files = this.data.src;
|
||||||
var copyright = "/**\n"+
|
var copyright = "/**\n"+
|
||||||
" * Copyright OpenJS Foundation and other contributors, https://openjsf.org/\n"+
|
" * Copyright 2013, 2015 IBM Corp.\n"+
|
||||||
" *\n"+
|
" *\n"+
|
||||||
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+
|
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+
|
||||||
" * you may not use this file except in compliance with the License.\n"+
|
" * you may not use this file except in compliance with the License.\n"+
|
||||||
@@ -607,7 +383,7 @@ module.exports = function(grunt) {
|
|||||||
" **/\n";
|
" **/\n";
|
||||||
|
|
||||||
if (files) {
|
if (files) {
|
||||||
for (var i=0; i<files.length; i++) {
|
for (var i=0;i<files.length;i++) {
|
||||||
var file = files[i];
|
var file = files[i];
|
||||||
if (!grunt.file.exists(file)) {
|
if (!grunt.file.exists(file)) {
|
||||||
grunt.log.warn('File '+ file + ' not found');
|
grunt.log.warn('File '+ file + ' not found');
|
||||||
@@ -628,38 +404,6 @@ module.exports = function(grunt) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
grunt.registerTask('verifyPackageDependencies', function() {
|
|
||||||
var done = this.async();
|
|
||||||
var verifyDependencies = require("./scripts/verify-package-dependencies.js");
|
|
||||||
verifyDependencies().then(function(failures) {
|
|
||||||
if (failures.length > 0) {
|
|
||||||
failures.forEach(f => grunt.log.error(f));
|
|
||||||
grunt.fail.fatal("Failed to verify package dependencies");
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.registerTask('verifyUiTestDependencies', function() {
|
|
||||||
if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
|
|
||||||
grunt.fail.fatal('You need to install the UI test dependencies first.\nUse the script in "scripts/install-ui-test-dependencies.sh"');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
grunt.registerTask('generatePublishScript',
|
|
||||||
'Generates a script to publish build output to npm',
|
|
||||||
function () {
|
|
||||||
const done = this.async();
|
|
||||||
const generatePublishScript = require("./scripts/generate-publish-script.js");
|
|
||||||
generatePublishScript().then(function(output) {
|
|
||||||
grunt.log.writeln(output);
|
|
||||||
|
|
||||||
const filePath = path.join(grunt.config.get('paths.dist'),"modules","publish.sh");
|
|
||||||
grunt.file.write(filePath,output);
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
grunt.registerTask('setDevEnv',
|
grunt.registerTask('setDevEnv',
|
||||||
'Sets NODE_ENV=development so non-minified assets are used',
|
'Sets NODE_ENV=development so non-minified assets are used',
|
||||||
function () {
|
function () {
|
||||||
@@ -668,42 +412,23 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
grunt.registerTask('default',
|
grunt.registerTask('default',
|
||||||
'Builds editor content then runs code style checks and unit tests on all components',
|
'Builds editor content then runs code style checks and unit tests on all components',
|
||||||
['build','verifyPackageDependencies','jshint:editor','nyc:all']);
|
['build','test-core','test-editor','test-nodes']);
|
||||||
|
|
||||||
grunt.registerTask('no-coverage',
|
|
||||||
'Builds editor content then runs code style checks and unit tests on all components without code coverage',
|
|
||||||
['build','verifyPackageDependencies','jshint:editor','simplemocha:all']);
|
|
||||||
|
|
||||||
|
|
||||||
grunt.registerTask('test-core',
|
grunt.registerTask('test-core',
|
||||||
'Runs code style check and unit tests on core runtime code',
|
'Runs code style check and unit tests on core runtime code',
|
||||||
['build','nyc:core']);
|
['jshint:core','simplemocha:core']);
|
||||||
|
|
||||||
grunt.registerTask('test-editor',
|
grunt.registerTask('test-editor',
|
||||||
'Runs code style check on editor code',
|
'Runs code style check on editor code',
|
||||||
['jshint:editor']);
|
['jshint:editor']);
|
||||||
|
|
||||||
if (!fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
|
|
||||||
grunt.registerTask('test-ui',
|
|
||||||
'Builds editor content then runs unit tests on editor ui',
|
|
||||||
['verifyUiTestDependencies']);
|
|
||||||
} else {
|
|
||||||
grunt.registerTask('test-ui',
|
|
||||||
'Builds editor content then runs unit tests on editor ui',
|
|
||||||
['verifyUiTestDependencies','build','jshint:editor','webdriver:all']);
|
|
||||||
}
|
|
||||||
|
|
||||||
grunt.registerTask('test-nodes',
|
grunt.registerTask('test-nodes',
|
||||||
'Runs unit tests on core nodes',
|
'Runs unit tests on core nodes',
|
||||||
['build','nyc:nodes']);
|
['simplemocha:nodes']);
|
||||||
|
|
||||||
grunt.registerTask('build',
|
grunt.registerTask('build',
|
||||||
'Builds editor content',
|
'Builds editor content',
|
||||||
['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']);
|
['clean:build','concat:build','concat:vendor','uglify:build','sass:build','jsonlint:messages','copy:build','attachCopyright']);
|
||||||
|
|
||||||
grunt.registerTask('build-dev',
|
|
||||||
'Developer mode: build dev version',
|
|
||||||
['clean:build','concat:build','concat:vendor','copy:build','sass:build','setDevEnv']);
|
|
||||||
|
|
||||||
grunt.registerTask('dev',
|
grunt.registerTask('dev',
|
||||||
'Developer mode: run node-red, watch for source changes and build/restart',
|
'Developer mode: run node-red, watch for source changes and build/restart',
|
||||||
@@ -711,18 +436,6 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
grunt.registerTask('release',
|
grunt.registerTask('release',
|
||||||
'Create distribution zip file',
|
'Create distribution zip file',
|
||||||
['build','verifyPackageDependencies','clean:release','mkdir:release','chmod:release','compress:release','pack-modules','generatePublishScript']);
|
['build','clean:release','copy:release','chmod:release','compress:release']);
|
||||||
|
|
||||||
grunt.registerTask('pack-modules',
|
|
||||||
'Create module pack files for release',
|
|
||||||
['mkdir:release','npm-command']);
|
|
||||||
|
|
||||||
|
|
||||||
grunt.registerTask('coverage',
|
|
||||||
'Run Istanbul code test coverage task',
|
|
||||||
['build','nyc:all']);
|
|
||||||
|
|
||||||
grunt.registerTask('docs',
|
|
||||||
'Generates API documentation',
|
|
||||||
['jsdoc']);
|
|
||||||
};
|
};
|
||||||
|
|||||||
1
LICENSE
@@ -1,4 +1,3 @@
|
|||||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
|
|||||||
28
README.md
@@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
http://nodered.org
|
http://nodered.org
|
||||||
|
|
||||||
[](https://travis-ci.org/node-red/node-red)
|
[](https://travis-ci.org/node-red/node-red)
|
||||||
[](https://coveralls.io/r/node-red/node-red?branch=master)
|
[](https://coveralls.io/r/node-red/node-red?branch=master)
|
||||||
|
|
||||||
Low-code programming for event-driven applications.
|
A visual tool for wiring the Internet of Things.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Check out http://nodered.org/docs/getting-started/ for full instructions on getting
|
Check out http://nodered.org/docs/getting-started/ for full instructions on getting
|
||||||
started.
|
started.
|
||||||
|
|
||||||
1. `sudo npm install -g --unsafe-perm node-red`
|
1. `sudo npm install -g node-red`
|
||||||
2. `node-red`
|
2. `node-red`
|
||||||
3. Open <http://localhost:1880>
|
3. Open <http://localhost:1880>
|
||||||
|
|
||||||
@@ -22,7 +22,8 @@ started.
|
|||||||
|
|
||||||
More documentation can be found [here](http://nodered.org/docs).
|
More documentation can be found [here](http://nodered.org/docs).
|
||||||
|
|
||||||
For further help, or general discussion, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
For further help, or general discussion, please use the
|
||||||
|
[mailing list](https://groups.google.com/forum/#!forum/node-red).
|
||||||
|
|
||||||
## Developers
|
## Developers
|
||||||
|
|
||||||
@@ -44,6 +45,9 @@ If you want to run the latest code from git, here's how to get started:
|
|||||||
4. Run
|
4. Run
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
|
or
|
||||||
|
|
||||||
|
node red.js
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -52,19 +56,17 @@ Before raising a pull-request, please read our
|
|||||||
|
|
||||||
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/).
|
||||||
By participating, you are expected to uphold this code. Please report unacceptable
|
By participating, you are expected to uphold this code. Please report unacceptable
|
||||||
behavior to any of the project's core team at team@nodered.org.
|
behavior to any of the [project's core team](https://github.com/orgs/node-red/teams/core).
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
Node-RED is a project of the [OpenJS Foundation](http://openjsf.org).
|
Node-RED is a creation of [IBM Emerging Technology](https://www.ibm.com/blogs/emerging-technology/).
|
||||||
|
|
||||||
It is maintained by:
|
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||||
|
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
|
||||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
|
||||||
* And many others...
|
|
||||||
|
|
||||||
|
For more open-source projects from IBM, head over [here](http://ibm.github.io).
|
||||||
|
|
||||||
## Copyright and license
|
## Copyright and license
|
||||||
|
|
||||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).
|
Copyright 2013, 2016 IBM Corp. under [the Apache 2.0 license](LICENSE).
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Please report any potential security issues to `team@nodered.org`. This will notify the core project team who will respond accordingly.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# Copyright JS Foundation and other contributors, http://js.foundation
|
# Copyright 2015 IBM Corp.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -29,16 +29,15 @@ do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Find the real location of this script
|
# Find the real location of this script
|
||||||
CURRENT_PATH=$(pwd)
|
CURRENT_PATH=`pwd`
|
||||||
SCRIPT_PATH=$(readlink -f "$0")
|
SCRIPT_PATH="${BASH_SOURCE[0]}";
|
||||||
while [ -h "${SCRIPT_PATH}" ]; do
|
while([ -h "${SCRIPT_PATH}" ]); do
|
||||||
cd "$(dirname "${SCRIPT_PATH}")" || exit 1
|
cd "`dirname "${SCRIPT_PATH}"`"
|
||||||
P=$(basename "${SCRIPT_PATH}")
|
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
|
||||||
SCRIPT_PATH=$(readlink "${P}")
|
|
||||||
done
|
done
|
||||||
cd "$(dirname "${SCRIPT_PATH}")" > /dev/null || exit 1
|
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
|
||||||
SCRIPT_PATH=$(pwd)
|
SCRIPT_PATH="`pwd`";
|
||||||
cd "$CURRENT_PATH" || exit 1
|
cd $CURRENT_PATH
|
||||||
|
|
||||||
# Run Node-RED
|
# Run Node-RED
|
||||||
exec /usr/bin/env node ${OPTIONS} ${SCRIPT_PATH}/../red.js ${ARGS}
|
exec /usr/bin/env node $OPTIONS $SCRIPT_PATH/../red.js $ARGS
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
editor/icons/alert.png
Normal file
|
After Width: | Height: | Size: 308 B |
|
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B |
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 393 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
editor/icons/bridge-dash.png
Normal file
|
After Width: | Height: | Size: 508 B |
BIN
editor/icons/bridge.png
Normal file
|
After Width: | Height: | Size: 575 B |
BIN
editor/icons/cog.png
Normal file
|
After Width: | Height: | Size: 493 B |
BIN
editor/icons/comment.png
Normal file
|
After Width: | Height: | Size: 601 B |
BIN
editor/icons/db.png
Normal file
|
After Width: | Height: | Size: 459 B |
BIN
editor/icons/debug.png
Normal file
|
After Width: | Height: | Size: 218 B |
BIN
editor/icons/envelope.png
Normal file
|
After Width: | Height: | Size: 324 B |
BIN
editor/icons/feed.png
Normal file
|
After Width: | Height: | Size: 378 B |
BIN
editor/icons/file.png
Normal file
|
After Width: | Height: | Size: 255 B |
BIN
editor/icons/function.png
Normal file
|
After Width: | Height: | Size: 457 B |
BIN
editor/icons/hash.png
Normal file
|
After Width: | Height: | Size: 502 B |
BIN
editor/icons/inject.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
editor/icons/join.png
Normal file
|
After Width: | Height: | Size: 253 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
editor/icons/light.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
editor/icons/link-out.png
Normal file
|
After Width: | Height: | Size: 402 B |
|
Before Width: | Height: | Size: 414 B After Width: | Height: | Size: 414 B |
|
Before Width: | Height: | Size: 671 B After Width: | Height: | Size: 671 B |
BIN
editor/icons/node-changed.png
Normal file
|
After Width: | Height: | Size: 386 B |
BIN
editor/icons/node-error.png
Normal file
|
After Width: | Height: | Size: 386 B |
BIN
editor/icons/parser-csv.png
Normal file
|
After Width: | Height: | Size: 413 B |
BIN
editor/icons/parser-html.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
editor/icons/parser-json.png
Normal file
|
After Width: | Height: | Size: 467 B |
BIN
editor/icons/parser-xml.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
editor/icons/range.png
Normal file
|
After Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 736 B |
BIN
editor/icons/rpi.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
editor/icons/serial.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
editor/icons/split.png
Normal file
|
After Width: | Height: | Size: 256 B |
BIN
editor/icons/subflow.png
Normal file
|
After Width: | Height: | Size: 439 B |
BIN
editor/icons/swap.png
Normal file
|
After Width: | Height: | Size: 592 B |
BIN
editor/icons/switch.png
Normal file
|
After Width: | Height: | Size: 509 B |
BIN
editor/icons/template.png
Normal file
|
After Width: | Height: | Size: 488 B |
BIN
editor/icons/timer.png
Normal file
|
After Width: | Height: | Size: 628 B |
BIN
editor/icons/trigger.png
Normal file
|
After Width: | Height: | Size: 258 B |
BIN
editor/icons/twitter.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
editor/icons/watch.png
Normal file
|
After Width: | Height: | Size: 591 B |
BIN
editor/icons/white-globe.png
Normal file
|
After Width: | Height: | Size: 707 B |
BIN
editor/images/deploy-flows-o.png
Normal file
|
After Width: | Height: | Size: 291 B |
BIN
editor/images/deploy-flows.png
Normal file
|
After Width: | Height: | Size: 386 B |
BIN
editor/images/deploy-full-o.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
editor/images/deploy-full.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
editor/images/deploy-nodes-o.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
editor/images/deploy-nodes.png
Normal file
|
After Width: | Height: | Size: 392 B |
BIN
editor/images/grip.png
Normal file
|
After Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
BIN
editor/images/node-red.png
Normal file
|
After Width: | Height: | Size: 1019 B |
BIN
editor/images/pw_maze_white.png
Normal file
|
After Width: | Height: | Size: 600 B |
BIN
editor/images/subflow_tab.png
Normal file
|
After Width: | Height: | Size: 410 B |
BIN
editor/images/typedInput/09.png
Normal file
|
After Width: | Height: | Size: 638 B |
BIN
editor/images/typedInput/az.png
Normal file
|
After Width: | Height: | Size: 546 B |
BIN
editor/images/typedInput/bool.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
editor/images/typedInput/json.png
Normal file
|
After Width: | Height: | Size: 588 B |
BIN
editor/images/typedInput/re.png
Normal file
|
After Width: | Height: | Size: 502 B |
67
editor/js/bidi/base-text-dir.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.bidi.baseTextDir = (function() {
|
||||||
|
var LRE = "\u202A",
|
||||||
|
RLE = "\u202B",
|
||||||
|
PDF = "\u202C";
|
||||||
|
|
||||||
|
function _isRTLValue(stringValue) {
|
||||||
|
var length = stringValue.length;
|
||||||
|
for (var i = 0 ; i < length; i++) {
|
||||||
|
if (_isBidiChar(stringValue.charCodeAt(i))) {
|
||||||
|
return true;
|
||||||
|
} else if (_isLatinChar(stringValue.charCodeAt(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RED.bidi.isMirroringEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isBidiChar(c) {
|
||||||
|
return (c >= 0x05d0 && c <= 0x05ff)||
|
||||||
|
(c >= 0x0600 && c <= 0x065f)||
|
||||||
|
(c >= 0x066a && c <= 0x06ef)||
|
||||||
|
(c >= 0x06fa && c <= 0x07ff)||
|
||||||
|
(c >= 0xfb1d && c <= 0xfdff)||
|
||||||
|
(c >= 0xfe70 && c <= 0xfefc);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isLatinChar(c) {
|
||||||
|
return (c > 64 && c < 91) || (c > 96 && c < 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforces the text direction of a given string by adding
|
||||||
|
* UCC (Unicode Control Characters)
|
||||||
|
* @param value - the string
|
||||||
|
*/
|
||||||
|
function _enforceTextDirectionWithUCC(value) {
|
||||||
|
if (value) {
|
||||||
|
var dir = RED.bidi.resolveBaseTextDir(value);
|
||||||
|
if (dir == "ltr") {
|
||||||
|
return LRE + value + PDF;
|
||||||
|
} else if (dir == "rtl") {
|
||||||
|
return RLE + value + PDF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enforceTextDirectionWithUCC: _enforceTextDirectionWithUCC,
|
||||||
|
isRTLValue : _isRTLValue
|
||||||
|
}
|
||||||
|
})();
|
||||||
189
editor/js/bidi/bidi.js
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.bidi = (function() {
|
||||||
|
var textDir = "";
|
||||||
|
var shaperType = "";
|
||||||
|
var calendarType = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the type of bidi-support
|
||||||
|
* BTD : Base text direction
|
||||||
|
* NS : Numeric Shaping
|
||||||
|
* CALENDAR : National Calendar
|
||||||
|
* STT_ATTACH : Structure Text Support, it is using to call attach function located at format.js
|
||||||
|
* STT_GETHTML : STT_ATTACH : Structure Text Support, it is using to call getHtml function located at format.js
|
||||||
|
*/
|
||||||
|
var _flags = {
|
||||||
|
BTD: 1,
|
||||||
|
NS: 2,
|
||||||
|
CALENDAR: 4,
|
||||||
|
STT_ATTACH: 8,
|
||||||
|
STT_GETHTML: 16
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse component position when mirroring is enabled
|
||||||
|
*/
|
||||||
|
var _componentPos = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if browser language is RTL language
|
||||||
|
*/
|
||||||
|
function _isMirroringEnabled() {
|
||||||
|
var isRTLLang = new RegExp("^(ar|he)").test(navigator.language);
|
||||||
|
if (isRTLLang) {
|
||||||
|
_componentPos.left = "right";
|
||||||
|
_componentPos.right = "left";
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
_componentPos.left = "left";
|
||||||
|
_componentPos.right = "right";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param val - the numeric shaping type: None , National or contextual
|
||||||
|
*/
|
||||||
|
function _setNumericShapingType(val) {
|
||||||
|
shaperType = val;
|
||||||
|
_refreshView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the national calendar preference
|
||||||
|
* @param val - the calendar type hijri, hebrew or gregorian
|
||||||
|
*/
|
||||||
|
function _setCalendarType(val) {
|
||||||
|
calendarType = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the date according to the current selected calendar
|
||||||
|
* @param date - the date object to be formatted
|
||||||
|
*/
|
||||||
|
function _getGlobalizedDate(date) {
|
||||||
|
var options = {};
|
||||||
|
var lang = navigator.language;
|
||||||
|
if (calendarType === "hijri") {
|
||||||
|
options = lang + "-u-ca-islamic";
|
||||||
|
} else if (calendarType === "hebrew") {
|
||||||
|
options = lang + "-u-ca-hebrew";
|
||||||
|
}
|
||||||
|
return date.toLocaleString(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text direction preference
|
||||||
|
* @param dir - the text direction preference
|
||||||
|
*/
|
||||||
|
function _setTextDirection(dir) {
|
||||||
|
textDir = dir;
|
||||||
|
_refreshView();
|
||||||
|
_enforceTextDirectionOnPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforces the text direction for all the spans with style bidiAware under
|
||||||
|
* workspace or sidebar div
|
||||||
|
*/
|
||||||
|
function _enforceTextDirectionOnPage() {
|
||||||
|
$("#workspace").find('span.bidiAware').each(function() {
|
||||||
|
$(this).attr("dir", _resolveBaseTextDir($(this).html()));
|
||||||
|
});
|
||||||
|
$("#sidebar").find('span.bidiAware').each(function() {
|
||||||
|
$(this).attr("dir", _resolveBaseTextDir($(this).text()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the text direction of a given string.
|
||||||
|
* @param value - the string
|
||||||
|
*/
|
||||||
|
function _resolveBaseTextDir(value) {
|
||||||
|
if (textDir == "auto") {
|
||||||
|
if (RED.bidi.baseTextDir.isRTLValue(value)) {
|
||||||
|
return "rtl";
|
||||||
|
} else {
|
||||||
|
return "ltr";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return textDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds event listeners to the Input to ensure its text-direction attribute
|
||||||
|
* is properly set based on its content.
|
||||||
|
* @param input - the input field
|
||||||
|
*/
|
||||||
|
function _prepareInput(input) {
|
||||||
|
input.on("keyup",_onInputChange).on("paste",_onInputChange).on("cut",_onInputChange);
|
||||||
|
// Set the initial text direction
|
||||||
|
_onInputChange.call(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onInputChange() {
|
||||||
|
$(this).attr("dir", _resolveBaseTextDir($(this).val()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the view whenever changing the user preferences
|
||||||
|
*/
|
||||||
|
function _refreshView() {
|
||||||
|
RED.nodes.eachNode(function(n) { n.dirty = true;});
|
||||||
|
RED.view.redraw();
|
||||||
|
RED.palette.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applying bidi support for these features: base-text-dir ,Numeric-shaping or both, STT ,Calendar which is controlled by the flag value.
|
||||||
|
* @param value- the string to apply the bidi-support on it.
|
||||||
|
* @param flag - indicates the type of bidi-support (Base-text-dir ,Numeric-shaping or both, STT , Calendar)
|
||||||
|
* @param type - could be one of filepath, url, email
|
||||||
|
* @param args - pass additional arguments to the handler. generally null.
|
||||||
|
*/
|
||||||
|
function _applyBidiSupport(value, flag, type, args) {
|
||||||
|
switch (flag) {
|
||||||
|
case 0:
|
||||||
|
value = RED.bidi.baseTextDir.enforceTextDirectionWithUCC(value);
|
||||||
|
return RED.bidi.numericShaping.shape(value, shaperType, textDir);
|
||||||
|
case 1:
|
||||||
|
return RED.bidi.baseTextDir.enforceTextDirectionWithUCC(value);
|
||||||
|
case 2:
|
||||||
|
return RED.bidi.numericShaping.shape(value, shaperType, textDir);
|
||||||
|
case 4:
|
||||||
|
return _getGlobalizedDate(value);
|
||||||
|
case 8:
|
||||||
|
return RED.bidi.format.attach(value, type, args, _isMirroringEnabled(), navigator.language);
|
||||||
|
case 16:
|
||||||
|
return RED.bidi.format.getHtml(value, type, args, _isMirroringEnabled(), navigator.language);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isMirroringEnabled: _isMirroringEnabled,
|
||||||
|
setNumericShapingType : _setNumericShapingType,
|
||||||
|
setCalendarType: _setCalendarType,
|
||||||
|
setTextDirection : _setTextDirection,
|
||||||
|
applyBidiSupport : _applyBidiSupport,
|
||||||
|
resolveBaseTextDir : _resolveBaseTextDir,
|
||||||
|
prepareInput: _prepareInput,
|
||||||
|
flags: _flags,
|
||||||
|
componentPos: _componentPos
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
* Copyright 2016 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
RED.text.format = (function() {
|
|
||||||
|
RED.bidi.format = (function() {
|
||||||
|
|
||||||
var TextSegment = (function() {
|
var TextSegment = (function() {
|
||||||
var TextSegment = function (obj) {
|
var TextSegment = function (obj) {
|
||||||
@@ -758,7 +759,7 @@ RED.text.format = (function() {
|
|||||||
{
|
{
|
||||||
guiDir: isRtl ? "rtl" : "ltr",
|
guiDir: isRtl ? "rtl" : "ltr",
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
points: "/\\:."
|
points: "/\\:.&<>"
|
||||||
};
|
};
|
||||||
if (!parseOnly) {
|
if (!parseOnly) {
|
||||||
return stext.parseAndDisplayStructure(text, fArgs, !!isHtml, locale);
|
return stext.parseAndDisplayStructure(text, fArgs, !!isHtml, locale);
|
||||||
@@ -876,7 +877,7 @@ RED.text.format = (function() {
|
|||||||
{
|
{
|
||||||
guiDir: isRtl ? "rtl" : "ltr",
|
guiDir: isRtl ? "rtl" : "ltr",
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
points: ":?#/@.[]="
|
points: ":?#/@.[]=&<>"
|
||||||
};
|
};
|
||||||
if (!parseOnly) {
|
if (!parseOnly) {
|
||||||
return stext.parseAndDisplayStructure(text, fArgs, !!isHtml, locale);
|
return stext.parseAndDisplayStructure(text, fArgs, !!isHtml, locale);
|
||||||
@@ -1242,7 +1243,7 @@ RED.text.format = (function() {
|
|||||||
element.dispatchEvent(event);
|
element.dispatchEvent(event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var range = selection.getRangeAt(0);
|
var range = selection.getRangeAt(0);
|
||||||
var tempRange = range.cloneRange(), startNode, startOffset;
|
var tempRange = range.cloneRange(), startNode, startOffset;
|
||||||
startNode = range.startContainer;
|
startNode = range.startContainer;
|
||||||
@@ -1304,7 +1305,19 @@ RED.text.format = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/*!
|
/**
|
||||||
|
* Returns the string representation of a given structured text
|
||||||
|
* @param text - the structured text
|
||||||
|
* @param type - could be one of filepath, url, email
|
||||||
|
* @param args - pass additional arguments to the handler. generally null.
|
||||||
|
* @param isRtl - indicates if the GUI is mirrored
|
||||||
|
* @param locale - the browser locale
|
||||||
|
*/
|
||||||
|
getString: function (text, type, args, isRtl, locale) {
|
||||||
|
return getHandler(type).format(text, args, isRtl, false, locale);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
* Returns the HTML representation of a given structured text
|
* Returns the HTML representation of a given structured text
|
||||||
* @param text - the structured text
|
* @param text - the structured text
|
||||||
* @param type - could be one of filepath, url, email
|
* @param type - could be one of filepath, url, email
|
||||||
@@ -1315,7 +1328,7 @@ RED.text.format = (function() {
|
|||||||
getHtml: function (text, type, args, isRtl, locale) {
|
getHtml: function (text, type, args, isRtl, locale) {
|
||||||
return getHandler(type).format(text, args, isRtl, true, locale);
|
return getHandler(type).format(text, args, isRtl, true, locale);
|
||||||
},
|
},
|
||||||
/*!
|
/**
|
||||||
* Handle Structured text correct display for a given HTML element.
|
* Handle Structured text correct display for a given HTML element.
|
||||||
* @param element - the element : should be of type div contenteditable=true
|
* @param element - the element : should be of type div contenteditable=true
|
||||||
* @param type - could be one of filepath, url, email
|
* @param type - could be one of filepath, url, email
|
||||||
86
editor/js/bidi/numeric-shaping.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.bidi.numericShaping = (function(){
|
||||||
|
var regex = /([0-9])|([\u0660-\u0669])|([\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FF\u0750-\u077F\u08A0-\u08E3\u200F\u202B\u202E\u2067\uFB50-\uFD3D\uFD40-\uFDCF\uFDF0-\uFDFC\uFDFE-\uFDFF\uFE70-\uFEFE]+)|([^0-9\u0660-\u0669\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FF\u0750-\u077F\u08A0-\u08E3\u200F\u202B\u202E\u2067\uFB50-\uFD3D\uFD40-\uFDCF\uFDF0-\uFDFC\uFDFE-\uFDFF\uFE70-\uFEFE\u0600-\u0607\u0609-\u060A\u060C\u060E-\u061A\u064B-\u066C\u0670\u06D6-\u06E4\u06E7-\u06ED\u06F0-\u06F9\u08E4-\u08FF\uFD3E-\uFD3F\uFDD0-\uFDEF\uFDFD\uFEFF\u0000-\u0040\u005B-\u0060\u007B-\u007F\u0080-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02B9-\u02BA\u02C2-\u02CF\u02D2-\u02DF\u02E5-\u02ED\u02EF-\u02FF\u2070\u2074-\u207E\u2080-\u208E\u2100-\u2101\u2103-\u2106\u2108-\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A-\u213B\u2140-\u2144\u214A-\u214D\u2150-\u215F\u2189\uA720-\uA721\uA788\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE]+)/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the digits in the text to European or Arabic digits According to
|
||||||
|
* the shaperType & the textDir.
|
||||||
|
*/
|
||||||
|
function _shape(text, shaperType, textDir) {
|
||||||
|
text = text.toString();
|
||||||
|
if(textDir === "auto"){
|
||||||
|
textDir = RED.bidi.resolveBaseTextDir(text);
|
||||||
|
}
|
||||||
|
if (!text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
switch (shaperType) {
|
||||||
|
case "defaultNumeral":
|
||||||
|
return _shapeEuropean(text);
|
||||||
|
case "national":
|
||||||
|
return _shapeArabic(text);
|
||||||
|
case "contextual":
|
||||||
|
return _shapeContextual(text, textDir === "rtl" ? 2 : 1);
|
||||||
|
default:
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the digits in the text to European digits.
|
||||||
|
*/
|
||||||
|
function _shapeEuropean(text) {
|
||||||
|
return text.replace(/[\u0660-\u0669]/g, function(c) {
|
||||||
|
return c.charCodeAt(0) - 1632;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the digits in the text to Arabic digits.
|
||||||
|
*/
|
||||||
|
function _shapeArabic(text) {
|
||||||
|
return text.replace(/[0-9]/g, function(c) {
|
||||||
|
return String.fromCharCode(parseInt(c) + 1632);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the digits in the text to European or Arabic digits
|
||||||
|
* According to the type of the preceding strong character.
|
||||||
|
* @param context:The current effective context.
|
||||||
|
* Allowed values:
|
||||||
|
* '1': European context
|
||||||
|
* '2': Arabic context
|
||||||
|
*/
|
||||||
|
function _shapeContextual(text, context) {
|
||||||
|
return text.replace(regex, function(match, latinDigit, arabicDigit, strongArabic, strongLatin){
|
||||||
|
if (latinDigit) {
|
||||||
|
return (context === 2) ? String.fromCharCode(parseInt(latinDigit) + 1632) : latinDigit;
|
||||||
|
} else if (arabicDigit) {
|
||||||
|
return (context === 1) ? arabicDigit.charCodeAt(0) - 1632 : arabicDigit;
|
||||||
|
} else if (strongArabic) {
|
||||||
|
context = 2;
|
||||||
|
} else if (strongLatin) {
|
||||||
|
context = 1;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
shape: _shape
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
* Copyright 2014, 2016 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -28,24 +28,14 @@ RED.comms = (function() {
|
|||||||
|
|
||||||
function connectWS() {
|
function connectWS() {
|
||||||
active = true;
|
active = true;
|
||||||
var wspath;
|
var path = location.hostname;
|
||||||
|
var port = location.port;
|
||||||
if (RED.settings.apiRootUrl) {
|
if (port.length !== 0) {
|
||||||
var m = /^(https?):\/\/(.*)$/.exec(RED.settings.apiRootUrl);
|
path = path+":"+port;
|
||||||
if (m) {
|
|
||||||
console.log(m);
|
|
||||||
wspath = "ws"+(m[1]==="https"?"s":"")+"://"+m[2]+"comms";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var path = location.hostname;
|
|
||||||
var port = location.port;
|
|
||||||
if (port.length !== 0) {
|
|
||||||
path = path+":"+port;
|
|
||||||
}
|
|
||||||
path = path+document.location.pathname;
|
|
||||||
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
|
||||||
wspath = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
|
||||||
}
|
}
|
||||||
|
path = path+document.location.pathname;
|
||||||
|
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
||||||
|
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
||||||
|
|
||||||
var auth_tokens = RED.settings.get("auth-tokens");
|
var auth_tokens = RED.settings.get("auth-tokens");
|
||||||
pendingAuth = (auth_tokens!=null);
|
pendingAuth = (auth_tokens!=null);
|
||||||
@@ -58,7 +48,7 @@ RED.comms = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ws = new WebSocket(wspath);
|
ws = new WebSocket(path);
|
||||||
ws.onopen = function() {
|
ws.onopen = function() {
|
||||||
reconnectAttempts = 0;
|
reconnectAttempts = 0;
|
||||||
if (errornotification) {
|
if (errornotification) {
|
||||||
@@ -74,41 +64,27 @@ RED.comms = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ws.onmessage = function(event) {
|
ws.onmessage = function(event) {
|
||||||
var message = JSON.parse(event.data);
|
var msg = JSON.parse(event.data);
|
||||||
if (message.auth) {
|
if (pendingAuth && msg.auth) {
|
||||||
if (pendingAuth) {
|
if (msg.auth === "ok") {
|
||||||
if (message.auth === "ok") {
|
pendingAuth = false;
|
||||||
pendingAuth = false;
|
completeConnection();
|
||||||
completeConnection();
|
} else if (msg.auth === "fail") {
|
||||||
} else if (message.auth === "fail") {
|
// anything else is an error...
|
||||||
// anything else is an error...
|
|
||||||
active = false;
|
|
||||||
RED.user.login({updateMenu:true},function() {
|
|
||||||
connectWS();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (message.auth === "fail") {
|
|
||||||
// Our current session has expired
|
|
||||||
active = false;
|
active = false;
|
||||||
RED.user.login({updateMenu:true},function() {
|
RED.user.login({updateMenu:true},function() {
|
||||||
connectWS();
|
connectWS();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else if (msg.topic) {
|
||||||
// Otherwise, 'message' is an array of actual comms messages
|
for (var t in subscriptions) {
|
||||||
for (var m = 0; m < message.length; m++) {
|
if (subscriptions.hasOwnProperty(t)) {
|
||||||
var msg = message[m];
|
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||||
if (msg.topic) {
|
if (re.test(msg.topic)) {
|
||||||
for (var t in subscriptions) {
|
var subscribers = subscriptions[t];
|
||||||
if (subscriptions.hasOwnProperty(t)) {
|
if (subscribers) {
|
||||||
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
for (var i=0;i<subscribers.length;i++) {
|
||||||
if (re.test(msg.topic)) {
|
subscribers[i](msg.topic,msg.data);
|
||||||
var subscribers = subscriptions[t];
|
|
||||||
if (subscribers) {
|
|
||||||
for (var i=0;i<subscribers.length;i++) {
|
|
||||||
subscribers[i](msg.topic,msg.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,10 +118,10 @@ RED.comms = (function() {
|
|||||||
connectWS();
|
connectWS();
|
||||||
} else {
|
} else {
|
||||||
var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>';
|
var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>';
|
||||||
errornotification.update(msg,{silent:true});
|
errornotification.update(msg);
|
||||||
$(errornotification).find("a").on("click", function(e) {
|
$(errornotification).find("a").click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
errornotification.update(RED._("notification.errors.lostConnection"),{silent:true});
|
errornotification.update(RED._("notification.errors.lostConnection"));
|
||||||
clearInterval(connectCountdownTimer);
|
clearInterval(connectCountdownTimer);
|
||||||
connectWS();
|
connectWS();
|
||||||
})
|
})
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
* Copyright 2015, 2016 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -32,19 +32,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function emit() {
|
function emit(evt,arg) {
|
||||||
var evt = arguments[0]
|
|
||||||
var args = Array.prototype.slice.call(arguments,1);
|
|
||||||
if (RED.events.DEBUG) {
|
|
||||||
console.warn(evt,args);
|
|
||||||
}
|
|
||||||
if (handlers[evt]) {
|
if (handlers[evt]) {
|
||||||
for (var i=0;i<handlers[evt].length;i++) {
|
for (var i=0;i<handlers[evt].length;i++) {
|
||||||
try {
|
try {
|
||||||
handlers[evt][i].apply(null, args);
|
handlers[evt][i](arg);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn("RED.events.emit error: ["+evt+"] "+(err.toString()));
|
console.log("RED.events.emit error: ["+evt+"] "+(err.toString()));
|
||||||
console.warn(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
297
editor/js/history.js
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013, 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.history = (function() {
|
||||||
|
var undo_history = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
|
||||||
|
markAllDirty: function() {
|
||||||
|
for (var i=0;i<undo_history.length;i++) {
|
||||||
|
undo_history[i].dirty = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: function() {
|
||||||
|
return undo_history
|
||||||
|
},
|
||||||
|
depth: function() {
|
||||||
|
return undo_history.length;
|
||||||
|
},
|
||||||
|
push: function(ev) {
|
||||||
|
undo_history.push(ev);
|
||||||
|
},
|
||||||
|
pop: function() {
|
||||||
|
var ev = undo_history.pop();
|
||||||
|
var i;
|
||||||
|
var node;
|
||||||
|
var subflow;
|
||||||
|
var modifiedTabs = {};
|
||||||
|
if (ev) {
|
||||||
|
if (ev.t == 'add') {
|
||||||
|
if (ev.nodes) {
|
||||||
|
for (i=0;i<ev.nodes.length;i++) {
|
||||||
|
node = RED.nodes.node(ev.nodes[i]);
|
||||||
|
if (node.z) {
|
||||||
|
modifiedTabs[node.z] = true;
|
||||||
|
}
|
||||||
|
RED.nodes.remove(ev.nodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.links) {
|
||||||
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
RED.nodes.removeLink(ev.links[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.workspaces) {
|
||||||
|
for (i=0;i<ev.workspaces.length;i++) {
|
||||||
|
RED.nodes.removeWorkspace(ev.workspaces[i].id);
|
||||||
|
RED.workspaces.remove(ev.workspaces[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.subflows) {
|
||||||
|
for (i=0;i<ev.subflows.length;i++) {
|
||||||
|
RED.nodes.removeSubflow(ev.subflows[i]);
|
||||||
|
RED.workspaces.remove(ev.subflows[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.subflow) {
|
||||||
|
if (ev.subflow.instances) {
|
||||||
|
ev.subflow.instances.forEach(function(n) {
|
||||||
|
var node = RED.nodes.node(n.id);
|
||||||
|
if (node) {
|
||||||
|
node.changed = n.changed;
|
||||||
|
node.dirty = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ev.subflow.hasOwnProperty('changed')) {
|
||||||
|
subflow = RED.nodes.subflow(ev.subflow.id);
|
||||||
|
if (subflow) {
|
||||||
|
subflow.changed = ev.subflow.changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.removedLinks) {
|
||||||
|
for (i=0;i<ev.removedLinks.length;i++) {
|
||||||
|
RED.nodes.addLink(ev.removedLinks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (ev.t == "delete") {
|
||||||
|
if (ev.workspaces) {
|
||||||
|
for (i=0;i<ev.workspaces.length;i++) {
|
||||||
|
RED.nodes.addWorkspace(ev.workspaces[i]);
|
||||||
|
RED.workspaces.add(ev.workspaces[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.subflow && ev.subflow.subflow) {
|
||||||
|
RED.nodes.addSubflow(ev.subflow.subflow);
|
||||||
|
}
|
||||||
|
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
|
||||||
|
subflow = RED.nodes.subflow(ev.subflowInputs[0].z);
|
||||||
|
subflow.in.push(ev.subflowInputs[0]);
|
||||||
|
subflow.in[0].dirty = true;
|
||||||
|
}
|
||||||
|
if (ev.subflowOutputs && ev.subflowOutputs.length > 0) {
|
||||||
|
subflow = RED.nodes.subflow(ev.subflowOutputs[0].z);
|
||||||
|
ev.subflowOutputs.sort(function(a,b) { return a.i-b.i});
|
||||||
|
for (i=0;i<ev.subflowOutputs.length;i++) {
|
||||||
|
var output = ev.subflowOutputs[i];
|
||||||
|
subflow.out.splice(output.i,0,output);
|
||||||
|
for (var j=output.i+1;j<subflow.out.length;j++) {
|
||||||
|
subflow.out[j].i++;
|
||||||
|
subflow.out[j].dirty = true;
|
||||||
|
}
|
||||||
|
RED.nodes.eachLink(function(l) {
|
||||||
|
if (l.source.type == "subflow:"+subflow.id) {
|
||||||
|
if (l.sourcePort >= output.i) {
|
||||||
|
l.sourcePort++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
|
||||||
|
ev.subflow.instances.forEach(function(n) {
|
||||||
|
var node = RED.nodes.node(n.id);
|
||||||
|
if (node) {
|
||||||
|
node.changed = n.changed;
|
||||||
|
node.dirty = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (subflow) {
|
||||||
|
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
|
||||||
|
n.inputs = subflow.in.length;
|
||||||
|
n.outputs = subflow.out.length;
|
||||||
|
while (n.outputs > n.ports.length) {
|
||||||
|
n.ports.push(n.ports.length);
|
||||||
|
}
|
||||||
|
n.resize = true;
|
||||||
|
n.dirty = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ev.nodes) {
|
||||||
|
for (i=0;i<ev.nodes.length;i++) {
|
||||||
|
RED.nodes.add(ev.nodes[i]);
|
||||||
|
modifiedTabs[ev.nodes[i].z] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.links) {
|
||||||
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
RED.nodes.addLink(ev.links[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.changes) {
|
||||||
|
for (i in ev.changes) {
|
||||||
|
if (ev.changes.hasOwnProperty(i)) {
|
||||||
|
node = RED.nodes.node(i);
|
||||||
|
if (node) {
|
||||||
|
for (var d in ev.changes[i]) {
|
||||||
|
if (ev.changes[i].hasOwnProperty(d)) {
|
||||||
|
node[d] = ev.changes[i][d];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (ev.t == "move") {
|
||||||
|
for (i=0;i<ev.nodes.length;i++) {
|
||||||
|
var n = ev.nodes[i];
|
||||||
|
n.n.x = n.ox;
|
||||||
|
n.n.y = n.oy;
|
||||||
|
n.n.dirty = true;
|
||||||
|
n.n.changed = n.changed;
|
||||||
|
}
|
||||||
|
// A move could have caused a link splice
|
||||||
|
if (ev.links) {
|
||||||
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
RED.nodes.removeLink(ev.links[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.removedLinks) {
|
||||||
|
for (i=0;i<ev.removedLinks.length;i++) {
|
||||||
|
RED.nodes.addLink(ev.removedLinks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (ev.t == "edit") {
|
||||||
|
for (i in ev.changes) {
|
||||||
|
if (ev.changes.hasOwnProperty(i)) {
|
||||||
|
if (ev.node._def.defaults[i].type) {
|
||||||
|
// This is a config node property
|
||||||
|
var currentConfigNode = RED.nodes.node(ev.node[i]);
|
||||||
|
if (currentConfigNode) {
|
||||||
|
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
|
||||||
|
}
|
||||||
|
var newConfigNode = RED.nodes.node(ev.changes[i]);
|
||||||
|
if (newConfigNode) {
|
||||||
|
newConfigNode.users.push(ev.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ev.node[i] = ev.changes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.subflow) {
|
||||||
|
if (ev.subflow.hasOwnProperty('inputCount')) {
|
||||||
|
if (ev.node.in.length > ev.subflow.inputCount) {
|
||||||
|
ev.node.in.splice(ev.subflow.inputCount);
|
||||||
|
} else if (ev.subflow.inputs.length > 0) {
|
||||||
|
ev.node.in = ev.node.in.concat(ev.subflow.inputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.subflow.hasOwnProperty('outputCount')) {
|
||||||
|
if (ev.node.out.length > ev.subflow.outputCount) {
|
||||||
|
ev.node.out.splice(ev.subflow.outputCount);
|
||||||
|
} else if (ev.subflow.outputs.length > 0) {
|
||||||
|
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.subflow.hasOwnProperty('instances')) {
|
||||||
|
ev.subflow.instances.forEach(function(n) {
|
||||||
|
var node = RED.nodes.node(n.id);
|
||||||
|
if (node) {
|
||||||
|
node.changed = n.changed;
|
||||||
|
node.dirty = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
|
||||||
|
n.inputs = ev.node.in.length;
|
||||||
|
n.outputs = ev.node.out.length;
|
||||||
|
RED.editor.updateNodeProperties(n);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
RED.editor.updateNodeProperties(ev.node);
|
||||||
|
RED.editor.validateNode(ev.node);
|
||||||
|
}
|
||||||
|
if (ev.links) {
|
||||||
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
RED.nodes.addLink(ev.links[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ev.node.dirty = true;
|
||||||
|
ev.node.changed = ev.changed;
|
||||||
|
} else if (ev.t == "createSubflow") {
|
||||||
|
if (ev.nodes) {
|
||||||
|
RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) {
|
||||||
|
n.z = ev.activeWorkspace;
|
||||||
|
n.dirty = true;
|
||||||
|
});
|
||||||
|
for (i=0;i<ev.nodes.length;i++) {
|
||||||
|
RED.nodes.remove(ev.nodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.links) {
|
||||||
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
RED.nodes.removeLink(ev.links[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RED.nodes.removeSubflow(ev.subflow.subflow);
|
||||||
|
RED.workspaces.remove(ev.subflow.subflow);
|
||||||
|
|
||||||
|
if (ev.removedLinks) {
|
||||||
|
for (i=0;i<ev.removedLinks.length;i++) {
|
||||||
|
RED.nodes.addLink(ev.removedLinks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (ev.t == "reorder") {
|
||||||
|
if (ev.order) {
|
||||||
|
RED.workspaces.order(ev.order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.keys(modifiedTabs).forEach(function(id) {
|
||||||
|
var subflow = RED.nodes.subflow(id);
|
||||||
|
if (subflow) {
|
||||||
|
RED.editor.validateNode(subflow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
RED.nodes.dirty(ev.dirty);
|
||||||
|
RED.view.redraw(true);
|
||||||
|
RED.palette.refresh();
|
||||||
|
RED.workspaces.refresh();
|
||||||
|
RED.sidebar.config.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
peek: function() {
|
||||||
|
return undo_history[undo_history.length-1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
43
editor/js/i18n.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013, 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.i18n = (function() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function(done) {
|
||||||
|
i18n.init({
|
||||||
|
resGetPath: 'locales/__ns__',
|
||||||
|
dynamicLoad: false,
|
||||||
|
load:'current',
|
||||||
|
ns: {
|
||||||
|
namespaces: ["editor","node-red"],
|
||||||
|
defaultNs: "editor"
|
||||||
|
},
|
||||||
|
fallbackLng: ['en-US'],
|
||||||
|
useCookie: false
|
||||||
|
},function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
RED["_"] = function() {
|
||||||
|
return i18n.t.apply(null,arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
loadCatalog: function(namespace,done) {
|
||||||
|
i18n.loadNamespace(namespace,done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
283
editor/js/main.js
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013, 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
var RED = (function() {
|
||||||
|
|
||||||
|
|
||||||
|
function loadNodeList() {
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept":"application/json"
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'nodes',
|
||||||
|
success: function(data) {
|
||||||
|
RED.nodes.setNodeList(data);
|
||||||
|
|
||||||
|
var nsCount = 0;
|
||||||
|
for (var i=0;i<data.length;i++) {
|
||||||
|
var ns = data[i];
|
||||||
|
if (ns.module != "node-red") {
|
||||||
|
nsCount++;
|
||||||
|
RED.i18n.loadCatalog(ns.id, function() {
|
||||||
|
nsCount--;
|
||||||
|
if (nsCount === 0) {
|
||||||
|
loadNodes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nsCount === 0) {
|
||||||
|
loadNodes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadNodes() {
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept":"text/html"
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'nodes',
|
||||||
|
success: function(data) {
|
||||||
|
$("body").append(data);
|
||||||
|
$("body").i18n();
|
||||||
|
$("#palette > .palette-spinner").hide();
|
||||||
|
$(".palette-scroll").removeClass("hide");
|
||||||
|
$("#palette-search").removeClass("hide");
|
||||||
|
loadFlows();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFlows() {
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept":"application/json",
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'flows',
|
||||||
|
success: function(nodes) {
|
||||||
|
var currentHash = window.location.hash;
|
||||||
|
RED.nodes.version(nodes.rev);
|
||||||
|
RED.nodes.import(nodes.flows);
|
||||||
|
RED.nodes.dirty(false);
|
||||||
|
RED.view.redraw(true);
|
||||||
|
if (/^#flow\/.+$/.test(currentHash)) {
|
||||||
|
RED.workspaces.show(currentHash.substring(6));
|
||||||
|
}
|
||||||
|
RED.comms.subscribe("status/#",function(topic,msg) {
|
||||||
|
var parts = topic.split("/");
|
||||||
|
var node = RED.nodes.node(parts[1]);
|
||||||
|
if (node) {
|
||||||
|
if (msg.hasOwnProperty("text")) {
|
||||||
|
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
|
||||||
|
}
|
||||||
|
node.status = msg;
|
||||||
|
if (statusEnabled) {
|
||||||
|
node.dirty = true;
|
||||||
|
RED.view.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RED.comms.subscribe("node/#",function(topic,msg) {
|
||||||
|
var i,m;
|
||||||
|
var typeList;
|
||||||
|
var info;
|
||||||
|
|
||||||
|
if (topic == "node/added") {
|
||||||
|
var addedTypes = [];
|
||||||
|
for (i=0;i<msg.length;i++) {
|
||||||
|
m = msg[i];
|
||||||
|
var id = m.id;
|
||||||
|
RED.nodes.addNodeSet(m);
|
||||||
|
addedTypes = addedTypes.concat(m.types);
|
||||||
|
RED.i18n.loadCatalog(id, function() {
|
||||||
|
$.get('nodes/'+id, function(data) {
|
||||||
|
$("body").append(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (addedTypes.length) {
|
||||||
|
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
||||||
|
}
|
||||||
|
} else if (topic == "node/removed") {
|
||||||
|
for (i=0;i<msg.length;i++) {
|
||||||
|
m = msg[i];
|
||||||
|
info = RED.nodes.removeNodeSet(m.id);
|
||||||
|
if (info.added) {
|
||||||
|
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (topic == "node/enabled") {
|
||||||
|
if (msg.types) {
|
||||||
|
info = RED.nodes.getNodeSet(msg.id);
|
||||||
|
if (info.added) {
|
||||||
|
RED.nodes.enableNodeSet(msg.id);
|
||||||
|
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
|
||||||
|
} else {
|
||||||
|
$.get('nodes/'+msg.id, function(data) {
|
||||||
|
$("body").append(data);
|
||||||
|
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (topic == "node/disabled") {
|
||||||
|
if (msg.types) {
|
||||||
|
RED.nodes.disableNodeSet(msg.id);
|
||||||
|
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Refresh flow library to ensure any examples are updated
|
||||||
|
RED.library.loadFlowLibrary();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAbout() {
|
||||||
|
$.get('red/about', function(data) {
|
||||||
|
var aboutHeader = '<div style="text-align:center;">'+
|
||||||
|
'<img width="50px" src="red/images/node-red-icon.svg" />'+
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
RED.sidebar.info.set(aboutHeader+marked(data));
|
||||||
|
RED.sidebar.info.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusEnabled = false;
|
||||||
|
function toggleStatus(state) {
|
||||||
|
statusEnabled = state;
|
||||||
|
RED.view.status(statusEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEditor() {
|
||||||
|
|
||||||
|
var menuOptions = [];
|
||||||
|
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||||
|
{id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid},
|
||||||
|
{id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid},
|
||||||
|
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
|
||||||
|
null,
|
||||||
|
{id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[
|
||||||
|
{id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if (s) {RED.bidi.setTextDirection("")}}},
|
||||||
|
{id:"menu-item-bidi-ltr",toggle:"text-direction",label:RED._("menu.label.view.ltr"), onselect:function(s) { if (s) {RED.bidi.setTextDirection("ltr")}}},
|
||||||
|
{id:"menu-item-bidi-rtl",toggle:"text-direction",label:RED._("menu.label.view.rtl"), onselect:function(s) { if (s) {RED.bidi.setTextDirection("rtl")}}},
|
||||||
|
{id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if (s) {RED.bidi.setTextDirection("auto")}}}
|
||||||
|
]},
|
||||||
|
{id:"menu-item-numerals",label:RED._("menu.label.view.numericShaping"),options:[
|
||||||
|
{id:"menu-item-numerals-default",toggle:"numeral-type",label:RED._("menu.label.view.defaultNumeral"),selected: true, onselect:function(s) { if(s){RED.bidi.setNumericShapingType("defaultNumeral")}}},
|
||||||
|
{id:"menu-item-numerals-national",toggle:"numeral-type",label:RED._("menu.label.view.national"), onselect:function(s) { if (s) {RED.bidi.setNumericShapingType("national")}}},
|
||||||
|
{id:"menu-item-numerals-contextual",toggle:"numeral-type",label:RED._("menu.label.view.contextual"), onselect:function(s) { if (s){RED.bidi.setNumericShapingType("contextual")}}}
|
||||||
|
]},
|
||||||
|
{id:"menu-item-calendars",label:RED._("menu.label.view.calendar"),options:[
|
||||||
|
{id:"menu-item-calendars-default",toggle:"calendar-type",label:RED._("menu.label.view.defaultCalendar"),selected: true, onselect:function(s) { if(s) {RED.bidi.setCalendarType("gregorian")}}},
|
||||||
|
{id:"menu-item-calendars-hijri",toggle:"calendar-type",label:RED._("menu.label.view.hijri"), onselect:function(s) { if (s) {RED.bidi.setCalendarType("hijri")}}},
|
||||||
|
{id:"menu-item-calendars-hebrew",toggle:"calendar-type",label:RED._("menu.label.view.hebrew"), onselect:function(s) { if (s) {RED.bidi.setCalendarType("hebrew")}}}
|
||||||
|
]},
|
||||||
|
null,
|
||||||
|
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}
|
||||||
|
]});
|
||||||
|
menuOptions.push(null);
|
||||||
|
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[
|
||||||
|
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
|
||||||
|
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
|
||||||
|
]});
|
||||||
|
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
|
||||||
|
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export},
|
||||||
|
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
|
||||||
|
]});
|
||||||
|
menuOptions.push(null);
|
||||||
|
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:RED.search.show});
|
||||||
|
menuOptions.push(null);
|
||||||
|
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function() {}});
|
||||||
|
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
|
||||||
|
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
|
||||||
|
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
|
||||||
|
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove}
|
||||||
|
]});
|
||||||
|
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||||
|
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
|
||||||
|
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||||
|
]});
|
||||||
|
menuOptions.push(null);
|
||||||
|
if (RED.settings.theme('palette.editable') !== false) {
|
||||||
|
RED.palette.editor.init();
|
||||||
|
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:RED.palette.editor.show});
|
||||||
|
menuOptions.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp});
|
||||||
|
menuOptions.push({id:"menu-item-help",
|
||||||
|
label: RED.settings.theme("menu.menu-item-help.label","Node-RED website"),
|
||||||
|
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
|
||||||
|
});
|
||||||
|
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: showAbout });
|
||||||
|
|
||||||
|
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
|
||||||
|
|
||||||
|
//apply rtl direction on body tag in case of GUI mirroring is enabled
|
||||||
|
if (RED.bidi.isMirroringEnabled()) {
|
||||||
|
$("body").attr("dir","rtl");
|
||||||
|
}
|
||||||
|
|
||||||
|
RED.user.init();
|
||||||
|
|
||||||
|
RED.library.init();
|
||||||
|
RED.palette.init();
|
||||||
|
RED.sidebar.init();
|
||||||
|
RED.subflow.init();
|
||||||
|
RED.workspaces.init();
|
||||||
|
RED.clipboard.init();
|
||||||
|
RED.search.init();
|
||||||
|
RED.view.init();
|
||||||
|
RED.editor.init();
|
||||||
|
|
||||||
|
RED.deploy.init(RED.settings.theme("deployButton",null));
|
||||||
|
|
||||||
|
RED.keyboard.add("workspace", /* ? */ 191,{shift:true},function() {RED.keyboard.showHelp();d3.event.preventDefault();});
|
||||||
|
RED.comms.connect();
|
||||||
|
|
||||||
|
$("#main-container").show();
|
||||||
|
$(".header-toolbar").show();
|
||||||
|
|
||||||
|
loadNodeList();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
|
||||||
|
document.title = document.title+" : "+window.location.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
ace.require("ace/ext/language_tools");
|
||||||
|
|
||||||
|
RED.i18n.init(function() {
|
||||||
|
RED.settings.init(loadEditor);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
})();
|
||||||
1249
editor/js/nodes.js
Normal file
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
* Copyright 2014 IBM, Antoine Aflalo
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -18,8 +18,6 @@
|
|||||||
RED.settings = (function () {
|
RED.settings = (function () {
|
||||||
|
|
||||||
var loadedSettings = {};
|
var loadedSettings = {};
|
||||||
var userSettings = {};
|
|
||||||
var pendingSave;
|
|
||||||
|
|
||||||
var hasLocalStorage = function () {
|
var hasLocalStorage = function () {
|
||||||
try {
|
try {
|
||||||
@@ -33,50 +31,27 @@ RED.settings = (function () {
|
|||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key === "auth-tokens") {
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
|
||||||
} else {
|
|
||||||
RED.utils.setMessageProperty(userSettings,key,value);
|
|
||||||
saveUserSettings();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the key is not set in the localStorage it returns <i>undefined</i>
|
* If the key is not set in the localStorage it returns <i>undefined</i>
|
||||||
* Else return the JSON parsed value
|
* Else return the JSON parsed value
|
||||||
* @param key
|
* @param key
|
||||||
* @param defaultIfUndefined
|
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
var get = function (key,defaultIfUndefined) {
|
var get = function (key) {
|
||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (key === "auth-tokens") {
|
return JSON.parse(localStorage.getItem(key));
|
||||||
return JSON.parse(localStorage.getItem(key));
|
|
||||||
} else {
|
|
||||||
var v;
|
|
||||||
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
|
|
||||||
if (v === undefined) {
|
|
||||||
try { v = RED.utils.getMessageProperty(RED.settings,key); } catch(err) {}
|
|
||||||
}
|
|
||||||
if (v === undefined) {
|
|
||||||
v = defaultIfUndefined;
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var remove = function (key) {
|
var remove = function (key) {
|
||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key === "auth-tokens") {
|
localStorage.removeItem(key);
|
||||||
localStorage.removeItem(key);
|
|
||||||
} else {
|
|
||||||
delete userSettings[key];
|
|
||||||
saveUserSettings();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var setProperties = function(data) {
|
var setProperties = function(data) {
|
||||||
@@ -93,26 +68,18 @@ RED.settings = (function () {
|
|||||||
loadedSettings = data;
|
loadedSettings = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
var setUserSettings = function(data) {
|
var init = function (done) {
|
||||||
userSettings = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
var init = function (options, done) {
|
|
||||||
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
||||||
if (accessTokenMatch) {
|
if (accessTokenMatch) {
|
||||||
var accessToken = accessTokenMatch[1];
|
var accessToken = accessTokenMatch[1];
|
||||||
RED.settings.set("auth-tokens",{access_token: accessToken});
|
RED.settings.set("auth-tokens",{access_token: accessToken});
|
||||||
window.location.search = "";
|
window.location.search = "";
|
||||||
}
|
}
|
||||||
RED.settings.apiRootUrl = options.apiRootUrl;
|
|
||||||
|
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
beforeSend: function(jqXHR,settings) {
|
beforeSend: function(jqXHR,settings) {
|
||||||
// Only attach auth header for requests to relative paths
|
// Only attach auth header for requests to relative paths
|
||||||
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
|
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
|
||||||
if (options.apiRootUrl) {
|
|
||||||
settings.url = options.apiRootUrl+settings.url;
|
|
||||||
}
|
|
||||||
var auth_tokens = RED.settings.get("auth-tokens");
|
var auth_tokens = RED.settings.get("auth-tokens");
|
||||||
if (auth_tokens) {
|
if (auth_tokens) {
|
||||||
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
|
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
|
||||||
@@ -125,7 +92,7 @@ RED.settings = (function () {
|
|||||||
load(done);
|
load(done);
|
||||||
}
|
}
|
||||||
|
|
||||||
var refreshSettings = function(done) {
|
var load = function(done) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
@@ -135,79 +102,25 @@ RED.settings = (function () {
|
|||||||
url: 'settings',
|
url: 'settings',
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
setProperties(data);
|
setProperties(data);
|
||||||
done(null, data);
|
if (RED.settings.user && RED.settings.user.anonymous) {
|
||||||
|
RED.settings.remove("auth-tokens");
|
||||||
|
}
|
||||||
|
console.log("Node-RED: " + data.version);
|
||||||
|
done();
|
||||||
},
|
},
|
||||||
error: function(jqXHR,textStatus,errorThrown) {
|
error: function(jqXHR,textStatus,errorThrown) {
|
||||||
if (jqXHR.status === 401) {
|
if (jqXHR.status === 401) {
|
||||||
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
|
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
|
||||||
window.location.search = "";
|
window.location.search = "";
|
||||||
}
|
}
|
||||||
RED.user.login(function() { refreshSettings(done); });
|
RED.user.login(function() { load(done); });
|
||||||
} else {
|
} else {
|
||||||
console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
|
console.log("Unexpected error:",jqXHR.status,textStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
var load = function(done) {
|
|
||||||
refreshSettings(function(err, data) {
|
|
||||||
if (!err) {
|
|
||||||
if (!RED.settings.user || RED.settings.user.anonymous) {
|
|
||||||
RED.settings.remove("auth-tokens");
|
|
||||||
}
|
|
||||||
console.log("Node-RED: " + data.version);
|
|
||||||
console.groupCollapsed("Versions");
|
|
||||||
console.log("jQuery",$().jquery)
|
|
||||||
console.log("jQuery UI",$.ui.version);
|
|
||||||
if(window.ace) { console.log("ACE",ace.version); }
|
|
||||||
if(window.monaco) { console.log("MONACO",monaco.version || "unknown"); }
|
|
||||||
console.log("D3",d3.version);
|
|
||||||
console.groupEnd();
|
|
||||||
loadUserSettings(done);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadUserSettings(done) {
|
|
||||||
$.ajax({
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json"
|
|
||||||
},
|
|
||||||
dataType: "json",
|
|
||||||
cache: false,
|
|
||||||
url: 'settings/user',
|
|
||||||
success: function (data) {
|
|
||||||
setUserSettings(data);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
error: function(jqXHR,textStatus,errorThrown) {
|
|
||||||
console.log("Unexpected error loading user settings:",jqXHR.status,textStatus);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveUserSettings() {
|
|
||||||
if (RED.user.hasPermission("settings.write")) {
|
|
||||||
if (pendingSave) {
|
|
||||||
clearTimeout(pendingSave);
|
|
||||||
}
|
|
||||||
pendingSave = setTimeout(function() {
|
|
||||||
pendingSave = null;
|
|
||||||
$.ajax({
|
|
||||||
method: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
url: 'settings/user',
|
|
||||||
data: JSON.stringify(userSettings),
|
|
||||||
success: function (data) {
|
|
||||||
},
|
|
||||||
error: function(jqXHR,textStatus,errorThrown) {
|
|
||||||
console.log("Unexpected error saving user settings:",jqXHR.status,textStatus);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function theme(property,defaultValue) {
|
function theme(property,defaultValue) {
|
||||||
if (!RED.settings.editorTheme) {
|
if (!RED.settings.editorTheme) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@@ -226,28 +139,14 @@ RED.settings = (function () {
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getLocal(key) {
|
|
||||||
return localStorage.getItem(key)
|
|
||||||
}
|
|
||||||
function setLocal(key, value) {
|
|
||||||
localStorage.setItem(key, value);
|
|
||||||
}
|
|
||||||
function removeLocal(key) {
|
|
||||||
localStorage.removeItem(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
load: load,
|
load: load,
|
||||||
loadUserSettings: loadUserSettings,
|
|
||||||
refreshSettings: refreshSettings,
|
|
||||||
set: set,
|
set: set,
|
||||||
get: get,
|
get: get,
|
||||||
remove: remove,
|
remove: remove,
|
||||||
theme: theme,
|
theme: theme
|
||||||
setLocal: setLocal,
|
|
||||||
getLocal: getLocal,
|
|
||||||
removeLocal: removeLocal
|
|
||||||
}
|
}
|
||||||
})();
|
})
|
||||||
|
();
|
||||||
307
editor/js/ui/clipboard.js
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.clipboard = (function() {
|
||||||
|
|
||||||
|
var dialog;
|
||||||
|
var dialogContainer;
|
||||||
|
var exportNodesDialog;
|
||||||
|
var importNodesDialog;
|
||||||
|
|
||||||
|
function setupDialogs() {
|
||||||
|
dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>')
|
||||||
|
.appendTo("body")
|
||||||
|
.dialog({
|
||||||
|
modal: true,
|
||||||
|
autoOpen: false,
|
||||||
|
width: 500,
|
||||||
|
resizable: false,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
id: "clipboard-dialog-cancel",
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "clipboard-dialog-close",
|
||||||
|
class: "primary",
|
||||||
|
text: RED._("common.label.close"),
|
||||||
|
click: function() {
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "clipboard-dialog-copy",
|
||||||
|
class: "primary",
|
||||||
|
text: RED._("clipboard.export.copy"),
|
||||||
|
click: function() {
|
||||||
|
$("#clipboard-export").select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.getSelection().removeAllRanges();
|
||||||
|
RED.notify(RED._("clipboard.nodesExported"));
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "clipboard-dialog-ok",
|
||||||
|
class: "primary",
|
||||||
|
text: RED._("common.label.import"),
|
||||||
|
click: function() {
|
||||||
|
RED.view.importNodes($("#clipboard-import").val(),$("#import-tab > a.selected").attr('id') === 'import-tab-new');
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
open: function(e) {
|
||||||
|
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||||
|
},
|
||||||
|
close: function(e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogContainer = dialog.children(".dialog-form");
|
||||||
|
|
||||||
|
exportNodesDialog =
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.export.copy"></label>'+
|
||||||
|
'<span id="export-range-group" class="button-group">'+
|
||||||
|
'<a id="export-range-selected" class="editor-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
|
||||||
|
'<a id="export-range-flow" class="editor-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
|
||||||
|
'<a id="export-range-full" class="editor-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<textarea readonly style="resize: none; width: 100%; border-radius: 4px;font-family: monospace; font-size: 12px; background:#f3f3f3; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="form-row" style="text-align: right;">'+
|
||||||
|
'<span id="export-format-group" class="button-group">'+
|
||||||
|
'<a id="export-format-mini" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
|
||||||
|
'<a id="export-format-full" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
importNodesDialog = '<div class="form-row">'+
|
||||||
|
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+
|
||||||
|
RED._("clipboard.pasteNodes")+
|
||||||
|
'"></textarea>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.import.import"></label>'+
|
||||||
|
'<span id="import-tab" class="button-group">'+
|
||||||
|
'<a id="import-tab-current" class="editor-button toggle selected" href="#" data-i18n="clipboard.export.current"></a>'+
|
||||||
|
'<a id="import-tab-new" class="editor-button toggle" href="#" data-i18n="clipboard.import.newFlow"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateImport() {
|
||||||
|
var importInput = $("#clipboard-import");
|
||||||
|
var v = importInput.val();
|
||||||
|
v = v.substring(v.indexOf('['),v.lastIndexOf(']')+1);
|
||||||
|
try {
|
||||||
|
JSON.parse(v);
|
||||||
|
importInput.removeClass("input-error");
|
||||||
|
importInput.val(v);
|
||||||
|
$("#clipboard-dialog-ok").button("enable");
|
||||||
|
} catch(err) {
|
||||||
|
if (v !== "") {
|
||||||
|
importInput.addClass("input-error");
|
||||||
|
}
|
||||||
|
$("#clipboard-dialog-ok").button("disable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function importNodes() {
|
||||||
|
dialogContainer.empty();
|
||||||
|
dialogContainer.append($(importNodesDialog));
|
||||||
|
dialogContainer.i18n();
|
||||||
|
|
||||||
|
$("#clipboard-dialog-ok").show();
|
||||||
|
$("#clipboard-dialog-cancel").show();
|
||||||
|
$("#clipboard-dialog-close").hide();
|
||||||
|
$("#clipboard-dialog-copy").hide();
|
||||||
|
$("#clipboard-dialog-ok").button("disable");
|
||||||
|
$("#clipboard-import").keyup(validateImport);
|
||||||
|
$("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)});
|
||||||
|
|
||||||
|
$("#import-tab > a").click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(this).parent().children().removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportNodes() {
|
||||||
|
dialogContainer.empty();
|
||||||
|
dialogContainer.append($(exportNodesDialog));
|
||||||
|
dialogContainer.i18n();
|
||||||
|
|
||||||
|
$("#export-format-group > a").click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(this).parent().children().removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
|
||||||
|
var flow = $("#clipboard-export").val();
|
||||||
|
if (flow.length > 0) {
|
||||||
|
var nodes = JSON.parse(flow);
|
||||||
|
|
||||||
|
var format = $(this).attr('id');
|
||||||
|
if (format === 'export-format-full') {
|
||||||
|
flow = JSON.stringify(nodes,null,4);
|
||||||
|
} else {
|
||||||
|
flow = JSON.stringify(nodes);
|
||||||
|
}
|
||||||
|
$("#clipboard-export").val(flow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#export-range-group > a").click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(this).parent().children().removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
var type = $(this).attr('id');
|
||||||
|
var flow = "";
|
||||||
|
var nodes = null;
|
||||||
|
if (type === 'export-range-selected') {
|
||||||
|
var selection = RED.view.selection();
|
||||||
|
nodes = RED.nodes.createExportableNodeSet(selection.nodes);
|
||||||
|
} else if (type === 'export-range-flow') {
|
||||||
|
var activeWorkspace = RED.workspaces.active();
|
||||||
|
nodes = RED.nodes.filterNodes({z:activeWorkspace});
|
||||||
|
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
|
||||||
|
nodes.unshift(parentNode);
|
||||||
|
nodes = RED.nodes.createExportableNodeSet(nodes);
|
||||||
|
} else if (type === 'export-range-full') {
|
||||||
|
nodes = RED.nodes.createCompleteNodeSet(false);
|
||||||
|
}
|
||||||
|
if (nodes !== null) {
|
||||||
|
if (RED.settings.flowFilePretty) {
|
||||||
|
flow = JSON.stringify(nodes,null,4);
|
||||||
|
} else {
|
||||||
|
flow = JSON.stringify(nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flow.length > 0) {
|
||||||
|
$("#export-copy").removeClass('disabled');
|
||||||
|
} else {
|
||||||
|
$("#export-copy").addClass('disabled');
|
||||||
|
}
|
||||||
|
$("#clipboard-export").val(flow);
|
||||||
|
})
|
||||||
|
|
||||||
|
$("#clipboard-dialog-ok").hide();
|
||||||
|
$("#clipboard-dialog-cancel").hide();
|
||||||
|
$("#clipboard-dialog-copy").hide();
|
||||||
|
$("#clipboard-dialog-close").hide();
|
||||||
|
var selection = RED.view.selection();
|
||||||
|
if (selection.nodes) {
|
||||||
|
$("#export-range-selected").click();
|
||||||
|
} else {
|
||||||
|
$("#export-range-selected").addClass('disabled').removeClass('selected');
|
||||||
|
$("#export-range-flow").click();
|
||||||
|
}
|
||||||
|
if (RED.settings.flowFilePretty) {
|
||||||
|
$("#export-format-full").click();
|
||||||
|
} else {
|
||||||
|
$("#export-format-mini").click();
|
||||||
|
}
|
||||||
|
$("#clipboard-export")
|
||||||
|
.focus(function() {
|
||||||
|
var textarea = $(this);
|
||||||
|
textarea.select();
|
||||||
|
textarea.mouseup(function() {
|
||||||
|
textarea.unbind("mouseup");
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
$("#clipboard-export").focus();
|
||||||
|
if (!document.queryCommandEnabled("copy")) {
|
||||||
|
$("#clipboard-dialog-cancel").hide();
|
||||||
|
$("#clipboard-dialog-close").show();
|
||||||
|
} else {
|
||||||
|
$("#clipboard-dialog-cancel").show();
|
||||||
|
$("#clipboard-dialog-copy").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
},0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDropTarget() {
|
||||||
|
$("#dropTarget").hide();
|
||||||
|
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function() {
|
||||||
|
setupDialogs();
|
||||||
|
RED.events.on("view:selection-changed",function(selection) {
|
||||||
|
if (!selection.nodes) {
|
||||||
|
RED.menu.setDisabled("menu-item-export",true);
|
||||||
|
RED.menu.setDisabled("menu-item-export-clipboard",true);
|
||||||
|
RED.menu.setDisabled("menu-item-export-library",true);
|
||||||
|
} else {
|
||||||
|
RED.menu.setDisabled("menu-item-export",false);
|
||||||
|
RED.menu.setDisabled("menu-item-export-clipboard",false);
|
||||||
|
RED.menu.setDisabled("menu-item-export-library",false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RED.keyboard.add("workspace", /* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
|
||||||
|
RED.keyboard.add("workspace", /* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
|
||||||
|
|
||||||
|
$('#chart').on("dragenter",function(event) {
|
||||||
|
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
$("#dropTarget").css({display:'table'});
|
||||||
|
RED.keyboard.add("*", /* ESCAPE */ 27,hideDropTarget);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#dropTarget').on("dragover",function(event) {
|
||||||
|
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("dragleave",function(event) {
|
||||||
|
hideDropTarget();
|
||||||
|
})
|
||||||
|
.on("drop",function(event) {
|
||||||
|
var data = event.originalEvent.dataTransfer.getData("text/plain");
|
||||||
|
hideDropTarget();
|
||||||
|
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
|
||||||
|
RED.view.importNodes(data);
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
import: importNodes,
|
||||||
|
export: exportNodes
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
* Copyright 2016 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
/**
|
/**
|
||||||
* options:
|
* options:
|
||||||
* - addButton : boolean|string - text for add label, default 'add'
|
* - addButton : boolean|string - text for add label, default 'add'
|
||||||
* - buttons : array - list of custom buttons (objects with fields 'id', 'label', 'icon', 'title', 'click')
|
|
||||||
* - height : number|'auto'
|
* - height : number|'auto'
|
||||||
* - resize : function - called when list as a whole is resized
|
* - resize : function - called when list as a whole is resized
|
||||||
* - resizeItem : function(item) - called to resize individual item
|
* - resizeItem : function(item) - called to resize individual item
|
||||||
@@ -28,22 +27,13 @@
|
|||||||
* - removable : boolean - whether to display delete button on items
|
* - removable : boolean - whether to display delete button on items
|
||||||
* - addItem : function(row,index,itemData) - when an item is added
|
* - addItem : function(row,index,itemData) - when an item is added
|
||||||
* - removeItem : function(itemData) - called when an item is removed
|
* - removeItem : function(itemData) - called when an item is removed
|
||||||
* - filter : function(itemData) - called for each item to determine if it should be shown
|
|
||||||
* - sort : function(itemDataA,itemDataB) - called to sort items
|
|
||||||
* - scrollOnAdd : boolean - whether to scroll to newly added items
|
|
||||||
* methods:
|
* methods:
|
||||||
* - addItem(itemData)
|
* - addItem(itemData)
|
||||||
* - insertItemAt : function(data,index) - add an item at the specified index
|
* - removeItem(itemData)
|
||||||
* - removeItem(itemData, detach) - remove the item. Optionally detach to preserve any event handlers on the item's label
|
|
||||||
* - getItemAt(index)
|
|
||||||
* - indexOf(itemData)
|
|
||||||
* - width(width)
|
* - width(width)
|
||||||
* - height(height)
|
* - height(height)
|
||||||
* - items()
|
* - items()
|
||||||
* - empty()
|
* - empty()
|
||||||
* - filter(filter)
|
|
||||||
* - sort(sort)
|
|
||||||
* - length()
|
|
||||||
*/
|
*/
|
||||||
$.widget( "nodered.editableList", {
|
$.widget( "nodered.editableList", {
|
||||||
_create: function() {
|
_create: function() {
|
||||||
@@ -54,78 +44,34 @@
|
|||||||
this.uiContainer = this.element
|
this.uiContainer = this.element
|
||||||
.wrap( "<div>" )
|
.wrap( "<div>" )
|
||||||
.parent();
|
.parent();
|
||||||
|
this.topContainer = this.uiContainer.wrap("<div>").parent();
|
||||||
|
|
||||||
if (this.options.header) {
|
|
||||||
this.options.header.addClass("red-ui-editableList-header");
|
|
||||||
this.borderContainer = this.uiContainer.wrap("<div>").parent();
|
|
||||||
this.borderContainer.prepend(this.options.header);
|
|
||||||
this.topContainer = this.borderContainer.wrap("<div>").parent();
|
|
||||||
} else {
|
|
||||||
this.topContainer = this.uiContainer.wrap("<div>").parent();
|
|
||||||
}
|
|
||||||
this.topContainer.addClass('red-ui-editableList');
|
this.topContainer.addClass('red-ui-editableList');
|
||||||
if (this.options.class) {
|
|
||||||
this.topContainer.addClass(this.options.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
var buttons = this.options.buttons || [];
|
|
||||||
|
|
||||||
if (this.options.addButton !== false) {
|
if (this.options.addButton !== false) {
|
||||||
var addLabel, addTitle;
|
var addLabel;
|
||||||
if (typeof this.options.addButton === 'string') {
|
if (typeof this.options.addButton === 'string') {
|
||||||
addLabel = this.options.addButton
|
addLabel = this.options.addButton
|
||||||
} else {
|
} else {
|
||||||
if (RED && RED._) {
|
if (RED && RED._) {
|
||||||
addLabel = RED._("editableList.add");
|
addLabel = RED._("editableList.add");
|
||||||
addTitle = RED._("editableList.addTitle");
|
|
||||||
} else {
|
} else {
|
||||||
addLabel = 'add';
|
addLabel = 'add';
|
||||||
addTitle = 'add new item';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buttons.unshift({
|
$('<a href="#" class="editor-button editor-button-small" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>')
|
||||||
label: addLabel,
|
.appendTo(this.topContainer)
|
||||||
icon: "fa fa-plus",
|
.click(function(evt) {
|
||||||
click: function(evt) {
|
|
||||||
that.addItem({});
|
|
||||||
},
|
|
||||||
title: addTitle
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons.forEach(function(button) {
|
|
||||||
var element = $('<button type="button" class="red-ui-button red-ui-button-small red-ui-editableList-addButton" style="margin-top: 4px; margin-right: 5px;"></button>')
|
|
||||||
.appendTo(that.topContainer)
|
|
||||||
.on("click", function(evt) {
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
if (button.click !== undefined) {
|
that.addItem({});
|
||||||
button.click(evt);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (button.id) {
|
|
||||||
element.attr("id", button.id);
|
|
||||||
}
|
|
||||||
if (button.title) {
|
|
||||||
element.attr("title", button.title);
|
|
||||||
}
|
|
||||||
if (button.icon) {
|
|
||||||
element.append($("<i></i>").attr("class", button.icon));
|
|
||||||
}
|
|
||||||
if (button.label) {
|
|
||||||
element.append($("<span></span>").text(" " + button.label));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.element.css("position") === "absolute") {
|
if (this.element.css("position") === "absolute") {
|
||||||
["top","left","bottom","right"].forEach(function(s) {
|
["top","left","bottom","right"].forEach(function(s) {
|
||||||
var v = that.element.css(s);
|
var v = that.element.css(s);
|
||||||
if (v!=="auto" && v!=="") {
|
if (s!=="auto" && s!=="") {
|
||||||
that.topContainer.css(s,v);
|
that.topContainer.css(s,v);
|
||||||
that.uiContainer.css(s,"0");
|
that.uiContainer.css(s,"0");
|
||||||
if (s === "top" && that.options.header) {
|
|
||||||
that.uiContainer.css(s,"20px")
|
|
||||||
}
|
|
||||||
that.element.css(s,'auto');
|
that.element.css(s,'auto');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -134,11 +80,6 @@
|
|||||||
this.uiContainer.css("position","absolute");
|
this.uiContainer.css("position","absolute");
|
||||||
|
|
||||||
}
|
}
|
||||||
if (this.options.header) {
|
|
||||||
this.borderContainer.addClass("red-ui-editableList-border");
|
|
||||||
} else {
|
|
||||||
this.uiContainer.addClass("red-ui-editableList-border");
|
|
||||||
}
|
|
||||||
this.uiContainer.addClass("red-ui-editableList-container");
|
this.uiContainer.addClass("red-ui-editableList-container");
|
||||||
|
|
||||||
this.uiHeight = this.element.height();
|
this.uiHeight = this.element.height();
|
||||||
@@ -154,13 +95,8 @@
|
|||||||
this.uiContainer.css("minHeight",minHeight);
|
this.uiContainer.css("minHeight",minHeight);
|
||||||
this.element.css("minHeight",0);
|
this.element.css("minHeight",0);
|
||||||
}
|
}
|
||||||
var maxHeight = this.element.css("maxHeight");
|
|
||||||
if (maxHeight !== '0px') {
|
|
||||||
this.uiContainer.css("maxHeight",maxHeight);
|
|
||||||
this.element.css("maxHeight",null);
|
|
||||||
}
|
|
||||||
if (this.options.height !== 'auto') {
|
if (this.options.height !== 'auto') {
|
||||||
this.uiContainer.css("overflow-y","auto");
|
this.uiContainer.css("overflow-y","scroll");
|
||||||
if (!isNaN(this.options.height)) {
|
if (!isNaN(this.options.height)) {
|
||||||
this.uiHeight = this.options.height;
|
this.uiHeight = this.options.height;
|
||||||
}
|
}
|
||||||
@@ -218,22 +154,17 @@
|
|||||||
if (this.options.resizeItem) {
|
if (this.options.resizeItem) {
|
||||||
var that = this;
|
var that = this;
|
||||||
this.element.children().each(function(i) {
|
this.element.children().each(function(i) {
|
||||||
that.options.resizeItem($(this).children(".red-ui-editableList-item-content"),i);
|
that.options.resizeItem($(this).find(".red-ui-editableList-item-content"),i);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_destroy: function() {
|
_destroy: function() {
|
||||||
if (this.topContainer) {
|
|
||||||
var tc = this.topContainer;
|
|
||||||
delete this.topContainer;
|
|
||||||
tc.remove();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
_refreshFilter: function() {
|
_refreshFilter: function() {
|
||||||
var that = this;
|
var that = this;
|
||||||
var count = 0;
|
var count = 0;
|
||||||
if (!this.activeFilter) {
|
if (!this.activeFilter) {
|
||||||
return this.element.children().show();
|
this.element.children().show();
|
||||||
}
|
}
|
||||||
var items = this.items();
|
var items = this.items();
|
||||||
items.each(function (i,el) {
|
items.each(function (i,el) {
|
||||||
@@ -258,7 +189,7 @@
|
|||||||
var items = this.element.children();
|
var items = this.element.children();
|
||||||
var that = this;
|
var that = this;
|
||||||
items.sort(function(A,B) {
|
items.sort(function(A,B) {
|
||||||
return that.activeSort($(A).children(".red-ui-editableList-item-content").data('data'),$(B).children(".red-ui-editableList-item-content").data('data'));
|
return that.activeSort($(A).find(".red-ui-editableList-item-content").data('data'),$(B).find(".red-ui-editableList-item-content").data('data'));
|
||||||
});
|
});
|
||||||
$.each(items,function(idx,li) {
|
$.each(items,function(idx,li) {
|
||||||
that.element.append(li);
|
that.element.append(li);
|
||||||
@@ -273,49 +204,10 @@
|
|||||||
this.uiHeight = desiredHeight;
|
this.uiHeight = desiredHeight;
|
||||||
this._resize();
|
this._resize();
|
||||||
},
|
},
|
||||||
getItemAt: function(index) {
|
addItem: function(data) {
|
||||||
var items = this.items();
|
|
||||||
if (index >= 0 && index < items.length) {
|
|
||||||
return $(items[index]).data('data');
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
indexOf: function(data) {
|
|
||||||
var items = this.items();
|
|
||||||
for (var i=0;i<items.length;i++) {
|
|
||||||
if ($(items[i]).data('data') === data) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
},
|
|
||||||
insertItemAt: function(data,index) {
|
|
||||||
var that = this;
|
var that = this;
|
||||||
data = data || {};
|
data = data || {};
|
||||||
var li = $('<li>');
|
var li = $('<li>');
|
||||||
var row = $('<div/>').addClass("red-ui-editableList-item-content").appendTo(li);
|
|
||||||
row.data('data',data);
|
|
||||||
if (this.options.sortable === true) {
|
|
||||||
$('<i class="red-ui-editableList-item-handle fa fa-bars"></i>').appendTo(li);
|
|
||||||
li.addClass("red-ui-editableList-item-sortable");
|
|
||||||
}
|
|
||||||
if (this.options.removable) {
|
|
||||||
var deleteButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(li);
|
|
||||||
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
|
||||||
li.addClass("red-ui-editableList-item-removable");
|
|
||||||
deleteButton.on("click", function(evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
var data = row.data('data');
|
|
||||||
li.addClass("red-ui-editableList-item-deleting")
|
|
||||||
li.fadeOut(300, function() {
|
|
||||||
$(this).remove();
|
|
||||||
if (that.options.removeItem) {
|
|
||||||
that.options.removeItem(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var added = false;
|
var added = false;
|
||||||
if (this.activeSort) {
|
if (this.activeSort) {
|
||||||
var items = this.items();
|
var items = this.items();
|
||||||
@@ -330,17 +222,32 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!added) {
|
if (!added) {
|
||||||
if (index <= 0) {
|
li.appendTo(this.element);
|
||||||
li.prependTo(this.element);
|
}
|
||||||
} else if (index > that.element.children().length-1) {
|
var row = $('<div/>').addClass("red-ui-editableList-item-content").appendTo(li);
|
||||||
li.appendTo(this.element);
|
row.data('data',data);
|
||||||
} else {
|
if (this.options.sortable === true) {
|
||||||
li.insertBefore(this.element.children().eq(index));
|
$('<i class="red-ui-editableList-item-handle fa fa-bars"></i>').appendTo(li);
|
||||||
}
|
li.addClass("red-ui-editableList-item-sortable");
|
||||||
|
}
|
||||||
|
if (this.options.removable) {
|
||||||
|
var deleteButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove editor-button editor-button-small"}).appendTo(li);
|
||||||
|
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
||||||
|
li.addClass("red-ui-editableList-item-removable");
|
||||||
|
deleteButton.click(function() {
|
||||||
|
var data = row.data('data');
|
||||||
|
li.addClass("red-ui-editableList-item-deleting")
|
||||||
|
li.fadeOut(300, function() {
|
||||||
|
$(this).remove();
|
||||||
|
if (that.options.removeItem) {
|
||||||
|
that.options.removeItem(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (this.options.addItem) {
|
if (this.options.addItem) {
|
||||||
var index = that.element.children().length-1;
|
var index = that.element.children().length-1;
|
||||||
// setTimeout(function() {
|
setTimeout(function() {
|
||||||
that.options.addItem(row,index,data);
|
that.options.addItem(row,index,data);
|
||||||
if (that.activeFilter) {
|
if (that.activeFilter) {
|
||||||
try {
|
try {
|
||||||
@@ -356,36 +263,23 @@
|
|||||||
that.uiContainer.scrollTop(that.element.height());
|
that.uiContainer.scrollTop(that.element.height());
|
||||||
},0);
|
},0);
|
||||||
}
|
}
|
||||||
// },0);
|
},0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addItem: function(data) {
|
removeItem: function(data) {
|
||||||
this.insertItemAt(data,this.element.children().length)
|
|
||||||
},
|
|
||||||
addItems: function(items) {
|
|
||||||
for (var i=0; i<items.length;i++) {
|
|
||||||
this.addItem(items[i]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeItem: function(data,detach) {
|
|
||||||
var items = this.element.children().filter(function(f) {
|
var items = this.element.children().filter(function(f) {
|
||||||
return data === $(this).children(".red-ui-editableList-item-content").data('data');
|
return data === $(this).find(".red-ui-editableList-item-content").data('data');
|
||||||
});
|
});
|
||||||
if (detach) {
|
items.remove();
|
||||||
items.detach();
|
|
||||||
} else {
|
|
||||||
items.remove();
|
|
||||||
}
|
|
||||||
if (this.options.removeItem) {
|
if (this.options.removeItem) {
|
||||||
this.options.removeItem(data);
|
this.options.removeItem(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: function() {
|
items: function() {
|
||||||
return this.element.children().map(function(i) { return $(this).children(".red-ui-editableList-item-content"); });
|
return this.element.children().map(function(i) { return $(this).find(".red-ui-editableList-item-content"); });
|
||||||
},
|
},
|
||||||
empty: function() {
|
empty: function() {
|
||||||
this.element.empty();
|
this.element.empty();
|
||||||
this.uiContainer.scrollTop(0);
|
|
||||||
},
|
},
|
||||||
filter: function(filter) {
|
filter: function(filter) {
|
||||||
if (filter !== undefined) {
|
if (filter !== undefined) {
|
||||||
@@ -401,22 +295,6 @@
|
|||||||
},
|
},
|
||||||
length: function() {
|
length: function() {
|
||||||
return this.element.children().length;
|
return this.element.children().length;
|
||||||
},
|
|
||||||
show: function(item) {
|
|
||||||
var items = this.element.children().filter(function(f) {
|
|
||||||
return item === $(this).children(".red-ui-editableList-item-content").data('data');
|
|
||||||
});
|
|
||||||
if (items.length > 0) {
|
|
||||||
this.uiContainer.scrollTop(this.uiContainer.scrollTop()+items.position().top)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getItem: function(li) {
|
|
||||||
var el = li.children(".red-ui-editableList-item-content");
|
|
||||||
if (el.length) {
|
|
||||||
return el.data('data');
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
257
editor/js/ui/common/menu.js
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014, 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.menu = (function() {
|
||||||
|
|
||||||
|
var menuItems = {};
|
||||||
|
|
||||||
|
function createMenuItem(opt) {
|
||||||
|
var item;
|
||||||
|
|
||||||
|
if (opt !== null && opt.id) {
|
||||||
|
var themeSetting = RED.settings.theme("menu."+opt.id);
|
||||||
|
if (themeSetting === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInitialState() {
|
||||||
|
var savedStateActive = isSavedStateActive(opt.id);
|
||||||
|
if (savedStateActive) {
|
||||||
|
link.addClass("active");
|
||||||
|
opt.onselect.call(opt, true);
|
||||||
|
} else if (savedStateActive === false) {
|
||||||
|
link.removeClass("active");
|
||||||
|
opt.onselect.call(opt, false);
|
||||||
|
} else if (opt.hasOwnProperty("selected")) {
|
||||||
|
if (opt.selected) {
|
||||||
|
link.addClass("active");
|
||||||
|
} else {
|
||||||
|
link.removeClass("active");
|
||||||
|
}
|
||||||
|
opt.onselect.call(opt, opt.selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt === null) {
|
||||||
|
item = $('<li class="divider"></li>');
|
||||||
|
} else {
|
||||||
|
item = $('<li></li>');
|
||||||
|
|
||||||
|
if (opt.group) {
|
||||||
|
item.addClass("menu-group-"+opt.group);
|
||||||
|
|
||||||
|
}
|
||||||
|
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
|
||||||
|
if (opt.toggle) {
|
||||||
|
linkContent += '<i class="fa fa-square pull-left"></i>';
|
||||||
|
linkContent += '<i class="fa fa-check-square pull-left"></i>';
|
||||||
|
|
||||||
|
}
|
||||||
|
if (opt.icon !== undefined) {
|
||||||
|
if (/\.png/.test(opt.icon)) {
|
||||||
|
linkContent += '<img src="'+opt.icon+'"/> ';
|
||||||
|
} else {
|
||||||
|
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.sublabel) {
|
||||||
|
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
|
||||||
|
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
|
||||||
|
} else {
|
||||||
|
linkContent += '<span class="menu-label">'+RED.bidi.applyBidiSupport(opt.label, RED.bidi.flags.NS)+'</span>'
|
||||||
|
}
|
||||||
|
|
||||||
|
linkContent += '</a>';
|
||||||
|
|
||||||
|
var link = $(linkContent).appendTo(item);
|
||||||
|
|
||||||
|
menuItems[opt.id] = opt;
|
||||||
|
|
||||||
|
if (opt.onselect) {
|
||||||
|
link.click(function() {
|
||||||
|
if ($(this).parent().hasClass("disabled")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opt.toggle) {
|
||||||
|
var selected = isSelected(opt.id);
|
||||||
|
if (typeof opt.toggle === "string") {
|
||||||
|
if (!selected) {
|
||||||
|
for (var m in menuItems) {
|
||||||
|
if (menuItems.hasOwnProperty(m)) {
|
||||||
|
var mi = menuItems[m];
|
||||||
|
if (mi.id != opt.id && opt.toggle == mi.toggle) {
|
||||||
|
setSelected(mi.id,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelected(opt.id,true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSelected(opt.id, !selected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opt.onselect.call(opt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setInitialState();
|
||||||
|
} else if (opt.href) {
|
||||||
|
link.attr("target","_blank").attr("href",opt.href);
|
||||||
|
} else if (!opt.options) {
|
||||||
|
item.addClass("disabled");
|
||||||
|
link.click(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (opt.options) {
|
||||||
|
item.addClass("dropdown-submenu pull-left");
|
||||||
|
var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item);
|
||||||
|
|
||||||
|
for (var i=0;i<opt.options.length;i++) {
|
||||||
|
var li = createMenuItem(opt.options[i]);
|
||||||
|
if (li) {
|
||||||
|
li.appendTo(submenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opt.disabled) {
|
||||||
|
item.addClass("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return item;
|
||||||
|
|
||||||
|
}
|
||||||
|
function createMenu(options) {
|
||||||
|
|
||||||
|
var button = $("#"+options.id);
|
||||||
|
|
||||||
|
//button.click(function(event) {
|
||||||
|
// $("#"+options.id+"-submenu").show();
|
||||||
|
// event.preventDefault();
|
||||||
|
//});
|
||||||
|
|
||||||
|
|
||||||
|
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
|
||||||
|
|
||||||
|
var lastAddedSeparator = false;
|
||||||
|
for (var i=0;i<options.options.length;i++) {
|
||||||
|
var opt = options.options[i];
|
||||||
|
if (opt !== null || !lastAddedSeparator) {
|
||||||
|
var li = createMenuItem(opt);
|
||||||
|
if (li) {
|
||||||
|
li.appendTo(topMenu);
|
||||||
|
lastAddedSeparator = (opt === null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSavedStateActive(id) {
|
||||||
|
return RED.settings.get("menu-" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(id) {
|
||||||
|
return $("#" + id).hasClass("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSavedState(id, state) {
|
||||||
|
RED.settings.set("menu-" + id, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelected(id,state) {
|
||||||
|
if (isSelected(id) == state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var opt = menuItems[id];
|
||||||
|
if (state) {
|
||||||
|
$("#"+id).addClass("active");
|
||||||
|
} else {
|
||||||
|
$("#"+id).removeClass("active");
|
||||||
|
}
|
||||||
|
if (opt && opt.onselect) {
|
||||||
|
opt.onselect.call(opt,state);
|
||||||
|
}
|
||||||
|
setSavedState(id, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDisabled(id,state) {
|
||||||
|
if (state) {
|
||||||
|
$("#"+id).parent().addClass("disabled");
|
||||||
|
} else {
|
||||||
|
$("#"+id).parent().removeClass("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addItem(id,opt) {
|
||||||
|
var item = createMenuItem(opt);
|
||||||
|
if (opt.group) {
|
||||||
|
var groupItems = $("#"+id+"-submenu").children(".menu-group-"+opt.group);
|
||||||
|
if (groupItems.length === 0) {
|
||||||
|
item.appendTo("#"+id+"-submenu");
|
||||||
|
} else {
|
||||||
|
for (var i=0;i<groupItems.length;i++) {
|
||||||
|
var groupItem = groupItems[i];
|
||||||
|
var label = $(groupItem).find(".menu-label").html();
|
||||||
|
if (opt.label < label) {
|
||||||
|
$(groupItem).before(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i === groupItems.length) {
|
||||||
|
item.appendTo("#"+id+"-submenu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.appendTo("#"+id+"-submenu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function removeItem(id) {
|
||||||
|
$("#"+id).parent().remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAction(id,action) {
|
||||||
|
var opt = menuItems[id];
|
||||||
|
if (opt) {
|
||||||
|
opt.onselect = action;
|
||||||
|
// $("#"+id).click(function() {
|
||||||
|
// if ($(this).parent().hasClass("disabled")) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (menuItems[id].toggle) {
|
||||||
|
// setSelected(id,!isSelected(id));
|
||||||
|
// } else {
|
||||||
|
// menuItems[id].onselect.call(menuItems[id]);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: createMenu,
|
||||||
|
setSelected: setSelected,
|
||||||
|
isSelected: isSelected,
|
||||||
|
setDisabled: setDisabled,
|
||||||
|
addItem: addItem,
|
||||||
|
removeItem: removeItem,
|
||||||
|
setAction: setAction
|
||||||
|
//TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary
|
||||||
|
}
|
||||||
|
})();
|
||||||
79
editor/js/ui/common/popover.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.popover = (function() {
|
||||||
|
|
||||||
|
|
||||||
|
function createPopover(options) {
|
||||||
|
var target = options.target;
|
||||||
|
|
||||||
|
var content = options.content;
|
||||||
|
var delay = options.delay;
|
||||||
|
var timer = null;
|
||||||
|
var active;
|
||||||
|
var div;
|
||||||
|
|
||||||
|
var openPopup = function() {
|
||||||
|
if (active) {
|
||||||
|
div = $('<div class="red-ui-popover"></div>').html(content).appendTo("body");
|
||||||
|
var targetPos = target.offset();
|
||||||
|
var targetWidth = target.width();
|
||||||
|
var targetHeight = target.height();
|
||||||
|
|
||||||
|
var divHeight = div.height();
|
||||||
|
var popoverPos = ((RED.bidi.isMirroringEnabled()) ? targetPos.left-targetWidth-210 : targetPos.left+targetWidth+17);
|
||||||
|
div.css({top: targetPos.top+targetHeight/2-divHeight/2-10,left:popoverPos});
|
||||||
|
div.fadeIn("fast");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var closePopup = function() {
|
||||||
|
if (!active) {
|
||||||
|
if (div) {
|
||||||
|
div.fadeOut("fast",function() {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
div = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.on('mouseenter',function(e) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
active = true;
|
||||||
|
timer = setTimeout(openPopup,delay.show);
|
||||||
|
});
|
||||||
|
target.on('mouseleave', function(e) {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
active = false;
|
||||||
|
setTimeout(closePopup,delay.hide);
|
||||||
|
});
|
||||||
|
var res = {
|
||||||
|
setContent: function(_content) {
|
||||||
|
content = _content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.data('popover',res);
|
||||||
|
return res;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
create: createPopover
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
97
editor/js/ui/common/searchBox.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
(function($) {
|
||||||
|
|
||||||
|
$.widget( "nodered.searchBox", {
|
||||||
|
_create: function() {
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
this.currentTimeout = null;
|
||||||
|
this.lastSent = "";
|
||||||
|
this.element.val("");
|
||||||
|
this.uiContainer = this.element.wrap("<div>").parent();
|
||||||
|
this.uiContainer.addClass("red-ui-searchBox-container");
|
||||||
|
|
||||||
|
$('<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) {
|
||||||
|
e.preventDefault();
|
||||||
|
that.element.val("");
|
||||||
|
that._change("",true);
|
||||||
|
that.element.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resultCount = $('<span>',{class:"red-ui-searchBox-resultCount hide"}).appendTo(this.uiContainer);
|
||||||
|
|
||||||
|
this.element.val("");
|
||||||
|
this.element.on("keydown",function(evt) {
|
||||||
|
if (evt.keyCode === 27) {
|
||||||
|
that.element.val("");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.element.on("keyup",function(evt) {
|
||||||
|
that._change($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.element.on("focus",function() {
|
||||||
|
$("body").one("mousedown",function() {
|
||||||
|
that.element.blur();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
_change: function(val,instant) {
|
||||||
|
var fireEvent = false;
|
||||||
|
if (val === "") {
|
||||||
|
this.clearButton.hide();
|
||||||
|
fireEvent = true;
|
||||||
|
} else {
|
||||||
|
this.clearButton.show();
|
||||||
|
fireEvent = (val.length >= (this.options.minimumLength||0));
|
||||||
|
}
|
||||||
|
var current = this.element.val();
|
||||||
|
fireEvent = fireEvent && current !== this.lastSent;
|
||||||
|
if (fireEvent) {
|
||||||
|
if (!instant && this.options.delay > 0) {
|
||||||
|
clearTimeout(this.currentTimeout);
|
||||||
|
var that = this;
|
||||||
|
this.currentTimeout = setTimeout(function() {
|
||||||
|
that.lastSent = that.element.val();
|
||||||
|
that._trigger("change");
|
||||||
|
},this.options.delay);
|
||||||
|
} else {
|
||||||
|
this._trigger("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: function(val) {
|
||||||
|
if (val === undefined) {
|
||||||
|
return this.element.val();
|
||||||
|
} else {
|
||||||
|
this.element.val(val);
|
||||||
|
this._change(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
count: function(val) {
|
||||||
|
if (val === undefined || val === null || val === "") {
|
||||||
|
this.resultCount.text("").hide();
|
||||||
|
} else {
|
||||||
|
val = RED.bidi.applyBidiSupport(val,RED.bidi.flags.NS);
|
||||||
|
this.resultCount.text(val).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})(jQuery);
|
||||||
350
editor/js/ui/common/tabs.js
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013, 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.tabs = (function() {
|
||||||
|
function createTabs(options) {
|
||||||
|
var tabs = {};
|
||||||
|
var currentTabWidth;
|
||||||
|
var currentActiveTabWidth = 0;
|
||||||
|
|
||||||
|
var ul = $("#"+options.id);
|
||||||
|
var wrapper = ul.wrap( "<div>" ).parent();
|
||||||
|
var scrollContainer = ul.wrap( "<div>" ).parent();
|
||||||
|
wrapper.addClass("red-ui-tabs");
|
||||||
|
if (options.addButton && typeof options.addButton === 'function') {
|
||||||
|
wrapper.addClass("red-ui-tabs-add");
|
||||||
|
var addButton = $('<div class="red-ui-tab-button"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
|
||||||
|
addButton.find('a').click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
options.addButton();
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
var scrollLeft;
|
||||||
|
var scrollRight;
|
||||||
|
|
||||||
|
if (options.scrollable) {
|
||||||
|
wrapper.addClass("red-ui-tabs-scrollable");
|
||||||
|
scrollContainer.addClass("red-ui-tabs-scroll-container");
|
||||||
|
scrollContainer.scroll(updateScroll);
|
||||||
|
scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
|
||||||
|
scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();});
|
||||||
|
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
|
||||||
|
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
|
||||||
|
}
|
||||||
|
function scrollEventHandler(evt,dir) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if ($(this).hasClass('disabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var currentScrollLeft = scrollContainer.scrollLeft();
|
||||||
|
scrollContainer.animate( { scrollLeft: dir }, 100);
|
||||||
|
var interval = setInterval(function() {
|
||||||
|
var newScrollLeft = scrollContainer.scrollLeft()
|
||||||
|
if (newScrollLeft === currentScrollLeft) {
|
||||||
|
clearInterval(interval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentScrollLeft = newScrollLeft;
|
||||||
|
scrollContainer.animate( { scrollLeft: dir }, 100);
|
||||||
|
},100);
|
||||||
|
$(this).one('mouseup',function() {
|
||||||
|
clearInterval(interval);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ul.children().first().addClass("active");
|
||||||
|
ul.children().addClass("red-ui-tab");
|
||||||
|
|
||||||
|
function onTabClick() {
|
||||||
|
activateTab($(this));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateScroll() {
|
||||||
|
if (ul.children().length !== 0) {
|
||||||
|
var sl = scrollContainer.scrollLeft();
|
||||||
|
var scWidth = scrollContainer.width();
|
||||||
|
var ulWidth = ul.width();
|
||||||
|
if (sl === 0) {
|
||||||
|
scrollLeft.hide();
|
||||||
|
} else {
|
||||||
|
scrollLeft.show();
|
||||||
|
}
|
||||||
|
if (sl === ulWidth-scWidth) {
|
||||||
|
scrollRight.hide();
|
||||||
|
} else {
|
||||||
|
scrollRight.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onTabDblClick() {
|
||||||
|
if (options.ondblclick) {
|
||||||
|
options.ondblclick(tabs[$(this).attr('href').slice(1)]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateTab(link) {
|
||||||
|
if (typeof link === "string") {
|
||||||
|
link = ul.find("a[href='#"+link+"']");
|
||||||
|
}
|
||||||
|
if (!link.parent().hasClass("active")) {
|
||||||
|
ul.children().removeClass("active");
|
||||||
|
ul.children().css({"transition": "width 100ms"});
|
||||||
|
link.parent().addClass("active");
|
||||||
|
if (options.scrollable) {
|
||||||
|
var pos = link.parent().position().left;
|
||||||
|
if (pos-21 < 0) {
|
||||||
|
scrollContainer.animate( { scrollLeft: '+='+(pos-50) }, 300);
|
||||||
|
} else if (pos + 120 > scrollContainer.width()) {
|
||||||
|
scrollContainer.animate( { scrollLeft: '+='+(pos + 140-scrollContainer.width()) }, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.onchange) {
|
||||||
|
options.onchange(tabs[link.attr('href').slice(1)]);
|
||||||
|
}
|
||||||
|
updateTabWidths();
|
||||||
|
setTimeout(function() {
|
||||||
|
ul.children().css({"transition": ""});
|
||||||
|
},100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTabWidths() {
|
||||||
|
var tabs = ul.find("li.red-ui-tab");
|
||||||
|
var width = wrapper.width();
|
||||||
|
var tabCount = tabs.size();
|
||||||
|
var tabWidth = (width-12-(tabCount*6))/tabCount;
|
||||||
|
currentTabWidth = (100*tabWidth/width)+"%";
|
||||||
|
currentActiveTabWidth = currentTabWidth+"%";
|
||||||
|
if (options.scrollable) {
|
||||||
|
tabWidth = Math.max(tabWidth,140);
|
||||||
|
currentTabWidth = tabWidth+"px";
|
||||||
|
currentActiveTabWidth = 0;
|
||||||
|
var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount);
|
||||||
|
ul.width(listWidth);
|
||||||
|
updateScroll();
|
||||||
|
} else if (options.hasOwnProperty("minimumActiveTabWidth")) {
|
||||||
|
if (tabWidth < options.minimumActiveTabWidth) {
|
||||||
|
tabCount -= 1;
|
||||||
|
tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount;
|
||||||
|
currentTabWidth = (100*tabWidth/width)+"%";
|
||||||
|
currentActiveTabWidth = options.minimumActiveTabWidth+"px";
|
||||||
|
} else {
|
||||||
|
currentActiveTabWidth = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tabs.css({width:currentTabWidth});
|
||||||
|
if (tabWidth < 50) {
|
||||||
|
ul.find(".red-ui-tab-close").hide();
|
||||||
|
ul.find(".red-ui-tab-icon").hide();
|
||||||
|
ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"})
|
||||||
|
} else {
|
||||||
|
ul.find(".red-ui-tab-close").show();
|
||||||
|
ul.find(".red-ui-tab-icon").show();
|
||||||
|
ul.find(".red-ui-tab-label").css({paddingLeft:""})
|
||||||
|
}
|
||||||
|
if (currentActiveTabWidth !== 0) {
|
||||||
|
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
|
||||||
|
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
|
||||||
|
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
|
||||||
|
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.find("li.red-ui-tab a").on("click",onTabClick).on("dblclick",onTabDblClick);
|
||||||
|
setTimeout(function() {
|
||||||
|
updateTabWidths();
|
||||||
|
},0);
|
||||||
|
|
||||||
|
|
||||||
|
function removeTab(id) {
|
||||||
|
var li = ul.find("a[href='#"+id+"']").parent();
|
||||||
|
if (li.hasClass("active")) {
|
||||||
|
var tab = li.prev();
|
||||||
|
if (tab.size() === 0) {
|
||||||
|
tab = li.next();
|
||||||
|
}
|
||||||
|
activateTab(tab.find("a"));
|
||||||
|
}
|
||||||
|
li.remove();
|
||||||
|
if (options.onremove) {
|
||||||
|
options.onremove(tabs[id]);
|
||||||
|
}
|
||||||
|
delete tabs[id];
|
||||||
|
updateTabWidths();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addTab: function(tab) {
|
||||||
|
tabs[tab.id] = tab;
|
||||||
|
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
|
||||||
|
li.data("tabId",tab.id);
|
||||||
|
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
|
||||||
|
if (tab.icon) {
|
||||||
|
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
|
||||||
|
}
|
||||||
|
var span = $('<span/>',{class:"bidiAware"});
|
||||||
|
span.attr('dir', RED.bidi.resolveBaseTextDir(tab.label)).text(RED.bidi.applyBidiSupport(tab.label,RED.bidi.flags.NS)).appendTo(link);
|
||||||
|
link.on("click",onTabClick);
|
||||||
|
link.on("dblclick",onTabDblClick);
|
||||||
|
if (tab.closeable) {
|
||||||
|
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
|
||||||
|
closeLink.append('<i class="fa fa-times" />');
|
||||||
|
|
||||||
|
closeLink.on("click",function(event) {
|
||||||
|
removeTab(tab.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateTabWidths();
|
||||||
|
if (options.onadd) {
|
||||||
|
options.onadd(tab);
|
||||||
|
}
|
||||||
|
link.attr("title",tab.label);
|
||||||
|
if (ul.find("li.red-ui-tab").size() == 1) {
|
||||||
|
activateTab(link);
|
||||||
|
}
|
||||||
|
if (options.onreorder) {
|
||||||
|
var originalTabOrder;
|
||||||
|
var tabDragIndex;
|
||||||
|
var tabElements = [];
|
||||||
|
var startDragIndex;
|
||||||
|
|
||||||
|
li.draggable({
|
||||||
|
axis:"x",
|
||||||
|
distance: 20,
|
||||||
|
start: function(event,ui) {
|
||||||
|
originalTabOrder = [];
|
||||||
|
tabElements = [];
|
||||||
|
ul.children().each(function(i) {
|
||||||
|
tabElements[i] = {
|
||||||
|
el:$(this),
|
||||||
|
text: $(this).text(),
|
||||||
|
left: $(this).position().left,
|
||||||
|
width: $(this).width()
|
||||||
|
};
|
||||||
|
if ($(this).is(li)) {
|
||||||
|
tabDragIndex = i;
|
||||||
|
startDragIndex = i;
|
||||||
|
}
|
||||||
|
originalTabOrder.push($(this).data("tabId"));
|
||||||
|
});
|
||||||
|
ul.children().each(function(i) {
|
||||||
|
if (i!==tabDragIndex) {
|
||||||
|
$(this).css({
|
||||||
|
position: 'absolute',
|
||||||
|
left: tabElements[i].left+"px",
|
||||||
|
width: tabElements[i].width+2,
|
||||||
|
transition: "left 0.3s"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
if (!li.hasClass('active')) {
|
||||||
|
li.css({'zIndex':1});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drag: function(event,ui) {
|
||||||
|
ui.position.left += tabElements[tabDragIndex].left+scrollContainer.scrollLeft();
|
||||||
|
var tabCenter = ui.position.left + tabElements[tabDragIndex].width/2 - scrollContainer.scrollLeft();
|
||||||
|
for (var i=0;i<tabElements.length;i++) {
|
||||||
|
if (i === tabDragIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (tabCenter > tabElements[i].left && tabCenter < tabElements[i].left+tabElements[i].width) {
|
||||||
|
if (i < tabDragIndex) {
|
||||||
|
tabElements[i].left += tabElements[tabDragIndex].width+8;
|
||||||
|
tabElements[tabDragIndex].el.detach().insertBefore(tabElements[i].el);
|
||||||
|
} else {
|
||||||
|
tabElements[i].left -= tabElements[tabDragIndex].width+8;
|
||||||
|
tabElements[tabDragIndex].el.detach().insertAfter(tabElements[i].el);
|
||||||
|
}
|
||||||
|
tabElements[i].el.css({left:tabElements[i].left+"px"});
|
||||||
|
|
||||||
|
tabElements.splice(i, 0, tabElements.splice(tabDragIndex, 1)[0]);
|
||||||
|
|
||||||
|
tabDragIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stop: function(event,ui) {
|
||||||
|
ul.children().css({position:"relative",left:"",transition:""});
|
||||||
|
if (!li.hasClass('active')) {
|
||||||
|
li.css({zIndex:""});
|
||||||
|
}
|
||||||
|
updateTabWidths();
|
||||||
|
if (startDragIndex !== tabDragIndex) {
|
||||||
|
options.onreorder(originalTabOrder, $.makeArray(ul.children().map(function() { return $(this).data('tabId');})));
|
||||||
|
}
|
||||||
|
activateTab(tabElements[tabDragIndex].el.data('tabId'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeTab: removeTab,
|
||||||
|
activateTab: activateTab,
|
||||||
|
resize: updateTabWidths,
|
||||||
|
count: function() {
|
||||||
|
return ul.find("li.red-ui-tab").size();
|
||||||
|
},
|
||||||
|
contains: function(id) {
|
||||||
|
return ul.find("a[href='#"+id+"']").length > 0;
|
||||||
|
},
|
||||||
|
renameTab: function(id,label) {
|
||||||
|
tabs[id].label = label;
|
||||||
|
var tab = ul.find("a[href='#"+id+"']");
|
||||||
|
tab.attr("title",label);
|
||||||
|
tab.find("span").attr('dir', RED.bidi.resolveBaseTextDir(label)).text(RED.bidi.applyBidiSupport(label,RED.bidi.flags.NS));
|
||||||
|
updateTabWidths();
|
||||||
|
},
|
||||||
|
order: function(order) {
|
||||||
|
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
|
||||||
|
if (existingTabOrder.length !== order.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var i;
|
||||||
|
var match = true;
|
||||||
|
for (i=0;i<order.length;i++) {
|
||||||
|
if (order[i] !== existingTabOrder[i]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var existingTabMap = {};
|
||||||
|
var existingTabs = ul.children().detach().each(function() {
|
||||||
|
existingTabMap[$(this).data("tabId")] = $(this);
|
||||||
|
});
|
||||||
|
for (i=0;i<order.length;i++) {
|
||||||
|
existingTabMap[order[i]].appendTo(ul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
create: createTabs
|
||||||
|
}
|
||||||
|
})();
|
||||||