sanity-plugin-workflow 1.0.0-beta.4 → 1.0.0-beta.5

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/README.md CHANGED
@@ -71,10 +71,13 @@ Documents can be promoted and demoted in the Workflow with the provided Document
71
71
  // Optional settings:
72
72
  // Used for the color of the Document Badge
73
73
  color: 'success',
74
- // Will enable document actions and drag-and-drop for only users with these Role
74
+ // Will limit document actions and drag-and-drop for only users with these Role
75
75
  roles: ['publisher', 'administrator'],
76
76
  // Requires the user to be "assigned" in order to update to this State
77
77
  requireAssignment: true,
78
+ // Requires the document to be valid before being promoted out of this State
79
+ // Warning: With many documents in the Kanban view this can negatively impact performance
80
+ requireValidation: true,
78
81
  // Defines which States a document can be moved to from this one
79
82
  transitions: ['changesRequested', 'approved']
80
83
  }
package/lib/index.d.ts CHANGED
@@ -6,6 +6,7 @@ declare type State = {
6
6
  title: string
7
7
  roles?: string[]
8
8
  requireAssignment?: boolean
9
+ requireValidation?: boolean
9
10
  color?: 'primary' | 'success' | 'warning' | 'danger'
10
11
  }
11
12
 
package/lib/index.esm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  var _templateObject, _templateObject2, _templateObject3;
2
2
  function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
3
- import { useClient, useValidationStatus, useCurrentUser, useSchema, Preview, useFormValue, defineType, defineField, UserAvatar, useTimeAgo, TextWithTone, definePlugin } from 'sanity';
3
+ import { useClient, useCurrentUser, useValidationStatus, useSchema, Preview, useFormValue, defineType, defineField, UserAvatar, useTimeAgo, TextWithTone, definePlugin } from 'sanity';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { UsersIcon, SplitVerticalIcon, CheckmarkIcon, ArrowRightIcon, ArrowLeftIcon, EditIcon, AddIcon, PublishIcon, ErrorOutlineIcon, WarningOutlineIcon, DragHandleIcon, UserIcon, ResetIcon, InfoOutlineIcon } from '@sanity/icons';
6
- import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
6
+ import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
7
7
  import { UserSelectMenu, useListeningQuery, useProjectUsers, Feedback } from 'sanity-plugin-utils';
8
8
  import { useToast, Button, Spinner, Card, Flex, Box, Text, useClickOutside, Popover, Grid, Tooltip, useTheme, Stack, MenuButton, Menu, Badge, Container } from '@sanity/ui';
9
9
  import { LexoRank } from 'lexorank';
@@ -36,8 +36,8 @@ const DEFAULT_CONFIG = {
36
36
  title: "Approved",
37
37
  color: "success",
38
38
  roles: ["administrator"],
39
- requireAssignment: true,
40
- transitions: ["changesRequested"]
39
+ transitions: ["changesRequested"],
40
+ requireAssignment: true
41
41
  }])
42
42
  };
43
43
  function UserAssignment(props) {
@@ -291,11 +291,6 @@ function UpdateWorkflow(props, allStates, actionState) {
291
291
  id,
292
292
  type
293
293
  } = props;
294
- const {
295
- validation,
296
- isValidating
297
- } = useValidationStatus(id, type);
298
- const hasValidationErrors = !isValidating && (validation == null ? void 0 : validation.length) > 0 && validation.find(v => v.level === "error");
299
294
  const user = useCurrentUser();
300
295
  const client = useClient({
301
296
  apiVersion: API_VERSION
@@ -313,6 +308,11 @@ function UpdateWorkflow(props, allStates, actionState) {
313
308
  const {
314
309
  assignees = []
315
310
  } = (_a = data == null ? void 0 : data.metadata) != null ? _a : {};
311
+ const {
312
+ validation,
313
+ isValidating
314
+ } = useValidationStatus(id, type);
315
+ const hasValidationErrors = (currentState == null ? void 0 : currentState.requireValidation) && !isValidating && (validation == null ? void 0 : validation.length) > 0 && validation.find(v => v.level === "error");
316
316
  if (error) {
317
317
  console.error(error);
318
318
  }
@@ -342,40 +342,39 @@ function UpdateWorkflow(props, allStates, actionState) {
342
342
  const direction = actionStateIndex > currentStateIndex ? "promote" : "demote";
343
343
  const DirectionIcon = direction === "promote" ? ArrowRightIcon : ArrowLeftIcon;
344
344
  const directionLabel = direction === "promote" ? "Promote" : "Demote";
345
- let title = "".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
346
345
  const userRoleCanUpdateState = ((_b = user == null ? void 0 : user.roles) == null ? void 0 : _b.length) && ((_c = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _c.length) ?
347
346
  // If the Action state is limited to specific roles
348
347
  // check that the current user has one of those roles
349
348
  arraysContainMatchingString(user.roles.map(r => r.name), actionState.roles) :
350
349
  // No roles specified on the next state, so anyone can update
351
350
  ((_d = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _d.length) !== 0;
352
- if (!userRoleCanUpdateState) {
353
- title = "Your User role cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
354
- }
355
351
  const actionStateIsAValidTransition = (currentState == null ? void 0 : currentState.id) && currentState.transitions.length ?
356
352
  // If the Current State limits transitions to specific States
357
353
  // Check that the Action State is in Current State's transitions array
358
354
  currentState.transitions.includes(actionState.id) :
359
355
  // Otherwise this isn't a problem
360
356
  true;
361
- if (!actionStateIsAValidTransition) {
362
- title = "You cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\" from \"").concat(currentState == null ? void 0 : currentState.title, "\"");
363
- }
364
357
  const userAssignmentCanUpdateState = actionState.requireAssignment ?
365
358
  // If the Action State requires assigned users
366
359
  // Check the current user ID is in the assignees array
367
360
  currentUser && assignees.length && assignees.includes(currentUser.id) :
368
361
  // Otherwise this isn't a problem
369
362
  true;
370
- if (!userAssignmentCanUpdateState) {
363
+ let title = "".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
364
+ if (!userRoleCanUpdateState) {
365
+ title = "Your User role cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
366
+ } else if (!actionStateIsAValidTransition) {
367
+ title = "You cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\" from \"").concat(currentState == null ? void 0 : currentState.title, "\"");
368
+ } else if (!userAssignmentCanUpdateState) {
371
369
  title = "You must be assigned to the document to ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
372
- }
373
- if (hasValidationErrors) {
370
+ } else if ((currentState == null ? void 0 : currentState.requireValidation) && isValidating) {
371
+ title = "Document is validating, cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
372
+ } else if (hasValidationErrors) {
374
373
  title = "Document has validation errors, cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
375
374
  }
376
375
  return {
377
376
  icon: DirectionIcon,
378
- disabled: loading || error || isValidating || hasValidationErrors || !currentState || !userRoleCanUpdateState || !actionStateIsAValidTransition || !userAssignmentCanUpdateState,
377
+ disabled: loading || error || (currentState == null ? void 0 : currentState.requireValidation) && isValidating || hasValidationErrors || !currentState || !userRoleCanUpdateState || !actionStateIsAValidTransition || !userAssignmentCanUpdateState,
379
378
  title,
380
379
  label: actionState.title,
381
380
  onHandle: () => onHandle(id, actionState)
@@ -908,6 +907,24 @@ function PublishedStatus(props) {
908
907
  })
909
908
  });
910
909
  }
910
+ function Validate(props) {
911
+ const {
912
+ documentId,
913
+ type,
914
+ onChange
915
+ } = props;
916
+ const {
917
+ isValidating,
918
+ validation = []
919
+ } = useValidationStatus(documentId, type);
920
+ useEffect(() => {
921
+ onChange({
922
+ isValidating,
923
+ validation
924
+ });
925
+ }, [onChange, isValidating, validation]);
926
+ return null;
927
+ }
911
928
  function ValidationStatus(props) {
912
929
  const {
913
930
  validation = []
@@ -933,7 +950,7 @@ function ValidationStatus(props) {
933
950
  });
934
951
  }
935
952
  function DocumentCard(props) {
936
- var _a;
953
+ var _a, _b;
937
954
  const {
938
955
  isDragDisabled,
939
956
  userRoleCanDrop,
@@ -948,18 +965,29 @@ function DocumentCard(props) {
948
965
  documentId
949
966
  } = (_a = item._metadata) != null ? _a : {};
950
967
  const schema = useSchema();
968
+ const state = states.find(s => {
969
+ var _a2;
970
+ return s.id === ((_a2 = item._metadata) == null ? void 0 : _a2.state);
971
+ });
951
972
  const isDarkMode = useTheme().sanity.color.dark;
952
973
  const defaultCardTone = isDarkMode ? "transparent" : "default";
974
+ const [optimisticValidation, setOptimisticValidation] = useState({
975
+ isValidating: (_b = state == null ? void 0 : state.requireValidation) != null ? _b : false,
976
+ validation: []
977
+ });
953
978
  const {
954
- validation = [],
955
- isValidating
956
- } = useValidationStatus(documentId != null ? documentId : "", item._type);
979
+ isValidating,
980
+ validation
981
+ } = optimisticValidation;
982
+ const handleValidation = useCallback(updates => {
983
+ setOptimisticValidation(updates);
984
+ }, []);
957
985
  const cardTone = useMemo(() => {
958
986
  let tone = defaultCardTone;
959
987
  if (!userRoleCanDrop) return isDarkMode ? "default" : "transparent";
960
988
  if (!documentId) return tone;
961
989
  if (isDragging) tone = "positive";
962
- if (!isValidating && validation.length > 0) {
990
+ if ((state == null ? void 0 : state.requireValidation) && !isValidating && validation.length > 0) {
963
991
  if (validation.some(v => v.level === "error")) {
964
992
  tone = "critical";
965
993
  } else {
@@ -967,7 +995,7 @@ function DocumentCard(props) {
967
995
  }
968
996
  }
969
997
  return tone;
970
- }, [isDarkMode, userRoleCanDrop, defaultCardTone, documentId, isDragging, validation, isValidating]);
998
+ }, [defaultCardTone, userRoleCanDrop, isDarkMode, documentId, isDragging, isValidating, validation, state == null ? void 0 : state.requireValidation]);
971
999
  useEffect(() => {
972
1000
  if (!isValidating && validation.length > 0) {
973
1001
  if (validation.some(v => v.level === "error")) {
@@ -984,75 +1012,81 @@ function DocumentCard(props) {
984
1012
  var _a2;
985
1013
  return states[states.length - 1].id === ((_a2 = item._metadata) == null ? void 0 : _a2.state);
986
1014
  }, [states, item._metadata.state]);
987
- return /* @__PURE__ */jsx(Box, {
988
- paddingBottom: 3,
989
- paddingX: 3,
990
- children: /* @__PURE__ */jsx(Card, {
991
- radius: 2,
992
- shadow: isDragging ? 3 : 1,
993
- tone: cardTone,
994
- children: /* @__PURE__ */jsxs(Stack, {
995
- children: [/* @__PURE__ */jsx(Card, {
996
- borderBottom: true,
997
- radius: 2,
998
- padding: 3,
999
- paddingLeft: 2,
1000
- tone: cardTone,
1001
- style: {
1002
- pointerEvents: "none"
1003
- },
1004
- children: /* @__PURE__ */jsxs(Flex, {
1005
- align: "center",
1006
- justify: "space-between",
1007
- gap: 1,
1008
- children: [/* @__PURE__ */jsx(Box, {
1009
- flex: 1,
1010
- children: /* @__PURE__ */jsx(Preview, {
1011
- layout: "default",
1012
- value: item,
1013
- schemaType: schema.get(item._type)
1014
- })
1015
- }), /* @__PURE__ */jsx(Box, {
1016
- style: {
1017
- flexShrink: 0
1018
- },
1019
- children: hasError || isDragDisabled ? null : /* @__PURE__ */jsx(DragHandleIcon, {})
1020
- })]
1021
- })
1022
- }), /* @__PURE__ */jsx(Card, {
1023
- padding: 2,
1024
- radius: 2,
1025
- tone: "inherit",
1026
- children: /* @__PURE__ */jsxs(Flex, {
1027
- align: "center",
1028
- justify: "space-between",
1029
- gap: 3,
1030
- children: [/* @__PURE__ */jsx(Box, {
1031
- flex: 1,
1032
- children: documentId && /* @__PURE__ */jsx(UserDisplay, {
1033
- userList,
1034
- assignees,
1015
+ return /* @__PURE__ */jsxs(Fragment, {
1016
+ children: [(state == null ? void 0 : state.requireValidation) ? /* @__PURE__ */jsx(Validate, {
1017
+ documentId,
1018
+ type: item._type,
1019
+ onChange: handleValidation
1020
+ }) : null, /* @__PURE__ */jsx(Box, {
1021
+ paddingBottom: 3,
1022
+ paddingX: 3,
1023
+ children: /* @__PURE__ */jsx(Card, {
1024
+ radius: 2,
1025
+ shadow: isDragging ? 3 : 1,
1026
+ tone: cardTone,
1027
+ children: /* @__PURE__ */jsxs(Stack, {
1028
+ children: [/* @__PURE__ */jsx(Card, {
1029
+ borderBottom: true,
1030
+ radius: 2,
1031
+ padding: 3,
1032
+ paddingLeft: 2,
1033
+ tone: cardTone,
1034
+ style: {
1035
+ pointerEvents: "none"
1036
+ },
1037
+ children: /* @__PURE__ */jsxs(Flex, {
1038
+ align: "center",
1039
+ justify: "space-between",
1040
+ gap: 1,
1041
+ children: [/* @__PURE__ */jsx(Box, {
1042
+ flex: 1,
1043
+ children: /* @__PURE__ */jsx(Preview, {
1044
+ layout: "default",
1045
+ value: item,
1046
+ schemaType: schema.get(item._type)
1047
+ })
1048
+ }), /* @__PURE__ */jsx(Box, {
1049
+ style: {
1050
+ flexShrink: 0
1051
+ },
1052
+ children: hasError || isDragDisabled ? null : /* @__PURE__ */jsx(DragHandleIcon, {})
1053
+ })]
1054
+ })
1055
+ }), /* @__PURE__ */jsx(Card, {
1056
+ padding: 2,
1057
+ radius: 2,
1058
+ tone: "inherit",
1059
+ children: /* @__PURE__ */jsxs(Flex, {
1060
+ align: "center",
1061
+ justify: "space-between",
1062
+ gap: 3,
1063
+ children: [/* @__PURE__ */jsx(Box, {
1064
+ flex: 1,
1065
+ children: documentId && /* @__PURE__ */jsx(UserDisplay, {
1066
+ userList,
1067
+ assignees,
1068
+ documentId,
1069
+ disabled: !userRoleCanDrop
1070
+ })
1071
+ }), validation.length > 0 ? /* @__PURE__ */jsx(ValidationStatus, {
1072
+ validation
1073
+ }) : null, /* @__PURE__ */jsx(DraftStatus, {
1074
+ document: item
1075
+ }), /* @__PURE__ */jsx(PublishedStatus, {
1076
+ document: item
1077
+ }), /* @__PURE__ */jsx(EditButton, {
1078
+ id: item._id,
1079
+ type: item._type,
1080
+ disabled: !userRoleCanDrop
1081
+ }), isLastState ? /* @__PURE__ */jsx(CompleteButton, {
1035
1082
  documentId,
1036
1083
  disabled: !userRoleCanDrop
1037
- })
1038
- }), validation.length > 0 ? /* @__PURE__ */jsx(ValidationStatus, {
1039
- validation
1040
- }) : null, /* @__PURE__ */jsx(DraftStatus, {
1041
- document: item
1042
- }), /* @__PURE__ */jsx(PublishedStatus, {
1043
- document: item
1044
- }), /* @__PURE__ */jsx(EditButton, {
1045
- id: item._id,
1046
- type: item._type,
1047
- disabled: !userRoleCanDrop
1048
- }), isLastState ? /* @__PURE__ */jsx(CompleteButton, {
1049
- documentId,
1050
- disabled: !userRoleCanDrop
1051
- }) : null]
1052
- })
1053
- })]
1084
+ }) : null]
1085
+ })
1086
+ })]
1087
+ })
1054
1088
  })
1055
- })
1089
+ })]
1056
1090
  });
1057
1091
  }
1058
1092
  function DocumentList(props) {
@@ -1351,12 +1385,12 @@ function FloatingCard(_ref3) {
1351
1385
  }, "floater") : null
1352
1386
  });
1353
1387
  }
1354
- function Validators(_ref4) {
1355
- let {
1388
+ function Verify(props) {
1389
+ const {
1356
1390
  data,
1357
1391
  userList,
1358
1392
  states
1359
- } = _ref4;
1393
+ } = props;
1360
1394
  const client = useClient({
1361
1395
  apiVersion: API_VERSION
1362
1396
  });
@@ -1587,10 +1621,10 @@ function WorkflowTool(props) {
1587
1621
  } else {
1588
1622
  const itemBefore = destinationStateItems[destination.index - 1];
1589
1623
  const itemBeforeRank = (_e = itemBefore == null ? void 0 : itemBefore._metadata) == null ? void 0 : _e.orderRank;
1590
- const itemBeforeRankParsed = itemBefore._metadata.orderRank ? LexoRank.parse(itemBeforeRank) : LexoRank.min();
1624
+ const itemBeforeRankParsed = itemBeforeRank ? LexoRank.parse(itemBeforeRank) : LexoRank.min();
1591
1625
  const itemAfter = destinationStateItems[destination.index];
1592
1626
  const itemAfterRank = (_f = itemAfter == null ? void 0 : itemAfter._metadata) == null ? void 0 : _f.orderRank;
1593
- const itemAfterRankParsed = itemAfter._metadata.orderRank ? LexoRank.parse(itemAfterRank) : LexoRank.max();
1627
+ const itemAfterRankParsed = itemAfterRank ? LexoRank.parse(itemAfterRank) : LexoRank.max();
1594
1628
  newOrder = itemBeforeRankParsed.between(itemAfterRankParsed).toString();
1595
1629
  }
1596
1630
  move(draggableId, destination, states, newOrder);
@@ -1646,7 +1680,7 @@ function WorkflowTool(props) {
1646
1680
  direction: "column",
1647
1681
  height: "fill",
1648
1682
  overflow: "hidden",
1649
- children: [/* @__PURE__ */jsx(Validators, {
1683
+ children: [/* @__PURE__ */jsx(Verify, {
1650
1684
  data,
1651
1685
  userList,
1652
1686
  states