@strapi/content-manager 5.60.0 → 5.61.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/constants/customOrder.js +14 -0
- package/dist/admin/constants/customOrder.js.map +1 -0
- package/dist/admin/constants/customOrder.mjs +10 -0
- package/dist/admin/constants/customOrder.mjs.map +1 -0
- package/dist/admin/hooks/useDocument.js +22 -11
- package/dist/admin/hooks/useDocument.js.map +1 -1
- package/dist/admin/hooks/useDocument.mjs +22 -11
- package/dist/admin/hooks/useDocument.mjs.map +1 -1
- package/dist/admin/hooks/useDocumentLayout.js.map +1 -1
- package/dist/admin/hooks/useDocumentLayout.mjs.map +1 -1
- package/dist/admin/hooks/useHasBeenNearViewport.js +70 -0
- package/dist/admin/hooks/useHasBeenNearViewport.js.map +1 -0
- package/dist/admin/hooks/useHasBeenNearViewport.mjs +46 -0
- package/dist/admin/hooks/useHasBeenNearViewport.mjs.map +1 -0
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.js +102 -32
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.mjs +104 -35
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.js +20 -17
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.mjs +20 -17
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js +15 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs +17 -3
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.js +21 -0
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.mjs +22 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js +4 -7
- package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs +4 -7
- package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js +46 -10
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs +47 -11
- package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormLayout.js +201 -51
- package/dist/admin/pages/EditView/components/FormLayout.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormLayout.mjs +202 -53
- package/dist/admin/pages/EditView/components/FormLayout.mjs.map +1 -1
- package/dist/admin/pages/ListConfiguration/components/Settings.js +31 -2
- package/dist/admin/pages/ListConfiguration/components/Settings.js.map +1 -1
- package/dist/admin/pages/ListConfiguration/components/Settings.mjs +31 -2
- package/dist/admin/pages/ListConfiguration/components/Settings.mjs.map +1 -1
- package/dist/admin/pages/ListView/ListViewPage.js +196 -132
- package/dist/admin/pages/ListView/ListViewPage.js.map +1 -1
- package/dist/admin/pages/ListView/ListViewPage.mjs +196 -132
- package/dist/admin/pages/ListView/ListViewPage.mjs.map +1 -1
- package/dist/admin/pages/ListView/components/CustomOrder.js +571 -0
- package/dist/admin/pages/ListView/components/CustomOrder.js.map +1 -0
- package/dist/admin/pages/ListView/components/CustomOrder.mjs +546 -0
- package/dist/admin/pages/ListView/components/CustomOrder.mjs.map +1 -0
- package/dist/admin/pages/ListView/components/TableActions.js +4 -1
- package/dist/admin/pages/ListView/components/TableActions.js.map +1 -1
- package/dist/admin/pages/ListView/components/TableActions.mjs +4 -1
- package/dist/admin/pages/ListView/components/TableActions.mjs.map +1 -1
- package/dist/admin/services/documents.js +22 -1
- package/dist/admin/services/documents.js.map +1 -1
- package/dist/admin/services/documents.mjs +21 -2
- package/dist/admin/services/documents.mjs.map +1 -1
- package/dist/admin/src/constants/customOrder.d.ts +8 -0
- package/dist/admin/src/exports.d.ts +1 -1
- package/dist/admin/src/hooks/useDocument.d.ts +4 -0
- package/dist/admin/src/hooks/useDocumentLayout.d.ts +6 -2
- package/dist/admin/src/hooks/useHasBeenNearViewport.d.ts +13 -0
- package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.d.ts +13 -1
- package/dist/admin/src/pages/EditView/components/FormLayout.d.ts +11 -1
- package/dist/admin/src/pages/ListView/components/CustomOrder.d.ts +72 -0
- package/dist/admin/src/services/documents.d.ts +10 -2
- package/dist/admin/translations/en.json.js +9 -0
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +9 -0
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/server/audit-logs.js +36 -0
- package/dist/server/audit-logs.js.map +1 -0
- package/dist/server/audit-logs.mjs +33 -0
- package/dist/server/audit-logs.mjs.map +1 -0
- package/dist/server/bootstrap.js +8 -0
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/server/bootstrap.mjs +8 -0
- package/dist/server/bootstrap.mjs.map +1 -1
- package/dist/server/controllers/collection-types.js +8 -0
- package/dist/server/controllers/collection-types.js.map +1 -1
- package/dist/server/controllers/collection-types.mjs +8 -0
- package/dist/server/controllers/collection-types.mjs.map +1 -1
- package/dist/server/controllers/content-types.js +15 -0
- package/dist/server/controllers/content-types.js.map +1 -1
- package/dist/server/controllers/content-types.mjs +16 -1
- package/dist/server/controllers/content-types.mjs.map +1 -1
- package/dist/server/controllers/index.js +3 -1
- package/dist/server/controllers/index.js.map +1 -1
- package/dist/server/controllers/index.mjs +2 -0
- package/dist/server/controllers/index.mjs.map +1 -1
- package/dist/server/controllers/relations.js +5 -1
- package/dist/server/controllers/relations.js.map +1 -1
- package/dist/server/controllers/relations.mjs +5 -1
- package/dist/server/controllers/relations.mjs.map +1 -1
- package/dist/server/controllers/single-types.js +10 -1
- package/dist/server/controllers/single-types.js.map +1 -1
- package/dist/server/controllers/single-types.mjs +10 -1
- package/dist/server/controllers/single-types.mjs.map +1 -1
- package/dist/server/controllers/validation/model-configuration.js +2 -1
- package/dist/server/controllers/validation/model-configuration.js.map +1 -1
- package/dist/server/controllers/validation/model-configuration.mjs +2 -1
- package/dist/server/controllers/validation/model-configuration.mjs.map +1 -1
- package/dist/server/custom-order/constants.js +21 -0
- package/dist/server/custom-order/constants.js.map +1 -0
- package/dist/server/custom-order/constants.mjs +17 -0
- package/dist/server/custom-order/constants.mjs.map +1 -0
- package/dist/server/custom-order/controllers/custom-order.js +77 -0
- package/dist/server/custom-order/controllers/custom-order.js.map +1 -0
- package/dist/server/custom-order/controllers/custom-order.mjs +75 -0
- package/dist/server/custom-order/controllers/custom-order.mjs.map +1 -0
- package/dist/server/custom-order/controllers/index.js +10 -0
- package/dist/server/custom-order/controllers/index.js.map +1 -0
- package/dist/server/custom-order/controllers/index.mjs +8 -0
- package/dist/server/custom-order/controllers/index.mjs.map +1 -0
- package/dist/server/custom-order/index.js +71 -0
- package/dist/server/custom-order/index.js.map +1 -0
- package/dist/server/custom-order/index.mjs +69 -0
- package/dist/server/custom-order/index.mjs.map +1 -0
- package/dist/server/custom-order/routes/custom-order.js +38 -0
- package/dist/server/custom-order/routes/custom-order.js.map +1 -0
- package/dist/server/custom-order/routes/custom-order.mjs +36 -0
- package/dist/server/custom-order/routes/custom-order.mjs.map +1 -0
- package/dist/server/custom-order/routes/index.js +13 -0
- package/dist/server/custom-order/routes/index.js.map +1 -0
- package/dist/server/custom-order/routes/index.mjs +11 -0
- package/dist/server/custom-order/routes/index.mjs.map +1 -0
- package/dist/server/custom-order/services/custom-order.js +286 -0
- package/dist/server/custom-order/services/custom-order.js.map +1 -0
- package/dist/server/custom-order/services/custom-order.mjs +284 -0
- package/dist/server/custom-order/services/custom-order.mjs.map +1 -0
- package/dist/server/custom-order/services/document-middleware.js +83 -0
- package/dist/server/custom-order/services/document-middleware.js.map +1 -0
- package/dist/server/custom-order/services/document-middleware.mjs +81 -0
- package/dist/server/custom-order/services/document-middleware.mjs.map +1 -0
- package/dist/server/custom-order/services/index.js +10 -0
- package/dist/server/custom-order/services/index.js.map +1 -0
- package/dist/server/custom-order/services/index.mjs +8 -0
- package/dist/server/custom-order/services/index.mjs.map +1 -0
- package/dist/server/custom-order/services/utils.js +152 -0
- package/dist/server/custom-order/services/utils.js.map +1 -0
- package/dist/server/custom-order/services/utils.mjs +148 -0
- package/dist/server/custom-order/services/utils.mjs.map +1 -0
- package/dist/server/custom-order/utils.js +13 -0
- package/dist/server/custom-order/utils.js.map +1 -0
- package/dist/server/custom-order/utils.mjs +10 -0
- package/dist/server/custom-order/utils.mjs.map +1 -0
- package/dist/server/history/constants.js +2 -1
- package/dist/server/history/constants.js.map +1 -1
- package/dist/server/history/constants.mjs +2 -1
- package/dist/server/history/constants.mjs.map +1 -1
- package/dist/server/history/controllers/history-version.js +6 -2
- package/dist/server/history/controllers/history-version.js.map +1 -1
- package/dist/server/history/controllers/history-version.mjs +6 -2
- package/dist/server/history/controllers/history-version.mjs.map +1 -1
- package/dist/server/history/services/history.js +23 -6
- package/dist/server/history/services/history.js.map +1 -1
- package/dist/server/history/services/history.mjs +24 -7
- package/dist/server/history/services/history.mjs.map +1 -1
- package/dist/server/history/services/lifecycles.js +2 -16
- package/dist/server/history/services/lifecycles.js.map +1 -1
- package/dist/server/history/services/lifecycles.mjs +2 -16
- package/dist/server/history/services/lifecycles.mjs.map +1 -1
- package/dist/server/register.js +4 -0
- package/dist/server/register.js.map +1 -1
- package/dist/server/register.mjs +4 -0
- package/dist/server/register.mjs.map +1 -1
- package/dist/server/routes/index.js +3 -1
- package/dist/server/routes/index.js.map +1 -1
- package/dist/server/routes/index.mjs +2 -0
- package/dist/server/routes/index.mjs.map +1 -1
- package/dist/server/services/content-types.js +2 -2
- package/dist/server/services/content-types.js.map +1 -1
- package/dist/server/services/content-types.mjs +2 -2
- package/dist/server/services/content-types.mjs.map +1 -1
- package/dist/server/services/document-manager.js +13 -0
- package/dist/server/services/document-manager.js.map +1 -1
- package/dist/server/services/document-manager.mjs +14 -1
- package/dist/server/services/document-manager.mjs.map +1 -1
- package/dist/server/services/index.js +3 -1
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/index.mjs +2 -0
- package/dist/server/services/index.mjs.map +1 -1
- package/dist/server/services/utils/configuration/settings.js +2 -1
- package/dist/server/services/utils/configuration/settings.js.map +1 -1
- package/dist/server/services/utils/configuration/settings.mjs +2 -1
- package/dist/server/services/utils/configuration/settings.mjs.map +1 -1
- package/dist/server/src/audit-logs.d.ts +40 -0
- package/dist/server/src/audit-logs.d.ts.map +1 -0
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/controllers/collection-types.d.ts +1 -1
- package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
- package/dist/server/src/controllers/content-types.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +21 -3
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/controllers/relations.d.ts +19 -1
- package/dist/server/src/controllers/relations.d.ts.map +1 -1
- package/dist/server/src/controllers/single-types.d.ts +1 -1
- package/dist/server/src/controllers/single-types.d.ts.map +1 -1
- package/dist/server/src/custom-order/constants.d.ts +18 -0
- package/dist/server/src/custom-order/constants.d.ts.map +1 -0
- package/dist/server/src/custom-order/controllers/custom-order.d.ts +8 -0
- package/dist/server/src/custom-order/controllers/custom-order.d.ts.map +1 -0
- package/dist/server/src/custom-order/controllers/index.d.ts +3 -0
- package/dist/server/src/custom-order/controllers/index.d.ts.map +1 -0
- package/dist/server/src/custom-order/index.d.ts +8 -0
- package/dist/server/src/custom-order/index.d.ts.map +1 -0
- package/dist/server/src/custom-order/routes/custom-order.d.ts +4 -0
- package/dist/server/src/custom-order/routes/custom-order.d.ts.map +1 -0
- package/dist/server/src/custom-order/routes/index.d.ts +10 -0
- package/dist/server/src/custom-order/routes/index.d.ts.map +1 -0
- package/dist/server/src/custom-order/services/custom-order.d.ts +43 -0
- package/dist/server/src/custom-order/services/custom-order.d.ts.map +1 -0
- package/dist/server/src/custom-order/services/document-middleware.d.ts +16 -0
- package/dist/server/src/custom-order/services/document-middleware.d.ts.map +1 -0
- package/dist/server/src/custom-order/services/index.d.ts +24 -0
- package/dist/server/src/custom-order/services/index.d.ts.map +1 -0
- package/dist/server/src/custom-order/services/utils.d.ts +34 -0
- package/dist/server/src/custom-order/services/utils.d.ts.map +1 -0
- package/dist/server/src/custom-order/utils.d.ts +6 -0
- package/dist/server/src/custom-order/utils.d.ts.map +1 -0
- package/dist/server/src/history/constants.d.ts.map +1 -1
- package/dist/server/src/history/controllers/history-version.d.ts.map +1 -1
- package/dist/server/src/history/services/history.d.ts.map +1 -1
- package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +22 -6
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/register.d.ts.map +1 -1
- package/dist/server/src/routes/index.d.ts.map +1 -1
- package/dist/server/src/services/content-types.d.ts +1 -3
- package/dist/server/src/services/content-types.d.ts.map +1 -1
- package/dist/server/src/services/document-manager.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +1 -3
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/utils/configuration/settings.d.ts.map +1 -1
- package/dist/shared/contracts/content-types.d.ts +5 -0
- package/dist/shared/contracts/content-types.d.ts.map +1 -1
- package/dist/shared/contracts/custom-order.d.ts +33 -0
- package/dist/shared/contracts/custom-order.d.ts.map +1 -0
- package/dist/shared/contracts/custom-order.js +3 -0
- package/dist/shared/contracts/custom-order.js.map +1 -0
- package/dist/shared/contracts/custom-order.mjs +2 -0
- package/dist/shared/contracts/custom-order.mjs.map +1 -0
- package/dist/shared/contracts/history-versions.d.ts +0 -1
- package/dist/shared/contracts/history-versions.d.ts.map +1 -1
- package/dist/shared/contracts/index.d.ts +1 -0
- package/dist/shared/contracts/index.d.ts.map +1 -1
- package/dist/shared/contracts/index.js +2 -0
- package/dist/shared/contracts/index.js.map +1 -1
- package/dist/shared/contracts/index.mjs +2 -0
- package/dist/shared/contracts/index.mjs.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../server/src/custom-order/index.ts"],"sourcesContent":["import type { Plugin, Struct } from '@strapi/types';\nimport { contentTypes as contentTypesUtils } from '@strapi/utils';\nimport { get } from 'lodash';\n\nimport { controllers } from './controllers';\nimport { services } from './services';\nimport { routes } from './routes';\nimport { POSITION_ATTRIBUTE } from './constants';\nimport { getService, isFeatureEnabled } from './utils';\n\n/**\n * Custom order can be turned on for any collection type listed in the Content Manager.\n */\nconst isOrderable = (contentType: Struct.ContentTypeSchema) =>\n contentTypesUtils.isCollectionType(contentType) &&\n get(contentType, 'pluginOptions.content-manager.visible', true) === true;\n\n/**\n * The future flag is read when the server starts rather than when this module is loaded,\n * so the controllers, services and routes are always there and do nothing while it is off.\n */\nconst feature: Partial<Plugin.LoadedPlugin> = {\n register({ strapi }) {\n if (!isFeatureEnabled(strapi)) {\n return;\n }\n\n /**\n * The setting is changed at runtime, while columns are only created when the server\n * starts, so every content type that could be ordered needs the attribute up front.\n */\n for (const contentType of Object.values(strapi.contentTypes)) {\n if (!isOrderable(contentType)) {\n continue;\n }\n\n strapi.get('content-types').extend(contentType.uid, (schema: Struct.ContentTypeSchema) => {\n Object.assign(schema.attributes, {\n [POSITION_ATTRIBUTE]: {\n type: 'integer',\n configurable: false,\n visible: false,\n // Needed for the Content Manager to sort on it, the value itself is only ever\n // written by moving a document\n writable: true,\n private: true,\n },\n });\n\n // Every default list sorts on the position, and moves look for rows without one\n const { identifiers } = strapi.db.metadata;\n const collectionName = schema.collectionName ?? schema.uid;\n\n schema.indexes = [\n ...(schema.indexes ?? []),\n {\n name: identifiers.getIndexName([collectionName, POSITION_ATTRIBUTE]),\n columns: [identifiers.getColumnName(POSITION_ATTRIBUTE)],\n },\n ];\n });\n }\n },\n async bootstrap({ strapi }) {\n if (!isFeatureEnabled(strapi)) {\n return;\n }\n\n await getService(strapi, 'custom-order').bootstrap();\n },\n controllers,\n services,\n routes,\n};\n\nexport default feature;\n"],"names":["isOrderable","contentType","contentTypesUtils","isCollectionType","get","feature","register","strapi","isFeatureEnabled","Object","values","contentTypes","extend","uid","schema","assign","attributes","POSITION_ATTRIBUTE","type","configurable","visible","writable","private","identifiers","db","metadata","collectionName","indexes","name","getIndexName","columns","getColumnName","bootstrap","getService","controllers","services","routes"],"mappings":";;;;;;;;;;AAUA;;IAGA,MAAMA,WAAAA,GAAc,CAACC,WAAAA,GACnBC,wBAAAA,CAAkBC,gBAAgB,CAACF,WAAAA,CAAAA,IACnCG,KAAAA,CAAIH,WAAAA,EAAa,uCAAA,EAAyC,IAAA,CAAA,KAAU,IAAA;AAEtE;;;AAGC,UACKI,OAAAA,GAAwC;IAC5CC,QAAAA,CAAAA,CAAS,EAAEC,MAAM,EAAE,EAAA;QACjB,IAAI,CAACC,uBAAiBD,MAAAA,CAAAA,EAAS;AAC7B,YAAA;AACF,QAAA;AAEA;;;QAIA,KAAK,MAAMN,WAAAA,IAAeQ,MAAAA,CAAOC,MAAM,CAACH,MAAAA,CAAOI,YAAY,CAAA,CAAG;YAC5D,IAAI,CAACX,YAAYC,WAAAA,CAAAA,EAAc;AAC7B,gBAAA;AACF,YAAA;YAEAM,MAAAA,CAAOH,GAAG,CAAC,eAAA,CAAA,CAAiBQ,MAAM,CAACX,WAAAA,CAAYY,GAAG,EAAE,CAACC,MAAAA,GAAAA;AACnDL,gBAAAA,MAAAA,CAAOM,MAAM,CAACD,MAAAA,CAAOE,UAAU,EAAE;AAC/B,oBAAA,CAACC,+BAAqB;wBACpBC,IAAAA,EAAM,SAAA;wBACNC,YAAAA,EAAc,KAAA;wBACdC,OAAAA,EAAS,KAAA;;;wBAGTC,QAAAA,EAAU,IAAA;wBACVC,OAAAA,EAAS;AACX;AACF,iBAAA,CAAA;;AAGA,gBAAA,MAAM,EAAEC,WAAW,EAAE,GAAGhB,MAAAA,CAAOiB,EAAE,CAACC,QAAQ;AAC1C,gBAAA,MAAMC,cAAAA,GAAiBZ,MAAAA,CAAOY,cAAc,IAAIZ,OAAOD,GAAG;AAE1DC,gBAAAA,MAAAA,CAAOa,OAAO,GAAG;uBACXb,MAAAA,CAAOa,OAAO,IAAI,EAAE;AACxB,oBAAA;wBACEC,IAAAA,EAAML,WAAAA,CAAYM,YAAY,CAAC;AAACH,4BAAAA,cAAAA;AAAgBT,4BAAAA;AAAmB,yBAAA,CAAA;wBACnEa,OAAAA,EAAS;AAACP,4BAAAA,WAAAA,CAAYQ,aAAa,CAACd,4BAAAA;AAAoB;AAC1D;AACD,iBAAA;AACH,YAAA,CAAA,CAAA;AACF,QAAA;AACF,IAAA,CAAA;IACA,MAAMe,SAAAA,CAAAA,CAAU,EAAEzB,MAAM,EAAE,EAAA;QACxB,IAAI,CAACC,uBAAiBD,MAAAA,CAAAA,EAAS;AAC7B,YAAA;AACF,QAAA;QAEA,MAAM0B,gBAAAA,CAAW1B,MAAAA,EAAQ,cAAA,CAAA,CAAgByB,SAAS,EAAA;AACpD,IAAA,CAAA;AACAE,iBAAAA,mBAAAA;AACAC,cAAAA,cAAAA;AACAC,YAAAA;AACF;;;;"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { contentTypes } from '@strapi/utils';
|
|
2
|
+
import { get } from 'lodash';
|
|
3
|
+
import { controllers } from './controllers/index.mjs';
|
|
4
|
+
import { services } from './services/index.mjs';
|
|
5
|
+
import { routes } from './routes/index.mjs';
|
|
6
|
+
import { POSITION_ATTRIBUTE } from './constants.mjs';
|
|
7
|
+
import { isFeatureEnabled, getService } from './utils.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Custom order can be turned on for any collection type listed in the Content Manager.
|
|
11
|
+
*/ const isOrderable = (contentType)=>contentTypes.isCollectionType(contentType) && get(contentType, 'pluginOptions.content-manager.visible', true) === true;
|
|
12
|
+
/**
|
|
13
|
+
* The future flag is read when the server starts rather than when this module is loaded,
|
|
14
|
+
* so the controllers, services and routes are always there and do nothing while it is off.
|
|
15
|
+
*/ const feature = {
|
|
16
|
+
register ({ strapi }) {
|
|
17
|
+
if (!isFeatureEnabled(strapi)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The setting is changed at runtime, while columns are only created when the server
|
|
22
|
+
* starts, so every content type that could be ordered needs the attribute up front.
|
|
23
|
+
*/ for (const contentType of Object.values(strapi.contentTypes)){
|
|
24
|
+
if (!isOrderable(contentType)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
strapi.get('content-types').extend(contentType.uid, (schema)=>{
|
|
28
|
+
Object.assign(schema.attributes, {
|
|
29
|
+
[POSITION_ATTRIBUTE]: {
|
|
30
|
+
type: 'integer',
|
|
31
|
+
configurable: false,
|
|
32
|
+
visible: false,
|
|
33
|
+
// Needed for the Content Manager to sort on it, the value itself is only ever
|
|
34
|
+
// written by moving a document
|
|
35
|
+
writable: true,
|
|
36
|
+
private: true
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// Every default list sorts on the position, and moves look for rows without one
|
|
40
|
+
const { identifiers } = strapi.db.metadata;
|
|
41
|
+
const collectionName = schema.collectionName ?? schema.uid;
|
|
42
|
+
schema.indexes = [
|
|
43
|
+
...schema.indexes ?? [],
|
|
44
|
+
{
|
|
45
|
+
name: identifiers.getIndexName([
|
|
46
|
+
collectionName,
|
|
47
|
+
POSITION_ATTRIBUTE
|
|
48
|
+
]),
|
|
49
|
+
columns: [
|
|
50
|
+
identifiers.getColumnName(POSITION_ATTRIBUTE)
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
async bootstrap ({ strapi }) {
|
|
58
|
+
if (!isFeatureEnabled(strapi)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await getService(strapi, 'custom-order').bootstrap();
|
|
62
|
+
},
|
|
63
|
+
controllers,
|
|
64
|
+
services,
|
|
65
|
+
routes
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export { feature as default };
|
|
69
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../server/src/custom-order/index.ts"],"sourcesContent":["import type { Plugin, Struct } from '@strapi/types';\nimport { contentTypes as contentTypesUtils } from '@strapi/utils';\nimport { get } from 'lodash';\n\nimport { controllers } from './controllers';\nimport { services } from './services';\nimport { routes } from './routes';\nimport { POSITION_ATTRIBUTE } from './constants';\nimport { getService, isFeatureEnabled } from './utils';\n\n/**\n * Custom order can be turned on for any collection type listed in the Content Manager.\n */\nconst isOrderable = (contentType: Struct.ContentTypeSchema) =>\n contentTypesUtils.isCollectionType(contentType) &&\n get(contentType, 'pluginOptions.content-manager.visible', true) === true;\n\n/**\n * The future flag is read when the server starts rather than when this module is loaded,\n * so the controllers, services and routes are always there and do nothing while it is off.\n */\nconst feature: Partial<Plugin.LoadedPlugin> = {\n register({ strapi }) {\n if (!isFeatureEnabled(strapi)) {\n return;\n }\n\n /**\n * The setting is changed at runtime, while columns are only created when the server\n * starts, so every content type that could be ordered needs the attribute up front.\n */\n for (const contentType of Object.values(strapi.contentTypes)) {\n if (!isOrderable(contentType)) {\n continue;\n }\n\n strapi.get('content-types').extend(contentType.uid, (schema: Struct.ContentTypeSchema) => {\n Object.assign(schema.attributes, {\n [POSITION_ATTRIBUTE]: {\n type: 'integer',\n configurable: false,\n visible: false,\n // Needed for the Content Manager to sort on it, the value itself is only ever\n // written by moving a document\n writable: true,\n private: true,\n },\n });\n\n // Every default list sorts on the position, and moves look for rows without one\n const { identifiers } = strapi.db.metadata;\n const collectionName = schema.collectionName ?? schema.uid;\n\n schema.indexes = [\n ...(schema.indexes ?? []),\n {\n name: identifiers.getIndexName([collectionName, POSITION_ATTRIBUTE]),\n columns: [identifiers.getColumnName(POSITION_ATTRIBUTE)],\n },\n ];\n });\n }\n },\n async bootstrap({ strapi }) {\n if (!isFeatureEnabled(strapi)) {\n return;\n }\n\n await getService(strapi, 'custom-order').bootstrap();\n },\n controllers,\n services,\n routes,\n};\n\nexport default feature;\n"],"names":["isOrderable","contentType","contentTypesUtils","isCollectionType","get","feature","register","strapi","isFeatureEnabled","Object","values","contentTypes","extend","uid","schema","assign","attributes","POSITION_ATTRIBUTE","type","configurable","visible","writable","private","identifiers","db","metadata","collectionName","indexes","name","getIndexName","columns","getColumnName","bootstrap","getService","controllers","services","routes"],"mappings":";;;;;;;;AAUA;;IAGA,MAAMA,WAAAA,GAAc,CAACC,WAAAA,GACnBC,YAAAA,CAAkBC,gBAAgB,CAACF,WAAAA,CAAAA,IACnCG,GAAAA,CAAIH,WAAAA,EAAa,uCAAA,EAAyC,IAAA,CAAA,KAAU,IAAA;AAEtE;;;AAGC,UACKI,OAAAA,GAAwC;IAC5CC,QAAAA,CAAAA,CAAS,EAAEC,MAAM,EAAE,EAAA;QACjB,IAAI,CAACC,iBAAiBD,MAAAA,CAAAA,EAAS;AAC7B,YAAA;AACF,QAAA;AAEA;;;QAIA,KAAK,MAAMN,WAAAA,IAAeQ,MAAAA,CAAOC,MAAM,CAACH,MAAAA,CAAOI,YAAY,CAAA,CAAG;YAC5D,IAAI,CAACX,YAAYC,WAAAA,CAAAA,EAAc;AAC7B,gBAAA;AACF,YAAA;YAEAM,MAAAA,CAAOH,GAAG,CAAC,eAAA,CAAA,CAAiBQ,MAAM,CAACX,WAAAA,CAAYY,GAAG,EAAE,CAACC,MAAAA,GAAAA;AACnDL,gBAAAA,MAAAA,CAAOM,MAAM,CAACD,MAAAA,CAAOE,UAAU,EAAE;AAC/B,oBAAA,CAACC,qBAAqB;wBACpBC,IAAAA,EAAM,SAAA;wBACNC,YAAAA,EAAc,KAAA;wBACdC,OAAAA,EAAS,KAAA;;;wBAGTC,QAAAA,EAAU,IAAA;wBACVC,OAAAA,EAAS;AACX;AACF,iBAAA,CAAA;;AAGA,gBAAA,MAAM,EAAEC,WAAW,EAAE,GAAGhB,MAAAA,CAAOiB,EAAE,CAACC,QAAQ;AAC1C,gBAAA,MAAMC,cAAAA,GAAiBZ,MAAAA,CAAOY,cAAc,IAAIZ,OAAOD,GAAG;AAE1DC,gBAAAA,MAAAA,CAAOa,OAAO,GAAG;uBACXb,MAAAA,CAAOa,OAAO,IAAI,EAAE;AACxB,oBAAA;wBACEC,IAAAA,EAAML,WAAAA,CAAYM,YAAY,CAAC;AAACH,4BAAAA,cAAAA;AAAgBT,4BAAAA;AAAmB,yBAAA,CAAA;wBACnEa,OAAAA,EAAS;AAACP,4BAAAA,WAAAA,CAAYQ,aAAa,CAACd,kBAAAA;AAAoB;AAC1D;AACD,iBAAA;AACH,YAAA,CAAA,CAAA;AACF,QAAA;AACF,IAAA,CAAA;IACA,MAAMe,SAAAA,CAAAA,CAAU,EAAEzB,MAAM,EAAE,EAAA;QACxB,IAAI,CAACC,iBAAiBD,MAAAA,CAAAA,EAAS;AAC7B,YAAA;AACF,QAAA;QAEA,MAAM0B,UAAAA,CAAW1B,MAAAA,EAAQ,cAAA,CAAA,CAAgByB,SAAS,EAAA;AACpD,IAAA,CAAA;AACAE,IAAAA,WAAAA;AACAC,IAAAA,QAAAA;AACAC,IAAAA;AACF;;;;"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var routing = require('../../middlewares/routing.js');
|
|
4
|
+
|
|
5
|
+
const info = {
|
|
6
|
+
pluginName: 'content-manager',
|
|
7
|
+
type: 'admin'
|
|
8
|
+
};
|
|
9
|
+
const customOrderRouter = {
|
|
10
|
+
type: 'admin',
|
|
11
|
+
routes: [
|
|
12
|
+
{
|
|
13
|
+
method: 'POST',
|
|
14
|
+
info,
|
|
15
|
+
path: '/collection-types/:model/:id/actions/move',
|
|
16
|
+
handler: 'custom-order.move',
|
|
17
|
+
config: {
|
|
18
|
+
middlewares: [
|
|
19
|
+
routing
|
|
20
|
+
],
|
|
21
|
+
policies: [
|
|
22
|
+
'admin::isAuthenticatedAdmin',
|
|
23
|
+
{
|
|
24
|
+
name: 'plugin::content-manager.hasPermissions',
|
|
25
|
+
config: {
|
|
26
|
+
actions: [
|
|
27
|
+
'plugin::content-manager.explorer.update'
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
exports.customOrderRouter = customOrderRouter;
|
|
38
|
+
//# sourceMappingURL=custom-order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-order.js","sources":["../../../../server/src/custom-order/routes/custom-order.ts"],"sourcesContent":["import type { Plugin } from '@strapi/types';\n\nimport { routing } from '../../middlewares';\n\nconst info = { pluginName: 'content-manager', type: 'admin' };\n\nconst customOrderRouter: Plugin.LoadedPlugin['routes'][string] = {\n type: 'admin',\n routes: [\n {\n method: 'POST',\n info,\n path: '/collection-types/:model/:id/actions/move',\n handler: 'custom-order.move',\n config: {\n middlewares: [routing],\n policies: [\n 'admin::isAuthenticatedAdmin',\n {\n name: 'plugin::content-manager.hasPermissions',\n config: { actions: ['plugin::content-manager.explorer.update'] },\n },\n ],\n },\n },\n ],\n};\n\nexport { customOrderRouter };\n"],"names":["info","pluginName","type","customOrderRouter","routes","method","path","handler","config","middlewares","routing","policies","name","actions"],"mappings":";;;;AAIA,MAAMA,IAAAA,GAAO;IAAEC,UAAAA,EAAY,iBAAA;IAAmBC,IAAAA,EAAM;AAAQ,CAAA;AAE5D,MAAMC,iBAAAA,GAA2D;IAC/DD,IAAAA,EAAM,OAAA;IACNE,MAAAA,EAAQ;AACN,QAAA;YACEC,MAAAA,EAAQ,MAAA;AACRL,YAAAA,IAAAA;YACAM,IAAAA,EAAM,2CAAA;YACNC,OAAAA,EAAS,mBAAA;YACTC,MAAAA,EAAQ;gBACNC,WAAAA,EAAa;AAACC,oBAAAA;AAAQ,iBAAA;gBACtBC,QAAAA,EAAU;AACR,oBAAA,6BAAA;AACA,oBAAA;wBACEC,IAAAA,EAAM,wCAAA;wBACNJ,MAAAA,EAAQ;4BAAEK,OAAAA,EAAS;AAAC,gCAAA;AAA0C;AAAC;AACjE;AACD;AACH;AACF;AACD;AACH;;;;"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import routing from '../../middlewares/routing.mjs';
|
|
2
|
+
|
|
3
|
+
const info = {
|
|
4
|
+
pluginName: 'content-manager',
|
|
5
|
+
type: 'admin'
|
|
6
|
+
};
|
|
7
|
+
const customOrderRouter = {
|
|
8
|
+
type: 'admin',
|
|
9
|
+
routes: [
|
|
10
|
+
{
|
|
11
|
+
method: 'POST',
|
|
12
|
+
info,
|
|
13
|
+
path: '/collection-types/:model/:id/actions/move',
|
|
14
|
+
handler: 'custom-order.move',
|
|
15
|
+
config: {
|
|
16
|
+
middlewares: [
|
|
17
|
+
routing
|
|
18
|
+
],
|
|
19
|
+
policies: [
|
|
20
|
+
'admin::isAuthenticatedAdmin',
|
|
21
|
+
{
|
|
22
|
+
name: 'plugin::content-manager.hasPermissions',
|
|
23
|
+
config: {
|
|
24
|
+
actions: [
|
|
25
|
+
'plugin::content-manager.explorer.update'
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { customOrderRouter };
|
|
36
|
+
//# sourceMappingURL=custom-order.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-order.mjs","sources":["../../../../server/src/custom-order/routes/custom-order.ts"],"sourcesContent":["import type { Plugin } from '@strapi/types';\n\nimport { routing } from '../../middlewares';\n\nconst info = { pluginName: 'content-manager', type: 'admin' };\n\nconst customOrderRouter: Plugin.LoadedPlugin['routes'][string] = {\n type: 'admin',\n routes: [\n {\n method: 'POST',\n info,\n path: '/collection-types/:model/:id/actions/move',\n handler: 'custom-order.move',\n config: {\n middlewares: [routing],\n policies: [\n 'admin::isAuthenticatedAdmin',\n {\n name: 'plugin::content-manager.hasPermissions',\n config: { actions: ['plugin::content-manager.explorer.update'] },\n },\n ],\n },\n },\n ],\n};\n\nexport { customOrderRouter };\n"],"names":["info","pluginName","type","customOrderRouter","routes","method","path","handler","config","middlewares","routing","policies","name","actions"],"mappings":";;AAIA,MAAMA,IAAAA,GAAO;IAAEC,UAAAA,EAAY,iBAAA;IAAmBC,IAAAA,EAAM;AAAQ,CAAA;AAE5D,MAAMC,iBAAAA,GAA2D;IAC/DD,IAAAA,EAAM,OAAA;IACNE,MAAAA,EAAQ;AACN,QAAA;YACEC,MAAAA,EAAQ,MAAA;AACRL,YAAAA,IAAAA;YACAM,IAAAA,EAAM,2CAAA;YACNC,OAAAA,EAAS,mBAAA;YACTC,MAAAA,EAAQ;gBACNC,WAAAA,EAAa;AAACC,oBAAAA;AAAQ,iBAAA;gBACtBC,QAAAA,EAAU;AACR,oBAAA,6BAAA;AACA,oBAAA;wBACEC,IAAAA,EAAM,wCAAA;wBACNJ,MAAAA,EAAQ;4BAAEK,OAAAA,EAAS;AAAC,gCAAA;AAA0C;AAAC;AACjE;AACD;AACH;AACF;AACD;AACH;;;;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var customOrder = require('./custom-order.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The routes will be merged with the other Content Manager routers,
|
|
7
|
+
* so we need to avoid conflicts in the router name.
|
|
8
|
+
*/ const routes = {
|
|
9
|
+
'custom-order': customOrder.customOrderRouter
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
exports.routes = routes;
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../server/src/custom-order/routes/index.ts"],"sourcesContent":["import type { Plugin } from '@strapi/types';\nimport { customOrderRouter } from './custom-order';\n\n/**\n * The routes will be merged with the other Content Manager routers,\n * so we need to avoid conflicts in the router name.\n */\nexport const routes = {\n 'custom-order': customOrderRouter,\n} satisfies Plugin.LoadedPlugin['routes'];\n"],"names":["routes","customOrderRouter"],"mappings":";;;;AAGA;;;UAIaA,MAAAA,GAAS;IACpB,cAAA,EAAgBC;AAClB;;;;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { customOrderRouter } from './custom-order.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The routes will be merged with the other Content Manager routers,
|
|
5
|
+
* so we need to avoid conflicts in the router name.
|
|
6
|
+
*/ const routes = {
|
|
7
|
+
'custom-order': customOrderRouter
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export { routes };
|
|
11
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../../server/src/custom-order/routes/index.ts"],"sourcesContent":["import type { Plugin } from '@strapi/types';\nimport { customOrderRouter } from './custom-order';\n\n/**\n * The routes will be merged with the other Content Manager routers,\n * so we need to avoid conflicts in the router name.\n */\nexport const routes = {\n 'custom-order': customOrderRouter,\n} satisfies Plugin.LoadedPlugin['routes'];\n"],"names":["routes","customOrderRouter"],"mappings":";;AAGA;;;UAIaA,MAAAA,GAAS;IACpB,cAAA,EAAgBC;AAClB;;;;"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var strapiUtils = require('@strapi/utils');
|
|
4
|
+
var store = require('../../services/utils/store.js');
|
|
5
|
+
var constants = require('../constants.js');
|
|
6
|
+
var documentMiddleware = require('./document-middleware.js');
|
|
7
|
+
var utils = require('./utils.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Core store key of the list view settings of a content type, see `services/utils/store.ts`.
|
|
11
|
+
*/ const CONFIGURATION_KEY_PREFIX = 'plugin_content_manager_configuration_content_types::';
|
|
12
|
+
const createCustomOrderService = ({ strapi })=>{
|
|
13
|
+
const state = {
|
|
14
|
+
enabled: new Set(),
|
|
15
|
+
loadedAt: 0,
|
|
16
|
+
refreshId: 0,
|
|
17
|
+
isRefreshing: false,
|
|
18
|
+
isInitialized: false
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Only the content types that received the position attribute at register can be ordered.
|
|
22
|
+
*/ const isOrderable = (uid)=>{
|
|
23
|
+
const model = strapi.getModel(uid);
|
|
24
|
+
return model?.attributes?.[constants.POSITION_ATTRIBUTE] !== undefined;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Reads which content types have custom order turned on in their list view settings.
|
|
28
|
+
* The setting lives in the database and can be changed at runtime, possibly from
|
|
29
|
+
* another instance, so it can't be read once and trusted forever.
|
|
30
|
+
*/ const refresh = async ()=>{
|
|
31
|
+
state.refreshId += 1;
|
|
32
|
+
const refreshId = state.refreshId;
|
|
33
|
+
const configurations = await store.getAllConfigurations();
|
|
34
|
+
// A more recent refresh has started in the meantime, its result is the one to keep
|
|
35
|
+
if (refreshId !== state.refreshId) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
state.enabled = new Set(configurations.flatMap(({ uid, settings })=>uid && settings?.customOrder === true && isOrderable(uid) ? [
|
|
39
|
+
uid
|
|
40
|
+
] : []));
|
|
41
|
+
state.loadedAt = Date.now();
|
|
42
|
+
};
|
|
43
|
+
const refreshInBackgroundIfStale = ()=>{
|
|
44
|
+
if (state.isRefreshing || Date.now() - state.loadedAt < constants.ENABLED_CACHE_TTL_MS) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
state.isRefreshing = true;
|
|
48
|
+
refresh().then(()=>healMissingPositions()).catch((error)=>{
|
|
49
|
+
strapi.log.warn('[custom-order] Could not refresh the list of ordered content types', {
|
|
50
|
+
error
|
|
51
|
+
});
|
|
52
|
+
}).finally(()=>{
|
|
53
|
+
state.isRefreshing = false;
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const getTable = (uid)=>{
|
|
57
|
+
const { tableName, attributes } = strapi.db.metadata.get(uid);
|
|
58
|
+
const columnOf = (attributeName)=>{
|
|
59
|
+
const attribute = attributes[attributeName];
|
|
60
|
+
return 'columnName' in attribute && attribute.columnName ? attribute.columnName : attributeName;
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
tableName,
|
|
64
|
+
positionColumn: columnOf(constants.POSITION_ATTRIBUTE),
|
|
65
|
+
documentIdColumn: columnOf('documentId')
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const toPosition = (value)=>value === null || value === undefined ? undefined : Number(value);
|
|
69
|
+
/**
|
|
70
|
+
* Takes the lock on the order of a content type until the current transaction ends.
|
|
71
|
+
*
|
|
72
|
+
* The position of a new document and a move both read positions before writing them. Two
|
|
73
|
+
* of them at the same time would give two documents the same position, and nothing could
|
|
74
|
+
* tell those two apart afterwards. The lock is the row holding the list view settings of
|
|
75
|
+
* the content type, which exists whenever custom order is on. `FOR UPDATE` holds it until
|
|
76
|
+
* the transaction ends on PostgreSQL and MySQL. SQLite ignores it, its single connection
|
|
77
|
+
* serializes writers anyway.
|
|
78
|
+
*/ const lockOrder = async (uid)=>{
|
|
79
|
+
await strapi.db.queryBuilder('strapi::core-store').select([
|
|
80
|
+
'id'
|
|
81
|
+
]).where({
|
|
82
|
+
key: `${CONFIGURATION_KEY_PREFIX}${uid}`
|
|
83
|
+
}).forUpdate().first().execute();
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Runs `fn` in a transaction, as the only writer of the order of a content type.
|
|
87
|
+
*/ const withOrderLock = async (uid, fn)=>{
|
|
88
|
+
const result = await strapi.db.transaction(async ()=>{
|
|
89
|
+
await lockOrder(uid);
|
|
90
|
+
return fn();
|
|
91
|
+
});
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
94
|
+
const hasRowWithoutPosition = async (uid)=>{
|
|
95
|
+
const row = await strapi.db.queryBuilder(uid).select([
|
|
96
|
+
'id'
|
|
97
|
+
]).where({
|
|
98
|
+
[constants.POSITION_ATTRIBUTE]: {
|
|
99
|
+
$null: true
|
|
100
|
+
}
|
|
101
|
+
}).first().execute();
|
|
102
|
+
return row !== undefined && row !== null;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Position of a document, or undefined if it has none (or does not exist).
|
|
106
|
+
*/ const getPosition = async (uid, documentId)=>{
|
|
107
|
+
const row = await strapi.db.queryBuilder(uid).select([
|
|
108
|
+
constants.POSITION_ATTRIBUTE
|
|
109
|
+
]).where({
|
|
110
|
+
documentId,
|
|
111
|
+
[constants.POSITION_ATTRIBUTE]: {
|
|
112
|
+
$notNull: true
|
|
113
|
+
}
|
|
114
|
+
}).first().execute();
|
|
115
|
+
return toPosition(row?.[constants.POSITION_ATTRIBUTE]);
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Position that puts a document before every other one.
|
|
119
|
+
*/ const getTopPosition = async (uid)=>{
|
|
120
|
+
const row = await strapi.db.queryBuilder(uid).min(constants.POSITION_ATTRIBUTE).first().execute();
|
|
121
|
+
const min = toPosition(row?.min);
|
|
122
|
+
return min === undefined ? 0 : min - 1;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Gives a position to every row that has none.
|
|
126
|
+
*
|
|
127
|
+
* - Rows of a document that already has a position (e.g. a locale created outside of the
|
|
128
|
+
* Document Service) take that position.
|
|
129
|
+
* - If nothing is ordered yet, documents are ordered by creation, which is the order the
|
|
130
|
+
* API returned so far.
|
|
131
|
+
* - Otherwise the remaining documents were created while custom order was off: like any
|
|
132
|
+
* new entry they go on top, the most recent first.
|
|
133
|
+
*/ const assignMissingPositions = async (uid)=>{
|
|
134
|
+
// Checked before taking the lock: this runs in the background on every refresh
|
|
135
|
+
if (!await hasRowWithoutPosition(uid)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const { tableName, positionColumn, documentIdColumn } = getTable(uid);
|
|
139
|
+
const knex = strapi.db.connection;
|
|
140
|
+
await strapi.db.transaction(async ({ trx })=>{
|
|
141
|
+
await lockOrder(uid);
|
|
142
|
+
const table = ()=>strapi.db.getConnection(tableName).transacting(trx);
|
|
143
|
+
// Another writer may have given the positions in the meantime
|
|
144
|
+
const rowWithoutPosition = await table().whereNull(positionColumn).first('id');
|
|
145
|
+
if (!rowWithoutPosition) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// The updated table can't be read directly in a sub query of its own update on MySQL,
|
|
149
|
+
// the aggregated derived tables below are what make these statements portable.
|
|
150
|
+
const documentIdOfUpdatedRow = knex.ref(`${tableName}.${documentIdColumn}`);
|
|
151
|
+
const topRow = await table().min({
|
|
152
|
+
min: positionColumn
|
|
153
|
+
}).first();
|
|
154
|
+
const top = toPosition(topRow?.min);
|
|
155
|
+
if (top !== undefined) {
|
|
156
|
+
const knownPositions = strapi.db.getConnection(tableName).select({
|
|
157
|
+
d: documentIdColumn
|
|
158
|
+
}).max({
|
|
159
|
+
p: positionColumn
|
|
160
|
+
}).whereNotNull(positionColumn).groupBy(documentIdColumn).as('x');
|
|
161
|
+
const positionOfDocument = knex.queryBuilder().select('x.p').from(knownPositions).where('x.d', documentIdOfUpdatedRow);
|
|
162
|
+
await table().whereNull(positionColumn).update({
|
|
163
|
+
[positionColumn]: knex.raw('(?)', [
|
|
164
|
+
positionOfDocument
|
|
165
|
+
])
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const firstRowIds = strapi.db.getConnection(tableName).select({
|
|
169
|
+
d: documentIdColumn
|
|
170
|
+
}).min({
|
|
171
|
+
m: 'id'
|
|
172
|
+
}).groupBy(documentIdColumn).as('x');
|
|
173
|
+
const firstRowIdOfDocument = knex.queryBuilder().select('x.m').from(firstRowIds).where('x.d', documentIdOfUpdatedRow);
|
|
174
|
+
await table().whereNull(positionColumn).update({
|
|
175
|
+
[positionColumn]: top === undefined ? knex.raw('(?)', [
|
|
176
|
+
firstRowIdOfDocument
|
|
177
|
+
]) : knex.raw('? - (?)', [
|
|
178
|
+
top,
|
|
179
|
+
firstRowIdOfDocument
|
|
180
|
+
])
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Rows written outside of the Document Service (an import, a raw query) have no position.
|
|
186
|
+
* Until they get one they sit at one end of the list, which end depends on the database,
|
|
187
|
+
* so they get one at the next refresh rather than at the next move.
|
|
188
|
+
*/ const healMissingPositions = async ()=>{
|
|
189
|
+
for (const uid of state.enabled){
|
|
190
|
+
await assignMissingPositions(uid);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const service = {
|
|
194
|
+
isOrderable,
|
|
195
|
+
refresh,
|
|
196
|
+
withOrderLock,
|
|
197
|
+
/**
|
|
198
|
+
* Whether the entries of a content type currently follow a custom order.
|
|
199
|
+
*/ isEnabled (uid) {
|
|
200
|
+
refreshInBackgroundIfStale();
|
|
201
|
+
return state.enabled.has(uid);
|
|
202
|
+
},
|
|
203
|
+
async bootstrap () {
|
|
204
|
+
// Prevent initializing the service twice
|
|
205
|
+
if (state.isInitialized) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
state.isInitialized = true;
|
|
209
|
+
await refresh();
|
|
210
|
+
await healMissingPositions();
|
|
211
|
+
strapi.documents.use(documentMiddleware.createDocumentMiddleware(service));
|
|
212
|
+
},
|
|
213
|
+
/**
|
|
214
|
+
* To call after the list view settings of a content type have been saved.
|
|
215
|
+
*/ async applySettings (uid, settings) {
|
|
216
|
+
if (settings?.customOrder === true && isOrderable(uid)) {
|
|
217
|
+
await assignMissingPositions(uid);
|
|
218
|
+
}
|
|
219
|
+
await refresh();
|
|
220
|
+
},
|
|
221
|
+
getTopPosition,
|
|
222
|
+
assignMissingPositions,
|
|
223
|
+
/**
|
|
224
|
+
* Makes sure every row of a document shares the document's position. Needed when a row
|
|
225
|
+
* is created for an existing document, typically a new locale.
|
|
226
|
+
*/ async syncDocumentPosition (uid, documentId) {
|
|
227
|
+
await withOrderLock(uid, async ()=>{
|
|
228
|
+
const position = await getPosition(uid, documentId) ?? await getTopPosition(uid);
|
|
229
|
+
await strapi.db.queryBuilder(uid).update({
|
|
230
|
+
[constants.POSITION_ATTRIBUTE]: position
|
|
231
|
+
}).where({
|
|
232
|
+
documentId,
|
|
233
|
+
[constants.POSITION_ATTRIBUTE]: {
|
|
234
|
+
$null: true
|
|
235
|
+
}
|
|
236
|
+
}).execute();
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
/**
|
|
240
|
+
* Places a document right before or after another one.
|
|
241
|
+
*
|
|
242
|
+
* Writes go through the query builder on purpose: the position is not content, so
|
|
243
|
+
* moving a document must not run lifecycles nor touch `updatedAt`, which would flag
|
|
244
|
+
* every shifted entry as modified.
|
|
245
|
+
*/ async move ({ uid, documentId, anchorId, placement }) {
|
|
246
|
+
const { positionColumn } = getTable(uid);
|
|
247
|
+
await withOrderLock(uid, async ()=>{
|
|
248
|
+
await assignMissingPositions(uid);
|
|
249
|
+
const from = await getPosition(uid, documentId);
|
|
250
|
+
const anchor = await getPosition(uid, anchorId);
|
|
251
|
+
if (from === undefined || anchor === undefined) {
|
|
252
|
+
throw new strapiUtils.errors.NotFoundError('Document not found');
|
|
253
|
+
}
|
|
254
|
+
const plan = utils.computeMove({
|
|
255
|
+
from,
|
|
256
|
+
anchor,
|
|
257
|
+
placement
|
|
258
|
+
});
|
|
259
|
+
if (!plan) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (plan.shift) {
|
|
263
|
+
const { from: start, to: end, by } = plan.shift;
|
|
264
|
+
const shiftedRows = strapi.db.queryBuilder(uid).where({
|
|
265
|
+
[constants.POSITION_ATTRIBUTE]: end === null ? {
|
|
266
|
+
$gte: start
|
|
267
|
+
} : {
|
|
268
|
+
$gte: start,
|
|
269
|
+
$lte: end
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
await (by === 1 ? shiftedRows.increment(positionColumn) : shiftedRows.decrement(positionColumn)).execute();
|
|
273
|
+
}
|
|
274
|
+
await strapi.db.queryBuilder(uid).update({
|
|
275
|
+
[constants.POSITION_ATTRIBUTE]: plan.position
|
|
276
|
+
}).where({
|
|
277
|
+
documentId
|
|
278
|
+
}).execute();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
return service;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
exports.createCustomOrderService = createCustomOrderService;
|
|
286
|
+
//# sourceMappingURL=custom-order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-order.js","sources":["../../../../server/src/custom-order/services/custom-order.ts"],"sourcesContent":["import type { Core, UID } from '@strapi/types';\nimport { errors } from '@strapi/utils';\n\nimport storeUtils from '../../services/utils/store';\nimport { ENABLED_CACHE_TTL_MS, POSITION_ATTRIBUTE } from '../constants';\nimport { createDocumentMiddleware } from './document-middleware';\nimport { computeMove, type Placement } from './utils';\n\ninterface StoredConfiguration {\n uid?: string;\n settings?: { customOrder?: boolean };\n}\n\ntype PositionRow = { [POSITION_ATTRIBUTE]: number | string | null };\n\n/**\n * Core store key of the list view settings of a content type, see `services/utils/store.ts`.\n */\nconst CONFIGURATION_KEY_PREFIX = 'plugin_content_manager_configuration_content_types::';\n\nconst createCustomOrderService = ({ strapi }: { strapi: Core.Strapi }) => {\n const state: {\n enabled: Set<string>;\n loadedAt: number;\n refreshId: number;\n isRefreshing: boolean;\n isInitialized: boolean;\n } = {\n enabled: new Set(),\n loadedAt: 0,\n refreshId: 0,\n isRefreshing: false,\n isInitialized: false,\n };\n\n /**\n * Only the content types that received the position attribute at register can be ordered.\n */\n const isOrderable = (uid: string) => {\n const model = strapi.getModel(uid as UID.ContentType);\n\n return model?.attributes?.[POSITION_ATTRIBUTE] !== undefined;\n };\n\n /**\n * Reads which content types have custom order turned on in their list view settings.\n * The setting lives in the database and can be changed at runtime, possibly from\n * another instance, so it can't be read once and trusted forever.\n */\n const refresh = async () => {\n state.refreshId += 1;\n const refreshId = state.refreshId;\n\n const configurations: StoredConfiguration[] = await storeUtils.getAllConfigurations();\n\n // A more recent refresh has started in the meantime, its result is the one to keep\n if (refreshId !== state.refreshId) {\n return;\n }\n\n state.enabled = new Set(\n configurations.flatMap(({ uid, settings }) =>\n uid && settings?.customOrder === true && isOrderable(uid) ? [uid] : []\n )\n );\n state.loadedAt = Date.now();\n };\n\n const refreshInBackgroundIfStale = () => {\n if (state.isRefreshing || Date.now() - state.loadedAt < ENABLED_CACHE_TTL_MS) {\n return;\n }\n\n state.isRefreshing = true;\n\n refresh()\n .then(() => healMissingPositions())\n .catch((error: unknown) => {\n strapi.log.warn('[custom-order] Could not refresh the list of ordered content types', {\n error,\n });\n })\n .finally(() => {\n state.isRefreshing = false;\n });\n };\n\n const getTable = (uid: UID.ContentType) => {\n const { tableName, attributes } = strapi.db.metadata.get(uid);\n\n const columnOf = (attributeName: string) => {\n const attribute = attributes[attributeName];\n\n return 'columnName' in attribute && attribute.columnName\n ? attribute.columnName\n : attributeName;\n };\n\n return {\n tableName,\n positionColumn: columnOf(POSITION_ATTRIBUTE),\n documentIdColumn: columnOf('documentId'),\n };\n };\n\n const toPosition = (value: number | string | null | undefined) =>\n value === null || value === undefined ? undefined : Number(value);\n\n /**\n * Takes the lock on the order of a content type until the current transaction ends.\n *\n * The position of a new document and a move both read positions before writing them. Two\n * of them at the same time would give two documents the same position, and nothing could\n * tell those two apart afterwards. The lock is the row holding the list view settings of\n * the content type, which exists whenever custom order is on. `FOR UPDATE` holds it until\n * the transaction ends on PostgreSQL and MySQL. SQLite ignores it, its single connection\n * serializes writers anyway.\n */\n const lockOrder = async (uid: UID.ContentType) => {\n await strapi.db\n .queryBuilder('strapi::core-store')\n .select(['id'])\n .where({ key: `${CONFIGURATION_KEY_PREFIX}${uid}` })\n .forUpdate()\n .first()\n .execute();\n };\n\n /**\n * Runs `fn` in a transaction, as the only writer of the order of a content type.\n */\n const withOrderLock = async <T>(uid: UID.ContentType, fn: () => Promise<T>): Promise<T> => {\n const result: T = await strapi.db.transaction(async () => {\n await lockOrder(uid);\n\n return fn();\n });\n\n return result;\n };\n\n const hasRowWithoutPosition = async (uid: UID.ContentType) => {\n const row = await strapi.db\n .queryBuilder(uid)\n .select(['id'])\n .where({ [POSITION_ATTRIBUTE]: { $null: true } })\n .first()\n .execute<{ id: number } | undefined>();\n\n return row !== undefined && row !== null;\n };\n\n /**\n * Position of a document, or undefined if it has none (or does not exist).\n */\n const getPosition = async (uid: UID.ContentType, documentId: string) => {\n const row = await strapi.db\n .queryBuilder(uid)\n .select([POSITION_ATTRIBUTE])\n .where({ documentId, [POSITION_ATTRIBUTE]: { $notNull: true } })\n .first()\n .execute<PositionRow | undefined>();\n\n return toPosition(row?.[POSITION_ATTRIBUTE]);\n };\n\n /**\n * Position that puts a document before every other one.\n */\n const getTopPosition = async (uid: UID.ContentType) => {\n const row = await strapi.db\n .queryBuilder(uid)\n .min(POSITION_ATTRIBUTE)\n .first()\n .execute<{ min: number | string | null } | undefined>();\n\n const min = toPosition(row?.min);\n\n return min === undefined ? 0 : min - 1;\n };\n\n /**\n * Gives a position to every row that has none.\n *\n * - Rows of a document that already has a position (e.g. a locale created outside of the\n * Document Service) take that position.\n * - If nothing is ordered yet, documents are ordered by creation, which is the order the\n * API returned so far.\n * - Otherwise the remaining documents were created while custom order was off: like any\n * new entry they go on top, the most recent first.\n */\n const assignMissingPositions = async (uid: UID.ContentType) => {\n // Checked before taking the lock: this runs in the background on every refresh\n if (!(await hasRowWithoutPosition(uid))) {\n return;\n }\n\n const { tableName, positionColumn, documentIdColumn } = getTable(uid);\n const knex = strapi.db.connection;\n\n await strapi.db.transaction(async ({ trx }) => {\n await lockOrder(uid);\n\n const table = () => strapi.db.getConnection(tableName).transacting(trx);\n\n // Another writer may have given the positions in the meantime\n const rowWithoutPosition = await table().whereNull(positionColumn).first('id');\n\n if (!rowWithoutPosition) {\n return;\n }\n\n // The updated table can't be read directly in a sub query of its own update on MySQL,\n // the aggregated derived tables below are what make these statements portable.\n const documentIdOfUpdatedRow = knex.ref(`${tableName}.${documentIdColumn}`);\n\n const topRow: { min: number | string | null } | undefined = await table()\n .min({ min: positionColumn })\n .first();\n const top = toPosition(topRow?.min);\n\n if (top !== undefined) {\n const knownPositions = strapi.db\n .getConnection(tableName)\n .select({ d: documentIdColumn })\n .max({ p: positionColumn })\n .whereNotNull(positionColumn)\n .groupBy(documentIdColumn)\n .as('x');\n\n const positionOfDocument = knex\n .queryBuilder()\n .select('x.p')\n .from(knownPositions)\n .where('x.d', documentIdOfUpdatedRow);\n\n await table()\n .whereNull(positionColumn)\n .update({ [positionColumn]: knex.raw('(?)', [positionOfDocument]) });\n }\n\n const firstRowIds = strapi.db\n .getConnection(tableName)\n .select({ d: documentIdColumn })\n .min({ m: 'id' })\n .groupBy(documentIdColumn)\n .as('x');\n\n const firstRowIdOfDocument = knex\n .queryBuilder()\n .select('x.m')\n .from(firstRowIds)\n .where('x.d', documentIdOfUpdatedRow);\n\n await table()\n .whereNull(positionColumn)\n .update({\n [positionColumn]:\n top === undefined\n ? knex.raw('(?)', [firstRowIdOfDocument])\n : knex.raw('? - (?)', [top, firstRowIdOfDocument]),\n });\n });\n };\n\n /**\n * Rows written outside of the Document Service (an import, a raw query) have no position.\n * Until they get one they sit at one end of the list, which end depends on the database,\n * so they get one at the next refresh rather than at the next move.\n */\n const healMissingPositions = async () => {\n for (const uid of state.enabled) {\n await assignMissingPositions(uid as UID.ContentType);\n }\n };\n\n const service = {\n isOrderable,\n\n refresh,\n\n withOrderLock,\n\n /**\n * Whether the entries of a content type currently follow a custom order.\n */\n isEnabled(uid: string) {\n refreshInBackgroundIfStale();\n\n return state.enabled.has(uid);\n },\n\n async bootstrap() {\n // Prevent initializing the service twice\n if (state.isInitialized) {\n return;\n }\n\n state.isInitialized = true;\n\n await refresh();\n await healMissingPositions();\n\n strapi.documents.use(createDocumentMiddleware(service));\n },\n\n /**\n * To call after the list view settings of a content type have been saved.\n */\n async applySettings(uid: UID.ContentType, settings?: { customOrder?: boolean } | null) {\n if (settings?.customOrder === true && isOrderable(uid)) {\n await assignMissingPositions(uid);\n }\n\n await refresh();\n },\n\n getTopPosition,\n\n assignMissingPositions,\n\n /**\n * Makes sure every row of a document shares the document's position. Needed when a row\n * is created for an existing document, typically a new locale.\n */\n async syncDocumentPosition(uid: UID.ContentType, documentId: string) {\n await withOrderLock(uid, async () => {\n const position = (await getPosition(uid, documentId)) ?? (await getTopPosition(uid));\n\n await strapi.db\n .queryBuilder(uid)\n .update({ [POSITION_ATTRIBUTE]: position })\n .where({ documentId, [POSITION_ATTRIBUTE]: { $null: true } })\n .execute();\n });\n },\n\n /**\n * Places a document right before or after another one.\n *\n * Writes go through the query builder on purpose: the position is not content, so\n * moving a document must not run lifecycles nor touch `updatedAt`, which would flag\n * every shifted entry as modified.\n */\n async move({\n uid,\n documentId,\n anchorId,\n placement,\n }: {\n uid: UID.ContentType;\n documentId: string;\n anchorId: string;\n placement: Placement;\n }) {\n const { positionColumn } = getTable(uid);\n\n await withOrderLock(uid, async () => {\n await assignMissingPositions(uid);\n\n const from = await getPosition(uid, documentId);\n const anchor = await getPosition(uid, anchorId);\n\n if (from === undefined || anchor === undefined) {\n throw new errors.NotFoundError('Document not found');\n }\n\n const plan = computeMove({ from, anchor, placement });\n\n if (!plan) {\n return;\n }\n\n if (plan.shift) {\n const { from: start, to: end, by } = plan.shift;\n\n const shiftedRows = strapi.db.queryBuilder(uid).where({\n [POSITION_ATTRIBUTE]: end === null ? { $gte: start } : { $gte: start, $lte: end },\n });\n\n await (\n by === 1 ? shiftedRows.increment(positionColumn) : shiftedRows.decrement(positionColumn)\n ).execute();\n }\n\n await strapi.db\n .queryBuilder(uid)\n .update({ [POSITION_ATTRIBUTE]: plan.position })\n .where({ documentId })\n .execute();\n });\n },\n };\n\n return service;\n};\n\nexport type CustomOrderService = ReturnType<typeof createCustomOrderService>;\n\nexport { createCustomOrderService };\n"],"names":["CONFIGURATION_KEY_PREFIX","createCustomOrderService","strapi","state","enabled","Set","loadedAt","refreshId","isRefreshing","isInitialized","isOrderable","uid","model","getModel","attributes","POSITION_ATTRIBUTE","undefined","refresh","configurations","storeUtils","getAllConfigurations","flatMap","settings","customOrder","Date","now","refreshInBackgroundIfStale","ENABLED_CACHE_TTL_MS","then","healMissingPositions","catch","error","log","warn","finally","getTable","tableName","db","metadata","get","columnOf","attributeName","attribute","columnName","positionColumn","documentIdColumn","toPosition","value","Number","lockOrder","queryBuilder","select","where","key","forUpdate","first","execute","withOrderLock","fn","result","transaction","hasRowWithoutPosition","row","$null","getPosition","documentId","$notNull","getTopPosition","min","assignMissingPositions","knex","connection","trx","table","getConnection","transacting","rowWithoutPosition","whereNull","documentIdOfUpdatedRow","ref","topRow","top","knownPositions","d","max","p","whereNotNull","groupBy","as","positionOfDocument","from","update","raw","firstRowIds","m","firstRowIdOfDocument","service","isEnabled","has","bootstrap","documents","use","createDocumentMiddleware","applySettings","syncDocumentPosition","position","move","anchorId","placement","anchor","errors","NotFoundError","plan","computeMove","shift","start","to","end","by","shiftedRows","$gte","$lte","increment","decrement"],"mappings":"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"}
|