diff --git a/docusaurus/docs/cms/api/rest.md b/docusaurus/docs/cms/api/rest.md index e2f67be0a0..bcdad44e92 100644 --- a/docusaurus/docs/cms/api/rest.md +++ b/docusaurus/docs/cms/api/rest.md @@ -152,18 +152,21 @@ You can pass an optional header while you're migrating to Strapi 5 (see the [rel { name: 'locale', type: 'string', required: false, description: 'Locale of the documents to fetch. See locale.' }, { name: 'status', type: 'string', required: false, description: 'published or draft. See status.' }, { name: 'publicationFilter', type: 'string', required: false, description: 'Query documents by the relationship between their draft and published versions. See publicationFilter.' }, - ]} - codePath="/api/restaurants" - codePathHighlights={['restaurants']} - codeTabs={[ - { - label: 'cURL', - code: `curl 'http://localhost:1337/api/restaurants' \\ - -H 'Authorization: Bearer '`, - }, - { - label: 'JavaScript', - code: `const response = await fetch( + ]}> + + + + +```bash +curl 'http://localhost:1337/api/restaurants' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( 'http://localhost:1337/api/restaurants', { headers: { @@ -171,49 +174,48 @@ You can pass an optional header while you're migrating to Strapi 5 (see the [rel }, } ); -const data = await response.json();`, - }, - ]} - responses={[ +const data = await response.json(); +``` + + + + + + + +```json +{ + "data": [ { - status: 200, - statusText: 'OK', - time: '23ms', - body: JSON.stringify({ - data: [ - { - id: 2, - documentId: "hgv1vny5cebq2l3czil1rpb3", - Name: "BMK Paris Bamako", - Description: null, - createdAt: "2024-03-06T13:42:05.098Z", - updatedAt: "2024-03-06T13:42:05.098Z", - publishedAt: "2024-03-06T13:42:05.103Z", - locale: "en" - }, - { - id: 4, - documentId: "znrlzntu9ei5onjvwfaalu2v", - Name: "Biscotte Restaurant", - Description: [ - { - type: "paragraph", - children: [{ type: "text", text: "Welcome to Biscotte restaurant! Restaurant Biscotte offers a cuisine based on fresh, quality products, often local, organic when possible, and always produced by passionate producers." }] - } - ], - createdAt: "2024-03-06T13:43:30.172Z", - updatedAt: "2024-03-06T13:43:30.172Z", - publishedAt: "2024-03-06T13:43:30.175Z", - locale: "en" - } - ], - meta: { - pagination: { page: 1, pageSize: 25, pageCount: 1, total: 2 } - } - }, null, 2), + "id": 2, + "documentId": "hgv1vny5cebq2l3czil1rpb3", + "Name": "BMK Paris Bamako", + "Description": null, + "createdAt": "2024-03-06T13:42:05.098Z", + "updatedAt": "2024-03-06T13:42:05.098Z", + "publishedAt": "2024-03-06T13:42:05.103Z", + "locale": "en" }, - ]} -/> + { + "id": 4, + "documentId": "znrlzntu9ei5onjvwfaalu2v", + "Name": "Biscotte Restaurant", + "createdAt": "2024-03-06T13:43:30.172Z", + "updatedAt": "2024-03-06T13:43:30.172Z", + "publishedAt": "2024-03-06T13:43:30.175Z", + "locale": "en" + } + ], + "meta": { + "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 2 } + } +} +``` + + + + + ### Get a document {#get} @@ -231,18 +233,21 @@ In Strapi 5, a specific document is reached by its `documentId`. params={[ { name: 'pluralApiId', type: 'string', required: true, description: 'Plural API ID of the content-type (e.g. restaurants)' }, { name: 'documentId', type: 'string', required: true, description: 'Unique document identifier' }, - ]} - codePath="/api/restaurants/znrlzntu9ei5onjvwfaalu2v" - codePathHighlights={['restaurants', 'znrlzntu9ei5onjvwfaalu2v']} - codeTabs={[ - { - label: 'cURL', - code: `curl 'http://localhost:1337/api/restaurants/znrlzntu9ei5onjvwfaalu2v' \\ - -H 'Authorization: Bearer '`, - }, - { - label: 'JavaScript', - code: `const response = await fetch( + ]}> + + + + +```bash +curl 'http://localhost:1337/api/restaurants/znrlzntu9ei5onjvwfaalu2v' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( 'http://localhost:1337/api/restaurants/znrlzntu9ei5onjvwfaalu2v', { headers: { @@ -250,42 +255,54 @@ In Strapi 5, a specific document is reached by its `documentId`. }, } ); -const data = await response.json();`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - time: '12ms', - body: JSON.stringify({ - data: { - id: 6, - documentId: "znrlzntu9ei5onjvwfaalu2v", - Name: "Biscotte Restaurant", - Description: [ - { - type: "paragraph", - children: [{ type: "text", text: "Welcome to Biscotte restaurant! Restaurant Biscotte offers a cuisine based on fresh, quality products." }] - } - ], - createdAt: "2024-02-27T10:19:04.953Z", - updatedAt: "2024-03-05T15:52:05.591Z", - publishedAt: "2024-03-05T15:52:05.600Z", - locale: "en" - }, - meta: {} - }, null, 2), - }, - { - status: 404, - statusText: 'Not Found', - body: JSON.stringify({ - error: { status: 404, name: 'NotFoundError', message: 'Document not found' } - }, null, 2), - }, - ]} -/> +const data = await response.json(); +``` + + + + + + + +```json +{ + "data": { + "id": 6, + "documentId": "znrlzntu9ei5onjvwfaalu2v", + "Name": "Biscotte Restaurant", + "Description": [ + { + "type": "paragraph", + "children": [{ "type": "text", "text": "Welcome to Biscotte restaurant! Restaurant Biscotte offers a cuisine based on fresh, quality products." }] + } + ], + "createdAt": "2024-02-27T10:19:04.953Z", + "updatedAt": "2024-03-05T15:52:05.591Z", + "publishedAt": "2024-03-05T15:52:05.600Z", + "locale": "en" + }, + "meta": {} +} +``` + + + + +```json +{ + "data": null, + "error": { + "status": 404, + "name": "NotFoundError", + "message": "Document not found" + } +} +``` + + + + + ### Create a document {#create} @@ -308,16 +325,16 @@ When you POST a document, each object inside a dynamic zone array must include ` paramTitle="Body Parameters" params={[ { name: 'data', type: 'object', required: true, description: 'Object containing the field values for the new document' }, - ]} - codePath="/api/restaurants" - codePathHighlights={['restaurants']} - codeTabs={[ - { - label: 'cURL', - code: `curl -X POST \\ - 'http://localhost:1337/api/restaurants' \\ - -H 'Authorization: Bearer ' \\ - -H 'Content-Type: application/json' \\ + ]}> + + + + +```bash +curl -X POST \ + 'http://localhost:1337/api/restaurants' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ -d '{ "data": { "Name": "Restaurant D", @@ -328,11 +345,14 @@ When you POST a document, each object inside a dynamic zone array must include ` } ] } - }'`, - }, - { - label: 'JavaScript', - code: `const response = await fetch( + }' +``` + + + + +```js +const response = await fetch( 'http://localhost:1337/api/restaurants', { method: 'POST', @@ -353,34 +373,39 @@ When you POST a document, each object inside a dynamic zone array must include ` }), } ); -const data = await response.json();`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - time: '45ms', - body: JSON.stringify({ - data: { - documentId: "bw64dnu97i56nq85106yt4du", - Name: "Restaurant D", - Description: [ - { - type: "paragraph", - children: [{ type: "text", text: "A very short description goes here." }] - } - ], - createdAt: "2024-03-05T16:44:47.689Z", - updatedAt: "2024-03-05T16:44:47.689Z", - publishedAt: "2024-03-05T16:44:47.687Z", - locale: "en" - }, - meta: {} - }, null, 2), - }, - ]} -/> +const data = await response.json(); +``` + + + + + + + +```json +{ + "data": { + "documentId": "bw64dnu97i56nq85106yt4du", + "Name": "Restaurant D", + "Description": [ + { + "type": "paragraph", + "children": [{ "type": "text", "text": "A very short description goes here." }] + } + ], + "createdAt": "2024-03-05T16:44:47.689Z", + "updatedAt": "2024-03-05T16:44:47.689Z", + "publishedAt": "2024-03-05T16:44:47.687Z", + "locale": "en" + }, + "meta": {} +} +``` + + + + + ### Update a document {#update} @@ -402,16 +427,16 @@ Each entry you send for a [dynamic zone](/cms/backend-customization/models#dynam paramTitle="Body Parameters" params={[ { name: 'data', type: 'object', required: true, description: 'Object containing the field values to update' }, - ]} - codePath="/api/restaurants/hgv1vny5cebq2l3czil1rpb3" - codePathHighlights={['restaurants', 'hgv1vny5cebq2l3czil1rpb3']} - codeTabs={[ - { - label: 'cURL', - code: `curl -X PUT \\ - 'http://localhost:1337/api/restaurants/hgv1vny5cebq2l3czil1rpb3' \\ - -H 'Authorization: Bearer ' \\ - -H 'Content-Type: application/json' \\ + ]}> + + + + +```bash +curl -X PUT \ + 'http://localhost:1337/api/restaurants/hgv1vny5cebq2l3czil1rpb3' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ -d '{ "data": { "Name": "BMK Paris Bamako", @@ -422,11 +447,14 @@ Each entry you send for a [dynamic zone](/cms/backend-customization/models#dynam } ] } - }'`, - }, - { - label: 'JavaScript', - code: `const response = await fetch( + }' +``` + + + + +```js +const response = await fetch( 'http://localhost:1337/api/restaurants/hgv1vny5cebq2l3czil1rpb3', { method: 'PUT', @@ -447,35 +475,40 @@ Each entry you send for a [dynamic zone](/cms/backend-customization/models#dynam }), } ); -const data = await response.json();`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - time: '34ms', - body: JSON.stringify({ - data: { - id: 9, - documentId: "hgv1vny5cebq2l3czil1rpb3", - Name: "BMK Paris Bamako", - Description: [ - { - type: "paragraph", - children: [{ type: "text", text: "A very short description goes here." }] - } - ], - createdAt: "2024-03-06T13:42:05.098Z", - updatedAt: "2024-03-06T14:16:56.883Z", - publishedAt: "2024-03-06T14:16:56.895Z", - locale: "en" - }, - meta: {} - }, null, 2), - }, - ]} -/> +const data = await response.json(); +``` + + + + + + + +```json +{ + "data": { + "id": 9, + "documentId": "hgv1vny5cebq2l3czil1rpb3", + "Name": "BMK Paris Bamako", + "Description": [ + { + "type": "paragraph", + "children": [{ "type": "text", "text": "A very short description goes here." }] + } + ], + "createdAt": "2024-03-06T13:42:05.098Z", + "updatedAt": "2024-03-06T14:16:56.883Z", + "publishedAt": "2024-03-06T14:16:56.895Z", + "locale": "en" + }, + "meta": {} +} +``` + + + + + ### Delete a document {#delete} @@ -489,19 +522,22 @@ const data = await response.json();`, params={[ { name: 'pluralApiId', type: 'string', required: true, description: 'Plural API ID (e.g. restaurants)' }, { name: 'documentId', type: 'string', required: true, description: 'Document ID of the entry to delete' }, - ]} - codePath="/api/restaurants/bw64dnu97i56nq85106yt4du" - codePathHighlights={['restaurants', 'bw64dnu97i56nq85106yt4du']} - codeTabs={[ - { - label: 'cURL', - code: `curl -X DELETE \\ - 'http://localhost:1337/api/restaurants/bw64dnu97i56nq85106yt4du' \\ - -H 'Authorization: Bearer '`, - }, - { - label: 'JavaScript', - code: `const response = await fetch( + ]}> + + + + +```bash +curl -X DELETE \ + 'http://localhost:1337/api/restaurants/bw64dnu97i56nq85106yt4du' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( 'http://localhost:1337/api/restaurants/bw64dnu97i56nq85106yt4du', { method: 'DELETE', @@ -509,9 +545,10 @@ const data = await response.json();`, Authorization: 'Bearer ', }, } -);`, - }, - ]} - responses={[]} - isLast -/> +); +``` + + + + + diff --git a/docusaurus/docs/cms/api/rest/filters.md b/docusaurus/docs/cms/api/rest/filters.md index 7ec5847760..bd25a57928 100644 --- a/docusaurus/docs/cms/api/rest/filters.md +++ b/docusaurus/docs/cms/api/rest/filters.md @@ -90,17 +90,22 @@ By default, the filters can only be used from `find` endpoints generated by the id="find-users-eq" method="GET" path="/api/users?filters[username][$eq]=John" - codePath="/api/users?filters[username][$eq]=John" title="Find users having 'John' as first name" - description="Use the $eq filter operator to find an exact match." - codeTabs={[ - { - label: 'cURL', - code: `GET /api/users?filters[username][$eq]=John`, - }, - { - label: 'JavaScript', - code: `const qs = require('qs'); + description="Use the $eq filter operator to find an exact match."> + + + + +```bash +curl 'http://localhost:1337/api/users?filters[username][$eq]=John' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ filters: { username: { @@ -111,14 +116,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/users?\${query}\`);`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: `{ +await request(`/api/users?${query}`); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -140,10 +148,13 @@ await request(\`/api/users?\${query}\`);`, "total": 1 } } -}`, - }, - ]} -/> +} +``` + + + + + ## Example: Find multiple restaurants with ids 3, 6,8 @@ -151,17 +162,22 @@ await request(\`/api/users?\${query}\`);`, id="find-restaurants-in" method="GET" path="/api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8" - codePath="/api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8" title="Find multiple restaurants with ids 3, 6, 8" - description="Use the $in filter operator with an array of values to find multiple exact values." - codeTabs={[ - { - label: 'cURL', - code: `GET /api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8`, - }, - { - label: 'JavaScript', - code: `const qs = require('qs'); + description="Use the $in filter operator with an array of values to find multiple exact values."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ filters: { id: { @@ -172,14 +188,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "id": 3, @@ -198,27 +217,35 @@ await request(\`/api/restaurants?\${query}\`);`, } ], "meta": {} -}`, - }, - ]} -/> +} +``` + + + + + ## Complex filtering + + + + +```bash +curl 'http://localhost:1337/api/books?filters[$and][0][$or][0][date][$eq]=2020-01-01&filters[$and][0][$or][1][date][$eq]=2020-01-02&filters[$and][1][author][name][$eq]=Kai%20doe' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ filters: { $and: [ @@ -249,14 +276,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/books?\${query}\`);`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: `{ +await request(`/api/books?${query}`); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -272,10 +302,13 @@ await request(\`/api/books?\${query}\`);`, } ], "meta": {} -}`, - }, - ]} -/> +} +``` + + + + + :::note The response above only contains a book's own attributes. The `author` relation traversed by the `$and` filter is not returned unless requested through the [`populate` parameter](/cms/api/rest/populate-select#population), for example by adding `&populate=author` to the request. @@ -297,17 +330,22 @@ Querying your API with deep filters may cause performance issues. If one of you + + + + +```bash +curl 'http://localhost:1337/api/restaurants?filters[chef][restaurants][stars][$eq]=5' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ filters: { chef: { @@ -322,14 +360,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -345,11 +386,13 @@ await request(\`/api/restaurants?\${query}\`);`, } ], "meta": {} -}`, - }, - ]} - isLast -/> +} +``` + + + + + :::note The response above mirrors the default REST output, which excludes the relations traversed by the filter. Add a [`populate` parameter](/cms/api/rest/populate-select#population) such as `&populate[chef][populate][restaurants]=true` to also return the `chef.restaurants` relation referenced in the filter. diff --git a/docusaurus/docs/cms/api/rest/guides/understanding-populate.md b/docusaurus/docs/cms/api/rest/guides/understanding-populate.md index 0792aa1229..07716c63d9 100644 --- a/docusaurus/docs/cms/api/rest/guides/understanding-populate.md +++ b/docusaurus/docs/cms/api/rest/guides/understanding-populate.md @@ -85,23 +85,43 @@ The following example is the full response for all 4 entries from the `articles` Notice how the response only includes the `title`, `slug`, `createdAt`, `updatedAt`, `publishedAt`, and `locale` fields, and the field content of the article as handled by the CKEditor plugin (`ckeditor_content`, truncated for brevity): + + + + +```bash +curl 'http://localhost:1337/api/articles' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -156,10 +176,13 @@ Notice how the response only includes the `title`, `slug`, `createdAt`, `updated "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + ### Example: With `populate=*` @@ -180,23 +203,43 @@ To populate deeply nested components, see the [populate components](#populate-co
+ + + + +```bash +curl 'http://localhost:1337/api/articles?populate=*' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles?populate=*', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -338,10 +381,13 @@ To populate deeply nested components, see the [populate components](#populate-co "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + ## Populate specific relations and fields @@ -429,23 +475,43 @@ Notice that the response does not include any media fields, relations, component
+ + + + +```bash +curl 'http://localhost:1337/api/articles' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -500,10 +566,13 @@ Notice that the response does not include any media fields, relations, component "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + #### Example: With `populate[0]=category` @@ -514,23 +583,43 @@ The following example is the full response for all 4 entries from the `articles` Notice that the response now includes additional data with the `category` field for each article (see highlighted lines): + + + + +```bash +curl 'http://localhost:1337/api/articles?populate[0]=category' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles?populate[0]=category', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -624,10 +713,13 @@ Notice that the response now includes additional data with the `category` field "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + ### Populate several levels deep for specific relations @@ -670,23 +762,43 @@ Let's compare and explain the responses returned with `populate[0]=category` (1 When we only populate 1 level deep, asking for the categories associated to articles, we can get the following example response (highlighted lines show the `category` relations field): + + + + +```bash +curl 'http://localhost:1337/api/articles?populate[0]=category' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles?populate[0]=category', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -780,10 +892,13 @@ When we only populate 1 level deep, asking for the categories associated to arti "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + #### Example: With 2-level deep population @@ -792,23 +907,43 @@ When we populate 2 levels deep, asking for the categories associated to articles Notice that we now have the `restaurants` relation field included with the response inside the `category` relation (see highlighted lines): + + + + +```bash +curl 'http://localhost:1337/api/articles?populate[category][populate][0]=restaurants' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles?populate[category][populate][0]=restaurants', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -886,10 +1021,13 @@ Notice that we now have the `restaurants` relation field included with the respo "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + ### Populate components @@ -933,23 +1071,43 @@ When we only populate the `seo` component, we go only 1 level deep, and we can g Notice there's no mention of the `metaSocial` component nested within the `seo` component: + + + + +```bash +curl 'http://localhost:1337/api/articles?populate[0]=seo' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles?populate[0]=seo', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -994,10 +1152,13 @@ Notice there's no mention of the `metaSocial` component nested within the `seo` "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + #### Example: 1st level and 2nd level component @@ -1006,23 +1167,43 @@ When we populate 2 levels deep, asking both for the `seo` component and the `met Notice that we now have the `metaSocial` component-related data included with the response (see highlighted lines): + + + + +```bash +curl 'http://localhost:1337/api/articles?populate[0]=seo&populate[1]=seo.metaSocial' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles?populate[0]=seo&populate[1]=seo.metaSocial', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -1076,10 +1257,13 @@ Notice that we now have the `metaSocial` component-related data included with th "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + ### Populate dynamic zones @@ -1363,23 +1547,43 @@ In the following example response, highlighted lines show that: - But because we have only asked to populate everything for the `CtaCommandLine` component and have not defined anything for the `faq` component, no data from the `faq` component is returned. + + + + +```bash +curl 'http://localhost:1337/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const response = await fetch( + 'http://localhost:1337/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*', + { + headers: { + Authorization: 'Bearer ', + }, + } +); +const data = await response.json(); +``` + + + + + + + +```json +{ "data": [ { "id": 1, @@ -1483,10 +1687,13 @@ In the following example response, highlighted lines show that: "total": 4 } } -}` - } - ]} -/> +} +``` + + + + + :::tip Avoid over-populating in production Using `populate=*` or deep population plugins can create unpredictable, costly database queries. In production, always populate explicitly and limit depth to 2-3 levels. Consider using route-level middlewares to centralize population logic. See on the Strapi blog. diff --git a/docusaurus/docs/cms/api/rest/locale.md b/docusaurus/docs/cms/api/rest/locale.md index 4db4189937..5b66b74e62 100644 --- a/docusaurus/docs/cms/api/rest/locale.md +++ b/docusaurus/docs/cms/api/rest/locale.md @@ -75,52 +75,55 @@ The following table lists the new possible use cases added by i18n to the REST A method="GET" path="/api/restaurants?locale=fr" title="Get all documents in a specific locale" - description="Returns all documents for a given locale." - codeTabs={[ - { - label: 'Request', - code: `GET http://localhost:1337/api/restaurants?locale=fr`, - }, - ]} - responses={[ + description="Returns all documents for a given locale."> + +```bash +curl 'http://localhost:1337/api/restaurants?locale=fr' \ + -H 'Authorization: Bearer ' +``` + + + + +```json +{ + "data": [ { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: [ - { - id: 5, - documentId: "h90lgohlzfpjf3bvan72mzll", - Title: "Meilleures pizzas", - Body: [ - { - type: "paragraph", - children: [ - { - type: "text", - text: "On déguste les meilleures pizzas de la ville à la Pizzeria Arrivederci." - } - ] - } - ], - createdAt: "2024-03-06T22:08:59.643Z", - updatedAt: "2024-03-06T22:10:21.127Z", - publishedAt: "2024-03-06T22:10:21.130Z", - locale: "fr" - } - ], - meta: { - pagination: { - page: 1, - pageSize: 25, - pageCount: 1, - total: 1 - } + "id": 5, + "documentId": "h90lgohlzfpjf3bvan72mzll", + "Title": "Meilleures pizzas", + "Body": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "text": "On déguste les meilleures pizzas de la ville à la Pizzeria Arrivederci." + } + ] } - }, null, 2), - }, - ]} -/> + ], + "createdAt": "2024-03-06T22:08:59.643Z", + "updatedAt": "2024-03-06T22:10:21.127Z", + "publishedAt": "2024-03-06T22:10:21.130Z", + "locale": "fr" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + + + + ### `GET` Get a document in a specific locale {#rest-get} @@ -140,49 +143,52 @@ To get a specific document in a collection type in a given locale, add the `loca method="GET" path="/api/restaurants/:documentId?locale=fr" title="Get a document in a specific locale (collection type)" - description="Returns a specific document in a collection type for a given locale." - codeTabs={[ - { - label: 'Request', - code: `GET /api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr`, - }, - ]} - responses={[ + description="Returns a specific document in a collection type for a given locale."> + +```bash +curl 'http://localhost:1337/api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr' \ + -H 'Authorization: Bearer ' +``` + + + + +```json +{ + "data": [ { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: [ - { - id: 22, - documentId: "lr5wju2og49bf820kj9kz8c3", - Name: "Biscotte Restaurant", - Description: [ - { - type: "paragraph", - children: [ - { - type: "text", - text: "Bienvenue au restaurant Biscotte! Le Restaurant Biscotte propose une cuisine à base de produits frais et de qualité, souvent locaux, biologiques lorsque cela est possible, et toujours produits par des producteurs passionnés." - } - ] - } - ], - locale: "fr" - } - ], - meta: { - pagination: { - page: 1, - pageSize: 25, - pageCount: 1, - total: 3 - } + "id": 22, + "documentId": "lr5wju2og49bf820kj9kz8c3", + "Name": "Biscotte Restaurant", + "Description": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "text": "Bienvenue au restaurant Biscotte! Le Restaurant Biscotte propose une cuisine à base de produits frais et de qualité, souvent locaux, biologiques lorsque cela est possible, et toujours produits par des producteurs passionnés." + } + ] } - }, null, 2), - }, - ]} -/> + ], + "locale": "fr" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 3 + } + } +} +``` + + + + + #### Single types {#get-one-single-type} @@ -193,33 +199,36 @@ To get a specific single type document in a given locale, add the `locale` param method="GET" path="/api/homepage?locale=fr" title="Get a document in a specific locale (single type)" - description="Returns a specific single type document for a given locale." - codeTabs={[ - { - label: 'Request', - code: `GET /api/homepage?locale=fr`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: { - id: 10, - documentId: "ukbpbnu8kbutpn98rsanyi50", - Title: "Page d'accueil", - Body: null, - createdAt: "2024-03-07T13:28:26.349Z", - updatedAt: "2024-03-07T13:28:26.349Z", - publishedAt: "2024-03-07T13:28:26.353Z", - locale: "fr" - }, - meta: {} - }, null, 2), - }, - ]} -/> + description="Returns a specific single type document for a given locale."> + +```bash +curl 'http://localhost:1337/api/homepage?locale=fr' \ + -H 'Authorization: Bearer ' +``` + + + + +```json +{ + "data": { + "id": 10, + "documentId": "ukbpbnu8kbutpn98rsanyi50", + "Title": "Page d'accueil", + "Body": null, + "createdAt": "2024-03-07T13:28:26.349Z", + "updatedAt": "2024-03-07T13:28:26.349Z", + "publishedAt": "2024-03-07T13:28:26.353Z", + "locale": "fr" + }, + "meta": {} +} +``` + + + + + ### `POST` Create a new localized document for a collection type {#rest-create} @@ -239,39 +248,43 @@ If no locale has been passed in the request body, the document is created using method="POST" path="/api/restaurants" title="Create a document for the default locale" - description="Creates a new document using the default locale." - codeTabs={[ - { - label: 'Request', - code: `POST http://localhost:1337/api/restaurants - + description="Creates a new document using the default locale."> + +```bash +curl -X POST \ + 'http://localhost:1337/api/restaurants' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "data": { + "Name": "Oplato" + } + }' +``` + + + + +```json { "data": { - "Name": "Oplato" - } -}`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: { - id: 13, - documentId: "jae8klabhuucbkgfe2xxc5dj", - Name: "Oplato", - Description: null, - createdAt: "2024-03-06T22:19:54.646Z", - updatedAt: "2024-03-06T22:19:54.646Z", - publishedAt: "2024-03-06T22:19:54.649Z", - locale: "en" - }, - meta: {} - }, null, 2), - }, - ]} -/> + "id": 13, + "documentId": "jae8klabhuucbkgfe2xxc5dj", + "Name": "Oplato", + "Description": null, + "createdAt": "2024-03-06T22:19:54.646Z", + "updatedAt": "2024-03-06T22:19:54.646Z", + "publishedAt": "2024-03-06T22:19:54.649Z", + "locale": "en" + }, + "meta": {} +} +``` + + + + + #### For a specific locale {#rest-create-specific-locale} @@ -282,39 +295,43 @@ To create a localized entry for a locale different from the default one, add the method="POST" path="/api/restaurants?locale=fr" title="Create a document for a specific locale" - description="Creates a new document for a specified locale." - codeTabs={[ - { - label: 'Request', - code: `POST http://localhost:1337/api/restaurants?locale=fr - + description="Creates a new document for a specified locale."> + +```bash +curl -X POST \ + 'http://localhost:1337/api/restaurants?locale=fr' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "data": { + "Name": "She'\''s Cake" + } + }' +``` + + + + +```json { "data": { - "Name": "She's Cake" - } -}`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: { - id: 15, - documentId: "ldcmn698iams5nuaehj69j5o", - Name: "She's Cake", - Description: null, - createdAt: "2024-03-06T22:21:18.373Z", - updatedAt: "2024-03-06T22:21:18.373Z", - publishedAt: "2024-03-06T22:21:18.378Z", - locale: "fr" - }, - meta: {} - }, null, 2), - }, - ]} -/> + "id": 15, + "documentId": "ldcmn698iams5nuaehj69j5o", + "Name": "She's Cake", + "Description": null, + "createdAt": "2024-03-06T22:21:18.373Z", + "updatedAt": "2024-03-06T22:21:18.373Z", + "publishedAt": "2024-03-06T22:21:18.378Z", + "locale": "fr" + }, + "meta": {} +} +``` + + + + + ### `PUT` Create a new, or update an existing, locale version for an existing document {#rest-update} @@ -351,39 +368,43 @@ To create a new locale for an existing document in a collection type, add the `l method="PUT" path="/api/restaurants/:documentId?locale=fr" title="Create or update a locale version (collection type)" - description="Creates a French locale for an existing restaurant, or updates it if it already exists." - codeTabs={[ - { - label: 'Request', - code: `PUT http://localhost:1337/api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr - + description="Creates a French locale for an existing restaurant, or updates it if it already exists."> + +```bash +curl -X PUT \ + 'http://localhost:1337/api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "data": { + "Name": "She'\''s Cake in French" + } + }' +``` + + + + +```json { "data": { - "Name": "She's Cake in French" - } -}`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: { - id: 19, - documentId: "lr5wju2og49bf820kj9kz8c3", - Name: "She's Cake in French", - Description: null, - createdAt: "2024-03-07T12:13:09.551Z", - updatedAt: "2024-03-07T12:13:09.551Z", - publishedAt: "2024-03-07T12:13:09.554Z", - locale: "fr" - }, - meta: {} - }, null, 2), - }, - ]} -/> + "id": 19, + "documentId": "lr5wju2og49bf820kj9kz8c3", + "Name": "She's Cake in French", + "Description": null, + "createdAt": "2024-03-07T12:13:09.551Z", + "updatedAt": "2024-03-07T12:13:09.551Z", + "publishedAt": "2024-03-07T12:13:09.554Z", + "locale": "fr" + }, + "meta": {} +} +``` + + + + + #### In a single type {#rest-put-single-type} @@ -394,39 +415,43 @@ To create a new locale for an existing single type document, add the `locale` pa method="PUT" path="/api/homepage?locale=fr" title="Create or update a locale version (single type)" - description="Creates a French locale for an existing Homepage single type, or updates it if it already exists." - codeTabs={[ - { - label: 'Request', - code: `PUT http://localhost:1337/api/homepage?locale=fr - + description="Creates a French locale for an existing Homepage single type, or updates it if it already exists."> + +```bash +curl -X PUT \ + 'http://localhost:1337/api/homepage?locale=fr' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "data": { + "Title": "Page d'\''accueil" + } + }' +``` + + + + +```json { "data": { - "Title": "Page d'accueil" - } -}`, - }, - ]} - responses={[ - { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: { - id: 10, - documentId: "ukbpbnu8kbutpn98rsanyi50", - Title: "Page d'accueil", - Body: null, - createdAt: "2024-03-07T13:28:26.349Z", - updatedAt: "2024-03-07T13:28:26.349Z", - publishedAt: "2024-03-07T13:28:26.353Z", - locale: "fr" - }, - meta: {} - }, null, 2), - }, - ]} -/> + "id": 10, + "documentId": "ukbpbnu8kbutpn98rsanyi50", + "Title": "Page d'accueil", + "Body": null, + "createdAt": "2024-03-07T13:28:26.349Z", + "updatedAt": "2024-03-07T13:28:26.349Z", + "publishedAt": "2024-03-07T13:28:26.353Z", + "locale": "fr" + }, + "meta": {} +} +``` + + + + +
@@ -445,21 +470,25 @@ To delete only a specific locale version of a document in a collection type, add method="DELETE" path="/api/restaurants/:documentId?locale=fr" title="Delete a locale version (collection type)" - description="Deletes a specific locale version of a document in a collection type." - codeTabs={[ - { - label: 'Request', - code: `DELETE /api/restaurants/abcdefghijklmno456?locale=fr`, - }, - ]} - responses={[ - { - status: 204, - statusText: 'No Content', - body: '', - }, - ]} -/> + description="Deletes a specific locale version of a document in a collection type."> + +```bash +curl -X DELETE \ + 'http://localhost:1337/api/restaurants/abcdefghijklmno456?locale=fr' \ + -H 'Authorization: Bearer ' +``` + + + + +```json + +``` + + + + + #### In a single type {#rest-delete-single-type} @@ -470,18 +499,22 @@ To delete only a specific locale version of a single type document, add the `loc method="DELETE" path="/api/homepage?locale=fr" title="Delete a locale version (single type)" - description="Deletes a specific locale version of a single type document." - codeTabs={[ - { - label: 'Request', - code: `DELETE /api/homepage?locale=fr`, - }, - ]} - responses={[ - { - status: 204, - statusText: 'No Content', - body: '', - }, - ]} -/> + description="Deletes a specific locale version of a single type document."> + +```bash +curl -X DELETE \ + 'http://localhost:1337/api/homepage?locale=fr' \ + -H 'Authorization: Bearer ' +``` + + + + +```json + +``` + + + + + diff --git a/docusaurus/docs/cms/api/rest/populate-select.md b/docusaurus/docs/cms/api/rest/populate-select.md index b948349d2c..a754c15b0f 100644 --- a/docusaurus/docs/cms/api/rest/populate-select.md +++ b/docusaurus/docs/cms/api/rest/populate-select.md @@ -54,17 +54,22 @@ Field selection does not work on relational, media, component, or dynamic zone f id="field-selection-example" method="GET" path="/api/restaurants?fields[0]=name&fields[1]=description" - codePath="/api/restaurants?fields[0]=name&fields[1]=description" title="Return only name and description fields" - description="Use the fields parameter to select only specific fields in the response." - codeTabs={[ - { - label: 'URL', - code: `GET /api/restaurants?fields[0]=name&fields[1]=description`, - }, - { - label: 'JavaScript', - code: `const qs = require('qs'); + description="Use the fields parameter to select only specific fields in the response."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?fields[0]=name&fields[1]=description' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify( { fields: ['name', 'description'], @@ -74,44 +79,50 @@ const query = qs.stringify( } ); -await request(\`/api/users?\${query}\`);`, - }, - ]} - responses={[ +await request(`/api/users?${query}`); +``` + + + + + + + +```json +{ + "data": [ { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: [ - { - id: 4, - Name: "Pizzeria Arrivederci", - Description: [ - { - type: "paragraph", - children: [ - { - type: "text", - text: "Specialized in pizza, we invite you to rediscover our classics, such as 4 Formaggi or Calzone, and our original creations such as Do Luigi or Nduja." - } - ] - } - ], - documentId: "lr5wju2og49bf820kj9kz8c3" - } - ], - meta: { - pagination: { - page: 1, - pageSize: 25, - pageCount: 1, - total: 4 - } + "id": 4, + "Name": "Pizzeria Arrivederci", + "Description": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "text": "Specialized in pizza, we invite you to rediscover our classics, such as 4 Formaggi or Calzone, and our original creations such as Do Luigi or Nduja." + } + ] } - }, null, 2), - }, - ]} -/> + ], + "documentId": "lr5wju2og49bf820kj9kz8c3" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 4 + } + } +} +``` + + + + + ## Population @@ -163,17 +174,22 @@ Top-level pagination parameters (e.g., `pagination[page]` and `pagination[pageSi + + + + +```bash +curl 'http://localhost:1337/api/articles?fields[0]=title&fields[1]=slug&populate[headerImage][fields][0]=name&populate[headerImage][fields][1]=url' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify( { fields: ['title', 'slug'], @@ -188,33 +204,39 @@ const query = qs.stringify( } ); -await request(\`/api/articles?\${query}\`);`, - }, - ]} - responses={[ +await request(`/api/articles?${query}`); +``` + + + + + + + +```json +{ + "data": [ { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: [ - { - id: 1, - documentId: "h90lgohlzfpjf3bvan72mzll", - title: "Test Article", - slug: "test-article", - headerImage: { - id: 1, - documentId: "cf07g1dbusqr8mzmlbqvlegx", - name: "17520.jpg", - url: "/uploads/17520_73c601c014.jpg" - } - } - ], - meta: {} - }, null, 2), - }, - ]} -/> + "id": 1, + "documentId": "h90lgohlzfpjf3bvan72mzll", + "title": "Test Article", + "slug": "test-article", + "headerImage": { + "id": 1, + "documentId": "cf07g1dbusqr8mzmlbqvlegx", + "name": "17520.jpg", + "url": "/uploads/17520_73c601c014.jpg" + } + } + ], + "meta": {} +} +``` + + + + + #### Populate with filtering @@ -223,17 +245,22 @@ await request(\`/api/articles?\${query}\`);`, + + + + +```bash +curl 'http://localhost:1337/api/articles?populate[categories][sort][0]=name%3Aasc&populate[categories][filters][name][$eq]=Cars' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify( { populate: { @@ -252,35 +279,41 @@ const query = qs.stringify( } ); -await request(\`/api/articles?\${query}\`);`, - }, - ]} - responses={[ +await request(`/api/articles?${query}`); +``` + + + + + + + +```json +{ + "data": [ { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: [ + "id": 1, + "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", + "title": "Test Article", + "categories": { + "data": [ { - id: 1, - documentId: "a1b2c3d4e5d6f7g8h9i0jkl", - title: "Test Article", - categories: { - data: [ - { - id: 2, - documentId: "jKd8djla9ndalk98hflj3", - name: "Cars" - } - ] - } + "id": 2, + "documentId": "jKd8djla9ndalk98hflj3", + "name": "Cars" } - ], - meta: {} - }, null, 2), - }, - ]} -/> + ] + } + } + ], + "meta": {} +} +``` + + + + + :::note For many-to-many and other join-table relations, an explicit `sort` within a `populate` object overrides the default connect order. Omit `sort` to preserve the connect order (the order in which entries were associated). diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index b4e9506fa6..c9bdc8860b 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -90,18 +90,22 @@ This parameter combination works only on a given locale; to find these documents '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the drafts that have never been published for their locale."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'never-published', @@ -109,14 +113,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -133,10 +140,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + ### Find drafts never published in any locale {#never-published-document} @@ -147,18 +157,22 @@ A document counts as published as soon as one of its locales is published: the d '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the drafts of documents never published in any locale."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published-document' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'never-published-document', @@ -166,14 +180,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "d41r46wac4xix5vpba7561at", @@ -190,10 +207,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + ### Find modified documents {#modified} @@ -204,18 +224,22 @@ For instance, with `status=draft`, the query returns the draft versions: '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the draft versions of documents with unpublished changes."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=modified' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'modified', @@ -223,14 +247,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -247,10 +274,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + +
With `status=published` (the REST default), the same query returns the currently live version of those documents instead: @@ -258,32 +288,39 @@ With `status=published` (the REST default), the same query returns the currently '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the currently live versions of documents with unpublished changes."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=modified' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'modified', }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -300,10 +337,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + ### Find unmodified documents {#unmodified} @@ -314,18 +354,22 @@ For instance, with `status=draft`, the query returns the draft versions: '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the draft versions of documents unchanged since their last publication."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=unmodified' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'unmodified', @@ -333,14 +377,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -357,10 +404,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + +
With `status=published` (the REST default), the same query returns the currently live version of those documents instead: @@ -368,32 +418,39 @@ With `status=published` (the REST default), the same query returns the currently '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the currently live versions of documents unchanged since their last publication."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=unmodified' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'unmodified', }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -410,10 +467,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + ### Find documents with a published version {#has-published-version} @@ -424,18 +484,22 @@ For instance, with `status=draft`, the query returns the draft versions: '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the draft versions of documents that also have a published version for the same locale."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'has-published-version', @@ -443,14 +507,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -467,10 +534,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + +
With `status=published` (the REST default), the same query returns the currently live version of those documents instead: @@ -478,32 +548,39 @@ With `status=published` (the REST default), the same query returns the currently '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the currently live versions of documents that also have a published version for the same locale."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=has-published-version' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'has-published-version', }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -520,10 +597,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + ### Find documents with a published version in at least one locale {#has-published-version-document} @@ -532,18 +612,22 @@ await request(\`/api/restaurants?\${query}\`);` '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return the draft versions of documents published in at least one locale."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version-document' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'has-published-version-document', @@ -551,14 +635,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -575,10 +662,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + ## Diagnostic values {#diagnostics} @@ -592,32 +682,39 @@ The `published-without-draft` and `published-with-draft` values are meant for da '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return published documents with no matching draft version for the same locale."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=published-without-draft' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'published-without-draft', }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "j0klm1n2o3p4q5r6s7t8u9v", @@ -634,42 +731,52 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + `publicationFilter=published-with-draft` selects published documents that also have a draft, which is every published document in a healthy database: '` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Return published documents that also have a matching draft version for the same locale."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=published-with-draft' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'published-with-draft', }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", @@ -686,10 +793,13 @@ await request(\`/api/restaurants?\${query}\`);` "total": 1 } } -}` - } - ]} -/> +} +``` + + + + + diff --git a/docusaurus/docs/cms/api/rest/sort-pagination.md b/docusaurus/docs/cms/api/rest/sort-pagination.md index d9eceaa638..2e51a6b42f 100644 --- a/docusaurus/docs/cms/api/rest/sort-pagination.md +++ b/docusaurus/docs/cms/api/rest/sort-pagination.md @@ -58,31 +58,39 @@ You can sort by multiple fields by passing fields in a `sort` array. id="sort-using-2-fields" method="GET" path="/api/restaurants?sort[0]=Description&sort[1]=Name" - codePath="/api/restaurants?sort[0]=Description&sort[1]=Name" title="Sort using 2 fields" - description="Sort results by Description and Name fields." - codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?sort[0]=Description&sort[1]=Name` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Sort results by Description and Name fields."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?sort[0]=Description&sort[1]=Name' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ sort: ['Description', 'Name'], }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "id": 9, @@ -123,10 +131,13 @@ await request(\`/api/restaurants?\${query}\`);` "meta": { // … } -}` - } - ]} -/> +} +``` + + + + + ### Example: Sort using 2 fields and set the order @@ -136,31 +147,39 @@ Using the `sort` parameter and defining `:asc` or `:desc` on sorted fields, you id="sort-using-2-fields-and-set-the-order" method="GET" path="/api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc" - codePath="/api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc" title="Sort using 2 fields and set the order" - description="Sort results by Description ascending and Name descending." - codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc` - }, - { - label: "JavaScript", - code: `const qs = require('qs'); + description="Sort results by Description ascending and Name descending."> + + + + +```bash +curl 'http://localhost:1337/api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ sort: ['Description:asc', 'Name:desc'], }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/restaurants?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/restaurants?${query}`); +``` + + + + + + + +```json +{ "data": [ { "id": 8, @@ -201,10 +220,13 @@ await request(\`/api/restaurants?\${query}\`);` "meta": { // … } -}` - } - ]} -/> +} +``` + + + + + ## Pagination @@ -230,17 +252,22 @@ To paginate results by page, use the following parameters: + + + + +```bash +curl 'http://localhost:1337/api/articles?pagination[page]=1&pagination[pageSize]=10' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ pagination: { page: 1, @@ -250,14 +277,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/articles?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/articles?${query}`); +``` + + + + + + + +```json +{ "data": [ // ... ], @@ -269,10 +299,13 @@ await request(\`/api/articles?\${query}\`);` "total": 48 } } -}` - } - ]} -/> +} +``` + + + + + ### Pagination by offset @@ -291,17 +324,22 @@ The default and maximum values for `pagination[limit]` can be [configured in the + + + + +```bash +curl 'http://localhost:1337/api/articles?pagination[start]=0&pagination[limit]=10' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ pagination: { start: 0, @@ -311,14 +349,17 @@ const query = qs.stringify({ encodeValuesOnly: true, // prettify URL }); -await request(\`/api/articles?\${query}\`);` - } - ]} - responses={[ - { - status: 200, - statusText: "OK", - body: `{ +await request(`/api/articles?${query}`); +``` + + + + + + + +```json +{ "data": [ // ... ], @@ -329,7 +370,10 @@ await request(\`/api/articles?\${query}\`);` "total": 42 } } -}` - } - ]} -/> +} +``` + + + + + diff --git a/docusaurus/docs/cms/api/rest/status.md b/docusaurus/docs/cms/api/rest/status.md index ec3e1a7793..0ef80ea627 100644 --- a/docusaurus/docs/cms/api/rest/status.md +++ b/docusaurus/docs/cms/api/rest/status.md @@ -55,63 +55,73 @@ To select documents by how their draft and published versions relate (never-publ id="get-draft-versions" method="GET" path="/api/articles?status=draft" - codePath="/api/articles?status=draft" title="Get draft versions of restaurants" - description="Returns draft versions of documents by passing the status=draft query parameter." - codeTabs={[ - { - label: 'cURL', - code: `curl 'http://localhost:1337/api/articles?status=draft' \\ - -H 'Authorization: Bearer '`, - }, - { - label: 'JavaScript', - code: `const qs = require('qs'); + description="Returns draft versions of documents by passing the status=draft query parameter."> + + + + +```bash +curl 'http://localhost:1337/api/articles?status=draft' \ + -H 'Authorization: Bearer ' +``` + + + + +```js +const qs = require('qs'); const query = qs.stringify({ status: 'draft', }, { encodeValuesOnly: true, // prettify URL }); -await request(\`/api/articles?\${query}\`);`, - }, - ]} - responses={[ +await request(`/api/articles?${query}`); +``` + + + + + + + +```json +{ + "data": [ { - status: 200, - statusText: 'OK', - body: JSON.stringify({ - data: [ - { - id: 5, - documentId: "znrlzntu9ei5onjvwfaalu2v", - Name: "Biscotte Restaurant", - Description: [ - { - type: "paragraph", - children: [ - { - type: "text", - text: "This is the draft version." - } - ] - } - ], - createdAt: "2024-03-06T13:43:30.172Z", - updatedAt: "2024-03-06T21:38:46.353Z", - publishedAt: null, - locale: "en" - } - ], - meta: { - pagination: { - page: 1, - pageSize: 25, - pageCount: 1, - total: 4 - } + "id": 5, + "documentId": "znrlzntu9ei5onjvwfaalu2v", + "Name": "Biscotte Restaurant", + "Description": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "text": "This is the draft version." + } + ] } - }, null, 2), - }, - ]} -/> + ], + "createdAt": "2024-03-06T13:43:30.172Z", + "updatedAt": "2024-03-06T21:38:46.353Z", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 4 + } + } +} +``` + + + + + diff --git a/docusaurus/docs/cms/quick-start.md b/docusaurus/docs/cms/quick-start.md index a021617f3f..acac1f7128 100644 --- a/docusaurus/docs/cms/quick-start.md +++ b/docusaurus/docs/cms/quick-start.md @@ -166,7 +166,6 @@ sources={{ /> Once Strapi has restarted, "Restaurant" is listed under _Content Manager > Collection types_ in the navigation. Wow, you have just created your very first content-type! It was so cool, let's create another one right now, just for pleasure. -
diff --git a/docusaurus/src/components/ApiReference/CodePanel.jsx b/docusaurus/src/components/ApiReference/CodePanel.jsx deleted file mode 100644 index 62ef38200f..0000000000 --- a/docusaurus/src/components/ApiReference/CodePanel.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useState } from 'react'; -import CopyCodeButton from './CopyCodeButton'; -import styles from './api-reference.module.scss'; -import clsx from 'clsx'; - -/** - * CodePanel with language tabs and URL bar. - * - * Usage: - * - */ -export default function CodePanel({ kind = 'http', method = 'GET', path, pathHighlights = [], tabs = [] }) { - const [activeTab, setActiveTab] = useState(0); - const methodKey = method.toUpperCase() === 'DELETE' ? 'del' : method.toLowerCase(); - const displayMethod = method.toUpperCase() === 'DELETE' ? 'DEL' : method.toUpperCase(); - const isJs = kind === 'js'; - - // Highlight path params - const renderPath = (p) => { - if (!p) return null; - let result = p; - pathHighlights.forEach(h => { - result = result.replace(h, `${h}`); - }); - // Also highlight :param patterns - result = result.replace(/:(\w+)/g, `:$1`); - return ; - }; - - return ( -
- {tabs.length > 1 && ( -
- {tabs.map((tab, i) => ( - setActiveTab(i)} - > - {tab.label} - - ))} -
- )} - {path && !isJs && ( -
- - {displayMethod} - - {renderPath(path)} -
- )} -
- -
-          {tabs[activeTab]?.code || ''}
-        
-
-
- ); -} diff --git a/docusaurus/src/components/ApiReference/CopyCodeButton.jsx b/docusaurus/src/components/ApiReference/CopyCodeButton.jsx deleted file mode 100644 index 50571bdaf8..0000000000 --- a/docusaurus/src/components/ApiReference/CopyCodeButton.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import styles from './api-reference.module.scss'; -import clsx from 'clsx'; - -/** - * Self-contained copy button for the API reference code/response panels. - * - * The Docusaurus @theme/CodeBlock/CopyButton only renders correctly inside a - * real (it depends on that component's CSS modules for sizing and - * icons). Used standalone it collapses to a 0x0 element. This button owns its - * own markup and styles, so it works anywhere. - */ -export default function CopyCodeButton({ code = '' }) { - const [copied, setCopied] = useState(false); - - const onCopy = useCallback(() => { - if (!code) return; - navigator.clipboard.writeText(code).then(() => { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }); - }, [code]); - - return ( - - ); -} diff --git a/docusaurus/src/components/ApiReference/Endpoint.jsx b/docusaurus/src/components/ApiReference/Endpoint.jsx index 36301ab44e..b905855dd2 100644 --- a/docusaurus/src/components/ApiReference/Endpoint.jsx +++ b/docusaurus/src/components/ApiReference/Endpoint.jsx @@ -3,38 +3,37 @@ import useBrokenLinks from '@docusaurus/useBrokenLinks'; import styles from './api-reference.module.scss'; import MethodPill from './MethodPill'; import ParamTable from './ParamTable'; -import CodePanel from './CodePanel'; -import ResponsePanel from './ResponsePanel'; /** - * Full 2-column endpoint block matching the V3 mockup. + * 2-column endpoint block. * - * Usage (HTTP / REST): - * + * The component only owns what's specific to an API endpoint — the method pill, + * the URL, the description, and the parameter table. The actual code (request + * examples and responses) is passed as CHILDREN and rendered by the site's + * standard Tabs + CodeBlock components, exactly like every other code block on + * the site. This is why syntax highlighting and the copy / wrap / Ask AI + * buttons are identical here: they *are* the standard code blocks. * - * Usage (JS API — Document Service, etc.): pass kind="js". This hides the HTTP - * method pill and the URL bar, renders `path` as the JS method signature, and - * labels the result "Returns" instead of an HTTP status. A JS call has no HTTP - * verb and no HTTP status, so the http chrome is suppressed. - * + * Authoring (see docs for full example): + * + * + * + * + * ```bash + * curl … + * ``` + * + * + * + * + * + * ```json + * { … } + * ``` + * + * + * */ export default function Endpoint({ id, @@ -45,25 +44,17 @@ export default function Endpoint({ description, params = [], paramTitle = 'Parameters', - codeTabs = [], - codePath, - codePathHighlights = [], - responses = [], - collapsibleResponse = false, - isLast = false, children, }) { useBrokenLinks().collectAnchor(id); - const hasColumns = (params.length > 0 || children) && (codeTabs.length > 0 || responses.length > 0); const isJs = kind === 'js'; + const hasParams = params.length > 0; return (
- {/* Header: full-width, above the 2-column grid */}
{isJs ? ( @@ -71,53 +62,23 @@ export default function Endpoint({ ) : ( <> - - {path} - + {path} )}
+ {title &&

{title}

} {description &&

{description}

}
- {/* 2-column grid: params left, code right */} - {hasColumns ? ( + {hasParams ? (
- {params.length > 0 && } - {children} -
-
- {codeTabs.length > 0 && ( - - )} - {responses.length > 0 && } +
+
{children}
) : ( - /* Fallback: no params, just code below the header */ - <> - {(codeTabs.length > 0 || responses.length > 0) && ( -
- {codeTabs.length > 0 && ( - - )} - {responses.length > 0 && } -
- )} - {children &&
{children}
} - +
{children}
)}
); diff --git a/docusaurus/src/components/ApiReference/ResponsePanel.jsx b/docusaurus/src/components/ApiReference/ResponsePanel.jsx deleted file mode 100644 index 445f1c2901..0000000000 --- a/docusaurus/src/components/ApiReference/ResponsePanel.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useState } from 'react'; -import CopyCodeButton from './CopyCodeButton'; -import styles from './api-reference.module.scss'; -import clsx from 'clsx'; - -/** - * ResponsePanel with status tabs and animated dot. - * - * Usage: - * - */ -const COLLAPSE_LINE_THRESHOLD = 10; - -export default function ResponsePanel({ kind = 'http', responses = [], collapsible = false }) { - const [activeIdx, setActiveIdx] = useState(0); - const [expanded, setExpanded] = useState(false); - if (!responses.length) return null; - - const isJs = kind === 'js'; - const active = responses[activeIdx]; - const isOk = active.status < 400; - - // Opt-in: collapse long response bodies behind a "Show more" toggle. - const lineCount = active.body ? String(active.body).split('\n').length : 0; - const isCollapsible = collapsible && lineCount > COLLAPSE_LINE_THRESHOLD; - const collapsed = isCollapsible && !expanded; - - const getTabStyle = (status) => { - if (status < 300) return 'response-tab--2xx'; - if (status < 400) return 'response-tab--3xx'; - if (status < 500) return 'response-tab--4xx'; - return 'response-tab--5xx'; - }; - - return ( -
- {!isJs && responses.length > 1 && ( -
- {responses.map((r, i) => ( - setActiveIdx(i)} - > - {r.status} - - ))} -
- )} - -
- {isJs ? ( - - Returns - - ) : ( - <> - - - {active.status} {active.statusText} - - {active.time && {active.time}} - - )} -
- -
-
- -
-            {active.body}
-          
- {collapsed && - {isCollapsible && ( - - )} -
-
- ); -} diff --git a/docusaurus/src/components/ApiReference/Responses.jsx b/docusaurus/src/components/ApiReference/Responses.jsx new file mode 100644 index 0000000000..bf118f88b6 --- /dev/null +++ b/docusaurus/src/components/ApiReference/Responses.jsx @@ -0,0 +1,70 @@ +import React, { Children, isValidElement } from 'react'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import clsx from 'clsx'; +import ExpandableContent from '../ExpandableContent'; +import styles from './api-reference.module.scss'; + +/** + * Response block(s) for an endpoint. + * + * renders the site's standard (one tab per status), so the + * status switcher looks and behaves like every other tab set on the site — and + * the code inside each tab is a standard CodeBlock (identical highlighting and + * copy / wrap / Ask AI buttons). Inside each tab, a colored pulsing dot + label + * (e.g. "200 OK") heads the response, then the code. + * + * wraps one status' code fence. + */ + +function dotClass(status) { + return status < 400 ? styles['responseDot--ok'] : styles['responseDot--err']; +} +function labelClass(status) { + return status < 400 ? styles['responseStatus--ok'] : styles['responseStatus--err']; +} + +export function ResponseTab({ status, statusText = '', collapsible = false, children }) { + const s = Number(status); + return ( + <> +
+ + + {s} {statusText} + +
+ {collapsible ? ( + {children} + ) : ( + children + )} + + ); +} + +export default function Responses({ children }) { + const items = Children.toArray(children).filter( + (c) => isValidElement(c) && c.type === ResponseTab, + ); + + // Single response: no tabs needed, just render it. + if (items.length <= 1) { + return
{items}
; + } + + return ( +
+ + {items.map((item, i) => { + const status = Number(item.props.status); + return ( + + {item} + + ); + })} + +
+ ); +} diff --git a/docusaurus/src/components/ApiReference/api-reference.module.scss b/docusaurus/src/components/ApiReference/api-reference.module.scss index 6fa52de42a..96308b7954 100644 --- a/docusaurus/src/components/ApiReference/api-reference.module.scss +++ b/docusaurus/src/components/ApiReference/api-reference.module.scss @@ -175,27 +175,32 @@ /* ═══ ENDPOINT — 2-column split ═══ */ .endpoint { - border-bottom: 1px solid var(--strapi-border); - /* Full-width header above the 2-column grid */ &__header { - padding: 40px 44px 20px; + padding: 16px 0 20px; } /* 2-column grid: params left, code right */ &__columns { display: grid; grid-template-columns: 1fr 1fr; + // Only a COLUMN gap: when the layout linearizes to one column (narrow + // width), a plain `gap` would also add a large row gap between the stacked + // params and code, on top of their paddings. Keep the vertical spacing to + // the paddings alone. + column-gap: 60px; } &__desc { - padding: 24px 44px 40px; - border-right: 1px solid var(--strapi-border); + padding: 24px 0 20px; } &__methodRow { display: flex; - align-items: center; + // Top-align so the method pill stays next to the FIRST line of a long URL + // that wraps onto several lines, instead of being vertically centered + // against the whole wrapped block. + align-items: flex-start; gap: 12px; margin-bottom: 20px; } @@ -204,6 +209,7 @@ font-family: var(--strapi-font-family-mono); font-size: 14px; color: var(--strapi-fg-2); + padding-top: 2px; } // JS API signature (Document Service etc.): shown instead of a method pill + @@ -243,7 +249,7 @@ } &__code { - padding: 24px 32px 40px; + padding: 12px 0 40px; // No background tint: the contrast on the code/response column added visual // noise without purpose. The column blends with the page in light and dark. position: sticky; @@ -257,8 +263,7 @@ /* Fallback: code-only layout when there are no params */ &__codeOnly { - padding: 24px 44px 40px; - max-width: 720px; + padding: 24px 0 40px; } } @@ -273,6 +278,10 @@ letter-spacing: 0.02em; position: relative; overflow: hidden; + // Keep the pill a fixed, compact size next to long URLs: never shrink in the + // flex row and never let the verb ("GET") wrap onto a second line ("GE"/"T"). + flex-shrink: 0; + white-space: nowrap; &::after { content: ''; @@ -355,7 +364,6 @@ font-family: var(--strapi-font-family-mono); font-size: 11px; color: var(--strapi-fg-4); - margin-top: 2px; } &__badge { @@ -381,6 +389,9 @@ font-size: 13px; line-height: 1.5; color: var(--strapi-fg-3); + // Nudge down to visually align the description text with the + // required/optional badge on the same row. + padding-top: 6px; code { font-family: var(--strapi-font-family-mono); @@ -412,26 +423,37 @@ &__tabs { display: flex; + align-items: center; border-bottom: 1px solid var(--strapi-border); - padding: 0 4px; + padding: 0 8px 0 4px; background: var(--strapi-surface-2); } &__tab { + appearance: none; + background: transparent; + border: none; + // These tabs are now real )} - {langLabel && ( - {langLabel} - )}
); @@ -140,41 +136,51 @@ export default function CodeBlockWrapper(props) { // Inject a real DOM cursor element into terminal blocks // (CSS ::after pseudo-elements get their background overridden by parent rules) const terminalRef = useRef(null); - const injectCursor = useCallback((node) => { - terminalRef.current = node; - if (!node) return; - - // Find the last token line and append a real cursor span - const injectCursorElement = () => { - // Remove any previously injected cursor - const existing = node.querySelector('.terminal-cursor'); - if (existing) existing.remove(); - - const codeEl = node.querySelector('code'); - if (!codeEl) return; - - const lastLine = codeEl.querySelector('.token-line:last-child'); - if (!lastLine) return; - - const cursor = document.createElement('span'); - cursor.className = 'terminal-cursor'; - - // Insert before the trailing
so the cursor stays on the same line - const lastBr = lastLine.querySelector('br:last-child'); - if (lastBr) { - lastLine.insertBefore(cursor, lastBr); - lastBr.remove(); - } else { - lastLine.appendChild(cursor); - } - }; - // Run after Docusaurus finishes rendering the code block - requestAnimationFrame(() => { - requestAnimationFrame(injectCursorElement); - }); + // Terminal blocks render their own title bar (with the Ask AI button) as a + // sibling of the Docusaurus code block, while the native copy/wrap buttons + // live deep inside the code block. Rather than align the two across separate + // DOM subtrees with brittle absolute positioning, physically MOVE the native + // button group into the title bar's actions container so Ask AI + wrap + copy + // sit in one flex row and align naturally. + const relocateButtons = useCallback((wrapper) => { + if (!wrapper) return; + const move = () => { + const actions = wrapper.querySelector('.code-title-bar__actions'); + if (!actions) return; + // Relocate the native copy/wrap group into the title bar. React may + // re-render a FRESH group inside the code block after we move one; that + // fresh group is the live one (its click handlers are wired), while the + // one we moved earlier becomes a detached, dead clone. So whenever a group + // appears outside the bar, treat it as the live one: drop whatever stale + // group we previously parked in the bar and move the fresh one in. This + // keeps the working button (and fixes copy being dead inside StepDetails). + const groups = Array.from(wrapper.querySelectorAll('[class*="buttonGroup"]')); + const outside = groups.filter((g) => !g.closest('.code-title-bar__actions')); + if (outside.length === 0) return; // nothing new to relocate + // The last one in document order is the most recently rendered (live) one. + const live = outside[outside.length - 1]; + // Remove any other groups (stale relocated one in the bar + extra dupes). + groups.forEach((g) => { + if (g !== live) g.remove(); + }); + actions.appendChild(live); + }; + // Run now and keep watching: React may re-insert a group after our move. + requestAnimationFrame(() => requestAnimationFrame(move)); + const observer = new MutationObserver(move); + observer.observe(wrapper, { childList: true, subtree: true }); + // Stop observing shortly after mount once the DOM has settled. + setTimeout(() => observer.disconnect(), 2000); }, []); + // Ref callback for the terminal wrapper. Only relocates the action buttons + // into the title bar; the blinking end-of-line cursor was removed. + const injectCursor = useCallback((node) => { + terminalRef.current = node; + if (node) relocateButtons(node); + }, [relocateButtons]); + // Terminal blocks: macOS-style wrapper with title bar + blinking cursor if (isTerminal) { return ( diff --git a/docusaurus/src/theme/MDXComponents.js b/docusaurus/src/theme/MDXComponents.js index a6fb64fe10..c232c986fd 100644 --- a/docusaurus/src/theme/MDXComponents.js +++ b/docusaurus/src/theme/MDXComponents.js @@ -43,6 +43,7 @@ import Checklist, { ChecklistItem } from '../components/Checklist'; import DebugComponent from '../components/DebugComponent'; // V3: API 2-column layout import Endpoint from '../components/ApiReference/Endpoint'; +import Responses, { ResponseTab } from '../components/ApiReference/Responses'; import StepDetails from '../components/StepDetails/StepDetails'; import NextSteps from '../components/NextSteps/NextSteps'; @@ -111,6 +112,8 @@ export default { Codemods, NamingConventions, Endpoint, + Responses, + ResponseTab, StepDetails, NextSteps, };