diff --git a/docs/src/components/DemoMixin/index.js b/docs/src/components/DemoMixin/index.js index 019a80b1..54bbe6d2 100644 --- a/docs/src/components/DemoMixin/index.js +++ b/docs/src/components/DemoMixin/index.js @@ -86,7 +86,7 @@ export default { .filter(item => { return ['vue', 'ts', 'js', 'jsx', 'scss'].includes(item.extension) }) - .sortBy(item => item.path.split('/').length && !item.path.endsWith('index.vue')) + .sortBy(item => item.path.split('/').length && !item.path.endsWith('index.vue') && !item.path.endsWith('index.jsx')) .toArray() }, } diff --git a/docs/src/demos/Examples/Community/React/MentionList.jsx b/docs/src/demos/Examples/Community/React/MentionList.jsx new file mode 100644 index 00000000..630485ea --- /dev/null +++ b/docs/src/demos/Examples/Community/React/MentionList.jsx @@ -0,0 +1,79 @@ +import React from 'react' +import './MentionList.scss' + +export class MentionList extends React.Component { + constructor(props) { + super(props) + + this.state = { + selectedIndex: 0, + } + } + + componentDidUpdate(oldProps) { + if (this.props.items !== oldProps.items) { + this.setState({ + selectedIndex: 0 + }) + } + } + + onKeyDown({ event }) { + if (event.key === 'ArrowUp') { + this.upHandler() + return true + } + + if (event.key === 'ArrowDown') { + this.downHandler() + return true + } + + if (event.key === 'Enter') { + this.enterHandler() + return true + } + + return false + } + + upHandler() { + this.setState({ + selectedIndex: ((this.state.selectedIndex + this.props.items.length) - 1) % this.props.items.length + }) + } + + downHandler() { + this.setState({ + selectedIndex: (this.state.selectedIndex + 1) % this.props.items.length + }) + } + + enterHandler() { + this.selectItem(this.state.selectedIndex) + } + + selectItem(index) { + const item = this.props.items[index] + + if (item) { + this.props.command({ id: item }) + } + } + + render() { + return ( +
+ {this.props.items.map((item, index) => ( + + ))} +
+ ) + } +} diff --git a/docs/src/demos/Examples/Community/React/MentionList.scss b/docs/src/demos/Examples/Community/React/MentionList.scss new file mode 100644 index 00000000..e909db98 --- /dev/null +++ b/docs/src/demos/Examples/Community/React/MentionList.scss @@ -0,0 +1,27 @@ +.items { + position: relative; + border-radius: 0.25rem; + background: white; + color: rgba(black, 0.8); + overflow: hidden; + font-size: 0.9rem; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.1), + 0px 10px 20px rgba(0, 0, 0, 0.1), + ; +} + +.item { + display: block; + width: 100%; + text-align: left; + background: transparent; + border: none; + padding: 0.2rem 0.5rem; + + &.is-selected, + &:hover { + color: #A975FF; + background: rgba(#A975FF, 0.1); + } +} diff --git a/docs/src/demos/Examples/Community/React/index.jsx b/docs/src/demos/Examples/Community/React/index.jsx new file mode 100644 index 00000000..05851737 --- /dev/null +++ b/docs/src/demos/Examples/Community/React/index.jsx @@ -0,0 +1,126 @@ +import React from 'react' +import tippy from 'tippy.js' +import { useEditor, EditorContent, ReactRenderer } from '@tiptap/react' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' +import CharacterCount from '@tiptap/extension-character-count' +import Mention from '@tiptap/extension-mention' +import { MentionList } from './MentionList' +import './styles.scss' + +export default () => { + const limit = 280 + + const editor = useEditor({ + extensions: [ + Document, + Paragraph, + Text, + CharacterCount.configure({ + limit, + }), + Mention.configure({ + HTMLAttributes: { + class: 'mention', + }, + suggestion: { + items: query => { + return [ + 'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet', + ].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5) + }, + render: () => { + let reactRenderer + let popup + + return { + onStart: props => { + reactRenderer = new ReactRenderer(MentionList, { + props, + editor: props.editor, + }) + + popup = tippy('body', { + getReferenceClientRect: props.clientRect, + appendTo: () => document.body, + content: reactRenderer.element, + showOnCreate: true, + interactive: true, + trigger: 'manual', + placement: 'bottom-start', + }) + }, + onUpdate(props) { + reactRenderer.updateProps(props) + + popup[0].setProps({ + getReferenceClientRect: props.clientRect, + }) + }, + onKeyDown(props) { + return reactRenderer.ref.onKeyDown(props) + }, + onExit() { + popup[0].destroy() + reactRenderer.destroy() + }, + } + } + }, + }) + ], + content: ` +

+ What do you all think about the new movie? +

+ `, + }) + + const percentage = editor + ? Math.round((100 / limit) * editor.getCharacterCount()) + : 0 + + return ( +
+ + {editor && +
+ + + + + + +
+ {editor.getCharacterCount()}/{limit} characters +
+
+ } +
+ ) +} diff --git a/docs/src/demos/Examples/Community/React/styles.scss b/docs/src/demos/Examples/Community/React/styles.scss new file mode 100644 index 00000000..d422be26 --- /dev/null +++ b/docs/src/demos/Examples/Community/React/styles.scss @@ -0,0 +1,41 @@ +/* Basic editor styles */ +.ProseMirror { + > * + * { + margin-top: 0.75em; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.1; + } +} + +.mention { + color: #A975FF; + background-color: rgba(#A975FF, 0.1); + border-radius: 0.3rem; + padding: 0.1rem 0.3rem; +} + +.character-count { + margin-top: 1rem; + display: flex; + align-items: center; + color: #68CEF8; + + &--warning { + color: #FB5151; + } + + &__graph { + margin-right: 0.5rem; + } + + &__text { + color: #868e96; + } +} diff --git a/docs/src/demos/Examples/Community/MentionList.vue b/docs/src/demos/Examples/Community/Vue/MentionList.vue similarity index 100% rename from docs/src/demos/Examples/Community/MentionList.vue rename to docs/src/demos/Examples/Community/Vue/MentionList.vue diff --git a/docs/src/demos/Examples/Community/Vue/index.spec.js b/docs/src/demos/Examples/Community/Vue/index.spec.js new file mode 100644 index 00000000..c65e99f3 --- /dev/null +++ b/docs/src/demos/Examples/Community/Vue/index.spec.js @@ -0,0 +1,7 @@ +context('/demos/Examples/Community/Vue', () => { + before(() => { + cy.visit('/demos/Examples/Community/Vue') + }) + + // TODO: Write tests +}) diff --git a/docs/src/demos/Examples/Community/index.vue b/docs/src/demos/Examples/Community/Vue/index.vue similarity index 99% rename from docs/src/demos/Examples/Community/index.vue rename to docs/src/demos/Examples/Community/Vue/index.vue index 7aec67da..ddbf177f 100644 --- a/docs/src/demos/Examples/Community/index.vue +++ b/docs/src/demos/Examples/Community/Vue/index.vue @@ -79,7 +79,7 @@ export default { items: query => { return [ 'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet', - ].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 10) + ].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5) }, render: () => { let component diff --git a/docs/src/demos/Examples/Community/index.spec.js b/docs/src/demos/Examples/Community/index.spec.js deleted file mode 100644 index f0a0ab4f..00000000 --- a/docs/src/demos/Examples/Community/index.spec.js +++ /dev/null @@ -1,7 +0,0 @@ -context('/demos/Examples/Community', () => { - before(() => { - cy.visit('/demos/Examples/Community') - }) - - // TODO: Write tests -}) diff --git a/docs/src/docPages/examples/community.md b/docs/src/docPages/examples/community.md index 3b0233ae..bcc957d8 100644 --- a/docs/src/docPages/examples/community.md +++ b/docs/src/docPages/examples/community.md @@ -1,3 +1,7 @@ # Suggestions - +## Vue + + +## React + diff --git a/docs/src/templates/DemoPage/style.scss b/docs/src/templates/DemoPage/style.scss index 6ede4df0..4609b200 100644 --- a/docs/src/templates/DemoPage/style.scss +++ b/docs/src/templates/DemoPage/style.scss @@ -1,3 +1,4 @@ .demo-page { padding: 1.25rem; + min-height: 12rem; }