@tinacms/app 0.0.0-003e348-20251023020516
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/CHANGELOG.md +1203 -0
- package/LICENSE +176 -0
- package/index.html +69 -0
- package/package.json +32 -0
- package/src/App.tsx +79 -0
- package/src/Playground.tsx +161 -0
- package/src/dummy-client.ts +1 -0
- package/src/fields/rich-text/index.tsx +6 -0
- package/src/fields/rich-text/monaco/error-message.tsx +118 -0
- package/src/fields/rich-text/monaco/index.tsx +230 -0
- package/src/fields/rich-text/monaco/use-debounce.ts +25 -0
- package/src/global.css +120 -0
- package/src/index.css +388 -0
- package/src/lib/build-form.ts +49 -0
- package/src/lib/errors.tsx +24 -0
- package/src/lib/expand-query.ts +273 -0
- package/src/lib/graphql-reducer.ts +1014 -0
- package/src/lib/types.ts +53 -0
- package/src/lib/util.ts +129 -0
- package/src/main.tsx +12 -0
- package/src/preflight.css +233 -0
- package/src/preview.tsx +27 -0
- package/src/vite-env.d.ts +8 -0
- package/tsconfig.json +32 -0
- package/tsconfig.node.json +10 -0
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type PostMessage =
|
|
2
|
+
| {
|
|
3
|
+
type: 'open' | 'close' | 'isEditMode';
|
|
4
|
+
id: string;
|
|
5
|
+
data: object;
|
|
6
|
+
}
|
|
7
|
+
| { type: 'field:selected'; fieldName: string }
|
|
8
|
+
| { type: 'quick-edit'; value: boolean }
|
|
9
|
+
| { type: 'user-select-form'; formId: string }
|
|
10
|
+
| { type: 'url-changed' };
|
|
11
|
+
|
|
12
|
+
export type Payload = {
|
|
13
|
+
id: string;
|
|
14
|
+
variables: object;
|
|
15
|
+
query: string;
|
|
16
|
+
data: object;
|
|
17
|
+
expandedQuery?: string;
|
|
18
|
+
expandedData?: object;
|
|
19
|
+
expandedQueryForResolver?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SystemInfo = {
|
|
23
|
+
breadcrumbs: string[];
|
|
24
|
+
basename: string;
|
|
25
|
+
filename: string;
|
|
26
|
+
path: string;
|
|
27
|
+
extension: string;
|
|
28
|
+
relativePath: string;
|
|
29
|
+
title?: string | null | undefined;
|
|
30
|
+
template: string;
|
|
31
|
+
// __typename: string
|
|
32
|
+
collection: {
|
|
33
|
+
name: string;
|
|
34
|
+
slug: string;
|
|
35
|
+
label: string;
|
|
36
|
+
path: string;
|
|
37
|
+
format?: string | null | undefined;
|
|
38
|
+
matches?: string | null | undefined;
|
|
39
|
+
// templates?: object
|
|
40
|
+
// fields?: object
|
|
41
|
+
// __typename: string
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type Document = {
|
|
46
|
+
_values: Record<string, unknown>;
|
|
47
|
+
_sys: SystemInfo;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ResolvedDocument = {
|
|
51
|
+
_internalValues: Record<string, unknown>;
|
|
52
|
+
_internalSys: SystemInfo;
|
|
53
|
+
};
|
package/src/lib/util.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const charCodeOfDot = '.'.charCodeAt(0);
|
|
2
|
+
const reEscapeChar = /\\(\\)?/g;
|
|
3
|
+
const rePropName = RegExp(
|
|
4
|
+
// Match anything that isn't a dot or bracket.
|
|
5
|
+
'[^.[\\]]+' +
|
|
6
|
+
'|' +
|
|
7
|
+
// Or match property names within brackets.
|
|
8
|
+
'\\[(?:' +
|
|
9
|
+
// Match a non-string expression.
|
|
10
|
+
'([^"\'][^[]*)' +
|
|
11
|
+
'|' +
|
|
12
|
+
// Or match strings (supports escaping characters).
|
|
13
|
+
'(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
|
|
14
|
+
')\\]' +
|
|
15
|
+
'|' +
|
|
16
|
+
// Or match "" as the space between consecutive dots or empty brackets.
|
|
17
|
+
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))',
|
|
18
|
+
'g'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Converts `string` to a property path array.
|
|
23
|
+
*
|
|
24
|
+
* @private
|
|
25
|
+
* @param {string} string The string to convert.
|
|
26
|
+
* @returns {Array} Returns the property path array.
|
|
27
|
+
*/
|
|
28
|
+
const stringToPath = (string: string) => {
|
|
29
|
+
const result = [];
|
|
30
|
+
if (string.charCodeAt(0) === charCodeOfDot) {
|
|
31
|
+
result.push('');
|
|
32
|
+
}
|
|
33
|
+
string.replace(rePropName, (match, expression, quote, subString) => {
|
|
34
|
+
let key = match;
|
|
35
|
+
if (quote) {
|
|
36
|
+
key = subString.replace(reEscapeChar, '$1');
|
|
37
|
+
} else if (expression) {
|
|
38
|
+
key = expression.trim();
|
|
39
|
+
}
|
|
40
|
+
result.push(key);
|
|
41
|
+
});
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const keysCache: { [key: string]: string[] } = {};
|
|
46
|
+
const keysRegex = /[.[\]]+/;
|
|
47
|
+
|
|
48
|
+
const toPath = (key: string): string[] => {
|
|
49
|
+
if (key === null || key === undefined || !key.length) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
if (typeof key !== 'string') {
|
|
53
|
+
throw new Error('toPath() expects a string');
|
|
54
|
+
}
|
|
55
|
+
if (keysCache[key] == null) {
|
|
56
|
+
/**
|
|
57
|
+
* The following patch fixes issue 456, introduced since v4.20.3:
|
|
58
|
+
*
|
|
59
|
+
* Before v4.20.3, i.e. in v4.20.2, a `key` like 'choices[]' would map to ['choices']
|
|
60
|
+
* (e.g. an array of choices used where 'choices[]' is name attribute of an input of type checkbox).
|
|
61
|
+
*
|
|
62
|
+
* Since v4.20.3, a `key` like 'choices[]' would map to ['choices', ''] which is wrong and breaks
|
|
63
|
+
* this kind of inputs e.g. in React.
|
|
64
|
+
*
|
|
65
|
+
* v4.20.3 introduced an unwanted breaking change, this patch fixes it, see the issue at the link below.
|
|
66
|
+
*
|
|
67
|
+
* @see https://github.com/final-form/final-form/issues/456
|
|
68
|
+
*/
|
|
69
|
+
if (key.endsWith('[]')) {
|
|
70
|
+
// v4.20.2 (a `key` like 'choices[]' should map to ['choices'], which is fine).
|
|
71
|
+
keysCache[key] = key.split(keysRegex).filter(Boolean);
|
|
72
|
+
} else {
|
|
73
|
+
// v4.20.3 (a `key` like 'choices[]' maps to ['choices', ''], which breaks applications relying on inputs like `<input type="checkbox" name="choices[]" />`).
|
|
74
|
+
keysCache[key] = stringToPath(key);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return keysCache[key];
|
|
78
|
+
};
|
|
79
|
+
export const getDeepestMetadata = (state: Object, complexKey: string): any => {
|
|
80
|
+
// Intentionally using iteration rather than recursion
|
|
81
|
+
const path = toPath(complexKey);
|
|
82
|
+
let current: any = state;
|
|
83
|
+
let metadata: any;
|
|
84
|
+
for (let i = 0; i < path.length; i++) {
|
|
85
|
+
const key = path[i];
|
|
86
|
+
if (
|
|
87
|
+
current === undefined ||
|
|
88
|
+
current === null ||
|
|
89
|
+
typeof current !== 'object' ||
|
|
90
|
+
(Array.isArray(current) && isNaN(Number(key)))
|
|
91
|
+
) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const value = current[key];
|
|
95
|
+
if (value?._tina_metadata) {
|
|
96
|
+
// We're at a reference field, we don't want to select the
|
|
97
|
+
// reference form, just the reference select field
|
|
98
|
+
if (complexKey === value._tina_metadata?.prefix && metadata) {
|
|
99
|
+
} else {
|
|
100
|
+
metadata = value._tina_metadata;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
current = value;
|
|
104
|
+
}
|
|
105
|
+
return metadata;
|
|
106
|
+
};
|
|
107
|
+
export const getFormAndFieldNameFromMetadata = (
|
|
108
|
+
object: object,
|
|
109
|
+
eventFieldName: string
|
|
110
|
+
) => {
|
|
111
|
+
const metadata = getDeepestMetadata(object, eventFieldName);
|
|
112
|
+
|
|
113
|
+
if (!metadata) {
|
|
114
|
+
console.warn(
|
|
115
|
+
'[getFormAndFieldNameFromMetadata] No metadata found for:',
|
|
116
|
+
eventFieldName
|
|
117
|
+
);
|
|
118
|
+
return { formId: undefined, fieldName: undefined };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const { id: formId, prefix } = metadata;
|
|
122
|
+
const prefixLength = prefix?.length ?? 0;
|
|
123
|
+
const localFieldName = eventFieldName.slice(prefixLength + 1);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
formId,
|
|
127
|
+
fieldName: localFieldName,
|
|
128
|
+
};
|
|
129
|
+
};
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import App from './App';
|
|
4
|
+
import './index.css';
|
|
5
|
+
|
|
6
|
+
const container = document.getElementById('root');
|
|
7
|
+
const root = createRoot(container!);
|
|
8
|
+
root.render(
|
|
9
|
+
<React.StrictMode>
|
|
10
|
+
<App />
|
|
11
|
+
</React.StrictMode>
|
|
12
|
+
);
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
body {
|
|
2
|
+
line-height: 1.5;
|
|
3
|
+
-webkit-text-size-adjust: 100%;
|
|
4
|
+
-moz-tab-size: 4;
|
|
5
|
+
tab-size: 4;
|
|
6
|
+
|
|
7
|
+
*,
|
|
8
|
+
::before,
|
|
9
|
+
::after {
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
border-width: 0;
|
|
12
|
+
border-style: solid;
|
|
13
|
+
border-color: transparent;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
::before,
|
|
17
|
+
::after {
|
|
18
|
+
--tw-content: "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
hr {
|
|
22
|
+
height: 0; /* 1 */
|
|
23
|
+
color: inherit; /* 2 */
|
|
24
|
+
border-top-width: 1px; /* 3 */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
abbr:where([title]) {
|
|
28
|
+
text-decoration: underline dotted;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
h1,
|
|
32
|
+
h2,
|
|
33
|
+
h3,
|
|
34
|
+
h4,
|
|
35
|
+
h5,
|
|
36
|
+
h6 {
|
|
37
|
+
font-size: inherit;
|
|
38
|
+
font-weight: inherit;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
a {
|
|
42
|
+
color: inherit;
|
|
43
|
+
text-decoration: inherit;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
b,
|
|
47
|
+
strong {
|
|
48
|
+
font-weight: bolder;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
code,
|
|
52
|
+
kbd,
|
|
53
|
+
samp,
|
|
54
|
+
pre {
|
|
55
|
+
font-family: theme(
|
|
56
|
+
"fontFamily.mono",
|
|
57
|
+
ui-monospace,
|
|
58
|
+
SFMono-Regular,
|
|
59
|
+
Menlo,
|
|
60
|
+
Monaco,
|
|
61
|
+
Consolas,
|
|
62
|
+
"Liberation Mono",
|
|
63
|
+
"Courier New",
|
|
64
|
+
monospace
|
|
65
|
+
); /* 1 */
|
|
66
|
+
font-size: 1em; /* 2 */
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
small {
|
|
70
|
+
font-size: 80%;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
sub,
|
|
74
|
+
sup {
|
|
75
|
+
font-size: 75%;
|
|
76
|
+
line-height: 0;
|
|
77
|
+
position: relative;
|
|
78
|
+
vertical-align: baseline;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
sub {
|
|
82
|
+
bottom: -0.25em;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
sup {
|
|
86
|
+
top: -0.5em;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
table {
|
|
90
|
+
text-indent: 0; /* 1 */
|
|
91
|
+
border-color: inherit; /* 2 */
|
|
92
|
+
border-collapse: collapse; /* 3 */
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
button,
|
|
96
|
+
input,
|
|
97
|
+
optgroup,
|
|
98
|
+
select,
|
|
99
|
+
textarea {
|
|
100
|
+
font-family: inherit; /* 1 */
|
|
101
|
+
font-size: 100%; /* 1 */
|
|
102
|
+
line-height: inherit; /* 1 */
|
|
103
|
+
color: inherit; /* 1 */
|
|
104
|
+
margin: 0; /* 2 */
|
|
105
|
+
padding: 0; /* 3 */
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
button,
|
|
109
|
+
select {
|
|
110
|
+
text-transform: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
button,
|
|
114
|
+
[type="button"],
|
|
115
|
+
[type="reset"],
|
|
116
|
+
[type="submit"] {
|
|
117
|
+
-webkit-appearance: button; /* 1 */
|
|
118
|
+
background-image: none; /* 2 */
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:-moz-focusring {
|
|
122
|
+
outline: auto;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
:-moz-ui-invalid {
|
|
126
|
+
box-shadow: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
progress {
|
|
130
|
+
vertical-align: baseline;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
::-webkit-inner-spin-button,
|
|
134
|
+
::-webkit-outer-spin-button {
|
|
135
|
+
height: auto;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
[type="search"] {
|
|
139
|
+
-webkit-appearance: textfield; /* 1 */
|
|
140
|
+
outline-offset: -2px; /* 2 */
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
::-webkit-search-decoration {
|
|
144
|
+
-webkit-appearance: none;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
::-webkit-file-upload-button {
|
|
148
|
+
-webkit-appearance: button; /* 1 */
|
|
149
|
+
font: inherit; /* 2 */
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
summary {
|
|
153
|
+
display: list-item;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
blockquote,
|
|
157
|
+
dl,
|
|
158
|
+
dd,
|
|
159
|
+
h1,
|
|
160
|
+
h2,
|
|
161
|
+
h3,
|
|
162
|
+
h4,
|
|
163
|
+
h5,
|
|
164
|
+
h6,
|
|
165
|
+
hr,
|
|
166
|
+
figure,
|
|
167
|
+
p,
|
|
168
|
+
pre {
|
|
169
|
+
margin: 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fieldset {
|
|
173
|
+
margin: 0;
|
|
174
|
+
padding: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
legend {
|
|
178
|
+
padding: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
ol,
|
|
182
|
+
ul,
|
|
183
|
+
menu {
|
|
184
|
+
list-style: none;
|
|
185
|
+
margin: 0;
|
|
186
|
+
padding: 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
li:before {
|
|
190
|
+
display: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
textarea {
|
|
194
|
+
resize: vertical;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
input::placeholder,
|
|
198
|
+
textarea::placeholder {
|
|
199
|
+
opacity: 1; /* 1 */
|
|
200
|
+
color: theme("colors.gray.400", #9ca3af); /* 2 */
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
button,
|
|
204
|
+
[role="button"] {
|
|
205
|
+
cursor: pointer;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
:disabled {
|
|
209
|
+
cursor: default;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
img,
|
|
213
|
+
svg,
|
|
214
|
+
video,
|
|
215
|
+
canvas,
|
|
216
|
+
audio,
|
|
217
|
+
iframe,
|
|
218
|
+
embed,
|
|
219
|
+
object {
|
|
220
|
+
display: block; /* 1 */
|
|
221
|
+
vertical-align: middle; /* 2 */
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
img,
|
|
225
|
+
video {
|
|
226
|
+
max-width: 100%;
|
|
227
|
+
height: auto;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
[hidden] {
|
|
231
|
+
display: none;
|
|
232
|
+
}
|
|
233
|
+
}
|
package/src/preview.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { defineConfig } from 'tinacms';
|
|
6
|
+
import { useGraphQLReducer } from './lib/graphql-reducer';
|
|
7
|
+
|
|
8
|
+
type Config = Parameters<typeof defineConfig>[0];
|
|
9
|
+
|
|
10
|
+
export const Preview = (
|
|
11
|
+
props: Config & {
|
|
12
|
+
url: string;
|
|
13
|
+
iframeRef: React.MutableRefObject<HTMLIFrameElement>;
|
|
14
|
+
}
|
|
15
|
+
) => {
|
|
16
|
+
useGraphQLReducer(props.iframeRef, props.url);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<iframe
|
|
20
|
+
data-test='tina-iframe'
|
|
21
|
+
id='tina-iframe'
|
|
22
|
+
ref={props.iframeRef}
|
|
23
|
+
className='h-screen w-full bg-white'
|
|
24
|
+
src={props.url}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": [
|
|
6
|
+
"DOM",
|
|
7
|
+
"DOM.Iterable",
|
|
8
|
+
"ESNext"
|
|
9
|
+
],
|
|
10
|
+
"allowJs": false,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"esModuleInterop": false,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"module": "ESNext",
|
|
17
|
+
"moduleResolution": "Node",
|
|
18
|
+
"resolveJsonModule": true,
|
|
19
|
+
"isolatedModules": true,
|
|
20
|
+
"noEmit": true,
|
|
21
|
+
"jsx": "react-jsx"
|
|
22
|
+
},
|
|
23
|
+
"exclude": [
|
|
24
|
+
"node_modules",
|
|
25
|
+
"**/node_modules/*"
|
|
26
|
+
],
|
|
27
|
+
"references": [
|
|
28
|
+
{
|
|
29
|
+
"path": "./tsconfig.node.json"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|