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;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
var autoComplete = function(options) {
|
const getMatch = function (value, searchValue) {
|
||||||
function getMatch(value, searchValue) {
|
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
||||||
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
const len = idx > -1 ? searchValue.length : 0;
|
||||||
const len = idx > -1 ? searchValue.length : 0;
|
return {
|
||||||
return {
|
index: idx,
|
||||||
index: idx,
|
found: idx > -1,
|
||||||
found: idx > -1,
|
pre: value.substring(0, idx),
|
||||||
pre: value.substring(0,idx),
|
match: value.substring(idx, idx + len),
|
||||||
match: value.substring(idx,idx+len),
|
post: value.substring(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)); }
|
const autoComplete = function (options) {
|
||||||
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
return function (val) {
|
||||||
if(match.post) { els.push($('<span/>').text(match.post)); }
|
|
||||||
return els;
|
|
||||||
}
|
|
||||||
return function(val) {
|
|
||||||
var matches = [];
|
var matches = [];
|
||||||
options.forEach(opt => {
|
options.forEach(opt => {
|
||||||
const optVal = opt.value;
|
const optVal = opt.value;
|
||||||
const optSrc = (opt.source||[]).join(",");
|
const optSrc = (opt.source || []).join(",");
|
||||||
const valMatch = getMatch(optVal, val);
|
const valMatch = getMatch(optVal, val);
|
||||||
const srcMatch = getMatch(optSrc, val);
|
const srcMatch = getMatch(optSrc, val);
|
||||||
if (valMatch.found || srcMatch.found) {
|
if (valMatch.found || srcMatch.found) {
|
||||||
const element = $('<div>',{style: "display: flex"});
|
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 valEl = $('<div/>', { style: "font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1" });
|
||||||
valEl.append(generateSpans(valMatch));
|
valEl.append(generateSpans(valMatch));
|
||||||
valEl.appendTo(element);
|
valEl.appendTo(element);
|
||||||
if (optSrc) {
|
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;
|
return matches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,18 +290,20 @@
|
|||||||
var allOptions = {
|
var allOptions = {
|
||||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
||||||
flow: {value:"flow",label:"flow.",hasValue:true,
|
flow: {value:"flow",label:"flow.",hasValue:true,
|
||||||
options:[],
|
//options:[],
|
||||||
validate:RED.utils.validatePropertyExpression,
|
validate:RED.utils.validatePropertyExpression,
|
||||||
parse: contextParse,
|
parse: contextParse,
|
||||||
export: contextExport,
|
export: contextExport,
|
||||||
valueLabel: contextLabel
|
valueLabel: contextLabel,
|
||||||
|
autoComplete: contextAutoComplete("flow"),
|
||||||
},
|
},
|
||||||
global: {value:"global",label:"global.",hasValue:true,
|
global: {value:"global",label:"global.",hasValue:true,
|
||||||
options:[],
|
//options:[],
|
||||||
validate:RED.utils.validatePropertyExpression,
|
validate:RED.utils.validatePropertyExpression,
|
||||||
parse: contextParse,
|
parse: contextParse,
|
||||||
export: contextExport,
|
export: contextExport,
|
||||||
valueLabel: contextLabel
|
valueLabel: contextLabel,
|
||||||
|
autoComplete: contextAutoComplete("global"),
|
||||||
},
|
},
|
||||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
|
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) {
|
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
|
||||||
@ -1184,7 +1308,7 @@
|
|||||||
this.elementDiv.show();
|
this.elementDiv.show();
|
||||||
if (opt.autoComplete) {
|
if (opt.autoComplete) {
|
||||||
this.input.autoComplete({
|
this.input.autoComplete({
|
||||||
search: opt.autoComplete,
|
search: (v, d) => opt.autoComplete(this.value(), d),
|
||||||
minLength: 0
|
minLength: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user