mirror of https://github.com/node-red/node-red.git
Merge pull request #5194 from node-red/5082-autocomplete-plugin-support
Add support for plugin sources of autoComplete fieldspull/5196/head
commit
26f9052f51
|
|
@ -673,6 +673,69 @@ var RED = (function() {
|
|||
|
||||
RED.sidebar.show(":first", true);
|
||||
|
||||
RED.plugins.registerPlugin('demo-suggestion-source', {
|
||||
type: 'node-red-flow-suggestion-source',
|
||||
getSuggestions: async function (context) {
|
||||
console.log(context)
|
||||
const suggestion = {
|
||||
label: 'Change/Debug Combo',
|
||||
nodes: [
|
||||
{ id: 'suggestion-1', type: 'change', x: 0, y: 0, wires:[['suggestion-2']] },
|
||||
{ id: 'suggestion-2', type: 'function', outputs: 3, x: 200, y: 0, wires:[['suggestion-3'],['suggestion-4'],['suggestion-6']] },
|
||||
{ id: 'suggestion-3', _g: 'suggestion-group-1', type: 'debug', x: 375, y: -40 },
|
||||
{ id: 'suggestion-4', _g: 'suggestion-group-1', type: 'debug', x: 375, y: 0 },
|
||||
{ id: 'suggestion-5', _g: 'suggestion-group-1', type: 'debug', x: 410, y: 40 },
|
||||
{ id: 'suggestion-6', type: 'junction', wires: [['suggestion-5']], x:325, y:40 }
|
||||
]
|
||||
}
|
||||
const suggestion2 = {
|
||||
label: 'Another Change/Debug Combo',
|
||||
nodes: [
|
||||
{ id: 'suggestion-1', type: 'change', x: 100, y: 100, wires:[['suggestion-2']] },
|
||||
{ id: 'suggestion-2', type: 'function', outputs: 3, x: 300, y: 100 },
|
||||
]
|
||||
}
|
||||
const suggestion3 = {
|
||||
nodes: [
|
||||
{ type: 'mqtt in' }
|
||||
]
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate async operation
|
||||
return [suggestion, suggestion2,suggestion3]
|
||||
}
|
||||
})
|
||||
|
||||
RED.plugins.registerPlugin('demo-mqtt-autocomplete-source', {
|
||||
type: 'node-red-mqtt-topic-autocomplete-source',
|
||||
getCompletions: async function (value, node) {
|
||||
return [
|
||||
'home/upstairs/temperature',
|
||||
'home/upstairs/humidity',
|
||||
'home/upstairs/pressure',
|
||||
'home/downstairs/temperature',
|
||||
'home/temperature',
|
||||
'home/humidity',
|
||||
'home/pressure'
|
||||
].map(t => t)
|
||||
}
|
||||
})
|
||||
|
||||
RED.plugins.registerPlugin('demo-mqtt-autocomplete-source-2', {
|
||||
type: 'node-red-mqtt-topic-autocomplete-source',
|
||||
getCompletions: async function (value, node) {
|
||||
return [
|
||||
'away/upstairs/temperature',
|
||||
'away/upstairs/humidity',
|
||||
'away/upstairs/pressure',
|
||||
'away/downstairs/temperature',
|
||||
'away/temperature',
|
||||
'away/humidity',
|
||||
'away/pressure'
|
||||
].map(t => t)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
setTimeout(function() {
|
||||
loader.end();
|
||||
checkTelemetry(function () {
|
||||
|
|
|
|||
|
|
@ -15,14 +15,25 @@
|
|||
* The function must either return auto-complete options, or pass them
|
||||
* to the optional 'done' parameter.
|
||||
* If the function signature includes 'done', it must be used
|
||||
* The auto-complete options can either be an array of strings, or an array of objects in the form:
|
||||
* {
|
||||
* value: String : the value to insert if selected
|
||||
* label: String|DOM Element : the label to display in the dropdown.
|
||||
* }
|
||||
*
|
||||
* minLength: number
|
||||
* If `minLength` is 0, pressing down arrow will show the list
|
||||
*
|
||||
* completionPluginType: String
|
||||
* If provided instead of `search`, this will look for any plugins
|
||||
* registered with the given type that implement the `getCompletions` function. This
|
||||
* can be an async function that returns an array of string completions. It does not support
|
||||
* the full options object as above.
|
||||
*
|
||||
* The auto-complete options should be an array of objects in the form:
|
||||
* {
|
||||
* value: String : the value to insert if selected
|
||||
* label: String|DOM Element : the label to display in the dropdown.
|
||||
* }
|
||||
* node: Node
|
||||
* If provided, this will be passed to the `getCompletions` function of the plugin
|
||||
* to allow the plugin to provide context-aware completions.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
|
@ -31,6 +42,54 @@
|
|||
const that = this;
|
||||
this.completionMenuShown = false;
|
||||
this.options.minLength = parseInteger(this.options.minLength, 1, 0);
|
||||
if (!this.options.search) {
|
||||
// No search function provided; nothing to provide completions
|
||||
if (this.options.completionPluginType) {
|
||||
const plugins = RED.plugins.getPluginsByType(this.options.completionPluginType)
|
||||
if (plugins.length > 0) {
|
||||
this.options.search = async function (value, done) {
|
||||
// for now, only support a single plugin
|
||||
const promises = plugins.map(plugin => plugin.getCompletions(value, that.options.context))
|
||||
const completions = (await Promise.all(promises)).flat()
|
||||
const results = []
|
||||
completions.forEach(completion => {
|
||||
const element = $('<div>',{style: "display: flex"})
|
||||
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" })
|
||||
const valMatch = getMatch(completion, value)
|
||||
if (valMatch.found) {
|
||||
valEl.append(generateSpans(valMatch))
|
||||
valEl.appendTo(element)
|
||||
results.push({
|
||||
value: completion,
|
||||
label: element,
|
||||
match: valMatch
|
||||
})
|
||||
}
|
||||
results.sort((a, b) => {
|
||||
if (a.match.exact && !b.match.exact) {
|
||||
return -1;
|
||||
} else if (!a.match.exact && b.match.exact) {
|
||||
return 1;
|
||||
} else if (a.match.index < b.match.index) {
|
||||
return -1;
|
||||
} else if (a.match.index > b.match.index) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
})
|
||||
done(results)
|
||||
}
|
||||
} else {
|
||||
// No search function and no plugins found
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// No search function and no plugin type provided
|
||||
return
|
||||
}
|
||||
}
|
||||
this.options.search = this.options.search || function() { return [] };
|
||||
this.element.addClass("red-ui-autoComplete");
|
||||
this.element.on("keydown.red-ui-autoComplete", function(evt) {
|
||||
|
|
@ -92,6 +151,11 @@
|
|||
}
|
||||
return
|
||||
}
|
||||
if (typeof completions[0] === "string") {
|
||||
completions = completions.map(function(c) {
|
||||
return { value: c, label: c };
|
||||
});
|
||||
}
|
||||
if (that.completionMenuShown) {
|
||||
that.menu.options(completions);
|
||||
} else {
|
||||
|
|
@ -123,4 +187,25 @@
|
|||
if(isNaN(n) || n < min || n > max) { n = def || 0; }
|
||||
return n;
|
||||
}
|
||||
// TODO: this is copied from typedInput - should be a shared utility
|
||||
function getMatch(value, searchValue) {
|
||||
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
||||
const len = idx > -1 ? searchValue.length : 0;
|
||||
return {
|
||||
index: idx,
|
||||
found: idx > -1,
|
||||
pre: value.substring(0,idx),
|
||||
match: value.substring(idx,idx+len),
|
||||
post: value.substring(idx+len),
|
||||
exact: idx === 0 && value.length === searchValue.length
|
||||
}
|
||||
}
|
||||
// TODO: this is copied from typedInput - should be a shared utility
|
||||
function generateSpans(match) {
|
||||
const els = [];
|
||||
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
||||
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
||||
if(match.post) { els.push($('<span/>').text(match.post)); }
|
||||
return els;
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
|
|||
|
|
@ -477,6 +477,16 @@
|
|||
}
|
||||
return RED._("node-red:mqtt.errors.invalid-topic");
|
||||
}
|
||||
function setupTopicField(node, input) {
|
||||
input.autoComplete({
|
||||
minLength: 0,
|
||||
completionPluginType: 'node-red-mqtt-topic-autocomplete-source',
|
||||
context: {
|
||||
node
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RED.nodes.registerType('mqtt-broker',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
|
|
@ -852,6 +862,7 @@
|
|||
},
|
||||
oneditprepare: function() {
|
||||
const node = this;
|
||||
setupTopicField(this, $("#node-input-topic"));
|
||||
const isV5Broker = function() {
|
||||
var confNode = RED.nodes.node($("#node-input-broker").val());
|
||||
return confNode && confNode.protocolVersion === "5";
|
||||
|
|
@ -930,6 +941,7 @@
|
|||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
setupTopicField(this, $("#node-input-topic"));
|
||||
|
||||
function showHideDynamicFields() {
|
||||
var confNode = RED.nodes.node($("#node-input-broker").val());
|
||||
|
|
|
|||
Loading…
Reference in New Issue