tinacms 0.66.8 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # tinacms
2
2
 
3
+ ## 0.67.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 86651039b: Updates to the way forms are generated in contextual editing. This lays the groundwork for
8
+ future updates but doesn't offer new behavior yet. It does come with a small breaking change:
9
+
10
+ [BREAKING]: The `id` of forms is now the actual document `path`. Previously this was the name of the GraphQL query node (eg. `getPostDocument`).
11
+ If you're using the [`formifyCallback`](https://tina.io/docs/advanced/customizing-forms/#customizing-a-form) prop to create global forms, you'll probably need to update the callback to check for the appropriate id.
12
+
13
+ Eg. `formConfig.id === 'getSiteNavsDocument'` should be something like `formConfig.id === 'content/navs/mynav.md'`
14
+
15
+ If you're experiencing any issues with contextual editing, you can disable this flag for now by specifying `cms.flags.set('use-unstable-formify', false)`.
16
+
17
+ ## 0.66.10
18
+
19
+ ### Patch Changes
20
+
21
+ - 39a8c4f7d: Fix issue with unstableFormify where `Document` interface fields were not being formified properly
22
+ - ec28a129b: Update to tina admin to use the frontend schema
23
+ - a28b787c5: With the rich-text editor, inserting a soft-break (`shift+enter`), this will now result in a `<br>` tag being inserted. Note that this will save the markdown with a backslash to indicate line break (instead of multiple empty spaces):
24
+
25
+ ```markdown
26
+ 123 Abc St\
27
+ Charlottetown, PEI
28
+ ```
29
+
30
+ - 93363dfc2: Prevent error on reset when nested blocks have changed in unstable formify hook
31
+ - abf25c673: The schema can now to used on the frontend (optional for now but will be the main path moving forward).
32
+
33
+ ### How to migrate.
34
+
35
+ If you gone though the `tinacms init` process there should be a file called `.tina/components/TinaProvider`. In that file you can import the schema from `schema.ts` and add it to the TinaCMS wrapper component.
36
+
37
+ ```tsx
38
+ import TinaCMS from 'tinacms'
39
+ import schema, { tinaConfig } from '../schema.ts'
40
+
41
+ // Importing the TinaProvider directly into your page will cause Tina to be added to the production bundle.
42
+ // Instead, import the tina/provider/index default export to have it dynamially imported in edit-moode
43
+ /**
44
+ *
45
+ * @private Do not import this directly, please import the dynamic provider instead
46
+ */
47
+ const TinaProvider = ({ children }) => {
48
+ return (
49
+ <TinaCMS {...tinaConfig} schema={schema}>
50
+ {children}
51
+ </TinaCMS>
52
+ )
53
+ }
54
+
55
+ export default TinaProvider
56
+ ```
57
+
58
+ - 591640db0: Fixes a bug with `breadcrumbs` to account for subfolders (instead of just the `filename`) and allows Documents to be created and updated within subfolders.
59
+
60
+ Before this fix, `breadcrumbs` was only the `basename` of the file minus the `extension`. So `my-folder-a/my-folder-b/my-file.md` would have `breadcrumbs` of `['my-file']`. With this change, `breadcrumbs` will be `['my-folder-a','my-folder-b','my-file']` (leaving out the `content/<collection>`).
61
+
62
+ - 875779ac6: Don't attempt to formify nodes which don't have data fields (ie. ...on Node)
63
+ - e8b0de1f7: Add `parentTypename` to fields to allow us to disambiguate between fields which have the same field names but different types. Example, an event from field name of `blocks.0.title` could belong to a `Cta` block or a `Hero` block, both of which have a `title` field.
64
+ - Updated dependencies [429d8e93e]
65
+ - Updated dependencies [6c517b5da]
66
+ - Updated dependencies [e81cf8867]
67
+ - Updated dependencies [abf25c673]
68
+ - Updated dependencies [801f39f62]
69
+ - Updated dependencies [0e270d878]
70
+ - Updated dependencies [e8b0de1f7]
71
+ - @tinacms/sharedctx@0.1.1
72
+ - @tinacms/toolkit@0.56.19
73
+ - @tinacms/schema-tools@0.0.2
74
+
75
+ ## 0.66.9
76
+
77
+ ### Patch Changes
78
+
79
+ - 91d5a6073: Allow "." in file names
80
+ - 11d55f441: Add experimental useGraphQLForms hook
81
+ - f41bd62ea: Ensure client-side Tina code only runs on the browser. Without this check, we'd see a server/client mismatch like:
82
+
83
+ ```
84
+ warning.js:33 Warning: Expected server HTML to contain a matching <div> in <body>.
85
+ ```
86
+
87
+ - Updated dependencies [e9a0c82cf]
88
+ - Updated dependencies [d4fdeaa9f]
89
+ - Updated dependencies [ed85f2594]
90
+ - Updated dependencies [d86e515ba]
91
+ - Updated dependencies [db0dab1d4]
92
+ - @tinacms/toolkit@0.56.18
93
+
3
94
  ## 0.66.8
4
95
 
5
96
  ### Patch Changes
@@ -14,12 +14,14 @@ import { TokenObject } from '../auth/authenticate';
14
14
  import { BranchData, EventBus } from '@tinacms/toolkit';
15
15
  import { DocumentNode, GraphQLSchema } from 'graphql';
16
16
  import gql from 'graphql-tag';
17
+ import { TinaSchema, TinaCloudSchema } from '@tinacms/schema-tools';
17
18
  export declare type TinaIOConfig = {
18
19
  frontendUrlOverride?: string;
19
20
  identityApiUrlOverride?: string;
20
21
  contentApiUrlOverride?: string;
21
22
  };
22
23
  interface ServerOptions {
24
+ schema?: TinaCloudSchema<false>;
23
25
  clientId: string;
24
26
  branch: string;
25
27
  customContentApiUrl?: string;
@@ -31,7 +33,8 @@ export declare class Client {
31
33
  frontendUrl: string;
32
34
  contentApiUrl: string;
33
35
  identityApiUrl: string;
34
- schema: GraphQLSchema;
36
+ gqlSchema: GraphQLSchema;
37
+ schema?: TinaSchema;
35
38
  clientId: string;
36
39
  contentApiBase: string;
37
40
  query: string;
@@ -103,6 +106,7 @@ export declare const DEFAULT_LOCAL_TINA_GQL_SERVER_URL = "http://localhost:4001/
103
106
  export declare class LocalClient extends Client {
104
107
  constructor(props?: {
105
108
  customContentApiUrl?: string;
109
+ schema?: TinaCloudSchema<false>;
106
110
  });
107
111
  isAuthorized(): Promise<boolean>;
108
112
  isAuthenticated(): Promise<boolean>;
@@ -97,7 +97,11 @@ const ToggleButton = () => {
97
97
  };
98
98
  const TinaEditProviderInner = ({ children, editMode }) => {
99
99
  const { edit } = useEditState();
100
- if (edit) {
100
+ const [isBrowser, setIsBrowser] = React.useState(false);
101
+ React.useEffect(() => {
102
+ setIsBrowser(true);
103
+ }, []);
104
+ if (edit && isBrowser) {
101
105
  return editMode;
102
106
  }
103
107
  return children;
@@ -102,7 +102,11 @@ var __objRest = (source, exclude) => {
102
102
  };
103
103
  const TinaEditProviderInner = ({ children, editMode }) => {
104
104
  const { edit } = sharedctx.useEditState();
105
- if (edit) {
105
+ const [isBrowser, setIsBrowser] = React__default["default"].useState(false);
106
+ React__default["default"].useEffect(() => {
107
+ setIsBrowser(true);
108
+ }, []);
109
+ if (edit && isBrowser) {
106
110
  return editMode;
107
111
  }
108
112
  return children;
@@ -0,0 +1,89 @@
1
+ /**
2
+ Copyright 2021 Forestry.io Holdings, Inc.
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ */
13
+ import * as G from 'graphql';
14
+ import type { BlueprintPath } from './types';
15
+ /**
16
+ *
17
+ * This check ensures that at type is a Document, but only one
18
+ * that can be "formified". When using `Node` or `Document`, those
19
+ * query fields should not have forms generated since they can't contain
20
+ * fields.
21
+ *
22
+ * ```graphql
23
+ * # Can be formified
24
+ * {
25
+ * getPostDocument(relativePath: "") {
26
+ * data {
27
+ * title
28
+ * }
29
+ * }
30
+ * }
31
+ * ```
32
+ *
33
+ * ```graphql
34
+ * # cannot be formified, even though it is a document field
35
+ * {
36
+ * getPostDocument(relativePath: "") {
37
+ * ...on Document {
38
+ * id
39
+ * }
40
+ * }
41
+ * }
42
+ * ```
43
+ */
44
+ export declare const isFormifiableDocument: (t: G.GraphQLOutputType) => boolean;
45
+ export declare const isScalarType: (t: G.GraphQLOutputType) => boolean;
46
+ export declare const isConnectionField: (t: G.GraphQLOutputType) => boolean;
47
+ /**
48
+ * Selects the appropriate field from a GraphQLObject based on the selection's name
49
+ */
50
+ export declare const getObjectField: (object: G.GraphQLOutputType, selectionNode: G.FieldNode) => G.GraphQLField<any, any, {
51
+ [key: string]: any;
52
+ }>;
53
+ /**
54
+ * Selects the appropriate type from a union based on the selection's typeCondition
55
+ *
56
+ * ```graphql
57
+ * post {
58
+ * # would return PostDocument
59
+ * ...on PostDocument { ... }
60
+ * }
61
+ * ```
62
+ */
63
+ export declare const getSelectedUnionType: (unionType: G.GraphQLOutputType, selectionNode: G.InlineFragmentNode) => any;
64
+ /**
65
+ * Checks if the given type is a list type. Even though
66
+ * this function is built-in to GraphQL it doesn't handle
67
+ * the scenario where the list type is wrapped in a non-null
68
+ * type, so the extra check here is needed.
69
+ */
70
+ export declare function isListType(type: unknown): boolean;
71
+ /**
72
+ *
73
+ * Throws an error if the provided type is not a GraphQLUnionType
74
+ */
75
+ export declare function ensureOperationDefinition(type: G.DefinitionNode): asserts type is G.OperationDefinitionNode;
76
+ /**
77
+ * Generates the name and alias information for a given field node
78
+ * and appends it to a shallow copy of the path provided
79
+ */
80
+ export declare function buildPath({ fieldNode, type, parentTypename, path, }: {
81
+ fieldNode: G.FieldNode;
82
+ type: G.GraphQLOutputType;
83
+ parentTypename?: string;
84
+ path?: BlueprintPath[];
85
+ }): BlueprintPath[];
86
+ export declare const metaFields: G.SelectionNode[];
87
+ export declare const getRelativeBlueprint: (path: BlueprintPath[]) => string;
88
+ export declare const getBlueprintId: (path: BlueprintPath[]) => string;
89
+ export declare const getFieldAliasForBlueprint: (path: BlueprintPath[]) => string;
@@ -1,18 +1,23 @@
1
1
  /**
2
-
3
2
  Copyright 2021 Forestry.io Holdings, Inc.
4
-
5
3
  Licensed under the Apache License, Version 2.0 (the "License");
6
4
  you may not use this file except in compliance with the License.
7
5
  You may obtain a copy of the License at
8
-
9
6
  http://www.apache.org/licenses/LICENSE-2.0
10
-
11
7
  Unless required by applicable law or agreed to in writing, software
12
8
  distributed under the License is distributed on an "AS IS" BASIS,
13
9
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
10
  See the License for the specific language governing permissions and
15
11
  limitations under the License.
16
-
17
12
  */
18
- export * from './SchemaTypes';
13
+ import * as G from 'graphql';
14
+ import type { DocumentBlueprint } from './types';
15
+ export declare const DATA_NODE_NAME = "data";
16
+ export declare const formify: ({ schema, query, getOptimizedQuery, }: {
17
+ schema: G.GraphQLSchema;
18
+ query: string;
19
+ getOptimizedQuery: (query: G.DocumentNode) => Promise<G.DocumentNode>;
20
+ }) => Promise<{
21
+ formifiedQuery: G.DocumentNode;
22
+ blueprints: DocumentBlueprint[];
23
+ }>;
@@ -10,48 +10,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
10
  See the License for the specific language governing permissions and
11
11
  limitations under the License.
12
12
  */
13
- import { GraphQLSchema, DocumentNode } from 'graphql';
14
- import React from 'react';
15
- import * as G from 'graphql';
16
13
  import type { TinaCMS } from '@tinacms/toolkit';
17
- declare type Action = {
18
- type: 'setSchema';
19
- value: G.GraphQLSchema;
20
- } | {
21
- type: 'setQuery';
22
- value: G.DocumentNode;
23
- } | {
24
- type: 'addNode';
25
- value: DocNode;
26
- };
27
- export declare type DocNode = {
28
- path: readonly (string | number)[];
29
- };
30
- export declare type Dispatch = React.Dispatch<Action>;
31
- /**
32
- * TODO: this is currently only used for testing, `formify` is where the primary logic is housed
33
- */
34
- export declare const useFormify: ({ query, cms }: {
35
- query: string;
14
+ import { formify } from './formify';
15
+ import { onSubmitArgs } from '../use-graphql-forms';
16
+ import type { OnChangeEvent, State } from './types';
17
+ export { formify };
18
+ export declare const useFormify: ({ query, cms, variables, onSubmit, formify: formifyFunc, eventList, }: {
19
+ query?: string;
36
20
  cms: TinaCMS;
37
- }) => {
38
- query: string;
39
- status: "pending" | "ready" | "done";
40
- nodes: DocNode[];
41
- };
42
- declare type TinaDocumentNode = {
43
- path: {
44
- name: string;
45
- alias: string;
46
- list?: boolean;
47
- }[];
48
- };
49
- export declare const formify: ({ schema, query, getOptimizedQuery, }: {
50
- schema: GraphQLSchema;
51
- query: string;
52
- getOptimizedQuery: (query: DocumentNode) => Promise<DocumentNode>;
53
- }) => Promise<{
54
- formifiedQuery: DocumentNode;
55
- nodes: TinaDocumentNode[];
56
- }>;
57
- export {};
21
+ variables: object;
22
+ onSubmit?: (args: onSubmitArgs) => void;
23
+ formify: any;
24
+ eventList?: OnChangeEvent[];
25
+ }) => State;
@@ -0,0 +1,27 @@
1
+ /**
2
+ Copyright 2021 Forestry.io Holdings, Inc.
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ */
13
+ import { FormNode, State, Action, OnChangeEvent } from './types';
14
+ export declare function reducer(state: State, action: Action): State;
15
+ export declare const buildChangeSet: (event: OnChangeEvent, formNode: FormNode) => {
16
+ fieldDefinition: {
17
+ name: string;
18
+ type: "string" | "object" | "reference";
19
+ list?: boolean;
20
+ parentTypename: string;
21
+ };
22
+ name: string;
23
+ formId: string;
24
+ mutationType: import("./types").ChangeMutation | import("./types").ReferenceChangeMutation | import("./types").InsertMutation | import("./types").MoveMutation | import("./types").RemoveMutation | import("./types").ResetMutation | import("./types").GlobalMutation;
25
+ value: unknown;
26
+ formNode: FormNode;
27
+ };
@@ -10,4 +10,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
10
  See the License for the specific language governing permissions and
11
11
  limitations under the License.
12
12
  */
13
- export {};
13
+ export declare const testRunner: (query: any, events: any, dirname: any, expectNoChange?: boolean) => Promise<void>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ Copyright 2021 Forestry.io Holdings, Inc.
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ */
13
+ import { TinaCMS } from '@tinacms/toolkit';
14
+ import 'isomorphic-fetch';
15
+ /**
16
+ * We're just mocking the tina api so we can mimic the real-world getSchema
17
+ */
18
+ declare const cms: TinaCMS;
19
+ export declare const printOutput: (event: any, previous: any, after: any) => string;
20
+ export { printState } from '../util';
21
+ export declare function sleep(ms: any): Promise<unknown>;
22
+ export { cms };
23
+ export declare const sequential: <A, B>(items: A[], callback: (args: A, idx: number) => Promise<B>) => Promise<B[]>;
24
+ export declare const buildFileOutput: (dirname: any) => string;
25
+ export declare const buildMarkdownOutput: (dirname: any, counter: any) => string;
@@ -0,0 +1,183 @@
1
+ /**
2
+ Copyright 2021 Forestry.io Holdings, Inc.
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ */
13
+ /// <reference types="react" />
14
+ import type * as G from 'graphql';
15
+ import type { Form, Field } from '@tinacms/toolkit';
16
+ export declare type Action = {
17
+ type: 'start';
18
+ value: {
19
+ query: string;
20
+ };
21
+ } | {
22
+ type: 'setData';
23
+ value: object;
24
+ } | {
25
+ type: 'addDocumentBlueprints';
26
+ value: {
27
+ blueprints: DocumentBlueprint[];
28
+ formifiedQuery: G.DocumentNode;
29
+ };
30
+ } | {
31
+ type: 'addOrReplaceDocumentFormNode';
32
+ value: {
33
+ formNode: FormNode;
34
+ documentForm?: DocumentForm;
35
+ };
36
+ } | {
37
+ type: 'onFieldChange';
38
+ value: {
39
+ event: OnChangeEvent;
40
+ form?: Form;
41
+ };
42
+ } | {
43
+ type: 'formOnReset';
44
+ value: {
45
+ event: OnChangeEvent;
46
+ form?: Form;
47
+ };
48
+ } | {
49
+ type: 'ready';
50
+ } | {
51
+ type: 'done';
52
+ } | {
53
+ type: 'setIn';
54
+ value: Pick<ChangeSet, 'path' | 'value' | 'displaceIndex'>;
55
+ };
56
+ export declare type Dispatch = React.Dispatch<Action>;
57
+ export declare type FormifiedDocumentNode = {
58
+ id: string;
59
+ _internalSys: {
60
+ path: string;
61
+ collection: {
62
+ name: any;
63
+ };
64
+ };
65
+ form: {
66
+ mutationInfo: {
67
+ string: string;
68
+ includeCollection?: boolean;
69
+ includeTemplate?: boolean;
70
+ };
71
+ label: string;
72
+ fields: Field[];
73
+ };
74
+ values: object;
75
+ };
76
+ export declare type ChangeMutation = {
77
+ type: 'change';
78
+ };
79
+ export declare type ReferenceChangeMutation = {
80
+ type: 'referenceChange';
81
+ };
82
+ export declare type InsertMutation = {
83
+ type: 'insert';
84
+ at: number;
85
+ };
86
+ export declare type MoveMutation = {
87
+ type: 'move';
88
+ from: number;
89
+ to: number;
90
+ };
91
+ export declare type RemoveMutation = {
92
+ type: 'remove';
93
+ at: number;
94
+ };
95
+ export declare type ResetMutation = {
96
+ type: 'reset';
97
+ };
98
+ export declare type GlobalMutation = {
99
+ type: 'global';
100
+ };
101
+ declare type MutationType = ChangeMutation | ReferenceChangeMutation | InsertMutation | MoveMutation | RemoveMutation | ResetMutation | GlobalMutation;
102
+ export declare type OnChangeEvent = {
103
+ type: 'forms:fields:onChange' | 'forms:reset';
104
+ value: unknown;
105
+ previousValue: unknown;
106
+ mutationType: MutationType;
107
+ formId: string;
108
+ field: {
109
+ data: {
110
+ tinaField: {
111
+ name: string;
112
+ type: 'string' | 'reference' | 'object';
113
+ list?: boolean;
114
+ parentTypename: string;
115
+ };
116
+ };
117
+ name: string;
118
+ };
119
+ };
120
+ export declare type ChangeSet = {
121
+ path: string;
122
+ value: unknown;
123
+ formId: string;
124
+ fieldDefinition: {
125
+ name: string;
126
+ type: 'string' | 'reference' | 'object';
127
+ list?: boolean;
128
+ };
129
+ mutationType: MutationType;
130
+ name: string;
131
+ displaceIndex?: boolean;
132
+ formNode: FormNode;
133
+ };
134
+ export declare type BlueprintPath = {
135
+ name: string;
136
+ alias: string;
137
+ parentTypename?: string;
138
+ list?: boolean;
139
+ isNode?: boolean;
140
+ };
141
+ export declare type DocumentBlueprint = {
142
+ /** The stringified representation of a path relative to root or it's parent document */
143
+ id: string;
144
+ /** The path to a field node */
145
+ path: BlueprintPath[];
146
+ /** The GraphQL SelectionNode, useful for re-fetching the given node */
147
+ selection: G.SelectionNode;
148
+ fields: FieldBlueprint[];
149
+ /** For now, only top-level, non-list nodes will be shown in the sidebar */
150
+ showInSidebar: boolean;
151
+ /** these 2 are not traditional GraphQL fields but need be kept in-sync regardless */
152
+ hasDataJSONField: boolean;
153
+ hasValuesField: boolean;
154
+ };
155
+ export declare type FieldBlueprint = {
156
+ /** The stringified representation of a path relative to root or it's parent document */
157
+ id: string;
158
+ documentBlueprintId: string;
159
+ /** The path to a field node */
160
+ path: BlueprintPath[];
161
+ };
162
+ export declare type FormNode = {
163
+ /** The stringified path with location values injected (eg. 'getBlockPageList.edges.0.node.data.social.1.relatedPage') */
164
+ documentFormId: string;
165
+ documentBlueprintId: string;
166
+ /** Coordinates for the DocumentBlueprint's '[]' values */
167
+ location: number[];
168
+ };
169
+ /** The document ID is the true ID 'content/pages/hello-world.md') */
170
+ declare type DocumentForm = Form;
171
+ export declare type State = {
172
+ schema: G.GraphQLSchema;
173
+ query: G.DocumentNode;
174
+ queryString: string;
175
+ status: 'idle' | 'initialized' | 'formified' | 'ready' | 'done';
176
+ count: number;
177
+ data: object;
178
+ changeSets: ChangeSet[];
179
+ blueprints: DocumentBlueprint[];
180
+ formNodes: FormNode[];
181
+ documentForms: DocumentForm[];
182
+ };
183
+ export {};