merge newui branch
This commit is contained in:
246
ui/src/collections/autocomplete.utils.js
Normal file
246
ui/src/collections/autocomplete.utils.js
Normal file
@@ -0,0 +1,246 @@
|
||||
window.app = window.app || {};
|
||||
window.app.utils = window.app.utils || {};
|
||||
|
||||
const defaultOptions = {
|
||||
maxKeys: 30,
|
||||
requestKeys: true,
|
||||
collectionJoinKeys: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates an array with the suitable autocomplete words for the targeted collection.
|
||||
*
|
||||
* @param {string|Object} targetCollection Collection model or identifier.
|
||||
* @param {string} word The autocomplete triggered "word".
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.maxKeys] The max number of returned autocomplete keys (default to 30).
|
||||
* @param {boolean} [options.requestKeys] Whether to include the `@request.*` keys (default to true).
|
||||
* @param {boolean} [options.collectionJoinKeys] Whether to include the `@collection.*` keys (default to true).
|
||||
* @return {Array}
|
||||
*/
|
||||
window.app.utils.collectionAutocompleteKeys = function(targetCollection, word, options = {}) {
|
||||
if (!targetCollection || !word || !app.store.collections?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
let result = collectionFieldsAutocomplete(word, app.store.collections, targetCollection).sort(keysSort);
|
||||
|
||||
if (options.requestKeys) {
|
||||
const keys = requestFieldsAutocomplete(word, app.store.collections, targetCollection).sort(keysSort);
|
||||
for (let k of keys) {
|
||||
result.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.collectionJoinKeys) {
|
||||
const keys = collectionJoinAutocomplete(word, app.store.collections).sort(keysSort);
|
||||
for (let k of keys) {
|
||||
result.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length > options.maxKeys) {
|
||||
return result.slice(0, options.maxKeys);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// sort shorter keys first
|
||||
function keysSort(a, b) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates recursively a list with all the autocomplete field keys
|
||||
* for the collectionNameOrId collection.
|
||||
*
|
||||
* @param {string} word
|
||||
* @param {Array} collections
|
||||
* @param {string|object} collection
|
||||
* @param {string} [prefix]
|
||||
* @param {number} [level]
|
||||
* @return {Array}
|
||||
*/
|
||||
function collectionFieldsAutocomplete(word, collections, collection, prefix = "", level = 0) {
|
||||
if (!word || level >= 4) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof collection == "string") {
|
||||
collection = collections.find((c) => c.name == collection || c.id == collection);
|
||||
}
|
||||
if (!collection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
word = word.toLowerCase();
|
||||
|
||||
const isAuth = collection.type == "auth";
|
||||
|
||||
const result = app.utils
|
||||
.getAllCollectionIdentifiers(collection, prefix)
|
||||
.filter((item) => item.toLowerCase().includes(word));
|
||||
|
||||
const fields = collection.fields || [];
|
||||
for (const field of fields) {
|
||||
if (field.type == "password" || (isAuth && field.name == "tokenKey")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const keys = [];
|
||||
|
||||
// special @request.body modifiers
|
||||
if (prefix == "@request.body.") {
|
||||
keys.push(prefix + field.name + ":changed");
|
||||
keys.push(prefix + field.name + ":isset");
|
||||
}
|
||||
|
||||
if (typeof app.fieldTypes[field.type]?.filterModifiers == "function") {
|
||||
const modifiers = app.fieldTypes[field.type]?.filterModifiers(field) || [];
|
||||
for (const m of modifiers) {
|
||||
keys.push(prefix + field.name + ":" + m);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of keys) {
|
||||
if (key.toLowerCase().includes(word)) {
|
||||
result.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// add relation fields
|
||||
if (field.type == "relation" && field.collectionId) {
|
||||
const subKeys = collectionFieldsAutocomplete(
|
||||
word,
|
||||
collections,
|
||||
field.collectionId,
|
||||
prefix + field.name + ".",
|
||||
level + 1,
|
||||
);
|
||||
for (const k of subKeys) {
|
||||
result.push(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add back relations
|
||||
for (const ref of collections) {
|
||||
const refFields = ref.fields || [];
|
||||
for (const field of refFields) {
|
||||
if (field.type != "relation" || field.collectionId != collection.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = prefix + ref.name + "_via_" + field.name;
|
||||
const subKeys = collectionFieldsAutocomplete(word, collections, ref, key + ".", level + 2); // +2 to reduce the recursive results
|
||||
for (const k of subKeys) {
|
||||
result.push(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list with all @request.* autocomplete field keys.
|
||||
*
|
||||
* @param {string} word
|
||||
* @param {Array} collections
|
||||
* @param {string|object} baseCollection (used for the `@request.body.*` fields)
|
||||
* @return {Array}
|
||||
*/
|
||||
function requestFieldsAutocomplete(word, collections, baseCollection) {
|
||||
if (!word) {
|
||||
return [];
|
||||
}
|
||||
|
||||
word = word.toLowerCase();
|
||||
|
||||
const result = [];
|
||||
|
||||
const common = [
|
||||
"@request.context",
|
||||
"@request.method",
|
||||
"@request.query.",
|
||||
"@request.body.",
|
||||
"@request.headers.",
|
||||
"@request.auth.collectionId",
|
||||
"@request.auth.collectionName",
|
||||
];
|
||||
for (const w of common) {
|
||||
if (!w.toLowerCase().includes(word)) {
|
||||
continue;
|
||||
}
|
||||
result.push(w);
|
||||
}
|
||||
|
||||
// load auth collection fields
|
||||
const authCollections = collections.filter((collection) => collection.type === "auth");
|
||||
for (const collection of authCollections) {
|
||||
if (collection.system) {
|
||||
continue; // skip system collections for now
|
||||
}
|
||||
const authKeys = collectionFieldsAutocomplete(word, collections, collection, "@request.auth.");
|
||||
for (const k of authKeys) {
|
||||
app.utils.pushUnique(result, k);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof baseCollection == "string") {
|
||||
baseCollection = collections.find((c) => c.name == baseCollection || c.id == baseCollection);
|
||||
}
|
||||
if (!baseCollection) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// load base collection fields into @request.body.*
|
||||
const keys = collectionFieldsAutocomplete(word, collections, baseCollection, "@request.body.");
|
||||
for (const key of keys) {
|
||||
result.push(key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list with all @collection.* autocomplete field keys.
|
||||
*
|
||||
* @param {string} word
|
||||
* @param {Array} collections
|
||||
* @return {Array}
|
||||
*/
|
||||
function collectionJoinAutocomplete(word, collections) {
|
||||
const result = [];
|
||||
|
||||
let basePrefix = "@collection.";
|
||||
|
||||
// to avoid unnecessary loading all @collection.* keys match with the word first
|
||||
let base, search;
|
||||
if (basePrefix.length < word.length) {
|
||||
base = word;
|
||||
search = basePrefix;
|
||||
} else {
|
||||
base = basePrefix;
|
||||
search = word;
|
||||
}
|
||||
if (!base.includes(search)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const collection of collections) {
|
||||
if (collection.system) {
|
||||
continue; // skip system collections for now
|
||||
}
|
||||
|
||||
const keys = collectionFieldsAutocomplete(word, collections, collection, basePrefix + collection.name + ".");
|
||||
for (const key of keys) {
|
||||
result.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user