@sanity/orderable-document-list 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { defineField, useSchema, PreviewCard, Preview, useClient } from "sanity";
1
+ import { defineField, useSchema, useDocumentVersionInfo, PreviewCard, Preview, DocumentStatusIndicator, DocumentStatus, usePerspective, useClient, isVersionId, getVersionFromId, isPublishedId, isDraftId, getPublishedId } from "sanity";
2
2
  import { LexoRank } from "lexorank";
3
3
  import { DragHandleIcon, ChevronUpIcon, ChevronDownIcon, SortIcon, GenerateIcon } from "@sanity/icons";
4
4
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
5
  import { createContext, useContext, useMemo, useState, useEffect, useCallback, Component } from "react";
6
- import { Flex, Box, Text, Button, Card, AvatarCounter, useToast, Spinner, Container, Stack } from "@sanity/ui";
6
+ import { Flex, Box, Text, Button, Tooltip, Card, AvatarCounter, useToast, Spinner, Container, Stack } from "@sanity/ui";
7
7
  import { useListeningQuery, Feedback } from "sanity-plugin-utils";
8
8
  import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
9
9
  import { usePaneRouter } from "sanity/structure";
10
- const ORDER_FIELD_NAME = "orderRank", API_VERSION = "2021-09-01";
10
+ const ORDER_FIELD_NAME = "orderRank", API_VERSION = "v2025-06-27";
11
11
  function initialRank(compareRankValue = "", newItemPosition = "after") {
12
12
  const compareRank = compareRankValue ? LexoRank.parse(compareRankValue) : LexoRank.min();
13
13
  return (newItemPosition === "before" ? compareRank.genPrev().genPrev() : compareRank.genNext().genNext()).toString();
@@ -51,11 +51,18 @@ function Document({
51
51
  isLast,
52
52
  dragBadge
53
53
  }) {
54
- const { showIncrements } = useContext(OrderableContext), schema = useSchema(), router = usePaneRouter(), { ChildLink, groupIndex, routerPanesState } = router, currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || !1, pressed = currentDoc === doc._id || currentDoc === doc._id.replace("drafts.", ""), selected = pressed && routerPanesState.length === groupIndex + 2, Link = useMemo(
54
+ const { showIncrements } = useContext(OrderableContext), schema = useSchema(), router = usePaneRouter(), versionsInfo = useDocumentVersionInfo(doc._id), { ChildLink, groupIndex, routerPanesState } = router, currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || !1, pressed = currentDoc === doc._id || currentDoc === doc._id.replace("drafts.", ""), selected = pressed && routerPanesState.length === groupIndex + 2, Link = useMemo(
55
55
  () => function(linkProps) {
56
56
  return /* @__PURE__ */ jsx(ChildLink, { ...linkProps, childId: doc._id });
57
57
  },
58
58
  [ChildLink, doc._id]
59
+ ), tooltip = /* @__PURE__ */ jsx(
60
+ DocumentStatus,
61
+ {
62
+ draft: versionsInfo.draft,
63
+ published: versionsInfo.published,
64
+ versions: versionsInfo.versions
65
+ }
59
66
  );
60
67
  return /* @__PURE__ */ jsx(
61
68
  PreviewCard,
@@ -96,14 +103,24 @@ function Document({
96
103
  }
97
104
  )
98
105
  ] }),
99
- /* @__PURE__ */ jsx(Box, { style: { width: "100%" }, children: /* @__PURE__ */ jsx(Flex, { flex: 1, align: "center", children: /* @__PURE__ */ jsx(
100
- Preview,
101
- {
102
- layout: "default",
103
- value: doc,
104
- schemaType: schema.get(doc._type)
105
- }
106
- ) }) }),
106
+ /* @__PURE__ */ jsx(Box, { style: { width: "100%" }, children: /* @__PURE__ */ jsxs(Flex, { flex: 1, align: "center", justify: "space-between", paddingRight: 3, children: [
107
+ /* @__PURE__ */ jsx(
108
+ Preview,
109
+ {
110
+ layout: "default",
111
+ value: doc,
112
+ schemaType: schema.get(doc._type)
113
+ }
114
+ ),
115
+ /* @__PURE__ */ jsx(Tooltip, { content: tooltip, portal: !0, placement: "right", boundaryElement: null, children: /* @__PURE__ */ jsx(Flex, { align: "center", children: /* @__PURE__ */ jsx(
116
+ DocumentStatusIndicator,
117
+ {
118
+ draft: versionsInfo.draft,
119
+ published: versionsInfo.published,
120
+ versions: versionsInfo.versions
121
+ }
122
+ ) }) })
123
+ ] }) }),
107
124
  dragBadge && /* @__PURE__ */ jsx(Card, { tone: "default", marginRight: 4, radius: 5, children: /* @__PURE__ */ jsx(AvatarCounter, { count: dragBadge }) })
108
125
  ] })
109
126
  }
@@ -166,7 +183,8 @@ const reorderDocuments = ({
166
183
  return { newOrder: all.sort(lexicographicalSort), patches, message };
167
184
  };
168
185
  function useSanityClient() {
169
- return useClient({ apiVersion: API_VERSION });
186
+ const { perspectiveStack } = usePerspective();
187
+ return useClient({ apiVersion: API_VERSION }).withConfig({ perspective: perspectiveStack });
170
188
  }
171
189
  const getItemStyle = (draggableStyle, itemIsUpdating) => ({
172
190
  userSelect: "none",
@@ -316,15 +334,48 @@ const DEFAULT_PARAMS = {};
316
334
  function getDocumentQuery({
317
335
  type,
318
336
  filter,
319
- params = DEFAULT_PARAMS
337
+ params = DEFAULT_PARAMS,
338
+ currentVersion
320
339
  }) {
321
- const querySelect = `*[_type == $type ${filter ? `&& ${filter}` : ""}]`, queryOrder = "|order(@[$order] asc)", queryFields = `{_id, _type, ${ORDER_FIELD_NAME}}`, query = `${querySelect}${queryOrder}${queryFields}`, queryParams = {
340
+ let perspectiveFilter = null;
341
+ currentVersion === "published" ? perspectiveFilter = '!(_id in path("drafts.**")) && !(_id in path("versions.**"))' : currentVersion === "drafts" ? perspectiveFilter = `
342
+ (_id in path("drafts.**") || (!(_id in path("drafts.**")) && !(_id in path("versions.**"))))
343
+ ` : perspectiveFilter = '(sanity::partOfRelease($currentVersion) || (!(_id in path("drafts.**")) && !(_id in path("versions.**"))) || (_id in path("drafts.**")))';
344
+ const querySelect = `*[_type == $type ${perspectiveFilter ? `&& ${perspectiveFilter}` : ""}${filter ? `&& ${filter}` : ""}]`, queryOrder = "|order(@[$order] asc)", queryFields = `{_id, _type, ${ORDER_FIELD_NAME}}`, query = `${querySelect}${queryOrder}${queryFields}`, queryParams = {
322
345
  ...params,
323
346
  type,
324
- order: ORDER_FIELD_NAME
347
+ order: ORDER_FIELD_NAME,
348
+ ...currentVersion && { currentVersion }
325
349
  };
326
350
  return { query, queryParams };
327
351
  }
352
+ const isVersionForCurrentPerspective = (document, perspectiveName, publishedId) => document._id && isVersionId(document._id) && getVersionFromId(document._id) === perspectiveName && getPublishedId(document._id) === publishedId, getFilteredDedupedDocs = (documents, perspectiveName) => {
353
+ const flatDocuments = documents.flat();
354
+ return flatDocuments.reduce((acc, cur) => {
355
+ if (!cur._id)
356
+ return acc;
357
+ if (isVersionId(cur._id)) {
358
+ const isCorrectVersion = getVersionFromId(cur._id) === perspectiveName;
359
+ return perspectiveName && perspectiveName !== "drafts" && perspectiveName !== "published" && isCorrectVersion ? [...acc, cur] : acc;
360
+ }
361
+ if (perspectiveName === "published")
362
+ return isPublishedId(cur._id) ? [...acc, cur] : acc;
363
+ if (!isDraftId(cur._id)) {
364
+ const publishedId = getPublishedId(cur._id), countNrPublished = JSON.stringify(flatDocuments).match(`/${publishedId}/g`), hasMatchingVersion = perspectiveName && perspectiveName !== "drafts" && perspectiveName !== "published" ? flatDocuments.some(
365
+ (doc) => isVersionForCurrentPerspective(doc, perspectiveName, publishedId)
366
+ ) : !1, hasDraft = flatDocuments.some((doc) => doc._id === `drafts.${cur._id}`);
367
+ return hasMatchingVersion || hasDraft || countNrPublished ? acc : [...acc, cur];
368
+ }
369
+ if (perspectiveName && perspectiveName !== "drafts" && perspectiveName !== "published") {
370
+ const baseId = getPublishedId(cur._id);
371
+ if (flatDocuments.some(
372
+ (doc) => isVersionForCurrentPerspective(doc, perspectiveName, baseId)
373
+ ))
374
+ return acc;
375
+ }
376
+ return cur.hasPublished = flatDocuments.some((doc) => doc._id === cur._id.replace("drafts.", "")), [...acc, cur];
377
+ }, []);
378
+ };
328
379
  function DocumentListQuery(props) {
329
380
  const [listIsUpdating, setListIsUpdating] = useState(!1), [data, setData] = useState([]), { query, queryParams } = getDocumentQuery(props), {
330
381
  data: _queryData,
@@ -336,7 +387,7 @@ function DocumentListQuery(props) {
336
387
  }), queryData = _queryData;
337
388
  useEffect(() => {
338
389
  if (queryData) {
339
- const filteredDocuments = queryData.reduce((acc, cur) => cur._id.startsWith("drafts.") ? (cur.hasPublished = queryData.some((doc) => doc._id === cur._id.replace("drafts.", "")), [...acc, cur]) : queryData.some((doc) => doc._id === `drafts.${cur._id}`) ? acc : [...acc, cur], []);
390
+ const filteredDocuments = getFilteredDedupedDocs(queryData, props.currentVersion);
340
391
  setData(filteredDocuments);
341
392
  } else
342
393
  setData([]);
@@ -376,7 +427,8 @@ function DocumentListWrapper({
376
427
  showIncrements,
377
428
  resetOrderTransaction,
378
429
  filter,
379
- params
430
+ params,
431
+ currentVersion
380
432
  }) {
381
433
  const toast = useToast(), schema = useSchema();
382
434
  useEffect(() => {
@@ -414,10 +466,18 @@ function DocumentListWrapper({
414
466
  " not found"
415
467
  ] });
416
468
  }, [type, schema]);
417
- return schemaIsInvalid ? /* @__PURE__ */ jsx(Box, { padding: 2, children: /* @__PURE__ */ jsx(Feedback, { description: schemaIsInvalid, tone: "caution" }) }) : /* @__PURE__ */ jsx(OrderableContext.Provider, { value: { showIncrements }, children: /* @__PURE__ */ jsx(DocumentListQuery, { type, filter, params }) });
469
+ return schemaIsInvalid ? /* @__PURE__ */ jsx(Box, { padding: 2, children: /* @__PURE__ */ jsx(Feedback, { description: schemaIsInvalid, tone: "caution" }) }) : /* @__PURE__ */ jsx(OrderableContext.Provider, { value: { showIncrements }, children: /* @__PURE__ */ jsx(
470
+ DocumentListQuery,
471
+ {
472
+ type,
473
+ filter,
474
+ params,
475
+ currentVersion
476
+ }
477
+ ) });
418
478
  }
419
479
  async function resetOrder(params) {
420
- const { client, ...queryProps } = params, { query, queryParams } = getDocumentQuery(queryProps), documents = await client.fetch(query, queryParams, {
480
+ const { client, currentVersion, ...queryProps } = params, { query, queryParams } = getDocumentQuery({ ...queryProps, currentVersion }), documents = await client.fetch(query, queryParams, {
421
481
  tag: "orderable-document-list.reset-order"
422
482
  });
423
483
  if (documents.length === 0)
@@ -470,7 +530,8 @@ class OrderableDocumentList extends Component {
470
530
  params: this?.props?.options?.params,
471
531
  type,
472
532
  showIncrements: this.state.showIncrements,
473
- resetOrderTransaction: this.state.resetOrderTransaction
533
+ resetOrderTransaction: this.state.resetOrderTransaction,
534
+ currentVersion: this?.props?.options?.currentVersion
474
535
  }
475
536
  ) : null;
476
537
  }
@@ -482,7 +543,7 @@ function orderableDocumentListDeskItem(config) {
482
543
  context and S are available when configuring structure.
483
544
  Example: orderableDocumentListDeskItem({type: 'category'})
484
545
  `);
485
- const { type, filter, menuItems = [], createIntent, params, title, icon, id, context, S } = config, { schema, getClient } = context, client = getClient({ apiVersion: API_VERSION }), listTitle = title ?? `Orderable ${type}`, listId = id ?? `orderable-${type}`, listIcon = icon ?? SortIcon, typeTitle = schema.get(type)?.title ?? type;
546
+ const { type, filter, menuItems = [], createIntent, params, title, icon, id, context, S } = config, { schema, getClient } = context, perspectiveStack = context.perspectiveStack || [], client = getClient({ apiVersion: API_VERSION }), currentVersion = perspectiveStack[0], listTitle = title ?? `Orderable ${type}`, listId = id ?? `orderable-${type}`, listIcon = icon ?? SortIcon, typeTitle = schema.get(type)?.title ?? type;
486
547
  return createIntent !== !1 && menuItems.push(
487
548
  S.menuItem().title(`Create new ${typeTitle}`).intent({ type: "create", params: { type } }).serialize()
488
549
  ), S.listItem().title(listTitle).id(listId).icon(listIcon).schemaType(type).child(
@@ -495,7 +556,7 @@ function orderableDocumentListDeskItem(config) {
495
556
  key: listId,
496
557
  type: "component",
497
558
  component: OrderableDocumentList,
498
- options: { type, filter, params, client },
559
+ options: { type, filter, params, client, currentVersion },
499
560
  menuItems: [
500
561
  ...menuItems,
501
562
  S.menuItem().title("Reset Order").icon(GenerateIcon).action("resetOrder").serialize(),
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/helpers/constants.ts","../src/helpers/initialRank.ts","../src/fields/orderRankField.ts","../src/fields/orderRankOrdering.ts","../src/OrderableContext.ts","../src/Document.tsx","../src/helpers/reorderDocuments.ts","../src/helpers/client.ts","../src/DraggableList.tsx","../src/helpers/query.ts","../src/DocumentListQuery.tsx","../src/DocumentListWrapper.tsx","../src/helpers/resetOrder.ts","../src/OrderableDocumentList.tsx","../src/desk-structure/orderableDocumentListDeskItem.ts"],"sourcesContent":["export const ORDER_FIELD_NAME = `orderRank` as const\n\nexport const API_VERSION = `2021-09-01` as const\n","import {LexoRank} from 'lexorank'\nimport {NewItemPosition} from '../types'\n\n// Use in initial value field by passing in the rank value of the last document\n// If not value passed, generate a sensibly low rank\nexport function initialRank(\n compareRankValue = ``,\n newItemPosition: NewItemPosition = 'after',\n): string {\n const compareRank = compareRankValue ? LexoRank.parse(compareRankValue) : LexoRank.min()\n const rank =\n newItemPosition === 'before' ? compareRank.genPrev().genPrev() : compareRank.genNext().genNext()\n\n return rank.toString()\n}\n","import {type ConfigContext, defineField, FieldDefinition, type StringDefinition} from 'sanity'\nimport {API_VERSION, ORDER_FIELD_NAME} from '../helpers/constants'\nimport {initialRank} from '../helpers/initialRank'\nimport type {NewItemPosition} from '../types'\n\nexport type SchemaContext = Omit<ConfigContext, 'schema' | 'currentUser' | 'client'>\n\nexport interface RankFieldConfig\n extends Partial<Omit<StringDefinition, 'name' | 'type' | 'initialValue'>> {\n type: string\n newItemPosition?: NewItemPosition\n}\n\nexport const orderRankField = (config: RankFieldConfig): FieldDefinition<'string'> => {\n if (!config?.type) {\n throw new Error(\n `\n type must be provided.\n Example: orderRankField({type: 'category'})\n `,\n )\n }\n\n const {type, newItemPosition = 'after', ...rest} = config\n return defineField({\n title: 'Order Rank',\n readOnly: true,\n hidden: true,\n ...rest,\n name: ORDER_FIELD_NAME,\n type: 'string',\n initialValue: async (p, {getClient}) => {\n const direction = newItemPosition === 'before' ? 'asc' : 'desc'\n\n const lastDocOrderRank = await getClient({apiVersion: API_VERSION}).fetch(\n `*[_type == $type]|order(@[$order] ${direction})[0][$order]`,\n {type, order: ORDER_FIELD_NAME},\n {tag: 'orderable-document-list.last-doc-order-rank'},\n )\n return initialRank(lastDocOrderRank, newItemPosition)\n },\n })\n}\n","import type {SortOrdering} from 'sanity'\nimport {ORDER_FIELD_NAME} from '../helpers/constants'\n\nexport const orderRankOrdering: SortOrdering = {\n title: 'Ordered',\n name: 'ordered',\n by: [{field: ORDER_FIELD_NAME, direction: 'asc'}],\n}\n","import {createContext} from 'react'\n\nexport interface OrderableContextValue {\n showIncrements?: boolean\n}\n\nexport const OrderableContext = createContext<OrderableContextValue>({})\n","import {useContext, useMemo, type ReactNode} from 'react'\nimport {ChevronDownIcon, ChevronUpIcon, DragHandleIcon} from '@sanity/icons'\nimport {AvatarCounter, Card, Box, Button, Flex, Text} from '@sanity/ui'\nimport {useSchema, SchemaType, PreviewCard, Preview} from 'sanity'\nimport {usePaneRouter} from 'sanity/structure'\n\nimport {OrderableContext} from './OrderableContext'\nimport type {SanityDocumentWithOrder} from './types'\n\nexport interface DocumentProps {\n doc: SanityDocumentWithOrder\n entities: SanityDocumentWithOrder[]\n increment: (\n index: number,\n nextIndex: number,\n docId: string,\n entities: SanityDocumentWithOrder[],\n ) => void\n index: number\n isFirst: boolean\n isLast: boolean\n dragBadge: number | false\n}\n\nexport function Document({\n doc,\n increment,\n entities,\n index,\n isFirst,\n isLast,\n dragBadge,\n}: DocumentProps) {\n const {showIncrements} = useContext(OrderableContext)\n const schema = useSchema()\n const router = usePaneRouter()\n const {ChildLink, groupIndex, routerPanesState} = router\n\n const currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || false\n const pressed = currentDoc === doc._id || currentDoc === doc._id.replace(`drafts.`, ``)\n const selected = pressed && routerPanesState.length === groupIndex + 2\n\n const Link = useMemo(\n () =>\n function LinkComponent(linkProps: {children: ReactNode}) {\n return <ChildLink {...linkProps} childId={doc._id} />\n },\n [ChildLink, doc._id],\n )\n\n return (\n <PreviewCard\n __unstable_focusRing\n // @ts-expect-error\n as={Link}\n data-as=\"a\"\n data-ui=\"PaneItem\"\n radius={2}\n pressed={pressed}\n selected={selected}\n sizing=\"border\"\n tabIndex={-1}\n tone=\"inherit\"\n width=\"100%\"\n flex={1}\n >\n <Flex align=\"center\">\n <Box paddingX={2} style={{flexShrink: 0}}>\n <Text size={2}>\n <DragHandleIcon cursor=\"grab\" />\n </Text>\n </Box>\n {showIncrements && (\n <Flex style={{flexShrink: 0}} align=\"center\" gap={1} paddingRight={1}>\n <Button\n padding={2}\n mode=\"ghost\"\n // eslint-disable-next-line react/jsx-no-bind\n onClick={() => increment(index, index + -1, doc._id, entities)}\n disabled={isFirst}\n icon={ChevronUpIcon}\n />\n <Button\n padding={2}\n mode=\"ghost\"\n disabled={isLast}\n // eslint-disable-next-line react/jsx-no-bind\n onClick={() => increment(index, index + 1, doc._id, entities)}\n icon={ChevronDownIcon}\n />\n </Flex>\n )}\n <Box style={{width: `100%`}}>\n <Flex flex={1} align=\"center\">\n <Preview\n layout=\"default\"\n value={doc}\n schemaType={schema.get(doc._type) as SchemaType}\n />\n </Flex>\n </Box>\n {dragBadge && (\n <Card tone=\"default\" marginRight={4} radius={5}>\n <AvatarCounter count={dragBadge} />\n </Card>\n )}\n </Flex>\n </PreviewCard>\n )\n}\n","import {LexoRank} from 'lexorank'\nimport type {PatchOperations} from 'sanity'\n\nimport type {SanityDocumentWithOrder} from '../types'\nimport {ORDER_FIELD_NAME} from './constants'\n\nexport interface MaifestArgs {\n entities: SanityDocumentWithOrder[]\n selectedItems: SanityDocumentWithOrder[]\n isMovingUp: boolean\n curIndex: number\n nextIndex: number\n prevIndex: number\n}\n\nexport interface ReorderArgs {\n entities: SanityDocumentWithOrder[]\n selectedIds: string[]\n source: any\n destination: any\n}\n\nexport interface ReorderReturn {\n newOrder: SanityDocumentWithOrder[]\n patches: [string, PatchOperations][]\n message: any\n}\n\nfunction lexicographicalSort(a: SanityDocumentWithOrder, b: SanityDocumentWithOrder) {\n if (!a[ORDER_FIELD_NAME] || !b[ORDER_FIELD_NAME]) {\n return 0\n } else if (a[ORDER_FIELD_NAME] < b[ORDER_FIELD_NAME]) {\n return -1\n } else if (a[ORDER_FIELD_NAME] > b[ORDER_FIELD_NAME]) {\n return 1\n }\n return 0\n}\n\nexport const reorderDocuments = ({\n entities,\n selectedIds,\n source,\n destination,\n}: ReorderArgs): ReorderReturn => {\n const startIndex = source.index\n const endIndex = destination.index\n const isMovingUp = startIndex > endIndex\n const selectedItems = entities.filter((item) => selectedIds.includes(item._id))\n const message = [\n 'Moved',\n selectedItems.length === 1 ? '1 document' : `${selectedItems.length} documents`,\n isMovingUp ? 'up' : 'down',\n 'from position',\n `${startIndex + 1} to ${endIndex + 1}`,\n ].join(' ')\n\n const {all, selected} = entities.reduce<{\n all: SanityDocumentWithOrder[]\n selected: SanityDocumentWithOrder[]\n }>(\n (acc, cur, curIndex) => {\n // Selected items get spread in below, so skip them here\n if (selectedIds.includes(cur._id)) {\n return {all: acc.all, selected: acc.selected}\n }\n\n // Drop selected items in\n if (curIndex === endIndex) {\n const prevIndex = curIndex - 1\n const prevRank = entities[prevIndex]?.[ORDER_FIELD_NAME]\n ? LexoRank.parse(entities[prevIndex]?.[ORDER_FIELD_NAME] as string)\n : LexoRank.min()\n\n const curRank = LexoRank.parse(entities[curIndex][ORDER_FIELD_NAME] as string)\n\n const nextIndex = curIndex + 1\n const nextRank = entities[nextIndex]?.[ORDER_FIELD_NAME]\n ? LexoRank.parse(entities[nextIndex]?.[ORDER_FIELD_NAME] as string)\n : LexoRank.max()\n\n let betweenRank = isMovingUp ? prevRank.between(curRank) : curRank.between(nextRank)\n\n // For each selected item, assign a new orderRank between now and next\n for (let selectedIndex = 0; selectedIndex < selectedItems.length; selectedIndex += 1) {\n selectedItems[selectedIndex][ORDER_FIELD_NAME] = betweenRank.toString()\n betweenRank = isMovingUp ? betweenRank.between(curRank) : betweenRank.between(nextRank)\n }\n\n return {\n // The `all` array gets sorted by order field later anyway\n // so that this probably isn't necessary ¯\\_(ツ)_/¯\n all: isMovingUp\n ? [...acc.all, ...selectedItems, cur]\n : [...acc.all, cur, ...selectedItems],\n selected: selectedItems,\n }\n }\n\n return {all: [...acc.all, cur], selected: acc.selected}\n },\n {all: [], selected: []},\n )\n\n const patches = selected.flatMap((doc) => {\n const docPatches: [string, PatchOperations][] = [\n [\n doc._id,\n {\n set: {\n [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME],\n },\n },\n ],\n ]\n\n // If it's a draft, we need to patch the published document as well\n if (doc._id.startsWith('drafts.') && doc.hasPublished) {\n docPatches.push([\n doc._id.replace('drafts.', ''),\n {\n set: {\n [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME],\n },\n },\n ])\n }\n\n return docPatches\n })\n\n // Safety-check to make sure everything is in order\n const allSorted = all.sort(lexicographicalSort)\n\n return {newOrder: allSorted, patches, message}\n}\n","import {type SanityClient, useClient} from 'sanity'\nimport {API_VERSION} from './constants'\n\nexport function useSanityClient(): SanityClient {\n return useClient({apiVersion: API_VERSION})\n}\n","import {useEffect, useState, useMemo, useCallback, type CSSProperties} from 'react'\nimport {DragDropContext, Draggable, Droppable, type DropResult} from '@hello-pangea/dnd'\nimport {Box, Card, useToast} from '@sanity/ui'\nimport type {PatchOperations} from 'sanity'\nimport {usePaneRouter} from 'sanity/structure'\n\nimport {Document} from './Document'\nimport {reorderDocuments} from './helpers/reorderDocuments'\nimport {ORDER_FIELD_NAME} from './helpers/constants'\nimport {useSanityClient} from './helpers/client'\nimport type {SanityDocumentWithOrder} from './types'\n\ninterface ListSetting {\n isDuplicate: boolean\n isGhosting: boolean\n isDragging: boolean\n isSelected: boolean\n}\n\nexport interface DraggableListProps {\n data: SanityDocumentWithOrder[]\n listIsUpdating: boolean\n setListIsUpdating: (val: boolean) => void\n}\n\nconst getItemStyle = (\n draggableStyle: CSSProperties | undefined,\n itemIsUpdating: boolean,\n): CSSProperties => ({\n userSelect: 'none',\n transition: 'opacity 500ms ease-in-out',\n opacity: itemIsUpdating ? 0.2 : 1,\n pointerEvents: itemIsUpdating ? 'none' : undefined,\n ...draggableStyle,\n})\n\nconst cardTone = (settings: ListSetting) => {\n const {isDuplicate, isGhosting, isDragging, isSelected} = settings\n\n if (isGhosting) return 'transparent'\n if (isDragging || isSelected) return 'primary'\n if (isDuplicate) return 'caution'\n\n return undefined\n}\n\nexport function DraggableList({data, listIsUpdating, setListIsUpdating}: DraggableListProps) {\n const toast = useToast()\n const router = usePaneRouter()\n const {groupIndex, routerPanesState} = router\n\n const currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || false\n\n // Maintains local state order before transaction completes\n const [orderedData, setOrderedData] = useState<SanityDocumentWithOrder[]>(data)\n\n // Update local state when documents change from an outside source\n useEffect(() => {\n if (!listIsUpdating) setOrderedData(data)\n /* eslint-disable-next-line react-hooks/exhaustive-deps */\n }, [data])\n\n const [draggingId, setDraggingId] = useState('')\n const [selectedIds, setSelectedIds] = useState<string[]>(currentDoc ? [currentDoc] : [])\n\n const clearSelected = useCallback(() => setSelectedIds([]), [setSelectedIds])\n\n const handleSelect = useCallback(\n (clickedId: string, index: number, nativeEvent: MouseEvent) => {\n const isSelected = selectedIds.includes(clickedId)\n const selectMultiple = nativeEvent.shiftKey\n const isUsingWindows = navigator.appVersion.indexOf('Win') !== -1\n const selectAdditional = isUsingWindows ? nativeEvent.ctrlKey : nativeEvent.metaKey\n\n let updatedIds = []\n\n // No modifier keys pressed during click:\n // - update selected to just this one\n // - open document\n if (!selectMultiple && !selectAdditional) {\n return setSelectedIds([clickedId])\n }\n\n // If shift key was held, prevent default to avoid new window opening\n if (selectMultiple) {\n nativeEvent.preventDefault()\n }\n\n // Shift key was held, add id's between last selected and this one\n // ...before adding this one\n if (selectMultiple && !isSelected) {\n const lastSelectedId = selectedIds[selectedIds.length - 1]\n const lastSelectedIndex = orderedData.findIndex((item) => item._id === lastSelectedId)\n\n const firstSelected = index < lastSelectedIndex ? index : lastSelectedIndex\n const lastSelected = index > lastSelectedIndex ? index : lastSelectedIndex\n\n const betweenIds = orderedData\n .filter((item, itemIndex) => itemIndex > firstSelected && itemIndex < lastSelected)\n .map((item) => item._id)\n\n updatedIds = [...selectedIds, ...betweenIds, clickedId]\n } else if (isSelected) {\n // Toggle off a single id\n updatedIds = selectedIds.filter((id) => id !== clickedId)\n } else {\n // Toggle on a single id\n updatedIds = [...selectedIds, clickedId]\n }\n\n return setSelectedIds(updatedIds)\n },\n [setSelectedIds, orderedData, selectedIds],\n )\n\n const client = useSanityClient()\n\n const transactPatches = useCallback(\n async (patches: [string, PatchOperations][], message: string) => {\n const transaction = client.transaction()\n\n patches.forEach(([docId, ops]) => transaction.patch(docId, ops))\n\n try {\n const updated = await transaction.commit({\n visibility: 'async',\n tag: 'orderable-document-list.reorder',\n })\n clearSelected()\n setDraggingId('')\n setListIsUpdating(false)\n toast.push({\n title: `${\n updated.results.length === 1 ? '1 document' : `${updated.results.length} documents`\n } reordered`,\n status: 'success',\n description: message,\n })\n } catch (err) {\n setDraggingId('')\n setListIsUpdating(false)\n toast.push({\n title: 'Reordering failed',\n status: 'error',\n })\n }\n },\n [client, setDraggingId, clearSelected, setListIsUpdating, toast],\n )\n\n const handleDragEnd = useCallback(\n (result: DropResult | undefined, entities: SanityDocumentWithOrder[]) => {\n setDraggingId('')\n\n const {source, destination, draggableId} = result ?? {}\n\n // Don't do anything if nothing changed\n if (source?.index === destination?.index) return\n\n // Don't do anything if we don't have the entitites\n if (!entities?.length || !draggableId) return\n\n // A document can be dragged without being one-of-many-selected\n const effectedIds = selectedIds?.length ? selectedIds : [draggableId]\n\n // Don't do anything if we don't have ids to effect\n if (!effectedIds?.length) return\n\n // Update state to update styles + prevent data refetching\n setListIsUpdating(true)\n setSelectedIds(effectedIds)\n\n const {newOrder, patches, message} = reorderDocuments({\n entities,\n selectedIds: effectedIds,\n source,\n destination,\n })\n\n // Update local state\n if (newOrder?.length) {\n setOrderedData(newOrder)\n }\n\n // Transact new order patches\n if (patches?.length) {\n transactPatches(patches, message)\n }\n },\n [selectedIds, setDraggingId, setSelectedIds, transactPatches, setListIsUpdating],\n )\n\n const handleDragStart = useCallback(\n (start: {draggableId: string}) => {\n const id = start.draggableId\n const selected = selectedIds.includes(id)\n\n // if dragging an item that is not selected - unselect all items\n if (!selected) clearSelected()\n\n setDraggingId(id)\n },\n [selectedIds, clearSelected, setDraggingId],\n )\n\n // Move one document up or down one place, by fake invoking the drag function\n const incrementIndex = useCallback(\n (shiftFrom: number, shiftTo: number, id: string, entities: SanityDocumentWithOrder[]) => {\n const result = {\n draggableId: id,\n source: {index: shiftFrom},\n destination: {index: shiftTo},\n }\n\n return handleDragEnd(result as DropResult, entities)\n },\n [handleDragEnd],\n )\n\n const onWindowKeyDown = useCallback(\n (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n clearSelected()\n }\n },\n [clearSelected],\n )\n\n useEffect(() => {\n window.addEventListener('keydown', onWindowKeyDown)\n\n return () => {\n window.removeEventListener('keydown', onWindowKeyDown)\n }\n }, [onWindowKeyDown])\n\n // Find all items with duplicate order field\n const duplicateOrders = useMemo(() => {\n if (!orderedData.length) return []\n\n const orderField = orderedData.map((item) => item[ORDER_FIELD_NAME])\n\n return orderField.filter((item, index) => orderField.indexOf(item) !== index)\n }, [orderedData])\n\n const onDragEnd = useCallback(\n (result: DropResult) => handleDragEnd(result, orderedData),\n [orderedData, handleDragEnd],\n )\n\n return (\n <DragDropContext onDragStart={handleDragStart} onDragEnd={onDragEnd}>\n <Droppable droppableId=\"documentSortZone\">\n {(provided) => (\n <div {...provided.droppableProps} ref={provided.innerRef}>\n {orderedData.map((item, index) => (\n <Draggable\n key={`${item._id}-${item[ORDER_FIELD_NAME]}`}\n draggableId={item._id}\n index={index}\n // onClick={(event) => handleDraggableClick(event, provided, snapshot)}\n >\n {(innerProvided, innerSnapshot) => {\n const isSelected = selectedIds.includes(item._id)\n const isDragging = innerSnapshot.isDragging\n const isGhosting = Boolean(!isDragging && draggingId && isSelected)\n const isUpdating = listIsUpdating && isSelected\n const isDisabled = Boolean(!item[ORDER_FIELD_NAME])\n const isDuplicate = duplicateOrders.includes(item[ORDER_FIELD_NAME])\n const tone = cardTone({isDuplicate, isGhosting, isDragging, isSelected})\n const selectedCount = selectedIds.length\n\n const dragBadge = isDragging && selectedCount > 1 ? selectedCount : false\n\n return (\n <div\n ref={innerProvided.innerRef}\n {...innerProvided.draggableProps}\n {...innerProvided.dragHandleProps}\n style={\n isDisabled\n ? {opacity: 0.2, pointerEvents: 'none'}\n : getItemStyle(innerProvided.draggableProps.style, isUpdating)\n }\n >\n <Box paddingBottom={1}>\n <Card\n tone={tone}\n shadow={isDragging ? 2 : undefined}\n radius={2}\n // eslint-disable-next-line react/jsx-no-bind\n onClick={(e) => handleSelect(item._id, index, e.nativeEvent)}\n >\n <Document\n doc={item}\n entities={orderedData}\n increment={incrementIndex}\n index={index}\n isFirst={index === 0}\n isLast={index === orderedData.length - 1}\n dragBadge={dragBadge}\n />\n </Card>\n </Box>\n </div>\n )\n }}\n </Draggable>\n ))}\n {provided.placeholder}\n </div>\n )}\n </Droppable>\n </DragDropContext>\n )\n}\n","import {ORDER_FIELD_NAME} from './constants'\n\nexport interface DocumentListQueryProps {\n type: string\n filter?: string\n params?: Record<string, unknown>\n}\n\nexport interface DocumentListQueryResult {\n query: string\n queryParams: Record<string, string | number | boolean | string[]>\n}\n\nconst DEFAULT_PARAMS = {}\n\nexport function getDocumentQuery({\n type,\n filter,\n params = DEFAULT_PARAMS,\n}: DocumentListQueryProps): DocumentListQueryResult {\n const querySelect = `*[_type == $type ${filter ? `&& ${filter}` : ''}]`\n const queryOrder = `|order(@[$order] asc)`\n const queryFields = `{_id, _type, ${ORDER_FIELD_NAME}}`\n\n const query = `${querySelect}${queryOrder}${queryFields}`\n const queryParams = {\n ...params,\n type,\n order: ORDER_FIELD_NAME,\n }\n\n return {query, queryParams}\n}\n","import {useEffect, useMemo, useState} from 'react'\nimport {Box, Flex, Container, Spinner, Stack, Text} from '@sanity/ui'\n\nimport {useListeningQuery, Feedback} from 'sanity-plugin-utils'\nimport {DraggableList} from './DraggableList'\nimport {ORDER_FIELD_NAME} from './helpers/constants'\nimport type {SanityDocumentWithOrder} from './types'\nimport {DocumentListQueryProps, getDocumentQuery} from './helpers/query'\n\nexport function DocumentListQuery(props: DocumentListQueryProps) {\n const [listIsUpdating, setListIsUpdating] = useState(false)\n const [data, setData] = useState<SanityDocumentWithOrder[] | null>([])\n\n const {query, queryParams} = getDocumentQuery(props)\n\n const {\n data: _queryData,\n loading,\n error,\n } = useListeningQuery<SanityDocumentWithOrder[]>(query, {\n params: queryParams,\n initialValue: [],\n })\n // @ts-expect-error Should not be needed to \"cast\", but sanity-plugin-utils is not typed correctly\n const queryData: SanityDocumentWithOrder[] = _queryData\n\n useEffect(() => {\n if (queryData) {\n const filteredDocuments = queryData.reduce<SanityDocumentWithOrder[]>((acc, cur) => {\n if (!cur._id.startsWith(`drafts.`)) {\n // eslint-disable-next-line max-nested-callbacks\n const alsoHasDraft = queryData.some((doc) => doc._id === `drafts.${cur._id}`)\n return alsoHasDraft ? acc : [...acc, cur]\n }\n\n // Check if the draft has a published version\n cur.hasPublished = queryData.some((doc) => doc._id === cur._id.replace(`drafts.`, ``))\n\n return [...acc, cur]\n }, [])\n\n setData(filteredDocuments)\n } else {\n setData([])\n }\n }, [queryData])\n\n const unorderedDataCount = useMemo(\n () => (data?.length ? data.filter((doc) => !doc[ORDER_FIELD_NAME]).length : 0),\n [data],\n )\n\n if (loading) {\n return (\n <Flex style={{width: `100%`, height: `100%`}} align=\"center\" justify=\"center\">\n <Spinner />\n </Flex>\n )\n }\n\n if (error) {\n return (\n <Box padding={2}>\n <Feedback tone=\"critical\" title=\"There was an error\" description=\"Please try again later\" />\n </Box>\n )\n }\n\n if (!data || data?.length == 0)\n return (\n <Flex align=\"center\" direction=\"column\" height=\"fill\" justify=\"center\">\n <Container width={1}>\n <Box paddingX={4} paddingY={5}>\n <Text align=\"center\" muted>\n No documents of this type\n </Text>\n </Box>\n </Container>\n </Flex>\n )\n\n return (\n <Stack space={1} style={{overflow: `auto`, height: `100%`}}>\n <Box padding={2}>\n {unorderedDataCount > 0 && (\n <Box marginBottom={2}>\n <Feedback\n tone=\"caution\"\n description={\n <>\n {unorderedDataCount}/{data?.length} documents have no order. Select{' '}\n <strong>Reset Order</strong> from the menu above to fix.\n </>\n }\n />\n </Box>\n )}\n <DraggableList\n data={data}\n listIsUpdating={listIsUpdating}\n setListIsUpdating={setListIsUpdating}\n />\n </Box>\n </Stack>\n )\n}\n","import {useEffect, useMemo} from 'react'\nimport {useToast, Box, type ToastParams} from '@sanity/ui'\n\nimport {useSchema} from 'sanity'\nimport {Feedback} from 'sanity-plugin-utils'\nimport {DocumentListQuery} from './DocumentListQuery'\nimport {OrderableContext} from './OrderableContext'\n\nimport {ORDER_FIELD_NAME} from './helpers/constants'\n\nexport interface DocumentListWrapperProps {\n showIncrements: boolean\n type: string\n resetOrderTransaction: ToastParams\n // eslint-disable-next-line react/require-default-props\n filter?: string\n // eslint-disable-next-line react/require-default-props\n params?: Record<string, unknown>\n}\n\n// 1. Validate first that the schema has been configured for ordering\n// 2. Setup context for showIncrements\nexport function DocumentListWrapper({\n type,\n showIncrements,\n resetOrderTransaction,\n filter,\n params,\n}: DocumentListWrapperProps) {\n const toast = useToast()\n const schema = useSchema()\n\n useEffect(() => {\n if (resetOrderTransaction?.title && resetOrderTransaction?.status) {\n toast.push(resetOrderTransaction)\n }\n }, [resetOrderTransaction, toast])\n\n const schemaIsInvalid = useMemo(() => {\n // Option not passed\n if (!type) {\n return (\n <>\n No <code>type</code> was configured\n </>\n )\n }\n\n const typeSchema = schema.get(type)\n\n // Schema not found\n if (!typeSchema) {\n return (\n <>\n Schema <code>{type}</code> not found\n </>\n )\n }\n\n // Schema lacks an order field\n if (\n !('fields' in typeSchema) ||\n !typeSchema.fields.some((field) => field?.name === ORDER_FIELD_NAME)\n ) {\n return (\n <>\n Schema <code>{type}</code> must have an <code>{ORDER_FIELD_NAME}</code> field of type{' '}\n <code>string</code>\n </>\n )\n }\n\n // Schema's order field is not a string\n if (\n 'fields' in typeSchema &&\n typeSchema.fields.some(\n (field) => field?.name === ORDER_FIELD_NAME && field?.type?.name !== 'string',\n )\n ) {\n return (\n <>\n <code>{ORDER_FIELD_NAME}</code> field on Schema <code>{type}</code> must be{' '}\n <code>string</code> type\n </>\n )\n }\n\n return ''\n }, [type, schema])\n\n if (schemaIsInvalid) {\n return (\n <Box padding={2}>\n <Feedback description={schemaIsInvalid} tone=\"caution\" />\n </Box>\n )\n }\n\n return (\n <OrderableContext.Provider value={{showIncrements}}>\n <DocumentListQuery type={type} filter={filter} params={params} />\n </OrderableContext.Provider>\n )\n}\n","import {LexoRank} from 'lexorank'\nimport type {MultipleMutationResult, SanityClient} from '@sanity/client'\nimport {ORDER_FIELD_NAME} from './constants'\nimport {DocumentListQueryProps, getDocumentQuery} from './query'\n\nexport interface ResetOrderParams extends DocumentListQueryProps {\n client: SanityClient\n}\n\n// Function to wipe and re-do ordering with LexoRank\n// Will at least attempt to start with the current order\nexport async function resetOrder(params: ResetOrderParams): Promise<MultipleMutationResult | null> {\n const {client, ...queryProps} = params\n const {query, queryParams} = getDocumentQuery(queryProps)\n const documents = await client.fetch<\n Array<{\n _id: string\n _type: string\n [ORDER_FIELD_NAME]: string\n }>\n >(query, queryParams, {\n tag: 'orderable-document-list.reset-order',\n })\n\n if (documents.length === 0) {\n return null\n }\n\n let aLexoRank = LexoRank.min()\n\n const transaction = documents\n .map((doc) => doc._id)\n .reduce((trx, documentId) => {\n // Generate next rank before even the first document so there's room to move!\n aLexoRank = aLexoRank.genNext().genNext()\n\n return trx.patch(documentId, {\n set: {[ORDER_FIELD_NAME]: aLexoRank.toString()},\n })\n }, client.transaction())\n\n return transaction.commit({\n visibility: 'async',\n tag: 'orderable-document-list.reset-order',\n })\n}\n","import {Component} from 'react'\n\nimport type {SanityClient} from '@sanity/client'\nimport type {ToastParams} from '@sanity/ui'\nimport {DocumentListWrapper} from './DocumentListWrapper'\nimport {resetOrder} from './helpers/resetOrder'\n\nexport interface OrderableDocumentListProps {\n options: {\n type: string\n client: SanityClient\n filter?: string\n params?: Record<string, unknown>\n }\n}\n\ninterface State {\n showIncrements: boolean\n resetOrderTransaction: ToastParams\n}\n\n// Must use a Class Component here so the actionHandlers can be called\nexport class OrderableDocumentList extends Component<OrderableDocumentListProps, State> {\n constructor(props: OrderableDocumentListProps) {\n super(props)\n this.state = {\n showIncrements: false,\n resetOrderTransaction: {},\n }\n }\n\n actionHandlers = {\n showIncrements: () => {\n this.setState((state) => ({\n showIncrements: !state.showIncrements,\n }))\n },\n\n resetOrder: async () => {\n this.setState(() => ({\n resetOrderTransaction: {\n status: `info`,\n title: `Reordering started...`,\n closable: true,\n },\n }))\n\n const update = await resetOrder(this.props.options)\n\n const reorderWasSuccessful = update?.results?.length\n\n this.setState(() => ({\n resetOrderTransaction: {\n status: reorderWasSuccessful ? `success` : `info`,\n title: reorderWasSuccessful\n ? `Reordered ${update.results.length === 1 ? `Document` : `Documents`}`\n : `Reordering failed`,\n closable: true,\n },\n }))\n },\n }\n\n render() {\n const type = this?.props?.options?.type\n if (!type) {\n return null\n }\n return (\n <DocumentListWrapper\n filter={this?.props?.options?.filter}\n params={this?.props?.options?.params}\n type={type}\n showIncrements={this.state.showIncrements}\n resetOrderTransaction={this.state.resetOrderTransaction}\n />\n )\n }\n}\n","import {GenerateIcon, SortIcon} from '@sanity/icons'\nimport type {ConfigContext} from 'sanity'\n\nimport type {ComponentType} from 'react'\nimport {StructureBuilder, type ListItem, type MenuItem} from 'sanity/structure'\nimport {OrderableDocumentList} from '../OrderableDocumentList'\nimport {API_VERSION} from '../helpers/constants'\n\nexport interface OrderableListConfig {\n type: string\n id?: string\n title?: string\n icon?: ComponentType\n params?: Record<string, unknown>\n filter?: string\n menuItems?: MenuItem[]\n createIntent?: boolean\n context: ConfigContext\n S: StructureBuilder\n}\n\nexport function orderableDocumentListDeskItem(config: OrderableListConfig): ListItem {\n if (!config?.type || !config.context || !config.S) {\n throw new Error(`\n type, context and S (StructureBuilder) must be provided.\n context and S are available when configuring structure.\n Example: orderableDocumentListDeskItem({type: 'category'})\n `)\n }\n\n const {type, filter, menuItems = [], createIntent, params, title, icon, id, context, S} = config\n const {schema, getClient} = context\n const client = getClient({apiVersion: API_VERSION})\n\n const listTitle = title ?? `Orderable ${type}`\n const listId = id ?? `orderable-${type}`\n const listIcon = icon ?? SortIcon\n const typeTitle = schema.get(type)?.title ?? type\n\n if (createIntent !== false) {\n menuItems.push(\n S.menuItem()\n .title(`Create new ${typeTitle}`)\n .intent({type: 'create', params: {type}})\n .serialize(),\n )\n }\n return S.listItem()\n .title(listTitle)\n .id(listId)\n .icon(listIcon)\n .schemaType(type)\n .child(\n Object.assign(\n S.documentTypeList(type)\n .canHandleIntent(() => !!createIntent)\n .serialize(),\n {\n // Prevents the component from re-rendering when switching documents\n __preserveInstance: true,\n // Prevents the component from NOT re-rendering when switching listItems\n key: listId,\n\n type: 'component',\n component: OrderableDocumentList,\n options: {type, filter, params, client},\n menuItems: [\n ...menuItems,\n S.menuItem().title(`Reset Order`).icon(GenerateIcon).action(`resetOrder`).serialize(),\n S.menuItem()\n .title(`Toggle Increments`)\n .icon(SortIcon)\n .action(`showIncrements`)\n .serialize(),\n ],\n },\n ),\n )\n .serialize()\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/helpers/constants.ts","../src/helpers/initialRank.ts","../src/fields/orderRankField.ts","../src/fields/orderRankOrdering.ts","../src/OrderableContext.ts","../src/Document.tsx","../src/helpers/reorderDocuments.ts","../src/helpers/client.ts","../src/DraggableList.tsx","../src/helpers/query.ts","../src/helpers/getFilteredDedupedDocs.ts","../src/DocumentListQuery.tsx","../src/DocumentListWrapper.tsx","../src/helpers/resetOrder.ts","../src/OrderableDocumentList.tsx","../src/desk-structure/orderableDocumentListDeskItem.ts"],"sourcesContent":["export const ORDER_FIELD_NAME = `orderRank` as const\n\n// same version we are using in the vision plugin\nexport const API_VERSION = `v2025-06-27` as const\n","import {LexoRank} from 'lexorank'\nimport {NewItemPosition} from '../types'\n\n// Use in initial value field by passing in the rank value of the last document\n// If not value passed, generate a sensibly low rank\nexport function initialRank(\n compareRankValue = ``,\n newItemPosition: NewItemPosition = 'after',\n): string {\n const compareRank = compareRankValue ? LexoRank.parse(compareRankValue) : LexoRank.min()\n const rank =\n newItemPosition === 'before' ? compareRank.genPrev().genPrev() : compareRank.genNext().genNext()\n\n return rank.toString()\n}\n","import {type ConfigContext, defineField, FieldDefinition, type StringDefinition} from 'sanity'\nimport {API_VERSION, ORDER_FIELD_NAME} from '../helpers/constants'\nimport {initialRank} from '../helpers/initialRank'\nimport type {NewItemPosition} from '../types'\n\nexport type SchemaContext = Omit<ConfigContext, 'schema' | 'currentUser' | 'client'>\n\nexport interface RankFieldConfig\n extends Partial<Omit<StringDefinition, 'name' | 'type' | 'initialValue'>> {\n type: string\n newItemPosition?: NewItemPosition\n}\n\nexport const orderRankField = (config: RankFieldConfig): FieldDefinition<'string'> => {\n if (!config?.type) {\n throw new Error(\n `\n type must be provided.\n Example: orderRankField({type: 'category'})\n `,\n )\n }\n\n const {type, newItemPosition = 'after', ...rest} = config\n return defineField({\n title: 'Order Rank',\n readOnly: true,\n hidden: true,\n ...rest,\n name: ORDER_FIELD_NAME,\n type: 'string',\n initialValue: async (p, {getClient}) => {\n const direction = newItemPosition === 'before' ? 'asc' : 'desc'\n\n const lastDocOrderRank = await getClient({apiVersion: API_VERSION}).fetch(\n `*[_type == $type]|order(@[$order] ${direction})[0][$order]`,\n {type, order: ORDER_FIELD_NAME},\n {tag: 'orderable-document-list.last-doc-order-rank'},\n )\n return initialRank(lastDocOrderRank, newItemPosition)\n },\n })\n}\n","import type {SortOrdering} from 'sanity'\nimport {ORDER_FIELD_NAME} from '../helpers/constants'\n\nexport const orderRankOrdering: SortOrdering = {\n title: 'Ordered',\n name: 'ordered',\n by: [{field: ORDER_FIELD_NAME, direction: 'asc'}],\n}\n","import {createContext} from 'react'\n\nexport interface OrderableContextValue {\n showIncrements?: boolean\n}\n\nexport const OrderableContext = createContext<OrderableContextValue>({})\n","import {useContext, useMemo, type ReactNode} from 'react'\nimport {ChevronDownIcon, ChevronUpIcon, DragHandleIcon} from '@sanity/icons'\nimport {AvatarCounter, Card, Box, Button, Flex, Text, Tooltip} from '@sanity/ui'\nimport {\n useSchema,\n SchemaType,\n PreviewCard,\n Preview,\n DocumentStatusIndicator,\n DocumentStatus,\n useDocumentVersionInfo,\n} from 'sanity'\nimport {usePaneRouter} from 'sanity/structure'\n\nimport {OrderableContext} from './OrderableContext'\nimport type {SanityDocumentWithOrder} from './types'\n\nexport interface DocumentProps {\n doc: SanityDocumentWithOrder\n entities: SanityDocumentWithOrder[]\n increment: (\n index: number,\n nextIndex: number,\n docId: string,\n entities: SanityDocumentWithOrder[],\n ) => void\n index: number\n isFirst: boolean\n isLast: boolean\n dragBadge: number | false\n}\n\nexport function Document({\n doc,\n increment,\n entities,\n index,\n isFirst,\n isLast,\n dragBadge,\n}: DocumentProps) {\n const {showIncrements} = useContext(OrderableContext)\n const schema = useSchema()\n const router = usePaneRouter()\n const versionsInfo = useDocumentVersionInfo(doc._id)\n\n const {ChildLink, groupIndex, routerPanesState} = router\n\n const currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || false\n const pressed = currentDoc === doc._id || currentDoc === doc._id.replace(`drafts.`, ``)\n const selected = pressed && routerPanesState.length === groupIndex + 2\n\n const Link = useMemo(\n () =>\n function LinkComponent(linkProps: {children: ReactNode}) {\n return <ChildLink {...linkProps} childId={doc._id} />\n },\n [ChildLink, doc._id],\n )\n\n const tooltip = (\n <DocumentStatus\n draft={versionsInfo.draft}\n published={versionsInfo.published}\n versions={versionsInfo.versions}\n />\n )\n\n return (\n <PreviewCard\n __unstable_focusRing\n // @ts-expect-error\n as={Link}\n data-as=\"a\"\n data-ui=\"PaneItem\"\n radius={2}\n pressed={pressed}\n selected={selected}\n sizing=\"border\"\n tabIndex={-1}\n tone=\"inherit\"\n width=\"100%\"\n flex={1}\n >\n <Flex align=\"center\">\n <Box paddingX={2} style={{flexShrink: 0}}>\n <Text size={2}>\n <DragHandleIcon cursor=\"grab\" />\n </Text>\n </Box>\n {showIncrements && (\n <Flex style={{flexShrink: 0}} align=\"center\" gap={1} paddingRight={1}>\n <Button\n padding={2}\n mode=\"ghost\"\n // eslint-disable-next-line react/jsx-no-bind\n onClick={() => increment(index, index + -1, doc._id, entities)}\n disabled={isFirst}\n icon={ChevronUpIcon}\n />\n <Button\n padding={2}\n mode=\"ghost\"\n disabled={isLast}\n // eslint-disable-next-line react/jsx-no-bind\n onClick={() => increment(index, index + 1, doc._id, entities)}\n icon={ChevronDownIcon}\n />\n </Flex>\n )}\n <Box style={{width: `100%`}}>\n <Flex flex={1} align=\"center\" justify=\"space-between\" paddingRight={3}>\n <Preview\n layout=\"default\"\n value={doc}\n schemaType={schema.get(doc._type) as SchemaType}\n />\n\n <Tooltip content={tooltip} portal placement=\"right\" boundaryElement={null}>\n <Flex align=\"center\">\n <DocumentStatusIndicator\n draft={versionsInfo.draft}\n published={versionsInfo.published}\n versions={versionsInfo.versions}\n />\n </Flex>\n </Tooltip>\n </Flex>\n </Box>\n {dragBadge && (\n <Card tone=\"default\" marginRight={4} radius={5}>\n <AvatarCounter count={dragBadge} />\n </Card>\n )}\n </Flex>\n </PreviewCard>\n )\n}\n","import {LexoRank} from 'lexorank'\nimport type {PatchOperations} from 'sanity'\n\nimport type {SanityDocumentWithOrder} from '../types'\nimport {ORDER_FIELD_NAME} from './constants'\n\nexport interface MaifestArgs {\n entities: SanityDocumentWithOrder[]\n selectedItems: SanityDocumentWithOrder[]\n isMovingUp: boolean\n curIndex: number\n nextIndex: number\n prevIndex: number\n}\n\nexport interface ReorderArgs {\n entities: SanityDocumentWithOrder[]\n selectedIds: string[]\n source: any\n destination: any\n}\n\nexport interface ReorderReturn {\n newOrder: SanityDocumentWithOrder[]\n patches: [string, PatchOperations][]\n message: any\n}\n\nfunction lexicographicalSort(a: SanityDocumentWithOrder, b: SanityDocumentWithOrder) {\n if (!a[ORDER_FIELD_NAME] || !b[ORDER_FIELD_NAME]) {\n return 0\n } else if (a[ORDER_FIELD_NAME] < b[ORDER_FIELD_NAME]) {\n return -1\n } else if (a[ORDER_FIELD_NAME] > b[ORDER_FIELD_NAME]) {\n return 1\n }\n return 0\n}\n\nexport const reorderDocuments = ({\n entities,\n selectedIds,\n source,\n destination,\n}: ReorderArgs): ReorderReturn => {\n const startIndex = source.index\n const endIndex = destination.index\n const isMovingUp = startIndex > endIndex\n const selectedItems = entities.filter((item) => selectedIds.includes(item._id))\n const message = [\n 'Moved',\n selectedItems.length === 1 ? '1 document' : `${selectedItems.length} documents`,\n isMovingUp ? 'up' : 'down',\n 'from position',\n `${startIndex + 1} to ${endIndex + 1}`,\n ].join(' ')\n\n const {all, selected} = entities.reduce<{\n all: SanityDocumentWithOrder[]\n selected: SanityDocumentWithOrder[]\n }>(\n (acc, cur, curIndex) => {\n // Selected items get spread in below, so skip them here\n if (selectedIds.includes(cur._id)) {\n return {all: acc.all, selected: acc.selected}\n }\n\n // Drop selected items in\n if (curIndex === endIndex) {\n const prevIndex = curIndex - 1\n const prevRank = entities[prevIndex]?.[ORDER_FIELD_NAME]\n ? LexoRank.parse(entities[prevIndex]?.[ORDER_FIELD_NAME] as string)\n : LexoRank.min()\n\n const curRank = LexoRank.parse(entities[curIndex][ORDER_FIELD_NAME] as string)\n\n const nextIndex = curIndex + 1\n const nextRank = entities[nextIndex]?.[ORDER_FIELD_NAME]\n ? LexoRank.parse(entities[nextIndex]?.[ORDER_FIELD_NAME] as string)\n : LexoRank.max()\n\n let betweenRank = isMovingUp ? prevRank.between(curRank) : curRank.between(nextRank)\n\n // For each selected item, assign a new orderRank between now and next\n for (let selectedIndex = 0; selectedIndex < selectedItems.length; selectedIndex += 1) {\n selectedItems[selectedIndex][ORDER_FIELD_NAME] = betweenRank.toString()\n betweenRank = isMovingUp ? betweenRank.between(curRank) : betweenRank.between(nextRank)\n }\n\n return {\n // The `all` array gets sorted by order field later anyway\n // so that this probably isn't necessary ¯\\_(ツ)_/¯\n all: isMovingUp\n ? [...acc.all, ...selectedItems, cur]\n : [...acc.all, cur, ...selectedItems],\n selected: selectedItems,\n }\n }\n\n return {all: [...acc.all, cur], selected: acc.selected}\n },\n {all: [], selected: []},\n )\n\n const patches = selected.flatMap((doc) => {\n const docPatches: [string, PatchOperations][] = [\n [\n doc._id,\n {\n set: {\n [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME],\n },\n },\n ],\n ]\n\n // If it's a draft, we need to patch the published document as well\n if (doc._id.startsWith('drafts.') && doc.hasPublished) {\n docPatches.push([\n doc._id.replace('drafts.', ''),\n {\n set: {\n [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME],\n },\n },\n ])\n }\n\n return docPatches\n })\n\n // Safety-check to make sure everything is in order\n const allSorted = all.sort(lexicographicalSort)\n\n return {newOrder: allSorted, patches, message}\n}\n","import {type SanityClient, useClient, usePerspective} from 'sanity'\nimport {API_VERSION} from './constants'\n\nexport function useSanityClient(): SanityClient {\n const {perspectiveStack} = usePerspective()\n\n return useClient({apiVersion: API_VERSION}).withConfig({perspective: perspectiveStack})\n}\n","import {useEffect, useState, useMemo, useCallback, type CSSProperties} from 'react'\nimport {DragDropContext, Draggable, Droppable, type DropResult} from '@hello-pangea/dnd'\nimport {Box, Card, useToast} from '@sanity/ui'\nimport type {PatchOperations} from 'sanity'\nimport {usePaneRouter} from 'sanity/structure'\n\nimport {Document} from './Document'\nimport {reorderDocuments} from './helpers/reorderDocuments'\nimport {ORDER_FIELD_NAME} from './helpers/constants'\nimport {useSanityClient} from './helpers/client'\nimport type {SanityDocumentWithOrder} from './types'\n\ninterface ListSetting {\n isDuplicate: boolean\n isGhosting: boolean\n isDragging: boolean\n isSelected: boolean\n}\n\nexport interface DraggableListProps {\n data: SanityDocumentWithOrder[]\n listIsUpdating: boolean\n setListIsUpdating: (val: boolean) => void\n}\n\nconst getItemStyle = (\n draggableStyle: CSSProperties | undefined,\n itemIsUpdating: boolean,\n): CSSProperties => ({\n userSelect: 'none',\n transition: 'opacity 500ms ease-in-out',\n opacity: itemIsUpdating ? 0.2 : 1,\n pointerEvents: itemIsUpdating ? 'none' : undefined,\n ...draggableStyle,\n})\n\nconst cardTone = (settings: ListSetting) => {\n const {isDuplicate, isGhosting, isDragging, isSelected} = settings\n\n if (isGhosting) return 'transparent'\n if (isDragging || isSelected) return 'primary'\n if (isDuplicate) return 'caution'\n\n return undefined\n}\n\nexport function DraggableList({data, listIsUpdating, setListIsUpdating}: DraggableListProps) {\n const toast = useToast()\n const router = usePaneRouter()\n const {groupIndex, routerPanesState} = router\n\n const currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || false\n\n // Maintains local state order before transaction completes\n const [orderedData, setOrderedData] = useState<SanityDocumentWithOrder[]>(data)\n\n // Update local state when documents change from an outside source\n useEffect(() => {\n if (!listIsUpdating) setOrderedData(data)\n /* eslint-disable-next-line react-hooks/exhaustive-deps */\n }, [data])\n\n const [draggingId, setDraggingId] = useState('')\n const [selectedIds, setSelectedIds] = useState<string[]>(currentDoc ? [currentDoc] : [])\n\n const clearSelected = useCallback(() => setSelectedIds([]), [setSelectedIds])\n\n const handleSelect = useCallback(\n (clickedId: string, index: number, nativeEvent: MouseEvent) => {\n const isSelected = selectedIds.includes(clickedId)\n const selectMultiple = nativeEvent.shiftKey\n const isUsingWindows = navigator.appVersion.indexOf('Win') !== -1\n const selectAdditional = isUsingWindows ? nativeEvent.ctrlKey : nativeEvent.metaKey\n\n let updatedIds = []\n\n // No modifier keys pressed during click:\n // - update selected to just this one\n // - open document\n if (!selectMultiple && !selectAdditional) {\n return setSelectedIds([clickedId])\n }\n\n // If shift key was held, prevent default to avoid new window opening\n if (selectMultiple) {\n nativeEvent.preventDefault()\n }\n\n // Shift key was held, add id's between last selected and this one\n // ...before adding this one\n if (selectMultiple && !isSelected) {\n const lastSelectedId = selectedIds[selectedIds.length - 1]\n const lastSelectedIndex = orderedData.findIndex((item) => item._id === lastSelectedId)\n\n const firstSelected = index < lastSelectedIndex ? index : lastSelectedIndex\n const lastSelected = index > lastSelectedIndex ? index : lastSelectedIndex\n\n const betweenIds = orderedData\n .filter((item, itemIndex) => itemIndex > firstSelected && itemIndex < lastSelected)\n .map((item) => item._id)\n\n updatedIds = [...selectedIds, ...betweenIds, clickedId]\n } else if (isSelected) {\n // Toggle off a single id\n updatedIds = selectedIds.filter((id) => id !== clickedId)\n } else {\n // Toggle on a single id\n updatedIds = [...selectedIds, clickedId]\n }\n\n return setSelectedIds(updatedIds)\n },\n [setSelectedIds, orderedData, selectedIds],\n )\n\n const client = useSanityClient()\n\n const transactPatches = useCallback(\n async (patches: [string, PatchOperations][], message: string) => {\n const transaction = client.transaction()\n\n patches.forEach(([docId, ops]) => transaction.patch(docId, ops))\n\n try {\n const updated = await transaction.commit({\n visibility: 'async',\n tag: 'orderable-document-list.reorder',\n })\n clearSelected()\n setDraggingId('')\n setListIsUpdating(false)\n toast.push({\n title: `${\n updated.results.length === 1 ? '1 document' : `${updated.results.length} documents`\n } reordered`,\n status: 'success',\n description: message,\n })\n } catch (err) {\n setDraggingId('')\n setListIsUpdating(false)\n toast.push({\n title: 'Reordering failed',\n status: 'error',\n })\n }\n },\n [client, setDraggingId, clearSelected, setListIsUpdating, toast],\n )\n\n const handleDragEnd = useCallback(\n (result: DropResult | undefined, entities: SanityDocumentWithOrder[]) => {\n setDraggingId('')\n\n const {source, destination, draggableId} = result ?? {}\n\n // Don't do anything if nothing changed\n if (source?.index === destination?.index) return\n\n // Don't do anything if we don't have the entitites\n if (!entities?.length || !draggableId) return\n\n // A document can be dragged without being one-of-many-selected\n const effectedIds = selectedIds?.length ? selectedIds : [draggableId]\n\n // Don't do anything if we don't have ids to effect\n if (!effectedIds?.length) return\n\n // Update state to update styles + prevent data refetching\n setListIsUpdating(true)\n setSelectedIds(effectedIds)\n\n const {newOrder, patches, message} = reorderDocuments({\n entities,\n selectedIds: effectedIds,\n source,\n destination,\n })\n\n // Update local state\n if (newOrder?.length) {\n setOrderedData(newOrder)\n }\n\n // Transact new order patches\n if (patches?.length) {\n transactPatches(patches, message)\n }\n },\n [selectedIds, setDraggingId, setSelectedIds, transactPatches, setListIsUpdating],\n )\n\n const handleDragStart = useCallback(\n (start: {draggableId: string}) => {\n const id = start.draggableId\n const selected = selectedIds.includes(id)\n\n // if dragging an item that is not selected - unselect all items\n if (!selected) clearSelected()\n\n setDraggingId(id)\n },\n [selectedIds, clearSelected, setDraggingId],\n )\n\n // Move one document up or down one place, by fake invoking the drag function\n const incrementIndex = useCallback(\n (shiftFrom: number, shiftTo: number, id: string, entities: SanityDocumentWithOrder[]) => {\n const result = {\n draggableId: id,\n source: {index: shiftFrom},\n destination: {index: shiftTo},\n }\n\n return handleDragEnd(result as DropResult, entities)\n },\n [handleDragEnd],\n )\n\n const onWindowKeyDown = useCallback(\n (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n clearSelected()\n }\n },\n [clearSelected],\n )\n\n useEffect(() => {\n window.addEventListener('keydown', onWindowKeyDown)\n\n return () => {\n window.removeEventListener('keydown', onWindowKeyDown)\n }\n }, [onWindowKeyDown])\n\n // Find all items with duplicate order field\n const duplicateOrders = useMemo(() => {\n if (!orderedData.length) return []\n\n const orderField = orderedData.map((item) => item[ORDER_FIELD_NAME])\n\n return orderField.filter((item, index) => orderField.indexOf(item) !== index)\n }, [orderedData])\n\n const onDragEnd = useCallback(\n (result: DropResult) => handleDragEnd(result, orderedData),\n [orderedData, handleDragEnd],\n )\n\n return (\n <DragDropContext onDragStart={handleDragStart} onDragEnd={onDragEnd}>\n <Droppable droppableId=\"documentSortZone\">\n {(provided) => (\n <div {...provided.droppableProps} ref={provided.innerRef}>\n {orderedData.map((item, index) => (\n <Draggable\n key={`${item._id}-${item[ORDER_FIELD_NAME]}`}\n draggableId={item._id}\n index={index}\n // onClick={(event) => handleDraggableClick(event, provided, snapshot)}\n >\n {(innerProvided, innerSnapshot) => {\n const isSelected = selectedIds.includes(item._id)\n const isDragging = innerSnapshot.isDragging\n const isGhosting = Boolean(!isDragging && draggingId && isSelected)\n const isUpdating = listIsUpdating && isSelected\n const isDisabled = Boolean(!item[ORDER_FIELD_NAME])\n const isDuplicate = duplicateOrders.includes(item[ORDER_FIELD_NAME])\n const tone = cardTone({isDuplicate, isGhosting, isDragging, isSelected})\n const selectedCount = selectedIds.length\n\n const dragBadge = isDragging && selectedCount > 1 ? selectedCount : false\n\n return (\n <div\n ref={innerProvided.innerRef}\n {...innerProvided.draggableProps}\n {...innerProvided.dragHandleProps}\n style={\n isDisabled\n ? {opacity: 0.2, pointerEvents: 'none'}\n : getItemStyle(innerProvided.draggableProps.style, isUpdating)\n }\n >\n <Box paddingBottom={1}>\n <Card\n tone={tone}\n shadow={isDragging ? 2 : undefined}\n radius={2}\n // eslint-disable-next-line react/jsx-no-bind\n onClick={(e) => handleSelect(item._id, index, e.nativeEvent)}\n >\n <Document\n doc={item}\n entities={orderedData}\n increment={incrementIndex}\n index={index}\n isFirst={index === 0}\n isLast={index === orderedData.length - 1}\n dragBadge={dragBadge}\n />\n </Card>\n </Box>\n </div>\n )\n }}\n </Draggable>\n ))}\n {provided.placeholder}\n </div>\n )}\n </Droppable>\n </DragDropContext>\n )\n}\n","import {ORDER_FIELD_NAME} from './constants'\n\nexport interface DocumentListQueryProps {\n type: string\n filter?: string\n params?: Record<string, unknown>\n currentVersion?: string\n}\n\nexport interface DocumentListQueryResult {\n query: string\n queryParams: Record<string, string | number | boolean | string[]>\n}\n\nconst DEFAULT_PARAMS = {}\n\nexport function getDocumentQuery({\n type,\n filter,\n params = DEFAULT_PARAMS,\n currentVersion,\n}: DocumentListQueryProps): DocumentListQueryResult {\n let perspectiveFilter = null\n if (currentVersion === 'published') {\n perspectiveFilter = '!(_id in path(\"drafts.**\")) && !(_id in path(\"versions.**\"))'\n } else if (currentVersion === 'drafts') {\n // Show drafts, and published when no draft exists\n perspectiveFilter = `\n (_id in path(\"drafts.**\") || (!(_id in path(\"drafts.**\")) && !(_id in path(\"versions.**\"))))\n `\n } else {\n // Default behavior: prioritize drafts over published when both exist\n // the priority should be a version\n perspectiveFilter = `(sanity::partOfRelease($currentVersion) || (!(_id in path(\"drafts.**\")) && !(_id in path(\"versions.**\"))) || (_id in path(\"drafts.**\")))`\n }\n\n const querySelect = `*[_type == $type ${perspectiveFilter ? `&& ${perspectiveFilter}` : ''}${filter ? `&& ${filter}` : ''}]`\n const queryOrder = `|order(@[$order] asc)`\n const queryFields = `{_id, _type, ${ORDER_FIELD_NAME}}`\n\n const query = `${querySelect}${queryOrder}${queryFields}`\n const queryParams = {\n ...params,\n type,\n order: ORDER_FIELD_NAME,\n ...(currentVersion && {currentVersion}),\n }\n\n return {query, queryParams}\n}\n","import {getPublishedId, getVersionFromId, isDraftId, isPublishedId, isVersionId} from 'sanity'\nimport {type SanityDocumentWithOrder} from '../types'\n\nconst isVersionForCurrentPerspective = (\n document: SanityDocumentWithOrder,\n perspectiveName: string,\n publishedId: string,\n) => {\n return (\n document._id &&\n isVersionId(document._id) &&\n getVersionFromId(document._id) === perspectiveName &&\n getPublishedId(document._id) === publishedId\n )\n}\n\n// this removes dedupped docs from the list and makes sure that it keeps the right docs\n// in the list while preserving the order established by the orderRank field\nexport const getFilteredDedupedDocs = (\n documents: SanityDocumentWithOrder[],\n perspectiveName?: string,\n): SanityDocumentWithOrder[] => {\n // Flatten the documents array in case it's nested (like in test data)\n const flatDocuments = documents.flat()\n\n return flatDocuments.reduce<SanityDocumentWithOrder[]>((acc, cur) => {\n if (!cur._id) {\n return acc\n }\n\n // Handle version-only documents\n if (isVersionId(cur._id)) {\n const versionFromId = getVersionFromId(cur._id)\n const isCorrectVersion = versionFromId === perspectiveName\n\n // Only include versions that match the current perspective\n if (\n perspectiveName &&\n perspectiveName !== 'drafts' &&\n perspectiveName !== 'published' &&\n isCorrectVersion\n ) {\n return [...acc, cur]\n }\n return acc\n }\n\n // Handle published perspective - only include published documents\n if (perspectiveName === 'published') {\n if (isPublishedId(cur._id)) {\n return [...acc, cur]\n }\n return acc\n }\n\n // in situations where the document is not a draft, we need to check if\n // the version should override a published document or a draft\n if (!isDraftId(cur._id)) {\n const publishedId = getPublishedId(cur._id)\n\n const countNrPublished = JSON.stringify(flatDocuments).match(`/${publishedId}/g`)\n\n // Check if there's a version that matches the perspectiveName\n const hasMatchingVersion =\n perspectiveName && perspectiveName !== 'drafts' && perspectiveName !== 'published'\n ? flatDocuments.some((doc) =>\n isVersionForCurrentPerspective(doc, perspectiveName, publishedId),\n )\n : false\n\n // Check if there's a draft\n const hasDraft = flatDocuments.some((doc) => doc._id === `drafts.${cur._id}`)\n\n // Priority: version > draft > published\n // If there's a matching version, skip published\n if (hasMatchingVersion) {\n return acc\n }\n\n // eslint-disable-next-line max-nested-callbacks\n const alsoHasDraft = hasDraft || countNrPublished\n return alsoHasDraft ? acc : [...acc, cur]\n }\n\n // For drafts, check if there's a version for this document in version perspective\n if (perspectiveName && perspectiveName !== 'drafts' && perspectiveName !== 'published') {\n const baseId = getPublishedId(cur._id)\n const hasVersion = flatDocuments.some((doc) =>\n isVersionForCurrentPerspective(doc, perspectiveName, baseId),\n )\n\n // If there's a version for this document, skip the draft\n if (hasVersion) {\n return acc\n }\n }\n\n // Check if the draft has a published version\n cur.hasPublished = flatDocuments.some((doc) => doc._id === cur._id.replace(`drafts.`, ``))\n\n return [...acc, cur]\n }, [])\n}\n","import {useEffect, useMemo, useState} from 'react'\nimport {Box, Flex, Container, Spinner, Stack, Text} from '@sanity/ui'\n\nimport {useListeningQuery, Feedback} from 'sanity-plugin-utils'\nimport {DraggableList} from './DraggableList'\nimport {ORDER_FIELD_NAME} from './helpers/constants'\nimport type {SanityDocumentWithOrder} from './types'\nimport {DocumentListQueryProps, getDocumentQuery} from './helpers/query'\nimport {getFilteredDedupedDocs} from './helpers/getFilteredDedupedDocs'\n\nexport function DocumentListQuery(props: DocumentListQueryProps) {\n const [listIsUpdating, setListIsUpdating] = useState(false)\n const [data, setData] = useState<SanityDocumentWithOrder[] | null>([])\n\n const {query, queryParams} = getDocumentQuery(props)\n\n const {\n data: _queryData,\n loading,\n error,\n } = useListeningQuery<SanityDocumentWithOrder[]>(query, {\n params: queryParams,\n initialValue: [],\n })\n // @ts-expect-error Should not be needed to \"cast\", but sanity-plugin-utils is not typed correctly\n const queryData: SanityDocumentWithOrder[] = _queryData\n\n useEffect(() => {\n if (queryData) {\n const filteredDocuments = getFilteredDedupedDocs(queryData, props.currentVersion)\n setData(filteredDocuments)\n } else {\n setData([])\n }\n }, [queryData])\n\n const unorderedDataCount = useMemo(\n () => (data?.length ? data.filter((doc) => !doc[ORDER_FIELD_NAME]).length : 0),\n [data],\n )\n\n if (loading) {\n return (\n <Flex style={{width: `100%`, height: `100%`}} align=\"center\" justify=\"center\">\n <Spinner />\n </Flex>\n )\n }\n\n if (error) {\n return (\n <Box padding={2}>\n <Feedback tone=\"critical\" title=\"There was an error\" description=\"Please try again later\" />\n </Box>\n )\n }\n\n if (!data || data?.length == 0)\n return (\n <Flex align=\"center\" direction=\"column\" height=\"fill\" justify=\"center\">\n <Container width={1}>\n <Box paddingX={4} paddingY={5}>\n <Text align=\"center\" muted>\n No documents of this type\n </Text>\n </Box>\n </Container>\n </Flex>\n )\n\n return (\n <Stack space={1} style={{overflow: `auto`, height: `100%`}}>\n <Box padding={2}>\n {unorderedDataCount > 0 && (\n <Box marginBottom={2}>\n <Feedback\n tone=\"caution\"\n description={\n <>\n {unorderedDataCount}/{data?.length} documents have no order. Select{' '}\n <strong>Reset Order</strong> from the menu above to fix.\n </>\n }\n />\n </Box>\n )}\n <DraggableList\n data={data}\n listIsUpdating={listIsUpdating}\n setListIsUpdating={setListIsUpdating}\n />\n </Box>\n </Stack>\n )\n}\n","import {useEffect, useMemo} from 'react'\nimport {useToast, Box, type ToastParams} from '@sanity/ui'\n\nimport {useSchema} from 'sanity'\nimport {Feedback} from 'sanity-plugin-utils'\nimport {DocumentListQuery} from './DocumentListQuery'\nimport {OrderableContext} from './OrderableContext'\n\nimport {ORDER_FIELD_NAME} from './helpers/constants'\n\nexport interface DocumentListWrapperProps {\n showIncrements: boolean\n type: string\n resetOrderTransaction: ToastParams\n // eslint-disable-next-line react/require-default-props\n filter?: string\n // eslint-disable-next-line react/require-default-props\n params?: Record<string, unknown>\n // eslint-disable-next-line react/require-default-props\n currentVersion?: string\n}\n\n// 1. Validate first that the schema has been configured for ordering\n// 2. Setup context for showIncrements\nexport function DocumentListWrapper({\n type,\n showIncrements,\n resetOrderTransaction,\n filter,\n params,\n currentVersion,\n}: DocumentListWrapperProps) {\n const toast = useToast()\n const schema = useSchema()\n\n useEffect(() => {\n if (resetOrderTransaction?.title && resetOrderTransaction?.status) {\n toast.push(resetOrderTransaction)\n }\n }, [resetOrderTransaction, toast])\n\n const schemaIsInvalid = useMemo(() => {\n // Option not passed\n if (!type) {\n return (\n <>\n No <code>type</code> was configured\n </>\n )\n }\n\n const typeSchema = schema.get(type)\n\n // Schema not found\n if (!typeSchema) {\n return (\n <>\n Schema <code>{type}</code> not found\n </>\n )\n }\n\n // Schema lacks an order field\n if (\n !('fields' in typeSchema) ||\n !typeSchema.fields.some((field) => field?.name === ORDER_FIELD_NAME)\n ) {\n return (\n <>\n Schema <code>{type}</code> must have an <code>{ORDER_FIELD_NAME}</code> field of type{' '}\n <code>string</code>\n </>\n )\n }\n\n // Schema's order field is not a string\n if (\n 'fields' in typeSchema &&\n typeSchema.fields.some(\n (field) => field?.name === ORDER_FIELD_NAME && field?.type?.name !== 'string',\n )\n ) {\n return (\n <>\n <code>{ORDER_FIELD_NAME}</code> field on Schema <code>{type}</code> must be{' '}\n <code>string</code> type\n </>\n )\n }\n\n return ''\n }, [type, schema])\n\n if (schemaIsInvalid) {\n return (\n <Box padding={2}>\n <Feedback description={schemaIsInvalid} tone=\"caution\" />\n </Box>\n )\n }\n\n return (\n <OrderableContext.Provider value={{showIncrements}}>\n <DocumentListQuery\n type={type}\n filter={filter}\n params={params}\n currentVersion={currentVersion}\n />\n </OrderableContext.Provider>\n )\n}\n","import {LexoRank} from 'lexorank'\nimport type {MultipleMutationResult, SanityClient} from '@sanity/client'\nimport {ORDER_FIELD_NAME} from './constants'\nimport {DocumentListQueryProps, getDocumentQuery} from './query'\n\nexport interface ResetOrderParams extends DocumentListQueryProps {\n client: SanityClient\n currentVersion?: string\n}\n\n// Function to wipe and re-do ordering with LexoRank\n// Will at least attempt to start with the current order\nexport async function resetOrder(params: ResetOrderParams): Promise<MultipleMutationResult | null> {\n const {client, currentVersion, ...queryProps} = params\n const {query, queryParams} = getDocumentQuery({...queryProps, currentVersion})\n\n const documents = await client.fetch<\n Array<{\n _id: string\n _type: string\n [ORDER_FIELD_NAME]: string\n }>\n >(query, queryParams, {\n tag: 'orderable-document-list.reset-order',\n })\n\n if (documents.length === 0) {\n return null\n }\n\n let aLexoRank = LexoRank.min()\n\n const transaction = documents\n .map((doc) => doc._id)\n .reduce((trx, documentId) => {\n // Generate next rank before even the first document so there's room to move!\n aLexoRank = aLexoRank.genNext().genNext()\n\n return trx.patch(documentId, {\n set: {[ORDER_FIELD_NAME]: aLexoRank.toString()},\n })\n }, client.transaction())\n\n return transaction.commit({\n visibility: 'async',\n tag: 'orderable-document-list.reset-order',\n })\n}\n","import {Component} from 'react'\n\nimport type {SanityClient} from '@sanity/client'\nimport type {ToastParams} from '@sanity/ui'\nimport {DocumentListWrapper} from './DocumentListWrapper'\nimport {resetOrder} from './helpers/resetOrder'\n\nexport interface OrderableDocumentListProps {\n options: {\n type: string\n client: SanityClient\n filter?: string\n params?: Record<string, unknown>\n currentVersion?: string\n }\n}\n\ninterface State {\n showIncrements: boolean\n resetOrderTransaction: ToastParams\n}\n\n// Must use a Class Component here so the actionHandlers can be called\nexport class OrderableDocumentList extends Component<OrderableDocumentListProps, State> {\n constructor(props: OrderableDocumentListProps) {\n super(props)\n this.state = {\n showIncrements: false,\n resetOrderTransaction: {},\n }\n }\n\n actionHandlers = {\n showIncrements: () => {\n this.setState((state) => ({\n showIncrements: !state.showIncrements,\n }))\n },\n\n resetOrder: async () => {\n this.setState(() => ({\n resetOrderTransaction: {\n status: `info`,\n title: `Reordering started...`,\n closable: true,\n },\n }))\n\n const update = await resetOrder(this.props.options)\n\n const reorderWasSuccessful = update?.results?.length\n\n this.setState(() => ({\n resetOrderTransaction: {\n status: reorderWasSuccessful ? `success` : `info`,\n title: reorderWasSuccessful\n ? `Reordered ${update.results.length === 1 ? `Document` : `Documents`}`\n : `Reordering failed`,\n closable: true,\n },\n }))\n },\n }\n\n render() {\n const type = this?.props?.options?.type\n if (!type) {\n return null\n }\n return (\n <DocumentListWrapper\n filter={this?.props?.options?.filter}\n params={this?.props?.options?.params}\n type={type}\n showIncrements={this.state.showIncrements}\n resetOrderTransaction={this.state.resetOrderTransaction}\n currentVersion={this?.props?.options?.currentVersion}\n />\n )\n }\n}\n","import {GenerateIcon, SortIcon} from '@sanity/icons'\nimport type {ConfigContext} from 'sanity'\n\nimport type {ComponentType} from 'react'\nimport {StructureBuilder, type ListItem, type MenuItem} from 'sanity/structure'\nimport {OrderableDocumentList} from '../OrderableDocumentList'\nimport {API_VERSION} from '../helpers/constants'\n\nexport interface OrderableListConfig {\n type: string\n id?: string\n title?: string\n icon?: ComponentType\n params?: Record<string, unknown>\n filter?: string\n menuItems?: MenuItem[]\n createIntent?: boolean\n context: ConfigContext\n S: StructureBuilder\n}\n\nexport function orderableDocumentListDeskItem(config: OrderableListConfig): ListItem {\n if (!config?.type || !config.context || !config.S) {\n throw new Error(`\n type, context and S (StructureBuilder) must be provided.\n context and S are available when configuring structure.\n Example: orderableDocumentListDeskItem({type: 'category'})\n `)\n }\n\n const {type, filter, menuItems = [], createIntent, params, title, icon, id, context, S} = config\n const {schema, getClient} = context\n // 'perspectiveStack' may not exist on ConfigContext in some versions\n const perspectiveStack = (context as any).perspectiveStack || []\n const client = getClient({apiVersion: API_VERSION})\n // the first position in the perspective stack is the current version\n const currentVersion = perspectiveStack[0]\n\n const listTitle = title ?? `Orderable ${type}`\n const listId = id ?? `orderable-${type}`\n const listIcon = icon ?? SortIcon\n const typeTitle = schema.get(type)?.title ?? type\n\n if (createIntent !== false) {\n menuItems.push(\n S.menuItem()\n .title(`Create new ${typeTitle}`)\n .intent({type: 'create', params: {type}})\n .serialize(),\n )\n }\n return S.listItem()\n .title(listTitle)\n .id(listId)\n .icon(listIcon)\n .schemaType(type)\n .child(\n Object.assign(\n S.documentTypeList(type)\n .canHandleIntent(() => !!createIntent)\n .serialize(),\n {\n // Prevents the component from re-rendering when switching documents\n __preserveInstance: true,\n // Prevents the component from NOT re-rendering when switching listItems\n key: listId,\n\n type: 'component',\n component: OrderableDocumentList,\n options: {type, filter, params, client, currentVersion},\n menuItems: [\n ...menuItems,\n S.menuItem().title(`Reset Order`).icon(GenerateIcon).action(`resetOrder`).serialize(),\n S.menuItem()\n .title(`Toggle Increments`)\n .icon(SortIcon)\n .action(`showIncrements`)\n .serialize(),\n ],\n },\n ),\n )\n .serialize()\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/orderable-document-list",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "Drag-and-drop Document Ordering without leaving the Editing surface",
5
5
  "keywords": [
6
6
  "sanity",
@@ -47,7 +47,8 @@
47
47
  "lint:fix": "eslint . --fix",
48
48
  "prepare": "husky install",
49
49
  "prepublishOnly": "npm run build",
50
- "watch": "pkg-utils watch --strict"
50
+ "watch": "pkg-utils watch --strict",
51
+ "test": "vitest"
51
52
  },
52
53
  "husky": {
53
54
  "hooks": {
@@ -61,13 +62,14 @@
61
62
  "@sanity/incompatible-plugin": "^1.0.5",
62
63
  "@sanity/ui": "^2.10.11",
63
64
  "lexorank": "^1.0.4",
64
- "sanity-plugin-utils": "^1.6.7"
65
+ "sanity-plugin-utils": "^1.6.8"
65
66
  },
66
67
  "devDependencies": {
67
68
  "@commitlint/cli": "^19.6.1",
68
69
  "@commitlint/config-conventional": "^19.6.0",
69
70
  "@sanity/pkg-utils": "^6.12.0",
70
71
  "@sanity/plugin-kit": "^4.0.18",
72
+ "@sanity/vision": "^4.1.0",
71
73
  "@sanity/semantic-release-preset": "^5.0.0",
72
74
  "@types/react": "^19",
73
75
  "@typescript-eslint/eslint-plugin": "^5.61.0",
@@ -87,15 +89,16 @@
87
89
  "react-dom": "19",
88
90
  "react-is": "^19",
89
91
  "rimraf": "^6.0.1",
90
- "sanity": "^3.74.1",
92
+ "sanity": "^4.1.0",
91
93
  "semantic-release": "^24.2.0",
92
94
  "styled-components": "^6.1.15",
93
- "typescript": "^5.1.6"
95
+ "typescript": "^5.1.6",
96
+ "vitest": "^3.2.4"
94
97
  },
95
98
  "peerDependencies": {
96
99
  "react": "^18 || ^19",
97
100
  "react-dom": "^18 || ^19",
98
- "sanity": "^3.0.0",
101
+ "sanity": "^3.77.0 || ^4.0.0-0",
99
102
  "styled-components": "^6.1"
100
103
  },
101
104
  "overrides": {
package/src/Document.tsx CHANGED
@@ -1,7 +1,15 @@
1
1
  import {useContext, useMemo, type ReactNode} from 'react'
2
2
  import {ChevronDownIcon, ChevronUpIcon, DragHandleIcon} from '@sanity/icons'
3
- import {AvatarCounter, Card, Box, Button, Flex, Text} from '@sanity/ui'
4
- import {useSchema, SchemaType, PreviewCard, Preview} from 'sanity'
3
+ import {AvatarCounter, Card, Box, Button, Flex, Text, Tooltip} from '@sanity/ui'
4
+ import {
5
+ useSchema,
6
+ SchemaType,
7
+ PreviewCard,
8
+ Preview,
9
+ DocumentStatusIndicator,
10
+ DocumentStatus,
11
+ useDocumentVersionInfo,
12
+ } from 'sanity'
5
13
  import {usePaneRouter} from 'sanity/structure'
6
14
 
7
15
  import {OrderableContext} from './OrderableContext'
@@ -34,6 +42,8 @@ export function Document({
34
42
  const {showIncrements} = useContext(OrderableContext)
35
43
  const schema = useSchema()
36
44
  const router = usePaneRouter()
45
+ const versionsInfo = useDocumentVersionInfo(doc._id)
46
+
37
47
  const {ChildLink, groupIndex, routerPanesState} = router
38
48
 
39
49
  const currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || false
@@ -48,6 +58,14 @@ export function Document({
48
58
  [ChildLink, doc._id],
49
59
  )
50
60
 
61
+ const tooltip = (
62
+ <DocumentStatus
63
+ draft={versionsInfo.draft}
64
+ published={versionsInfo.published}
65
+ versions={versionsInfo.versions}
66
+ />
67
+ )
68
+
51
69
  return (
52
70
  <PreviewCard
53
71
  __unstable_focusRing
@@ -91,12 +109,22 @@ export function Document({
91
109
  </Flex>
92
110
  )}
93
111
  <Box style={{width: `100%`}}>
94
- <Flex flex={1} align="center">
112
+ <Flex flex={1} align="center" justify="space-between" paddingRight={3}>
95
113
  <Preview
96
114
  layout="default"
97
115
  value={doc}
98
116
  schemaType={schema.get(doc._type) as SchemaType}
99
117
  />
118
+
119
+ <Tooltip content={tooltip} portal placement="right" boundaryElement={null}>
120
+ <Flex align="center">
121
+ <DocumentStatusIndicator
122
+ draft={versionsInfo.draft}
123
+ published={versionsInfo.published}
124
+ versions={versionsInfo.versions}
125
+ />
126
+ </Flex>
127
+ </Tooltip>
100
128
  </Flex>
101
129
  </Box>
102
130
  {dragBadge && (
@@ -6,6 +6,7 @@ import {DraggableList} from './DraggableList'
6
6
  import {ORDER_FIELD_NAME} from './helpers/constants'
7
7
  import type {SanityDocumentWithOrder} from './types'
8
8
  import {DocumentListQueryProps, getDocumentQuery} from './helpers/query'
9
+ import {getFilteredDedupedDocs} from './helpers/getFilteredDedupedDocs'
9
10
 
10
11
  export function DocumentListQuery(props: DocumentListQueryProps) {
11
12
  const [listIsUpdating, setListIsUpdating] = useState(false)
@@ -26,19 +27,7 @@ export function DocumentListQuery(props: DocumentListQueryProps) {
26
27
 
27
28
  useEffect(() => {
28
29
  if (queryData) {
29
- const filteredDocuments = queryData.reduce<SanityDocumentWithOrder[]>((acc, cur) => {
30
- if (!cur._id.startsWith(`drafts.`)) {
31
- // eslint-disable-next-line max-nested-callbacks
32
- const alsoHasDraft = queryData.some((doc) => doc._id === `drafts.${cur._id}`)
33
- return alsoHasDraft ? acc : [...acc, cur]
34
- }
35
-
36
- // Check if the draft has a published version
37
- cur.hasPublished = queryData.some((doc) => doc._id === cur._id.replace(`drafts.`, ``))
38
-
39
- return [...acc, cur]
40
- }, [])
41
-
30
+ const filteredDocuments = getFilteredDedupedDocs(queryData, props.currentVersion)
42
31
  setData(filteredDocuments)
43
32
  } else {
44
33
  setData([])