Skip to content

Commit

Permalink
373 - Use ReactQuill and QuillMention instead of react-mentions
Browse files Browse the repository at this point in the history
  • Loading branch information
kresimir-coko committed Aug 5, 2023
1 parent 14e201e commit cc109da
Show file tree
Hide file tree
Showing 17 changed files with 694 additions and 281 deletions.
195 changes: 130 additions & 65 deletions client/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@
"@tanstack/react-query-devtools": "^4.32.0",
"@tanstack/react-table": "^8.9.3",
"@types/d3": "^7.4.0",
"@types/quill": "^2.0.10",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"d3-hierarchy": "^3.1.2",
"d3-timer": "^3.0.1",
"lucide-react": "^0.263.1",
"quill-mention": "^3.4.0",
"react": "^18.2.0",
"react-datepicker": "^4.16.0",
"react-dom": "^18.2.0",
Expand All @@ -76,7 +78,7 @@
"react-hook-form": "^7.45.2",
"react-inlinesvg": "^3.0.2",
"react-json-view": "^1.21.3",
"react-mentions": "^4.4.10",
"react-quill": "^2.0.0",
"react-router-dom": "^6.14.2",
"react-select": "^5.7.4",
"reactflow": "^11.7.4",
Expand All @@ -103,7 +105,6 @@
"@testing-library/react-hooks": "^8.0.1",
"@types/node": "^20.4.5",
"@types/react-datepicker": "^4.15.0",
"@types/react-mentions": "^4.1.8",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
Expand Down
41 changes: 41 additions & 0 deletions client/src/components/MentionsInput/MentionBlot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {Quill} from 'react-quill';

const Embed = Quill.import('blots/embed');

export default class MentionBlot extends Embed {
static blotName = 'bytechef-mention';
static tagName = 'div';
static className = 'bytechef-mention';

static create(data: {denotationChar: string; value: string; icon: string}) {
const node = super.create();
const iconNode = document.createElement('span');
const contentNode = document.createElement('span');

iconNode.innerHTML = data.icon;
contentNode.innerHTML = data.value;

node.appendChild(iconNode);
node.appendChild(contentNode);

return MentionBlot.setDataValues(node, data);
}

static setDataValues(element: HTMLElement, data: {[key: string]: string}) {
const domNode = element;

Object.keys(data).forEach((key) => {
domNode.dataset[key] = data[key];

if (key === 'component') {
domNode.dataset[key] = JSON.stringify(data[key]);
}
});

return domNode;
}

static value(domNode: {dataset: object}) {
return domNode.dataset;
}
}
127 changes: 127 additions & 0 deletions client/src/components/MentionsInput/MentionsInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import QuillMention from 'quill-mention';
import React, {useCallback, useRef, useState} from 'react';
import ReactQuill, {Quill} from 'react-quill';

import './mentionsInput.css';

import {useNodeDetailsDialogStore} from '@/pages/automation/project/stores/useNodeDetailsDialogStore';
import {DataPillType} from '@/types/types';
import {twMerge} from 'tailwind-merge';

import MentionBlot from './MentionBlot';

Quill.register('modules/mentions', QuillMention);
Quill.register('formats/bytechef-mention', MentionBlot);

const MentionInputListItem = (item: DataPillType) => `
<div>
<span>${item.icon}</span>
<span>${item.value}</span>
</div>
`;

type MentionsInputProps = {
data: Array<DataPillType>;
id: string;
placeholder?: string;
};

const MentionsInput = ({data, id, placeholder}: MentionsInputProps) => {
const [value, setValue] = useState('');
const editorRef = useRef<ReactQuill>(null);

const {focusedInput, setFocusedInput} = useNodeDetailsDialogStore();

const modules = {
mention: {
allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
blotName: 'mention',
dataAttributes: ['component'],
fixMentionsToQuill: true,
mentionDenotationChars: ['${'],
onSelect: useCallback(
(
item: DataPillType,
insertItem: (
data: DataPillType,
programmaticInsert: boolean,
overriddenOptions: object
) => void
) => {
const component = JSON.parse(item.component as string);

insertItem(
{
component,
icon: component.icon,
id: item.id,
value: item.value,
},
false,
{
blotName: 'bytechef-mention',
}
);
},
[]
),
renderItem: useCallback(
(item: DataPillType) => MentionInputListItem(item),
[]
),
showDenotationChar: false,
source: useCallback(
(
searchTerm: string,
renderList: (arg1: Array<object>, arg2: string) => void
) => {
const formattedData = data.map((datum) => ({
...datum,
icon: JSON.parse(datum.component as string).icon,
}));

if (searchTerm.length === 0) {
renderList(formattedData, searchTerm);
} else {
const matches = formattedData.filter(
(datum) =>
~datum.value
.toLowerCase()
.indexOf(searchTerm.toLowerCase())
);

renderList(matches, searchTerm);
}
},
[data]
),
spaceAfterInsert: true,
},
toolbar: false,
};

return (
<ReactQuill
className={twMerge(
'h-full w-full',
focusedInput?.props.id === id && 'focused'
)}
formats={['bytechef-mention', 'mention']}
id={id}
key="keyBAR"
modules={modules}
onChange={setValue}
onFocus={() => {
if (editorRef.current) {
setFocusedInput(editorRef.current);
}
}}
placeholder={placeholder}
ref={editorRef}
value={value}
/>
);
};

export default React.memo(MentionsInput);
1 change: 1 addition & 0 deletions client/src/components/MentionsInput/mentions.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'quill-mention';
97 changes: 97 additions & 0 deletions client/src/components/MentionsInput/mentionsInput.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
.quill.focused .ql-editor {
@apply shadow-lg shadow-blue-200;
@apply ring ring-blue-500;
}

.ql-editor.ql-blank::before {
@apply content-[attr(data-placeholder)];
@apply absolute;
@apply pointer-events-none;
@apply opacity-100;
@apply text-gray-400;
}

.ql-clipboard {
@apply invisible;
@apply h-0;
}

.ql-editor {
@apply border;
@apply rounded-md;
@apply p-2;
@apply text-sm;
@apply leading-8;
@apply focus:outline-none focus:ring focus:ring-blue-500;
}

.ql-mention-list-container {
@apply bg-gray-100;
@apply border;
@apply shadow-lg;
@apply z-50;
@apply m-4 mt-6;
@apply rounded-md;
@apply max-h-96;
@apply overflow-y-auto;
}

.ql-mention-list {
@apply rounded-md;
@apply space-y-2;
@apply p-2;
}

.ql-mention-list-item {
@apply px-2 py-1;
@apply text-sm;
@apply bg-white;
@apply rounded-md;
@apply cursor-pointer;
@apply border-2 border-transparent;
@apply hover:border-2 hover:border-gray-400;
@apply flex;
}

.ql-mention-list-item div {
@apply flex;
@apply items-center;
@apply space-x-2;
}

.ql-mention-list-item svg {
@apply h-4;
@apply w-4;
}

.mention {
@apply border;
@apply bg-gray-100;
@apply px-2 py-1;
@apply rounded-full;
}

.mention::before {
@apply bg-[url(data-icon)];
@apply content-[''];
@apply h-4 w-4;
}

.bytechef-mention {
@apply inline-flex;
@apply items-center;
}

.bytechef-mention > span {
@apply inline-flex;
@apply items-center;
@apply border;
@apply bg-gray-100;
@apply px-2;
@apply rounded-full;
}

.bytechef-mention svg {
@apply mr-1;
@apply h-4 w-4;
}
Loading

0 comments on commit cc109da

Please sign in to comment.