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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Sanity.io
3
+ Copyright (c) 2023 Sanity.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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. You can either drag image(s) into the editor or click the bottom bar to bring up a file selector.
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 v2 version used react-mde for editing. The current v3 version @uiw/react-md-editor. This may change before GA.
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
- At the moment the v3 version does not have drag-and-drop image upload support.
18
- You can still add markdown image tags, but you will have to get the image url yourself for now.
17
+ ![example.png](./assets/example.png)
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
- ![demo](https://user-images.githubusercontent.com/38528/113196621-91ec8780-9218-11eb-86cc-cf0adfa2fd01.gif)
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
- function e(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function t(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}import{jsx as r}from"react/jsx-runtime";import n from"@uiw/react-md-editor";import{useState as o,useEffect as a,forwardRef as i,useCallback as c}from"react";import{useClient as s,PatchEvent as l,unset as u,set as p,defineType as m,definePlugin as f}from"sanity";import{useTheme as d,Card as y}from"@sanity/ui";import b from"rehype-sanitize";const g=i((function(e,t){const{value:i="",onChange:m,elementProps:{onBlur:f,onFocus:g},readOnly:w}=e,[h,P]=o(i),j=function(e,t){const[r,n]=o(e);return a((()=>{const r=setTimeout((()=>{n(e)}),t);return()=>clearTimeout(r)}),[e,t]),r}(h,100),v=s({apiVersion:"2022-01-01"});a((()=>{P(i)}),[i]),a((()=>{!j&&i?m(l.from([u()])):j!==i&&m(l.from([p(j)]))}),[j,m]);const{sanity:k}=d(),D=c((async e=>{await O(e.clipboardData,P,v)}),[P,v]),E=c((async e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer&&await O(e.dataTransfer,P,v)}),[P,v]);return r("div",{ref:t,"data-color-mode":k.color.dark?"dark":"light",children:w?r(y,{border:!0,padding:3,children:r(n.Markdown,{source:i,rehypePlugins:[[b]]})}):r(n,{value:h,onChange:P,onBlur:f,onFocus:g,previewOptions:{rehypePlugins:[[b]]},preview:"edit",onPaste:D,onDrop:E})})}));async function O(e,t,r){const n=[];for(let t=0;t<e.items.length;t+=1){const r=e.files.item(t);r&&n.push(r)}await Promise.all(n.map((async e=>{const n=await r.assets.upload("image",e).then((e=>"".concat(e.url,"?w=450"))),o=function(e){const t=document.querySelector("textarea");if(!t)return null;let r=t.value;const n=r.length,o=t.selectionStart,a=t.selectionEnd,i=r.slice(0,o),c=r.slice(o,n);return r=i+e+c,t.value=r,t.selectionEnd=a+e.length,r}("![](".concat(n,")"));o&&t(o)})))}const w=m(function(r){for(var n=1;n<arguments.length;n++){var o=null!=arguments[n]?arguments[n]:{};n%2?e(Object(o),!0).forEach((function(e){t(r,e,o[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(o)):e(Object(o)).forEach((function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(o,e))}))}return r}({type:"string",name:"markdown",title:"Markdown"},{components:{input:g}})),h=g,P=f({name:"markdown-editor",schema:{types:[w]}});export{h as MarkdownEditor,P as markdownSchema,w as markdownSchemaType};
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
@@ -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(`![](${url})`)\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
- "use strict";function e(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function t(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}Object.defineProperty(exports,"__esModule",{value:!0});var r=require("react/jsx-runtime"),n=require("@uiw/react-md-editor"),o=require("react"),a=require("sanity"),i=require("@sanity/ui"),c=require("rehype-sanitize");function s(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var u=s(n),l=s(c);const f=o.forwardRef((function(e,t){const{value:n="",onChange:c,elementProps:{onBlur:s,onFocus:f},readOnly:p}=e,[y,m]=o.useState(n),b=function(e,t){const[r,n]=o.useState(e);return o.useEffect((()=>{const r=setTimeout((()=>{n(e)}),t);return()=>clearTimeout(r)}),[e,t]),r}(y,100),h=a.useClient({apiVersion:"2022-01-01"});o.useEffect((()=>{m(n)}),[n]),o.useEffect((()=>{!b&&n?c(a.PatchEvent.from([a.unset()])):b!==n&&c(a.PatchEvent.from([a.set(b)]))}),[b,c]);const{sanity:w}=i.useTheme(),g=o.useCallback((async e=>{await d(e.clipboardData,m,h)}),[m,h]),O=o.useCallback((async e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer&&await d(e.dataTransfer,m,h)}),[m,h]);return r.jsx("div",{ref:t,"data-color-mode":w.color.dark?"dark":"light",children:p?r.jsx(i.Card,{border:!0,padding:3,children:r.jsx(u.default.Markdown,{source:n,rehypePlugins:[[l.default]]})}):r.jsx(u.default,{value:y,onChange:m,onBlur:s,onFocus:f,previewOptions:{rehypePlugins:[[l.default]]},preview:"edit",onPaste:g,onDrop:O})})}));async function d(e,t,r){const n=[];for(let t=0;t<e.items.length;t+=1){const r=e.files.item(t);r&&n.push(r)}await Promise.all(n.map((async e=>{const n=await r.assets.upload("image",e).then((e=>"".concat(e.url,"?w=450"))),o=function(e){const t=document.querySelector("textarea");if(!t)return null;let r=t.value;const n=r.length,o=t.selectionStart,a=t.selectionEnd,i=r.slice(0,o),c=r.slice(o,n);return r=i+e+c,t.value=r,t.selectionEnd=a+e.length,r}("![](".concat(n,")"));o&&t(o)})))}const p=a.defineType(function(r){for(var n=1;n<arguments.length;n++){var o=null!=arguments[n]?arguments[n]:{};n%2?e(Object(o),!0).forEach((function(e){t(r,e,o[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(o)):e(Object(o)).forEach((function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(o,e))}))}return r}({type:"string",name:"markdown",title:"Markdown"},{components:{input:f}})),y=f,m=a.definePlugin({name:"markdown-editor",schema:{types:[p]}});exports.MarkdownEditor=y,exports.markdownSchema=m,exports.markdownSchemaType=p;
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(`![](${url})`)\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.0.0",
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": "https://github.com/sanity-io/sanity-plugin-markdown.git"
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/src/index.d.ts",
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/src/index.d.ts",
35
+ "types": "./lib/index.d.ts",
36
36
  "files": [
37
- "src",
38
37
  "lib",
39
- "v2-incompatible.js",
40
- "sanity.json"
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 src -w",
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": " npm run build",
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-beta.32",
57
- "@uiw/react-md-editor": "^3.19.7",
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.18.0",
64
- "@sanity/plugin-kit": "^2.1.16",
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.42.0",
68
- "@typescript-eslint/parser": "^5.42.0",
69
- "eslint": "^8.26.0",
70
- "eslint-config-prettier": "^8.5.0",
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.10",
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
- "prettier": "^2.7.1",
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.0.0-rc.2",
85
+ "sanity": "^3.1.4",
82
86
  "styled-components": "^5.3.6",
83
- "typescript": "^4.8.4"
87
+ "typescript": "^4.9.4"
84
88
  },
85
89
  "peerDependencies": {
90
+ "easymde": "^2",
86
91
  "react": "^18",
87
- "sanity": "dev-preview || 3.0.0-rc.2",
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
- import {MarkdownEditor as Editor} from './components/Editor'
2
- import {definePlugin} from 'sanity'
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
- // re-exporting MarkdownEditor directly explodes @parcel/transformer-typescript-types :shrug:
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 {MarkdownEditor} from './components/Editor'
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
- ...({components: {input: MarkdownEditor}} as {}), //TODO revert when rc.1 ships
47
+ components: {input: MarkdownInput},
25
48
  })
@@ -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(`![](${url})`)
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
- }
@@ -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
- }