247 lines
7.1 KiB
JavaScript
247 lines
7.1 KiB
JavaScript
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;
|
|
}
|