Add Savvy React example

This commit is contained in:
Sven Adlung
2021-11-15 17:19:36 +01:00
parent 40624e7500
commit 0333ca3928
7 changed files with 313 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
import { Extension } from '@tiptap/core'
import { Plugin } from 'prosemirror-state'
import findColors from './findColors'
export const ColorHighlighter = Extension.create({
name: 'colorHighlighter',
addProseMirrorPlugins() {
return [
new Plugin({
state: {
init(_, { doc }) {
return findColors(doc)
},
apply(transaction, oldState) {
return transaction.docChanged ? findColors(transaction.doc) : oldState
},
},
props: {
decorations(state) {
return this.getState(state)
},
},
}),
]
},
})

View File

@@ -0,0 +1,133 @@
import { Extension, textInputRule } from '@tiptap/core'
export const SmilieReplacer = Extension.create({
name: 'smilieReplacer',
addInputRules() {
return [
textInputRule({ find: /-___- $/, replace: '😑 ' }),
textInputRule({ find: /:'-\) $/, replace: '😂 ' }),
textInputRule({ find: /':-\) $/, replace: '😅 ' }),
textInputRule({ find: /':-D $/, replace: '😅 ' }),
textInputRule({ find: />:-\) $/, replace: '😆 ' }),
textInputRule({ find: /-__- $/, replace: '😑 ' }),
textInputRule({ find: /':-\( $/, replace: '😓 ' }),
textInputRule({ find: /:'-\( $/, replace: '😢 ' }),
textInputRule({ find: />:-\( $/, replace: '😠 ' }),
textInputRule({ find: /O:-\) $/, replace: '😇 ' }),
textInputRule({ find: /0:-3 $/, replace: '😇 ' }),
textInputRule({ find: /0:-\) $/, replace: '😇 ' }),
textInputRule({ find: /0;\^\) $/, replace: '😇 ' }),
textInputRule({ find: /O;-\) $/, replace: '😇 ' }),
textInputRule({ find: /0;-\) $/, replace: '😇 ' }),
textInputRule({ find: /O:-3 $/, replace: '😇 ' }),
textInputRule({ find: /:'\) $/, replace: '😂 ' }),
textInputRule({ find: /:-D $/, replace: '😃 ' }),
textInputRule({ find: /':\) $/, replace: '😅 ' }),
textInputRule({ find: /'=\) $/, replace: '😅 ' }),
textInputRule({ find: /':D $/, replace: '😅 ' }),
textInputRule({ find: /'=D $/, replace: '😅 ' }),
textInputRule({ find: />:\) $/, replace: '😆 ' }),
textInputRule({ find: />;\) $/, replace: '😆 ' }),
textInputRule({ find: />=\) $/, replace: '😆 ' }),
textInputRule({ find: /;-\) $/, replace: '😉 ' }),
textInputRule({ find: /\*-\) $/, replace: '😉 ' }),
textInputRule({ find: /;-\] $/, replace: '😉 ' }),
textInputRule({ find: /;\^\) $/, replace: '😉 ' }),
textInputRule({ find: /B-\) $/, replace: '😎 ' }),
textInputRule({ find: /8-\) $/, replace: '😎 ' }),
textInputRule({ find: /B-D $/, replace: '😎 ' }),
textInputRule({ find: /8-D $/, replace: '😎 ' }),
textInputRule({ find: /:-\* $/, replace: '😘 ' }),
textInputRule({ find: /:\^\* $/, replace: '😘 ' }),
textInputRule({ find: /:-\) $/, replace: '🙂 ' }),
textInputRule({ find: /-_- $/, replace: '😑 ' }),
textInputRule({ find: /:-X $/, replace: '😶 ' }),
textInputRule({ find: /:-# $/, replace: '😶 ' }),
textInputRule({ find: /:-x $/, replace: '😶 ' }),
textInputRule({ find: />.< $/, replace: '😣 ' }),
textInputRule({ find: /:-O $/, replace: '😮 ' }),
textInputRule({ find: /:-o $/, replace: '😮 ' }),
textInputRule({ find: /O_O $/, replace: '😮 ' }),
textInputRule({ find: />:O $/, replace: '😮 ' }),
textInputRule({ find: /:-P $/, replace: '😛 ' }),
textInputRule({ find: /:-p $/, replace: '😛 ' }),
textInputRule({ find: /:-Þ $/, replace: '😛 ' }),
textInputRule({ find: /:-þ $/, replace: '😛 ' }),
textInputRule({ find: /:-b $/, replace: '😛 ' }),
textInputRule({ find: />:P $/, replace: '😜 ' }),
textInputRule({ find: /X-P $/, replace: '😜 ' }),
textInputRule({ find: /x-p $/, replace: '😜 ' }),
textInputRule({ find: /':\( $/, replace: '😓 ' }),
textInputRule({ find: /'=\( $/, replace: '😓 ' }),
textInputRule({ find: />:\\ $/, replace: '😕 ' }),
textInputRule({ find: />:\/ $/, replace: '😕 ' }),
textInputRule({ find: /:-\/ $/, replace: '😕 ' }),
textInputRule({ find: /:-. $/, replace: '😕 ' }),
textInputRule({ find: />:\[ $/, replace: '😞 ' }),
textInputRule({ find: /:-\( $/, replace: '😞 ' }),
textInputRule({ find: /:-\[ $/, replace: '😞 ' }),
textInputRule({ find: /:'\( $/, replace: '😢 ' }),
textInputRule({ find: /;-\( $/, replace: '😢 ' }),
textInputRule({ find: /#-\) $/, replace: '😵 ' }),
textInputRule({ find: /%-\) $/, replace: '😵 ' }),
textInputRule({ find: /X-\) $/, replace: '😵 ' }),
textInputRule({ find: />:\( $/, replace: '😠 ' }),
textInputRule({ find: /0:3 $/, replace: '😇 ' }),
textInputRule({ find: /0:\) $/, replace: '😇 ' }),
textInputRule({ find: /O:\) $/, replace: '😇 ' }),
textInputRule({ find: /O=\) $/, replace: '😇 ' }),
textInputRule({ find: /O:3 $/, replace: '😇 ' }),
textInputRule({ find: /<\/3 $/, replace: '💔 ' }),
textInputRule({ find: /:D $/, replace: '😃 ' }),
textInputRule({ find: /=D $/, replace: '😃 ' }),
textInputRule({ find: /;\) $/, replace: '😉 ' }),
textInputRule({ find: /\*\) $/, replace: '😉 ' }),
textInputRule({ find: /;\] $/, replace: '😉 ' }),
textInputRule({ find: /;D $/, replace: '😉 ' }),
textInputRule({ find: /B\) $/, replace: '😎 ' }),
textInputRule({ find: /8\) $/, replace: '😎 ' }),
textInputRule({ find: /:\* $/, replace: '😘 ' }),
textInputRule({ find: /=\* $/, replace: '😘 ' }),
textInputRule({ find: /:\) $/, replace: '🙂 ' }),
textInputRule({ find: /=\] $/, replace: '🙂 ' }),
textInputRule({ find: /=\) $/, replace: '🙂 ' }),
textInputRule({ find: /:\] $/, replace: '🙂 ' }),
textInputRule({ find: /:X $/, replace: '😶 ' }),
textInputRule({ find: /:# $/, replace: '😶 ' }),
textInputRule({ find: /=X $/, replace: '😶 ' }),
textInputRule({ find: /=x $/, replace: '😶 ' }),
textInputRule({ find: /:x $/, replace: '😶 ' }),
textInputRule({ find: /=# $/, replace: '😶 ' }),
textInputRule({ find: /:O $/, replace: '😮 ' }),
textInputRule({ find: /:o $/, replace: '😮 ' }),
textInputRule({ find: /:P $/, replace: '😛 ' }),
textInputRule({ find: /=P $/, replace: '😛 ' }),
textInputRule({ find: /:p $/, replace: '😛 ' }),
textInputRule({ find: /=p $/, replace: '😛 ' }),
textInputRule({ find: /:Þ $/, replace: '😛 ' }),
textInputRule({ find: /:þ $/, replace: '😛 ' }),
textInputRule({ find: /:b $/, replace: '😛 ' }),
textInputRule({ find: /d: $/, replace: '😛 ' }),
textInputRule({ find: /:\/ $/, replace: '😕 ' }),
textInputRule({ find: /:\\ $/, replace: '😕 ' }),
textInputRule({ find: /=\/ $/, replace: '😕 ' }),
textInputRule({ find: /=\\ $/, replace: '😕 ' }),
textInputRule({ find: /:L $/, replace: '😕 ' }),
textInputRule({ find: /=L $/, replace: '😕 ' }),
textInputRule({ find: /:\( $/, replace: '😞 ' }),
textInputRule({ find: /:\[ $/, replace: '😞 ' }),
textInputRule({ find: /=\( $/, replace: '😞 ' }),
textInputRule({ find: /;\( $/, replace: '😢 ' }),
textInputRule({ find: /D: $/, replace: '😨 ' }),
textInputRule({ find: /:\$ $/, replace: '😳 ' }),
textInputRule({ find: /=\$ $/, replace: '😳 ' }),
textInputRule({ find: /#\) $/, replace: '😵 ' }),
textInputRule({ find: /%\) $/, replace: '😵 ' }),
textInputRule({ find: /X\) $/, replace: '😵 ' }),
textInputRule({ find: /:@ $/, replace: '😠 ' }),
textInputRule({ find: /<3 $/, replace: '❤️ ' }),
textInputRule({ find: /\/shrug $/, replace: '¯\\_(ツ)_/¯' }),
]
},
})

View File

@@ -0,0 +1,28 @@
import { Decoration, DecorationSet } from 'prosemirror-view'
import { Node } from 'prosemirror-model'
export default function (doc: Node): DecorationSet {
const hexColor = /(#[0-9a-f]{3,6})\b/gi
const decorations: Decoration[] = []
doc.descendants((node, position) => {
if (!node.text) {
return
}
Array.from(node.text.matchAll(hexColor)).forEach(match => {
const color = match[0]
const index = match.index || 0
const from = position + index
const to = from + color.length
const decoration = Decoration.inline(from, to, {
class: 'color',
style: `--color: ${color}`,
})
decorations.push(decoration)
})
})
return DecorationSet.create(doc, decorations)
}

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="app"></div>
<script type="module">
import setup from "../../../../setup/react.ts";
import source from "@source";
setup("Examples/Savvy", source);
</script>
</body>
</html>

View File

@@ -0,0 +1,38 @@
import React from 'react'
import { useEditor, EditorContent } from '@tiptap/react'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Code from '@tiptap/extension-code'
import Typography from '@tiptap/extension-typography'
import { ColorHighlighter } from './ColorHighlighter'
import { SmilieReplacer } from './SmilieReplacer'
import './styles.scss'
export default () => {
const editor = useEditor({
extensions: [Document, Paragraph, Text, Code, Typography, ColorHighlighter, SmilieReplacer],
content: `
<p>
→ With the Typography extension, tiptap understands »what you mean« and adds correct characters to your text — its like a “typography nerd” on your side.
</p>
<p>
Try it out and type <code>(c)</code>, <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, <code>--</code> or <code>1x1</code> here:
</p>
<p></p>
<p>
Or add completely custom input rules. We added a custom extension here that replaces smilies like <code>:-)</code>, <code><3</code> or <code>>:P</code> with emojis. Try it out:
</p>
<p></p>
<p>
You can also teach the editor new things. For example to recognize hex colors and add a color swatch on the fly: #FFF, #0D0D0D, #616161, #A975FF, #FB5151, #FD9170, #FFCB6B, #68CEF8, #80cbc4, #9DEF8F
</p>
`,
})
if (!editor) {
return null
}
return <EditorContent editor={editor} />
}

View File

@@ -0,0 +1,34 @@
context('/src/Examples/Savvy/React/', () => {
before(() => {
cy.visit('/src/Examples/Savvy/React/')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()
})
})
const tests = [
['(c)', '©'],
['->', '→'],
['>>', '»'],
['1/2', '½'],
['!=', '≠'],
['--', '—'],
['1x1', '1×1'],
[':-) ', '🙂'],
['<3 ', '❤️'],
['>:P ', '😜'],
]
tests.forEach(test => {
it(`should parse ${test[0]} correctly`, () => {
cy.get('.ProseMirror').type(test[0]).should('contain', test[1])
})
})
it('should parse hex colors correctly', () => {
cy.get('.ProseMirror').type('#FD9170').find('.color')
})
})

View File

@@ -0,0 +1,38 @@
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
}
/* Color swatches */
.color {
white-space: nowrap;
&::before {
background-color: var(--color);
border: 1px solid rgba(128, 128, 128, 0.3);
border-radius: 2px;
content: " ";
display: inline-block;
height: 1em;
margin-bottom: 0.15em;
margin-right: 0.1em;
vertical-align: middle;
width: 1em;
}
}