UNPKG

@kids-reporter/cms-core

Version:
227 lines (212 loc) 8.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _core = require("@keystone-6/core"); var _fields = require("@keystone-6/core/fields"); /** * For `relationship` field, KeystoneJS won't take user input order into account. * Therefore, after the create/update operation is done, * the order of relationship items maybe not be the same order as the user input order. * * This function * - adds monitoring fields in the list * - adds virtual fields in the list. These virtual fields could return relationship items in order. * - decorate `list.hooks.resolveInput` to record the user input order in the monitoring fields * * For example, if we have two lists like * ``` * const User = { * fields: { * name: text(), * } * } * * const Post = { * fields: { * title: text(), * content: text(), * authors: relationship({ref: 'User', many: true}) * } * } * ``` * * if we want to snapshot the authors input order, we can use this function like * ``` * const postList = addManualOrderRelationshipFields([ * { * fieldName: 'manualOrderOfAuthors', * fieldLabel: 'authors 手動排序結果', * targetFieldName: 'authors', // the target field to record the user input order * targetListName: 'User', // relationship list name * targetListLabelField: 'name', // refer to `User.fields.name` * } * ])(Post) * ``` * * `addManualOrderRelationshipFields` will create another JSON field `manualOrderOfAuthors` and virtual field `authorsInInputOrder` in the list, * and decorate `list.hooks.resolveInput` to record the update/create operation, * if the operation modifies the order of the relationship field. * * `authorsInInputOrder` is a virtual field, which means its value is computed on-the-fly, not stored in the database. This virtual field combines relationship field `authors` and monitoring field `manualOrderOfAuthors` to sort the authors in specific input order. */ function addManualOrderRelationshipFields(manualOrderFields = [], list) { var _list$hooks; manualOrderFields.forEach(mo => { var _list$fields; if (!((_list$fields = list.fields) !== null && _list$fields !== void 0 && _list$fields[mo.fieldName])) { list.fields[mo.fieldName] = (0, _fields.json)({ label: mo.fieldLabel, ui: { itemView: { fieldMode: 'read' } } }); } // add virtual field definition addVirtualFieldToReturnItemsInInputOrder(list, mo); }); // decorate `resolveInput` hook list.hooks = list.hooks || {}; const originResolveInput = (_list$hooks = list.hooks) === null || _list$hooks === void 0 ? void 0 : _list$hooks.resolveInput; list.hooks.resolveInput = async props => { let resolvedData = props.resolvedData; if (typeof originResolveInput === 'function') { resolvedData = await originResolveInput(props); } const { item, context } = props; // check if create/update item has the fields // we want to monitor for (let i = 0; i < manualOrderFields.length; i++) { var _resolvedData; const { targetFieldName, fieldName, targetListName, targetListLabelField } = manualOrderFields[i]; // if create/update operation creates/modifies the `${targetFieldName}` field if ((_resolvedData = resolvedData) !== null && _resolvedData !== void 0 && _resolvedData[targetFieldName]) { var _resolvedData$targetF2; let currentOrder = []; // update operation due to `item` not being `undefiend` if (item) { var _resolvedData$targetF; const previousOrder = Array.isArray(item[fieldName]) ? item[fieldName] : []; // user disconnects/removes some relationship items. const disconnectIds = ((_resolvedData$targetF = resolvedData[targetFieldName]) === null || _resolvedData$targetF === void 0 || (_resolvedData$targetF = _resolvedData$targetF.disconnect) === null || _resolvedData$targetF === void 0 ? void 0 : _resolvedData$targetF.map(obj => obj.id.toString())) || []; // filtered out to-be-disconnected relationship items currentOrder = previousOrder.filter(({ id }) => { return disconnectIds.indexOf(id) === -1; }); } // user connects/adds some relationship item. const connectedIds = ((_resolvedData$targetF2 = resolvedData[targetFieldName]) === null || _resolvedData$targetF2 === void 0 || (_resolvedData$targetF2 = _resolvedData$targetF2.connect) === null || _resolvedData$targetF2 === void 0 ? void 0 : _resolvedData$targetF2.map(obj => obj.id.toString())) || []; if (connectedIds.length > 0) { // Query relationship items from the database. // Therefore, we can have other fields to record in the monitoring field const sfToConnect = await context.db[targetListName].findMany({ where: { id: { in: connectedIds } } }); // Database query results are not sorted. // We need to sort them by ourselves. for (let i = 0; i < connectedIds.length; i++) { const sf = sfToConnect.find(obj => { return `${obj.id}` === connectedIds[i]; }); if (sf) { currentOrder.push({ id: sf.id.toString(), [targetListLabelField]: sf[targetListLabelField] }); } } } // records the order in the monitoring field resolvedData[fieldName] = currentOrder; } } return resolvedData; }; return list; } /** * This functiona adds the virtual field onto Keystone6 `list` object. * For instance, if we want to use monitoring field `manualOrderOfAuthors` * to monitor relationship field `authors` in the `post` list object. * * We could write * ``` * addVirtualFieldToReturnItemsInInputOrder(post, { * fieldName: 'manualOrderOfAuthors', // monitoring field * targetFieldName: 'authors', // monitored field * targetListName: 'Author' // relationship list * }) * ``` * after executing, * `post` list will have `authorsInInputOrder` virtual field. * * Return value of this virtual field will follow * `graphql.list(lists.Author.types.output)` GraphQL schema. * * And the GQL resolver will be defined in `resolve` function. */ function addVirtualFieldToReturnItemsInInputOrder(list, manualOrderField) { const virtualFieldName = `${manualOrderField.targetFieldName}InInputOrder`; list.fields[virtualFieldName] = (0, _fields.virtual)({ field: lists => { var _lists$manualOrderFie; return _core.graphql.field({ type: _core.graphql.list(lists === null || lists === void 0 || (_lists$manualOrderFie = lists[manualOrderField.targetListName]) === null || _lists$manualOrderFie === void 0 ? void 0 : _lists$manualOrderFie.types.output), async resolve(item, args, context) { var _context$db; const manualOrderFieldValue = (item === null || item === void 0 ? void 0 : item[manualOrderField.fieldName]) || []; if (!Array.isArray(manualOrderFieldValue)) { return []; } // collect ids from relationship items const ids = manualOrderFieldValue.map(value => value.id); // query items from database const unorderedItems = await ((_context$db = context.db) === null || _context$db === void 0 ? void 0 : _context$db[manualOrderField.targetListName].findMany({ where: { id: { in: ids } } })); const orderedItems = []; // sort items according to input order manualOrderFieldValue.forEach(value => { const writer = unorderedItems.find(ui => `${ui === null || ui === void 0 ? void 0 : ui.id}` === `${value === null || value === void 0 ? void 0 : value.id}`); if (writer) { orderedItems.push(writer); } }); return orderedItems; } }); }, ui: { // keystone somehow needs `ui.query` even we "hidden" the field in the next two lines. query: `{ id, ${manualOrderField.targetFieldName} }`, itemView: { fieldMode: 'hidden' }, createView: { fieldMode: 'hidden' } } }); } var _default = exports.default = addManualOrderRelationshipFields;