tinacms 0.68.12 → 0.68.15

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.
@@ -25,7 +25,7 @@ export declare class TinaAdminApi {
25
25
  collection: string;
26
26
  relativePath: string;
27
27
  }): Promise<void>;
28
- fetchCollection(collectionName: string, includeDocuments: boolean, after?: string): Promise<Collection>;
28
+ fetchCollection(collectionName: string, includeDocuments: boolean, after?: string, sortKey?: string, order?: 'asc' | 'desc'): Promise<Collection>;
29
29
  fetchDocument(collectionName: string, relativePath: string): Promise<{
30
30
  document: DocumentForm;
31
31
  }>;
@@ -12,17 +12,19 @@ limitations under the License.
12
12
  */
13
13
  import type { TinaCMS } from '@tinacms/toolkit';
14
14
  import type { Collection } from '../types';
15
- export declare const useGetCollection: (cms: TinaCMS, collectionName: string, includeDocuments?: boolean, after?: string) => {
15
+ export declare const useGetCollection: (cms: TinaCMS, collectionName: string, includeDocuments?: boolean, after?: string, sortKey?: string) => {
16
16
  collection: Collection;
17
17
  loading: boolean;
18
18
  error: Error;
19
19
  reFetchCollection: () => void;
20
+ collectionExtra: import("@tinacms/schema-tools").TinaCloudCollection<true>;
20
21
  };
21
- declare const GetCollection: ({ cms, collectionName, includeDocuments, startCursor, children, }: {
22
+ declare const GetCollection: ({ cms, collectionName, includeDocuments, startCursor, sortKey, children, }: {
22
23
  cms: TinaCMS;
23
24
  collectionName: string;
24
25
  includeDocuments?: boolean;
25
26
  startCursor?: string;
27
+ sortKey?: string;
26
28
  children: any;
27
29
  }) => JSX.Element;
28
30
  export default GetCollection;
package/dist/client.es.js CHANGED
@@ -36,9 +36,7 @@ class TinaClient {
36
36
  };
37
37
  this.apiUrl = url;
38
38
  this.readonlyToken = token;
39
- if (queries) {
40
- this.queries = queries(this);
41
- }
39
+ this.queries = queries(this);
42
40
  }
43
41
  async request(args) {
44
42
  const headers = new Headers();
@@ -57,9 +55,18 @@ class TinaClient {
57
55
  body: bodyString,
58
56
  redirect: "follow"
59
57
  });
58
+ if (!res.ok) {
59
+ let additionalInfo = "";
60
+ if (res.status === 401) {
61
+ additionalInfo = "Please check that your client ID, URL and read only token are configured properly.";
62
+ }
63
+ throw new Error(`Server responded with status code ${res.status}, ${res.statusText}. ${additionalInfo ? additionalInfo : ""} Please see our FAQ for more information: https://tina.io/docs/errors/faq/`);
64
+ }
60
65
  const json = await res.json();
61
66
  if (json.errors) {
62
- throw new Error(`Unable to fetch, errors:
67
+ throw new Error(`Unable to fetch, please see our FAQ for more information: https://tina.io/docs/errors/faq/
68
+
69
+ Errors:
63
70
  ${json.errors.map((error) => error.message).join("\n")}`);
64
71
  }
65
72
  return {
package/dist/client.js CHANGED
@@ -43,9 +43,7 @@
43
43
  };
44
44
  this.apiUrl = url;
45
45
  this.readonlyToken = token;
46
- if (queries) {
47
- this.queries = queries(this);
48
- }
46
+ this.queries = queries(this);
49
47
  }
50
48
  async request(args) {
51
49
  const headers = new Headers();
@@ -64,9 +62,18 @@
64
62
  body: bodyString,
65
63
  redirect: "follow"
66
64
  });
65
+ if (!res.ok) {
66
+ let additionalInfo = "";
67
+ if (res.status === 401) {
68
+ additionalInfo = "Please check that your client ID, URL and read only token are configured properly.";
69
+ }
70
+ throw new Error(`Server responded with status code ${res.status}, ${res.statusText}. ${additionalInfo ? additionalInfo : ""} Please see our FAQ for more information: https://tina.io/docs/errors/faq/`);
71
+ }
67
72
  const json = await res.json();
68
73
  if (json.errors) {
69
- throw new Error(`Unable to fetch, errors:
74
+ throw new Error(`Unable to fetch, please see our FAQ for more information: https://tina.io/docs/errors/faq/
75
+
76
+ Errors:
70
77
  ${json.errors.map((error) => error.message).join("\n")}`);
71
78
  }
72
79
  return {
package/dist/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useCMS, Form, GlobalFormPlugin, EventBus, Modal, ModalPopup, ModalHeader, ModalBody, ModalActions, Button, LoadingDots, useLocalStorage, TinaCMS, BranchSwitcherPlugin, BranchDataProvider, TinaProvider, TinaMediaStore, DummyMediaStore, Nav, LocalWarning, OverflowMenu, CursorPaginator, PopupModal, FormStatus, FormBuilder } from "@tinacms/toolkit";
1
+ import { useCMS, Form, GlobalFormPlugin, EventBus, Modal, ModalPopup, ModalHeader, ModalBody, ModalActions, Button, LoadingDots, useLocalStorage, TinaCMS, BranchSwitcherPlugin, BranchDataProvider, TinaProvider, TinaMediaStore, DummyMediaStore, Nav, LocalWarning, Select, OverflowMenu, CursorPaginator, PopupModal, FormStatus, FormBuilder } from "@tinacms/toolkit";
2
2
  export * from "@tinacms/toolkit";
3
3
  import * as G from "graphql";
4
4
  import { TypeInfo, visit, visitWithTypeInfo, getNamedType, GraphQLObjectType, isLeafType, GraphQLUnionType, isScalarType as isScalarType$1, getIntrospectionQuery, buildClientSchema, print, parse } from "graphql";
@@ -2155,7 +2155,7 @@ mutation addPendingDocumentMutation(
2155
2155
  const enrichedSchema = new TinaSchema({
2156
2156
  version: { fullVersion: "", major: "", minor: "", patch: "" },
2157
2157
  meta: { flags: [] },
2158
- ...addNamespaceToSchema(options.schema, [])
2158
+ ...addNamespaceToSchema({ ...options.schema }, [])
2159
2159
  });
2160
2160
  this.schema = enrichedSchema;
2161
2161
  }
@@ -2459,7 +2459,6 @@ class TinaAdminApi {
2459
2459
  constructor(cms) {
2460
2460
  this.api = cms.api.tina;
2461
2461
  this.schema = cms.api.tina.schema;
2462
- this.useDataLayer = cms.flags.get("experimentalData");
2463
2462
  }
2464
2463
  async isAuthenticated() {
2465
2464
  return await this.api.isAuthenticated();
@@ -2484,11 +2483,10 @@ class TinaAdminApi {
2484
2483
  }
2485
2484
  }`, { variables: { collection, relativePath } });
2486
2485
  }
2487
- async fetchCollection(collectionName, includeDocuments, after) {
2486
+ async fetchCollection(collectionName, includeDocuments, after, sortKey, order) {
2488
2487
  if (includeDocuments === true) {
2489
- if (this.useDataLayer) {
2490
- const sort = this.schema.getIsTitleFieldName(collectionName);
2491
- const response = await this.api.request(`#graphql
2488
+ const sort = sortKey || this.schema.getIsTitleFieldName(collectionName);
2489
+ const response = order === "asc" ? await this.api.request(`#graphql
2492
2490
  query($collection: String!, $includeDocuments: Boolean!, $sort: String, $limit: Float, $after: String){
2493
2491
  collection(collection: $collection){
2494
2492
  name
@@ -2522,45 +2520,56 @@ class TinaAdminApi {
2522
2520
  }
2523
2521
  }
2524
2522
  }`, {
2525
- variables: {
2526
- collection: collectionName,
2527
- includeDocuments,
2528
- sort,
2529
- limit: 10,
2530
- after
2531
- }
2532
- });
2533
- return response.collection;
2534
- } else {
2535
- const response = await this.api.request(`#graphql
2536
- query($collection: String!, $includeDocuments: Boolean!){
2537
- collection(collection: $collection){
2538
- name
2539
- label
2540
- format
2541
- templates
2542
- documents @include(if: $includeDocuments) {
2543
- totalCount
2544
- edges {
2545
- node {
2546
- ... on Document {
2547
- _sys {
2548
- template
2549
- breadcrumbs
2550
- path
2551
- basename
2552
- relativePath
2553
- filename
2554
- extension
2523
+ variables: {
2524
+ collection: collectionName,
2525
+ includeDocuments,
2526
+ sort,
2527
+ limit: 10,
2528
+ after
2529
+ }
2530
+ }) : await this.api.request(`#graphql
2531
+ query($collection: String!, $includeDocuments: Boolean!, $sort: String, $limit: Float, $after: String){
2532
+ collection(collection: $collection){
2533
+ name
2534
+ label
2535
+ format
2536
+ templates
2537
+ documents(sort: $sort, before: $after, last: $limit) @include(if: $includeDocuments) {
2538
+ totalCount
2539
+ pageInfo {
2540
+ hasPreviousPage
2541
+ hasNextPage
2542
+ startCursor
2543
+ endCursor
2544
+ }
2545
+ edges {
2546
+ node {
2547
+ ... on Document {
2548
+ _sys {
2549
+ title
2550
+ template
2551
+ breadcrumbs
2552
+ path
2553
+ basename
2554
+ relativePath
2555
+ filename
2556
+ extension
2557
+ }
2555
2558
  }
2556
2559
  }
2557
2560
  }
2558
2561
  }
2559
2562
  }
2560
- }
2561
- }`, { variables: { collection: collectionName, includeDocuments } });
2562
- return response.collection;
2563
- }
2563
+ }`, {
2564
+ variables: {
2565
+ collection: collectionName,
2566
+ includeDocuments,
2567
+ sort,
2568
+ limit: 10,
2569
+ after
2570
+ }
2571
+ });
2572
+ return response.collection;
2564
2573
  } else {
2565
2574
  try {
2566
2575
  const collection = this.schema.getCollection(collectionName);
@@ -3312,6 +3321,9 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3312
3321
  .tina-tailwind .gap-4 {
3313
3322
  gap: 16px;
3314
3323
  }
3324
+ .tina-tailwind .gap-2 {
3325
+ gap: 8px;
3326
+ }
3315
3327
  .tina-tailwind .gap-3 {
3316
3328
  gap: 12px;
3317
3329
  }
@@ -3330,6 +3342,9 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3330
3342
  .tina-tailwind .overflow-y-auto {
3331
3343
  overflow-y: auto;
3332
3344
  }
3345
+ .tina-tailwind .whitespace-normal {
3346
+ white-space: normal;
3347
+ }
3333
3348
  .tina-tailwind .whitespace-nowrap {
3334
3349
  white-space: nowrap;
3335
3350
  }
@@ -3451,8 +3466,8 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3451
3466
  .tina-tailwind .pb-4 {
3452
3467
  padding-bottom: 16px;
3453
3468
  }
3454
- .tina-tailwind .pt-18 {
3455
- padding-top: 72px;
3469
+ .tina-tailwind .pt-16 {
3470
+ padding-top: 64px;
3456
3471
  }
3457
3472
  .tina-tailwind .pt-3 {
3458
3473
  padding-top: 12px;
@@ -3496,6 +3511,9 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3496
3511
  .tina-tailwind .font-medium {
3497
3512
  font-weight: 500;
3498
3513
  }
3514
+ .tina-tailwind .font-semibold {
3515
+ font-weight: 600;
3516
+ }
3499
3517
  .tina-tailwind .uppercase {
3500
3518
  text-transform: uppercase;
3501
3519
  }
@@ -4590,7 +4608,7 @@ const PageHeader = ({
4590
4608
  isLocalMode,
4591
4609
  children
4592
4610
  }) => /* @__PURE__ */ React.createElement(React.Fragment, null, isLocalMode && /* @__PURE__ */ React.createElement(LocalWarning, null), /* @__PURE__ */ React.createElement("div", {
4593
- className: "bg-white pb-4 pt-18 border-b border-gray-200 px-12"
4611
+ className: "bg-white pb-4 pt-16 border-b border-gray-200 px-12"
4594
4612
  }, /* @__PURE__ */ React.createElement("div", {
4595
4613
  className: "w-full mx-auto max-w-screen-xl"
4596
4614
  }, /* @__PURE__ */ React.createElement("div", {
@@ -4717,8 +4735,10 @@ const LoadingPage = () => /* @__PURE__ */ React.createElement(React.Fragment, nu
4717
4735
  fontWeight: "normal"
4718
4736
  }
4719
4737
  }, "Please wait, Tina is loading data..."))));
4720
- const useGetCollection = (cms, collectionName, includeDocuments = true, after = "") => {
4738
+ const useGetCollection = (cms, collectionName, includeDocuments = true, after = "", sortKey) => {
4721
4739
  const api = new TinaAdminApi(cms);
4740
+ const schema = cms.api.tina.schema;
4741
+ const collectionExtra = schema.getCollection(collectionName);
4722
4742
  const [collection, setCollection] = useState(void 0);
4723
4743
  const [loading, setLoading] = useState(true);
4724
4744
  const [error, setError] = useState(void 0);
@@ -4726,8 +4746,10 @@ const useGetCollection = (cms, collectionName, includeDocuments = true, after =
4726
4746
  useEffect(() => {
4727
4747
  const fetchCollection = async () => {
4728
4748
  if (await api.isAuthenticated()) {
4749
+ const { name, order } = JSON.parse(sortKey || "{}");
4750
+ const validSortKey = collectionExtra.fields.map((x) => x.name).includes(name) ? name : void 0;
4729
4751
  try {
4730
- const collection2 = await api.fetchCollection(collectionName, includeDocuments, after);
4752
+ const collection2 = await api.fetchCollection(collectionName, includeDocuments, after, validSortKey, order);
4731
4753
  setCollection(collection2);
4732
4754
  } catch (error2) {
4733
4755
  cms.alerts.error(`[${error2.name}] GetCollection failed: ${error2.message}`, 30 * 1e3);
@@ -4740,26 +4762,29 @@ const useGetCollection = (cms, collectionName, includeDocuments = true, after =
4740
4762
  };
4741
4763
  setLoading(true);
4742
4764
  fetchCollection();
4743
- }, [cms, collectionName, resetState, after]);
4765
+ }, [cms, collectionName, resetState, after, sortKey]);
4744
4766
  const reFetchCollection = () => setResetSate((x) => x + 1);
4745
- return { collection, loading, error, reFetchCollection };
4767
+ return { collection, loading, error, reFetchCollection, collectionExtra };
4746
4768
  };
4747
4769
  const GetCollection = ({
4748
4770
  cms,
4749
4771
  collectionName,
4750
4772
  includeDocuments = true,
4751
4773
  startCursor,
4774
+ sortKey,
4752
4775
  children
4753
4776
  }) => {
4754
- const { collection, loading, error, reFetchCollection } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "");
4777
+ const { collection, loading, error, reFetchCollection, collectionExtra } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "", sortKey) || {};
4755
4778
  if (error) {
4756
4779
  return null;
4757
4780
  }
4758
4781
  if (loading) {
4759
4782
  return /* @__PURE__ */ React.createElement(LoadingPage, null);
4760
4783
  }
4761
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children(collection, loading, reFetchCollection));
4784
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children(collection, loading, reFetchCollection, collectionExtra));
4762
4785
  };
4786
+ const LOCAL_STORAGE_KEY = "tinacms.admin.collection.list.page";
4787
+ const isSSR = typeof window === "undefined";
4763
4788
  const TemplateMenu = ({ templates }) => {
4764
4789
  return /* @__PURE__ */ React.createElement(Menu, {
4765
4790
  as: "div",
@@ -4808,8 +4833,17 @@ const CollectionListPage = () => {
4808
4833
  });
4809
4834
  const [endCursor, setEndCursor] = useState("");
4810
4835
  const [prevCursors, setPrevCursors] = useState([]);
4836
+ const [sortKey, setSortKey] = useState(isSSR ? "" : window.localStorage.getItem(`${LOCAL_STORAGE_KEY}.${collectionName}`) || JSON.stringify({
4837
+ order: "asc",
4838
+ name: ""
4839
+ }));
4840
+ const [sortOrder, setSortOrder] = useState("asc");
4811
4841
  const loc = useLocation();
4812
4842
  useEffect(() => {
4843
+ setSortKey(window.localStorage.getItem(`${LOCAL_STORAGE_KEY}.${collectionName}`) || JSON.stringify({
4844
+ order: "asc",
4845
+ name: ""
4846
+ }));
4813
4847
  setEndCursor("");
4814
4848
  setPrevCursors([]);
4815
4849
  }, [loc]);
@@ -4818,14 +4852,15 @@ const CollectionListPage = () => {
4818
4852
  cms,
4819
4853
  collectionName,
4820
4854
  includeDocuments: true,
4821
- startCursor: endCursor
4822
- }, (collection, _loading, reFetchCollection) => {
4855
+ startCursor: endCursor,
4856
+ sortKey
4857
+ }, (collection, _loading, reFetchCollection, collectionExtra) => {
4823
4858
  var _a, _b;
4824
4859
  const totalCount = collection.documents.totalCount;
4825
4860
  const documents = collection.documents.edges;
4826
4861
  const admin = cms.api.admin;
4827
4862
  const pageInfo = collection.documents.pageInfo;
4828
- const useDataFlag = cms.flags.get("experimentalData");
4863
+ const fields = collectionExtra.fields.filter((x) => ["string", "number", "datetime"].includes(x.type));
4829
4864
  return /* @__PURE__ */ React.createElement(PageWrapper, null, /* @__PURE__ */ React.createElement(React.Fragment, null, open && /* @__PURE__ */ React.createElement(DeleteModal, {
4830
4865
  filename: vars.relativePath,
4831
4866
  deleteFunc: async () => {
@@ -4842,9 +4877,56 @@ const CollectionListPage = () => {
4842
4877
  close: () => setOpen(false)
4843
4878
  }), /* @__PURE__ */ React.createElement(PageHeader, {
4844
4879
  isLocalMode: (_b = (_a = cms == null ? void 0 : cms.api) == null ? void 0 : _a.tina) == null ? void 0 : _b.isLocalMode
4845
- }, /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("h3", {
4880
+ }, /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
4881
+ className: "flex flex-col gap-4"
4882
+ }, /* @__PURE__ */ React.createElement("h3", {
4846
4883
  className: "font-sans text-2xl text-gray-700"
4847
- }, collection.label ? collection.label : collection.name), !collection.templates && /* @__PURE__ */ React.createElement(Link, {
4884
+ }, collection.label ? collection.label : collection.name), fields.length > 0 && /* @__PURE__ */ React.createElement("div", {
4885
+ className: "flex gap-2 items-center"
4886
+ }, /* @__PURE__ */ React.createElement("label", {
4887
+ htmlFor: "sort",
4888
+ className: "block font-sans text-xs font-semibold text-gray-500 whitespace-normal"
4889
+ }, "Sort by"), /* @__PURE__ */ React.createElement(Select, {
4890
+ name: "sort",
4891
+ options: [
4892
+ {
4893
+ label: "Default",
4894
+ value: JSON.stringify({
4895
+ order: "asc",
4896
+ name: ""
4897
+ })
4898
+ },
4899
+ ...fields.map((x) => [
4900
+ {
4901
+ label: x.label + " (Ascending)",
4902
+ value: JSON.stringify({
4903
+ name: x.name,
4904
+ order: "asc"
4905
+ })
4906
+ },
4907
+ {
4908
+ label: x.label + " (Descending)",
4909
+ value: JSON.stringify({
4910
+ name: x.name,
4911
+ order: "desc"
4912
+ })
4913
+ }
4914
+ ]).flat()
4915
+ ],
4916
+ input: {
4917
+ id: "sort",
4918
+ name: "sort",
4919
+ value: sortKey,
4920
+ onChange: (e) => {
4921
+ const val = JSON.parse(e.target.value);
4922
+ setEndCursor("");
4923
+ setPrevCursors([]);
4924
+ window == null ? void 0 : window.localStorage.setItem(`${LOCAL_STORAGE_KEY}.${collectionName}`, e.target.value);
4925
+ setSortKey(e.target.value);
4926
+ setSortOrder(val.order);
4927
+ }
4928
+ }
4929
+ }))), !collection.templates && /* @__PURE__ */ React.createElement(Link, {
4848
4930
  to: `new`,
4849
4931
  className: "icon-parent inline-flex items-center font-medium focus:outline-none focus:ring-2 focus:shadow-outline text-center rounded-full justify-center transition-all duration-150 ease-out shadow text-white bg-blue-500 hover:bg-blue-600 focus:ring-blue-500 text-sm h-10 px-6"
4850
4932
  }, "Create New", " ", /* @__PURE__ */ React.createElement(BiPlus, {
@@ -4928,11 +5010,11 @@ const CollectionListPage = () => {
4928
5010
  }
4929
5011
  ]
4930
5012
  })));
4931
- }))), useDataFlag && /* @__PURE__ */ React.createElement("div", {
5013
+ }))), /* @__PURE__ */ React.createElement("div", {
4932
5014
  className: "pt-3"
4933
5015
  }, /* @__PURE__ */ React.createElement(CursorPaginator, {
4934
5016
  variant: "white",
4935
- hasNext: pageInfo == null ? void 0 : pageInfo.hasNextPage,
5017
+ hasNext: sortOrder === "asc" ? pageInfo == null ? void 0 : pageInfo.hasNextPage : pageInfo.hasPreviousPage,
4936
5018
  navigateNext: () => {
4937
5019
  const newState = [...prevCursors, endCursor];
4938
5020
  setPrevCursors(newState);
@@ -5270,9 +5352,9 @@ const Redirect = () => {
5270
5352
  return null;
5271
5353
  };
5272
5354
  const TinaAdmin = () => {
5273
- const isSSR = typeof window === "undefined";
5355
+ const isSSR2 = typeof window === "undefined";
5274
5356
  const { edit } = useEditState();
5275
- if (isSSR) {
5357
+ if (isSSR2) {
5276
5358
  return null;
5277
5359
  }
5278
5360
  if (!edit) {
package/dist/index.js CHANGED
@@ -2173,7 +2173,7 @@ mutation addPendingDocumentMutation(
2173
2173
  const enrichedSchema = new schemaTools.TinaSchema({
2174
2174
  version: { fullVersion: "", major: "", minor: "", patch: "" },
2175
2175
  meta: { flags: [] },
2176
- ...schemaTools.addNamespaceToSchema(options.schema, [])
2176
+ ...schemaTools.addNamespaceToSchema({ ...options.schema }, [])
2177
2177
  });
2178
2178
  this.schema = enrichedSchema;
2179
2179
  }
@@ -2477,7 +2477,6 @@ mutation addPendingDocumentMutation(
2477
2477
  constructor(cms) {
2478
2478
  this.api = cms.api.tina;
2479
2479
  this.schema = cms.api.tina.schema;
2480
- this.useDataLayer = cms.flags.get("experimentalData");
2481
2480
  }
2482
2481
  async isAuthenticated() {
2483
2482
  return await this.api.isAuthenticated();
@@ -2502,11 +2501,10 @@ mutation addPendingDocumentMutation(
2502
2501
  }
2503
2502
  }`, { variables: { collection, relativePath } });
2504
2503
  }
2505
- async fetchCollection(collectionName, includeDocuments, after) {
2504
+ async fetchCollection(collectionName, includeDocuments, after, sortKey, order) {
2506
2505
  if (includeDocuments === true) {
2507
- if (this.useDataLayer) {
2508
- const sort = this.schema.getIsTitleFieldName(collectionName);
2509
- const response = await this.api.request(`#graphql
2506
+ const sort = sortKey || this.schema.getIsTitleFieldName(collectionName);
2507
+ const response = order === "asc" ? await this.api.request(`#graphql
2510
2508
  query($collection: String!, $includeDocuments: Boolean!, $sort: String, $limit: Float, $after: String){
2511
2509
  collection(collection: $collection){
2512
2510
  name
@@ -2540,45 +2538,56 @@ mutation addPendingDocumentMutation(
2540
2538
  }
2541
2539
  }
2542
2540
  }`, {
2543
- variables: {
2544
- collection: collectionName,
2545
- includeDocuments,
2546
- sort,
2547
- limit: 10,
2548
- after
2541
+ variables: {
2542
+ collection: collectionName,
2543
+ includeDocuments,
2544
+ sort,
2545
+ limit: 10,
2546
+ after
2547
+ }
2548
+ }) : await this.api.request(`#graphql
2549
+ query($collection: String!, $includeDocuments: Boolean!, $sort: String, $limit: Float, $after: String){
2550
+ collection(collection: $collection){
2551
+ name
2552
+ label
2553
+ format
2554
+ templates
2555
+ documents(sort: $sort, before: $after, last: $limit) @include(if: $includeDocuments) {
2556
+ totalCount
2557
+ pageInfo {
2558
+ hasPreviousPage
2559
+ hasNextPage
2560
+ startCursor
2561
+ endCursor
2549
2562
  }
2550
- });
2551
- return response.collection;
2552
- } else {
2553
- const response = await this.api.request(`#graphql
2554
- query($collection: String!, $includeDocuments: Boolean!){
2555
- collection(collection: $collection){
2556
- name
2557
- label
2558
- format
2559
- templates
2560
- documents @include(if: $includeDocuments) {
2561
- totalCount
2562
- edges {
2563
- node {
2564
- ... on Document {
2565
- _sys {
2566
- template
2567
- breadcrumbs
2568
- path
2569
- basename
2570
- relativePath
2571
- filename
2572
- extension
2563
+ edges {
2564
+ node {
2565
+ ... on Document {
2566
+ _sys {
2567
+ title
2568
+ template
2569
+ breadcrumbs
2570
+ path
2571
+ basename
2572
+ relativePath
2573
+ filename
2574
+ extension
2575
+ }
2573
2576
  }
2574
2577
  }
2575
2578
  }
2576
2579
  }
2577
2580
  }
2578
- }
2579
- }`, { variables: { collection: collectionName, includeDocuments } });
2580
- return response.collection;
2581
- }
2581
+ }`, {
2582
+ variables: {
2583
+ collection: collectionName,
2584
+ includeDocuments,
2585
+ sort,
2586
+ limit: 10,
2587
+ after
2588
+ }
2589
+ });
2590
+ return response.collection;
2582
2591
  } else {
2583
2592
  try {
2584
2593
  const collection = this.schema.getCollection(collectionName);
@@ -3330,6 +3339,9 @@ mutation addPendingDocumentMutation(
3330
3339
  .tina-tailwind .gap-4 {
3331
3340
  gap: 16px;
3332
3341
  }
3342
+ .tina-tailwind .gap-2 {
3343
+ gap: 8px;
3344
+ }
3333
3345
  .tina-tailwind .gap-3 {
3334
3346
  gap: 12px;
3335
3347
  }
@@ -3348,6 +3360,9 @@ mutation addPendingDocumentMutation(
3348
3360
  .tina-tailwind .overflow-y-auto {
3349
3361
  overflow-y: auto;
3350
3362
  }
3363
+ .tina-tailwind .whitespace-normal {
3364
+ white-space: normal;
3365
+ }
3351
3366
  .tina-tailwind .whitespace-nowrap {
3352
3367
  white-space: nowrap;
3353
3368
  }
@@ -3469,8 +3484,8 @@ mutation addPendingDocumentMutation(
3469
3484
  .tina-tailwind .pb-4 {
3470
3485
  padding-bottom: 16px;
3471
3486
  }
3472
- .tina-tailwind .pt-18 {
3473
- padding-top: 72px;
3487
+ .tina-tailwind .pt-16 {
3488
+ padding-top: 64px;
3474
3489
  }
3475
3490
  .tina-tailwind .pt-3 {
3476
3491
  padding-top: 12px;
@@ -3514,6 +3529,9 @@ mutation addPendingDocumentMutation(
3514
3529
  .tina-tailwind .font-medium {
3515
3530
  font-weight: 500;
3516
3531
  }
3532
+ .tina-tailwind .font-semibold {
3533
+ font-weight: 600;
3534
+ }
3517
3535
  .tina-tailwind .uppercase {
3518
3536
  text-transform: uppercase;
3519
3537
  }
@@ -4608,7 +4626,7 @@ This will work when developing locally but NOT when deployed to production.
4608
4626
  isLocalMode,
4609
4627
  children
4610
4628
  }) => /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, isLocalMode && /* @__PURE__ */ React__default["default"].createElement(toolkit.LocalWarning, null), /* @__PURE__ */ React__default["default"].createElement("div", {
4611
- className: "bg-white pb-4 pt-18 border-b border-gray-200 px-12"
4629
+ className: "bg-white pb-4 pt-16 border-b border-gray-200 px-12"
4612
4630
  }, /* @__PURE__ */ React__default["default"].createElement("div", {
4613
4631
  className: "w-full mx-auto max-w-screen-xl"
4614
4632
  }, /* @__PURE__ */ React__default["default"].createElement("div", {
@@ -4735,8 +4753,10 @@ This will work when developing locally but NOT when deployed to production.
4735
4753
  fontWeight: "normal"
4736
4754
  }
4737
4755
  }, "Please wait, Tina is loading data..."))));
4738
- const useGetCollection = (cms, collectionName, includeDocuments = true, after = "") => {
4756
+ const useGetCollection = (cms, collectionName, includeDocuments = true, after = "", sortKey) => {
4739
4757
  const api = new TinaAdminApi(cms);
4758
+ const schema = cms.api.tina.schema;
4759
+ const collectionExtra = schema.getCollection(collectionName);
4740
4760
  const [collection, setCollection] = React.useState(void 0);
4741
4761
  const [loading, setLoading] = React.useState(true);
4742
4762
  const [error, setError] = React.useState(void 0);
@@ -4744,8 +4764,10 @@ This will work when developing locally but NOT when deployed to production.
4744
4764
  React.useEffect(() => {
4745
4765
  const fetchCollection = async () => {
4746
4766
  if (await api.isAuthenticated()) {
4767
+ const { name, order } = JSON.parse(sortKey || "{}");
4768
+ const validSortKey = collectionExtra.fields.map((x) => x.name).includes(name) ? name : void 0;
4747
4769
  try {
4748
- const collection2 = await api.fetchCollection(collectionName, includeDocuments, after);
4770
+ const collection2 = await api.fetchCollection(collectionName, includeDocuments, after, validSortKey, order);
4749
4771
  setCollection(collection2);
4750
4772
  } catch (error2) {
4751
4773
  cms.alerts.error(`[${error2.name}] GetCollection failed: ${error2.message}`, 30 * 1e3);
@@ -4758,26 +4780,29 @@ This will work when developing locally but NOT when deployed to production.
4758
4780
  };
4759
4781
  setLoading(true);
4760
4782
  fetchCollection();
4761
- }, [cms, collectionName, resetState, after]);
4783
+ }, [cms, collectionName, resetState, after, sortKey]);
4762
4784
  const reFetchCollection = () => setResetSate((x) => x + 1);
4763
- return { collection, loading, error, reFetchCollection };
4785
+ return { collection, loading, error, reFetchCollection, collectionExtra };
4764
4786
  };
4765
4787
  const GetCollection = ({
4766
4788
  cms,
4767
4789
  collectionName,
4768
4790
  includeDocuments = true,
4769
4791
  startCursor,
4792
+ sortKey,
4770
4793
  children
4771
4794
  }) => {
4772
- const { collection, loading, error, reFetchCollection } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "");
4795
+ const { collection, loading, error, reFetchCollection, collectionExtra } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "", sortKey) || {};
4773
4796
  if (error) {
4774
4797
  return null;
4775
4798
  }
4776
4799
  if (loading) {
4777
4800
  return /* @__PURE__ */ React__default["default"].createElement(LoadingPage, null);
4778
4801
  }
4779
- return /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, children(collection, loading, reFetchCollection));
4802
+ return /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, children(collection, loading, reFetchCollection, collectionExtra));
4780
4803
  };
4804
+ const LOCAL_STORAGE_KEY = "tinacms.admin.collection.list.page";
4805
+ const isSSR = typeof window === "undefined";
4781
4806
  const TemplateMenu = ({ templates }) => {
4782
4807
  return /* @__PURE__ */ React__default["default"].createElement(react.Menu, {
4783
4808
  as: "div",
@@ -4826,8 +4851,17 @@ This will work when developing locally but NOT when deployed to production.
4826
4851
  });
4827
4852
  const [endCursor, setEndCursor] = React.useState("");
4828
4853
  const [prevCursors, setPrevCursors] = React.useState([]);
4854
+ const [sortKey, setSortKey] = React.useState(isSSR ? "" : window.localStorage.getItem(`${LOCAL_STORAGE_KEY}.${collectionName}`) || JSON.stringify({
4855
+ order: "asc",
4856
+ name: ""
4857
+ }));
4858
+ const [sortOrder, setSortOrder] = React.useState("asc");
4829
4859
  const loc = reactRouterDom.useLocation();
4830
4860
  React.useEffect(() => {
4861
+ setSortKey(window.localStorage.getItem(`${LOCAL_STORAGE_KEY}.${collectionName}`) || JSON.stringify({
4862
+ order: "asc",
4863
+ name: ""
4864
+ }));
4831
4865
  setEndCursor("");
4832
4866
  setPrevCursors([]);
4833
4867
  }, [loc]);
@@ -4836,14 +4870,15 @@ This will work when developing locally but NOT when deployed to production.
4836
4870
  cms,
4837
4871
  collectionName,
4838
4872
  includeDocuments: true,
4839
- startCursor: endCursor
4840
- }, (collection, _loading, reFetchCollection) => {
4873
+ startCursor: endCursor,
4874
+ sortKey
4875
+ }, (collection, _loading, reFetchCollection, collectionExtra) => {
4841
4876
  var _a, _b;
4842
4877
  const totalCount = collection.documents.totalCount;
4843
4878
  const documents = collection.documents.edges;
4844
4879
  const admin = cms.api.admin;
4845
4880
  const pageInfo = collection.documents.pageInfo;
4846
- const useDataFlag = cms.flags.get("experimentalData");
4881
+ const fields = collectionExtra.fields.filter((x) => ["string", "number", "datetime"].includes(x.type));
4847
4882
  return /* @__PURE__ */ React__default["default"].createElement(PageWrapper, null, /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, open && /* @__PURE__ */ React__default["default"].createElement(DeleteModal, {
4848
4883
  filename: vars.relativePath,
4849
4884
  deleteFunc: async () => {
@@ -4860,9 +4895,56 @@ This will work when developing locally but NOT when deployed to production.
4860
4895
  close: () => setOpen(false)
4861
4896
  }), /* @__PURE__ */ React__default["default"].createElement(PageHeader, {
4862
4897
  isLocalMode: (_b = (_a = cms == null ? void 0 : cms.api) == null ? void 0 : _a.tina) == null ? void 0 : _b.isLocalMode
4863
- }, /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, /* @__PURE__ */ React__default["default"].createElement("h3", {
4898
+ }, /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, /* @__PURE__ */ React__default["default"].createElement("div", {
4899
+ className: "flex flex-col gap-4"
4900
+ }, /* @__PURE__ */ React__default["default"].createElement("h3", {
4864
4901
  className: "font-sans text-2xl text-gray-700"
4865
- }, collection.label ? collection.label : collection.name), !collection.templates && /* @__PURE__ */ React__default["default"].createElement(reactRouterDom.Link, {
4902
+ }, collection.label ? collection.label : collection.name), fields.length > 0 && /* @__PURE__ */ React__default["default"].createElement("div", {
4903
+ className: "flex gap-2 items-center"
4904
+ }, /* @__PURE__ */ React__default["default"].createElement("label", {
4905
+ htmlFor: "sort",
4906
+ className: "block font-sans text-xs font-semibold text-gray-500 whitespace-normal"
4907
+ }, "Sort by"), /* @__PURE__ */ React__default["default"].createElement(toolkit.Select, {
4908
+ name: "sort",
4909
+ options: [
4910
+ {
4911
+ label: "Default",
4912
+ value: JSON.stringify({
4913
+ order: "asc",
4914
+ name: ""
4915
+ })
4916
+ },
4917
+ ...fields.map((x) => [
4918
+ {
4919
+ label: x.label + " (Ascending)",
4920
+ value: JSON.stringify({
4921
+ name: x.name,
4922
+ order: "asc"
4923
+ })
4924
+ },
4925
+ {
4926
+ label: x.label + " (Descending)",
4927
+ value: JSON.stringify({
4928
+ name: x.name,
4929
+ order: "desc"
4930
+ })
4931
+ }
4932
+ ]).flat()
4933
+ ],
4934
+ input: {
4935
+ id: "sort",
4936
+ name: "sort",
4937
+ value: sortKey,
4938
+ onChange: (e) => {
4939
+ const val = JSON.parse(e.target.value);
4940
+ setEndCursor("");
4941
+ setPrevCursors([]);
4942
+ window == null ? void 0 : window.localStorage.setItem(`${LOCAL_STORAGE_KEY}.${collectionName}`, e.target.value);
4943
+ setSortKey(e.target.value);
4944
+ setSortOrder(val.order);
4945
+ }
4946
+ }
4947
+ }))), !collection.templates && /* @__PURE__ */ React__default["default"].createElement(reactRouterDom.Link, {
4866
4948
  to: `new`,
4867
4949
  className: "icon-parent inline-flex items-center font-medium focus:outline-none focus:ring-2 focus:shadow-outline text-center rounded-full justify-center transition-all duration-150 ease-out shadow text-white bg-blue-500 hover:bg-blue-600 focus:ring-blue-500 text-sm h-10 px-6"
4868
4950
  }, "Create New", " ", /* @__PURE__ */ React__default["default"].createElement(BiPlus, {
@@ -4946,11 +5028,11 @@ This will work when developing locally but NOT when deployed to production.
4946
5028
  }
4947
5029
  ]
4948
5030
  })));
4949
- }))), useDataFlag && /* @__PURE__ */ React__default["default"].createElement("div", {
5031
+ }))), /* @__PURE__ */ React__default["default"].createElement("div", {
4950
5032
  className: "pt-3"
4951
5033
  }, /* @__PURE__ */ React__default["default"].createElement(toolkit.CursorPaginator, {
4952
5034
  variant: "white",
4953
- hasNext: pageInfo == null ? void 0 : pageInfo.hasNextPage,
5035
+ hasNext: sortOrder === "asc" ? pageInfo == null ? void 0 : pageInfo.hasNextPage : pageInfo.hasPreviousPage,
4954
5036
  navigateNext: () => {
4955
5037
  const newState = [...prevCursors, endCursor];
4956
5038
  setPrevCursors(newState);
@@ -5288,9 +5370,9 @@ This will work when developing locally but NOT when deployed to production.
5288
5370
  return null;
5289
5371
  };
5290
5372
  const TinaAdmin = () => {
5291
- const isSSR = typeof window === "undefined";
5373
+ const isSSR2 = typeof window === "undefined";
5292
5374
  const { edit } = sharedctx.useEditState();
5293
- if (isSSR) {
5375
+ if (isSSR2) {
5294
5376
  return null;
5295
5377
  }
5296
5378
  if (!edit) {
package/dist/style.css CHANGED
@@ -544,6 +544,9 @@
544
544
  .tina-tailwind .gap-4 {
545
545
  gap: 16px;
546
546
  }
547
+ .tina-tailwind .gap-2 {
548
+ gap: 8px;
549
+ }
547
550
  .tina-tailwind .gap-3 {
548
551
  gap: 12px;
549
552
  }
@@ -562,6 +565,9 @@
562
565
  .tina-tailwind .overflow-y-auto {
563
566
  overflow-y: auto;
564
567
  }
568
+ .tina-tailwind .whitespace-normal {
569
+ white-space: normal;
570
+ }
565
571
  .tina-tailwind .whitespace-nowrap {
566
572
  white-space: nowrap;
567
573
  }
@@ -683,8 +689,8 @@
683
689
  .tina-tailwind .pb-4 {
684
690
  padding-bottom: 16px;
685
691
  }
686
- .tina-tailwind .pt-18 {
687
- padding-top: 72px;
692
+ .tina-tailwind .pt-16 {
693
+ padding-top: 64px;
688
694
  }
689
695
  .tina-tailwind .pt-3 {
690
696
  padding-top: 12px;
@@ -728,6 +734,9 @@
728
734
  .tina-tailwind .font-medium {
729
735
  font-weight: 500;
730
736
  }
737
+ .tina-tailwind .font-semibold {
738
+ font-weight: 600;
739
+ }
731
740
  .tina-tailwind .uppercase {
732
741
  text-transform: uppercase;
733
742
  }
@@ -43,7 +43,7 @@ declare type APIProviderProps = {
43
43
  */
44
44
  branch?: never;
45
45
  /**
46
- * Your clientID from tina.aio
46
+ * Your clientId from tina.io
47
47
  *
48
48
  * @deprecated use apiURL instead
49
49
  */
@@ -73,41 +73,11 @@ declare type APIProviderProps = {
73
73
  */
74
74
  branch?: never;
75
75
  /**
76
- * Your clientID from tina.aio
76
+ * Your clientId from tina.io
77
77
  *
78
78
  * @deprecated use apiURL instead
79
79
  */
80
80
  clientId?: never;
81
- } | {
82
- /**
83
- * Content API URL
84
- *
85
- */
86
- apiURL?: never;
87
- /**
88
- * Point to the local version of GraphQL instead of tina.io
89
- * https://tina.io/docs/tinacms-context/#adding-tina-to-the-sites-frontend
90
- *
91
- * @deprecated use apiURL instead
92
- */
93
- isLocalClient?: boolean;
94
- /**
95
- * The base branch to pull content from. Note that this is ignored for local development
96
- *
97
- * @deprecated use apiURL instead
98
- */
99
- branch?: string;
100
- /**
101
- * Your clientID from tina.aio
102
- *
103
- * @deprecated use apiURL instead
104
- */
105
- clientId?: string;
106
- /**
107
- * The API url From this client will be used to make requests.
108
- *
109
- */
110
- client: never;
111
81
  };
112
82
  interface BaseProviderProps {
113
83
  /** Callback if you need access to the TinaCMS instance */
@@ -14,7 +14,7 @@ export declare const TINA_HOST = "content.tinajs.io";
14
14
  export interface TinaClientArgs<GenQueries = Record<string, unknown>> {
15
15
  url: string;
16
16
  token?: string;
17
- queries?: (client: TinaClient<GenQueries>) => GenQueries;
17
+ queries: (client: TinaClient<GenQueries>) => GenQueries;
18
18
  }
19
19
  export declare type TinaClientRequestArgs = {
20
20
  variables?: Record<string, any>;
@@ -32,7 +32,7 @@ export declare class TinaClient<GenQueries> {
32
32
  /**
33
33
  *
34
34
  */
35
- queries?: GenQueries;
35
+ queries: GenQueries;
36
36
  constructor({ token, url, queries }: TinaClientArgs<GenQueries>);
37
37
  request<DataType extends Record<string, any> = any>(args: TinaClientRequestArgs): Promise<{
38
38
  data: DataType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinacms",
3
- "version": "0.68.12",
3
+ "version": "0.68.15",
4
4
  "main": "dist/index.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "exports": {
@@ -43,9 +43,9 @@
43
43
  "@headlessui/react": "^1.5.0",
44
44
  "@heroicons/react": "^1.0.4",
45
45
  "@react-hook/window-size": "^3.0.7",
46
- "@tinacms/schema-tools": "0.0.7",
46
+ "@tinacms/schema-tools": "0.0.9",
47
47
  "@tinacms/sharedctx": "0.1.2",
48
- "@tinacms/toolkit": "0.56.35",
48
+ "@tinacms/toolkit": "0.56.37",
49
49
  "crypto-js": "^4.0.0",
50
50
  "fetch-ponyfill": "^7.1.0",
51
51
  "final-form": "4.20.1",