@tinacms/app 0.0.0-00f6525-20241015065741

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.
@@ -0,0 +1,236 @@
1
+ /**
2
+
3
+
4
+
5
+ */
6
+
7
+ import React from 'react'
8
+ import MonacoEditor, { useMonaco, loader } from '@monaco-editor/react'
9
+ /**
10
+ * MDX is built directly to the app because of how we load dependencies.
11
+ * Since we drop the package.json in to the end users folder, we can't
12
+ * easily install the current version of the mdx package in all scenarios
13
+ * (when we're working in the monorepo, or working with a tagged npm version)
14
+ */
15
+ import { parseMDX, stringifyMDX } from '@tinacms/mdx'
16
+ import { useDebounce } from './use-debounce'
17
+ import type * as monaco from 'monaco-editor'
18
+ import {
19
+ buildError,
20
+ ErrorMessage,
21
+ InvalidMarkdownElement,
22
+ } from './error-message'
23
+ import { RichTextType } from 'tinacms'
24
+
25
+ export const uuid = () => {
26
+ // @ts-ignore
27
+ return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
28
+ (
29
+ c ^
30
+ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
31
+ ).toString(16)
32
+ )
33
+ }
34
+
35
+ type Monaco = typeof monaco
36
+
37
+ // 0.33.0 has a bug https://github.com/microsoft/monaco-editor/issues/2947
38
+ loader.config({
39
+ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.31.1/min/vs' },
40
+ })
41
+
42
+ /**
43
+ * Since monaco lazy-loads we may have a delay from when the block is inserted
44
+ * to when monaco has intantiated, keep trying to focus on it.
45
+ *
46
+ * Will try for 3 seconds before moving on
47
+ */
48
+ let retryCount = 0
49
+ const retryFocus = (ref) => {
50
+ if (ref.current) {
51
+ ref.current.focus()
52
+ } else {
53
+ if (retryCount < 30) {
54
+ setTimeout(() => {
55
+ retryCount = retryCount + 1
56
+ retryFocus(ref)
57
+ }, 100)
58
+ }
59
+ }
60
+ }
61
+
62
+ export const RawEditor = (props: RichTextType) => {
63
+ const monaco = useMonaco() as Monaco
64
+ const monacoEditorRef =
65
+ React.useRef<monaco.editor.IStandaloneCodeEditor>(null)
66
+ const [height, setHeight] = React.useState(100)
67
+ const id = React.useMemo(() => uuid(), [])
68
+ const field = props.field
69
+ const inputValue = React.useMemo(() => {
70
+ // @ts-ignore no access to the rich-text type from this package
71
+ const res = stringifyMDX(props.input.value, field, (value) => value)
72
+ return typeof props.input.value === 'string' ? props.input.value : res
73
+ }, [])
74
+ const [value, setValue] = React.useState(inputValue)
75
+ const [error, setError] = React.useState<InvalidMarkdownElement>(null)
76
+
77
+ const debouncedValue = useDebounce(value, 500)
78
+
79
+ React.useEffect(() => {
80
+ // @ts-ignore no access to the rich-text type from this package
81
+ const parsedValue = parseMDX(value, field, (value) => value)
82
+ if (
83
+ parsedValue.children[0] &&
84
+ parsedValue.children[0].type === 'invalid_markdown'
85
+ ) {
86
+ const invalidMarkdown = parsedValue.children[0]
87
+ setError(invalidMarkdown)
88
+ } else {
89
+ setError(null)
90
+ }
91
+ props.input.onChange(parsedValue)
92
+ }, [JSON.stringify(debouncedValue)])
93
+
94
+ React.useEffect(() => {
95
+ if (monacoEditorRef.current) {
96
+ if (error) {
97
+ const errorMessage = buildError(error)
98
+ monaco.editor.setModelMarkers(monacoEditorRef.current.getModel(), id, [
99
+ {
100
+ ...errorMessage.position,
101
+ message: errorMessage.message,
102
+ severity: 8,
103
+ },
104
+ ])
105
+ } else {
106
+ monaco.editor.setModelMarkers(
107
+ monacoEditorRef.current.getModel(),
108
+ id,
109
+ []
110
+ )
111
+ }
112
+ }
113
+ }, [JSON.stringify(error), monacoEditorRef.current])
114
+
115
+ React.useEffect(() => {
116
+ if (monaco) {
117
+ monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)
118
+ monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
119
+ // disable errors
120
+ noSemanticValidation: true,
121
+ noSyntaxValidation: true,
122
+ })
123
+ // TODO: autocomplete suggestions
124
+ // monaco.languages.registerCompletionItemProvider('markdown', {
125
+ // provideCompletionItems: function (model, position) {
126
+ // const word = model.getWordUntilPosition(position)
127
+ // const range = {
128
+ // startLineNumber: position.lineNumber,
129
+ // endLineNumber: position.lineNumber,
130
+ // startColumn: word.startColumn,
131
+ // endColumn: word.endColumn,
132
+ // }
133
+ // return {
134
+ // suggestions: [
135
+ // {
136
+ // label: '<DateTime />',
137
+ // insertText: '<DateTime format="iso" />',
138
+ // kind: 0,
139
+ // range,
140
+ // },
141
+ // ],
142
+ // }
143
+ // },
144
+ // })
145
+ }
146
+ }, [monaco])
147
+
148
+ function handleEditorDidMount(
149
+ monacoEditor: monaco.editor.IStandaloneCodeEditor,
150
+ monaco: Monaco
151
+ ) {
152
+ monacoEditorRef.current = monacoEditor
153
+ monacoEditor.onDidContentSizeChange(() => {
154
+ // FIXME: if the window is too tall the performance degrades, come up with a nice
155
+ // balance between the two
156
+ setHeight(Math.min(Math.max(100, monacoEditor.getContentHeight()), 1000))
157
+ monacoEditor.layout()
158
+ })
159
+ }
160
+
161
+ return (
162
+ <div className="relative">
163
+ <div className="sticky top-1 w-full flex justify-between mb-2 z-50 max-w-full bg-white">
164
+ <Button onClick={() => props.setRawMode(false)}>
165
+ View in rich-text editor 📝
166
+ </Button>
167
+ <ErrorMessage error={error} />
168
+ </div>
169
+ <div style={{ height: `${height}px` }}>
170
+ <MonacoEditor
171
+ path={id}
172
+ onMount={handleEditorDidMount}
173
+ // Setting a custom theme is kind of buggy because it doesn't get defined until monaco has mounted.
174
+ // So we end up with the default (light) theme in some scenarios. Seems like a race condition.
175
+ // theme="vs-dark"
176
+ options={{
177
+ scrollBeyondLastLine: false,
178
+ tabSize: 2,
179
+ disableLayerHinting: true,
180
+ accessibilitySupport: 'off',
181
+ codeLens: false,
182
+ wordWrap: 'on',
183
+ minimap: {
184
+ enabled: false,
185
+ },
186
+ fontSize: 14,
187
+ lineHeight: 2,
188
+ formatOnPaste: true,
189
+ lineNumbers: 'on',
190
+ lineNumbersMinChars: 2,
191
+ formatOnType: true,
192
+ fixedOverflowWidgets: true,
193
+ // Takes too much horizontal space for iframe
194
+ folding: false,
195
+ renderLineHighlight: 'none',
196
+ scrollbar: {
197
+ verticalScrollbarSize: 4,
198
+ horizontalScrollbarSize: 4,
199
+ // https://github.com/microsoft/monaco-editor/issues/2007#issuecomment-644425664
200
+ alwaysConsumeMouseWheel: false,
201
+ },
202
+ }}
203
+ language={'markdown'}
204
+ value={value}
205
+ onChange={(value) => {
206
+ try {
207
+ setValue(value)
208
+ } catch (e) {
209
+ console.log('error', e)
210
+ }
211
+ }}
212
+ />
213
+ </div>
214
+ </div>
215
+ )
216
+ }
217
+
218
+ const Button = (props) => {
219
+ return (
220
+ <button
221
+ className={`${
222
+ props.align === 'left'
223
+ ? 'rounded-l-md border-r-0'
224
+ : 'rounded-r-md border-l-0'
225
+ } flex justify-center w-full shadow rounded-md bg-white cursor-pointer relative inline-flex items-center px-2 py-2 border border-gray-200 hover:text-white text-sm font-medium transition-all ease-out duration-150 hover:bg-blue-500 focus:z-10 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500`}
226
+ type="button"
227
+ onClick={props.onClick}
228
+ >
229
+ <span className="text-sm font-semibold tracking-wide align-baseline mr-1">
230
+ {props.children}
231
+ </span>
232
+ </button>
233
+ )
234
+ }
235
+
236
+ export default RawEditor
@@ -0,0 +1,25 @@
1
+ /**
2
+
3
+
4
+
5
+ */
6
+ import { useState, useEffect } from 'react'
7
+ export function useDebounce(value, delay) {
8
+ const [debouncedValue, setDebouncedValue] = useState(value)
9
+ useEffect(
10
+ () => {
11
+ // Update debounced value after delay
12
+ const handler = setTimeout(() => {
13
+ setDebouncedValue(value)
14
+ }, delay)
15
+ // Cancel the timeout if value changes (also on delay change or unmount)
16
+ // This is how we prevent debounced value from updating if value is changed ...
17
+ // .. within the delay period. Timeout gets cleared and restarted.
18
+ return () => {
19
+ clearTimeout(handler)
20
+ }
21
+ },
22
+ [value, delay] // Only re-call effect if value or delay changes
23
+ )
24
+ return debouncedValue
25
+ }
package/src/global.css ADDED
@@ -0,0 +1,120 @@
1
+ :root {
2
+ --tina-color-primary-light: #2296fe;
3
+ --tina-color-primary: #0084ff;
4
+ --tina-color-primary-dark: #0574e4;
5
+ --tina-color-error-light: #eb6337;
6
+ --tina-color-error: #ec4815;
7
+ --tina-color-error-dark: #dc4419;
8
+ --tina-color-warning-light: #f5e06e;
9
+ --tina-color-warning: #e9d050;
10
+ --tina-color-warning-dark: #d3ba38;
11
+ --tina-color-success-light: #57c355;
12
+ --tina-color-success: #3cad3a;
13
+ --tina-color-success-dark: #249a21;
14
+ --tina-color-grey-0: #ffffff;
15
+ --tina-color-grey-1: #f6f6f9;
16
+ --tina-color-grey-2: #edecf3;
17
+ --tina-color-grey-3: #e1ddec;
18
+ --tina-color-grey-4: #b2adbe;
19
+ --tina-color-grey-5: #918c9e;
20
+ --tina-color-grey-6: #716c7f;
21
+ --tina-color-grey-7: #565165;
22
+ --tina-color-grey-8: #433e52;
23
+ --tina-color-grey-9: #363145;
24
+ --tina-color-grey-10: #252336;
25
+ --tina-color-indicator: var(--tina-color-primary);
26
+
27
+ --tina-radius-small: 5px;
28
+ --tina-radius-big: 24px;
29
+
30
+ --tina-padding-small: 12px;
31
+ --tina-padding-big: 20px;
32
+
33
+ --tina-font-size-0: 12px;
34
+ --tina-font-size-1: 13px;
35
+ --tina-font-size-2: 15px;
36
+ --tina-font-size-3: 16px;
37
+ --tina-font-size-4: 18px;
38
+ --tina-font-size-5: 20px;
39
+ --tina-font-size-6: 22px;
40
+ --tina-font-size-7: 26px;
41
+ --tina-font-size-8: 32px;
42
+
43
+ --tina-font-family: 'Inter', sans-serif;
44
+
45
+ --tina-font-weight-regular: 400;
46
+ --tina-font-weight-bold: 600;
47
+
48
+ --tina-shadow-big: 0px 2px 3px rgba(0, 0, 0, 0.05),
49
+ 0 4px 12px rgba(0, 0, 0, 0.1);
50
+ --tina-shadow-small: 0px 2px 3px rgba(0, 0, 0, 0.12);
51
+
52
+ --tina-timing-short: 85ms;
53
+ --tina-timing-medium: 150ms;
54
+ --tina-timing-long: 250ms;
55
+
56
+ --tina-z-index-0: 0;
57
+ --tina-z-index-1: 10;
58
+ --tina-z-index-2: 20;
59
+ --tina-z-index-3: 30;
60
+ --tina-z-index-4: 40;
61
+ --tina-z-index-5: 50;
62
+
63
+ --tina-sidebar-width: 340px;
64
+ --tina-sidebar-header-height: 60px;
65
+ --tina-toolbar-height: 62px;
66
+ }
67
+
68
+ @keyframes fly-in-left {
69
+ 0% {
70
+ transform: translate3d(100%, 0, 0);
71
+ }
72
+
73
+ 100% {
74
+ transform: translate3d(0, 0, 0);
75
+ }
76
+ }
77
+
78
+ @keyframes fly-in-up {
79
+ 0% {
80
+ transform: translate3d(0, 100%, 0);
81
+ }
82
+
83
+ 100% {
84
+ transform: translate3d(0, 0, 0);
85
+ }
86
+ }
87
+
88
+ @keyframes fade-in {
89
+ 0% {
90
+ opacity: 0;
91
+ }
92
+
93
+ 100% {
94
+ opacity: 1;
95
+ }
96
+ }
97
+
98
+ @keyframes popup-right {
99
+ 0% {
100
+ transform: translate3d(-2rem, 0, 0);
101
+ opacity: 0;
102
+ }
103
+
104
+ 100% {
105
+ transform: translate3d(0, 0, 0);
106
+ opacity: 1;
107
+ }
108
+ }
109
+
110
+ @keyframes popup-down {
111
+ 0% {
112
+ transform: translate3d(0, -2rem, 0);
113
+ opacity: 0;
114
+ }
115
+
116
+ 100% {
117
+ transform: translate3d(0, 0, 0);
118
+ opacity: 1;
119
+ }
120
+ }