-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(web,novui): initial implementation of var autocomplete in contro…
…ls (#6097) Co-authored-by: Joel Anton <joel@novu.co> Co-authored-by: Richard Fontein <32132657+rifont@users.noreply.github.com>
- Loading branch information
1 parent
aee2429
commit 43805d4
Showing
28 changed files
with
1,009 additions
and
273 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,7 @@ | |
"bcast", | ||
"behaviour", | ||
"bestguess", | ||
"tiptap", | ||
"binipdisplay", | ||
"bitauth", | ||
"bitjson", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './routing'; | ||
export * from './variables'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { SystemVariablesWithTypes } from '@novu/shared'; | ||
|
||
export function getSuggestionVariables(schemaObject, namespace: string) { | ||
return Object.keys(schemaObject).flatMap((name) => { | ||
const schemaItem = schemaObject[name]; | ||
if (schemaItem?.type === 'object') { | ||
return getSuggestionVariables(schemaItem.properties, `${namespace}.${name}`); | ||
} | ||
|
||
return `${namespace}.${name}`; | ||
}); | ||
} | ||
|
||
export const subscriberVariables = getSuggestionVariables(SystemVariablesWithTypes.subscriber, 'subscriber'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
@layer reset, base, mantine, tokens, recipes, utilities; | ||
|
||
@import '@mantine/core/styles.layer.css'; | ||
@import '@mantine/tiptap/styles.layer.css'; | ||
@import '../styled-system/styles.css'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
libs/novui/src/json-schema-components/widgets/InputEditorWidget.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { useEffect, useMemo, useRef } from 'react'; | ||
|
||
import { WidgetProps } from '@rjsf/utils'; | ||
|
||
import { Input } from '@mantine/core'; | ||
import { RichTextEditor } from '@mantine/tiptap'; | ||
|
||
import { type Extensions, useEditor } from '@tiptap/react'; | ||
import Text from '@tiptap/extension-text'; | ||
import Paragraph from '@tiptap/extension-paragraph'; | ||
import { ReactRenderer } from '@tiptap/react'; | ||
import Document from '@tiptap/extension-document'; | ||
import Mention from '@tiptap/extension-mention'; | ||
|
||
import { css, cx } from '../../../styled-system/css'; | ||
import { input, inputEditorWidget } from '../../../styled-system/recipes'; | ||
import { splitCssProps } from '../../../styled-system/jsx'; | ||
|
||
import { VariableSuggestionList, SuggestionListRef, VariableItem } from './VariableSuggestionList'; | ||
|
||
const inputEditorClassNames = inputEditorWidget(); | ||
|
||
const AUTOCOMPLETE_OPEN_TAG = '{{'; | ||
const AUTOCOMPLETE_CLOSE_TAG = '}}'; | ||
|
||
const AUTOCOMPLETE_REGEX = new RegExp(`${AUTOCOMPLETE_OPEN_TAG}(.*?(.*?))${AUTOCOMPLETE_CLOSE_TAG}`, 'gm'); | ||
|
||
export const InputEditorWidget = (props: WidgetProps) => { | ||
const { value, label, formContext, onChange, required, readonly, rawErrors, options, schema } = props; | ||
const [variantProps, inputProps] = input.splitVariantProps({}); | ||
const [cssProps] = splitCssProps(inputProps); | ||
const classNames = input(variantProps); | ||
|
||
const { variables = [] } = formContext; | ||
const reactRenderer = useRef<ReactRenderer<SuggestionListRef>>(null); | ||
|
||
const variablesList = useMemo<VariableItem[]>(() => { | ||
return variables?.map((variable: string) => { | ||
return { label: variable, id: variable }; | ||
}); | ||
}, [variables]); | ||
|
||
const baseExtensions: Extensions = [Document, Paragraph, Text]; | ||
|
||
if (variables.length) { | ||
baseExtensions.push( | ||
Mention.configure({ | ||
HTMLAttributes: { | ||
class: 'suggestion', | ||
}, | ||
renderHTML: ({ options, node }) => { | ||
return [ | ||
'span', | ||
options.HTMLAttributes, | ||
`${options.suggestion.char}${node.attrs.label ?? node.attrs.id}${AUTOCOMPLETE_CLOSE_TAG}`, | ||
]; | ||
}, | ||
renderText: ({ options, node }) => { | ||
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}${AUTOCOMPLETE_CLOSE_TAG}`; | ||
}, | ||
suggestion: { | ||
items: ({ query }) => { | ||
return variablesList?.filter((item) => item.label.toLowerCase().includes(query.toLowerCase())); | ||
}, | ||
char: AUTOCOMPLETE_OPEN_TAG, | ||
render() { | ||
return { | ||
onStart: (props) => { | ||
reactRenderer.current = new ReactRenderer(VariableSuggestionList, { | ||
props, | ||
editor: props.editor, | ||
}); | ||
}, | ||
onUpdate(props) { | ||
reactRenderer.current?.updateProps(props); | ||
}, | ||
onKeyDown(props) { | ||
if (!reactRenderer.current?.ref) { | ||
return false; | ||
} | ||
|
||
return reactRenderer.current?.ref.onKeyDown(props); | ||
}, | ||
onExit() { | ||
reactRenderer.current?.destroy(); | ||
}, | ||
}; | ||
}, | ||
}, | ||
}) | ||
); | ||
} | ||
|
||
const editor = useEditor({ | ||
extensions: baseExtensions, | ||
content: '', | ||
editable: !readonly, | ||
onFocus: () => { | ||
reactRenderer.current?.ref?.focus(); | ||
}, | ||
onUpdate: ({ editor }) => { | ||
const content = editor.isEmpty ? undefined : editor.getText(); | ||
onChange(content); | ||
}, | ||
}); | ||
|
||
useEffect(() => { | ||
if (editor) { | ||
const newValue = value | ||
?.toString() | ||
.replace( | ||
AUTOCOMPLETE_REGEX, | ||
'<span data-id="$1" contenteditable="false" class="suggestion" data-type="mention">$1</span>' | ||
); | ||
|
||
editor.commands.setContent(newValue); | ||
} | ||
}, []); | ||
|
||
return ( | ||
<Input.Wrapper | ||
classNames={classNames} | ||
className={cx('group', css(cssProps))} | ||
required={required} | ||
label={label} | ||
description={props.schema.description} | ||
error={rawErrors?.length > 0 && rawErrors} | ||
> | ||
<RichTextEditor classNames={inputEditorClassNames} editor={editor}> | ||
<RichTextEditor.Content /> | ||
</RichTextEditor> | ||
</Input.Wrapper> | ||
); | ||
}; |
24 changes: 0 additions & 24 deletions
24
libs/novui/src/json-schema-components/widgets/InputWidget.tsx
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.