diff --git a/packages/markups/src/blocks/OrderedListBlock.js b/packages/markups/src/blocks/OrderedListBlock.js
index 0c1a3c62f3..da404f9298 100644
--- a/packages/markups/src/blocks/OrderedListBlock.js
+++ b/packages/markups/src/blocks/OrderedListBlock.js
@@ -1,9 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import InlineElements from '../elements/InlineElements';
+import { listStyles } from './blocks.styles';
const OrderedListBlock = ({ items }) => (
-
+
{items.map((item, index) => (
-
diff --git a/packages/markups/src/blocks/UnOrderedListBlock.js b/packages/markups/src/blocks/UnOrderedListBlock.js
index a0f5f9ecf2..4ac0130beb 100644
--- a/packages/markups/src/blocks/UnOrderedListBlock.js
+++ b/packages/markups/src/blocks/UnOrderedListBlock.js
@@ -1,9 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import InlineElements from '../elements/InlineElements';
+import { listStyles } from './blocks.styles';
const UnOrderedListBlock = ({ items }) => (
-
+
{items.map((item, index) => (
-
diff --git a/packages/markups/src/blocks/blocks.styles.js b/packages/markups/src/blocks/blocks.styles.js
index 63765585de..87d2c85dbb 100644
--- a/packages/markups/src/blocks/blocks.styles.js
+++ b/packages/markups/src/blocks/blocks.styles.js
@@ -9,3 +9,8 @@ export const TaskListBlockStyles = {
gap: 0.5em;
`,
};
+
+export const listStyles = css`
+ padding-left: 1.5em;
+ margin: 0;
+`;
diff --git a/packages/react/src/lib/insertListPrefix.js b/packages/react/src/lib/insertListPrefix.js
new file mode 100644
index 0000000000..68f549aed4
--- /dev/null
+++ b/packages/react/src/lib/insertListPrefix.js
@@ -0,0 +1,20 @@
+const insertListPrefix = (messageRef, listPrefix) => {
+ const input = messageRef.current;
+ if (!input) return;
+
+ const { selectionStart, value } = input;
+ const before = value.slice(0, selectionStart);
+ const after = value.slice(selectionStart);
+
+ const needsNewline = before.length > 0 && !before.endsWith('\n');
+ const prefix = listPrefix(1);
+ const insert = (needsNewline ? '\n' : '') + prefix;
+
+ input.value = before + insert + after;
+ const cursorPos = selectionStart + insert.length;
+ input.selectionStart = cursorPos;
+ input.selectionEnd = cursorPos;
+ input.focus();
+};
+
+export default insertListPrefix;
diff --git a/packages/react/src/lib/textFormat.js b/packages/react/src/lib/textFormat.js
index a1978c0033..c69d6da5cc 100644
--- a/packages/react/src/lib/textFormat.js
+++ b/packages/react/src/lib/textFormat.js
@@ -8,4 +8,16 @@ export const formatter = [
pattern: '```\n{{text}}\n```',
tooltip: 'Multi-line code',
},
+ {
+ name: 'list-numbers',
+ type: 'list',
+ listPrefix: (n) => `${n}. `,
+ tooltip: 'Ordered list',
+ },
+ {
+ name: 'list-bullets',
+ type: 'list',
+ listPrefix: () => '- ',
+ tooltip: 'Unordered list',
+ },
];
diff --git a/packages/react/src/views/ChatInput/ChatInput.js b/packages/react/src/views/ChatInput/ChatInput.js
index e753b689ae..2e43e86673 100644
--- a/packages/react/src/views/ChatInput/ChatInput.js
+++ b/packages/react/src/views/ChatInput/ChatInput.js
@@ -449,10 +449,51 @@ const ChatInput = ({ scrollToBottom, clearUnreadDividerRef }) => {
formatSelection(messageRef, '*{{text}}*');
break;
}
- case (e.ctrlKey || e.metaKey || e.shiftKey) && e.code === 'Enter':
+ case (e.ctrlKey || e.metaKey || e.shiftKey) && e.code === 'Enter': {
e.preventDefault();
- handleNewLine(e);
+ const input = messageRef.current;
+ const { value, selectionStart } = input;
+ const textBefore = value.slice(0, selectionStart);
+ const currentLine = textBefore.split('\n').pop();
+ // Match numbered list: "1. ", "2. ", etc.
+ const olMatch = currentLine.match(/^(\d+)\.\s/);
+ // Match bullet list: "- "
+ const ulMatch = currentLine.match(/^-\s/);
+
+ if (olMatch && currentLine.trim() === `${olMatch[1]}.`) {
+ const lineStart = selectionStart - currentLine.length;
+ const rest = value.slice(selectionStart);
+ input.value = value.slice(0, lineStart) + rest;
+ input.selectionStart = lineStart;
+ input.selectionEnd = lineStart;
+ handleNewLine(e, false);
+ } else if (ulMatch && currentLine.trim() === '-') {
+ const lineStart = selectionStart - currentLine.length;
+ const rest = value.slice(selectionStart);
+ input.value = value.slice(0, lineStart) + rest;
+ input.selectionStart = lineStart;
+ input.selectionEnd = lineStart;
+ handleNewLine(e, false);
+ } else if (olMatch) {
+ const nextNum = parseInt(olMatch[1], 10) + 1;
+ const prefix = `\n${nextNum}. `;
+ const after = value.slice(selectionStart);
+ input.value = textBefore + prefix + after;
+ input.selectionStart = selectionStart + prefix.length;
+ input.selectionEnd = selectionStart + prefix.length;
+ handleNewLine(e, false);
+ } else if (ulMatch) {
+ const prefix = '\n- ';
+ const after = value.slice(selectionStart);
+ input.value = textBefore + prefix + after;
+ input.selectionStart = selectionStart + prefix.length;
+ input.selectionEnd = selectionStart + prefix.length;
+ handleNewLine(e, false);
+ } else {
+ handleNewLine(e);
+ }
break;
+ }
case e.code === 'Escape':
if (editMessage.msg || editMessage.attachments) {
e.preventDefault();
diff --git a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js
index 5d8c20a600..dfa72f50a6 100644
--- a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js
+++ b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js
@@ -15,6 +15,7 @@ import AudioMessageRecorder from './AudioMessageRecorder';
import VideoMessageRecorder from './VideoMessageRecoder';
import { getChatInputFormattingToolbarStyles } from './ChatInput.styles';
import formatSelection from '../../lib/formatSelection';
+import insertListPrefix from '../../lib/insertListPrefix';
import InsertLinkToolBox from './InsertLinkToolBox';
const ChatInputFormattingToolbar = ({
@@ -23,7 +24,15 @@ const ChatInputFormattingToolbar = ({
triggerButton,
optionConfig = {
surfaceItems: ['emoji', 'formatter', 'link', 'audio', 'video', 'file'],
- formatters: ['bold', 'italic', 'strike', 'code', 'multiline'],
+ formatters: [
+ 'bold',
+ 'italic',
+ 'strike',
+ 'code',
+ 'multiline',
+ 'list-numbers',
+ 'list-bullets',
+ ],
smallScreenSurfaceItems: ['emoji', 'video', 'audio', 'file'],
popOverItems: ['formatter', 'link'],
},
@@ -55,7 +64,11 @@ const ChatInputFormattingToolbar = ({
inputRef.current.click();
};
const handleFormatterClick = (item) => {
- formatSelection(messageRef, item.pattern);
+ if (item.type === 'list') {
+ insertListPrefix(messageRef, item.listPrefix);
+ } else {
+ formatSelection(messageRef, item.pattern);
+ }
setPopoverOpen(false);
};
const handleEmojiClick = (emojiEvent) => {
@@ -224,7 +237,7 @@ const ChatInputFormattingToolbar = ({
ghost
onClick={() => {
if (isRecordingMessage) return;
- formatSelection(messageRef, item.pattern);
+ handleFormatterClick(item);
}}
>
- formatSelection(messageRef, itemInFormatter.pattern)
- }
+ onClick={() => handleFormatterClick(itemInFormatter)}
>
(
+
+);
+
+export default ListBullets;
diff --git a/packages/ui-elements/src/components/Icon/icons/ListNumbers.js b/packages/ui-elements/src/components/Icon/icons/ListNumbers.js
new file mode 100644
index 0000000000..80ea33f4c5
--- /dev/null
+++ b/packages/ui-elements/src/components/Icon/icons/ListNumbers.js
@@ -0,0 +1,14 @@
+import React from 'react';
+
+const ListNumbers = (props) => (
+
+);
+
+export default ListNumbers;
diff --git a/packages/ui-elements/src/components/Icon/icons/index.js b/packages/ui-elements/src/components/Icon/icons/index.js
index 1f416a020a..8d2b920731 100644
--- a/packages/ui-elements/src/components/Icon/icons/index.js
+++ b/packages/ui-elements/src/components/Icon/icons/index.js
@@ -65,6 +65,8 @@ import Arc from './Arc';
import Avatar from './Avatar';
import FormatText from './FormatText';
import Cog from './Cog';
+import ListNumbers from './ListNumbers';
+import ListBullets from './ListBullets';
import Team from './Team';
const icons = {
@@ -136,6 +138,8 @@ const icons = {
avatar: Avatar,
'format-text': FormatText,
cog: Cog,
+ 'list-numbers': ListNumbers,
+ 'list-bullets': ListBullets,
};
export default icons;