Last active
November 25, 2020 13:12
-
-
Save kmelve/fa487c0af9fafafb8dc67fe49a26d27c to your computer and use it in GitHub Desktop.
Handle pasting of Github flavored markdown for the Portable Text array.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Remember to install dependencies: | |
yarn add unified remark-parse remark-gfm remark-rehype rehype-stringify | |
*/ | |
import unified from 'unified' | |
import parse from 'remark-parse' | |
import gfm from 'remark-gfm' | |
import remark2rehype from 'remark-rehype' | |
import stringify from 'rehype-stringify' | |
import blockTools from '@sanity/block-tools' | |
const uuid = () => | |
Math.random().toString(36).substring(2, 15) + | |
Math.random().toString(36).substring(2, 15) | |
export async function handlePaste (input) { | |
const { event, type, path } = input | |
const text = event.clipboardData.getData('text/plain') | |
// const json = event.clipboardData.getData('application/json') | |
if (text) { | |
const {contents} = await convertMDtoPortableText(text) | |
const patch = await convertHTMLtoPortableText(contents, type) | |
return patch | |
} | |
// return undefined to let the defaults do the work | |
return undefined | |
} | |
async function convertMDtoPortableText (markdownContent) { | |
const PT = await unified() | |
.use(parse) | |
.use(gfm) | |
.use(remark2rehype) | |
.use(stringify) | |
.process(markdownContent) | |
return PT | |
} | |
function convertHTMLtoPortableText (html, type, path) { | |
const hasCodeType = type.of.map(({ name }) => name).includes('code') | |
if (!hasCodeType) { | |
console.log( | |
'Run `sanity install @sanity/code-input, and add `type: "code"` to your schema.' | |
) | |
return | |
} | |
if (html && hasCodeType) { | |
const blocks = blockTools.htmlToBlocks(html, type, { | |
rules: [ | |
{ | |
deserialize(el, next, block) { | |
if (!el) { | |
return undefined | |
} | |
if (el.tagName.toLowerCase() !== 'table') { | |
return undefined | |
} | |
const table = el | |
const rows = table.children[1] | |
return block({ | |
_type: 'table', | |
_key: uuid(), | |
rows: Array.from(rows.children).map((row) => { | |
return { | |
_type: 'row', | |
_key: uuid(), | |
cells: Array.from(row.children).map((cell) => ({ | |
_type: 'cell', | |
_key: uuid(), | |
value: [ | |
{ | |
_type: 'block', | |
_key: uuid(), | |
markDefs: [], | |
children: [ | |
{ | |
_type: 'span', | |
_key: uuid(), | |
text: cell.textContent, | |
}, | |
], | |
}, | |
], | |
})), | |
} | |
}), | |
}) | |
}, | |
}, | |
{ | |
deserialize(el, next, block) { | |
/** | |
* `el` and `next` is DOM Elements | |
* learn all about them: | |
* https://developer.mozilla.org/en-US/docs/Web/API/Element | |
**/ | |
if ( | |
!el || | |
!el.children || | |
(el.tagName && el.tagName.toLowerCase() !== 'pre') | |
) { | |
return undefined | |
} | |
const code = el.children[0] | |
const childNodes = | |
code && code.tagName.toLowerCase() === 'code' | |
? code.childNodes | |
: el.childNodes | |
let text = '' | |
childNodes.forEach((node) => { | |
text += node.textContent | |
}) | |
/** | |
* Return this as an own block (via block helper function), | |
* instead of appending it to a default block's children | |
*/ | |
return block({ | |
_type: 'code', | |
code: text, | |
}) | |
}, | |
}, | |
], | |
}) | |
// return an insert patch | |
return { insert: blocks, path } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, forwardRef, Fragment } from 'react' | |
import { BlockEditor } from 'part:@sanity/form-builder' | |
import Switch from 'part:@sanity/components/toggles/switch' | |
import css from './PTeditor.module.css' | |
import { handlePaste } from './handlePaste' | |
function CustomEditor(props, ref){ | |
const [customPaste, setCustomPaste] = useState(false) | |
function handleCustomPaste () { | |
setCustomPaste({ customPaste: !customPaste }) | |
} | |
const { value = [] } = props | |
const wordsPerMinute = 200 | |
const plainText = blocksToText(value) | |
const wordTokens = plainText.split(/\w+/g).filter(Boolean) | |
const characterCount = plainText.length | |
const wordCount = wordTokens.length | |
const readingTime = Math.ceil(wordCount / wordsPerMinute) | |
return ( | |
<div> | |
<BlockEditor | |
ref={ref} | |
onPaste={customPaste ? handlePaste : undefined} | |
{...props} | |
/> | |
<div className={css.infoBar}> | |
<div> | |
<div className={css.pill}>🔠 {characterCount}</div> | |
<div className={css.pill}>🚾 {wordCount}</div> | |
<div className={css.pill}>⏱ {readingTime} min </div> | |
</div> | |
<div className={css.pill}> | |
<Switch | |
label={`Markdown paste (${customPaste ? 'on' : 'off'})`} | |
onChange={handleCustomPaste} | |
checked={customPaste} | |
/> | |
</div> | |
</div> | |
</div> | |
) | |
} | |
const defaults = { nonTextBehavior: 'remove' } | |
function blocksToText(blocks, opts = {}) { | |
const options = Object.assign({}, defaults, opts) | |
return blocks | |
.map(block => { | |
if (block._type !== 'block' || !block.children) { | |
return options.nonTextBehavior === 'remove' | |
? '' | |
: `[${block._type} block]` | |
} | |
return block.children.map(child => child.text).join('') | |
}) | |
.join('\n\n') | |
} | |
export default forwardRef(CustomEditor) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.infoBar { | |
padding: 0.5em 0; | |
display: flex; | |
@nest &>div+div { | |
padding-left: 0.5rem; | |
} | |
} | |
.pill { | |
display: inline-block; | |
} | |
.pill+.pill { | |
margin-left: 0.5em; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import PTEditor from './PTEditor' | |
export default { | |
title: 'Rich Text', | |
name: 'richText', | |
inputComponent: PTEditor, | |
type: 'array', | |
of: [ | |
{ | |
title: 'Block', | |
type: 'block', | |
}, | |
{ | |
title: 'Code Block', | |
type: 'code' | |
}, | |
{ | |
name: 'table', | |
type: 'object', | |
fields: [ | |
{ | |
name: 'rows', | |
type: 'array', | |
of: [ | |
{ | |
type: 'object', | |
name: 'row', | |
fields: [ | |
{ | |
name: 'cells', | |
type: 'array', | |
of: [ | |
{ | |
name: 'cell', | |
type: 'object', | |
fields: [ | |
{ | |
name: 'value', | |
type: 'blockContent', | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment