tinacms 1.5.4 → 1.5.6

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.
@@ -1,4 +1,12 @@
1
1
  /// <reference types="react" />
2
+ import type { TinaCMS } from '@tinacms/toolkit';
2
3
  declare const CollectionCreatePage: () => JSX.Element;
3
- export declare const RenderForm: ({ cms, collection, folder, templateName, mutationInfo, customDefaults, }: any) => JSX.Element;
4
+ export declare const RenderForm: ({ cms, collection, folder, templateName, mutationInfo, customDefaults, }: {
5
+ cms: TinaCMS;
6
+ collection: any;
7
+ folder: any;
8
+ templateName: any;
9
+ mutationInfo: any;
10
+ customDefaults?: any;
11
+ }) => JSX.Element;
4
12
  export default CollectionCreatePage;
package/dist/index.es.js CHANGED
@@ -1962,6 +1962,9 @@ var styles = `.tina-tailwind {
1962
1962
  --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
1963
1963
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1964
1964
  }
1965
+ .tina-tailwind .outline {
1966
+ outline-style: solid;
1967
+ }
1965
1968
  .tina-tailwind .ring-1 {
1966
1969
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1967
1970
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@@ -4104,6 +4107,18 @@ const RenderForm$1 = ({
4104
4107
  const windowWidth = useWindowWidth();
4105
4108
  const renderNavToggle = windowWidth < navBreakpoint + 1;
4106
4109
  const headerPadding = renderNavToggle ? "px-20" : "px-6";
4110
+ React.useEffect(() => {
4111
+ cms.dispatch({ type: "forms:add", value: form });
4112
+ cms.dispatch({ type: "forms:set-active-form-id", value: form.id });
4113
+ return () => {
4114
+ cms.dispatch({ type: "forms:remove", value: form.id });
4115
+ cms.dispatch({ type: "forms:set-active-form-id", value: null });
4116
+ };
4117
+ }, [JSON.stringify(formInfo.fields)]);
4118
+ if (!cms.state.activeFormId) {
4119
+ return null;
4120
+ }
4121
+ const activeForm = cms.state.forms.find(({ tinaForm }) => tinaForm.id === form.id);
4107
4122
  return /* @__PURE__ */ React.createElement(PageWrapper, null, /* @__PURE__ */ React.createElement(React.Fragment, null, ((_f = (_e = cms == null ? void 0 : cms.api) == null ? void 0 : _e.tina) == null ? void 0 : _f.isLocalMode) ? /* @__PURE__ */ React.createElement(LocalWarning, null) : /* @__PURE__ */ React.createElement(BillingWarning, null), /* @__PURE__ */ React.createElement("div", {
4108
4123
  className: `py-4 border-b border-gray-200 bg-white ${headerPadding}`
4109
4124
  }, /* @__PURE__ */ React.createElement("div", {
@@ -4121,8 +4136,8 @@ const RenderForm$1 = ({
4121
4136
  className: "text-xl text-gray-700 font-medium leading-tight"
4122
4137
  }, "Create New")), /* @__PURE__ */ React.createElement(FormStatus, {
4123
4138
  pristine: formIsPristine
4124
- }))), /* @__PURE__ */ React.createElement(FormBuilder, {
4125
- form,
4139
+ }))), activeForm && /* @__PURE__ */ React.createElement(FormBuilder, {
4140
+ form: activeForm,
4126
4141
  onPristineChange: setFormIsPristine
4127
4142
  })));
4128
4143
  };
@@ -4284,6 +4299,18 @@ const RenderForm = ({
4284
4299
  const windowWidth = useWindowWidth();
4285
4300
  const renderNavToggle = windowWidth < navBreakpoint + 1;
4286
4301
  const headerPadding = renderNavToggle ? "px-20" : "px-6";
4302
+ React.useEffect(() => {
4303
+ cms.dispatch({ type: "forms:add", value: form });
4304
+ cms.dispatch({ type: "forms:set-active-form-id", value: form.id });
4305
+ return () => {
4306
+ cms.dispatch({ type: "forms:remove", value: form.id });
4307
+ cms.dispatch({ type: "forms:set-active-form-id", value: null });
4308
+ };
4309
+ }, [JSON.stringify(document._values)]);
4310
+ if (!cms.state.activeFormId) {
4311
+ return null;
4312
+ }
4313
+ const activeForm = cms.state.forms.find(({ tinaForm }) => tinaForm.id === form.id);
4287
4314
  return /* @__PURE__ */ React.createElement(React.Fragment, null, ((_b = (_a = cms == null ? void 0 : cms.api) == null ? void 0 : _a.tina) == null ? void 0 : _b.isLocalMode) ? /* @__PURE__ */ React.createElement(LocalWarning, null) : /* @__PURE__ */ React.createElement(BillingWarning, null), /* @__PURE__ */ React.createElement("div", {
4288
4315
  className: `py-4 border-b border-gray-200 bg-white ${headerPadding}`
4289
4316
  }, /* @__PURE__ */ React.createElement("div", {
@@ -4301,8 +4328,8 @@ const RenderForm = ({
4301
4328
  className: "text-xl text-gray-700 font-medium leading-tight"
4302
4329
  }, "Edit ", `${filename}.${collection.format}`)), /* @__PURE__ */ React.createElement(FormStatus, {
4303
4330
  pristine: formIsPristine
4304
- }))), /* @__PURE__ */ React.createElement(FormBuilder, {
4305
- form,
4331
+ }))), activeForm && /* @__PURE__ */ React.createElement(FormBuilder, {
4332
+ form: activeForm,
4306
4333
  onPristineChange: setFormIsPristine
4307
4334
  }));
4308
4335
  };
package/dist/index.js CHANGED
@@ -1977,6 +1977,9 @@ mutation addPendingDocumentMutation(
1977
1977
  --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
1978
1978
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1979
1979
  }
1980
+ .tina-tailwind .outline {
1981
+ outline-style: solid;
1982
+ }
1980
1983
  .tina-tailwind .ring-1 {
1981
1984
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1982
1985
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@@ -4119,6 +4122,18 @@ This will work when developing locally but NOT when deployed to production.
4119
4122
  const windowWidth = windowSize.useWindowWidth();
4120
4123
  const renderNavToggle = windowWidth < navBreakpoint + 1;
4121
4124
  const headerPadding = renderNavToggle ? "px-20" : "px-6";
4125
+ React__default["default"].useEffect(() => {
4126
+ cms.dispatch({ type: "forms:add", value: form });
4127
+ cms.dispatch({ type: "forms:set-active-form-id", value: form.id });
4128
+ return () => {
4129
+ cms.dispatch({ type: "forms:remove", value: form.id });
4130
+ cms.dispatch({ type: "forms:set-active-form-id", value: null });
4131
+ };
4132
+ }, [JSON.stringify(formInfo.fields)]);
4133
+ if (!cms.state.activeFormId) {
4134
+ return null;
4135
+ }
4136
+ const activeForm = cms.state.forms.find(({ tinaForm }) => tinaForm.id === form.id);
4122
4137
  return /* @__PURE__ */ React__default["default"].createElement(PageWrapper, null, /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, ((_f = (_e = cms == null ? void 0 : cms.api) == null ? void 0 : _e.tina) == null ? void 0 : _f.isLocalMode) ? /* @__PURE__ */ React__default["default"].createElement(toolkit.LocalWarning, null) : /* @__PURE__ */ React__default["default"].createElement(toolkit.BillingWarning, null), /* @__PURE__ */ React__default["default"].createElement("div", {
4123
4138
  className: `py-4 border-b border-gray-200 bg-white ${headerPadding}`
4124
4139
  }, /* @__PURE__ */ React__default["default"].createElement("div", {
@@ -4136,8 +4151,8 @@ This will work when developing locally but NOT when deployed to production.
4136
4151
  className: "text-xl text-gray-700 font-medium leading-tight"
4137
4152
  }, "Create New")), /* @__PURE__ */ React__default["default"].createElement(toolkit.FormStatus, {
4138
4153
  pristine: formIsPristine
4139
- }))), /* @__PURE__ */ React__default["default"].createElement(toolkit.FormBuilder, {
4140
- form,
4154
+ }))), activeForm && /* @__PURE__ */ React__default["default"].createElement(toolkit.FormBuilder, {
4155
+ form: activeForm,
4141
4156
  onPristineChange: setFormIsPristine
4142
4157
  })));
4143
4158
  };
@@ -4299,6 +4314,18 @@ This will work when developing locally but NOT when deployed to production.
4299
4314
  const windowWidth = windowSize.useWindowWidth();
4300
4315
  const renderNavToggle = windowWidth < navBreakpoint + 1;
4301
4316
  const headerPadding = renderNavToggle ? "px-20" : "px-6";
4317
+ React__default["default"].useEffect(() => {
4318
+ cms.dispatch({ type: "forms:add", value: form });
4319
+ cms.dispatch({ type: "forms:set-active-form-id", value: form.id });
4320
+ return () => {
4321
+ cms.dispatch({ type: "forms:remove", value: form.id });
4322
+ cms.dispatch({ type: "forms:set-active-form-id", value: null });
4323
+ };
4324
+ }, [JSON.stringify(document._values)]);
4325
+ if (!cms.state.activeFormId) {
4326
+ return null;
4327
+ }
4328
+ const activeForm = cms.state.forms.find(({ tinaForm }) => tinaForm.id === form.id);
4302
4329
  return /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, ((_b = (_a = cms == null ? void 0 : cms.api) == null ? void 0 : _a.tina) == null ? void 0 : _b.isLocalMode) ? /* @__PURE__ */ React__default["default"].createElement(toolkit.LocalWarning, null) : /* @__PURE__ */ React__default["default"].createElement(toolkit.BillingWarning, null), /* @__PURE__ */ React__default["default"].createElement("div", {
4303
4330
  className: `py-4 border-b border-gray-200 bg-white ${headerPadding}`
4304
4331
  }, /* @__PURE__ */ React__default["default"].createElement("div", {
@@ -4316,8 +4343,8 @@ This will work when developing locally but NOT when deployed to production.
4316
4343
  className: "text-xl text-gray-700 font-medium leading-tight"
4317
4344
  }, "Edit ", `${filename}.${collection.format}`)), /* @__PURE__ */ React__default["default"].createElement(toolkit.FormStatus, {
4318
4345
  pristine: formIsPristine
4319
- }))), /* @__PURE__ */ React__default["default"].createElement(toolkit.FormBuilder, {
4320
- form,
4346
+ }))), activeForm && /* @__PURE__ */ React__default["default"].createElement(toolkit.FormBuilder, {
4347
+ form: activeForm,
4321
4348
  onPristineChange: setFormIsPristine
4322
4349
  }));
4323
4350
  };
package/dist/react.d.ts CHANGED
@@ -1,7 +1,3 @@
1
- /**
2
- * This is an experimental version of the useTina hook,
3
- * it is only meant to be used with Tina in "iframe mode".
4
- */
5
1
  export declare function useTina<T extends object>(props: {
6
2
  query: string;
7
3
  variables: object;
@@ -19,9 +15,19 @@ export declare function useEditState(): {
19
15
  * is working with.
20
16
  */
21
17
  export declare const tinaField: <T extends object & {
22
- _tina_metadata?: {
23
- id: string;
24
- name?: string;
25
- fields: Record<string, string>;
18
+ _content_source?: {
19
+ queryId: string;
20
+ path: (number | string)[];
26
21
  };
27
- }>(obj: T, field?: Exclude<keyof T, "__typename" | "_sys">) => string;
22
+ }>(object: T, property?: Exclude<keyof T, "__typename" | "_sys">, index?: number) => string;
23
+ export declare const addMetadata: <T extends object>(id: string, object: T & {
24
+ type?: string;
25
+ _content_source?: unknown;
26
+ }, path: (string | number)[]) => T;
27
+ /**
28
+ * This is a pretty rudimentary approach to hashing the query and variables to
29
+ * ensure we treat multiple queries on the page uniquely. It's possible
30
+ * that we would have collisions, and I'm not sure of the likeliness but seems
31
+ * like it'd be rare.
32
+ */
33
+ export declare const hashFromQuery: (input: string) => string;
package/dist/react.es.js CHANGED
@@ -1,23 +1,110 @@
1
1
  import React from "react";
2
2
  function useTina(props) {
3
+ const stringifiedQuery = JSON.stringify({
4
+ query: props.query,
5
+ variables: props.variables
6
+ });
7
+ const id = React.useMemo(() => hashFromQuery(stringifiedQuery), [stringifiedQuery]);
3
8
  const [data, setData] = React.useState(props.data);
4
9
  const [isClient, setIsClient] = React.useState(false);
5
- const id = JSON.stringify({ query: props.query, variables: props.variables });
10
+ const [quickEditEnabled, setQuickEditEnabled] = React.useState(false);
11
+ const [isInTinaIframe, setIsInTinaIframe] = React.useState(false);
6
12
  React.useEffect(() => {
7
13
  setIsClient(true);
8
14
  setData(props.data);
9
15
  }, [id]);
16
+ React.useEffect(() => {
17
+ if (quickEditEnabled) {
18
+ let mouseDownHandler = function(e) {
19
+ const attributeNames = e.target.getAttributeNames();
20
+ const tinaAttribute = attributeNames.find((name) => name.startsWith("data-tina-field"));
21
+ let fieldName;
22
+ if (tinaAttribute) {
23
+ e.preventDefault();
24
+ e.stopPropagation();
25
+ fieldName = e.target.getAttribute(tinaAttribute);
26
+ } else {
27
+ const ancestor = e.target.closest("[data-tina-field], [data-tina-field-overlay]");
28
+ if (ancestor) {
29
+ const attributeNames2 = ancestor.getAttributeNames();
30
+ const tinaAttribute2 = attributeNames2.find((name) => name.startsWith("data-tina-field"));
31
+ if (tinaAttribute2) {
32
+ e.preventDefault();
33
+ e.stopPropagation();
34
+ fieldName = ancestor.getAttribute(tinaAttribute2);
35
+ }
36
+ }
37
+ }
38
+ if (fieldName) {
39
+ if (isInTinaIframe) {
40
+ parent.postMessage({ type: "field:selected", fieldName }, window.location.origin);
41
+ }
42
+ }
43
+ };
44
+ const style = document.createElement("style");
45
+ style.type = "text/css";
46
+ style.textContent = `
47
+ [data-tina-field] {
48
+ outline: 2px dashed rgba(34,150,254,0.5);
49
+ transition: box-shadow ease-out 150ms;
50
+ }
51
+ [data-tina-field]:hover {
52
+ box-shadow: inset 100vi 100vh rgba(34,150,254,0.3);
53
+ outline: 2px solid rgba(34,150,254,1);
54
+ cursor: pointer;
55
+ }
56
+ [data-tina-field-overlay] {
57
+ outline: 2px dashed rgba(34,150,254,0.5);
58
+ position: relative;
59
+ }
60
+ [data-tina-field-overlay]:hover {
61
+ cursor: pointer;
62
+ outline: 2px solid rgba(34,150,254,1);
63
+ }
64
+ [data-tina-field-overlay]::after {
65
+ content: '';
66
+ position: absolute;
67
+ inset: 0;
68
+ z-index: 20;
69
+ transition: opacity ease-out 150ms;
70
+ background-color: rgba(34,150,254,0.3);
71
+ opacity: 0;
72
+ }
73
+ [data-tina-field-overlay]:hover::after {
74
+ opacity: 1;
75
+ }
76
+ `;
77
+ document.head.appendChild(style);
78
+ document.body.classList.add("__tina-quick-editing-enabled");
79
+ document.addEventListener("click", mouseDownHandler, true);
80
+ return () => {
81
+ document.removeEventListener("click", mouseDownHandler, true);
82
+ document.body.classList.remove("__tina-quick-editing-enabled");
83
+ style.remove();
84
+ };
85
+ }
86
+ }, [quickEditEnabled, isInTinaIframe]);
10
87
  React.useEffect(() => {
11
88
  parent.postMessage({ type: "open", ...props, id }, window.location.origin);
12
89
  window.addEventListener("message", (event) => {
90
+ if (event.data.type === "quickEditEnabled") {
91
+ setQuickEditEnabled(event.data.value);
92
+ }
13
93
  if (event.data.id === id && event.data.type === "updateData") {
14
94
  setData(event.data.data);
95
+ setIsInTinaIframe(true);
96
+ const anyTinaField = document.querySelector("[data-tina-field]");
97
+ if (anyTinaField) {
98
+ parent.postMessage({ type: "quick-edit", value: true }, window.location.origin);
99
+ } else {
100
+ parent.postMessage({ type: "quick-edit", value: false }, window.location.origin);
101
+ }
15
102
  }
16
103
  });
17
104
  return () => {
18
105
  parent.postMessage({ type: "close", id }, window.location.origin);
19
106
  };
20
- }, [id]);
107
+ }, [id, setQuickEditEnabled]);
21
108
  return { data, isClient };
22
109
  }
23
110
  function useEditState() {
@@ -35,16 +122,86 @@ function useEditState() {
35
122
  }, []);
36
123
  return { edit };
37
124
  }
38
- const tinaField = (obj, field) => {
125
+ const tinaField = (object, property, index) => {
39
126
  var _a, _b, _c;
40
- if (!field) {
41
- return `${(_a = obj._tina_metadata) == null ? void 0 : _a.id}#${(_b = obj._tina_metadata) == null ? void 0 : _b.name}`;
42
- }
43
- if (obj == null ? void 0 : obj._tina_metadata) {
44
- if (typeof field === "string") {
45
- return `${(_c = obj._tina_metadata) == null ? void 0 : _c.id}#${obj._tina_metadata.fields[field]}`;
127
+ if (object._content_source) {
128
+ if (!property) {
129
+ return [
130
+ (_a = object._content_source) == null ? void 0 : _a.queryId,
131
+ object._content_source.path.join(".")
132
+ ].join("---");
133
+ }
134
+ if (typeof index === "number") {
135
+ return [
136
+ (_b = object._content_source) == null ? void 0 : _b.queryId,
137
+ [...object._content_source.path, property, index].join(".")
138
+ ].join("---");
46
139
  }
140
+ return [
141
+ (_c = object._content_source) == null ? void 0 : _c.queryId,
142
+ [...object._content_source.path, property].join(".")
143
+ ].join("---");
47
144
  }
48
145
  return "";
49
146
  };
50
- export { tinaField, useEditState, useTina };
147
+ const addMetadata = (id, object, path) => {
148
+ Object.entries(object).forEach(([key, value]) => {
149
+ if (Array.isArray(value)) {
150
+ value.forEach((item, index) => {
151
+ if (isScalarOrUndefined(item)) {
152
+ return;
153
+ }
154
+ if (Array.isArray(item)) {
155
+ return;
156
+ }
157
+ const itemObject = item;
158
+ addMetadata(id, itemObject, [...path, key, index]);
159
+ });
160
+ } else {
161
+ if (isScalarOrUndefined(value)) {
162
+ return;
163
+ }
164
+ const itemObject = value;
165
+ addMetadata(id, itemObject, [...path, key]);
166
+ }
167
+ });
168
+ if ((object == null ? void 0 : object.type) === "root") {
169
+ return;
170
+ }
171
+ object._content_source = {
172
+ queryId: id,
173
+ path
174
+ };
175
+ return object;
176
+ };
177
+ function isScalarOrUndefined(value) {
178
+ const type = typeof value;
179
+ if (type === "string")
180
+ return true;
181
+ if (type === "number")
182
+ return true;
183
+ if (type === "boolean")
184
+ return true;
185
+ if (type === "undefined")
186
+ return true;
187
+ if (value == null)
188
+ return true;
189
+ if (value instanceof String)
190
+ return true;
191
+ if (value instanceof Number)
192
+ return true;
193
+ if (value instanceof Boolean)
194
+ return true;
195
+ return false;
196
+ }
197
+ const hashFromQuery = (input) => {
198
+ let hash = 0;
199
+ for (let i = 0; i < input.length; i++) {
200
+ const char = input.charCodeAt(i);
201
+ hash = (hash << 5) - hash + char & 4294967295;
202
+ }
203
+ const nonNegativeHash = Math.abs(hash);
204
+ const alphanumericHash = nonNegativeHash.toString(36);
205
+ return alphanumericHash;
206
+ };
207
+ export { addMetadata, hashFromQuery, tinaField, useEditState, useTina };
package/dist/react.js CHANGED
@@ -7,24 +7,111 @@
7
7
  }
8
8
  var React__default = /* @__PURE__ */ _interopDefaultLegacy(React);
9
9
  function useTina(props) {
10
+ const stringifiedQuery = JSON.stringify({
11
+ query: props.query,
12
+ variables: props.variables
13
+ });
14
+ const id = React__default["default"].useMemo(() => hashFromQuery(stringifiedQuery), [stringifiedQuery]);
10
15
  const [data, setData] = React__default["default"].useState(props.data);
11
16
  const [isClient, setIsClient] = React__default["default"].useState(false);
12
- const id = JSON.stringify({ query: props.query, variables: props.variables });
17
+ const [quickEditEnabled, setQuickEditEnabled] = React__default["default"].useState(false);
18
+ const [isInTinaIframe, setIsInTinaIframe] = React__default["default"].useState(false);
13
19
  React__default["default"].useEffect(() => {
14
20
  setIsClient(true);
15
21
  setData(props.data);
16
22
  }, [id]);
23
+ React__default["default"].useEffect(() => {
24
+ if (quickEditEnabled) {
25
+ let mouseDownHandler = function(e) {
26
+ const attributeNames = e.target.getAttributeNames();
27
+ const tinaAttribute = attributeNames.find((name) => name.startsWith("data-tina-field"));
28
+ let fieldName;
29
+ if (tinaAttribute) {
30
+ e.preventDefault();
31
+ e.stopPropagation();
32
+ fieldName = e.target.getAttribute(tinaAttribute);
33
+ } else {
34
+ const ancestor = e.target.closest("[data-tina-field], [data-tina-field-overlay]");
35
+ if (ancestor) {
36
+ const attributeNames2 = ancestor.getAttributeNames();
37
+ const tinaAttribute2 = attributeNames2.find((name) => name.startsWith("data-tina-field"));
38
+ if (tinaAttribute2) {
39
+ e.preventDefault();
40
+ e.stopPropagation();
41
+ fieldName = ancestor.getAttribute(tinaAttribute2);
42
+ }
43
+ }
44
+ }
45
+ if (fieldName) {
46
+ if (isInTinaIframe) {
47
+ parent.postMessage({ type: "field:selected", fieldName }, window.location.origin);
48
+ }
49
+ }
50
+ };
51
+ const style = document.createElement("style");
52
+ style.type = "text/css";
53
+ style.textContent = `
54
+ [data-tina-field] {
55
+ outline: 2px dashed rgba(34,150,254,0.5);
56
+ transition: box-shadow ease-out 150ms;
57
+ }
58
+ [data-tina-field]:hover {
59
+ box-shadow: inset 100vi 100vh rgba(34,150,254,0.3);
60
+ outline: 2px solid rgba(34,150,254,1);
61
+ cursor: pointer;
62
+ }
63
+ [data-tina-field-overlay] {
64
+ outline: 2px dashed rgba(34,150,254,0.5);
65
+ position: relative;
66
+ }
67
+ [data-tina-field-overlay]:hover {
68
+ cursor: pointer;
69
+ outline: 2px solid rgba(34,150,254,1);
70
+ }
71
+ [data-tina-field-overlay]::after {
72
+ content: '';
73
+ position: absolute;
74
+ inset: 0;
75
+ z-index: 20;
76
+ transition: opacity ease-out 150ms;
77
+ background-color: rgba(34,150,254,0.3);
78
+ opacity: 0;
79
+ }
80
+ [data-tina-field-overlay]:hover::after {
81
+ opacity: 1;
82
+ }
83
+ `;
84
+ document.head.appendChild(style);
85
+ document.body.classList.add("__tina-quick-editing-enabled");
86
+ document.addEventListener("click", mouseDownHandler, true);
87
+ return () => {
88
+ document.removeEventListener("click", mouseDownHandler, true);
89
+ document.body.classList.remove("__tina-quick-editing-enabled");
90
+ style.remove();
91
+ };
92
+ }
93
+ }, [quickEditEnabled, isInTinaIframe]);
17
94
  React__default["default"].useEffect(() => {
18
95
  parent.postMessage({ type: "open", ...props, id }, window.location.origin);
19
96
  window.addEventListener("message", (event) => {
97
+ if (event.data.type === "quickEditEnabled") {
98
+ setQuickEditEnabled(event.data.value);
99
+ }
20
100
  if (event.data.id === id && event.data.type === "updateData") {
21
101
  setData(event.data.data);
102
+ setIsInTinaIframe(true);
103
+ const anyTinaField = document.querySelector("[data-tina-field]");
104
+ if (anyTinaField) {
105
+ parent.postMessage({ type: "quick-edit", value: true }, window.location.origin);
106
+ } else {
107
+ parent.postMessage({ type: "quick-edit", value: false }, window.location.origin);
108
+ }
22
109
  }
23
110
  });
24
111
  return () => {
25
112
  parent.postMessage({ type: "close", id }, window.location.origin);
26
113
  };
27
- }, [id]);
114
+ }, [id, setQuickEditEnabled]);
28
115
  return { data, isClient };
29
116
  }
30
117
  function useEditState() {
@@ -42,18 +129,90 @@
42
129
  }, []);
43
130
  return { edit };
44
131
  }
45
- const tinaField = (obj, field) => {
132
+ const tinaField = (object, property, index) => {
46
133
  var _a, _b, _c;
47
- if (!field) {
48
- return `${(_a = obj._tina_metadata) == null ? void 0 : _a.id}#${(_b = obj._tina_metadata) == null ? void 0 : _b.name}`;
49
- }
50
- if (obj == null ? void 0 : obj._tina_metadata) {
51
- if (typeof field === "string") {
52
- return `${(_c = obj._tina_metadata) == null ? void 0 : _c.id}#${obj._tina_metadata.fields[field]}`;
134
+ if (object._content_source) {
135
+ if (!property) {
136
+ return [
137
+ (_a = object._content_source) == null ? void 0 : _a.queryId,
138
+ object._content_source.path.join(".")
139
+ ].join("---");
140
+ }
141
+ if (typeof index === "number") {
142
+ return [
143
+ (_b = object._content_source) == null ? void 0 : _b.queryId,
144
+ [...object._content_source.path, property, index].join(".")
145
+ ].join("---");
53
146
  }
147
+ return [
148
+ (_c = object._content_source) == null ? void 0 : _c.queryId,
149
+ [...object._content_source.path, property].join(".")
150
+ ].join("---");
54
151
  }
55
152
  return "";
56
153
  };
154
+ const addMetadata = (id, object, path) => {
155
+ Object.entries(object).forEach(([key, value]) => {
156
+ if (Array.isArray(value)) {
157
+ value.forEach((item, index) => {
158
+ if (isScalarOrUndefined(item)) {
159
+ return;
160
+ }
161
+ if (Array.isArray(item)) {
162
+ return;
163
+ }
164
+ const itemObject = item;
165
+ addMetadata(id, itemObject, [...path, key, index]);
166
+ });
167
+ } else {
168
+ if (isScalarOrUndefined(value)) {
169
+ return;
170
+ }
171
+ const itemObject = value;
172
+ addMetadata(id, itemObject, [...path, key]);
173
+ }
174
+ });
175
+ if ((object == null ? void 0 : object.type) === "root") {
176
+ return;
177
+ }
178
+ object._content_source = {
179
+ queryId: id,
180
+ path
181
+ };
182
+ return object;
183
+ };
184
+ function isScalarOrUndefined(value) {
185
+ const type = typeof value;
186
+ if (type === "string")
187
+ return true;
188
+ if (type === "number")
189
+ return true;
190
+ if (type === "boolean")
191
+ return true;
192
+ if (type === "undefined")
193
+ return true;
194
+ if (value == null)
195
+ return true;
196
+ if (value instanceof String)
197
+ return true;
198
+ if (value instanceof Number)
199
+ return true;
200
+ if (value instanceof Boolean)
201
+ return true;
202
+ return false;
203
+ }
204
+ const hashFromQuery = (input) => {
205
+ let hash = 0;
206
+ for (let i = 0; i < input.length; i++) {
207
+ const char = input.charCodeAt(i);
208
+ hash = (hash << 5) - hash + char & 4294967295;
209
+ }
210
+ const nonNegativeHash = Math.abs(hash);
211
+ const alphanumericHash = nonNegativeHash.toString(36);
212
+ return alphanumericHash;
213
+ };
214
+ exports2.addMetadata = addMetadata;
215
+ exports2.hashFromQuery = hashFromQuery;
57
216
  exports2.tinaField = tinaField;
58
217
  exports2.useEditState = useEditState;
59
218
  exports2.useTina = useTina;
package/dist/style.css CHANGED
@@ -991,6 +991,9 @@
991
991
  --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
992
992
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
993
993
  }
994
+ .tina-tailwind .outline {
995
+ outline-style: solid;
996
+ }
994
997
  .tina-tailwind .ring-1 {
995
998
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
996
999
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinacms",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "main": "dist/index.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "exports": {
@@ -56,9 +56,9 @@
56
56
  "@headlessui/react": "^1.5.0",
57
57
  "@heroicons/react": "^1.0.4",
58
58
  "@react-hook/window-size": "^3.0.7",
59
- "@tinacms/schema-tools": "1.4.3",
59
+ "@tinacms/schema-tools": "1.4.4",
60
60
  "@tinacms/sharedctx": "1.0.1",
61
- "@tinacms/toolkit": "1.7.1",
61
+ "@tinacms/toolkit": "1.7.2",
62
62
  "crypto-js": "^4.0.0",
63
63
  "encoding": "0.1.13",
64
64
  "fetch-ponyfill": "^7.1.0",
@@ -68,7 +68,7 @@
68
68
  "lodash.set": "^4.3.2",
69
69
  "prism-react-renderer": "^1.3.5",
70
70
  "react-icons": "^4.3.1",
71
- "react-router-dom": "6",
71
+ "react-router-dom": "6.3.0",
72
72
  "yup": "^0.32.0",
73
73
  "zod": "^3.14.3"
74
74
  },