sanity-plugin-markdown 3.0.0 → 4.0.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/LICENSE +1 -1
- package/README.md +124 -15
- package/lib/index.d.ts +108 -0
- package/lib/index.esm.js +140 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +155 -1
- package/lib/index.js.map +1 -1
- package/package.json +31 -26
- package/src/components/MarkdownInput.tsx +94 -0
- package/src/components/MarkdownInputStyles.tsx +61 -0
- package/src/index.ts +3 -15
- package/src/plugin.tsx +54 -0
- package/src/schema.ts +26 -3
- package/lib/src/index.d.ts +0 -37
- package/src/components/Editor.tsx +0 -134
- package/src/hooks/useDebounce.ts +0 -14
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -7,26 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
A Markdown editor with preview for Sanity Studio.
|
|
9
9
|
|
|
10
|
-
Supports Github flavored markdown and image uploads.
|
|
10
|
+
Supports Github flavored markdown and image uploads.
|
|
11
|
+
You can either drag image(s) into the editor or click the bottom bar to bring up a file selector.
|
|
11
12
|
The resulting image URL(s) are inserted with a default width parameter which you can change to your liking using the [Sanity image pipeline parameters](https://www.sanity.io/docs/image-urls).
|
|
12
|
-
|
|
13
|
-
### Known issues with the current v3 version
|
|
14
13
|
|
|
15
|
-
The
|
|
14
|
+
The current version is a wrapper around [React SimpleMDE (EasyMDE) Markdown Editor](https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor),
|
|
15
|
+
and by extension, [EasyMDE](https://github.com/Ionaru/easy-markdown-editor).
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
You can still add markdown image tags, but you will have to get the image url yourself for now.
|
|
17
|
+

|
|
19
18
|
|
|
20
19
|
## Installation
|
|
20
|
+
Install `sanity-plugin-markdown` and `easymde@2` (peer dependency).
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
npm install --save sanity-plugin-markdown
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
or
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
yarn add sanity-plugin-markdown
|
|
23
|
+
npm install --save sanity-plugin-markdown easymde@2
|
|
30
24
|
```
|
|
31
25
|
|
|
32
26
|
## Usage
|
|
@@ -59,10 +53,125 @@ const myDocument = {
|
|
|
59
53
|
]
|
|
60
54
|
}
|
|
61
55
|
```
|
|
62
|
-
### Demo
|
|
63
56
|
|
|
64
|
-
|
|
57
|
+
### Customizing the default markdown input editor
|
|
58
|
+
|
|
59
|
+
The plugin takes an `input` config option that can be used in combination with the `MarkdownInput` export
|
|
60
|
+
to configure the underlying React SimpleMDE component:
|
|
61
|
+
|
|
62
|
+
* Create a custom component that wraps MarkdownInput
|
|
63
|
+
* Memoize reactMdeProps and pass along
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// CustomMarkdownInput.tsx
|
|
67
|
+
import { MarkdownInput, MarkdownInputProps } from 'sanity-plugin-markdown'
|
|
68
|
+
|
|
69
|
+
export function CustomMarkdownInput(props) {
|
|
70
|
+
const reactMdeProps: MarkdownInputProps['reactMdeProps'] =
|
|
71
|
+
useMemo(() => {
|
|
72
|
+
return {
|
|
73
|
+
options: {
|
|
74
|
+
toolbar: ['bold', 'italic'],
|
|
75
|
+
// more options available, see:
|
|
76
|
+
// https://github.com/Ionaru/easy-markdown-editor#options-list
|
|
77
|
+
},
|
|
78
|
+
// more props available, see:
|
|
79
|
+
// https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor
|
|
80
|
+
}
|
|
81
|
+
}, [])
|
|
82
|
+
|
|
83
|
+
return <MarkdownInput {...props} reactMdeProps={reactMdeProps} />
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
Set the plugin input option:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// studio.config.ts
|
|
90
|
+
import {markdownSchema} from 'sanity-plugin-markdown'
|
|
91
|
+
import {CustomMarkdownInput} from './CustomMarkdownInput'
|
|
92
|
+
|
|
93
|
+
export default defineConfig({
|
|
94
|
+
// ... rest of the config
|
|
95
|
+
plugins: [
|
|
96
|
+
markdownSchema({input: CustomMarkdownInput}),
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Customize editor for a single field
|
|
102
|
+
|
|
103
|
+
Implement a custom input similar to the one above, and use it as `components.input` on the field directly.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
defineField({
|
|
107
|
+
type: 'markdown',
|
|
108
|
+
name: 'markdown',
|
|
109
|
+
title: 'Markdown',
|
|
110
|
+
components: {input: CustomMarkdownInput}
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Customizing editor preview
|
|
115
|
+
|
|
116
|
+
One way to customize the preview that does not involve ReactDOMServer
|
|
117
|
+
(used by React SimpleMDE) is to install [marked](https://github.com/markedjs/marked) and
|
|
118
|
+
[DOMPurify](https://github.com/cure53/DOMPurify) and create a custom preview:
|
|
119
|
+
|
|
120
|
+
`npm i marked dompurify`
|
|
121
|
+
|
|
122
|
+
Then use these to create a custom editor:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
// MarkdownInputCustomPreview.tsx
|
|
126
|
+
import { MarkdownInput, MarkdownInputProps } from 'sanity-plugin-markdown'
|
|
127
|
+
import DOMPurify from 'dompurify'
|
|
128
|
+
import {marked} from 'marked'
|
|
129
|
+
|
|
130
|
+
export function CustomMarkdownInput(props) {
|
|
131
|
+
const reactMdeProps: MarkdownInputProps['reactMdeProps'] =
|
|
132
|
+
useMemo(() => {
|
|
133
|
+
return {
|
|
134
|
+
options: {
|
|
135
|
+
previewRender: (markdownText) => {
|
|
136
|
+
// configure as needed according to
|
|
137
|
+
// https://github.com/markedjs/marked#docs
|
|
138
|
+
return DOMPurify.sanitize(marked.parse(markdownText))
|
|
139
|
+
}
|
|
140
|
+
//customizing using renderingConfig is also an option
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
}, [])
|
|
144
|
+
|
|
145
|
+
return <MarkdownInput {...props} reactMdeProps={reactMdeProps} />
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use the component as described in previous sections.
|
|
150
|
+
|
|
151
|
+
### Custom image urls
|
|
152
|
+
|
|
153
|
+
Provide a function to options.imageUrl that takes a SanityImageAssetDocument and returns a string.
|
|
154
|
+
|
|
155
|
+
The function will be invoked whenever an image is pasted or dragged into the markdown editor,
|
|
156
|
+
after upload completes.
|
|
157
|
+
|
|
158
|
+
The default implementation uses
|
|
159
|
+
```js
|
|
160
|
+
imageAsset => `${imageAsset.url}?w=450`
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Example imageUrl option
|
|
65
164
|
|
|
165
|
+
```js
|
|
166
|
+
defineField({
|
|
167
|
+
type: 'markdown',
|
|
168
|
+
name: 'markdown',
|
|
169
|
+
title: 'Markdown',
|
|
170
|
+
options: {
|
|
171
|
+
imageUrl: imageAsset => `${imageAsset.url}?w=400&h=400`
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
```
|
|
66
175
|
|
|
67
176
|
## License
|
|
68
177
|
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
|
|
3
|
+
import {Options} from 'easymde'
|
|
4
|
+
import {Plugin as Plugin_2} from 'sanity'
|
|
5
|
+
import {ReactElement} from 'react'
|
|
6
|
+
import {SanityImageAssetDocument} from '@sanity/client'
|
|
7
|
+
import {SimpleMDEReactProps} from 'react-simplemde-editor'
|
|
8
|
+
import {StringDefinition} from 'sanity'
|
|
9
|
+
import {StringInputProps} from 'sanity'
|
|
10
|
+
|
|
11
|
+
export declare const defaultMdeTools: Options['toolbar']
|
|
12
|
+
|
|
13
|
+
export declare interface MarkdownConfig {
|
|
14
|
+
/**
|
|
15
|
+
* When provided, will replace the default input component.
|
|
16
|
+
*
|
|
17
|
+
* Use this to customize MarkdownInput by wrapping it in a custom component,
|
|
18
|
+
* and provide any custom props for https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor
|
|
19
|
+
* via the `reactMdeProps` prop.
|
|
20
|
+
*
|
|
21
|
+
* ### Example
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* // CustomMarkdownInput.tsx
|
|
25
|
+
* import { MarkdownInput, MarkdownInputProps } from 'sanity-plugin-markdown'
|
|
26
|
+
*
|
|
27
|
+
* export function CustomMarkdownInput(props) {
|
|
28
|
+
* const reactMdeProps: MarkdownInputProps['reactMdeProps'] =
|
|
29
|
+
* useMemo(() => {
|
|
30
|
+
* return {
|
|
31
|
+
* options: {
|
|
32
|
+
* toolbar: ['bold', 'italic'],
|
|
33
|
+
* // more options available, see:
|
|
34
|
+
* // https://github.com/Ionaru/easy-markdown-editor#options-list
|
|
35
|
+
* },
|
|
36
|
+
* // more props available, see:
|
|
37
|
+
* // https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor
|
|
38
|
+
* }
|
|
39
|
+
* }, [])
|
|
40
|
+
*
|
|
41
|
+
* return <MarkdownInput {...props} reactMdeProps={reactMdeProps} />
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* // studio.config.ts
|
|
45
|
+
* markdownSchema({input: CustomMarkdownInput})
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
input?: (props: StringInputProps) => ReactElement
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export declare interface MarkdownDefinition
|
|
55
|
+
extends Omit<StringDefinition, 'type' | 'fields' | 'options'> {
|
|
56
|
+
type: typeof markdownTypeName
|
|
57
|
+
options?: MarkdownOptions
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export declare function MarkdownInput(props: MarkdownInputProps): JSX.Element
|
|
61
|
+
|
|
62
|
+
export declare interface MarkdownInputProps extends StringInputProps {
|
|
63
|
+
/**
|
|
64
|
+
* These are passed along directly to
|
|
65
|
+
*
|
|
66
|
+
* Note: MarkdownInput sets certain reactMdeProps.options by default.
|
|
67
|
+
* These will be merged with any custom options.
|
|
68
|
+
* */
|
|
69
|
+
reactMdeProps?: Omit<SimpleMDEReactProps, 'value' | 'onChange'>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
declare interface MarkdownOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Used to create image url for any uploaded image.
|
|
75
|
+
* The function will be invoked whenever an image is pasted or dragged into the
|
|
76
|
+
* markdown editor, after upload completes.
|
|
77
|
+
*
|
|
78
|
+
* The default implementation uses
|
|
79
|
+
* ```js
|
|
80
|
+
* imageAsset => `${imageAsset.url}?w=450`
|
|
81
|
+
* ```
|
|
82
|
+
* ## Example
|
|
83
|
+
* ```js
|
|
84
|
+
* {
|
|
85
|
+
* imageUrl: imageAsset => `${imageAsset.url}?w=400&h=400`
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
* @param imageAsset
|
|
89
|
+
*/
|
|
90
|
+
imageUrl?: (imageAsset: SanityImageAssetDocument) => string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export declare const markdownSchema: Plugin_2<void | MarkdownConfig>
|
|
94
|
+
|
|
95
|
+
export declare const markdownSchemaType: {
|
|
96
|
+
type: 'string'
|
|
97
|
+
name: 'markdown'
|
|
98
|
+
} & Omit<StringDefinition, 'preview'>
|
|
99
|
+
|
|
100
|
+
declare const markdownTypeName: 'markdown'
|
|
101
|
+
|
|
102
|
+
export {}
|
|
103
|
+
|
|
104
|
+
declare module '@sanity/types' {
|
|
105
|
+
interface IntrinsicDefinitions {
|
|
106
|
+
markdown: MarkdownDefinition
|
|
107
|
+
}
|
|
108
|
+
}
|
package/lib/index.esm.js
CHANGED
|
@@ -1,2 +1,141 @@
|
|
|
1
|
-
|
|
1
|
+
const _excluded = ["options"];
|
|
2
|
+
var _templateObject;
|
|
3
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
4
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
5
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
6
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
7
|
+
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
8
|
+
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
|
|
9
|
+
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
|
10
|
+
function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
|
|
11
|
+
import { useClient, PatchEvent, set, unset, defineType, definePlugin } from 'sanity';
|
|
12
|
+
import { jsx } from 'react/jsx-runtime';
|
|
13
|
+
import 'easymde/dist/easymde.min.css';
|
|
14
|
+
import { useCallback, useMemo } from 'react';
|
|
15
|
+
import SimpleMdeReact from 'react-simplemde-editor';
|
|
16
|
+
import styled from 'styled-components';
|
|
17
|
+
import { Box } from '@sanity/ui';
|
|
18
|
+
const MarkdownInputStyles = styled(Box)(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n & .CodeMirror.CodeMirror {\n color: ", ";\n border-color: ", ";\n background-color: inherit;\n }\n\n & .cm-s-easymde .CodeMirror-cursor {\n border-color: ", ";\n }\n\n & .editor-toolbar,\n .editor-preview-side {\n border-color: ", ";\n }\n\n & .CodeMirror-focused .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ", ";\n }\n\n & .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ", ";\n }\n\n & .editor-toolbar > * {\n color: ", ";\n }\n\n & .editor-toolbar > .active,\n .editor-toolbar > button:hover,\n .editor-preview pre,\n .cm-s-easymde .cm-comment {\n background-color: ", ";\n }\n\n & .editor-preview {\n background-color: ", ";\n\n & h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n font-size: revert;\n }\n\n & ul,\n li {\n list-style: revert;\n padding: revert;\n }\n\n & a {\n text-decoration: revert;\n }\n }\n"])), _ref => {
|
|
19
|
+
let {
|
|
20
|
+
theme
|
|
21
|
+
} = _ref;
|
|
22
|
+
return theme.sanity.color.card.enabled.fg;
|
|
23
|
+
}, _ref2 => {
|
|
24
|
+
let {
|
|
25
|
+
theme
|
|
26
|
+
} = _ref2;
|
|
27
|
+
return theme.sanity.color.card.enabled.border;
|
|
28
|
+
}, _ref3 => {
|
|
29
|
+
let {
|
|
30
|
+
theme
|
|
31
|
+
} = _ref3;
|
|
32
|
+
return theme.sanity.color.card.enabled.fg;
|
|
33
|
+
}, _ref4 => {
|
|
34
|
+
let {
|
|
35
|
+
theme
|
|
36
|
+
} = _ref4;
|
|
37
|
+
return theme.sanity.color.card.enabled.border;
|
|
38
|
+
}, _ref5 => {
|
|
39
|
+
let {
|
|
40
|
+
theme
|
|
41
|
+
} = _ref5;
|
|
42
|
+
return theme.sanity.color.selectable.primary.hovered.bg;
|
|
43
|
+
}, _ref6 => {
|
|
44
|
+
let {
|
|
45
|
+
theme
|
|
46
|
+
} = _ref6;
|
|
47
|
+
return theme.sanity.color.card.enabled.bg;
|
|
48
|
+
}, _ref7 => {
|
|
49
|
+
let {
|
|
50
|
+
theme
|
|
51
|
+
} = _ref7;
|
|
52
|
+
return theme.sanity.color.card.enabled.fg;
|
|
53
|
+
}, _ref8 => {
|
|
54
|
+
let {
|
|
55
|
+
theme
|
|
56
|
+
} = _ref8;
|
|
57
|
+
return theme.sanity.color.card.enabled.bg;
|
|
58
|
+
}, _ref9 => {
|
|
59
|
+
let {
|
|
60
|
+
theme
|
|
61
|
+
} = _ref9;
|
|
62
|
+
return theme.sanity.color.card.enabled.bg;
|
|
63
|
+
});
|
|
64
|
+
const defaultMdeTools = ["heading", "bold", "italic", "|", "quote", "unordered-list", "ordered-list", "|", "link", "image", "code", "|", "preview", "side-by-side"];
|
|
65
|
+
function MarkdownInput(props) {
|
|
66
|
+
var _a;
|
|
67
|
+
const {
|
|
68
|
+
value = "",
|
|
69
|
+
onChange,
|
|
70
|
+
elementProps: {
|
|
71
|
+
onBlur,
|
|
72
|
+
onFocus,
|
|
73
|
+
ref
|
|
74
|
+
},
|
|
75
|
+
reactMdeProps: {
|
|
76
|
+
options: mdeCustomOptions
|
|
77
|
+
} = {},
|
|
78
|
+
schemaType
|
|
79
|
+
} = props,
|
|
80
|
+
reactMdeProps = _objectWithoutProperties(props.reactMdeProps, _excluded);
|
|
81
|
+
const client = useClient({
|
|
82
|
+
apiVersion: "2022-01-01"
|
|
83
|
+
});
|
|
84
|
+
const {
|
|
85
|
+
imageUrl
|
|
86
|
+
} = (_a = schemaType.options) != null ? _a : {};
|
|
87
|
+
const imageUpload = useCallback((file, onSuccess, onError) => {
|
|
88
|
+
client.assets.upload("image", file).then(doc => onSuccess(imageUrl ? imageUrl(doc) : "".concat(doc.url, "?w=450"))).catch(e => {
|
|
89
|
+
console.error(e);
|
|
90
|
+
onError(e.message);
|
|
91
|
+
});
|
|
92
|
+
}, [client, imageUrl]);
|
|
93
|
+
const mdeOptions = useMemo(() => {
|
|
94
|
+
return _objectSpread({
|
|
95
|
+
autofocus: false,
|
|
96
|
+
spellChecker: false,
|
|
97
|
+
sideBySideFullscreen: false,
|
|
98
|
+
uploadImage: true,
|
|
99
|
+
imageUploadFunction: imageUpload,
|
|
100
|
+
toolbar: defaultMdeTools,
|
|
101
|
+
status: false
|
|
102
|
+
}, mdeCustomOptions);
|
|
103
|
+
}, [imageUpload, mdeCustomOptions]);
|
|
104
|
+
const handleChange = useCallback(newValue => {
|
|
105
|
+
onChange(PatchEvent.from(newValue ? set(newValue) : unset()));
|
|
106
|
+
}, [onChange]);
|
|
107
|
+
return /* @__PURE__ */jsx(MarkdownInputStyles, {
|
|
108
|
+
children: /* @__PURE__ */jsx(SimpleMdeReact, _objectSpread(_objectSpread({}, reactMdeProps), {}, {
|
|
109
|
+
ref,
|
|
110
|
+
value,
|
|
111
|
+
onChange: handleChange,
|
|
112
|
+
onBlur,
|
|
113
|
+
onFocus,
|
|
114
|
+
options: mdeOptions,
|
|
115
|
+
spellCheck: false
|
|
116
|
+
}))
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const markdownTypeName = "markdown";
|
|
120
|
+
const markdownSchemaType = defineType({
|
|
121
|
+
type: "string",
|
|
122
|
+
name: markdownTypeName,
|
|
123
|
+
title: "Markdown",
|
|
124
|
+
components: {
|
|
125
|
+
input: MarkdownInput
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
const markdownSchema = definePlugin(config => {
|
|
129
|
+
return {
|
|
130
|
+
name: "markdown-editor",
|
|
131
|
+
schema: {
|
|
132
|
+
types: [config && config.input ? _objectSpread(_objectSpread({}, markdownSchemaType), {}, {
|
|
133
|
+
components: {
|
|
134
|
+
input: config.input
|
|
135
|
+
}
|
|
136
|
+
}) : markdownSchemaType]
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
export { MarkdownInput, defaultMdeTools, markdownSchema, markdownSchemaType };
|
|
2
141
|
//# sourceMappingURL=index.esm.js.map
|
package/lib/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/components/Editor.tsx","../src/hooks/useDebounce.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import MDEditor from '@uiw/react-md-editor'\nimport useDebounce from '../hooks/useDebounce'\nimport {PatchEvent, SanityClient, set, StringInputProps, unset, useClient} from 'sanity'\nimport React, {\n ClipboardEvent,\n forwardRef,\n Ref,\n SetStateAction,\n useCallback,\n useEffect,\n useState,\n DragEvent,\n} from 'react'\nimport {Card, useTheme} from '@sanity/ui'\nimport rehypeSanitize from 'rehype-sanitize'\n\nexport const MarkdownEditor = forwardRef(function MarkdownEditor(\n props: StringInputProps,\n ref: Ref<any>\n) {\n const {\n value = '',\n onChange,\n elementProps: {onBlur, onFocus},\n readOnly,\n } = props\n const [editedValue, setEditedValue] = useState<string | undefined>(value)\n const debouncedValue = useDebounce(editedValue, 100)\n // const client = useClient({apiVersion: '2021-03-25'})\n const client = useClient({apiVersion: '2022-01-01'})\n useEffect(() => {\n setEditedValue(value)\n }, [value])\n\n useEffect(() => {\n if (!debouncedValue && value) {\n onChange(PatchEvent.from([unset()]))\n } else if (debouncedValue !== value) {\n onChange(PatchEvent.from([set(debouncedValue)]))\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debouncedValue, onChange])\n\n const {sanity: studioTheme} = useTheme()\n const onPaste = useCallback(\n async (event: ClipboardEvent<HTMLDivElement>) => {\n await onImagePasted(event.clipboardData, setEditedValue, client)\n },\n [setEditedValue, client]\n )\n\n const onDrop = useCallback(\n async (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n if (event.dataTransfer) {\n await onImagePasted(event.dataTransfer, setEditedValue, client)\n }\n },\n [setEditedValue, client]\n )\n\n return (\n <div ref={ref} data-color-mode={studioTheme.color.dark ? 'dark' : 'light'}>\n {readOnly ? (\n <Card border padding={3}>\n <MDEditor.Markdown source={value} rehypePlugins={[[rehypeSanitize]]} />\n </Card>\n ) : (\n <MDEditor\n value={editedValue}\n onChange={setEditedValue}\n onBlur={onBlur}\n onFocus={onFocus}\n previewOptions={{\n rehypePlugins: [[rehypeSanitize]],\n }}\n preview=\"edit\"\n onPaste={onPaste}\n onDrop={onDrop}\n />\n )}\n </div>\n )\n})\n\n// https://github.com/uiwjs/react-md-editor/issues/83#issuecomment-1185471844\nasync function onImagePasted(\n dataTransfer: DataTransfer,\n setMarkdown: (value: SetStateAction<string | undefined>) => void,\n client: SanityClient\n) {\n const files: File[] = []\n for (let index = 0; index < dataTransfer.items.length; index += 1) {\n const file = dataTransfer.files.item(index)\n\n if (file) {\n files.push(file)\n }\n }\n\n await Promise.all(\n files.map(async (file) => {\n const url = await client.assets.upload('image', file).then((doc) => `${doc.url}?w=450`)\n const insertedMarkdown = insertToTextArea(``)\n if (!insertedMarkdown) {\n return\n }\n setMarkdown(insertedMarkdown)\n })\n )\n}\n\nfunction insertToTextArea(insertString: string) {\n const textarea = document.querySelector('textarea')\n if (!textarea) {\n return null\n }\n\n let sentence = textarea.value\n const len = sentence.length\n const pos = textarea.selectionStart\n const end = textarea.selectionEnd\n\n const front = sentence.slice(0, pos)\n const back = sentence.slice(pos, len)\n\n sentence = front + insertString + back\n\n textarea.value = sentence\n textarea.selectionEnd = end + insertString.length\n\n return sentence\n}\n","import {useState, useEffect} from 'react'\n\nexport default function useDebounce(value: unknown, delay: number) {\n const [debouncedValue, setDebouncedValue] = useState(value)\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value)\n }, delay)\n\n return () => clearTimeout(handler)\n }, [value, delay])\n\n return debouncedValue\n}\n","import {defineType, StringDefinition} from 'sanity'\nimport {MarkdownEditor} from './components/Editor'\n\nconst markdownTypeName = 'markdown' as const\n\n/**\n * @public\n */\nexport interface MarkdownDefinition extends Omit<StringDefinition, 'type' | 'fields' | 'options'> {\n type: typeof markdownTypeName\n}\n\ndeclare module '@sanity/types' {\n // makes type: 'markdown' narrow correctly when using defineType/defineField/defineArrayMember\n export interface IntrinsicDefinitions {\n markdown: MarkdownDefinition\n }\n}\n\nexport const markdownSchemaType = defineType({\n type: 'string',\n name: markdownTypeName,\n title: 'Markdown',\n ...({components: {input: MarkdownEditor}} as {}), //TODO revert when rc.1 ships\n})\n","import {MarkdownEditor as Editor} from './components/Editor'\nimport {definePlugin} from 'sanity'\nimport {markdownSchemaType, MarkdownDefinition} from './schema'\n\n// re-exporting MarkdownEditor directly explodes @parcel/transformer-typescript-types :shrug:\nconst MarkdownEditor = Editor\n\nexport {MarkdownEditor, markdownSchemaType}\nexport type {MarkdownDefinition}\n\nexport const markdownSchema = definePlugin({\n name: 'markdown-editor',\n schema: {\n types: [markdownSchemaType],\n },\n})\n"],"names":["MarkdownEditor","forwardRef","props","ref","value","onChange","elementProps","onBlur","onFocus","readOnly","editedValue","setEditedValue","useState","debouncedValue","delay","setDebouncedValue","useEffect","handler","setTimeout","clearTimeout","useDebounce","client","useClient","apiVersion","PatchEvent","from","unset","set","sanity","studioTheme","useTheme","onPaste","useCallback","async","onImagePasted","event","clipboardData","onDrop","preventDefault","stopPropagation","dataTransfer","jsx","color","dark","children","Card","border","padding","MDEditor","Markdown","source","rehypePlugins","rehypeSanitize","previewOptions","preview","setMarkdown","files","index","items","length","file","item","push","Promise","all","map","url","assets","upload","then","doc","insertedMarkdown","insertString","textarea","document","querySelector","sentence","len","pos","selectionStart","end","selectionEnd","front","slice","back","insertToTextArea","concat","markdownSchemaType","defineType","_objectSpread","type","name","title","components","input","Editor","markdownSchema","definePlugin","schema","types"],"mappings":"2qBAgBO,MAAMA,EAAiBC,GAAW,SACvCC,EACAC,GAEM,MAAAC,MACJA,EAAQ,GAAAC,SACRA,EACAC,cAAcC,OAACA,EAAAC,QAAQA,GAAOC,SAC9BA,GACEP,GACGQ,EAAaC,GAAkBC,EAA6BR,GAC7DS,ECzBgB,SAAYT,EAAgBU,GAClD,MAAOD,EAAgBE,GAAqBH,EAASR,GAS9C,OARPY,GAAU,KACF,MAAAC,EAAUC,YAAW,KACzBH,EAAkBX,EAAK,GACtBU,GAEI,MAAA,IAAMK,aAAaF,EAAO,GAChC,CAACb,EAAOU,IAEJD,CACT,CDcyBO,CAAYV,EAAa,KAE1CW,EAASC,EAAU,CAACC,WAAY,eACtCP,GAAU,KACRL,EAAeP,EAAK,GACnB,CAACA,IAEJY,GAAU,MACHH,GAAkBT,EACrBC,EAASmB,EAAWC,KAAK,CAACC,OACjBb,IAAmBT,GAC5BC,EAASmB,EAAWC,KAAK,CAACE,EAAId,KAChC,GAEC,CAACA,EAAgBR,IAEpB,MAAOuB,OAAQC,GAAeC,IACxBC,EAAUC,GACdC,gBACQC,EAAcC,EAAMC,cAAezB,EAAgBU,EAAM,GAEjE,CAACV,EAAgBU,IAGbgB,EAASL,GACbC,UACEE,EAAMG,iBACNH,EAAMI,kBACFJ,EAAMK,oBACFN,EAAcC,EAAMK,aAAc7B,EAAgBU,EAC1D,GAEF,CAACV,EAAgBU,IAGnB,OACGoB,EAAA,MAAA,CAAItC,MAAU,kBAAiB0B,EAAYa,MAAMC,KAAO,OAAS,QAC/DC,WACEH,EAAAI,EAAA,CAAKC,QAAM,EAACC,QAAS,EACpBH,SAAAH,EAACO,EAASC,SAAT,CAAkBC,OAAQ9C,EAAO+C,cAAe,CAAC,CAACC,QAGpDX,EAAAO,EAAA,CACC5C,MAAOM,EACPL,SAAUM,EACVJ,SACAC,UACA6C,eAAgB,CACdF,cAAe,CAAC,CAACC,KAEnBE,QAAQ,OACRvB,UACAM,YAKV,IAGAJ,eAAeC,EACbM,EACAe,EACAlC,GAEA,MAAMmC,EAAgB,GACtB,IAAA,IAASC,EAAQ,EAAGA,EAAQjB,EAAakB,MAAMC,OAAQF,GAAS,EAAG,CACjE,MAAMG,EAAOpB,EAAagB,MAAMK,KAAKJ,GAEjCG,GACFJ,EAAMM,KAAKF,EAEf,OAEMG,QAAQC,IACZR,EAAMS,KAAIhC,UACR,MAAMiC,QAAY7C,EAAO8C,OAAOC,OAAO,QAASR,GAAMS,MAAMC,aAAWA,EAAIJ,IAAW,YAChFK,EASZ,SAA0BC,GAClB,MAAAC,EAAWC,SAASC,cAAc,YACxC,IAAKF,EACI,OAAA,KAGT,IAAIG,EAAWH,EAASrE,MACxB,MAAMyE,EAAMD,EAASjB,OACfmB,EAAML,EAASM,eACfC,EAAMP,EAASQ,aAEfC,EAAQN,EAASO,MAAM,EAAGL,GAC1BM,EAAOR,EAASO,MAAML,EAAKD,GAO1B,OALPD,EAAWM,EAAQV,EAAeY,EAElCX,EAASrE,MAAQwE,EACRH,EAAAQ,aAAeD,EAAMR,EAAab,OAEpCiB,CACT,CA7B+BS,CAAiB,OAAAC,OAAOpB,EAAM,MAClDK,GAGLhB,EAAYgB,EAAgB,IAGlC,CE5GA,MAgBagB,EAAqBC,iWAAWC,CAAA,CAC3CC,KAAM,SACNC,KAlBuB,WAmBvBC,MAAO,YACH,CAACC,WAAY,CAACC,MAAO9F,MClBrBA,EAAiB+F,EAKVC,EAAiBC,EAAa,CACzCN,KAAM,kBACNO,OAAQ,CACNC,MAAO,CAACZ"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/components/MarkdownInputStyles.tsx","../src/components/MarkdownInput.tsx","../src/schema.ts","../src/plugin.tsx"],"sourcesContent":["import styled from 'styled-components'\nimport {Box} from '@sanity/ui'\n\nexport const MarkdownInputStyles = styled(Box)`\n & .CodeMirror.CodeMirror {\n color: ${({theme}) => theme.sanity.color.card.enabled.fg};\n border-color: ${({theme}) => theme.sanity.color.card.enabled.border};\n background-color: inherit;\n }\n\n & .cm-s-easymde .CodeMirror-cursor {\n border-color: ${({theme}) => theme.sanity.color.card.enabled.fg};\n }\n\n & .editor-toolbar,\n .editor-preview-side {\n border-color: ${({theme}) => theme.sanity.color.card.enabled.border};\n }\n\n & .CodeMirror-focused .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ${({theme}) => theme.sanity.color.selectable.primary.hovered.bg};\n }\n\n & .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};\n }\n\n & .editor-toolbar > * {\n color: ${({theme}) => theme.sanity.color.card.enabled.fg};\n }\n\n & .editor-toolbar > .active,\n .editor-toolbar > button:hover,\n .editor-preview pre,\n .cm-s-easymde .cm-comment {\n background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};\n }\n\n & .editor-preview {\n background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};\n\n & h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n font-size: revert;\n }\n\n & ul,\n li {\n list-style: revert;\n padding: revert;\n }\n\n & a {\n text-decoration: revert;\n }\n }\n`\n","import 'easymde/dist/easymde.min.css'\nimport {type Options as EasyMdeOptions} from 'easymde'\nimport React, {useCallback, useMemo} from 'react'\nimport SimpleMdeReact, {SimpleMDEReactProps} from 'react-simplemde-editor'\nimport {PatchEvent, set, StringInputProps, unset, useClient} from 'sanity'\nimport {MarkdownOptions} from '../schema'\nimport {MarkdownInputStyles} from './MarkdownInputStyles'\n\nexport interface MarkdownInputProps extends StringInputProps {\n /**\n * These are passed along directly to\n *\n * Note: MarkdownInput sets certain reactMdeProps.options by default.\n * These will be merged with any custom options.\n * */\n reactMdeProps?: Omit<SimpleMDEReactProps, 'value' | 'onChange'>\n}\n\nexport const defaultMdeTools: EasyMdeOptions['toolbar'] = [\n 'heading',\n 'bold',\n 'italic',\n '|',\n 'quote',\n 'unordered-list',\n 'ordered-list',\n '|',\n 'link',\n 'image',\n 'code',\n '|',\n 'preview',\n 'side-by-side',\n]\n\nexport function MarkdownInput(props: MarkdownInputProps) {\n const {\n value = '',\n onChange,\n elementProps: {onBlur, onFocus, ref},\n reactMdeProps: {options: mdeCustomOptions, ...reactMdeProps} = {},\n schemaType,\n } = props\n const client = useClient({apiVersion: '2022-01-01'})\n const {imageUrl} = (schemaType.options as MarkdownOptions | undefined) ?? {}\n\n const imageUpload = useCallback(\n (file: File, onSuccess: (url: string) => void, onError: (error: string) => void) => {\n client.assets\n .upload('image', file)\n .then((doc) => onSuccess(imageUrl ? imageUrl(doc) : `${doc.url}?w=450`))\n .catch((e) => {\n console.error(e)\n onError(e.message)\n })\n },\n [client, imageUrl]\n )\n\n const mdeOptions: EasyMdeOptions = useMemo(() => {\n return {\n autofocus: false,\n spellChecker: false,\n sideBySideFullscreen: false,\n uploadImage: true,\n imageUploadFunction: imageUpload,\n toolbar: defaultMdeTools,\n status: false,\n ...mdeCustomOptions,\n }\n }, [imageUpload, mdeCustomOptions])\n\n const handleChange = useCallback(\n (newValue: string) => {\n onChange(PatchEvent.from(newValue ? set(newValue) : unset()))\n },\n [onChange]\n )\n\n return (\n <MarkdownInputStyles>\n <SimpleMdeReact\n {...reactMdeProps}\n ref={ref}\n value={value}\n onChange={handleChange}\n onBlur={onBlur}\n onFocus={onFocus}\n options={mdeOptions}\n spellCheck={false}\n />\n </MarkdownInputStyles>\n )\n}\n","import {defineType, StringDefinition} from 'sanity'\nimport {MarkdownInput} from './components/MarkdownInput'\nimport {SanityImageAssetDocument} from '@sanity/client'\n\nexport const markdownTypeName = 'markdown' as const\n\nexport interface MarkdownOptions {\n /**\n * Used to create image url for any uploaded image.\n * The function will be invoked whenever an image is pasted or dragged into the\n * markdown editor, after upload completes.\n *\n * The default implementation uses\n * ```js\n * imageAsset => `${imageAsset.url}?w=450`\n * ```\n * ## Example\n * ```js\n * {\n * imageUrl: imageAsset => `${imageAsset.url}?w=400&h=400`\n * }\n * ```\n * @param imageAsset\n */\n imageUrl?: (imageAsset: SanityImageAssetDocument) => string\n}\n\n/**\n * @public\n */\nexport interface MarkdownDefinition extends Omit<StringDefinition, 'type' | 'fields' | 'options'> {\n type: typeof markdownTypeName\n options?: MarkdownOptions\n}\n\ndeclare module '@sanity/types' {\n // makes type: 'markdown' narrow correctly when using defineType/defineField/defineArrayMember\n export interface IntrinsicDefinitions {\n markdown: MarkdownDefinition\n }\n}\n\nexport const markdownSchemaType = defineType({\n type: 'string',\n name: markdownTypeName,\n title: 'Markdown',\n components: {input: MarkdownInput},\n})\n","import {definePlugin, StringInputProps} from 'sanity'\nimport {markdownSchemaType} from './schema'\nimport {ReactElement} from 'react'\n\nexport interface MarkdownConfig {\n /**\n * When provided, will replace the default input component.\n *\n * Use this to customize MarkdownInput by wrapping it in a custom component,\n * and provide any custom props for https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor\n * via the `reactMdeProps` prop.\n *\n * ### Example\n *\n * ```tsx\n * // CustomMarkdownInput.tsx\n * import { MarkdownInput, MarkdownInputProps } from 'sanity-plugin-markdown'\n *\n * export function CustomMarkdownInput(props) {\n * const reactMdeProps: MarkdownInputProps['reactMdeProps'] =\n * useMemo(() => {\n * return {\n * options: {\n * toolbar: ['bold', 'italic'],\n * // more options available, see:\n * // https://github.com/Ionaru/easy-markdown-editor#options-list\n * },\n * // more props available, see:\n * // https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor\n * }\n * }, [])\n *\n * return <MarkdownInput {...props} reactMdeProps={reactMdeProps} />\n * }\n *\n * // studio.config.ts\n * markdownSchema({input: CustomMarkdownInput})\n * ```\n */\n input?: (props: StringInputProps) => ReactElement\n}\n\nexport const markdownSchema = definePlugin((config: MarkdownConfig | void) => {\n return {\n name: 'markdown-editor',\n schema: {\n types: [\n config && config.input\n ? {...markdownSchemaType, components: {input: config.input}}\n : markdownSchemaType,\n ],\n },\n }\n})\n"],"names":["MarkdownInputStyles","styled","Box","theme","sanity","color","card","enabled","fg","border","selectable","primary","hovered","bg","defaultMdeTools","MarkdownInput","props","_a","value","onChange","elementProps","onBlur","onFocus","ref","reactMdeProps","options","mdeCustomOptions","schemaType","client","useClient","apiVersion","imageUrl","imageUpload","useCallback","file","onSuccess","onError","assets","upload","then","doc","url","catch","e","console","error","message","mdeOptions","useMemo","autofocus","spellChecker","sideBySideFullscreen","uploadImage","imageUploadFunction","toolbar","status","handleChange","newValue","PatchEvent","from","set","unset","jsx","children","SimpleMdeReact","spellCheck","markdownTypeName","markdownSchemaType","defineType","type","name","title","components","input","markdownSchema","definePlugin","config","schema","types"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1,2 +1,156 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _excluded = ["options"];
|
|
4
|
+
var _templateObject;
|
|
5
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
6
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
7
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
8
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
9
|
+
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
10
|
+
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
|
|
11
|
+
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
|
12
|
+
function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
|
|
13
|
+
Object.defineProperty(exports, '__esModule', {
|
|
14
|
+
value: true
|
|
15
|
+
});
|
|
16
|
+
var sanity = require('sanity');
|
|
17
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
18
|
+
require('easymde/dist/easymde.min.css');
|
|
19
|
+
var react = require('react');
|
|
20
|
+
var SimpleMdeReact = require('react-simplemde-editor');
|
|
21
|
+
var styled = require('styled-components');
|
|
22
|
+
var ui = require('@sanity/ui');
|
|
23
|
+
function _interopDefaultCompat(e) {
|
|
24
|
+
return e && typeof e === 'object' && 'default' in e ? e : {
|
|
25
|
+
default: e
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
var SimpleMdeReact__default = /*#__PURE__*/_interopDefaultCompat(SimpleMdeReact);
|
|
29
|
+
var styled__default = /*#__PURE__*/_interopDefaultCompat(styled);
|
|
30
|
+
const MarkdownInputStyles = styled__default.default(ui.Box)(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n & .CodeMirror.CodeMirror {\n color: ", ";\n border-color: ", ";\n background-color: inherit;\n }\n\n & .cm-s-easymde .CodeMirror-cursor {\n border-color: ", ";\n }\n\n & .editor-toolbar,\n .editor-preview-side {\n border-color: ", ";\n }\n\n & .CodeMirror-focused .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ", ";\n }\n\n & .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ", ";\n }\n\n & .editor-toolbar > * {\n color: ", ";\n }\n\n & .editor-toolbar > .active,\n .editor-toolbar > button:hover,\n .editor-preview pre,\n .cm-s-easymde .cm-comment {\n background-color: ", ";\n }\n\n & .editor-preview {\n background-color: ", ";\n\n & h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n font-size: revert;\n }\n\n & ul,\n li {\n list-style: revert;\n padding: revert;\n }\n\n & a {\n text-decoration: revert;\n }\n }\n"])), _ref => {
|
|
31
|
+
let {
|
|
32
|
+
theme
|
|
33
|
+
} = _ref;
|
|
34
|
+
return theme.sanity.color.card.enabled.fg;
|
|
35
|
+
}, _ref2 => {
|
|
36
|
+
let {
|
|
37
|
+
theme
|
|
38
|
+
} = _ref2;
|
|
39
|
+
return theme.sanity.color.card.enabled.border;
|
|
40
|
+
}, _ref3 => {
|
|
41
|
+
let {
|
|
42
|
+
theme
|
|
43
|
+
} = _ref3;
|
|
44
|
+
return theme.sanity.color.card.enabled.fg;
|
|
45
|
+
}, _ref4 => {
|
|
46
|
+
let {
|
|
47
|
+
theme
|
|
48
|
+
} = _ref4;
|
|
49
|
+
return theme.sanity.color.card.enabled.border;
|
|
50
|
+
}, _ref5 => {
|
|
51
|
+
let {
|
|
52
|
+
theme
|
|
53
|
+
} = _ref5;
|
|
54
|
+
return theme.sanity.color.selectable.primary.hovered.bg;
|
|
55
|
+
}, _ref6 => {
|
|
56
|
+
let {
|
|
57
|
+
theme
|
|
58
|
+
} = _ref6;
|
|
59
|
+
return theme.sanity.color.card.enabled.bg;
|
|
60
|
+
}, _ref7 => {
|
|
61
|
+
let {
|
|
62
|
+
theme
|
|
63
|
+
} = _ref7;
|
|
64
|
+
return theme.sanity.color.card.enabled.fg;
|
|
65
|
+
}, _ref8 => {
|
|
66
|
+
let {
|
|
67
|
+
theme
|
|
68
|
+
} = _ref8;
|
|
69
|
+
return theme.sanity.color.card.enabled.bg;
|
|
70
|
+
}, _ref9 => {
|
|
71
|
+
let {
|
|
72
|
+
theme
|
|
73
|
+
} = _ref9;
|
|
74
|
+
return theme.sanity.color.card.enabled.bg;
|
|
75
|
+
});
|
|
76
|
+
const defaultMdeTools = ["heading", "bold", "italic", "|", "quote", "unordered-list", "ordered-list", "|", "link", "image", "code", "|", "preview", "side-by-side"];
|
|
77
|
+
function MarkdownInput(props) {
|
|
78
|
+
var _a;
|
|
79
|
+
const {
|
|
80
|
+
value = "",
|
|
81
|
+
onChange,
|
|
82
|
+
elementProps: {
|
|
83
|
+
onBlur,
|
|
84
|
+
onFocus,
|
|
85
|
+
ref
|
|
86
|
+
},
|
|
87
|
+
reactMdeProps: {
|
|
88
|
+
options: mdeCustomOptions
|
|
89
|
+
} = {},
|
|
90
|
+
schemaType
|
|
91
|
+
} = props,
|
|
92
|
+
reactMdeProps = _objectWithoutProperties(props.reactMdeProps, _excluded);
|
|
93
|
+
const client = sanity.useClient({
|
|
94
|
+
apiVersion: "2022-01-01"
|
|
95
|
+
});
|
|
96
|
+
const {
|
|
97
|
+
imageUrl
|
|
98
|
+
} = (_a = schemaType.options) != null ? _a : {};
|
|
99
|
+
const imageUpload = react.useCallback((file, onSuccess, onError) => {
|
|
100
|
+
client.assets.upload("image", file).then(doc => onSuccess(imageUrl ? imageUrl(doc) : "".concat(doc.url, "?w=450"))).catch(e => {
|
|
101
|
+
console.error(e);
|
|
102
|
+
onError(e.message);
|
|
103
|
+
});
|
|
104
|
+
}, [client, imageUrl]);
|
|
105
|
+
const mdeOptions = react.useMemo(() => {
|
|
106
|
+
return _objectSpread({
|
|
107
|
+
autofocus: false,
|
|
108
|
+
spellChecker: false,
|
|
109
|
+
sideBySideFullscreen: false,
|
|
110
|
+
uploadImage: true,
|
|
111
|
+
imageUploadFunction: imageUpload,
|
|
112
|
+
toolbar: defaultMdeTools,
|
|
113
|
+
status: false
|
|
114
|
+
}, mdeCustomOptions);
|
|
115
|
+
}, [imageUpload, mdeCustomOptions]);
|
|
116
|
+
const handleChange = react.useCallback(newValue => {
|
|
117
|
+
onChange(sanity.PatchEvent.from(newValue ? sanity.set(newValue) : sanity.unset()));
|
|
118
|
+
}, [onChange]);
|
|
119
|
+
return /* @__PURE__ */jsxRuntime.jsx(MarkdownInputStyles, {
|
|
120
|
+
children: /* @__PURE__ */jsxRuntime.jsx(SimpleMdeReact__default.default, _objectSpread(_objectSpread({}, reactMdeProps), {}, {
|
|
121
|
+
ref,
|
|
122
|
+
value,
|
|
123
|
+
onChange: handleChange,
|
|
124
|
+
onBlur,
|
|
125
|
+
onFocus,
|
|
126
|
+
options: mdeOptions,
|
|
127
|
+
spellCheck: false
|
|
128
|
+
}))
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const markdownTypeName = "markdown";
|
|
132
|
+
const markdownSchemaType = sanity.defineType({
|
|
133
|
+
type: "string",
|
|
134
|
+
name: markdownTypeName,
|
|
135
|
+
title: "Markdown",
|
|
136
|
+
components: {
|
|
137
|
+
input: MarkdownInput
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const markdownSchema = sanity.definePlugin(config => {
|
|
141
|
+
return {
|
|
142
|
+
name: "markdown-editor",
|
|
143
|
+
schema: {
|
|
144
|
+
types: [config && config.input ? _objectSpread(_objectSpread({}, markdownSchemaType), {}, {
|
|
145
|
+
components: {
|
|
146
|
+
input: config.input
|
|
147
|
+
}
|
|
148
|
+
}) : markdownSchemaType]
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
exports.MarkdownInput = MarkdownInput;
|
|
153
|
+
exports.defaultMdeTools = defaultMdeTools;
|
|
154
|
+
exports.markdownSchema = markdownSchema;
|
|
155
|
+
exports.markdownSchemaType = markdownSchemaType;
|
|
2
156
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/components/Editor.tsx","../src/hooks/useDebounce.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import MDEditor from '@uiw/react-md-editor'\nimport useDebounce from '../hooks/useDebounce'\nimport {PatchEvent, SanityClient, set, StringInputProps, unset, useClient} from 'sanity'\nimport React, {\n ClipboardEvent,\n forwardRef,\n Ref,\n SetStateAction,\n useCallback,\n useEffect,\n useState,\n DragEvent,\n} from 'react'\nimport {Card, useTheme} from '@sanity/ui'\nimport rehypeSanitize from 'rehype-sanitize'\n\nexport const MarkdownEditor = forwardRef(function MarkdownEditor(\n props: StringInputProps,\n ref: Ref<any>\n) {\n const {\n value = '',\n onChange,\n elementProps: {onBlur, onFocus},\n readOnly,\n } = props\n const [editedValue, setEditedValue] = useState<string | undefined>(value)\n const debouncedValue = useDebounce(editedValue, 100)\n // const client = useClient({apiVersion: '2021-03-25'})\n const client = useClient({apiVersion: '2022-01-01'})\n useEffect(() => {\n setEditedValue(value)\n }, [value])\n\n useEffect(() => {\n if (!debouncedValue && value) {\n onChange(PatchEvent.from([unset()]))\n } else if (debouncedValue !== value) {\n onChange(PatchEvent.from([set(debouncedValue)]))\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debouncedValue, onChange])\n\n const {sanity: studioTheme} = useTheme()\n const onPaste = useCallback(\n async (event: ClipboardEvent<HTMLDivElement>) => {\n await onImagePasted(event.clipboardData, setEditedValue, client)\n },\n [setEditedValue, client]\n )\n\n const onDrop = useCallback(\n async (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n if (event.dataTransfer) {\n await onImagePasted(event.dataTransfer, setEditedValue, client)\n }\n },\n [setEditedValue, client]\n )\n\n return (\n <div ref={ref} data-color-mode={studioTheme.color.dark ? 'dark' : 'light'}>\n {readOnly ? (\n <Card border padding={3}>\n <MDEditor.Markdown source={value} rehypePlugins={[[rehypeSanitize]]} />\n </Card>\n ) : (\n <MDEditor\n value={editedValue}\n onChange={setEditedValue}\n onBlur={onBlur}\n onFocus={onFocus}\n previewOptions={{\n rehypePlugins: [[rehypeSanitize]],\n }}\n preview=\"edit\"\n onPaste={onPaste}\n onDrop={onDrop}\n />\n )}\n </div>\n )\n})\n\n// https://github.com/uiwjs/react-md-editor/issues/83#issuecomment-1185471844\nasync function onImagePasted(\n dataTransfer: DataTransfer,\n setMarkdown: (value: SetStateAction<string | undefined>) => void,\n client: SanityClient\n) {\n const files: File[] = []\n for (let index = 0; index < dataTransfer.items.length; index += 1) {\n const file = dataTransfer.files.item(index)\n\n if (file) {\n files.push(file)\n }\n }\n\n await Promise.all(\n files.map(async (file) => {\n const url = await client.assets.upload('image', file).then((doc) => `${doc.url}?w=450`)\n const insertedMarkdown = insertToTextArea(``)\n if (!insertedMarkdown) {\n return\n }\n setMarkdown(insertedMarkdown)\n })\n )\n}\n\nfunction insertToTextArea(insertString: string) {\n const textarea = document.querySelector('textarea')\n if (!textarea) {\n return null\n }\n\n let sentence = textarea.value\n const len = sentence.length\n const pos = textarea.selectionStart\n const end = textarea.selectionEnd\n\n const front = sentence.slice(0, pos)\n const back = sentence.slice(pos, len)\n\n sentence = front + insertString + back\n\n textarea.value = sentence\n textarea.selectionEnd = end + insertString.length\n\n return sentence\n}\n","import {useState, useEffect} from 'react'\n\nexport default function useDebounce(value: unknown, delay: number) {\n const [debouncedValue, setDebouncedValue] = useState(value)\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value)\n }, delay)\n\n return () => clearTimeout(handler)\n }, [value, delay])\n\n return debouncedValue\n}\n","import {defineType, StringDefinition} from 'sanity'\nimport {MarkdownEditor} from './components/Editor'\n\nconst markdownTypeName = 'markdown' as const\n\n/**\n * @public\n */\nexport interface MarkdownDefinition extends Omit<StringDefinition, 'type' | 'fields' | 'options'> {\n type: typeof markdownTypeName\n}\n\ndeclare module '@sanity/types' {\n // makes type: 'markdown' narrow correctly when using defineType/defineField/defineArrayMember\n export interface IntrinsicDefinitions {\n markdown: MarkdownDefinition\n }\n}\n\nexport const markdownSchemaType = defineType({\n type: 'string',\n name: markdownTypeName,\n title: 'Markdown',\n ...({components: {input: MarkdownEditor}} as {}), //TODO revert when rc.1 ships\n})\n","import {MarkdownEditor as Editor} from './components/Editor'\nimport {definePlugin} from 'sanity'\nimport {markdownSchemaType, MarkdownDefinition} from './schema'\n\n// re-exporting MarkdownEditor directly explodes @parcel/transformer-typescript-types :shrug:\nconst MarkdownEditor = Editor\n\nexport {MarkdownEditor, markdownSchemaType}\nexport type {MarkdownDefinition}\n\nexport const markdownSchema = definePlugin({\n name: 'markdown-editor',\n schema: {\n types: [markdownSchemaType],\n },\n})\n"],"names":["MarkdownEditor","forwardRef","props","ref","value","onChange","elementProps","onBlur","onFocus","readOnly","editedValue","setEditedValue","useState","debouncedValue","delay","setDebouncedValue","useEffect","handler","setTimeout","clearTimeout","useDebounce","client","useClient","apiVersion","PatchEvent","from","unset","set","sanity","studioTheme","useTheme","onPaste","useCallback","async","onImagePasted","event","clipboardData","onDrop","preventDefault","stopPropagation","dataTransfer","jsx","color","dark","children","Card","border","padding","MDEditor","Markdown","source","rehypePlugins","rehypeSanitize","previewOptions","preview","setMarkdown","files","index","items","length","file","item","push","Promise","all","map","url","assets","upload","then","doc","insertedMarkdown","insertString","textarea","document","querySelector","sentence","len","pos","selectionStart","end","selectionEnd","front","slice","back","insertToTextArea","concat","markdownSchemaType","defineType","_objectSpread","type","name","title","components","input","Editor","markdownSchema","definePlugin","schema","types"],"mappings":"qpBAgBO,MAAMA,EAAiBC,EAAAA,YAAW,SACvCC,EACAC,GAEM,MAAAC,MACJA,EAAQ,GAAAC,SACRA,EACAC,cAAcC,OAACA,EAAAC,QAAQA,GAAOC,SAC9BA,GACEP,GACGQ,EAAaC,GAAkBC,WAA6BR,GAC7DS,ECzBgB,SAAYT,EAAgBU,GAClD,MAAOD,EAAgBE,GAAqBH,WAASR,GAS9C,OARPY,EAAAA,WAAU,KACF,MAAAC,EAAUC,YAAW,KACzBH,EAAkBX,EAAK,GACtBU,GAEI,MAAA,IAAMK,aAAaF,EAAO,GAChC,CAACb,EAAOU,IAEJD,CACT,CDcyBO,CAAYV,EAAa,KAE1CW,EAASC,EAAAA,UAAU,CAACC,WAAY,eACtCP,EAAAA,WAAU,KACRL,EAAeP,EAAK,GACnB,CAACA,IAEJY,EAAAA,WAAU,MACHH,GAAkBT,EACrBC,EAASmB,aAAWC,KAAK,CAACC,EAAAA,WACjBb,IAAmBT,GAC5BC,EAASmB,aAAWC,KAAK,CAACE,MAAId,KAChC,GAEC,CAACA,EAAgBR,IAEpB,MAAOuB,OAAQC,GAAeC,EAASA,WACjCC,EAAUC,EAAAA,aACdC,gBACQC,EAAcC,EAAMC,cAAezB,EAAgBU,EAAM,GAEjE,CAACV,EAAgBU,IAGbgB,EAASL,EAAAA,aACbC,UACEE,EAAMG,iBACNH,EAAMI,kBACFJ,EAAMK,oBACFN,EAAcC,EAAMK,aAAc7B,EAAgBU,EAC1D,GAEF,CAACV,EAAgBU,IAGnB,OACGoB,EAAAA,IAAA,MAAA,CAAItC,MAAU,kBAAiB0B,EAAYa,MAAMC,KAAO,OAAS,QAC/DC,WACEH,EAAAA,IAAAI,OAAA,CAAKC,QAAM,EAACC,QAAS,EACpBH,SAAAH,EAAAA,IAACO,UAASC,SAAT,CAAkBC,OAAQ9C,EAAO+C,cAAe,CAAC,CAACC,EAAAA,cAGpDX,EAAAA,IAAAO,UAAA,CACC5C,MAAOM,EACPL,SAAUM,EACVJ,SACAC,UACA6C,eAAgB,CACdF,cAAe,CAAC,CAACC,EAAAA,WAEnBE,QAAQ,OACRvB,UACAM,YAKV,IAGAJ,eAAeC,EACbM,EACAe,EACAlC,GAEA,MAAMmC,EAAgB,GACtB,IAAA,IAASC,EAAQ,EAAGA,EAAQjB,EAAakB,MAAMC,OAAQF,GAAS,EAAG,CACjE,MAAMG,EAAOpB,EAAagB,MAAMK,KAAKJ,GAEjCG,GACFJ,EAAMM,KAAKF,EAEf,OAEMG,QAAQC,IACZR,EAAMS,KAAIhC,UACR,MAAMiC,QAAY7C,EAAO8C,OAAOC,OAAO,QAASR,GAAMS,MAAMC,aAAWA,EAAIJ,IAAW,YAChFK,EASZ,SAA0BC,GAClB,MAAAC,EAAWC,SAASC,cAAc,YACxC,IAAKF,EACI,OAAA,KAGT,IAAIG,EAAWH,EAASrE,MACxB,MAAMyE,EAAMD,EAASjB,OACfmB,EAAML,EAASM,eACfC,EAAMP,EAASQ,aAEfC,EAAQN,EAASO,MAAM,EAAGL,GAC1BM,EAAOR,EAASO,MAAML,EAAKD,GAO1B,OALPD,EAAWM,EAAQV,EAAeY,EAElCX,EAASrE,MAAQwE,EACRH,EAAAQ,aAAeD,EAAMR,EAAab,OAEpCiB,CACT,CA7B+BS,CAAiB,OAAAC,OAAOpB,EAAM,MAClDK,GAGLhB,EAAYgB,EAAgB,IAGlC,CE5GA,MAgBagB,EAAqBC,EAAAA,0WAAWC,CAAA,CAC3CC,KAAM,SACNC,KAlBuB,WAmBvBC,MAAO,YACH,CAACC,WAAY,CAACC,MAAO9F,MClBrBA,EAAiB+F,EAKVC,EAAiBC,EAAAA,aAAa,CACzCN,KAAM,kBACNO,OAAQ,CACNC,MAAO,CAACZ"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/components/MarkdownInputStyles.tsx","../src/components/MarkdownInput.tsx","../src/schema.ts","../src/plugin.tsx"],"sourcesContent":["import styled from 'styled-components'\nimport {Box} from '@sanity/ui'\n\nexport const MarkdownInputStyles = styled(Box)`\n & .CodeMirror.CodeMirror {\n color: ${({theme}) => theme.sanity.color.card.enabled.fg};\n border-color: ${({theme}) => theme.sanity.color.card.enabled.border};\n background-color: inherit;\n }\n\n & .cm-s-easymde .CodeMirror-cursor {\n border-color: ${({theme}) => theme.sanity.color.card.enabled.fg};\n }\n\n & .editor-toolbar,\n .editor-preview-side {\n border-color: ${({theme}) => theme.sanity.color.card.enabled.border};\n }\n\n & .CodeMirror-focused .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ${({theme}) => theme.sanity.color.selectable.primary.hovered.bg};\n }\n\n & .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {\n background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};\n }\n\n & .editor-toolbar > * {\n color: ${({theme}) => theme.sanity.color.card.enabled.fg};\n }\n\n & .editor-toolbar > .active,\n .editor-toolbar > button:hover,\n .editor-preview pre,\n .cm-s-easymde .cm-comment {\n background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};\n }\n\n & .editor-preview {\n background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};\n\n & h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n font-size: revert;\n }\n\n & ul,\n li {\n list-style: revert;\n padding: revert;\n }\n\n & a {\n text-decoration: revert;\n }\n }\n`\n","import 'easymde/dist/easymde.min.css'\nimport {type Options as EasyMdeOptions} from 'easymde'\nimport React, {useCallback, useMemo} from 'react'\nimport SimpleMdeReact, {SimpleMDEReactProps} from 'react-simplemde-editor'\nimport {PatchEvent, set, StringInputProps, unset, useClient} from 'sanity'\nimport {MarkdownOptions} from '../schema'\nimport {MarkdownInputStyles} from './MarkdownInputStyles'\n\nexport interface MarkdownInputProps extends StringInputProps {\n /**\n * These are passed along directly to\n *\n * Note: MarkdownInput sets certain reactMdeProps.options by default.\n * These will be merged with any custom options.\n * */\n reactMdeProps?: Omit<SimpleMDEReactProps, 'value' | 'onChange'>\n}\n\nexport const defaultMdeTools: EasyMdeOptions['toolbar'] = [\n 'heading',\n 'bold',\n 'italic',\n '|',\n 'quote',\n 'unordered-list',\n 'ordered-list',\n '|',\n 'link',\n 'image',\n 'code',\n '|',\n 'preview',\n 'side-by-side',\n]\n\nexport function MarkdownInput(props: MarkdownInputProps) {\n const {\n value = '',\n onChange,\n elementProps: {onBlur, onFocus, ref},\n reactMdeProps: {options: mdeCustomOptions, ...reactMdeProps} = {},\n schemaType,\n } = props\n const client = useClient({apiVersion: '2022-01-01'})\n const {imageUrl} = (schemaType.options as MarkdownOptions | undefined) ?? {}\n\n const imageUpload = useCallback(\n (file: File, onSuccess: (url: string) => void, onError: (error: string) => void) => {\n client.assets\n .upload('image', file)\n .then((doc) => onSuccess(imageUrl ? imageUrl(doc) : `${doc.url}?w=450`))\n .catch((e) => {\n console.error(e)\n onError(e.message)\n })\n },\n [client, imageUrl]\n )\n\n const mdeOptions: EasyMdeOptions = useMemo(() => {\n return {\n autofocus: false,\n spellChecker: false,\n sideBySideFullscreen: false,\n uploadImage: true,\n imageUploadFunction: imageUpload,\n toolbar: defaultMdeTools,\n status: false,\n ...mdeCustomOptions,\n }\n }, [imageUpload, mdeCustomOptions])\n\n const handleChange = useCallback(\n (newValue: string) => {\n onChange(PatchEvent.from(newValue ? set(newValue) : unset()))\n },\n [onChange]\n )\n\n return (\n <MarkdownInputStyles>\n <SimpleMdeReact\n {...reactMdeProps}\n ref={ref}\n value={value}\n onChange={handleChange}\n onBlur={onBlur}\n onFocus={onFocus}\n options={mdeOptions}\n spellCheck={false}\n />\n </MarkdownInputStyles>\n )\n}\n","import {defineType, StringDefinition} from 'sanity'\nimport {MarkdownInput} from './components/MarkdownInput'\nimport {SanityImageAssetDocument} from '@sanity/client'\n\nexport const markdownTypeName = 'markdown' as const\n\nexport interface MarkdownOptions {\n /**\n * Used to create image url for any uploaded image.\n * The function will be invoked whenever an image is pasted or dragged into the\n * markdown editor, after upload completes.\n *\n * The default implementation uses\n * ```js\n * imageAsset => `${imageAsset.url}?w=450`\n * ```\n * ## Example\n * ```js\n * {\n * imageUrl: imageAsset => `${imageAsset.url}?w=400&h=400`\n * }\n * ```\n * @param imageAsset\n */\n imageUrl?: (imageAsset: SanityImageAssetDocument) => string\n}\n\n/**\n * @public\n */\nexport interface MarkdownDefinition extends Omit<StringDefinition, 'type' | 'fields' | 'options'> {\n type: typeof markdownTypeName\n options?: MarkdownOptions\n}\n\ndeclare module '@sanity/types' {\n // makes type: 'markdown' narrow correctly when using defineType/defineField/defineArrayMember\n export interface IntrinsicDefinitions {\n markdown: MarkdownDefinition\n }\n}\n\nexport const markdownSchemaType = defineType({\n type: 'string',\n name: markdownTypeName,\n title: 'Markdown',\n components: {input: MarkdownInput},\n})\n","import {definePlugin, StringInputProps} from 'sanity'\nimport {markdownSchemaType} from './schema'\nimport {ReactElement} from 'react'\n\nexport interface MarkdownConfig {\n /**\n * When provided, will replace the default input component.\n *\n * Use this to customize MarkdownInput by wrapping it in a custom component,\n * and provide any custom props for https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor\n * via the `reactMdeProps` prop.\n *\n * ### Example\n *\n * ```tsx\n * // CustomMarkdownInput.tsx\n * import { MarkdownInput, MarkdownInputProps } from 'sanity-plugin-markdown'\n *\n * export function CustomMarkdownInput(props) {\n * const reactMdeProps: MarkdownInputProps['reactMdeProps'] =\n * useMemo(() => {\n * return {\n * options: {\n * toolbar: ['bold', 'italic'],\n * // more options available, see:\n * // https://github.com/Ionaru/easy-markdown-editor#options-list\n * },\n * // more props available, see:\n * // https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor\n * }\n * }, [])\n *\n * return <MarkdownInput {...props} reactMdeProps={reactMdeProps} />\n * }\n *\n * // studio.config.ts\n * markdownSchema({input: CustomMarkdownInput})\n * ```\n */\n input?: (props: StringInputProps) => ReactElement\n}\n\nexport const markdownSchema = definePlugin((config: MarkdownConfig | void) => {\n return {\n name: 'markdown-editor',\n schema: {\n types: [\n config && config.input\n ? {...markdownSchemaType, components: {input: config.input}}\n : markdownSchemaType,\n ],\n },\n }\n})\n"],"names":["MarkdownInputStyles","styled","Box","theme","sanity","color","card","enabled","fg","border","selectable","primary","hovered","bg","defaultMdeTools","MarkdownInput","props","_a","value","onChange","elementProps","onBlur","onFocus","ref","reactMdeProps","options","mdeCustomOptions","schemaType","client","useClient","apiVersion","imageUrl","imageUpload","useCallback","file","onSuccess","onError","assets","upload","then","doc","url","catch","e","console","error","message","mdeOptions","useMemo","autofocus","spellChecker","sideBySideFullscreen","uploadImage","imageUploadFunction","toolbar","status","handleChange","newValue","PatchEvent","from","set","unset","jsx","children","SimpleMdeReact","spellCheck","markdownTypeName","markdownSchemaType","defineType","type","name","title","components","input","markdownSchema","definePlugin","config","schema","types"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-markdown",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Markdown fields in Sanity Studio. Supports Github flavored Markdown and image uploads.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "
|
|
18
|
+
"url": "git@github.com:sanity-io/sanity-plugin-markdown.git"
|
|
19
19
|
},
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"author": "Sanity.io <hello@sanity.io>",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
|
-
"types": "./lib/
|
|
24
|
+
"types": "./lib/index.d.ts",
|
|
25
25
|
"source": "./src/index.ts",
|
|
26
26
|
"import": "./lib/index.esm.js",
|
|
27
27
|
"require": "./lib/index.js",
|
|
@@ -32,59 +32,64 @@
|
|
|
32
32
|
"main": "./lib/index.js",
|
|
33
33
|
"module": "./lib/index.esm.js",
|
|
34
34
|
"source": "./src/index.ts",
|
|
35
|
-
"types": "./lib/
|
|
35
|
+
"types": "./lib/index.d.ts",
|
|
36
36
|
"files": [
|
|
37
|
-
"src",
|
|
38
37
|
"lib",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
38
|
+
"sanity.json",
|
|
39
|
+
"src",
|
|
40
|
+
"v2-incompatible.js"
|
|
41
41
|
],
|
|
42
42
|
"scripts": {
|
|
43
43
|
"prebuild": "npm run clean && plugin-kit verify-package --silent && pkg-utils",
|
|
44
|
-
"build": "pkg-utils build --strict",
|
|
44
|
+
"build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict",
|
|
45
45
|
"clean": "rimraf lib",
|
|
46
46
|
"compile": "tsc --noEmit",
|
|
47
|
-
"format": "prettier
|
|
47
|
+
"format": "prettier --write --cache --ignore-unknown .",
|
|
48
48
|
"link-watch": "plugin-kit link-watch",
|
|
49
49
|
"lint": "eslint .",
|
|
50
50
|
"prepare": "husky install",
|
|
51
|
-
"prepublishOnly": "
|
|
52
|
-
"watch": "pkg-utils watch"
|
|
51
|
+
"prepublishOnly": "run-s build",
|
|
52
|
+
"watch": "pkg-utils watch --strict"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
56
|
-
"@sanity/ui": "1.0.0
|
|
57
|
-
"
|
|
58
|
-
"rehype-sanitize": "^5.0.1"
|
|
56
|
+
"@sanity/ui": "^1.0.0",
|
|
57
|
+
"react-simplemde-editor": "^5.2.0"
|
|
59
58
|
},
|
|
60
59
|
"devDependencies": {
|
|
61
60
|
"@commitlint/cli": "^17.2.0",
|
|
62
61
|
"@commitlint/config-conventional": "^17.2.0",
|
|
63
|
-
"@sanity/pkg-utils": "^1.
|
|
64
|
-
"@sanity/plugin-kit": "^
|
|
62
|
+
"@sanity/pkg-utils": "^2.1.0",
|
|
63
|
+
"@sanity/plugin-kit": "^3.1.1",
|
|
65
64
|
"@sanity/semantic-release-preset": "^2.0.2",
|
|
65
|
+
"@types/react": "^18.0.26",
|
|
66
66
|
"@types/styled-components": "^5.1.26",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
68
|
-
"@typescript-eslint/parser": "^5.
|
|
69
|
-
"
|
|
70
|
-
"eslint
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
|
68
|
+
"@typescript-eslint/parser": "^5.48.0",
|
|
69
|
+
"easymde": "^2.18.0",
|
|
70
|
+
"eslint": "^8.31.0",
|
|
71
|
+
"eslint-config-prettier": "^8.6.0",
|
|
71
72
|
"eslint-config-sanity": "^6.0.0",
|
|
72
73
|
"eslint-plugin-prettier": "^4.2.1",
|
|
73
|
-
"eslint-plugin-react": "^7.31.
|
|
74
|
+
"eslint-plugin-react": "^7.31.11",
|
|
74
75
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
75
76
|
"husky": "^8.0.1",
|
|
76
77
|
"lint-staged": "^13.0.3",
|
|
77
|
-
"
|
|
78
|
+
"npm-run-all": "^4.1.5",
|
|
79
|
+
"prettier": "^2.8.1",
|
|
78
80
|
"prettier-plugin-packagejson": "^2.3.0",
|
|
79
|
-
"react": "^18",
|
|
81
|
+
"react": "^18.2.0",
|
|
82
|
+
"react-dom": "^18.2.0",
|
|
83
|
+
"react-is": "^18.2.0",
|
|
80
84
|
"rimraf": "^3.0.2",
|
|
81
|
-
"sanity": "3.
|
|
85
|
+
"sanity": "^3.1.4",
|
|
82
86
|
"styled-components": "^5.3.6",
|
|
83
|
-
"typescript": "^4.
|
|
87
|
+
"typescript": "^4.9.4"
|
|
84
88
|
},
|
|
85
89
|
"peerDependencies": {
|
|
90
|
+
"easymde": "^2",
|
|
86
91
|
"react": "^18",
|
|
87
|
-
"sanity": "
|
|
92
|
+
"sanity": "^3",
|
|
88
93
|
"styled-components": "^5.2"
|
|
89
94
|
},
|
|
90
95
|
"engines": {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import 'easymde/dist/easymde.min.css'
|
|
2
|
+
import {type Options as EasyMdeOptions} from 'easymde'
|
|
3
|
+
import React, {useCallback, useMemo} from 'react'
|
|
4
|
+
import SimpleMdeReact, {SimpleMDEReactProps} from 'react-simplemde-editor'
|
|
5
|
+
import {PatchEvent, set, StringInputProps, unset, useClient} from 'sanity'
|
|
6
|
+
import {MarkdownOptions} from '../schema'
|
|
7
|
+
import {MarkdownInputStyles} from './MarkdownInputStyles'
|
|
8
|
+
|
|
9
|
+
export interface MarkdownInputProps extends StringInputProps {
|
|
10
|
+
/**
|
|
11
|
+
* These are passed along directly to
|
|
12
|
+
*
|
|
13
|
+
* Note: MarkdownInput sets certain reactMdeProps.options by default.
|
|
14
|
+
* These will be merged with any custom options.
|
|
15
|
+
* */
|
|
16
|
+
reactMdeProps?: Omit<SimpleMDEReactProps, 'value' | 'onChange'>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const defaultMdeTools: EasyMdeOptions['toolbar'] = [
|
|
20
|
+
'heading',
|
|
21
|
+
'bold',
|
|
22
|
+
'italic',
|
|
23
|
+
'|',
|
|
24
|
+
'quote',
|
|
25
|
+
'unordered-list',
|
|
26
|
+
'ordered-list',
|
|
27
|
+
'|',
|
|
28
|
+
'link',
|
|
29
|
+
'image',
|
|
30
|
+
'code',
|
|
31
|
+
'|',
|
|
32
|
+
'preview',
|
|
33
|
+
'side-by-side',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
export function MarkdownInput(props: MarkdownInputProps) {
|
|
37
|
+
const {
|
|
38
|
+
value = '',
|
|
39
|
+
onChange,
|
|
40
|
+
elementProps: {onBlur, onFocus, ref},
|
|
41
|
+
reactMdeProps: {options: mdeCustomOptions, ...reactMdeProps} = {},
|
|
42
|
+
schemaType,
|
|
43
|
+
} = props
|
|
44
|
+
const client = useClient({apiVersion: '2022-01-01'})
|
|
45
|
+
const {imageUrl} = (schemaType.options as MarkdownOptions | undefined) ?? {}
|
|
46
|
+
|
|
47
|
+
const imageUpload = useCallback(
|
|
48
|
+
(file: File, onSuccess: (url: string) => void, onError: (error: string) => void) => {
|
|
49
|
+
client.assets
|
|
50
|
+
.upload('image', file)
|
|
51
|
+
.then((doc) => onSuccess(imageUrl ? imageUrl(doc) : `${doc.url}?w=450`))
|
|
52
|
+
.catch((e) => {
|
|
53
|
+
console.error(e)
|
|
54
|
+
onError(e.message)
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
[client, imageUrl]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const mdeOptions: EasyMdeOptions = useMemo(() => {
|
|
61
|
+
return {
|
|
62
|
+
autofocus: false,
|
|
63
|
+
spellChecker: false,
|
|
64
|
+
sideBySideFullscreen: false,
|
|
65
|
+
uploadImage: true,
|
|
66
|
+
imageUploadFunction: imageUpload,
|
|
67
|
+
toolbar: defaultMdeTools,
|
|
68
|
+
status: false,
|
|
69
|
+
...mdeCustomOptions,
|
|
70
|
+
}
|
|
71
|
+
}, [imageUpload, mdeCustomOptions])
|
|
72
|
+
|
|
73
|
+
const handleChange = useCallback(
|
|
74
|
+
(newValue: string) => {
|
|
75
|
+
onChange(PatchEvent.from(newValue ? set(newValue) : unset()))
|
|
76
|
+
},
|
|
77
|
+
[onChange]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<MarkdownInputStyles>
|
|
82
|
+
<SimpleMdeReact
|
|
83
|
+
{...reactMdeProps}
|
|
84
|
+
ref={ref}
|
|
85
|
+
value={value}
|
|
86
|
+
onChange={handleChange}
|
|
87
|
+
onBlur={onBlur}
|
|
88
|
+
onFocus={onFocus}
|
|
89
|
+
options={mdeOptions}
|
|
90
|
+
spellCheck={false}
|
|
91
|
+
/>
|
|
92
|
+
</MarkdownInputStyles>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import {Box} from '@sanity/ui'
|
|
3
|
+
|
|
4
|
+
export const MarkdownInputStyles = styled(Box)`
|
|
5
|
+
& .CodeMirror.CodeMirror {
|
|
6
|
+
color: ${({theme}) => theme.sanity.color.card.enabled.fg};
|
|
7
|
+
border-color: ${({theme}) => theme.sanity.color.card.enabled.border};
|
|
8
|
+
background-color: inherit;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
& .cm-s-easymde .CodeMirror-cursor {
|
|
12
|
+
border-color: ${({theme}) => theme.sanity.color.card.enabled.fg};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
& .editor-toolbar,
|
|
16
|
+
.editor-preview-side {
|
|
17
|
+
border-color: ${({theme}) => theme.sanity.color.card.enabled.border};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
& .CodeMirror-focused .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {
|
|
21
|
+
background-color: ${({theme}) => theme.sanity.color.selectable.primary.hovered.bg};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
& .CodeMirror-selected.CodeMirror-selected.CodeMirror-selected {
|
|
25
|
+
background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
& .editor-toolbar > * {
|
|
29
|
+
color: ${({theme}) => theme.sanity.color.card.enabled.fg};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
& .editor-toolbar > .active,
|
|
33
|
+
.editor-toolbar > button:hover,
|
|
34
|
+
.editor-preview pre,
|
|
35
|
+
.cm-s-easymde .cm-comment {
|
|
36
|
+
background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
& .editor-preview {
|
|
40
|
+
background-color: ${({theme}) => theme.sanity.color.card.enabled.bg};
|
|
41
|
+
|
|
42
|
+
& h1,
|
|
43
|
+
h2,
|
|
44
|
+
h3,
|
|
45
|
+
h4,
|
|
46
|
+
h5,
|
|
47
|
+
h6 {
|
|
48
|
+
font-size: revert;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
& ul,
|
|
52
|
+
li {
|
|
53
|
+
list-style: revert;
|
|
54
|
+
padding: revert;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
& a {
|
|
58
|
+
text-decoration: revert;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {markdownSchemaType, MarkdownDefinition} from './schema'
|
|
1
|
+
export {markdownSchemaType, type MarkdownDefinition} from './schema'
|
|
2
|
+
export {MarkdownInput, defaultMdeTools, type MarkdownInputProps} from './components/MarkdownInput'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
const MarkdownEditor = Editor
|
|
7
|
-
|
|
8
|
-
export {MarkdownEditor, markdownSchemaType}
|
|
9
|
-
export type {MarkdownDefinition}
|
|
10
|
-
|
|
11
|
-
export const markdownSchema = definePlugin({
|
|
12
|
-
name: 'markdown-editor',
|
|
13
|
-
schema: {
|
|
14
|
-
types: [markdownSchemaType],
|
|
15
|
-
},
|
|
16
|
-
})
|
|
4
|
+
export {markdownSchema, type MarkdownConfig} from './plugin'
|
package/src/plugin.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {definePlugin, StringInputProps} from 'sanity'
|
|
2
|
+
import {markdownSchemaType} from './schema'
|
|
3
|
+
import {ReactElement} from 'react'
|
|
4
|
+
|
|
5
|
+
export interface MarkdownConfig {
|
|
6
|
+
/**
|
|
7
|
+
* When provided, will replace the default input component.
|
|
8
|
+
*
|
|
9
|
+
* Use this to customize MarkdownInput by wrapping it in a custom component,
|
|
10
|
+
* and provide any custom props for https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor
|
|
11
|
+
* via the `reactMdeProps` prop.
|
|
12
|
+
*
|
|
13
|
+
* ### Example
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // CustomMarkdownInput.tsx
|
|
17
|
+
* import { MarkdownInput, MarkdownInputProps } from 'sanity-plugin-markdown'
|
|
18
|
+
*
|
|
19
|
+
* export function CustomMarkdownInput(props) {
|
|
20
|
+
* const reactMdeProps: MarkdownInputProps['reactMdeProps'] =
|
|
21
|
+
* useMemo(() => {
|
|
22
|
+
* return {
|
|
23
|
+
* options: {
|
|
24
|
+
* toolbar: ['bold', 'italic'],
|
|
25
|
+
* // more options available, see:
|
|
26
|
+
* // https://github.com/Ionaru/easy-markdown-editor#options-list
|
|
27
|
+
* },
|
|
28
|
+
* // more props available, see:
|
|
29
|
+
* // https://github.com/RIP21/react-simplemde-editor#react-simplemde-easymde-markdown-editor
|
|
30
|
+
* }
|
|
31
|
+
* }, [])
|
|
32
|
+
*
|
|
33
|
+
* return <MarkdownInput {...props} reactMdeProps={reactMdeProps} />
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* // studio.config.ts
|
|
37
|
+
* markdownSchema({input: CustomMarkdownInput})
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
input?: (props: StringInputProps) => ReactElement
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const markdownSchema = definePlugin((config: MarkdownConfig | void) => {
|
|
44
|
+
return {
|
|
45
|
+
name: 'markdown-editor',
|
|
46
|
+
schema: {
|
|
47
|
+
types: [
|
|
48
|
+
config && config.input
|
|
49
|
+
? {...markdownSchemaType, components: {input: config.input}}
|
|
50
|
+
: markdownSchemaType,
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
})
|
package/src/schema.ts
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import {defineType, StringDefinition} from 'sanity'
|
|
2
|
-
import {
|
|
2
|
+
import {MarkdownInput} from './components/MarkdownInput'
|
|
3
|
+
import {SanityImageAssetDocument} from '@sanity/client'
|
|
3
4
|
|
|
4
|
-
const markdownTypeName = 'markdown' as const
|
|
5
|
+
export const markdownTypeName = 'markdown' as const
|
|
6
|
+
|
|
7
|
+
export interface MarkdownOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Used to create image url for any uploaded image.
|
|
10
|
+
* The function will be invoked whenever an image is pasted or dragged into the
|
|
11
|
+
* markdown editor, after upload completes.
|
|
12
|
+
*
|
|
13
|
+
* The default implementation uses
|
|
14
|
+
* ```js
|
|
15
|
+
* imageAsset => `${imageAsset.url}?w=450`
|
|
16
|
+
* ```
|
|
17
|
+
* ## Example
|
|
18
|
+
* ```js
|
|
19
|
+
* {
|
|
20
|
+
* imageUrl: imageAsset => `${imageAsset.url}?w=400&h=400`
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
* @param imageAsset
|
|
24
|
+
*/
|
|
25
|
+
imageUrl?: (imageAsset: SanityImageAssetDocument) => string
|
|
26
|
+
}
|
|
5
27
|
|
|
6
28
|
/**
|
|
7
29
|
* @public
|
|
8
30
|
*/
|
|
9
31
|
export interface MarkdownDefinition extends Omit<StringDefinition, 'type' | 'fields' | 'options'> {
|
|
10
32
|
type: typeof markdownTypeName
|
|
33
|
+
options?: MarkdownOptions
|
|
11
34
|
}
|
|
12
35
|
|
|
13
36
|
declare module '@sanity/types' {
|
|
@@ -21,5 +44,5 @@ export const markdownSchemaType = defineType({
|
|
|
21
44
|
type: 'string',
|
|
22
45
|
name: markdownTypeName,
|
|
23
46
|
title: 'Markdown',
|
|
24
|
-
|
|
47
|
+
components: {input: MarkdownInput},
|
|
25
48
|
})
|
package/lib/src/index.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
|
|
3
|
-
import {ForwardRefExoticComponent} from 'react'
|
|
4
|
-
import {Plugin as Plugin_2} from 'sanity'
|
|
5
|
-
import {RefAttributes} from 'react'
|
|
6
|
-
import {StringDefinition} from 'sanity'
|
|
7
|
-
import {StringInputProps} from 'sanity'
|
|
8
|
-
import {StringSchemaType} from 'sanity'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @public
|
|
12
|
-
*/
|
|
13
|
-
export declare interface MarkdownDefinition
|
|
14
|
-
extends Omit<StringDefinition, 'type' | 'fields' | 'options'> {
|
|
15
|
-
type: typeof markdownTypeName
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export declare const MarkdownEditor: ForwardRefExoticComponent<
|
|
19
|
-
StringInputProps<StringSchemaType> & RefAttributes<any>
|
|
20
|
-
>
|
|
21
|
-
|
|
22
|
-
export declare const markdownSchema: Plugin_2<void>
|
|
23
|
-
|
|
24
|
-
export declare const markdownSchemaType: {
|
|
25
|
-
type: 'string'
|
|
26
|
-
name: 'markdown'
|
|
27
|
-
} & Omit<StringDefinition, 'preview'>
|
|
28
|
-
|
|
29
|
-
declare const markdownTypeName: 'markdown'
|
|
30
|
-
|
|
31
|
-
export {}
|
|
32
|
-
|
|
33
|
-
declare module '@sanity/types' {
|
|
34
|
-
interface IntrinsicDefinitions {
|
|
35
|
-
markdown: MarkdownDefinition
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import MDEditor from '@uiw/react-md-editor'
|
|
2
|
-
import useDebounce from '../hooks/useDebounce'
|
|
3
|
-
import {PatchEvent, SanityClient, set, StringInputProps, unset, useClient} from 'sanity'
|
|
4
|
-
import React, {
|
|
5
|
-
ClipboardEvent,
|
|
6
|
-
forwardRef,
|
|
7
|
-
Ref,
|
|
8
|
-
SetStateAction,
|
|
9
|
-
useCallback,
|
|
10
|
-
useEffect,
|
|
11
|
-
useState,
|
|
12
|
-
DragEvent,
|
|
13
|
-
} from 'react'
|
|
14
|
-
import {Card, useTheme} from '@sanity/ui'
|
|
15
|
-
import rehypeSanitize from 'rehype-sanitize'
|
|
16
|
-
|
|
17
|
-
export const MarkdownEditor = forwardRef(function MarkdownEditor(
|
|
18
|
-
props: StringInputProps,
|
|
19
|
-
ref: Ref<any>
|
|
20
|
-
) {
|
|
21
|
-
const {
|
|
22
|
-
value = '',
|
|
23
|
-
onChange,
|
|
24
|
-
elementProps: {onBlur, onFocus},
|
|
25
|
-
readOnly,
|
|
26
|
-
} = props
|
|
27
|
-
const [editedValue, setEditedValue] = useState<string | undefined>(value)
|
|
28
|
-
const debouncedValue = useDebounce(editedValue, 100)
|
|
29
|
-
// const client = useClient({apiVersion: '2021-03-25'})
|
|
30
|
-
const client = useClient({apiVersion: '2022-01-01'})
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
setEditedValue(value)
|
|
33
|
-
}, [value])
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (!debouncedValue && value) {
|
|
37
|
-
onChange(PatchEvent.from([unset()]))
|
|
38
|
-
} else if (debouncedValue !== value) {
|
|
39
|
-
onChange(PatchEvent.from([set(debouncedValue)]))
|
|
40
|
-
}
|
|
41
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
-
}, [debouncedValue, onChange])
|
|
43
|
-
|
|
44
|
-
const {sanity: studioTheme} = useTheme()
|
|
45
|
-
const onPaste = useCallback(
|
|
46
|
-
async (event: ClipboardEvent<HTMLDivElement>) => {
|
|
47
|
-
await onImagePasted(event.clipboardData, setEditedValue, client)
|
|
48
|
-
},
|
|
49
|
-
[setEditedValue, client]
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
const onDrop = useCallback(
|
|
53
|
-
async (event: DragEvent<HTMLDivElement>) => {
|
|
54
|
-
event.preventDefault()
|
|
55
|
-
event.stopPropagation()
|
|
56
|
-
if (event.dataTransfer) {
|
|
57
|
-
await onImagePasted(event.dataTransfer, setEditedValue, client)
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
[setEditedValue, client]
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div ref={ref} data-color-mode={studioTheme.color.dark ? 'dark' : 'light'}>
|
|
65
|
-
{readOnly ? (
|
|
66
|
-
<Card border padding={3}>
|
|
67
|
-
<MDEditor.Markdown source={value} rehypePlugins={[[rehypeSanitize]]} />
|
|
68
|
-
</Card>
|
|
69
|
-
) : (
|
|
70
|
-
<MDEditor
|
|
71
|
-
value={editedValue}
|
|
72
|
-
onChange={setEditedValue}
|
|
73
|
-
onBlur={onBlur}
|
|
74
|
-
onFocus={onFocus}
|
|
75
|
-
previewOptions={{
|
|
76
|
-
rehypePlugins: [[rehypeSanitize]],
|
|
77
|
-
}}
|
|
78
|
-
preview="edit"
|
|
79
|
-
onPaste={onPaste}
|
|
80
|
-
onDrop={onDrop}
|
|
81
|
-
/>
|
|
82
|
-
)}
|
|
83
|
-
</div>
|
|
84
|
-
)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
// https://github.com/uiwjs/react-md-editor/issues/83#issuecomment-1185471844
|
|
88
|
-
async function onImagePasted(
|
|
89
|
-
dataTransfer: DataTransfer,
|
|
90
|
-
setMarkdown: (value: SetStateAction<string | undefined>) => void,
|
|
91
|
-
client: SanityClient
|
|
92
|
-
) {
|
|
93
|
-
const files: File[] = []
|
|
94
|
-
for (let index = 0; index < dataTransfer.items.length; index += 1) {
|
|
95
|
-
const file = dataTransfer.files.item(index)
|
|
96
|
-
|
|
97
|
-
if (file) {
|
|
98
|
-
files.push(file)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
await Promise.all(
|
|
103
|
-
files.map(async (file) => {
|
|
104
|
-
const url = await client.assets.upload('image', file).then((doc) => `${doc.url}?w=450`)
|
|
105
|
-
const insertedMarkdown = insertToTextArea(``)
|
|
106
|
-
if (!insertedMarkdown) {
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
setMarkdown(insertedMarkdown)
|
|
110
|
-
})
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function insertToTextArea(insertString: string) {
|
|
115
|
-
const textarea = document.querySelector('textarea')
|
|
116
|
-
if (!textarea) {
|
|
117
|
-
return null
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
let sentence = textarea.value
|
|
121
|
-
const len = sentence.length
|
|
122
|
-
const pos = textarea.selectionStart
|
|
123
|
-
const end = textarea.selectionEnd
|
|
124
|
-
|
|
125
|
-
const front = sentence.slice(0, pos)
|
|
126
|
-
const back = sentence.slice(pos, len)
|
|
127
|
-
|
|
128
|
-
sentence = front + insertString + back
|
|
129
|
-
|
|
130
|
-
textarea.value = sentence
|
|
131
|
-
textarea.selectionEnd = end + insertString.length
|
|
132
|
-
|
|
133
|
-
return sentence
|
|
134
|
-
}
|
package/src/hooks/useDebounce.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {useState, useEffect} from 'react'
|
|
2
|
-
|
|
3
|
-
export default function useDebounce(value: unknown, delay: number) {
|
|
4
|
-
const [debouncedValue, setDebouncedValue] = useState(value)
|
|
5
|
-
useEffect(() => {
|
|
6
|
-
const handler = setTimeout(() => {
|
|
7
|
-
setDebouncedValue(value)
|
|
8
|
-
}, delay)
|
|
9
|
-
|
|
10
|
-
return () => clearTimeout(handler)
|
|
11
|
-
}, [value, delay])
|
|
12
|
-
|
|
13
|
-
return debouncedValue
|
|
14
|
-
}
|