mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-03-03 04:31:13 +01:00
feat: add a basic mention implementation example (#2233)
* feat: add a basic mention implementation example Just a simple example to get people started when trying to implement their own mentions implementation. * feat(MentionsExample): use a floating suggestions menu. * fix(MentionsExample): update to slate 0.42
This commit is contained in:
parent
861f30a3f9
commit
63ad062083
examples
@ -31,6 +31,7 @@ import SearchHighlighting from './search-highlighting'
|
||||
import InputTester from './input-tester'
|
||||
import SyncingOperations from './syncing-operations'
|
||||
import Tables from './tables'
|
||||
import Mentions from './mentions'
|
||||
|
||||
/**
|
||||
* Examples.
|
||||
@ -62,6 +63,7 @@ const EXAMPLES = [
|
||||
['History', History, '/history'],
|
||||
['Versions', Versions, '/versions'],
|
||||
['Input Tester', InputTester, '/input-tester'],
|
||||
['Mentions', Mentions, '/mentions'],
|
||||
]
|
||||
|
||||
/**
|
||||
|
99
examples/mentions/Suggestions.js
Normal file
99
examples/mentions/Suggestions.js
Normal file
@ -0,0 +1,99 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
import styled from 'react-emotion'
|
||||
|
||||
const SuggestionList = styled('ul')`
|
||||
background: #fff;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
`
|
||||
|
||||
const Suggestion = styled('li')`
|
||||
align-items: center;
|
||||
border-left: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;
|
||||
border-top: 1px solid #ddd;
|
||||
|
||||
display: flex;
|
||||
height: 32px;
|
||||
padding: 4px 8px;
|
||||
|
||||
&:hover {
|
||||
background: #87cefa;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
`
|
||||
|
||||
const DEFAULT_POSITION = {
|
||||
top: -10000,
|
||||
left: -10000,
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggestions is a PureComponent because we need to prevent updates when x/ y
|
||||
* Are just going to be the same value. Otherwise we will update forever.
|
||||
*/
|
||||
|
||||
class Suggestions extends React.PureComponent {
|
||||
menuRef = React.createRef()
|
||||
|
||||
state = DEFAULT_POSITION
|
||||
|
||||
/**
|
||||
* On update, update the menu.
|
||||
*/
|
||||
|
||||
componentDidMount = () => {
|
||||
this.updateMenu()
|
||||
}
|
||||
|
||||
componentDidUpdate = () => {
|
||||
this.updateMenu()
|
||||
}
|
||||
|
||||
render() {
|
||||
const root = window.document.getElementById('root')
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<SuggestionList
|
||||
ref={this.menuRef}
|
||||
style={{
|
||||
top: this.state.top,
|
||||
left: this.state.left,
|
||||
}}
|
||||
>
|
||||
{this.props.users.map(user => {
|
||||
return (
|
||||
<Suggestion key={user.id} onClick={() => this.props.onSelect(user)}>
|
||||
{user.username}
|
||||
</Suggestion>
|
||||
)
|
||||
})}
|
||||
</SuggestionList>,
|
||||
root
|
||||
)
|
||||
}
|
||||
|
||||
updateMenu() {
|
||||
const anchor = window.document.querySelector(this.props.anchor)
|
||||
|
||||
if (!anchor) {
|
||||
return this.setState(DEFAULT_POSITION)
|
||||
}
|
||||
|
||||
const anchorRect = anchor.getBoundingClientRect()
|
||||
|
||||
this.setState({
|
||||
top: anchorRect.bottom,
|
||||
left: anchorRect.left,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Suggestions
|
306
examples/mentions/index.js
Normal file
306
examples/mentions/index.js
Normal file
@ -0,0 +1,306 @@
|
||||
/*
|
||||
This example is intended to be a super basic mentions implementation that
|
||||
people can work off of. What is show here is how to detect when a user starts
|
||||
typing a mention, making a search query, and then inserting a mention when
|
||||
the user selects an item. There are a few improvements that can be made in a
|
||||
production implementation:
|
||||
|
||||
1. Serialization - in an actual implementation, you will probably want to
|
||||
serialize the mentions out in a manner that your DB can parse, in order
|
||||
to send notifications on the back end.
|
||||
2. Linkifying the mentions - There isn't really a good place to link to for
|
||||
this example. But in most cases you would probably want to link to the
|
||||
user's profile on click.
|
||||
3. Keyboard accessibility - it adds quite a bit of complexity to the
|
||||
implementation to add this, as it involves capturing keyboard events like up
|
||||
/ down / enter and proxying them into the `Suggestions` component using a
|
||||
`ref`. I've left this out because this is already a pretty confusing use
|
||||
case.
|
||||
4. Plugin Mentions - in reality, you will probably want to put mentions into a
|
||||
plugin, and make them configurable to support more than one kind of mention,
|
||||
like users and hashtags. As you can see below it is a bit unweildy to bolt
|
||||
all this directly to the editor.
|
||||
|
||||
The list of characters was extracted from Wikipedia:
|
||||
https://en.wikipedia.org/wiki/List_of_Star_Wars_characters
|
||||
*/
|
||||
|
||||
import { Editor } from 'slate-react'
|
||||
import { Value } from 'slate'
|
||||
import _ from 'lodash'
|
||||
import React from 'react'
|
||||
|
||||
import initialValue from './value.json'
|
||||
import users from './users.json'
|
||||
import Suggestions from './Suggestions'
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const USER_MENTION_NODE_TYPE = 'userMention'
|
||||
|
||||
/**
|
||||
* The decoration mark type that the menu will position itself against. The
|
||||
* "context" is just the current text after the @ symbol.
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const CONTEXT_MARK_TYPE = 'mentionContext'
|
||||
|
||||
const schema = {
|
||||
inlines: {
|
||||
[USER_MENTION_NODE_TYPE]: {
|
||||
// It's important that we mark the mentions as void nodes so that users
|
||||
// can't edit the text of the mention.
|
||||
isVoid: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The regex to use to find the searchQuery.
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
|
||||
const CAPTURE_REGEX = /@(\S*)$/
|
||||
|
||||
/**
|
||||
* Get get the potential mention input.
|
||||
*
|
||||
* @type {Value}
|
||||
*/
|
||||
|
||||
function getInput(value) {
|
||||
// In some cases, like if the node that was selected gets deleted,
|
||||
// `startText` can be null.
|
||||
if (!value.startText) {
|
||||
return null
|
||||
}
|
||||
|
||||
const startOffset = value.selection.start.offset
|
||||
const textBefore = value.startText.text.slice(0, startOffset)
|
||||
const result = CAPTURE_REGEX.exec(textBefore)
|
||||
|
||||
return result === null ? null : result[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends React.Component
|
||||
*/
|
||||
|
||||
class MentionsExample extends React.Component {
|
||||
/**
|
||||
* Deserialize the initial editor value.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
state = {
|
||||
users: [],
|
||||
value: Value.fromJSON(initialValue),
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {React.RefObject}
|
||||
*/
|
||||
|
||||
editorRef = React.createRef()
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Editor
|
||||
spellCheck
|
||||
autoFocus
|
||||
placeholder="Try mentioning some people..."
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
ref={this.editorRef}
|
||||
renderNode={this.renderNode}
|
||||
renderMark={this.renderMark}
|
||||
schema={schema}
|
||||
/>
|
||||
<Suggestions
|
||||
anchor=".mention-context"
|
||||
users={this.state.users}
|
||||
onSelect={this.insertMention}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderMark(props, next) {
|
||||
if (props.mark.type === CONTEXT_MARK_TYPE) {
|
||||
return (
|
||||
// Adding the className here is important so taht the `Suggestions`
|
||||
// component can find an anchor.
|
||||
<span {...props.attributes} className="mention-context">
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
renderNode(props, next) {
|
||||
const { attributes, node } = props
|
||||
|
||||
if (node.type === USER_MENTION_NODE_TYPE) {
|
||||
// This is where you could turn the mention into a link to the user's
|
||||
// profile or something.
|
||||
return <b {...attributes}>{props.node.text}</b>
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current "context" with a user mention node corresponding to
|
||||
* the given user.
|
||||
* @param {Object} user
|
||||
* @param {string} user.id
|
||||
* @param {string} user.username
|
||||
*/
|
||||
|
||||
insertMention = user => {
|
||||
const value = this.state.value
|
||||
const inputValue = getInput(value)
|
||||
|
||||
// Delete the captured value, including the `@` symbol
|
||||
this.editorRef.current.change(change => {
|
||||
change = change.deleteBackward(inputValue.length + 1)
|
||||
|
||||
const selectedRange = change.value.selection
|
||||
|
||||
change
|
||||
.insertText(' ')
|
||||
.insertInlineAtRange(selectedRange, {
|
||||
data: {
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{
|
||||
text: `@${user.username}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
type: USER_MENTION_NODE_TYPE,
|
||||
})
|
||||
.focus()
|
||||
|
||||
this.setState({
|
||||
value: change.value,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On change, save the new `value`.
|
||||
*
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onChange = change => {
|
||||
const inputValue = getInput(change.value)
|
||||
|
||||
if (inputValue !== this.lastInputValue) {
|
||||
this.lastInputValue = inputValue
|
||||
|
||||
if (hasValidAncestors(change.value)) {
|
||||
this.search(inputValue)
|
||||
}
|
||||
|
||||
const { selection } = change.value
|
||||
|
||||
let decorations = change.value.decorations.filter(
|
||||
value => value.mark.type !== CONTEXT_MARK_TYPE
|
||||
)
|
||||
|
||||
if (inputValue && hasValidAncestors(change.value)) {
|
||||
decorations = decorations.push({
|
||||
anchor: {
|
||||
key: selection.start.key,
|
||||
offset: selection.start.offset - inputValue.length,
|
||||
},
|
||||
focus: {
|
||||
key: selection.start.key,
|
||||
offset: selection.start.offset,
|
||||
},
|
||||
mark: {
|
||||
type: CONTEXT_MARK_TYPE,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return change.withoutSaving(() => change.setValue({ decorations }))
|
||||
}
|
||||
|
||||
this.setState({ value: change.value })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of users that match the given search query
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
search(searchQuery) {
|
||||
// We don't want to show the wrong users for the current search query, so
|
||||
// wipe them out.
|
||||
this.setState({
|
||||
users: [],
|
||||
})
|
||||
|
||||
if (!searchQuery) return
|
||||
|
||||
// In order to make this seem like an API call, add a set timeout for some
|
||||
// async.
|
||||
setTimeout(() => {
|
||||
// WARNING: In a production environment you should escape the search query.
|
||||
const regex = RegExp(`^${searchQuery}`, 'gi')
|
||||
|
||||
// If you want to get fancy here, you can add some emphasis to the part
|
||||
// of the string that matches.
|
||||
const result = _.filter(users, user => {
|
||||
return user.username.match(regex)
|
||||
})
|
||||
|
||||
this.setState({
|
||||
// Only return the first 5 results
|
||||
users: result.slice(0, 5),
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current selection has valid ancestors for a context. In our
|
||||
* case, we want to make sure that the mention is only a direct child of a
|
||||
* paragraph. In this simple example it isn't that important, but in a complex
|
||||
* editor you wouldn't want it to be a child of another inline like a link.
|
||||
*
|
||||
* @param {Value} value
|
||||
*/
|
||||
|
||||
function hasValidAncestors(value) {
|
||||
const { document, selection } = value
|
||||
|
||||
const invalidParent = document.getClosest(
|
||||
selection.start.key,
|
||||
// In this simple case, we only want mentions to live inside a paragraph.
|
||||
// This check can be adjusted for more complex rich text implementations.
|
||||
node => node.type !== 'paragraph'
|
||||
)
|
||||
|
||||
return !invalidParent
|
||||
}
|
||||
|
||||
export default MentionsExample
|
417
examples/mentions/users.json
Normal file
417
examples/mentions/users.json
Normal file
@ -0,0 +1,417 @@
|
||||
[
|
||||
{ "username": "2-1B", "id": "1" },
|
||||
{ "username": "4-LOM", "id": "2" },
|
||||
{ "username": "8D8", "id": "3" },
|
||||
{ "username": "99", "id": "4" },
|
||||
{ "username": "0-0-0", "id": "5" },
|
||||
{ "username": "A'Koba", "id": "6" },
|
||||
{ "username": "Admiral Gial Ackbar", "id": "7" },
|
||||
{ "username": "Sim Aloo", "id": "8" },
|
||||
{ "username": "Almec", "id": "9" },
|
||||
{ "username": "Mas Amedda", "id": "10" },
|
||||
{ "username": "Amee", "id": "11" },
|
||||
{ "username": "Padmé Amidala", "id": "12" },
|
||||
{ "username": "Cassian Andor", "id": "13" },
|
||||
{ "username": "Fodesinbeed Annodue", "id": "14" },
|
||||
{ "username": "Raymus Antilles", "id": "15" },
|
||||
{ "username": "Wedge Antilles", "id": "16" },
|
||||
{ "username": "AP-5", "id": "17" },
|
||||
{ "username": "Queen Apailana", "id": "18" },
|
||||
{ "username": "Doctor Aphra", "id": "19" },
|
||||
{ "username": "Faro Argyus", "id": "20" },
|
||||
{ "username": "Aiolin and Morit Astarte", "id": "21" },
|
||||
{ "username": "Ello Asty", "id": "22" },
|
||||
{ "username": "AZI-3", "id": "23" },
|
||||
{ "username": "Walrus Man", "id": "24" },
|
||||
{ "username": "Kitster Banai", "id": "25" },
|
||||
{ "username": "Cad Bane", "id": "26" },
|
||||
{ "username": "Darth Bane", "id": "27" },
|
||||
{ "username": "Barada", "id": "28" },
|
||||
{ "username": "Jom Barell", "id": "29" },
|
||||
{ "username": "Moradmin Bast", "id": "30" },
|
||||
{ "username": "BB-8", "id": "31" },
|
||||
{ "username": "BB-9E", "id": "32" },
|
||||
{ "username": "Tobias Beckett", "id": "33" },
|
||||
{ "username": "Val Beckett", "id": "34" },
|
||||
{ "username": "The Bendu", "id": "35" },
|
||||
{ "username": "Shara Bey", "id": "36" },
|
||||
{ "username": "Sio Bibble", "id": "37" },
|
||||
{ "username": "Depa Billaba", "id": "38" },
|
||||
{ "username": "Jar Jar Binks", "id": "39" },
|
||||
{ "username": "Temiri Blagg", "id": "40" },
|
||||
{ "username": "Commander Bly", "id": "41" },
|
||||
{ "username": "Bobbajo", "id": "42" },
|
||||
{ "username": "Dud Bolt", "id": "43" },
|
||||
{ "username": "Mister Bones", "id": "44" },
|
||||
{ "username": "Lux Bonteri", "id": "45" },
|
||||
{ "username": "Mina Bonteri", "id": "46" },
|
||||
{ "username": "Borvo the Hutt", "id": "47" },
|
||||
{ "username": "Bossk", "id": "48" },
|
||||
{ "username": "Ezra Bridger", "id": "49" },
|
||||
{ "username": "BT-1", "id": "50" },
|
||||
{ "username": "Sora Bulq", "id": "51" },
|
||||
{ "username": "C1-10P", "id": "52" },
|
||||
{ "username": "C-3PO", "id": "53" },
|
||||
{ "username": "Lando Calrissian", "id": "54" },
|
||||
{ "username": "Moden Canady", "id": "55" },
|
||||
{ "username": "Ransolm Casterfo", "id": "56" },
|
||||
{ "username": "Chewbacca", "id": "57" },
|
||||
{ "username": "Chief Chirpa", "id": "58" },
|
||||
{ "username": "Rush Clovis", "id": "59" },
|
||||
{ "username": "Commander Cody (CC-2224)", "id": "60" },
|
||||
{ "username": "Lieutenant Kaydel Ko Connix", "id": "61" },
|
||||
{ "username": "Jeremoch Colton", "id": "62" },
|
||||
{ "username": "Cordé", "id": "63" },
|
||||
{ "username": "Salacious B. Crumb", "id": "64" },
|
||||
{ "username": "Arvel Crynyd", "id": "65" },
|
||||
{ "username": "Dr. Cylo", "id": "66" },
|
||||
{ "username": "Larma D'Acy", "id": "67" },
|
||||
{ "username": "Figrin D'an", "id": "68" },
|
||||
{ "username": "Kes Dameron", "id": "69" },
|
||||
{ "username": "Poe Dameron", "id": "70" },
|
||||
{ "username": "Vober Dand", "id": "71" },
|
||||
{ "username": "Joclad Danva", "id": "72" },
|
||||
{ "username": "Dapp", "id": "73" },
|
||||
{ "username": "Biggs Darklighter", "id": "74" },
|
||||
{ "username": "Oro Dassyne", "id": "75" },
|
||||
{ "username": "Gizor Dellso", "id": "76" },
|
||||
{ "username": "Dengar", "id": "77" },
|
||||
{ "username": "Bren Derlin", "id": "78" },
|
||||
{ "username": "Ima-Gun Di", "id": "79" },
|
||||
{ "username": "Rinnriyin Di", "id": "80" },
|
||||
{ "username": "DJ", "id": "81" },
|
||||
{ "username": "Lott Dod", "id": "82" },
|
||||
{ "username": "Jan Dodonna", "id": "83" },
|
||||
{ "username": "Daultay Dofine", "id": "84" },
|
||||
{ "username": "Dogma", "id": "85" },
|
||||
{ "username": "Darth Tyranus", "id": "86" },
|
||||
{ "username": "Dormé", "id": "87" },
|
||||
{ "username": "Cin Drallig", "id": "88" },
|
||||
{ "username": "Garven Dreis", "id": "89" },
|
||||
{ "username": "Droidbait", "id": "90" },
|
||||
{ "username": "Rio Durant", "id": "91" },
|
||||
{ "username": "Lok Durd", "id": "92" },
|
||||
{ "username": "Eirtaé", "id": "93" },
|
||||
{ "username": "Dineé Ellberger", "id": "94" },
|
||||
{ "username": "Ellé", "id": "95" },
|
||||
{ "username": "Caluan Ematt", "id": "96" },
|
||||
{ "username": "Embo", "id": "97" },
|
||||
{ "username": "Emperor's Royal Guard", "id": "98" },
|
||||
{ "username": "Jas Emari", "id": "99" },
|
||||
{ "username": "Ebe E. Endocott", "id": "100" },
|
||||
{ "username": "Galen Erso", "id": "101" },
|
||||
{ "username": "Jyn Erso", "id": "102" },
|
||||
{ "username": "Lyra Erso", "id": "103" },
|
||||
{ "username": "EV-9D9", "id": "104" },
|
||||
{ "username": "Moralo Eval", "id": "105" },
|
||||
{ "username": "Doctor Evazan", "id": "106" },
|
||||
{ "username": "Onaconda Farr", "id": "107" },
|
||||
{ "username": "Boba Fett", "id": "108" },
|
||||
{ "username": "Jango Fett", "id": "109" },
|
||||
{ "username": "Feral", "id": "110" },
|
||||
{ "username": "Commander Fil (CC-3714)", "id": "111" },
|
||||
{ "username": "Finn", "id": "112" },
|
||||
{ "username": "Kit Fisto", "id": "113" },
|
||||
{ "username": "Fives", "id": "114" },
|
||||
{ "username": "FN-1824", "id": "115" },
|
||||
{ "username": "FN-2003", "id": "116" },
|
||||
{ "username": "Nines", "id": "117" },
|
||||
{ "username": "Bib Fortuna", "id": "118" },
|
||||
{ "username": "Commander Fox", "id": "119" },
|
||||
{ "username": "FX-7", "id": "120" },
|
||||
{ "username": "GA-97", "id": "121" },
|
||||
{ "username": "Adi Gallia", "id": "122" },
|
||||
{ "username": "Gardulla the Hutt", "id": "123" },
|
||||
{ "username": "Yarna d'al' Gargan", "id": "124" },
|
||||
{ "username": "Gonk droid", "id": "125" },
|
||||
{ "username": "Commander Gree", "id": "126" },
|
||||
{ "username": "Greedo", "id": "127" },
|
||||
{ "username": "Janus Greejatus", "id": "128" },
|
||||
{ "username": "Captain Gregor", "id": "129" },
|
||||
{ "username": "Grievous", "id": "130" },
|
||||
{ "username": "Grummgar", "id": "131" },
|
||||
{ "username": "Gungi", "id": "132" },
|
||||
{ "username": "Nute Gunray", "id": "133" },
|
||||
{ "username": "Mars Guo", "id": "134" },
|
||||
{ "username": "Rune Haako", "id": "135" },
|
||||
{ "username": "Rako Hardeen", "id": "136" },
|
||||
{ "username": "Gideon Hask", "id": "137" },
|
||||
{ "username": "Hevy", "id": "138" },
|
||||
{ "username": "San Hill", "id": "139" },
|
||||
{ "username": "Clegg Holdfast", "id": "140" },
|
||||
{ "username": "Vice Admiral Amilyn Holdo", "id": "141" },
|
||||
{ "username": "Tey How", "id": "142" },
|
||||
{ "username": "Huyang", "id": "143" },
|
||||
{ "username": "Armitage Hux", "id": "144" },
|
||||
{ "username": "Brendol Hux", "id": "145" },
|
||||
{ "username": "IG-88", "id": "146" },
|
||||
{ "username": "Chirrut Îmwe", "id": "147" },
|
||||
{ "username": "Inquisitors", "id": "148" },
|
||||
{ "username": "Grand Inquisitor", "id": "149" },
|
||||
{ "username": "Fifth Brother", "id": "150" },
|
||||
{ "username": "Sixth Brother", "id": "151" },
|
||||
{ "username": "Seventh Sister", "id": "152" },
|
||||
{ "username": "Eighth Brother", "id": "153" },
|
||||
{ "username": "Sidon Ithano", "id": "154" },
|
||||
{ "username": "Jabba", "id": "155" },
|
||||
{ "username": "Queen Jamillia", "id": "156" },
|
||||
{ "username": "Wes Janson", "id": "157" },
|
||||
{ "username": "Kanan Jarrus", "id": "158" },
|
||||
{ "username": "Jaxxon", "id": "159" },
|
||||
{ "username": "Greeata Jendowanian", "id": "160" },
|
||||
{ "username": "Tiaan Jerjerrod", "id": "161" },
|
||||
{ "username": "Commander Jet", "id": "162" },
|
||||
{ "username": "Dexter Jettster", "id": "163" },
|
||||
{ "username": "Qui-Gon Jinn", "id": "164" },
|
||||
{ "username": "Jira", "id": "165" },
|
||||
{ "username": "Jubnuk", "id": "166" },
|
||||
{ "username": "K-2SO", "id": "167" },
|
||||
{ "username": "Tee Watt Kaa", "id": "168" },
|
||||
{ "username": "Agent Kallus", "id": "169" },
|
||||
{ "username": "Harter Kalonia", "id": "170" },
|
||||
{ "username": "Maz Kanata", "id": "171" },
|
||||
{ "username": "Colonel Kaplan", "id": "172" },
|
||||
{ "username": "Karbin", "id": "173" },
|
||||
{ "username": "Karina the Great", "id": "174" },
|
||||
{ "username": "Alton Kastle", "id": "175" },
|
||||
{ "username": "King Katuunko", "id": "176" },
|
||||
{ "username": "Coleman Kcaj", "id": "177" },
|
||||
{ "username": "Obi-Wan Kenobi", "id": "178" },
|
||||
{ "username": "Ki-Adi-Mundi", "id": "179" },
|
||||
{ "username": "Klaatu", "id": "180" },
|
||||
{ "username": "Klik-Klak", "id": "181" },
|
||||
{ "username": "Derek Klivian", "id": "182" },
|
||||
{ "username": "Agen Kolar", "id": "183" },
|
||||
{ "username": "Plo Koon", "id": "184" },
|
||||
{ "username": "Eeth Koth", "id": "185" },
|
||||
{ "username": "Sergeant Kreel", "id": "186" },
|
||||
{ "username": "Pong Krell", "id": "187" },
|
||||
{ "username": "Black Krrsantan", "id": "188" },
|
||||
{ "username": "Bo-Katan Kryze", "id": "189" },
|
||||
{ "username": "Satine Kryze", "id": "190" },
|
||||
{ "username": "Conder Kyl", "id": "191" },
|
||||
{ "username": "Thane Kyrell", "id": "192" },
|
||||
{ "username": "L3-37", "id": "193" },
|
||||
{ "username": "L'ulo", "id": "194" },
|
||||
{ "username": "Beru Lars", "id": "195" },
|
||||
{ "username": "Cliegg Lars", "id": "196" },
|
||||
{ "username": "Owen Lars", "id": "197" },
|
||||
{ "username": "Cut Lawquane", "id": "198" },
|
||||
{ "username": "Tasu Leech", "id": "199" },
|
||||
{ "username": "Xamuel Lennox", "id": "200" },
|
||||
{ "username": "Tallissan Lintra", "id": "201" },
|
||||
{ "username": "Slowen Lo", "id": "202" },
|
||||
{ "username": "Lobot", "id": "203" },
|
||||
{ "username": "Logray", "id": "204" },
|
||||
{ "username": "Lumat", "id": "205" },
|
||||
{ "username": "Crix Madine", "id": "206" },
|
||||
{ "username": "Shu Mai", "id": "207" },
|
||||
{ "username": "Malakili", "id": "208" },
|
||||
{ "username": "Baze Malbus", "id": "209" },
|
||||
{ "username": "Mama the Hutt", "id": "210" },
|
||||
{ "username": "Ody Mandrell", "id": "211" },
|
||||
{ "username": "Darth Maul", "id": "212" },
|
||||
{ "username": "Saelt-Marae", "id": "213" },
|
||||
{ "username": "Mawhonic", "id": "214" },
|
||||
{ "username": "Droopy McCool", "id": "215" },
|
||||
{ "username": "Pharl McQuarrie", "id": "216" },
|
||||
{ "username": "ME-8D9", "id": "217" },
|
||||
{ "username": "Lyn Me", "id": "218" },
|
||||
{ "username": "Tion Medon", "id": "219" },
|
||||
{ "username": "Del Meeko", "id": "220" },
|
||||
{ "username": "Aks Moe", "id": "221" },
|
||||
{ "username": "Sly Moore", "id": "222" },
|
||||
{ "username": "Morley", "id": "223" },
|
||||
{ "username": "Delian Mors", "id": "224" },
|
||||
{ "username": "Mon Mothma", "id": "225" },
|
||||
{ "username": "Conan Antonio Motti", "id": "226" },
|
||||
{ "username": "Jobal Naberrie", "id": "227" },
|
||||
{ "username": "Pooja Naberrie", "id": "228" },
|
||||
{ "username": "Ruwee Naberrie", "id": "229" },
|
||||
{ "username": "Ryoo Naberrie", "id": "230" },
|
||||
{ "username": "Sola Naberrie", "id": "231" },
|
||||
{ "username": "Hammerhead", "id": "232" },
|
||||
{ "username": "Boss Nass", "id": "233" },
|
||||
{ "username": "Lorth Needa", "id": "234" },
|
||||
{ "username": "Queen Neeyutnee", "id": "235" },
|
||||
{ "username": "Enfys Nest", "id": "236" },
|
||||
{ "username": "Bazine Netal", "id": "237" },
|
||||
{ "username": "Niima the Hutt", "id": "238" },
|
||||
{ "username": "Jocasta Nu", "id": "239" },
|
||||
{ "username": "Po Nudo", "id": "240" },
|
||||
{ "username": "Nien Nunb", "id": "241" },
|
||||
{ "username": "Has Obbit", "id": "242" },
|
||||
{ "username": "Barriss Offee", "id": "243" },
|
||||
{ "username": "Hondo Ohnaka", "id": "244" },
|
||||
{ "username": "Ric Olié", "id": "245" },
|
||||
{ "username": "Omi", "id": "246" },
|
||||
{ "username": "Ketsu Onyo", "id": "247" },
|
||||
{ "username": "Oola", "id": "248" },
|
||||
{ "username": "OOM-9", "id": "249" },
|
||||
{ "username": "Savage Opress", "id": "250" },
|
||||
{ "username": "Senator Organa", "id": "251" },
|
||||
{ "username": "Breha Antilles-Organa", "id": "252" },
|
||||
{ "username": "Leia Organa", "id": "253" },
|
||||
{ "username": "Garazeb \"Zeb\" Orrelios", "id": "254" },
|
||||
{ "username": "Orrimarko", "id": "255" },
|
||||
{ "username": "Admiral Ozzel", "id": "256" },
|
||||
{ "username": "Odd Ball", "id": "257" },
|
||||
{ "username": "Pablo-Jill", "id": "258" },
|
||||
{ "username": "Teemto Pagalies", "id": "259" },
|
||||
{ "username": "Captain Quarsh Panaka", "id": "260" },
|
||||
{ "username": "Casca Panzoro", "id": "261" },
|
||||
{ "username": "Reeve Panzoro", "id": "262" },
|
||||
{ "username": "Baron Papanoida", "id": "263" },
|
||||
{ "username": "Che Amanwe Papanoida", "id": "264" },
|
||||
{ "username": "Chi Eekway Papanoida", "id": "265" },
|
||||
{ "username": "Paploo", "id": "266" },
|
||||
{ "username": "Captain Phasma", "id": "267" },
|
||||
{ "username": "Even Piell", "id": "268" },
|
||||
{ "username": "Admiral Firmus Piett", "id": "269" },
|
||||
{ "username": "Sarco Plank", "id": "270" },
|
||||
{ "username": "Unkar Plutt", "id": "271" },
|
||||
{ "username": "Poggle the Lesser", "id": "272" },
|
||||
{ "username": "Yarael Poof", "id": "273" },
|
||||
{ "username": "Jek Tono Porkins", "id": "274" },
|
||||
{ "username": "Nahdonnis Praji", "id": "275" },
|
||||
{ "username": "PZ-4CO", "id": "276" },
|
||||
{ "username": "Ben Quadinaros", "id": "277" },
|
||||
{ "username": "Qi'ra", "id": "278" },
|
||||
{ "username": "Quarrie", "id": "279" },
|
||||
{ "username": "Quiggold", "id": "280" },
|
||||
{ "username": "Artoo", "id": "281" },
|
||||
{ "username": "R2-KT", "id": "282" },
|
||||
{ "username": "R3-S6", "id": "283" },
|
||||
{ "username": "R4-P17", "id": "284" },
|
||||
{ "username": "R5-D4", "id": "285" },
|
||||
{ "username": "RA-7", "id": "286" },
|
||||
{ "username": "Rabé", "id": "287" },
|
||||
{ "username": "Admiral Raddus", "id": "288" },
|
||||
{ "username": "Dak Ralter", "id": "289" },
|
||||
{ "username": "Oppo Rancisis", "id": "290" },
|
||||
{ "username": "Admiral Dodd Rancit", "id": "291" },
|
||||
{ "username": "Rappertunie", "id": "292" },
|
||||
{ "username": "Siniir Rath Velus", "id": "293" },
|
||||
{ "username": "Gallius Rax", "id": "294" },
|
||||
{ "username": "Eneb Ray", "id": "295" },
|
||||
{ "username": "Max Rebo", "id": "296" },
|
||||
{ "username": "Ciena Ree", "id": "297" },
|
||||
{ "username": "Ree-Yees", "id": "298" },
|
||||
{ "username": "Kylo Ren", "id": "299" },
|
||||
{ "username": "Captain Rex", "id": "300" },
|
||||
{ "username": "Rey", "id": "301" },
|
||||
{ "username": "Carlist Rieekan", "id": "302" },
|
||||
{ "username": "Riley", "id": "303" },
|
||||
{ "username": "Rogue Squadron", "id": "304" },
|
||||
{ "username": "Romba", "id": "305" },
|
||||
{ "username": "Bodhi Rook", "id": "306" },
|
||||
{ "username": "Pagetti Rook", "id": "307" },
|
||||
{ "username": "Rotta the Hutt", "id": "308" },
|
||||
{ "username": "Rukh", "id": "309" },
|
||||
{ "username": "Sabé", "id": "310" },
|
||||
{ "username": "Saché", "id": "311" },
|
||||
{ "username": "Sarkli", "id": "312" },
|
||||
{ "username": "Admiral U.O. Statura", "id": "313" },
|
||||
{ "username": "Joph Seastriker", "id": "314" },
|
||||
{ "username": "Miraj Scintel", "id": "315" },
|
||||
{ "username": "Admiral Terrinald Screed", "id": "316" },
|
||||
{ "username": "Sebulba", "id": "317" },
|
||||
{ "username": "Aayla Secura", "id": "318" },
|
||||
{ "username": "Korr Sella", "id": "319" },
|
||||
{ "username": "Zev Senesca", "id": "320" },
|
||||
{ "username": "Echuu Shen-Jon", "id": "321" },
|
||||
{ "username": "Sifo-Dyas", "id": "322" },
|
||||
{ "username": "Aurra Sing", "id": "323" },
|
||||
{ "username": "Luke Skywalker", "id": "324" },
|
||||
{ "username": "Shmi Skywalker", "id": "325" },
|
||||
{ "username": "The Smuggler", "id": "326" },
|
||||
{ "username": "Snaggletooth", "id": "327" },
|
||||
{ "username": "Snoke", "id": "328" },
|
||||
{ "username": "Sy Snootles", "id": "329" },
|
||||
{ "username": "Osi Sobeck", "id": "330" },
|
||||
{ "username": "Han Solo", "id": "331" },
|
||||
{ "username": "Greer Sonnel", "id": "332" },
|
||||
{ "username": "Sana Starros", "id": "333" },
|
||||
{ "username": "Lama Su", "id": "334" },
|
||||
{ "username": "Mercurial Swift", "id": "335" },
|
||||
{ "username": "Gavyn Sykes", "id": "336" },
|
||||
{ "username": "Cham Syndulla", "id": "337" },
|
||||
{ "username": "Hera Syndulla", "id": "338" },
|
||||
{ "username": "Jacen Syndulla", "id": "339" },
|
||||
{ "username": "Orn Free Taa", "id": "340" },
|
||||
{ "username": "Cassio Tagge", "id": "341" },
|
||||
{ "username": "Mother Talzin", "id": "342" },
|
||||
{ "username": "Wat Tambor", "id": "343" },
|
||||
{ "username": "Riff Tamson", "id": "344" },
|
||||
{ "username": "Fulcrum", "id": "345" },
|
||||
{ "username": "Tarfful", "id": "346" },
|
||||
{ "username": "Jova Tarkin", "id": "347" },
|
||||
{ "username": "Wilhuff Tarkin", "id": "348" },
|
||||
{ "username": "Roos Tarpals", "id": "349" },
|
||||
{ "username": "TC-14", "id": "350" },
|
||||
{ "username": "Berch Teller", "id": "351" },
|
||||
{ "username": "Teebo", "id": "352" },
|
||||
{ "username": "Teedo", "id": "353" },
|
||||
{ "username": "Mod Terrik", "id": "354" },
|
||||
{ "username": "Tessek", "id": "355" },
|
||||
{ "username": "Lor San Tekka", "id": "356" },
|
||||
{ "username": "Petty Officer Thanisson", "id": "357" },
|
||||
{ "username": "Inspector Thanoth", "id": "358" },
|
||||
{ "username": "Lieutenant Thire", "id": "359" },
|
||||
{ "username": "Thrawn", "id": "360" },
|
||||
{ "username": "C'ai Threnalli", "id": "361" },
|
||||
{ "username": "Shaak Ti", "id": "362" },
|
||||
{ "username": "Paige Tico", "id": "363" },
|
||||
{ "username": "Rose Tico", "id": "364" },
|
||||
{ "username": "Saesee Tiin", "id": "365" },
|
||||
{ "username": "Bala-Tik", "id": "366" },
|
||||
{ "username": "Meena Tills", "id": "367" },
|
||||
{ "username": "Quay Tolsite", "id": "368" },
|
||||
{ "username": "Bargwill Tomder", "id": "369" },
|
||||
{ "username": "Wag Too", "id": "370" },
|
||||
{ "username": "Coleman Trebor", "id": "371" },
|
||||
{ "username": "Admiral Trench", "id": "372" },
|
||||
{ "username": "Strono Tuggs", "id": "373" },
|
||||
{ "username": "Tup", "id": "374" },
|
||||
{ "username": "Letta Turmond", "id": "375" },
|
||||
{ "username": "Longo Two-Guns", "id": "376" },
|
||||
{ "username": "Cpatain Typho", "id": "377" },
|
||||
{ "username": "Ratts Tyerell", "id": "378" },
|
||||
{ "username": "U9-C4", "id": "379" },
|
||||
{ "username": "Luminara Unduli", "id": "380" },
|
||||
{ "username": "Finis Valorum", "id": "381" },
|
||||
{ "username": "Eli Vanto", "id": "382" },
|
||||
{ "username": "Nahdar Vebb", "id": "383" },
|
||||
{ "username": "Maximilian Veers", "id": "384" },
|
||||
{ "username": "Asajj Ventress", "id": "385" },
|
||||
{ "username": "Evaan Verlaine", "id": "386" },
|
||||
{ "username": "Garrick Versio", "id": "387" },
|
||||
{ "username": "Iden Versio", "id": "388" },
|
||||
{ "username": "Lanever Villecham", "id": "389" },
|
||||
{ "username": "Nuvo Vindi", "id": "390" },
|
||||
{ "username": "Tulon Voidgazer", "id": "391" },
|
||||
{ "username": "Dryden Vos", "id": "392" },
|
||||
{ "username": "Quinlan Vos", "id": "393" },
|
||||
{ "username": "WAC-47", "id": "394" },
|
||||
{ "username": "Wald", "id": "395" },
|
||||
{ "username": "Warok", "id": "396" },
|
||||
{ "username": "Wicket W. Warrick", "id": "397" },
|
||||
{ "username": "Watto", "id": "398" },
|
||||
{ "username": "Taun We", "id": "399" },
|
||||
{ "username": "Zam Wesell", "id": "400" },
|
||||
{ "username": "Norra Wexley", "id": "401" },
|
||||
{ "username": "Snap Wexley", "id": "402" },
|
||||
{ "username": "Vanden Willard", "id": "403" },
|
||||
{ "username": "Mace Windu", "id": "404" },
|
||||
{ "username": "Commander Wolffe", "id": "405" },
|
||||
{ "username": "Wollivan", "id": "406" },
|
||||
{ "username": "Sabine Wren", "id": "407" },
|
||||
{ "username": "Wuher", "id": "408" },
|
||||
{ "username": "Yaddle", "id": "409" },
|
||||
{ "username": "Yoda", "id": "410" },
|
||||
{ "username": "Joh Yowza", "id": "411" },
|
||||
{ "username": "Wullf Yularen", "id": "412" },
|
||||
{ "username": "Ziro the Hutt", "id": "413" },
|
||||
{ "username": "Zuckuss", "id": "414" },
|
||||
{ "username": "Constable Zuvio", "id": "415" }
|
||||
]
|
20
examples/mentions/value.json
Normal file
20
examples/mentions/value.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"document": {
|
||||
"nodes": [
|
||||
{
|
||||
"object": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"object": "text",
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try mentioning some users, like Luke or Leia."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user