tinacms 0.68.14 → 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/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";
@@ -2483,10 +2483,10 @@ class TinaAdminApi {
2483
2483
  }
2484
2484
  }`, { variables: { collection, relativePath } });
2485
2485
  }
2486
- async fetchCollection(collectionName, includeDocuments, after) {
2486
+ async fetchCollection(collectionName, includeDocuments, after, sortKey, order) {
2487
2487
  if (includeDocuments === true) {
2488
- const sort = this.schema.getIsTitleFieldName(collectionName);
2489
- 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
2490
2490
  query($collection: String!, $includeDocuments: Boolean!, $sort: String, $limit: Float, $after: String){
2491
2491
  collection(collection: $collection){
2492
2492
  name
@@ -2527,6 +2527,47 @@ class TinaAdminApi {
2527
2527
  limit: 10,
2528
2528
  after
2529
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
+ }
2558
+ }
2559
+ }
2560
+ }
2561
+ }
2562
+ }
2563
+ }`, {
2564
+ variables: {
2565
+ collection: collectionName,
2566
+ includeDocuments,
2567
+ sort,
2568
+ limit: 10,
2569
+ after
2570
+ }
2530
2571
  });
2531
2572
  return response.collection;
2532
2573
  } else {
@@ -3280,6 +3321,9 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3280
3321
  .tina-tailwind .gap-4 {
3281
3322
  gap: 16px;
3282
3323
  }
3324
+ .tina-tailwind .gap-2 {
3325
+ gap: 8px;
3326
+ }
3283
3327
  .tina-tailwind .gap-3 {
3284
3328
  gap: 12px;
3285
3329
  }
@@ -3298,6 +3342,9 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3298
3342
  .tina-tailwind .overflow-y-auto {
3299
3343
  overflow-y: auto;
3300
3344
  }
3345
+ .tina-tailwind .whitespace-normal {
3346
+ white-space: normal;
3347
+ }
3301
3348
  .tina-tailwind .whitespace-nowrap {
3302
3349
  white-space: nowrap;
3303
3350
  }
@@ -3419,8 +3466,8 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3419
3466
  .tina-tailwind .pb-4 {
3420
3467
  padding-bottom: 16px;
3421
3468
  }
3422
- .tina-tailwind .pt-18 {
3423
- padding-top: 72px;
3469
+ .tina-tailwind .pt-16 {
3470
+ padding-top: 64px;
3424
3471
  }
3425
3472
  .tina-tailwind .pt-3 {
3426
3473
  padding-top: 12px;
@@ -3464,6 +3511,9 @@ var styles = /* @__PURE__ */ (() => `.tina-tailwind {
3464
3511
  .tina-tailwind .font-medium {
3465
3512
  font-weight: 500;
3466
3513
  }
3514
+ .tina-tailwind .font-semibold {
3515
+ font-weight: 600;
3516
+ }
3467
3517
  .tina-tailwind .uppercase {
3468
3518
  text-transform: uppercase;
3469
3519
  }
@@ -4558,7 +4608,7 @@ const PageHeader = ({
4558
4608
  isLocalMode,
4559
4609
  children
4560
4610
  }) => /* @__PURE__ */ React.createElement(React.Fragment, null, isLocalMode && /* @__PURE__ */ React.createElement(LocalWarning, null), /* @__PURE__ */ React.createElement("div", {
4561
- 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"
4562
4612
  }, /* @__PURE__ */ React.createElement("div", {
4563
4613
  className: "w-full mx-auto max-w-screen-xl"
4564
4614
  }, /* @__PURE__ */ React.createElement("div", {
@@ -4685,8 +4735,10 @@ const LoadingPage = () => /* @__PURE__ */ React.createElement(React.Fragment, nu
4685
4735
  fontWeight: "normal"
4686
4736
  }
4687
4737
  }, "Please wait, Tina is loading data..."))));
4688
- const useGetCollection = (cms, collectionName, includeDocuments = true, after = "") => {
4738
+ const useGetCollection = (cms, collectionName, includeDocuments = true, after = "", sortKey) => {
4689
4739
  const api = new TinaAdminApi(cms);
4740
+ const schema = cms.api.tina.schema;
4741
+ const collectionExtra = schema.getCollection(collectionName);
4690
4742
  const [collection, setCollection] = useState(void 0);
4691
4743
  const [loading, setLoading] = useState(true);
4692
4744
  const [error, setError] = useState(void 0);
@@ -4694,8 +4746,10 @@ const useGetCollection = (cms, collectionName, includeDocuments = true, after =
4694
4746
  useEffect(() => {
4695
4747
  const fetchCollection = async () => {
4696
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;
4697
4751
  try {
4698
- const collection2 = await api.fetchCollection(collectionName, includeDocuments, after);
4752
+ const collection2 = await api.fetchCollection(collectionName, includeDocuments, after, validSortKey, order);
4699
4753
  setCollection(collection2);
4700
4754
  } catch (error2) {
4701
4755
  cms.alerts.error(`[${error2.name}] GetCollection failed: ${error2.message}`, 30 * 1e3);
@@ -4708,26 +4762,29 @@ const useGetCollection = (cms, collectionName, includeDocuments = true, after =
4708
4762
  };
4709
4763
  setLoading(true);
4710
4764
  fetchCollection();
4711
- }, [cms, collectionName, resetState, after]);
4765
+ }, [cms, collectionName, resetState, after, sortKey]);
4712
4766
  const reFetchCollection = () => setResetSate((x) => x + 1);
4713
- return { collection, loading, error, reFetchCollection };
4767
+ return { collection, loading, error, reFetchCollection, collectionExtra };
4714
4768
  };
4715
4769
  const GetCollection = ({
4716
4770
  cms,
4717
4771
  collectionName,
4718
4772
  includeDocuments = true,
4719
4773
  startCursor,
4774
+ sortKey,
4720
4775
  children
4721
4776
  }) => {
4722
- const { collection, loading, error, reFetchCollection } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "");
4777
+ const { collection, loading, error, reFetchCollection, collectionExtra } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "", sortKey) || {};
4723
4778
  if (error) {
4724
4779
  return null;
4725
4780
  }
4726
4781
  if (loading) {
4727
4782
  return /* @__PURE__ */ React.createElement(LoadingPage, null);
4728
4783
  }
4729
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children(collection, loading, reFetchCollection));
4784
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children(collection, loading, reFetchCollection, collectionExtra));
4730
4785
  };
4786
+ const LOCAL_STORAGE_KEY = "tinacms.admin.collection.list.page";
4787
+ const isSSR = typeof window === "undefined";
4731
4788
  const TemplateMenu = ({ templates }) => {
4732
4789
  return /* @__PURE__ */ React.createElement(Menu, {
4733
4790
  as: "div",
@@ -4776,8 +4833,17 @@ const CollectionListPage = () => {
4776
4833
  });
4777
4834
  const [endCursor, setEndCursor] = useState("");
4778
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");
4779
4841
  const loc = useLocation();
4780
4842
  useEffect(() => {
4843
+ setSortKey(window.localStorage.getItem(`${LOCAL_STORAGE_KEY}.${collectionName}`) || JSON.stringify({
4844
+ order: "asc",
4845
+ name: ""
4846
+ }));
4781
4847
  setEndCursor("");
4782
4848
  setPrevCursors([]);
4783
4849
  }, [loc]);
@@ -4786,13 +4852,15 @@ const CollectionListPage = () => {
4786
4852
  cms,
4787
4853
  collectionName,
4788
4854
  includeDocuments: true,
4789
- startCursor: endCursor
4790
- }, (collection, _loading, reFetchCollection) => {
4855
+ startCursor: endCursor,
4856
+ sortKey
4857
+ }, (collection, _loading, reFetchCollection, collectionExtra) => {
4791
4858
  var _a, _b;
4792
4859
  const totalCount = collection.documents.totalCount;
4793
4860
  const documents = collection.documents.edges;
4794
4861
  const admin = cms.api.admin;
4795
4862
  const pageInfo = collection.documents.pageInfo;
4863
+ const fields = collectionExtra.fields.filter((x) => ["string", "number", "datetime"].includes(x.type));
4796
4864
  return /* @__PURE__ */ React.createElement(PageWrapper, null, /* @__PURE__ */ React.createElement(React.Fragment, null, open && /* @__PURE__ */ React.createElement(DeleteModal, {
4797
4865
  filename: vars.relativePath,
4798
4866
  deleteFunc: async () => {
@@ -4809,9 +4877,56 @@ const CollectionListPage = () => {
4809
4877
  close: () => setOpen(false)
4810
4878
  }), /* @__PURE__ */ React.createElement(PageHeader, {
4811
4879
  isLocalMode: (_b = (_a = cms == null ? void 0 : cms.api) == null ? void 0 : _a.tina) == null ? void 0 : _b.isLocalMode
4812
- }, /* @__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", {
4813
4883
  className: "font-sans text-2xl text-gray-700"
4814
- }, 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, {
4815
4930
  to: `new`,
4816
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"
4817
4932
  }, "Create New", " ", /* @__PURE__ */ React.createElement(BiPlus, {
@@ -4899,7 +5014,7 @@ const CollectionListPage = () => {
4899
5014
  className: "pt-3"
4900
5015
  }, /* @__PURE__ */ React.createElement(CursorPaginator, {
4901
5016
  variant: "white",
4902
- hasNext: pageInfo == null ? void 0 : pageInfo.hasNextPage,
5017
+ hasNext: sortOrder === "asc" ? pageInfo == null ? void 0 : pageInfo.hasNextPage : pageInfo.hasPreviousPage,
4903
5018
  navigateNext: () => {
4904
5019
  const newState = [...prevCursors, endCursor];
4905
5020
  setPrevCursors(newState);
@@ -5237,9 +5352,9 @@ const Redirect = () => {
5237
5352
  return null;
5238
5353
  };
5239
5354
  const TinaAdmin = () => {
5240
- const isSSR = typeof window === "undefined";
5355
+ const isSSR2 = typeof window === "undefined";
5241
5356
  const { edit } = useEditState();
5242
- if (isSSR) {
5357
+ if (isSSR2) {
5243
5358
  return null;
5244
5359
  }
5245
5360
  if (!edit) {
package/dist/index.js CHANGED
@@ -2501,10 +2501,10 @@ mutation addPendingDocumentMutation(
2501
2501
  }
2502
2502
  }`, { variables: { collection, relativePath } });
2503
2503
  }
2504
- async fetchCollection(collectionName, includeDocuments, after) {
2504
+ async fetchCollection(collectionName, includeDocuments, after, sortKey, order) {
2505
2505
  if (includeDocuments === true) {
2506
- const sort = this.schema.getIsTitleFieldName(collectionName);
2507
- 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
2508
2508
  query($collection: String!, $includeDocuments: Boolean!, $sort: String, $limit: Float, $after: String){
2509
2509
  collection(collection: $collection){
2510
2510
  name
@@ -2537,6 +2537,47 @@ mutation addPendingDocumentMutation(
2537
2537
  }
2538
2538
  }
2539
2539
  }
2540
+ }`, {
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
2562
+ }
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
+ }
2576
+ }
2577
+ }
2578
+ }
2579
+ }
2580
+ }
2540
2581
  }`, {
2541
2582
  variables: {
2542
2583
  collection: collectionName,
@@ -3298,6 +3339,9 @@ mutation addPendingDocumentMutation(
3298
3339
  .tina-tailwind .gap-4 {
3299
3340
  gap: 16px;
3300
3341
  }
3342
+ .tina-tailwind .gap-2 {
3343
+ gap: 8px;
3344
+ }
3301
3345
  .tina-tailwind .gap-3 {
3302
3346
  gap: 12px;
3303
3347
  }
@@ -3316,6 +3360,9 @@ mutation addPendingDocumentMutation(
3316
3360
  .tina-tailwind .overflow-y-auto {
3317
3361
  overflow-y: auto;
3318
3362
  }
3363
+ .tina-tailwind .whitespace-normal {
3364
+ white-space: normal;
3365
+ }
3319
3366
  .tina-tailwind .whitespace-nowrap {
3320
3367
  white-space: nowrap;
3321
3368
  }
@@ -3437,8 +3484,8 @@ mutation addPendingDocumentMutation(
3437
3484
  .tina-tailwind .pb-4 {
3438
3485
  padding-bottom: 16px;
3439
3486
  }
3440
- .tina-tailwind .pt-18 {
3441
- padding-top: 72px;
3487
+ .tina-tailwind .pt-16 {
3488
+ padding-top: 64px;
3442
3489
  }
3443
3490
  .tina-tailwind .pt-3 {
3444
3491
  padding-top: 12px;
@@ -3482,6 +3529,9 @@ mutation addPendingDocumentMutation(
3482
3529
  .tina-tailwind .font-medium {
3483
3530
  font-weight: 500;
3484
3531
  }
3532
+ .tina-tailwind .font-semibold {
3533
+ font-weight: 600;
3534
+ }
3485
3535
  .tina-tailwind .uppercase {
3486
3536
  text-transform: uppercase;
3487
3537
  }
@@ -4576,7 +4626,7 @@ This will work when developing locally but NOT when deployed to production.
4576
4626
  isLocalMode,
4577
4627
  children
4578
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", {
4579
- 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"
4580
4630
  }, /* @__PURE__ */ React__default["default"].createElement("div", {
4581
4631
  className: "w-full mx-auto max-w-screen-xl"
4582
4632
  }, /* @__PURE__ */ React__default["default"].createElement("div", {
@@ -4703,8 +4753,10 @@ This will work when developing locally but NOT when deployed to production.
4703
4753
  fontWeight: "normal"
4704
4754
  }
4705
4755
  }, "Please wait, Tina is loading data..."))));
4706
- const useGetCollection = (cms, collectionName, includeDocuments = true, after = "") => {
4756
+ const useGetCollection = (cms, collectionName, includeDocuments = true, after = "", sortKey) => {
4707
4757
  const api = new TinaAdminApi(cms);
4758
+ const schema = cms.api.tina.schema;
4759
+ const collectionExtra = schema.getCollection(collectionName);
4708
4760
  const [collection, setCollection] = React.useState(void 0);
4709
4761
  const [loading, setLoading] = React.useState(true);
4710
4762
  const [error, setError] = React.useState(void 0);
@@ -4712,8 +4764,10 @@ This will work when developing locally but NOT when deployed to production.
4712
4764
  React.useEffect(() => {
4713
4765
  const fetchCollection = async () => {
4714
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;
4715
4769
  try {
4716
- const collection2 = await api.fetchCollection(collectionName, includeDocuments, after);
4770
+ const collection2 = await api.fetchCollection(collectionName, includeDocuments, after, validSortKey, order);
4717
4771
  setCollection(collection2);
4718
4772
  } catch (error2) {
4719
4773
  cms.alerts.error(`[${error2.name}] GetCollection failed: ${error2.message}`, 30 * 1e3);
@@ -4726,26 +4780,29 @@ This will work when developing locally but NOT when deployed to production.
4726
4780
  };
4727
4781
  setLoading(true);
4728
4782
  fetchCollection();
4729
- }, [cms, collectionName, resetState, after]);
4783
+ }, [cms, collectionName, resetState, after, sortKey]);
4730
4784
  const reFetchCollection = () => setResetSate((x) => x + 1);
4731
- return { collection, loading, error, reFetchCollection };
4785
+ return { collection, loading, error, reFetchCollection, collectionExtra };
4732
4786
  };
4733
4787
  const GetCollection = ({
4734
4788
  cms,
4735
4789
  collectionName,
4736
4790
  includeDocuments = true,
4737
4791
  startCursor,
4792
+ sortKey,
4738
4793
  children
4739
4794
  }) => {
4740
- const { collection, loading, error, reFetchCollection } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "");
4795
+ const { collection, loading, error, reFetchCollection, collectionExtra } = useGetCollection(cms, collectionName, includeDocuments, startCursor || "", sortKey) || {};
4741
4796
  if (error) {
4742
4797
  return null;
4743
4798
  }
4744
4799
  if (loading) {
4745
4800
  return /* @__PURE__ */ React__default["default"].createElement(LoadingPage, null);
4746
4801
  }
4747
- 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));
4748
4803
  };
4804
+ const LOCAL_STORAGE_KEY = "tinacms.admin.collection.list.page";
4805
+ const isSSR = typeof window === "undefined";
4749
4806
  const TemplateMenu = ({ templates }) => {
4750
4807
  return /* @__PURE__ */ React__default["default"].createElement(react.Menu, {
4751
4808
  as: "div",
@@ -4794,8 +4851,17 @@ This will work when developing locally but NOT when deployed to production.
4794
4851
  });
4795
4852
  const [endCursor, setEndCursor] = React.useState("");
4796
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");
4797
4859
  const loc = reactRouterDom.useLocation();
4798
4860
  React.useEffect(() => {
4861
+ setSortKey(window.localStorage.getItem(`${LOCAL_STORAGE_KEY}.${collectionName}`) || JSON.stringify({
4862
+ order: "asc",
4863
+ name: ""
4864
+ }));
4799
4865
  setEndCursor("");
4800
4866
  setPrevCursors([]);
4801
4867
  }, [loc]);
@@ -4804,13 +4870,15 @@ This will work when developing locally but NOT when deployed to production.
4804
4870
  cms,
4805
4871
  collectionName,
4806
4872
  includeDocuments: true,
4807
- startCursor: endCursor
4808
- }, (collection, _loading, reFetchCollection) => {
4873
+ startCursor: endCursor,
4874
+ sortKey
4875
+ }, (collection, _loading, reFetchCollection, collectionExtra) => {
4809
4876
  var _a, _b;
4810
4877
  const totalCount = collection.documents.totalCount;
4811
4878
  const documents = collection.documents.edges;
4812
4879
  const admin = cms.api.admin;
4813
4880
  const pageInfo = collection.documents.pageInfo;
4881
+ const fields = collectionExtra.fields.filter((x) => ["string", "number", "datetime"].includes(x.type));
4814
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, {
4815
4883
  filename: vars.relativePath,
4816
4884
  deleteFunc: async () => {
@@ -4827,9 +4895,56 @@ This will work when developing locally but NOT when deployed to production.
4827
4895
  close: () => setOpen(false)
4828
4896
  }), /* @__PURE__ */ React__default["default"].createElement(PageHeader, {
4829
4897
  isLocalMode: (_b = (_a = cms == null ? void 0 : cms.api) == null ? void 0 : _a.tina) == null ? void 0 : _b.isLocalMode
4830
- }, /* @__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", {
4831
4901
  className: "font-sans text-2xl text-gray-700"
4832
- }, 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, {
4833
4948
  to: `new`,
4834
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"
4835
4950
  }, "Create New", " ", /* @__PURE__ */ React__default["default"].createElement(BiPlus, {
@@ -4917,7 +5032,7 @@ This will work when developing locally but NOT when deployed to production.
4917
5032
  className: "pt-3"
4918
5033
  }, /* @__PURE__ */ React__default["default"].createElement(toolkit.CursorPaginator, {
4919
5034
  variant: "white",
4920
- hasNext: pageInfo == null ? void 0 : pageInfo.hasNextPage,
5035
+ hasNext: sortOrder === "asc" ? pageInfo == null ? void 0 : pageInfo.hasNextPage : pageInfo.hasPreviousPage,
4921
5036
  navigateNext: () => {
4922
5037
  const newState = [...prevCursors, endCursor];
4923
5038
  setPrevCursors(newState);
@@ -5255,9 +5370,9 @@ This will work when developing locally but NOT when deployed to production.
5255
5370
  return null;
5256
5371
  };
5257
5372
  const TinaAdmin = () => {
5258
- const isSSR = typeof window === "undefined";
5373
+ const isSSR2 = typeof window === "undefined";
5259
5374
  const { edit } = sharedctx.useEditState();
5260
- if (isSSR) {
5375
+ if (isSSR2) {
5261
5376
  return null;
5262
5377
  }
5263
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinacms",
3
- "version": "0.68.14",
3
+ "version": "0.68.15",
4
4
  "main": "dist/index.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "exports": {
@@ -45,7 +45,7 @@
45
45
  "@react-hook/window-size": "^3.0.7",
46
46
  "@tinacms/schema-tools": "0.0.9",
47
47
  "@tinacms/sharedctx": "0.1.2",
48
- "@tinacms/toolkit": "0.56.36",
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",