Add Savvy React example
This commit is contained in:
27
demos/src/Examples/Savvy/React/ColorHighlighter.ts
Normal file
27
demos/src/Examples/Savvy/React/ColorHighlighter.ts
Normal 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)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
133
demos/src/Examples/Savvy/React/SmilieReplacer.ts
Normal file
133
demos/src/Examples/Savvy/React/SmilieReplacer.ts
Normal 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: '¯\\_(ツ)_/¯' }),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
28
demos/src/Examples/Savvy/React/findColors.ts
Normal file
28
demos/src/Examples/Savvy/React/findColors.ts
Normal 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)
|
||||||
|
}
|
||||||
15
demos/src/Examples/Savvy/React/index.html
Normal file
15
demos/src/Examples/Savvy/React/index.html
Normal 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>
|
||||||
38
demos/src/Examples/Savvy/React/index.jsx
Normal file
38
demos/src/Examples/Savvy/React/index.jsx
Normal 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 — it’s 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} />
|
||||||
|
}
|
||||||
34
demos/src/Examples/Savvy/React/index.spec.js
Normal file
34
demos/src/Examples/Savvy/React/index.spec.js
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
||||||
38
demos/src/Examples/Savvy/React/styles.scss
Normal file
38
demos/src/Examples/Savvy/React/styles.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user