mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Add autoComplete for flow
and global
types
This commit is contained in:
parent
33cf34f7c7
commit
e7c7621b16
@ -54,35 +54,157 @@
|
||||
return icon;
|
||||
}
|
||||
|
||||
var autoComplete = function(options) {
|
||||
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),
|
||||
const getMatch = function (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),
|
||||
};
|
||||
};
|
||||
|
||||
const generateSpans = function (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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Autocomplete for flow and global types
|
||||
* @param {"flow"|"global"} type Either `flow` or `global`
|
||||
* @returns void
|
||||
*/
|
||||
const contextAutoComplete = function (type) {
|
||||
const currentSearch = {
|
||||
completeKey: "",
|
||||
searchKey: "",
|
||||
store: "",
|
||||
options: [],
|
||||
};
|
||||
|
||||
function getContext(contextUrl) {
|
||||
return new Promise((resolve, _reject) => {
|
||||
$.getJSON(`context/${contextUrl}`)
|
||||
.done((data) => {
|
||||
try {
|
||||
const decoded = Object.entries(data).reduce((acc, [store, value]) => {
|
||||
acc[store] = Object.entries(value).reduce((acc, [k, v]) => {
|
||||
acc[k] = RED.utils.decodeObject(v.msg, v.format);
|
||||
return acc;
|
||||
}, {});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
resolve(decoded);
|
||||
} catch (error) {
|
||||
console.error("Failed to load context:", error);
|
||||
resolve({});
|
||||
}
|
||||
})
|
||||
.fail(() => resolve({}));
|
||||
});
|
||||
}
|
||||
|
||||
function getContextOptions(contextUrl, store, keyParts) {
|
||||
return new Promise(async (resolve, _reject) => {
|
||||
const data = await getContext(contextUrl);
|
||||
|
||||
if (!(store in data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const options = keyParts.reduce((options, key, i) => {
|
||||
if (typeof options === "object" && key in options) {
|
||||
options = options[key];
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
|
||||
return options;
|
||||
}, data[store]);
|
||||
|
||||
resolve(Object.keys(options || {}));
|
||||
});
|
||||
}
|
||||
|
||||
function updateSearch(value, contextUrl) {
|
||||
return new Promise(async (resolve, _reject) => {
|
||||
const { key, store } = RED.utils.parseContextKey(value);
|
||||
const keyParts = key.split(".");
|
||||
const searchKey = keyParts.pop();
|
||||
const completeKey = keyParts.join(".");
|
||||
|
||||
// "" or "foo."
|
||||
const valueEnd = /^.?$|\.$/.test(value);
|
||||
const completeKeyDiff = completeKey !== currentSearch.completeKey;
|
||||
const storeDiff = store !== currentSearch.store;
|
||||
if (valueEnd || completeKeyDiff || storeDiff) {
|
||||
currentSearch.completeKey = completeKey;
|
||||
currentSearch.store = store;
|
||||
currentSearch.options = await getContextOptions(contextUrl, store, keyParts);
|
||||
}
|
||||
|
||||
currentSearch.searchKey = searchKey;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return function (value, done) {
|
||||
let contextUrl;
|
||||
|
||||
if (type === "global") {
|
||||
contextUrl = "global";
|
||||
} else if (type === "flow") {
|
||||
contextUrl = `flow/${RED.workspaces.active()}`;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
updateSearch(value, contextUrl).then(() => {
|
||||
const { completeKey, options, searchKey } = currentSearch;
|
||||
const matches = options
|
||||
.reduce((opts, optKey) => {
|
||||
const keyMatch = getMatch(optKey, searchKey);
|
||||
|
||||
if (keyMatch.found) {
|
||||
const element = $("<div>", { style: "display: flex" });
|
||||
|
||||
$("<div/>", { style: "font-family: var(--red-ui-monospace-font); white-space: nowrap; overflow: hidden; flex-grow: 1" })
|
||||
.append(generateSpans(keyMatch))
|
||||
.appendTo(element);
|
||||
|
||||
opts.push({
|
||||
value: `${completeKey}${completeKey && "."}${optKey}`,
|
||||
label: element,
|
||||
i: keyMatch.index,
|
||||
});
|
||||
}
|
||||
|
||||
return opts;
|
||||
}, [])
|
||||
.sort(function (a, b) { return a.i - b.i });
|
||||
|
||||
done(matches);
|
||||
}).catch();
|
||||
}
|
||||
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;
|
||||
}
|
||||
return function(val) {
|
||||
}
|
||||
|
||||
const autoComplete = function (options) {
|
||||
return function (val) {
|
||||
var matches = [];
|
||||
options.forEach(opt => {
|
||||
const optVal = opt.value;
|
||||
const optSrc = (opt.source||[]).join(",");
|
||||
const optSrc = (opt.source || []).join(",");
|
||||
const valMatch = getMatch(optVal, val);
|
||||
const srcMatch = getMatch(optSrc, val);
|
||||
if (valMatch.found || srcMatch.found) {
|
||||
const element = $('<div>',{style: "display: flex"});
|
||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
||||
const element = $('<div>', { style: "display: flex" });
|
||||
const valEl = $('<div/>', { style: "font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1" });
|
||||
valEl.append(generateSpans(valMatch));
|
||||
valEl.appendTo(element);
|
||||
if (optSrc) {
|
||||
@ -97,7 +219,7 @@
|
||||
});
|
||||
}
|
||||
})
|
||||
matches.sort(function(A,B){return A.i-B.i})
|
||||
matches.sort(function (A, B) { return A.i - B.i })
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
@ -168,18 +290,20 @@
|
||||
var allOptions = {
|
||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
||||
flow: {value:"flow",label:"flow.",hasValue:true,
|
||||
options:[],
|
||||
//options:[],
|
||||
validate:RED.utils.validatePropertyExpression,
|
||||
parse: contextParse,
|
||||
export: contextExport,
|
||||
valueLabel: contextLabel
|
||||
valueLabel: contextLabel,
|
||||
autoComplete: contextAutoComplete("flow"),
|
||||
},
|
||||
global: {value:"global",label:"global.",hasValue:true,
|
||||
options:[],
|
||||
//options:[],
|
||||
validate:RED.utils.validatePropertyExpression,
|
||||
parse: contextParse,
|
||||
export: contextExport,
|
||||
valueLabel: contextLabel
|
||||
valueLabel: contextLabel,
|
||||
autoComplete: contextAutoComplete("global"),
|
||||
},
|
||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
|
||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
|
||||
@ -1184,7 +1308,7 @@
|
||||
this.elementDiv.show();
|
||||
if (opt.autoComplete) {
|
||||
this.input.autoComplete({
|
||||
search: opt.autoComplete,
|
||||
search: (v, d) => opt.autoComplete(this.value(), d),
|
||||
minLength: 0
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user