Skip to content

Commit

Permalink
fix(factories): Allow alphanumeric IDs for suggestion nodes in `creat…
Browse files Browse the repository at this point in the history
…eSuggestionExtension` (Doist#66)
  • Loading branch information
rfgamaral authored Dec 19, 2022
1 parent 416fb26 commit a1726a6
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 140 deletions.
16 changes: 8 additions & 8 deletions src/factories/create-suggestion-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type SuggestionAttributes = {
/**
* The suggestion node unique identifier to be rendered by the editor as a `data-id` attribute.
*/
id: number
id: number | string

/**
* The suggestion node label to be rendered by the editor as a `data-label` attribute and the
Expand Down Expand Up @@ -110,7 +110,7 @@ type SuggestionStorage<SuggestionItemType> = Readonly<{
/**
* A collection of suggestion items indexed by the item id.
*/
itemsById: { readonly [id: number]: SuggestionItemType | undefined }
itemsById: { readonly [id: SuggestionAttributes['id']]: SuggestionItemType | undefined }
}>

/**
Expand All @@ -136,7 +136,7 @@ type SuggestionExtensionResult<SuggestionItemType> = Node<SuggestionOptions<Sugg
* @returns A new suggestion extension tailored to a specific use case.
*/
function createSuggestionExtension<
SuggestionItemType extends { [id: string]: unknown } = SuggestionAttributes,
SuggestionItemType extends { [id: SuggestionAttributes['id']]: unknown } = SuggestionAttributes,
>(
type: string,
items: SuggestionItemType[] = [],
Expand All @@ -148,8 +148,8 @@ function createSuggestionExtension<
? []
: [
RequireAtLeastOne<{
id: ConditionalKeys<SuggestionItemType, number>
label: ConditionalKeys<SuggestionItemType, string>
id: ConditionalKeys<SuggestionItemType, SuggestionAttributes['id']>
label: ConditionalKeys<SuggestionItemType, SuggestionAttributes['label']>
}>,
]
): SuggestionExtensionResult<SuggestionItemType> {
Expand Down Expand Up @@ -200,13 +200,13 @@ function createSuggestionExtension<
default: null,
parseHTML: (element) => element.getAttribute('data-id'),
renderHTML: (attributes) => ({
'data-id': Number(attributes[idAttribute]),
'data-id': String(attributes[idAttribute]),
}),
},
[labelAttribute]: {
default: null,
parseHTML: (element: Element) => {
const id = Number(element.getAttribute('data-id'))
const id = String(element.getAttribute('data-id'))
const item = this.storage.itemsById[id]

// Read the latest item label from the storage, if available, otherwise
Expand All @@ -229,7 +229,7 @@ function createSuggestionExtension<
{
[`data-${attributeType}`]: '',
'aria-label': this.options.renderAriaLabel?.({
id: Number(node.attrs[idAttribute]),
id: String(node.attrs[idAttribute]),
label: String(node.attrs[labelAttribute]),
}),
},
Expand Down
2 changes: 1 addition & 1 deletion src/serializers/html/extensions/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function link(suggestionNodes: NodeType[]): marked.MarkedExtension {
renderer: {
link(href, title, text) {
if (href && linkSchemaRegex.test(href)) {
const [, schema, id] = /^([a-z-]+):\/\/(\d+)$/i.exec(href) || []
const [, schema, id] = /^([a-z-]+):\/\/(\S+)$/i.exec(href) || []

if (schema && id && text) {
return `<span data-${schema} data-id="${id}" data-label="${text}"></span>`
Expand Down
13 changes: 13 additions & 0 deletions src/serializers/html/html.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,19 @@ Answer: [Doist Frontend](channel://190200)`),
})

describe('with custom `*Suggestion` extensions', () => {
test('suggestion extensions support alphanumeric IDs', () => {
const customSerializer = createHTMLSerializer(
getSchema([RichTextKit, createSuggestionExtension('mention')]),
)

expect(
customSerializer.serialize(`Question: Who's the head of the Frontend team?
Answer: [Henning M](mention://user:190200@doist.dev)`),
).toBe(
'<p>Question: Who\'s the head of the Frontend team?<br>Answer: <span data-mention="" data-id="user:190200@doist.dev" data-label="Henning M"></span></p>',
)
})

test('mention suggestions HTML output is correct', () => {
const customSerializer = createHTMLSerializer(
getSchema([RichTextKit, createSuggestionExtension('mention')]),
Expand Down
27 changes: 17 additions & 10 deletions stories/documentation/usage/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,25 @@ To help typing a TypeScript code base, you may find the `SuggestionRendererProps
```tsx
import { createSuggestionExtension, TypistEditor, RichTextKit } from '@doist/typist'

import type { SuggestionExtensionResult } from '@doist/typist'

type MentionUser = {
id: string
name: string
}

function TypistEditorContainer({ content }) {
const MENTION_USERS: MentionUser[] = [
{ id: 7582311426, name: 'Amy Adams' },
{ id: 4759170383, name: 'Brad Pitt' },
{ id: 2860927956, name: 'Christian Bale' },
{ id: 6191245793, name: 'Emily Blunt' },
{ id: 1244620246, name: 'Heath Ledger' },
{ id: 6912657435, name: 'Kate Winslet' },
{ id: 6023459994, name: 'Robert Downey Jr.' },
{ id: 2500654397, name: 'Sandra Bullock' },
{ id: 9072641833, name: 'Viola Davis' },
{ id: 6606640946, name: 'Willem Dafoe' },
{ id: '7582311426', name: 'Amy Adams' },
{ id: '4759170383', name: 'Brad Pitt' },
{ id: '2860927956', name: 'Christian Bale' },
{ id: '6191245793', name: 'Emily Blunt' },
{ id: '1244620246', name: 'Heath Ledger' },
{ id: '6912657435', name: 'Kate Winslet' },
{ id: '6023459994', name: 'Robert Downey Jr.' },
{ id: '2500654397', name: 'Sandra Bullock' },
{ id: '9072641833', name: 'Viola Davis' },
{ id: '6606640946', name: 'Willem Dafoe' },
]

const MentionExtension: SuggestionExtensionResult<MentionUser> =
Expand Down
217 changes: 111 additions & 106 deletions stories/typist-editor/constants/suggestions.ts
Original file line number Diff line number Diff line change
@@ -1,114 +1,11 @@
import { shuffle } from 'lodash-es'

type SuggestionItem = {
type HashtagSuggestionItem = {
id: number
name: string
}

const MENTION_SUGGESTION_ITEMS: SuggestionItem[] = shuffle([
{ id: 8218877932, name: 'Al Pacino' },
{ id: 2376248434, name: 'Alan Rickman' },
{ id: 7582311426, name: 'Amy Adams' },
{ id: 5441252154, name: 'Anne Bancroft' },
{ id: 8140398438, name: 'Anne Hathaway' },
{ id: 3777716804, name: 'Anthony Hopkins' },
{ id: 4478885052, name: 'Audrey Hepburn' },
{ id: 3100863011, name: 'Barbara Stanwyck' },
{ id: 8874179979, name: 'Benedict Cumberbatch' },
{ id: 9000940982, name: 'Bette Davis' },
{ id: 4759170383, name: 'Brad Pitt' },
{ id: 5956789123, name: 'Cate Blanchett' },
{ id: 8470497758, name: 'Charlize Theron' },
{ id: 2860927956, name: 'Christian Bale' },
{ id: 4457661309, name: 'Christopher Walken' },
{ id: 7343443712, name: 'Clint Eastwood' },
{ id: 2465030183, name: 'Daniel Day-Lewis' },
{ id: 5614659455, name: 'Denzel Washington' },
{ id: 7659120581, name: 'Diane Keaton' },
{ id: 6222459463, name: 'Ed Harris' },
{ id: 6730011157, name: 'Edward Norton' },
{ id: 2019696630, name: 'Elizabeth Taylor' },
{ id: 6191245793, name: 'Emily Blunt' },
{ id: 6865092325, name: 'Emma Stone' },
{ id: 5064838948, name: 'Emma Thompson' },
{ id: 6192534926, name: 'Frances McDormand' },
{ id: 4473889068, name: 'Gary Oldman' },
{ id: 1225854407, name: 'Gary Sinise' },
{ id: 4595551363, name: 'Glenn Close' },
{ id: 2408231736, name: 'Grace Kelly' },
{ id: 8304547201, name: 'Harrison Ford' },
{ id: 1244620246, name: 'Heath Ledger' },
{ id: 5661480326, name: 'Helen Mirren' },
{ id: 7569742737, name: 'Helena Bonham Carter' },
{ id: 3421423628, name: 'Hugh Jackman' },
{ id: 2758495686, name: 'Ian McKellen' },
{ id: 7492110081, name: 'Ingrid Bergman' },
{ id: 1838559507, name: 'Jack Nicholson' },
{ id: 2418976696, name: 'James Earl Jones' },
{ id: 4637367751, name: 'James Stewart' },
{ id: 8122384676, name: 'Jamie Lee Curtis' },
{ id: 1582145121, name: 'Jeff Bridges' },
{ id: 3826097343, name: 'Jessica Lange' },
{ id: 1899996303, name: 'Joaquin Phoenix' },
{ id: 8213809556, name: 'Jodie Foster' },
{ id: 2769654314, name: 'John Malkovich' },
{ id: 3973945098, name: 'Johnny Depp' },
{ id: 6486781572, name: 'Judi Dench' },
{ id: 5190636267, name: 'Judy Garland' },
{ id: 7615641462, name: 'Julia Roberts' },
{ id: 8247258609, name: 'Julianne Moore' },
{ id: 5312890529, name: 'Julie Andrews' },
{ id: 6912657435, name: 'Kate Winslet' },
{ id: 3010776063, name: 'Katherine Hepburn' },
{ id: 3207878474, name: 'Kathy Bates' },
{ id: 9871381263, name: 'Keanu Reeves' },
{ id: 6386622812, name: 'Kurt Russell' },
{ id: 3421420327, name: 'Lauren Bacall' },
{ id: 2300368234, name: 'Leonardo DiCaprio' },
{ id: 9618430022, name: 'Liam Neeson' },
{ id: 1178907031, name: 'Lucille Ball' },
{ id: 1811477526, name: 'Maggie Smith' },
{ id: 8689785604, name: 'Margot Robbie' },
{ id: 6392738649, name: 'Marilyn Monroe' },
{ id: 7839597351, name: 'Marlon Brando' },
{ id: 9817442942, name: 'Matt Damon' },
{ id: 6949006047, name: 'Matthew McConaughey' },
{ id: 2153090253, name: "Maureen O'Hara" },
{ id: 1337198757, name: 'Meryl Streep' },
{ id: 3629953174, name: 'Michael Caine' },
{ id: 7091971426, name: 'Michael Keaton' },
{ id: 6885375349, name: 'Michelle Pfeiffer' },
{ id: 5972052865, name: 'Morgan Freeman' },
{ id: 2690675889, name: 'Natalie Portman' },
{ id: 2788414803, name: 'Natalie Wood' },
{ id: 8884477514, name: 'Nicole Kidman' },
{ id: 5284776100, name: 'Olivia de Havilland' },
{ id: 6528954143, name: 'Patrick Stewart' },
{ id: 3637504189, name: 'Paul Newman' },
{ id: 4106668076, name: 'Rita Hayworth' },
{ id: 4782151713, name: 'Robert De Niro' },
{ id: 6023459994, name: 'Robert Downey Jr.' },
{ id: 7249698311, name: 'Robert Duvall' },
{ id: 7421532064, name: 'Robin Williams' },
{ id: 9329767532, name: 'Russell Crowe' },
{ id: 8909819154, name: 'Sally Field' },
{ id: 5068389870, name: 'Sam Elliott' },
{ id: 4103461020, name: 'Samuel L. Jackson' },
{ id: 2500654397, name: 'Sandra Bullock' },
{ id: 9306639155, name: 'Scarlett Johansson' },
{ id: 3051881376, name: 'Sean Connery' },
{ id: 2694624022, name: 'Sigourney Weaver' },
{ id: 2434031862, name: 'Steve Buscemi' },
{ id: 8290874650, name: 'Susan Sarandon' },
{ id: 6728264612, name: 'Tom Hanks' },
{ id: 7631497125, name: 'Tom Hardy' },
{ id: 8488152868, name: 'Tommy Lee Jones' },
{ id: 9072641833, name: 'Viola Davis' },
{ id: 6419632986, name: 'Vivien Leigh' },
{ id: 6606640946, name: 'Willem Dafoe' },
])

const HASHTAG_SUGGESTION_ITEMS: SuggestionItem[] = shuffle([
const HASHTAG_SUGGESTION_ITEMS: HashtagSuggestionItem[] = shuffle([
{ id: 2268429318, name: 'amazing' },
{ id: 9426970746, name: 'art' },
{ id: 7206271383, name: 'autumn' },
Expand Down Expand Up @@ -161,6 +58,114 @@ const HASHTAG_SUGGESTION_ITEMS: SuggestionItem[] = shuffle([
{ id: 7625628140, name: 'yummy' },
])

type MentionSuggestionItem = {
uid: string
name: string
}

const MENTION_SUGGESTION_ITEMS: MentionSuggestionItem[] = shuffle([
{ uid: '8218877932', name: 'Al Pacino' },
{ uid: '2376248434', name: 'Alan Rickman' },
{ uid: '7582311426', name: 'Amy Adams' },
{ uid: '5441252154', name: 'Anne Bancroft' },
{ uid: '8140398438', name: 'Anne Hathaway' },
{ uid: '3777716804', name: 'Anthony Hopkins' },
{ uid: '4478885052', name: 'Audrey Hepburn' },
{ uid: '3100863011', name: 'Barbara Stanwyck' },
{ uid: '8874179979', name: 'Benedict Cumberbatch' },
{ uid: '9000940982', name: 'Bette Davis' },
{ uid: '4759170383', name: 'Brad Pitt' },
{ uid: '5956789123', name: 'Cate Blanchett' },
{ uid: '8470497758', name: 'Charlize Theron' },
{ uid: '2860927956', name: 'Christian Bale' },
{ uid: '4457661309', name: 'Christopher Walken' },
{ uid: '7343443712', name: 'Clint Eastwood' },
{ uid: '2465030183', name: 'Daniel Day-Lewis' },
{ uid: '5614659455', name: 'Denzel Washington' },
{ uid: '7659120581', name: 'Diane Keaton' },
{ uid: '6222459463', name: 'Ed Harris' },
{ uid: '6730011157', name: 'Edward Norton' },
{ uid: '2019696630', name: 'Elizabeth Taylor' },
{ uid: '6191245793', name: 'Emily Blunt' },
{ uid: '6865092325', name: 'Emma Stone' },
{ uid: '5064838948', name: 'Emma Thompson' },
{ uid: '6192534926', name: 'Frances McDormand' },
{ uid: '4473889068', name: 'Gary Oldman' },
{ uid: '1225854407', name: 'Gary Sinise' },
{ uid: '4595551363', name: 'Glenn Close' },
{ uid: '2408231736', name: 'Grace Kelly' },
{ uid: '8304547201', name: 'Harrison Ford' },
{ uid: '1244620246', name: 'Heath Ledger' },
{ uid: '5661480326', name: 'Helen Mirren' },
{ uid: '7569742737', name: 'Helena Bonham Carter' },
{ uid: '3421423628', name: 'Hugh Jackman' },
{ uid: '2758495686', name: 'Ian McKellen' },
{ uid: '7492110081', name: 'Ingrid Bergman' },
{ uid: '1838559507', name: 'Jack Nicholson' },
{ uid: '2418976696', name: 'James Earl Jones' },
{ uid: '4637367751', name: 'James Stewart' },
{ uid: '8122384676', name: 'Jamie Lee Curtis' },
{ uid: '1582145121', name: 'Jeff Bridges' },
{ uid: '3826097343', name: 'Jessica Lange' },
{ uid: '1899996303', name: 'Joaquin Phoenix' },
{ uid: '8213809556', name: 'Jodie Foster' },
{ uid: '2769654314', name: 'John Malkovich' },
{ uid: '3973945098', name: 'Johnny Depp' },
{ uid: '6486781572', name: 'Judi Dench' },
{ uid: '5190636267', name: 'Judy Garland' },
{ uid: '7615641462', name: 'Julia Roberts' },
{ uid: '8247258609', name: 'Julianne Moore' },
{ uid: '5312890529', name: 'Julie Andrews' },
{ uid: '6912657435', name: 'Kate Winslet' },
{ uid: '3010776063', name: 'Katherine Hepburn' },
{ uid: '3207878474', name: 'Kathy Bates' },
{ uid: '9871381263', name: 'Keanu Reeves' },
{ uid: '6386622812', name: 'Kurt Russell' },
{ uid: '3421420327', name: 'Lauren Bacall' },
{ uid: '2300368234', name: 'Leonardo DiCaprio' },
{ uid: '9618430022', name: 'Liam Neeson' },
{ uid: '1178907031', name: 'Lucille Ball' },
{ uid: '1811477526', name: 'Maggie Smith' },
{ uid: '8689785604', name: 'Margot Robbie' },
{ uid: '6392738649', name: 'Marilyn Monroe' },
{ uid: '7839597351', name: 'Marlon Brando' },
{ uid: '9817442942', name: 'Matt Damon' },
{ uid: '6949006047', name: 'Matthew McConaughey' },
{ uid: '2153090253', name: "Maureen O'Hara" },
{ uid: '1337198757', name: 'Meryl Streep' },
{ uid: '3629953174', name: 'Michael Caine' },
{ uid: '7091971426', name: 'Michael Keaton' },
{ uid: '6885375349', name: 'Michelle Pfeiffer' },
{ uid: '5972052865', name: 'Morgan Freeman' },
{ uid: '2690675889', name: 'Natalie Portman' },
{ uid: '2788414803', name: 'Natalie Wood' },
{ uid: '8884477514', name: 'Nicole Kidman' },
{ uid: '5284776100', name: 'Olivia de Havilland' },
{ uid: '6528954143', name: 'Patrick Stewart' },
{ uid: '3637504189', name: 'Paul Newman' },
{ uid: '4106668076', name: 'Rita Hayworth' },
{ uid: '4782151713', name: 'Robert De Niro' },
{ uid: '6023459994', name: 'Robert Downey Jr.' },
{ uid: '7249698311', name: 'Robert Duvall' },
{ uid: '7421532064', name: 'Robin Williams' },
{ uid: '9329767532', name: 'Russell Crowe' },
{ uid: '8909819154', name: 'Sally Field' },
{ uid: '5068389870', name: 'Sam Elliott' },
{ uid: '4103461020', name: 'Samuel L. Jackson' },
{ uid: '2500654397', name: 'Sandra Bullock' },
{ uid: '9306639155', name: 'Scarlett Johansson' },
{ uid: '3051881376', name: 'Sean Connery' },
{ uid: '2694624022', name: 'Sigourney Weaver' },
{ uid: '2434031862', name: 'Steve Buscemi' },
{ uid: '8290874650', name: 'Susan Sarandon' },
{ uid: '6728264612', name: 'Tom Hanks' },
{ uid: '7631497125', name: 'Tom Hardy' },
{ uid: '8488152868', name: 'Tommy Lee Jones' },
{ uid: '9072641833', name: 'Viola Davis' },
{ uid: '6419632986', name: 'Vivien Leigh' },
{ uid: '6606640946', name: 'Willem Dafoe' },
])

/**
* Workaround for the current typing incompatibility between Tippy.js and Tiptap Suggestion utility.
*
Expand All @@ -182,4 +187,4 @@ const DOM_RECT_FALLBACK: DOMRect = {

export { DOM_RECT_FALLBACK, HASHTAG_SUGGESTION_ITEMS, MENTION_SUGGESTION_ITEMS }

export type { SuggestionItem }
export type { HashtagSuggestionItem, MentionSuggestionItem }
14 changes: 9 additions & 5 deletions stories/typist-editor/extensions/hashtag-suggestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import { HashtagSuggestionDropdown } from './suggestions/hashtag-suggestion-drop

import type { Instance as TippyInstace, Props as TippyProps } from 'tippy.js'
import type { SuggestionExtensionResult, SuggestionRendererRef } from '../../../src'
import type { SuggestionItem } from '../constants/suggestions'
import type { HashtagSuggestionItem } from '../constants/suggestions'

const HASHTAG_SUGGESTION_TYPE = 'hashtag'
const HASHTAG_SUGGESTION_NODE_TYPE = `${HASHTAG_SUGGESTION_TYPE}Suggestion`

const HashtagSuggestion: SuggestionExtensionResult<SuggestionItem> =
createSuggestionExtension<SuggestionItem>(HASHTAG_SUGGESTION_TYPE, HASHTAG_SUGGESTION_ITEMS, {
label: 'name',
}).configure({
const HashtagSuggestion: SuggestionExtensionResult<HashtagSuggestionItem> =
createSuggestionExtension<HashtagSuggestionItem>(
HASHTAG_SUGGESTION_TYPE,
HASHTAG_SUGGESTION_ITEMS,
{
label: 'name',
},
).configure({
triggerChar: '#',
renderAriaLabel({ label }) {
return `Hashtag: #${label}`
Expand Down
Loading

0 comments on commit a1726a6

Please sign in to comment.