@tinacms/app 2.5.10 → 2.5.11
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/package.json +8 -8
- package/src/App.tsx +6 -3
- package/src/Playground.tsx +6 -5
- package/src/fields/rich-text/error-boundary.test.tsx +58 -0
- package/src/fields/rich-text/error-boundary.tsx +62 -0
- package/src/fields/rich-text/monaco/error-message.tsx +33 -48
- package/src/fields/rich-text/monaco/index.tsx +119 -49
- package/src/index.css +9 -1
- package/src/preview.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinacms/app",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.11",
|
|
4
4
|
"main": "src/main.tsx",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
],
|
|
12
12
|
"license": "Apache-2.0",
|
|
13
13
|
"devDependencies": {
|
|
14
|
+
"@testing-library/react": "^16.2.0",
|
|
14
15
|
"@types/react": "^18.3.18",
|
|
15
16
|
"@types/react-dom": "^18.3.5",
|
|
16
17
|
"happy-dom": "15.10.2",
|
|
18
|
+
"monaco-editor": "0.31.0",
|
|
17
19
|
"typescript": "^5.7.3",
|
|
18
20
|
"vite": "^5.4.14",
|
|
19
21
|
"vitest": "^2.1.9"
|
|
@@ -24,20 +26,18 @@
|
|
|
24
26
|
},
|
|
25
27
|
"dependencies": {
|
|
26
28
|
"@graphiql/toolkit": "0.8.4",
|
|
27
|
-
"@
|
|
28
|
-
"@
|
|
29
|
-
"@monaco-editor/react": "4.7.0-rc.0",
|
|
29
|
+
"@monaco-editor/loader": "^1.7.0",
|
|
30
|
+
"@radix-ui/react-popover": "^1.1.18",
|
|
30
31
|
"final-form": "4.20.10",
|
|
31
32
|
"graphiql": "3.0.0-alpha.1",
|
|
32
33
|
"graphql": "15.8.0",
|
|
33
|
-
"
|
|
34
|
+
"lucide-react": "^0.424.0",
|
|
34
35
|
"react": "^18.3.1",
|
|
35
36
|
"react-dom": "^18.3.1",
|
|
36
37
|
"react-router-dom": "^6.30.3",
|
|
37
|
-
"typescript": "^5.7.3",
|
|
38
38
|
"zod": "^3.24.2",
|
|
39
|
-
"
|
|
40
|
-
"tinacms": "^
|
|
39
|
+
"tinacms": "^3.12.0",
|
|
40
|
+
"@tinacms/mdx": "^2.2.0"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"url": "https://github.com/tinacms/tinacms.git",
|
package/src/App.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import React, { Suspense } from 'react';
|
|
|
2
2
|
import TinaCMS, { TinaAdmin, useCMS, MdxFieldPluginExtendible } from 'tinacms';
|
|
3
3
|
import { Preview } from './preview';
|
|
4
4
|
import Playground from './Playground';
|
|
5
|
+
import { RawEditorErrorBoundary } from './fields/rich-text/error-boundary';
|
|
5
6
|
|
|
6
7
|
// TODO: Resolve this to local file in tsconfig.json
|
|
7
8
|
// @ts-expect-error
|
|
@@ -21,9 +22,11 @@ const Editor = (props) => {
|
|
|
21
22
|
setRawMode={setRawMode}
|
|
22
23
|
{...props}
|
|
23
24
|
rawEditor={
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
25
|
+
<RawEditorErrorBoundary onDismiss={() => setRawMode(false)}>
|
|
26
|
+
<Suspense fallback={<div>Loading raw editor...</div>}>
|
|
27
|
+
<RawEditor {...props} setRawMode={setRawMode} rawMode={rawMode} />
|
|
28
|
+
</Suspense>
|
|
29
|
+
</RawEditorErrorBoundary>
|
|
27
30
|
}
|
|
28
31
|
/>
|
|
29
32
|
);
|
package/src/Playground.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
|
2
|
-
import { FolderIcon } from '@heroicons/react/outline';
|
|
3
2
|
import { queries } from 'CLIENT_IMPORT';
|
|
4
3
|
import { GraphiQL } from 'graphiql';
|
|
5
4
|
import { parse, print } from 'graphql';
|
|
5
|
+
import { Folder } from 'lucide-react';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { useCMS } from 'tinacms';
|
|
8
8
|
|
|
@@ -56,8 +56,9 @@ const Playground = () => {
|
|
|
56
56
|
|
|
57
57
|
const ref = React.useRef();
|
|
58
58
|
|
|
59
|
-
const getToken = () => {
|
|
60
|
-
|
|
59
|
+
const getToken = async () => {
|
|
60
|
+
const token = await cms.api.tina.authProvider.getToken();
|
|
61
|
+
return token?.id_token ?? token?.access_token;
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
if (!autoQueries) {
|
|
@@ -133,7 +134,7 @@ const Playground = () => {
|
|
|
133
134
|
fetcher={async (params, options) => {
|
|
134
135
|
const fetcher = createGraphiQLFetcher({
|
|
135
136
|
url: cms.api.tina.contentApiUrl || __API_URL__,
|
|
136
|
-
headers: { Authorization: `Bearer ${getToken()}` },
|
|
137
|
+
headers: { Authorization: `Bearer ${await getToken()}` },
|
|
137
138
|
});
|
|
138
139
|
return fetcher(params, options);
|
|
139
140
|
}}
|
|
@@ -144,7 +145,7 @@ const Playground = () => {
|
|
|
144
145
|
plugins={[
|
|
145
146
|
{
|
|
146
147
|
title: 'Queries',
|
|
147
|
-
icon: () => <
|
|
148
|
+
icon: () => <Folder />,
|
|
148
149
|
content: (values) => <Plugin />,
|
|
149
150
|
},
|
|
150
151
|
]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { RawEditorErrorBoundary } from './error-boundary';
|
|
5
|
+
|
|
6
|
+
const Throws = ({ message }: { message: string }) => {
|
|
7
|
+
throw new Error(message);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const FAILED_IMPORT = 'Failed to fetch dynamically imported module';
|
|
11
|
+
const FAILED_SERIALIZE = 'Marks inside inline code are not supported';
|
|
12
|
+
|
|
13
|
+
const renderWithThrow = (message: string, onDismiss = () => {}) => {
|
|
14
|
+
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
15
|
+
render(
|
|
16
|
+
<RawEditorErrorBoundary onDismiss={onDismiss}>
|
|
17
|
+
<Throws message={message} />
|
|
18
|
+
</RawEditorErrorBoundary>
|
|
19
|
+
);
|
|
20
|
+
consoleError.mockRestore();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('RawEditorErrorBoundary', () => {
|
|
24
|
+
it('renders its children when nothing throws', () => {
|
|
25
|
+
render(
|
|
26
|
+
<RawEditorErrorBoundary onDismiss={() => {}}>
|
|
27
|
+
<div>raw editor</div>
|
|
28
|
+
</RawEditorErrorBoundary>
|
|
29
|
+
);
|
|
30
|
+
expect(screen.getByText('raw editor')).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('keeps a failed lazy import inside the field', () => {
|
|
34
|
+
renderWithThrow(FAILED_IMPORT);
|
|
35
|
+
expect(
|
|
36
|
+
screen.getByText(/Couldn't open this field in raw markdown/)
|
|
37
|
+
).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('keeps a failure to serialize the field inside the field', () => {
|
|
41
|
+
renderWithThrow(FAILED_SERIALIZE);
|
|
42
|
+
expect(
|
|
43
|
+
screen.getByText(/Couldn't open this field in raw markdown/)
|
|
44
|
+
).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('shows the underlying message so the editor knows what to undo', () => {
|
|
48
|
+
renderWithThrow(FAILED_SERIALIZE);
|
|
49
|
+
expect(screen.getByText(FAILED_SERIALIZE)).toBeTruthy();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('offers a way back to the rich-text editor', () => {
|
|
53
|
+
const onDismiss = vi.fn();
|
|
54
|
+
renderWithThrow(FAILED_IMPORT, onDismiss);
|
|
55
|
+
fireEvent.click(screen.getByRole('button'));
|
|
56
|
+
expect(onDismiss).toHaveBeenCalledOnce();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
onDismiss: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The raw editor fails in more than one way: its chunk is loaded lazily, so the
|
|
10
|
+
* import can reject, and it serializes the field on render, so content it can't
|
|
11
|
+
* represent throws. Either one unmounts the whole admin without a boundary,
|
|
12
|
+
* leaving a blank page.
|
|
13
|
+
*
|
|
14
|
+
* The heading names the outcome rather than a cause, since the boundary cannot
|
|
15
|
+
* tell them apart. The message underneath is the one thing that lets an editor
|
|
16
|
+
* act — "Marks inside inline code are not supported" says which formatting to
|
|
17
|
+
* undo — and it is what a bug report needs verbatim.
|
|
18
|
+
*/
|
|
19
|
+
export class RawEditorErrorBoundary extends React.Component<
|
|
20
|
+
Props,
|
|
21
|
+
{ message: string | null }
|
|
22
|
+
> {
|
|
23
|
+
state: { message: string | null } = { message: null };
|
|
24
|
+
|
|
25
|
+
static getDerivedStateFromError(error: Error) {
|
|
26
|
+
return { message: error?.message ?? '' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentDidCatch(error: Error) {
|
|
30
|
+
console.error('Raw markdown editor failed to open:', error);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
render() {
|
|
34
|
+
if (this.state.message === null) {
|
|
35
|
+
return this.props.children;
|
|
36
|
+
}
|
|
37
|
+
// `whitespace-normal` because the field wrapper sets `nowrap`, which this
|
|
38
|
+
// would otherwise inherit and clip the message.
|
|
39
|
+
return (
|
|
40
|
+
<div className='p-4 text-sm whitespace-normal text-gray-700'>
|
|
41
|
+
<p className='mb-2'>
|
|
42
|
+
Couldn't open this field in raw markdown. Your content hasn't changed.
|
|
43
|
+
</p>
|
|
44
|
+
{this.state.message ? (
|
|
45
|
+
<p className='mb-3 font-mono text-xs break-words text-gray-500'>
|
|
46
|
+
{this.state.message}
|
|
47
|
+
</p>
|
|
48
|
+
) : null}
|
|
49
|
+
<button
|
|
50
|
+
type='button'
|
|
51
|
+
className='rounded border border-gray-200 bg-white px-2 py-1 font-medium shadow hover:bg-blue-500 hover:text-white'
|
|
52
|
+
onClick={() => {
|
|
53
|
+
this.setState({ message: null });
|
|
54
|
+
this.props.onDismiss();
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
Back to rich-text editor
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
PopoverButton,
|
|
4
|
-
PopoverPanel,
|
|
5
|
-
Transition,
|
|
6
|
-
} from '@headlessui/react';
|
|
7
|
-
import { XCircleIcon } from '@heroicons/react/solid';
|
|
1
|
+
import { CircleX } from 'lucide-react';
|
|
2
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
8
3
|
/**
|
|
9
4
|
|
|
10
5
|
|
|
11
6
|
|
|
12
7
|
*/
|
|
13
8
|
import React from 'react';
|
|
14
|
-
import { Fragment } from 'react';
|
|
15
9
|
// import { InvalidMarkdownElement } from '@tinacms/mdx/src/parse/plate'
|
|
16
10
|
export type EmptyTextElement = { type: 'text'; text: '' };
|
|
17
11
|
export type PositionItem = {
|
|
@@ -72,47 +66,38 @@ export function ErrorMessage({ error }: { error: InvalidMarkdownElement }) {
|
|
|
72
66
|
const message = buildErrorMessage(error);
|
|
73
67
|
|
|
74
68
|
return (
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
aria-hidden='true'
|
|
102
|
-
/>
|
|
103
|
-
</div>
|
|
104
|
-
<div className='ml-3'>
|
|
105
|
-
<h3 className='text-sm font-medium text-red-800 whitespace-pre-wrap'>
|
|
106
|
-
{message}
|
|
107
|
-
</h3>
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
69
|
+
<PopoverPrimitive.Root>
|
|
70
|
+
<PopoverPrimitive.Trigger
|
|
71
|
+
className={`p-2 shaodw-lg border ${error ? '' : ' opacity-0 hidden '}`}
|
|
72
|
+
>
|
|
73
|
+
<span className='sr-only'>Errors</span>
|
|
74
|
+
<CircleX className='h-5 w-5 text-red-400' aria-hidden='true' />
|
|
75
|
+
</PopoverPrimitive.Trigger>
|
|
76
|
+
<PopoverPrimitive.Portal>
|
|
77
|
+
<PopoverPrimitive.Content
|
|
78
|
+
align='end'
|
|
79
|
+
sideOffset={12}
|
|
80
|
+
className='z-50 w-[300px] outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0'
|
|
81
|
+
>
|
|
82
|
+
<div className='overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5'>
|
|
83
|
+
<div className='rounded bg-red-50 p-4 overflow-scroll'>
|
|
84
|
+
<div className='flex'>
|
|
85
|
+
<div className='flex-shrink-0'>
|
|
86
|
+
<CircleX
|
|
87
|
+
className='h-5 w-5 text-red-400'
|
|
88
|
+
aria-hidden='true'
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
<div className='ml-3'>
|
|
92
|
+
<h3 className='text-sm font-medium text-red-800 whitespace-pre-wrap'>
|
|
93
|
+
{message}
|
|
94
|
+
</h3>
|
|
110
95
|
</div>
|
|
111
96
|
</div>
|
|
112
|
-
</
|
|
113
|
-
</
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</PopoverPrimitive.Content>
|
|
100
|
+
</PopoverPrimitive.Portal>
|
|
101
|
+
</PopoverPrimitive.Root>
|
|
117
102
|
);
|
|
118
103
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import MonacoEditor, { useMonaco, loader } from '@monaco-editor/react';
|
|
1
|
+
import loader from '@monaco-editor/loader';
|
|
3
2
|
/**
|
|
4
3
|
* MDX is built directly to the app because of how we load dependencies.
|
|
5
4
|
* Since we drop the package.json in to the end users folder, we can't
|
|
@@ -8,14 +7,15 @@ import MonacoEditor, { useMonaco, loader } from '@monaco-editor/react';
|
|
|
8
7
|
*/
|
|
9
8
|
import { parseMDX, serializeMDX } from '@tinacms/mdx';
|
|
10
9
|
import type * as monaco from 'monaco-editor';
|
|
10
|
+
import React from 'react';
|
|
11
11
|
import { RichTextType } from 'tinacms';
|
|
12
|
+
import { RichTextEditorSwitchedEvent, captureEvent } from 'tinacms';
|
|
12
13
|
import {
|
|
13
14
|
ErrorMessage,
|
|
14
15
|
InvalidMarkdownElement,
|
|
15
16
|
buildError,
|
|
16
17
|
} from './error-message';
|
|
17
18
|
import { useDebounce } from './use-debounce';
|
|
18
|
-
import { RichTextEditorSwitchedEvent, captureEvent } from 'tinacms';
|
|
19
19
|
|
|
20
20
|
export const uuid = () => {
|
|
21
21
|
// @ts-ignore
|
|
@@ -29,6 +29,76 @@ export const uuid = () => {
|
|
|
29
29
|
|
|
30
30
|
type Monaco = typeof monaco;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The editor itself is fetched from a CDN at runtime by @monaco-editor/loader;
|
|
34
|
+
* the `monaco-editor` package is a devDependency used only for its types.
|
|
35
|
+
*/
|
|
36
|
+
const useMonaco = (): Monaco | null => {
|
|
37
|
+
const [instance, setInstance] = React.useState<Monaco | null>(null);
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
let cancelled = false;
|
|
40
|
+
loader
|
|
41
|
+
.init()
|
|
42
|
+
.then((m) => {
|
|
43
|
+
if (!cancelled) {
|
|
44
|
+
setInstance(m as Monaco);
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.catch((error) => {
|
|
48
|
+
console.error('Monaco initialization: error:', error);
|
|
49
|
+
});
|
|
50
|
+
return () => {
|
|
51
|
+
cancelled = true;
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
return instance;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const MonacoEditor = (props: {
|
|
58
|
+
monaco: Monaco;
|
|
59
|
+
path: string;
|
|
60
|
+
value: string;
|
|
61
|
+
language: string;
|
|
62
|
+
options: monaco.editor.IStandaloneEditorConstructionOptions;
|
|
63
|
+
onMount: (
|
|
64
|
+
editor: monaco.editor.IStandaloneCodeEditor,
|
|
65
|
+
monaco: Monaco
|
|
66
|
+
) => void;
|
|
67
|
+
onChange: (value: string) => void;
|
|
68
|
+
}) => {
|
|
69
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
70
|
+
// Read through a ref so re-creating the editor isn't tied to callback identity.
|
|
71
|
+
const onChangeRef = React.useRef(props.onChange);
|
|
72
|
+
onChangeRef.current = props.onChange;
|
|
73
|
+
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
const { monaco, path, value, language, options, onMount } = props;
|
|
76
|
+
if (!containerRef.current) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const uri = monaco.Uri.parse(`inmemory://tina/${path}`);
|
|
80
|
+
const model =
|
|
81
|
+
monaco.editor.getModel(uri) ||
|
|
82
|
+
monaco.editor.createModel(value, language, uri);
|
|
83
|
+
const editor = monaco.editor.create(containerRef.current, {
|
|
84
|
+
model,
|
|
85
|
+
automaticLayout: true,
|
|
86
|
+
...options,
|
|
87
|
+
});
|
|
88
|
+
const subscription = editor.onDidChangeModelContent(() => {
|
|
89
|
+
onChangeRef.current(editor.getValue());
|
|
90
|
+
});
|
|
91
|
+
onMount(editor, monaco);
|
|
92
|
+
return () => {
|
|
93
|
+
subscription.dispose();
|
|
94
|
+
model.dispose();
|
|
95
|
+
editor.dispose();
|
|
96
|
+
};
|
|
97
|
+
}, [props.monaco]);
|
|
98
|
+
|
|
99
|
+
return <div ref={containerRef} style={{ height: '100%', width: '100%' }} />;
|
|
100
|
+
};
|
|
101
|
+
|
|
32
102
|
/**
|
|
33
103
|
* Since monaco lazy-loads we may have a delay from when the block is inserted
|
|
34
104
|
* to when monaco has intantiated, keep trying to focus on it.
|
|
@@ -166,52 +236,52 @@ export const RawEditor = (props: RichTextType) => {
|
|
|
166
236
|
<ErrorMessage error={error} />
|
|
167
237
|
</div>
|
|
168
238
|
<div style={{ height: `${height}px` }}>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
{monaco ? (
|
|
240
|
+
<MonacoEditor
|
|
241
|
+
monaco={monaco}
|
|
242
|
+
path={id}
|
|
243
|
+
onMount={handleEditorDidMount}
|
|
244
|
+
// Setting a custom theme is kind of buggy because it doesn't get defined until monaco has mounted.
|
|
245
|
+
// So we end up with the default (light) theme in some scenarios. Seems like a race condition.
|
|
246
|
+
// theme="vs-dark"
|
|
247
|
+
options={{
|
|
248
|
+
scrollBeyondLastLine: false,
|
|
249
|
+
tabSize: 2,
|
|
250
|
+
disableLayerHinting: true,
|
|
251
|
+
accessibilitySupport: 'off',
|
|
252
|
+
codeLens: false,
|
|
253
|
+
wordWrap: 'on',
|
|
254
|
+
minimap: {
|
|
255
|
+
enabled: false,
|
|
256
|
+
},
|
|
257
|
+
fontSize: 14,
|
|
258
|
+
lineHeight: 2,
|
|
259
|
+
formatOnPaste: true,
|
|
260
|
+
lineNumbers: 'on',
|
|
261
|
+
lineNumbersMinChars: 2,
|
|
262
|
+
formatOnType: true,
|
|
263
|
+
fixedOverflowWidgets: true,
|
|
264
|
+
// Takes too much horizontal space for iframe
|
|
265
|
+
folding: false,
|
|
266
|
+
renderLineHighlight: 'none',
|
|
267
|
+
scrollbar: {
|
|
268
|
+
verticalScrollbarSize: 4,
|
|
269
|
+
horizontalScrollbarSize: 4,
|
|
270
|
+
// https://github.com/microsoft/monaco-editor/issues/2007#issuecomment-644425664
|
|
271
|
+
alwaysConsumeMouseWheel: false,
|
|
272
|
+
},
|
|
273
|
+
}}
|
|
274
|
+
language={'markdown'}
|
|
275
|
+
value={value}
|
|
276
|
+
onChange={(value) => {
|
|
277
|
+
try {
|
|
278
|
+
setValue(value);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
console.log('error', e);
|
|
281
|
+
}
|
|
282
|
+
}}
|
|
283
|
+
/>
|
|
284
|
+
) : null}
|
|
215
285
|
</div>
|
|
216
286
|
</div>
|
|
217
287
|
);
|
package/src/index.css
CHANGED
|
@@ -9,7 +9,15 @@
|
|
|
9
9
|
@apply bg-blue-600 !important;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/*
|
|
13
|
+
* Exists for the hand-written Tina SVGs (AddIcon, CloseIcon, TinaIcon, …), which
|
|
14
|
+
* are solid shapes and expect to be coloured via `fill`.
|
|
15
|
+
*
|
|
16
|
+
* lucide icons are stroke-based and ship `fill="none"`. CSS beats a presentation
|
|
17
|
+
* attribute, so without the :not() they get flooded with currentColor and their
|
|
18
|
+
* internal detail disappears — a filled disc instead of a refresh arrow.
|
|
19
|
+
*/
|
|
20
|
+
.icon-parent svg:not(.lucide) {
|
|
13
21
|
fill: currentColor;
|
|
14
22
|
}
|
|
15
23
|
|