Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/add-display-only-field.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@tinacms/schema-tools": patch
"@tinacms/graphql": patch
"tinacms": patch
---

Add 'displayOnly' field type for display-only form fields
5 changes: 5 additions & 0 deletions .changeset/fix-code-block-meta-preservation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tinacms/mdx": patch
---

Preserve code block infostring meta through parse/serialize round-trip. Metadata after the language identifier (e.g. `{1,3-5}` or `title="app.py"`) was silently dropped on every save; it is now captured in the parser and emitted by the serializer.
8 changes: 8 additions & 0 deletions packages/@tinacms/graphql/src/builder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,8 @@ export class Builder {
depth: number
) => Promise<SelectionSetNode | FieldNode | false> = async (field, depth) => {
switch (field.type) {
case 'displayOnly':
return false;
case 'string':
case 'image':
case 'datetime':
Expand Down Expand Up @@ -1098,6 +1100,8 @@ export class Builder {

private _buildFieldFilter = async (field: TinaField<true>) => {
switch (field.type) {
case 'displayOnly':
return undefined;
case 'boolean':
return astBuilder.InputValueDefinition({
name: field.name,
Expand Down Expand Up @@ -1260,6 +1264,8 @@ export class Builder {

private _buildFieldMutation = async (field: TinaField<true>) => {
switch (field.type) {
case 'displayOnly':
return undefined;
case 'boolean':
return astBuilder.InputValueDefinition({
name: field.name,
Expand Down Expand Up @@ -1532,6 +1538,8 @@ Visit https://tina.io/docs/r/content-fields/#list-fields/ for more information
`;

switch (field.type) {
case 'displayOnly':
return undefined;
case 'boolean':
case 'datetime':
case 'number':
Expand Down
4 changes: 4 additions & 0 deletions packages/@tinacms/graphql/src/resolver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const resolveFieldData = async (
assertShape<{ [key: string]: unknown }>(rawData, (yup) => yup.object());
const value = rawData[field.name];
switch (field.type) {
case 'displayOnly':
break;
case 'datetime':
// See you in March ;)
if (value instanceof Date) {
Expand Down Expand Up @@ -1537,6 +1539,8 @@ export class Resolver {
throw new Error(`Expected to find field by name ${fieldName}`);
}
switch (field.type) {
case 'displayOnly':
break;
case 'datetime':
// @ts-ignore FIXME: Argument of type 'string | { [key: string]: unknown; } | (string | { [key: string]: unknown; })[]' is not assignable to parameter of type 'string'
accum[fieldName] = resolveDateInput(fieldValue, field);
Expand Down
2 changes: 1 addition & 1 deletion packages/@tinacms/graphql/src/schema/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('The schema validation', () => {
],
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"'type' must be one of: string, number, boolean, datetime, image, reference, object, rich-text, password, but got 'some-type' at someName.myTitle"`
`"'type' must be one of: string, number, boolean, datetime, image, reference, object, rich-text, password, displayOnly, but got 'some-type' at someName.myTitle"`
);
});

Expand Down
1 change: 1 addition & 0 deletions packages/@tinacms/graphql/src/schema/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const FIELD_TYPES: TinaField['type'][] = [
'object',
'rich-text',
'password',
'displayOnly',
];

export const validateSchema = async (schema: Schema) => {
Expand Down
1 change: 1 addition & 0 deletions packages/@tinacms/mdx/src/next/stringify/pre-processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const blockElement = (
return {
type: 'code',
lang: content.lang,
meta: content.meta,
value: codeLinesToString(content.children).join('\n'),
};
case 'mdxJsxFlowElement':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { RichTextField } from '@tinacms/schema-tools';

export const field: RichTextField = {
name: 'body',
type: 'rich-text',
parser: { type: 'markdown', skipEscaping: 'html' },
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```js {1,3-5}
console.log('hello');
```

```python title="app.py"
print("hello")
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expect, it } from 'vitest';
import { parseMDX } from '../../../parse';
import { serializeMDX } from '../../../stringify';
import * as util from '../util';
import { field } from './field';
import input from './in.md?raw';

it('preserves code block meta (infostring) through parse/serialize round-trip', () => {
const tree = parseMDX(input, field, (v) => v);
expect(util.print(tree)).toMatchFile(util.nodePath(__dirname));
const string = serializeMDX(tree, field, (v) => v);
expect(string).toMatchFile(util.mdPath(__dirname));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"type": "root",
"children": [
{
"type": "code_block",
"lang": "js",
"meta": "{1,3-5}",
"value": "console.log('hello');",
"children": [
{
"type": "code_line",
"children": [
{
"text": "console.log('hello');"
}
]
}
]
},
{
"type": "code_block",
"lang": "python",
"meta": "title=\"app.py\"",
"value": "print(\"hello\")",
"children": [
{
"type": "code_line",
"children": [
{
"text": "print(\"hello\")"
}
]
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```js {1,3-5}
console.log('hello');
```

```python title="app.py"
print("hello")
```
1 change: 1 addition & 0 deletions packages/@tinacms/mdx/src/parse/plate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type CodeLineElement = {
export type CodeBlockElement = {
type: 'code_block';
lang?: string;
meta?: string;
value?: string; // this is needed for mdast, it is not used by Plate as the 'value' (platev48) now stores this in the children as CodeLineElements
children: CodeLineElement[];
};
Expand Down
1 change: 1 addition & 0 deletions packages/@tinacms/mdx/src/parse/remarkToPlate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export const remarkToSlate = (
const code = (content: Md.Code): Plate.CodeBlockElement => {
const extra: Record<string, string> = {};
if (content.lang) extra['lang'] = content.lang;
if (content.meta) extra['meta'] = content.meta;

const value = content.value ?? '';
const children =
Expand Down
1 change: 1 addition & 0 deletions packages/@tinacms/mdx/src/stringify/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export const blockElement = (
return {
type: 'code',
lang: content.lang,
meta: content.meta,
value: codeLinesToString(content),
};
case 'mdxJsxFlowElement':
Expand Down
6 changes: 6 additions & 0 deletions packages/@tinacms/schema-tools/src/schema/resolveField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ export const resolveField = (
component: 'reference',
...extraFields,
};
case 'displayOnly':
return {
component: 'displayOnly',
...field,
...extraFields,
};
default:
// @ts-ignore
throw new Error(`Unknown field type ${field.type}`);
Expand Down
11 changes: 11 additions & 0 deletions packages/@tinacms/schema-tools/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,16 @@ export type PasswordField = (
type: 'password';
};

export type DisplayOnlyField = BaseField & {
type: 'displayOnly';
list?: never;
required?: never;
indexed?: never;
ui?: {
component?: FC<any> | null;
};
};

export type ToolbarOverrideType =
| 'heading'
| 'link'
Expand Down Expand Up @@ -504,6 +514,7 @@ type Field<WithNamespace extends boolean = false> = (
| RichTextField<WithNamespace>
| ObjectField<WithNamespace>
| PasswordField
| DisplayOnlyField
) &
MaybeNamespace<WithNamespace>;

Expand Down
36 changes: 36 additions & 0 deletions packages/@tinacms/schema-tools/src/validate/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const TypeName = [
'object',
'reference',
'rich-text',
'displayOnly',
] as const;

const formattedTypes = ` - ${TypeName.join('\n - ')}`;
Expand Down Expand Up @@ -96,6 +97,40 @@ const DateTimeField = TinaScalerBase.extend({
timeFormat: z.string().optional(),
});

// ==========
// Display-only fields
// ==========
const DisplayOnlyField = TinaField.omit({ required: true }).extend({
type: z.literal('displayOnly', {
invalid_type_error: typeTypeError,
required_error: typeRequiredError,
}),
list: z
.literal(undefined, {
errorMap: () => ({
message:
'Property `list` is not allowed on fields of `type: displayOnly`.',
}),
})
.optional(),
required: z
.literal(undefined, {
errorMap: () => ({
message:
'Property `required` is not allowed on fields of `type: displayOnly`.',
}),
})
.optional(),
indexed: z
.literal(undefined, {
errorMap: () => ({
message:
'Property `indexed` is not allowed on fields of `type: displayOnly`.',
}),
})
.optional(),
});

// ==========
// Non Scaler fields
// ==========
Expand Down Expand Up @@ -199,6 +234,7 @@ export const TinaFieldZod: z.ZodType<TinaFieldType> = z.lazy(() => {
ObjectField,
RichTextField,
PasswordField,
DisplayOnlyField,
],
{
errorMap: (issue, ctx) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import { wrapFieldWithNoHeader } from './wrap-field-with-meta';

const DefaultDisplayOnlyField: React.FC<any> = () => {
return (
<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 <code>ui.component</code>
</div>
);
};

export const DisplayOnlyFieldPlugin = {
name: 'displayOnly',
Component: wrapFieldWithNoHeader(DefaultDisplayOnlyField),
};

export const InfoBox = ({
message,
links,
}: {
message: string;
links?: { text: string; url: string }[];
}): React.FC<any> => {
const InfoBoxComponent: React.FC<any> = () => {
return (
<div className='relative w-full px-2 mb-5 last:mb-0'>
<div className='rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm w-full'>
<p className='text-gray-700 whitespace-normal break-words'>
{message}
</p>
{links && links.length > 0 && (
<ul className='flex flex-col gap-1 mt-2'>
{links.map((link) => (
<li key={link.url}>
<a
href={link.url}
target='_blank'
rel='noopener noreferrer'
className='text-blue-600 underline hover:text-blue-800'
>
{link.text}
</a>
</li>
))}
</ul>
)}
</div>
</div>
);
};
InfoBoxComponent.displayName = 'InfoBox';
return InfoBoxComponent;
};
1 change: 1 addition & 0 deletions packages/tinacms/src/toolkit/fields/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from './reference-field-plugin';
export * from './button-toggle-field-plugin';
export * from './hidden-field-plugin';
export * from './password-field-plugin';
export * from './display-only-field-plugin';
2 changes: 2 additions & 0 deletions packages/tinacms/src/toolkit/tina-cms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
CheckboxGroupFieldPlugin,
ColorFieldPlugin,
DateFieldPlugin,
DisplayOnlyFieldPlugin,
GroupFieldPlugin,
GroupListFieldPlugin,
HiddenFieldPlugin,
Expand Down Expand Up @@ -65,6 +66,7 @@ const DEFAULT_FIELDS = [
ButtonToggleFieldPlugin,
HiddenFieldPlugin,
PasswordFieldPlugin,
DisplayOnlyFieldPlugin,
];

export interface TinaCMSConfig extends CMSConfig {
Expand Down
Loading