tinacms 3.7.2 → 3.7.4

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/dist/index.js CHANGED
@@ -33939,8 +33939,12 @@ const MermaidElementWithRef = ({ config }) => {
33939
33939
  const mermaidRef = useRef(null);
33940
33940
  useEffect(() => {
33941
33941
  if (mermaidRef.current) {
33942
- mermaid.initialize({ startOnLoad: true });
33943
- mermaid.init();
33942
+ const renderMermaid = async () => {
33943
+ await mermaid.run({
33944
+ nodes: [mermaidRef.current.querySelector(".mermaid")]
33945
+ });
33946
+ };
33947
+ renderMermaid();
33944
33948
  }
33945
33949
  }, [config]);
33946
33950
  return /* @__PURE__ */ React__default.createElement("div", { contentEditable: false, className: "border-border border-b pt-10" }, /* @__PURE__ */ React__default.createElement("div", { ref: mermaidRef }, /* @__PURE__ */ React__default.createElement("pre", { className: "mermaid not-tina-prose" }, config)));
@@ -33973,15 +33977,28 @@ const CodeBlockElement = withRef$1(
33973
33977
  if (element.lang !== "mermaid") {
33974
33978
  return;
33975
33979
  }
33976
- if (mermaid.parse(codeLineToString(element))) {
33977
- setCodeBlockError(null);
33978
- }
33979
- }, [element.children]);
33980
- mermaid.parseError = (err) => {
33981
- setCodeBlockError(
33982
- String(err.message) || "An error occurred while parsing the diagram."
33983
- );
33984
- };
33980
+ let isCancelled = false;
33981
+ const validateMermaid = async () => {
33982
+ try {
33983
+ await mermaid.parse(
33984
+ codeLineToString(element)
33985
+ );
33986
+ if (!isCancelled) {
33987
+ setCodeBlockError(null);
33988
+ }
33989
+ } catch (err) {
33990
+ if (!isCancelled) {
33991
+ setCodeBlockError(
33992
+ String(err.message) || "An error occurred while parsing the diagram."
33993
+ );
33994
+ }
33995
+ }
33996
+ };
33997
+ validateMermaid();
33998
+ return () => {
33999
+ isCancelled = true;
34000
+ };
34001
+ }, [element.children, element.lang]);
33985
34002
  return /* @__PURE__ */ React__default.createElement(
33986
34003
  PlateElement,
33987
34004
  {
@@ -38289,29 +38306,27 @@ class Form {
38289
38306
  const templateName = namePath.slice(0, currentPathIndex + 2).join(".");
38290
38307
  if ((item == null ? void 0 : item.type) === "img") {
38291
38308
  const imageName = namePath.slice(0, currentPathIndex + 2).join(".");
38309
+ const imageBaseName = templateName.replace(/\.props$/, "");
38292
38310
  return {
38293
- ...formOrObjectField,
38294
- // name: [formOrObjectField.name, 'img'].join('.'),
38311
+ label: "Image",
38295
38312
  name: [imageName].join("."),
38296
38313
  fields: [
38297
38314
  {
38298
38315
  type: "image",
38299
- // label: 'URL',
38300
- name: [templateName, "url"].join("."),
38316
+ label: "URL",
38317
+ name: [imageBaseName, "url"].join("."),
38301
38318
  component: "image"
38302
38319
  },
38303
38320
  {
38304
38321
  type: "string",
38305
38322
  label: "Alt",
38306
- name: [templateName.replace(/\.props$/, ""), "alt"].join("."),
38323
+ name: [imageBaseName, "alt"].join("."),
38307
38324
  component: "text"
38308
38325
  },
38309
38326
  {
38310
38327
  type: "string",
38311
38328
  label: "Caption",
38312
- name: [templateName.replace(/\.props$/, ""), "caption"].join(
38313
- "."
38314
- ),
38329
+ name: [imageBaseName, "caption"].join("."),
38315
38330
  component: "text"
38316
38331
  }
38317
38332
  ]
@@ -39571,12 +39586,6 @@ const ResetModal = ({ close: close2, reset: reset2 }) => {
39571
39586
  "Reset"
39572
39587
  ))));
39573
39588
  };
39574
- function AiFillWarning(props) {
39575
- return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 1024 1024" }, "child": [{ "tag": "path", "attr": { "d": "M955.7 856l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zM480 416c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v184c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V416zm32 352a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z" }, "child": [] }] })(props);
39576
- }
39577
- function AiOutlineLoading(props) {
39578
- return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 1024 1024" }, "child": [{ "tag": "path", "attr": { "d": "M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z" }, "child": [] }] })(props);
39579
- }
39580
39589
  const textFieldClasses = "shadow-inner focus:shadow-outline focus:border-blue-500 focus:outline-none block text-base placeholder:text-gray-300 px-3 py-2 text-gray-600 w-full bg-white border border-gray-200 transition-all ease-out duration-150 focus:text-gray-900 rounded";
39581
39590
  const disabledClasses$1 = "opacity-50 pointer-events-none cursor-not-allowed";
39582
39591
  const BaseTextField = React.forwardRef(({ className, disabled, ...rest }, ref) => {
@@ -40226,19 +40235,19 @@ const RadioOption = ({ checked, htmlFor, children, ...props }) => /* @__PURE__ *
40226
40235
  /* @__PURE__ */ React.createElement(
40227
40236
  "span",
40228
40237
  {
40229
- className: `relative h-[19px] w-[19px] rounded border text-indigo-600 focus:ring-indigo-500 transition ease-out duration-150 ${checked ? "border-blue-500 bg-blue-500 shadow-sm group-hover:bg-blue-400 group-hover:border-blue-400" : "border-gray-200 bg-white shadow-inner group-hover:bg-gray-100"}`
40238
+ className: `relative flex h-[19px] w-[19px] items-center justify-center rounded-full border transition ease-out duration-150 ${checked ? "border-blue-500 bg-white shadow-sm group-hover:border-blue-400" : "border-gray-300 bg-white group-hover:border-blue-300"}`
40230
40239
  },
40231
40240
  /* @__PURE__ */ React.createElement(
40232
- BiCheck,
40241
+ "span",
40233
40242
  {
40234
- className: `absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[17px] h-[17px] transition ease-out duration-150 ${checked ? "opacity-100 text-white group-hover:opacity-80" : "text-blue-500 opacity-0 grou-hover:opacity-30"}`
40243
+ className: `h-[9px] w-[9px] rounded-full bg-blue-500 transition ease-out duration-150 ${checked ? "scale-100 opacity-100 group-hover:bg-blue-400" : "scale-75 opacity-0"}`
40235
40244
  }
40236
40245
  )
40237
40246
  ),
40238
40247
  /* @__PURE__ */ React.createElement(
40239
40248
  "span",
40240
40249
  {
40241
- className: `relative transition ease-out duration-150 ${checked ? "text-gray-800 opacity-100" : "text-gray-700 opacity-70 group-hover:opacity-100"}`
40250
+ className: `relative transition ease-out duration-150 ${checked ? "text-gray-800 opacity-100" : "text-gray-700 opacity-100 group-hover:text-gray-800"}`
40242
40251
  },
40243
40252
  children
40244
40253
  )
@@ -41935,13 +41944,8 @@ const ImageField = wrapFieldsWithMeta(
41935
41944
  async function onChange(media) {
41936
41945
  var _a2, _b;
41937
41946
  if (media) {
41938
- const parsedValue = (
41939
- // @ts-ignore
41940
- typeof ((_b = (_a2 = cms == null ? void 0 : cms.media) == null ? void 0 : _a2.store) == null ? void 0 : _b.parse) === "function" ? (
41941
- // @ts-ignore
41942
- cms.media.store.parse(media)
41943
- ) : media
41944
- );
41947
+ const item = Array.isArray(media) ? media[0] : media;
41948
+ const parsedValue = typeof ((_b = (_a2 = cms == null ? void 0 : cms.media) == null ? void 0 : _a2.store) == null ? void 0 : _b.parse) === "function" ? cms.media.store.parse(item) : item.src || item;
41945
41949
  props.input.onChange(parsedValue);
41946
41950
  }
41947
41951
  }
@@ -43774,6 +43778,38 @@ const PasswordFieldPlugin = {
43774
43778
  },
43775
43779
  parse: parse$2
43776
43780
  };
43781
+ const DefaultDisplayOnlyField = () => {
43782
+ return /* @__PURE__ */ React.createElement("div", { className: "rounded-lg border border-gray-200 bg-gray-50 p-3 text-sm text-gray-500 italic" }, "Display-only field — provide a component via ", /* @__PURE__ */ React.createElement("code", null, "ui.component"));
43783
+ };
43784
+ const DisplayOnlyFieldPlugin = {
43785
+ name: "displayOnly",
43786
+ Component: wrapFieldWithNoHeader(DefaultDisplayOnlyField)
43787
+ };
43788
+ const InfoBox = ({
43789
+ message,
43790
+ links
43791
+ }) => {
43792
+ const InfoBoxComponent = () => {
43793
+ return /* @__PURE__ */ React.createElement("div", { className: "relative w-full px-2 mb-5 last:mb-0" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm w-full" }, /* @__PURE__ */ React.createElement("p", { className: "text-gray-700 whitespace-normal break-words" }, message), links && links.length > 0 && /* @__PURE__ */ React.createElement("ul", { className: "flex flex-col gap-1 mt-2" }, links.map((link) => /* @__PURE__ */ React.createElement("li", { key: link.url }, /* @__PURE__ */ React.createElement(
43794
+ "a",
43795
+ {
43796
+ href: link.url,
43797
+ target: "_blank",
43798
+ rel: "noopener noreferrer",
43799
+ className: "text-blue-600 underline hover:text-blue-800"
43800
+ },
43801
+ link.text
43802
+ ))))));
43803
+ };
43804
+ InfoBoxComponent.displayName = "InfoBox";
43805
+ return InfoBoxComponent;
43806
+ };
43807
+ function AiFillWarning(props) {
43808
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 1024 1024" }, "child": [{ "tag": "path", "attr": { "d": "M955.7 856l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zM480 416c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v184c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V416zm32 352a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z" }, "child": [] }] })(props);
43809
+ }
43810
+ function AiOutlineLoading(props) {
43811
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 1024 1024" }, "child": [{ "tag": "path", "attr": { "d": "M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z" }, "child": [] }] })(props);
43812
+ }
43777
43813
  function GrCircleQuestion(props) {
43778
43814
  return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "fill": "none", "strokeWidth": "2", "d": "M12,22 C17.5228475,22 22,17.5228475 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,17.5228475 6.4771525,22 12,22 Z M12,15 L12,14 C12,13 12,12.5 13,12 C14,11.5 15,11 15,9.5 C15,8.5 14,7 12,7 C10,7 9,8.26413718 9,10 M12,16 L12,18" }, "child": [] }] })(props);
43779
43815
  }
@@ -44179,6 +44215,9 @@ function BranchSelectorTable({
44179
44215
  null
44180
44216
  );
44181
44217
  const cms = useCMS$1();
44218
+ const isCurrentBranchDeleted = !branchList.some(
44219
+ (b) => b.name === currentBranch
44220
+ );
44182
44221
  const columns = [
44183
44222
  {
44184
44223
  accessorKey: "name",
@@ -44355,7 +44394,7 @@ function BranchSelectorTable({
44355
44394
  {
44356
44395
  className: cn(
44357
44396
  "bg-transparent hover:!bg-transparent",
44358
- currentBranch === currentBranchData.name ? "border-l-[3px] border-l-tina-orange bg-[#f8fafc]" : ""
44397
+ isCurrentBranchDeleted ? "border-l-[3px] border-l-yellow-400 bg-yellow-50" : "border-l-[3px] border-l-tina-orange bg-[#f8fafc]"
44359
44398
  )
44360
44399
  },
44361
44400
  /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, currentBranchData.protected ? /* @__PURE__ */ React.createElement(BiLockAlt, { className: "w-4 h-auto opacity-70 text-blue-500 flex-shrink-0" }) : /* @__PURE__ */ React.createElement(BiGitBranch, { className: "w-4 h-auto opacity-70 text-gray-600 flex-shrink-0" }), /* @__PURE__ */ React.createElement("p", { className: "font-bold" }, currentBranchData.name)), /* @__PURE__ */ React.createElement("div", { className: "w-fit mt-1" }, /* @__PURE__ */ React.createElement(
@@ -44367,6 +44406,15 @@ function BranchSelectorTable({
44367
44406
  },
44368
44407
  /* @__PURE__ */ React.createElement(BiPencil, { className: "w-3 h-auto inline-block mr-1" }),
44369
44408
  "Currently editing"
44409
+ )), isCurrentBranchDeleted && /* @__PURE__ */ React.createElement("div", { className: "w-fit" }, /* @__PURE__ */ React.createElement(
44410
+ Badge,
44411
+ {
44412
+ displayIcon: false,
44413
+ calloutStyle: "warning",
44414
+ className: "w-fit flex-shrink-0"
44415
+ },
44416
+ /* @__PURE__ */ React.createElement(AiFillWarning, { className: "w-3 h-auto inline-block mr-1" }),
44417
+ "Branch no longer exists"
44370
44418
  )))),
44371
44419
  /* @__PURE__ */ React.createElement(TableCell, null, ((_e = currentBranchData.indexStatus) == null ? void 0 : _e.timestamp) && /* @__PURE__ */ React.createElement("div", null, formatDistanceToNow(
44372
44420
  new Date(currentBranchData.indexStatus.timestamp)
@@ -46100,10 +46148,10 @@ function GridMediaItem({ item, active, onClick }) {
46100
46148
  "button",
46101
46149
  {
46102
46150
  className: cn(
46103
- "relative flex flex-col items-center justify-center w-full outline-none",
46151
+ "relative flex flex-col gap-1 items-center w-full outline-none rounded-lg overflow-hidden border-2 transition",
46104
46152
  {
46105
- "shadow hover:shadow-md hover:scale-103 hover:border-gray-150": !active,
46106
- "ring-2 ring-tina-orange": active,
46153
+ "border-black/10 hover:border-tina-orange/50 shadow-sm hover:shadow-md bg-white": !active,
46154
+ "border-tina-orange bg-tina-orange/10 shadow-md": active,
46107
46155
  "cursor-pointer": item.type === "dir"
46108
46156
  }
46109
46157
  ),
@@ -46115,35 +46163,23 @@ function GridMediaItem({ item, active, onClick }) {
46115
46163
  }
46116
46164
  }
46117
46165
  },
46118
- /* @__PURE__ */ React__default.createElement(
46119
- "span",
46120
- {
46121
- className: cn(
46122
- "absolute bottom-0 left-0 w-full text-xs text-white px-2 py-1 truncate z-10",
46123
- active ? "bg-tina-orange/60" : "bg-black/60"
46124
- ),
46125
- style: { pointerEvents: "none" }
46126
- },
46127
- item.filename
46128
- ),
46129
46166
  item.new && /* @__PURE__ */ React__default.createElement("span", { className: "absolute top-1 right-1 rounded shadow bg-green-100 border border-green-200 text-[10px] tracking-wide font-bold text-green-600 px-1.5 py-0.5 z-10" }, "NEW"),
46130
- /* @__PURE__ */ React__default.createElement("div", { className: "relative w-full flex items-center justify-center" }, itemIsImage ? /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
46167
+ /* @__PURE__ */ React__default.createElement("div", { className: "w-full overflow-hidden aspect-[1/1]" }, itemIsImage ? /* @__PURE__ */ React__default.createElement(
46131
46168
  "img",
46132
46169
  {
46133
- className: cn(
46134
- "block overflow-hidden object-center object-contain max-w-full max-h-[16rem] m-auto shadow"
46135
- ),
46170
+ className: "block w-full h-full object-center object-cover",
46136
46171
  style: checkerboardStyle,
46137
46172
  src: thumbnail,
46138
46173
  alt: item.filename
46139
46174
  }
46140
- )) : /* @__PURE__ */ React__default.createElement("div", { className: "p-4 w-full flex flex-col gap-4 items-center justify-center" }, /* @__PURE__ */ React__default.createElement(
46175
+ ) : /* @__PURE__ */ React__default.createElement("div", { className: "w-full h-full flex flex-col items-center justify-center" }, /* @__PURE__ */ React__default.createElement(
46141
46176
  FileIcon,
46142
46177
  {
46143
46178
  className: `w-[40%] h-auto ${item.type === "dir" ? "fill-tina-orange" : "fill-gray-300"}`,
46144
46179
  size: 40
46145
46180
  }
46146
- )))
46181
+ ))),
46182
+ /* @__PURE__ */ React__default.createElement("div", { className: "mt-auto w-full px-2 py-1 text-sm truncate" }, item.filename)
46147
46183
  ));
46148
46184
  }
46149
46185
  const DeleteModal$1 = ({
@@ -48004,7 +48040,7 @@ const NavProvider = ({
48004
48040
  const name = "tinacms";
48005
48041
  const type = "module";
48006
48042
  const typings = "dist/index.d.ts";
48007
- const version$1 = "3.7.2";
48043
+ const version$1 = "3.7.4";
48008
48044
  const main = "dist/index.js";
48009
48045
  const module = "./dist/index.js";
48010
48046
  const exports = {
@@ -48104,7 +48140,7 @@ const dependencies = {
48104
48140
  "graphql-tag": "catalog:",
48105
48141
  "is-hotkey": "catalog:",
48106
48142
  "lucide-react": "catalog:",
48107
- mermaid: "9.3.0",
48143
+ mermaid: "^11.12.2",
48108
48144
  moment: "catalog:",
48109
48145
  "moment-timezone": "^0.6.0",
48110
48146
  "monaco-editor": "catalog:",
@@ -48966,7 +49002,7 @@ const TreeNodeComponent = ({
48966
49002
  node3.children.length > 0 && /* @__PURE__ */ React.createElement(
48967
49003
  BiChevronRight,
48968
49004
  {
48969
- className: `w-4 h-4 text-gray-500 transition-transform duration-150 -ml-1 ${isExpanded ? "rotate-90" : ""}`
49005
+ className: `w-4 h-4 flex-none text-gray-500 transition-transform duration-150 -ml-1 ${isExpanded ? "rotate-90" : ""}`
48970
49006
  }
48971
49007
  ),
48972
49008
  isExpanded ? /* @__PURE__ */ React.createElement(BiFolderOpen, { className: "w-4 h-4 text-orange-500 flex-none" }) : /* @__PURE__ */ React.createElement(BiFolder, { className: "w-4 h-4 text-orange-500 flex-none" }),
@@ -49057,16 +49093,21 @@ const FormLists = (props) => {
49057
49093
  onChange: (e3) => setShowReferences(e3.target.checked),
49058
49094
  className: "w-4 h-4 text-orange-500 border-gray-300 rounded focus:ring-orange-500"
49059
49095
  }
49060
- ), /* @__PURE__ */ React.createElement("span", null, "Show all references"))), /* @__PURE__ */ React.createElement("div", { className: "flex-1 overflow-x-auto overflow-y-auto min-h-0" }, cms.state.formLists.map((formList, index) => /* @__PURE__ */ React.createElement("div", { key: `${formList.id}-${index}` }, /* @__PURE__ */ React.createElement(
49096
+ ), /* @__PURE__ */ React.createElement("span", null, "Show all references"))), /* @__PURE__ */ React.createElement("div", { className: "flex-1 overflow-x-auto overflow-y-auto min-h-0 pb-16" }, /* @__PURE__ */ React.createElement(
49061
49097
  FormList,
49062
49098
  {
49063
49099
  setActiveFormId: (id2) => {
49064
49100
  cms.dispatch({ type: "forms:set-active-form-id", value: id2 });
49065
49101
  },
49066
- formList,
49102
+ formList: {
49103
+ id: "merged",
49104
+ label: "All",
49105
+ items: cms.state.formLists.flatMap((fl) => fl.items),
49106
+ formIds: cms.state.formLists.flatMap((fl) => fl.formIds)
49107
+ },
49067
49108
  showReferences
49068
49109
  }
49069
- )))));
49110
+ )));
49070
49111
  };
49071
49112
  const FormList = (props) => {
49072
49113
  const cms = useCMS();
@@ -49784,7 +49825,8 @@ const DEFAULT_FIELDS = [
49784
49825
  ReferenceFieldPlugin,
49785
49826
  ButtonToggleFieldPlugin,
49786
49827
  HiddenFieldPlugin,
49787
- PasswordFieldPlugin
49828
+ PasswordFieldPlugin,
49829
+ DisplayOnlyFieldPlugin
49788
49830
  ];
49789
49831
  class TinaCMS extends CMS {
49790
49832
  constructor({
@@ -66034,37 +66076,20 @@ const pathRelativeToCollection = (collectionPath, fullPath) => {
66034
66076
  `Path ${fullPath} not within collection path ${collectionPath}`
66035
66077
  );
66036
66078
  };
66037
- const formatDefaultBranchName = (filePath, crudType) => {
66038
- let result = filePath;
66039
- const contentPrefix = "content/";
66040
- if (result.startsWith(contentPrefix)) {
66041
- result = result.substring(contentPrefix.length);
66042
- }
66043
- const lastDot = result.lastIndexOf(".");
66044
- const lastSlash = Math.max(result.lastIndexOf("/"), result.lastIndexOf("\\"));
66045
- if (lastDot > lastSlash && lastDot > 0) {
66046
- result = result.slice(0, lastDot);
66047
- }
66048
- if (crudType === "delete") {
66049
- result = `❌-${result}`;
66050
- }
66051
- return result;
66079
+ const WORKFLOW_STEPS = [
66080
+ { id: 1, name: "Creating branch", description: "Setting up workspace" },
66081
+ { id: 2, name: "Updating branch", description: "Syncing content to branch" },
66082
+ { id: 3, name: "Creating pull request", description: "Preparing for review" }
66083
+ ];
66084
+ const formatTime = (seconds) => {
66085
+ const mins = Math.floor(seconds / 60);
66086
+ const secs = seconds % 60;
66087
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
66052
66088
  };
66053
- const CreateBranchModal = ({
66054
- close: close2,
66055
- safeSubmit,
66056
- path: path3,
66057
- values,
66058
- crudType,
66059
- tinaForm
66060
- }) => {
66089
+ function useEditorialWorkflow() {
66061
66090
  const cms = useCMS$1();
66062
66091
  const tinaApi = cms.api.tina;
66063
66092
  const { setCurrentBranch } = useBranchData();
66064
- const [disabled, setDisabled] = React.useState(false);
66065
- const [newBranchName, setNewBranchName] = React.useState(
66066
- formatDefaultBranchName(path3, crudType)
66067
- );
66068
66093
  const [isExecuting, setIsExecuting] = React.useState(false);
66069
66094
  const [errorMessage, setErrorMessage] = React.useState("");
66070
66095
  const [currentStep, setCurrentStep] = React.useState(0);
@@ -66083,33 +66108,21 @@ const CreateBranchModal = ({
66083
66108
  clearInterval(interval);
66084
66109
  };
66085
66110
  }, [isExecuting, currentStep]);
66086
- const formatTime = (seconds) => {
66087
- const mins = Math.floor(seconds / 60);
66088
- const secs = seconds % 60;
66089
- return `${mins}:${secs.toString().padStart(2, "0")}`;
66111
+ const reset2 = () => {
66112
+ setErrorMessage("");
66113
+ setIsExecuting(false);
66114
+ setCurrentStep(0);
66090
66115
  };
66091
- const steps = [
66092
- {
66093
- id: 1,
66094
- name: "Creating branch",
66095
- description: "Setting up workspace"
66096
- },
66097
- {
66098
- id: 2,
66099
- name: "Updating branch",
66100
- description: "Syncing content to branch"
66101
- },
66102
- {
66103
- id: 3,
66104
- name: "Creating pull request",
66105
- description: "Preparing for review"
66106
- }
66107
- ];
66108
- const executeEditorialWorkflow = async () => {
66116
+ const executeWorkflow = async ({
66117
+ branchName,
66118
+ baseBranch,
66119
+ path: path3,
66120
+ values,
66121
+ crudType,
66122
+ tinaForm
66123
+ }) => {
66109
66124
  var _a2;
66110
66125
  try {
66111
- const branchName = `tina/${newBranchName}`;
66112
- setDisabled(true);
66113
66126
  setIsExecuting(true);
66114
66127
  setCurrentStep(1);
66115
66128
  let graphql2 = "";
@@ -66139,7 +66152,7 @@ const CreateBranchModal = ({
66139
66152
  const relativePath = pathRelativeToCollection(collection.path, path3);
66140
66153
  const result = await tinaApi.executeEditorialWorkflow({
66141
66154
  branchName,
66142
- baseBranch: tinaApi.branch,
66155
+ baseBranch,
66143
66156
  prTitle: `${branchName.replace("tina/", "").replace("-", " ")} (PR from TinaCMS)`,
66144
66157
  graphQLContentOp: {
66145
66158
  query: graphql2,
@@ -66186,124 +66199,215 @@ const CreateBranchModal = ({
66186
66199
  const folderPath = relativePath.includes("/") ? relativePath.substring(0, relativePath.lastIndexOf("/")) : "";
66187
66200
  window.location.hash = `#/collections/${collection.name}${folderPath ? `/${folderPath}` : ""}`;
66188
66201
  }
66189
- close2();
66202
+ return true;
66190
66203
  } catch (e3) {
66191
66204
  console.error(e3);
66192
- let errorMessage2 = "Branch operation failed. Talking to GitHub was unsuccessful, please try again. If the problem persists please contact support at https://tina.io/support 🦙";
66205
+ let errMessage = "Branch operation failed. Talking to GitHub was unsuccessful, please try again. If the problem persists please contact support at https://tina.io/support 🦙";
66193
66206
  const err = e3;
66194
66207
  if (err.errorCode) {
66195
66208
  switch (err.errorCode) {
66196
66209
  case EDITORIAL_WORKFLOW_ERROR.BRANCH_EXISTS:
66197
- errorMessage2 = "A branch with this name already exists";
66210
+ errMessage = "A branch with this name already exists";
66198
66211
  break;
66199
66212
  case EDITORIAL_WORKFLOW_ERROR.BRANCH_HIERARCHY_CONFLICT:
66200
- errorMessage2 = err.message || "Branch name conflicts with an existing branch";
66213
+ errMessage = err.message || "Branch name conflicts with an existing branch";
66201
66214
  break;
66202
66215
  case EDITORIAL_WORKFLOW_ERROR.VALIDATION_FAILED:
66203
- errorMessage2 = err.message || "Invalid branch name";
66216
+ errMessage = err.message || "Invalid branch name";
66204
66217
  break;
66205
66218
  default:
66206
- errorMessage2 = err.message || errorMessage2;
66219
+ errMessage = err.message || errMessage;
66207
66220
  break;
66208
66221
  }
66209
66222
  } else if (err.message) {
66210
66223
  if (err.message.toLowerCase().includes("already exists")) {
66211
- errorMessage2 = "A branch with this name already exists";
66224
+ errMessage = "A branch with this name already exists";
66212
66225
  } else if (err.message.toLowerCase().includes("conflict")) {
66213
- errorMessage2 = err.message;
66226
+ errMessage = err.message;
66214
66227
  }
66215
66228
  }
66216
- setErrorMessage(errorMessage2);
66217
- setDisabled(false);
66229
+ setErrorMessage(errMessage);
66218
66230
  setIsExecuting(false);
66219
66231
  setCurrentStep(0);
66232
+ return false;
66220
66233
  }
66221
66234
  };
66222
- const renderProgressIndicator = () => {
66223
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex justify-between my-8 relative px-5 sm:gap-x-8" }, /* @__PURE__ */ React.createElement(
66224
- "div",
66225
- {
66226
- className: "absolute top-5 h-0.5 bg-gray-200 -z-10",
66227
- style: { left: "50px", right: "50px" }
66235
+ return {
66236
+ isExecuting,
66237
+ errorMessage,
66238
+ currentStep,
66239
+ elapsedTime,
66240
+ executeWorkflow,
66241
+ reset: reset2
66242
+ };
66243
+ }
66244
+ const WorkflowProgressIndicator = ({
66245
+ currentStep,
66246
+ isExecuting,
66247
+ elapsedTime
66248
+ }) => {
66249
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "flex justify-between my-8 relative px-5 sm:gap-x-8" }, /* @__PURE__ */ React.createElement(
66250
+ "div",
66251
+ {
66252
+ className: "absolute top-5 h-0.5 bg-gray-200 -z-10",
66253
+ style: { left: "50px", right: "50px" }
66254
+ }
66255
+ ), currentStep > 1 && currentStep <= WORKFLOW_STEPS.length && /* @__PURE__ */ React.createElement(
66256
+ "div",
66257
+ {
66258
+ className: "absolute top-5 h-0.5 bg-tina-orange -z-10 transition-all duration-500",
66259
+ style: {
66260
+ left: "50px",
66261
+ width: `calc((100% - 100px) * ${(currentStep - 1) / (WORKFLOW_STEPS.length - 1)})`
66228
66262
  }
66229
- ), currentStep > 1 && currentStep <= steps.length && /* @__PURE__ */ React.createElement(
66230
- "div",
66231
- {
66232
- className: "absolute top-5 h-0.5 bg-tina-orange -z-10 transition-all duration-500",
66233
- style: {
66234
- left: "50px",
66235
- width: `calc((100% - 100px) * ${(currentStep - 1) / (steps.length - 1)})`
66236
- }
66263
+ }
66264
+ ), currentStep > 2 && /* @__PURE__ */ React.createElement(
66265
+ "div",
66266
+ {
66267
+ className: "absolute top-5 h-0.5 bg-green-500 -z-10 transition-all duration-500",
66268
+ style: {
66269
+ left: "50px",
66270
+ width: `calc((100% - 100px) * ${Math.min(1, (currentStep - 2) / (WORKFLOW_STEPS.length - 1))})`
66237
66271
  }
66238
- ), currentStep > 2 && /* @__PURE__ */ React.createElement(
66272
+ }
66273
+ ), WORKFLOW_STEPS.map((step, index) => {
66274
+ const stepNumber = index + 1;
66275
+ const isActive = stepNumber === currentStep;
66276
+ const isCompleted = stepNumber < currentStep;
66277
+ return /* @__PURE__ */ React.createElement("div", { key: step.id, className: "flex flex-col items-center relative" }, /* @__PURE__ */ React.createElement(
66239
66278
  "div",
66240
66279
  {
66241
- className: "absolute top-5 h-0.5 bg-green-500 -z-10 transition-all duration-500",
66242
- style: {
66243
- left: "50px",
66244
- width: `calc((100% - 100px) * ${Math.min(1, (currentStep - 2) / (steps.length - 1))})`
66245
- }
66246
- }
66247
- ), steps.map((step, index) => {
66248
- const stepNumber = index + 1;
66249
- const isActive = stepNumber === currentStep;
66250
- const isCompleted = stepNumber < currentStep;
66251
- return /* @__PURE__ */ React.createElement(
66252
- "div",
66280
+ className: `w-10 h-10 rounded-full flex items-center justify-center font-medium mb-3 border-2 transition-all duration-300 select-none ${isCompleted ? "bg-green-500 border-green-500 text-white" : isActive ? "bg-tina-orange border-tina-orange text-white" : "bg-white border-gray-200 text-gray-400"}`
66281
+ },
66282
+ isCompleted ? /* @__PURE__ */ React.createElement(
66283
+ "svg",
66253
66284
  {
66254
- key: step.id,
66255
- className: "flex flex-col items-center relative"
66285
+ className: "w-5 h-5",
66286
+ fill: "currentColor",
66287
+ viewBox: "0 0 20 20"
66256
66288
  },
66257
66289
  /* @__PURE__ */ React.createElement(
66258
- "div",
66290
+ "path",
66259
66291
  {
66260
- className: `w-10 h-10 rounded-full flex items-center justify-center font-medium mb-3 border-2 transition-all duration-300 select-none ${isCompleted ? "bg-green-500 border-green-500 text-white" : isActive ? "bg-tina-orange border-tina-orange text-white" : "bg-white border-gray-200 text-gray-400"}`
66261
- },
66262
- isCompleted ? /* @__PURE__ */ React.createElement(
66263
- "svg",
66264
- {
66265
- className: "w-5 h-5",
66266
- fill: "currentColor",
66267
- viewBox: "0 0 20 20"
66268
- },
66269
- /* @__PURE__ */ React.createElement(
66270
- "path",
66271
- {
66272
- fillRule: "evenodd",
66273
- d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
66274
- clipRule: "evenodd"
66275
- }
66276
- )
66277
- ) : isActive ? /* @__PURE__ */ React.createElement(AiOutlineLoading, { className: "animate-spin text-lg" }) : stepNumber
66278
- ),
66279
- /* @__PURE__ */ React.createElement("div", { className: "text-center max-w-24" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold leading-tight" }, step.name), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-400 mt-1 leading-tight" }, step.description))
66292
+ fillRule: "evenodd",
66293
+ d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
66294
+ clipRule: "evenodd"
66295
+ }
66296
+ )
66297
+ ) : isActive ? /* @__PURE__ */ React.createElement(AiOutlineLoading, { className: "animate-spin text-lg" }) : stepNumber
66298
+ ), /* @__PURE__ */ React.createElement("div", { className: "text-center max-w-24" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold leading-tight" }, step.name), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-400 mt-1 leading-tight" }, step.description)));
66299
+ })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-500" }, "Estimated time: 1-2 min"), isExecuting && currentStep > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 text-sm text-gray-500 tabular-nums" }, /* @__PURE__ */ React.createElement("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20" }, /* @__PURE__ */ React.createElement(
66300
+ "path",
66301
+ {
66302
+ fillRule: "evenodd",
66303
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z",
66304
+ clipRule: "evenodd"
66305
+ }
66306
+ )), formatTime(elapsedTime))), /* @__PURE__ */ React.createElement(
66307
+ "a",
66308
+ {
66309
+ className: "underline text-tina-orange-dark font-medium text-xs",
66310
+ href: "https://tina.io/docs/r/editorial-workflow",
66311
+ target: "_blank"
66312
+ },
66313
+ "Learn more about Editorial Workflow"
66314
+ ));
66315
+ };
66316
+ const formatDefaultBranchName = (filePath, crudType) => {
66317
+ let result = filePath;
66318
+ const contentPrefix = "content/";
66319
+ if (result.startsWith(contentPrefix)) {
66320
+ result = result.substring(contentPrefix.length);
66321
+ }
66322
+ const lastDot = result.lastIndexOf(".");
66323
+ const lastSlash = Math.max(result.lastIndexOf("/"), result.lastIndexOf("\\"));
66324
+ if (lastDot > lastSlash && lastDot > 0) {
66325
+ result = result.slice(0, lastDot);
66326
+ }
66327
+ if (crudType === "delete") {
66328
+ result = `❌-${result}`;
66329
+ }
66330
+ return result;
66331
+ };
66332
+ const CreateBranchModal = ({
66333
+ close: close2,
66334
+ safeSubmit,
66335
+ path: path3,
66336
+ values,
66337
+ crudType,
66338
+ tinaForm,
66339
+ onBaseBranchDeleted
66340
+ }) => {
66341
+ const cms = useCMS$1();
66342
+ const tinaApi = cms.api.tina;
66343
+ const [newBranchName, setNewBranchName] = React.useState(
66344
+ formatDefaultBranchName(path3, crudType)
66345
+ );
66346
+ const [isBranchGuardChecking, setIsBranchGuardChecking] = React.useState(false);
66347
+ const {
66348
+ isExecuting,
66349
+ errorMessage,
66350
+ currentStep,
66351
+ elapsedTime,
66352
+ executeWorkflow,
66353
+ reset: reset2
66354
+ } = useEditorialWorkflow();
66355
+ const executeEditorialWorkflow = async () => {
66356
+ setIsBranchGuardChecking(true);
66357
+ const baseBranch = decodeURIComponent(tinaApi.branch);
66358
+ let baseBranchExists = true;
66359
+ try {
66360
+ console.debug(
66361
+ "[tina:branch-guard] executeEditorialWorkflow: checking base branch:",
66362
+ baseBranch
66280
66363
  );
66281
- })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-500" }, "Estimated time: 1-2 min "), isExecuting && currentStep > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 text-sm text-gray-500 tabular-nums" }, /* @__PURE__ */ React.createElement("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20" }, /* @__PURE__ */ React.createElement(
66282
- "path",
66283
- {
66284
- fillRule: "evenodd",
66285
- d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z",
66286
- clipRule: "evenodd"
66287
- }
66288
- )), formatTime(elapsedTime))), /* @__PURE__ */ React.createElement(
66289
- "a",
66290
- {
66291
- className: "underline text-tina-orange-dark font-medium text-xs",
66292
- href: "https://tina.io/docs/tinacloud/editorial-workflow",
66293
- target: "_blank"
66294
- },
66295
- "Learn more about Editorial Workflow"
66296
- ));
66364
+ baseBranchExists = await tinaApi.branchExists(baseBranch);
66365
+ } catch (err) {
66366
+ console.error(
66367
+ "[tina:branch-guard] executeEditorialWorkflow: branchExists threw, failing open:",
66368
+ err
66369
+ );
66370
+ }
66371
+ console.debug(
66372
+ "[tina:branch-guard] executeEditorialWorkflow: base branch exists?",
66373
+ baseBranchExists
66374
+ );
66375
+ if (!baseBranchExists) {
66376
+ console.debug(
66377
+ "[tina:branch-guard] executeEditorialWorkflow: base branch deleted — handing off"
66378
+ );
66379
+ onBaseBranchDeleted == null ? void 0 : onBaseBranchDeleted();
66380
+ return;
66381
+ }
66382
+ setIsBranchGuardChecking(false);
66383
+ const success = await executeWorkflow({
66384
+ branchName: `tina/${newBranchName}`,
66385
+ baseBranch,
66386
+ path: path3,
66387
+ values,
66388
+ crudType,
66389
+ tinaForm
66390
+ });
66391
+ if (success) {
66392
+ close2();
66393
+ }
66297
66394
  };
66298
66395
  const renderStateContent = () => {
66299
66396
  if (isExecuting) {
66300
- return renderProgressIndicator();
66397
+ return /* @__PURE__ */ React.createElement(
66398
+ WorkflowProgressIndicator,
66399
+ {
66400
+ currentStep,
66401
+ isExecuting,
66402
+ elapsedTime
66403
+ }
66404
+ );
66301
66405
  } else {
66302
66406
  return /* @__PURE__ */ React.createElement("div", { className: "max-w-sm" }, errorMessage && /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 text-red-700 py-2 px-3 mb-4 bg-red-50 border border-red-200 rounded" }, /* @__PURE__ */ React.createElement(BiError, { className: "w-5 h-auto text-red-400 flex-shrink-0" }), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, /* @__PURE__ */ React.createElement("b", null, "Error:"), " ", errorMessage)), /* @__PURE__ */ React.createElement("p", { className: "text-lg text-gray-700 font-bold mb-2" }, "First, let's create a copy"), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-gray-700 mb-4 max-w-sm" }, "To make changes, you need to create a copy then get it approved and merged for it to go live.", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("span", { className: "text-gray-500" }, "Learn more about "), /* @__PURE__ */ React.createElement(
66303
66407
  "a",
66304
66408
  {
66305
66409
  className: "underline text-tina-orange-dark font-medium",
66306
- href: "https://tina.io/docs/tinacloud/editorial-workflow",
66410
+ href: "https://tina.io/docs/r/editorial-workflow",
66307
66411
  target: "_blank"
66308
66412
  },
66309
66413
  "Editorial Workflow"
@@ -66315,7 +66419,7 @@ const CreateBranchModal = ({
66315
66419
  placeholder: "e.g. {{PAGE-NAME}}-updates",
66316
66420
  value: newBranchName,
66317
66421
  onChange: (e3) => {
66318
- setErrorMessage("");
66422
+ reset2();
66319
66423
  setNewBranchName(e3.target.value);
66320
66424
  }
66321
66425
  }
@@ -66336,7 +66440,7 @@ const CreateBranchModal = ({
66336
66440
  variant: "primary",
66337
66441
  align: "start",
66338
66442
  className: "w-full sm:w-auto",
66339
- disabled: newBranchName === "" || disabled,
66443
+ disabled: newBranchName === "" || isBranchGuardChecking,
66340
66444
  onMainAction: executeEditorialWorkflow,
66341
66445
  items: [
66342
66446
  {
@@ -66374,6 +66478,89 @@ const PrefixedTextField = ({
66374
66478
  }
66375
66479
  )));
66376
66480
  };
66481
+ const BranchDeletedModal = ({
66482
+ branchName,
66483
+ close: close2,
66484
+ path: path3,
66485
+ values,
66486
+ crudType,
66487
+ tinaForm
66488
+ }) => {
66489
+ const cms = useCMS$1();
66490
+ const tinaApi = cms.api.tina;
66491
+ const [newBranchName, setNewBranchName] = React.useState("");
66492
+ const baseBranch = tinaApi.protectedBranches[0] || cms.api.tina.schema.config.config.repoProvider.defaultBranchName || "main";
66493
+ const {
66494
+ isExecuting,
66495
+ errorMessage,
66496
+ currentStep,
66497
+ elapsedTime,
66498
+ executeWorkflow,
66499
+ reset: reset2
66500
+ } = useEditorialWorkflow();
66501
+ const handleCreate = async () => {
66502
+ const success = await executeWorkflow({
66503
+ branchName: `tina/${newBranchName}`,
66504
+ baseBranch,
66505
+ path: path3,
66506
+ values,
66507
+ crudType,
66508
+ tinaForm
66509
+ });
66510
+ if (success)
66511
+ close2();
66512
+ };
66513
+ return /* @__PURE__ */ React.createElement(Modal, { className: "flex" }, /* @__PURE__ */ React.createElement(PopupModal, { className: "w-auto" }, /* @__PURE__ */ React.createElement(ModalHeader, { close: isExecuting ? void 0 : close2 }, "Branch no longer exists"), /* @__PURE__ */ React.createElement(ModalBody, { padded: true }, isExecuting ? /* @__PURE__ */ React.createElement(
66514
+ WorkflowProgressIndicator,
66515
+ {
66516
+ currentStep,
66517
+ isExecuting,
66518
+ elapsedTime
66519
+ }
66520
+ ) : /* @__PURE__ */ React.createElement("div", { className: "max-w-sm" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3 p-3 mb-4 bg-yellow-50 border border-yellow-200 rounded text-yellow-800 text-sm" }, /* @__PURE__ */ React.createElement(
66521
+ GitBranchIcon,
66522
+ {
66523
+ className: "w-4 h-4 mt-0.5 flex-shrink-0 text-yellow-600",
66524
+ style: { fill: "none" }
66525
+ }
66526
+ ), /* @__PURE__ */ React.createElement("span", null, "The branch", " ", /* @__PURE__ */ React.createElement("span", { className: "font-mono font-semibold" }, branchName), " ", "no longer exists. It may have been merged or deleted. Your changes cannot be pushed to it.")), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-gray-700 mb-4" }, "Create a new branch from", " ", /* @__PURE__ */ React.createElement("span", { className: "font-mono font-semibold" }, baseBranch), " to continue editing, or cancel and switch to an existing branch from the branch menu."), errorMessage && /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 text-red-700 py-2 px-3 mb-4 bg-red-50 border border-red-200 rounded" }, /* @__PURE__ */ React.createElement(BiError, { className: "w-5 h-auto text-red-400 flex-shrink-0" }), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, /* @__PURE__ */ React.createElement("b", null, "Error:"), " ", errorMessage)), /* @__PURE__ */ React.createElement(
66527
+ PrefixedTextField,
66528
+ {
66529
+ name: "new-branch-name",
66530
+ label: "New Branch Name",
66531
+ placeholder: "e.g. my-updates",
66532
+ value: newBranchName,
66533
+ onChange: (e3) => {
66534
+ reset2();
66535
+ setNewBranchName(formatBranchName(e3.target.value));
66536
+ }
66537
+ }
66538
+ ))), !isExecuting && /* @__PURE__ */ React.createElement(ModalActions, { align: "end" }, /* @__PURE__ */ React.createElement(
66539
+ Button$2,
66540
+ {
66541
+ variant: "secondary",
66542
+ className: "w-full sm:w-auto",
66543
+ onClick: close2
66544
+ },
66545
+ "Cancel"
66546
+ ), /* @__PURE__ */ React.createElement(
66547
+ Button$2,
66548
+ {
66549
+ variant: "primary",
66550
+ className: "w-full sm:w-auto",
66551
+ disabled: !newBranchName,
66552
+ onClick: handleCreate
66553
+ },
66554
+ /* @__PURE__ */ React.createElement(
66555
+ GitBranchIcon,
66556
+ {
66557
+ className: "w-4 h-4 mr-1",
66558
+ style: { fill: "none" }
66559
+ }
66560
+ ),
66561
+ "Create new branch"
66562
+ ))));
66563
+ };
66377
66564
  const NoFieldsPlaceholder = () => /* @__PURE__ */ React.createElement(
66378
66565
  "div",
66379
66566
  {
@@ -66430,6 +66617,8 @@ const FormBuilder = ({
66430
66617
  const cms = useCMS$1();
66431
66618
  const hideFooter = !!rest.hideFooter;
66432
66619
  const [createBranchModalOpen, setCreateBranchModalOpen] = React.useState(false);
66620
+ const [deletedBranchModalOpen, setDeletedBranchModalOpen] = React.useState(false);
66621
+ const [isGuardChecking, setIsGuardChecking] = React.useState(false);
66433
66622
  const tinaForm = form.tinaForm;
66434
66623
  const finalForm = form.tinaForm.finalForm;
66435
66624
  React.useEffect(() => {
@@ -66489,26 +66678,79 @@ const FormBuilder = ({
66489
66678
  const canSubmit = !pristine && !submitting && !hasValidationErrors && !(invalid && !dirtySinceLastSubmit);
66490
66679
  const safeSubmit = async () => {
66491
66680
  if (canSubmit) {
66681
+ const alertsBefore = new Set(cms.alerts.all.map((a2) => a2.id));
66682
+ console.debug(
66683
+ "[tina:branch-guard] safeSubmit: calling handleSubmit"
66684
+ );
66492
66685
  const result = await handleSubmit();
66493
66686
  if (result && result[FORM_ERROR]) {
66494
66687
  const error2 = result[FORM_ERROR];
66688
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
66689
+ console.debug(
66690
+ "[tina:branch-guard] safeSubmit: FORM_ERROR detected:",
66691
+ errorMsg
66692
+ );
66693
+ if (/branch.*not found/i.test(errorMsg)) {
66694
+ console.debug(
66695
+ "[tina:branch-guard] safeSubmit: branch-not-found — dismissing alert and opening modal"
66696
+ );
66697
+ for (const alert of cms.alerts.all) {
66698
+ if (!alertsBefore.has(alert.id) && alert.level === "error") {
66699
+ cms.alerts.dismiss(alert);
66700
+ }
66701
+ }
66702
+ setDeletedBranchModalOpen(true);
66703
+ return;
66704
+ }
66495
66705
  captureEvent(SaveContentErrorEvent, {
66496
66706
  documentPath: tinaForm.path,
66497
- error: error2 instanceof Error ? error2.message : String(error2)
66707
+ error: errorMsg
66498
66708
  });
66499
66709
  } else {
66500
66710
  captureEvent(SavedContentEvent, {
66501
66711
  documentPath: tinaForm.path
66502
66712
  });
66503
66713
  }
66714
+ } else {
66715
+ console.debug(
66716
+ "[tina:branch-guard] safeSubmit: skipped — canSubmit is false"
66717
+ );
66504
66718
  }
66505
66719
  };
66506
66720
  const safeHandleSubmit = async () => {
66721
+ setIsGuardChecking(true);
66722
+ const currentBranch = decodeURIComponent(cms.api.tina.getBranch());
66723
+ let exists = true;
66724
+ try {
66725
+ console.debug(
66726
+ "[tina:branch-guard] safeHandleSubmit: checking branch:",
66727
+ currentBranch
66728
+ );
66729
+ exists = await cms.api.tina.branchExists(currentBranch);
66730
+ } catch (err) {
66731
+ console.error(
66732
+ "[tina:branch-guard] safeHandleSubmit: branchExists threw, failing open:",
66733
+ err
66734
+ );
66735
+ }
66736
+ console.debug(
66737
+ "[tina:branch-guard] safeHandleSubmit: branchExists returned:",
66738
+ exists
66739
+ );
66740
+ if (!exists) {
66741
+ console.debug(
66742
+ "[tina:branch-guard] safeHandleSubmit: branch missing — opening modal"
66743
+ );
66744
+ setIsGuardChecking(false);
66745
+ setDeletedBranchModalOpen(true);
66746
+ return;
66747
+ }
66507
66748
  if (usingProtectedBranch) {
66508
66749
  setCreateBranchModalOpen(true);
66509
66750
  } else {
66510
- safeSubmit();
66751
+ await safeSubmit();
66511
66752
  }
66753
+ setIsGuardChecking(false);
66512
66754
  };
66513
66755
  return /* @__PURE__ */ React.createElement(React.Fragment, null, createBranchModalOpen && /* @__PURE__ */ React.createElement(
66514
66756
  CreateBranchModal,
@@ -66518,7 +66760,21 @@ const FormBuilder = ({
66518
66760
  path: tinaForm.path,
66519
66761
  values: tinaForm.values,
66520
66762
  tinaForm,
66521
- close: () => setCreateBranchModalOpen(false)
66763
+ close: () => setCreateBranchModalOpen(false),
66764
+ onBaseBranchDeleted: () => {
66765
+ setCreateBranchModalOpen(false);
66766
+ setDeletedBranchModalOpen(true);
66767
+ }
66768
+ }
66769
+ ), deletedBranchModalOpen && /* @__PURE__ */ React.createElement(
66770
+ BranchDeletedModal,
66771
+ {
66772
+ branchName: decodeURIComponent(cms.api.tina.getBranch()),
66773
+ close: () => setDeletedBranchModalOpen(false),
66774
+ path: tinaForm.path,
66775
+ values: tinaForm.values,
66776
+ crudType: tinaForm.crudType,
66777
+ tinaForm
66522
66778
  }
66523
66779
  ), /* @__PURE__ */ React.createElement(DragDropContext, { onDragEnd: moveArrayItem }, /* @__PURE__ */ React.createElement(FormKeyBindings, { onSubmit: safeHandleSubmit }), /* @__PURE__ */ React.createElement(FormPortalProvider, null, /* @__PURE__ */ React.createElement(FormWrapper, { id: tinaForm.id }, (tinaForm == null ? void 0 : tinaForm.fields.length) ? /* @__PURE__ */ React.createElement(
66524
66780
  FieldsBuilder,
@@ -66542,8 +66798,8 @@ const FormBuilder = ({
66542
66798
  Button$2,
66543
66799
  {
66544
66800
  onClick: safeHandleSubmit,
66545
- disabled: !canSubmit,
66546
- busy: submitting,
66801
+ disabled: !canSubmit || isGuardChecking,
66802
+ busy: submitting || isGuardChecking,
66547
66803
  variant: "primary"
66548
66804
  },
66549
66805
  submitting && /* @__PURE__ */ React.createElement(LoadingDots, null),
@@ -68183,7 +68439,9 @@ const useImageToolbarButton = (state) => {
68183
68439
  allowDelete: true,
68184
68440
  directory: "",
68185
68441
  onSelect: (media) => {
68186
- insertImg(editor, media);
68442
+ var _a2, _b;
68443
+ const src = typeof ((_b = (_a2 = cms == null ? void 0 : cms.media) == null ? void 0 : _a2.store) == null ? void 0 : _b.parse) === "function" ? cms.media.store.parse(media) : media.src;
68444
+ insertImg(editor, { ...media, src: src || media.src });
68187
68445
  }
68188
68446
  });
68189
68447
  };
@@ -121847,6 +122105,12 @@ mutation addPendingDocumentMutation(
121847
122105
  var _a2;
121848
122106
  return this.usingEditorialWorkflow && ((_a2 = this.protectedBranches) == null ? void 0 : _a2.includes(decodeURIComponent(this.branch)));
121849
122107
  }
122108
+ async branchExists(branchName) {
122109
+ if (this.isLocalMode)
122110
+ return true;
122111
+ const branches = await this.listBranches({ includeIndexStatus: false });
122112
+ return branches.some((b) => b.name === branchName);
122113
+ }
121850
122114
  async createBranch({ baseBranch, branchName }) {
121851
122115
  const url = `${this.contentApiBase}/github/${this.clientId}/create_branch`;
121852
122116
  try {
@@ -124754,7 +125018,7 @@ const RenderForm$1 = ({
124754
125018
  }
124755
125019
  return true;
124756
125020
  }
124757
- const isValid = /[\.\-_\/a-zA-Z0-9]*$/.test(value);
125021
+ const isValid = /^[\.\-_\/a-zA-Z0-9]*$/.test(value);
124758
125022
  if (value && !isValid) {
124759
125023
  return "Must contain only a-z, A-Z, 0-9, -, _, ., or /.";
124760
125024
  }
@@ -125134,6 +125398,16 @@ const ScreenPage = () => {
125134
125398
  } })));
125135
125399
  });
125136
125400
  };
125401
+ const TROUBLESHOOTING_URL = "https://tina.io/docs/tinacloud/troubleshooting";
125402
+ const ErrorModalContent = (props) => {
125403
+ const { title, message } = props;
125404
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("div", null, title), /* @__PURE__ */ React__default.createElement("p", null, message), /* @__PURE__ */ React__default.createElement("a", { href: TROUBLESHOOTING_URL, target: "_blank", rel: "noopener noreferrer" }, "Learn more"));
125405
+ };
125406
+ const showErrorModal = (title, message, cms) => {
125407
+ if (cms.alerts.all.some((a2) => a2.level === "error"))
125408
+ return;
125409
+ cms.alerts.error(() => /* @__PURE__ */ React__default.createElement(ErrorModalContent, { title, message }));
125410
+ };
125137
125411
  const getBackendType = (client) => {
125138
125412
  var _a2, _b, _c;
125139
125413
  if (!client)
@@ -125252,21 +125526,22 @@ const CheckSchema = ({
125252
125526
  localSchema: schemaJson
125253
125527
  }).then((isSchemaMatchedToCloud) => {
125254
125528
  if (isSchemaMatchedToCloud === false) {
125255
- cms.alerts.error(
125256
- `GraphQL Schema Mismatch - Editing may not work.
125257
-
125258
- If you just switched branches, try going back to the previous branch.
125259
-
125260
- If you just pushed changes to the branch, try pulling the latest changes.
125261
-
125262
- For more information, please see https://tina.io/docs/tinacloud/troubleshooting`
125529
+ showErrorModal(
125530
+ "GraphQL Schema Mismatch",
125531
+ "Editing may not work. If you just switched branches, try going back to the previous branch. If you just pushed changes, try pulling the latest.",
125532
+ cms
125263
125533
  );
125264
125534
  }
125265
125535
  }).catch((error2) => {
125266
125536
  if (error2.message.includes("has not been indexed by TinaCloud")) {
125267
125537
  setSchemaMissingError(true);
125268
125538
  } else {
125269
- cms.alerts.error(`Unexpected error checking schema: ${error2}`);
125539
+ console.error("Unexpected error checking schema:", error2);
125540
+ showErrorModal(
125541
+ "Unexpected Error",
125542
+ "An unexpected error occurred while validating your Tina schema. If after refreshing the issue persists, reach out to us on Discord.",
125543
+ cms
125544
+ );
125270
125545
  throw error2;
125271
125546
  }
125272
125547
  });
@@ -125604,6 +125879,7 @@ export {
125604
125879
  DateFieldPlugin,
125605
125880
  DeleteImageButton,
125606
125881
  Dismissible,
125882
+ DisplayOnlyFieldPlugin,
125607
125883
  DragHandle,
125608
125884
  DragIcon,
125609
125885
  DropdownButton,
@@ -125651,6 +125927,7 @@ export {
125651
125927
  ImageField,
125652
125928
  ImageFieldPlugin,
125653
125929
  ImageUpload,
125930
+ InfoBox,
125654
125931
  InfoIcon,
125655
125932
  Input$1 as Input,
125656
125933
  ItalicIcon$1 as ItalicIcon,
@@ -147,6 +147,7 @@ export declare class Client {
147
147
  githubPullRequestUrl?: string;
148
148
  }[]>;
149
149
  usingProtectedBranch(): boolean;
150
+ branchExists(branchName: string): Promise<boolean>;
150
151
  createBranch({ baseBranch, branchName }: BranchData): Promise<string>;
151
152
  getLatestVersion(): Promise<LatestVersionResponse>;
152
153
  /**
@@ -71,6 +71,13 @@ export interface MediaStore {
71
71
  * @default false
72
72
  */
73
73
  isStatic?: boolean;
74
+ /**
75
+ * Converts a Media object to the value stored in a form field.
76
+ *
77
+ * Typically returns `media.src`. If not implemented, the image field
78
+ * plugin falls back to `media.src`.
79
+ */
80
+ parse?(media: Media): string;
74
81
  }
75
82
  export declare type MediaListOffset = string | number;
76
83
  /**
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ export declare const DisplayOnlyFieldPlugin: {
3
+ name: string;
4
+ Component: (props: any) => React.JSX.Element;
5
+ };
6
+ export declare const InfoBox: ({ message, links, }: {
7
+ message: string;
8
+ links?: {
9
+ text: string;
10
+ url: string;
11
+ }[];
12
+ }) => React.FC<any>;
@@ -18,3 +18,4 @@ export * from './reference-field-plugin';
18
18
  export * from './button-toggle-field-plugin';
19
19
  export * from './hidden-field-plugin';
20
20
  export * from './password-field-plugin';
21
+ export * from './display-only-field-plugin';
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import { Form } from '../forms';
3
+ export declare const BranchDeletedModal: ({ branchName, close, path, values, crudType, tinaForm, }: {
4
+ branchName: string;
5
+ close: () => void;
6
+ path: string;
7
+ values: Record<string, unknown>;
8
+ crudType: string;
9
+ tinaForm?: Form;
10
+ }) => React.JSX.Element;
@@ -1,12 +1,13 @@
1
1
  import * as React from 'react';
2
2
  import { Form } from '../forms';
3
- export declare const CreateBranchModal: ({ close, safeSubmit, path, values, crudType, tinaForm, }: {
3
+ export declare const CreateBranchModal: ({ close, safeSubmit, path, values, crudType, tinaForm, onBaseBranchDeleted, }: {
4
4
  safeSubmit: () => Promise<void>;
5
5
  close: () => void;
6
6
  path: string;
7
7
  values: Record<string, unknown>;
8
8
  crudType: string;
9
9
  tinaForm?: Form;
10
+ onBaseBranchDeleted?: () => void;
10
11
  }) => React.JSX.Element;
11
12
  export declare const PrefixedTextField: ({ label, prefix, ...props }: {
12
13
  [x: string]: any;
@@ -0,0 +1,26 @@
1
+ import { Form } from '../forms';
2
+ export declare const WORKFLOW_STEPS: {
3
+ id: number;
4
+ name: string;
5
+ description: string;
6
+ }[];
7
+ export declare const formatTime: (seconds: number) => string;
8
+ export interface ExecuteWorkflowOptions {
9
+ branchName: string;
10
+ baseBranch: string;
11
+ path: string;
12
+ values: Record<string, unknown>;
13
+ crudType: string;
14
+ tinaForm?: Form;
15
+ }
16
+ export interface UseEditorialWorkflowResult {
17
+ isExecuting: boolean;
18
+ errorMessage: string;
19
+ currentStep: number;
20
+ elapsedTime: number;
21
+ /** Returns true on success, false on failure (error captured in errorMessage) */
22
+ executeWorkflow: (opts: ExecuteWorkflowOptions) => Promise<boolean>;
23
+ /** Reset error/executing state so the form can be retried */
24
+ reset: () => void;
25
+ }
26
+ export declare function useEditorialWorkflow(): UseEditorialWorkflowResult;
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+ interface WorkflowProgressIndicatorProps {
3
+ currentStep: number;
4
+ isExecuting: boolean;
5
+ elapsedTime: number;
6
+ }
7
+ export declare const WorkflowProgressIndicator: ({ currentStep, isExecuting, elapsedTime, }: WorkflowProgressIndicatorProps) => React.JSX.Element;
8
+ export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "tinacms",
3
3
  "type": "module",
4
4
  "typings": "dist/index.d.ts",
5
- "version": "3.7.2",
5
+ "version": "3.7.4",
6
6
  "main": "dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "exports": {
@@ -93,7 +93,7 @@
93
93
  "graphql-tag": "^2.12.6",
94
94
  "is-hotkey": "^0.2.0",
95
95
  "lucide-react": "^0.424.0",
96
- "mermaid": "9.3.0",
96
+ "mermaid": "^11.12.2",
97
97
  "moment": "2.29.4",
98
98
  "moment-timezone": "^0.6.0",
99
99
  "monaco-editor": "0.31.0",
@@ -114,9 +114,9 @@
114
114
  "webfontloader": "1.6.28",
115
115
  "yup": "^1.6.1",
116
116
  "zod": "^3.24.2",
117
- "@tinacms/mdx": "2.1.1",
118
- "@tinacms/schema-tools": "2.7.1",
119
- "@tinacms/search": "1.2.9"
117
+ "@tinacms/mdx": "2.1.2",
118
+ "@tinacms/schema-tools": "2.7.2",
119
+ "@tinacms/search": "1.2.11"
120
120
  },
121
121
  "devDependencies": {
122
122
  "@graphql-tools/utils": "^10.8.1",