Jelajahi Sumber

Merge pull request #23669 from overleaf/mj-ide-chat-look

[web] Update chat in editor redesign

GitOrigin-RevId: 79c79eb9c774fbaa1a5a1e15386b629cc03239b3
David 1 tahun lalu
induk
melakukan
fc19e1d34a

+ 3 - 0
services/web/frontend/extracted-translations.json

@@ -259,6 +259,7 @@
   "code_editor_tooltip_message": "",
   "code_editor_tooltip_message": "",
   "code_editor_tooltip_title": "",
   "code_editor_tooltip_title": "",
   "collaborate_online_and_offline": "",
   "collaborate_online_and_offline": "",
+  "collaborator_chat": "",
   "collabs_per_proj": "",
   "collabs_per_proj": "",
   "collabs_per_proj_single": "",
   "collabs_per_proj_single": "",
   "collapse": "",
   "collapse": "",
@@ -1044,6 +1045,7 @@
   "no_libraries_selected": "",
   "no_libraries_selected": "",
   "no_members": "",
   "no_members": "",
   "no_messages": "",
   "no_messages": "",
+  "no_messages_yet": "",
   "no_new_commits_in_github": "",
   "no_new_commits_in_github": "",
   "no_one_has_commented_or_left_any_suggestions_yet": "",
   "no_one_has_commented_or_left_any_suggestions_yet": "",
   "no_other_projects_found": "",
   "no_other_projects_found": "",
@@ -1585,6 +1587,7 @@
   "start_by_fixing_the_first_error_in_your_doc": "",
   "start_by_fixing_the_first_error_in_your_doc": "",
   "start_free_trial": "",
   "start_free_trial": "",
   "start_free_trial_without_exclamation": "",
   "start_free_trial_without_exclamation": "",
+  "start_the_conversation_by_saying_hello_or_sharing_an_update": "",
   "start_typing_find_your_company": "",
   "start_typing_find_your_company": "",
   "start_typing_find_your_organization": "",
   "start_typing_find_your_organization": "",
   "start_typing_find_your_university": "",
   "start_typing_find_your_university": "",

+ 9 - 3
services/web/frontend/js/features/chat/components/message-list.tsx

@@ -1,6 +1,7 @@
 import moment from 'moment'
 import moment from 'moment'
 import Message from './message'
 import Message from './message'
 import type { Message as MessageType } from '@/features/chat/context/chat-context'
 import type { Message as MessageType } from '@/features/chat/context/chat-context'
+import MessageRedesign from '@/features/ide-redesign/components/chat/message'
 import { useUserContext } from '@/shared/context/user-context'
 import { useUserContext } from '@/shared/context/user-context'
 
 
 const FIVE_MINUTES = 5 * 60 * 1000
 const FIVE_MINUTES = 5 * 60 * 1000
@@ -16,11 +17,16 @@ function formatTimestamp(date: moment.MomentInput) {
 interface MessageListProps {
 interface MessageListProps {
   messages: MessageType[]
   messages: MessageType[]
   resetUnreadMessages(...args: unknown[]): unknown
   resetUnreadMessages(...args: unknown[]): unknown
+  newDesign?: boolean
 }
 }
 
 
-function MessageList({ messages, resetUnreadMessages }: MessageListProps) {
+function MessageList({
+  messages,
+  resetUnreadMessages,
+  newDesign,
+}: MessageListProps) {
   const user = useUserContext()
   const user = useUserContext()
-
+  const MessageComponent = newDesign ? MessageRedesign : Message
   function shouldRenderDate(messageIndex: number) {
   function shouldRenderDate(messageIndex: number) {
     if (messageIndex === 0) {
     if (messageIndex === 0) {
       return true
       return true
@@ -58,7 +64,7 @@ function MessageList({ messages, resetUnreadMessages }: MessageListProps) {
               </time>
               </time>
             </div>
             </div>
           )}
           )}
-          <Message
+          <MessageComponent
             message={message}
             message={message}
             fromSelf={message.user ? message.user.id === user.id : false}
             fromSelf={message.user ? message.user.id === user.id : false}
           />
           />

+ 1 - 1
services/web/frontend/js/features/chat/components/message.tsx

@@ -3,7 +3,7 @@ import MessageContent from './message-content'
 import type { Message as MessageType } from '@/features/chat/context/chat-context'
 import type { Message as MessageType } from '@/features/chat/context/chat-context'
 import { User } from '../../../../../types/user'
 import { User } from '../../../../../types/user'
 
 
-interface MessageProps {
+export interface MessageProps {
   message: MessageType
   message: MessageType
   fromSelf: boolean
   fromSelf: boolean
 }
 }

+ 46 - 34
services/web/frontend/js/features/ide-redesign/components/chat.tsx → services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx

@@ -9,8 +9,10 @@ import MaterialIcon from '@/shared/components/material-icon'
 import { useUserContext } from '@/shared/context/user-context'
 import { useUserContext } from '@/shared/context/user-context'
 import { lazy, Suspense, useEffect } from 'react'
 import { lazy, Suspense, useEffect } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
+import classNames from 'classnames'
+import { RailPanelHeader } from '../rail'
 
 
-const MessageList = lazy(() => import('../../chat/components/message-list'))
+const MessageList = lazy(() => import('../../../chat/components/message-list'))
 
 
 export const ChatIndicator = () => {
 export const ChatIndicator = () => {
   const { unreadMessageCount } = useChatContext()
   const { unreadMessageCount } = useChatContext()
@@ -24,7 +26,6 @@ const Loading = () => <FullSizeLoadingSpinner delay={500} className="pt-4" />
 
 
 export const ChatPane = () => {
 export const ChatPane = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
-
   const user = useUserContext()
   const user = useUserContext()
   const {
   const {
     status,
     status,
@@ -65,44 +66,55 @@ export const ChatPane = () => {
   }
   }
 
 
   return (
   return (
-    <aside className="chat">
-      <InfiniteScroll
-        atEnd={atEnd}
-        className="messages"
-        fetchData={loadMoreMessages}
-        isLoading={status === 'pending'}
-        itemCount={messageContentCount}
-      >
-        <div>
-          <h2 className="visually-hidden">{t('chat')}</h2>
-          <Suspense fallback={<Loading />}>
-            {status === 'pending' && <Loading />}
-            {shouldDisplayPlaceholder && <Placeholder />}
-            <MessageList
-              messages={messages}
-              resetUnreadMessages={markMessagesAsRead}
-            />
-          </Suspense>
-        </div>
-      </InfiniteScroll>
-      <MessageInput
-        resetUnreadMessages={markMessagesAsRead}
-        sendMessage={sendMessage}
-      />
-    </aside>
+    <div className="chat-panel">
+      <RailPanelHeader title={t('collaborator_chat')} />
+      <div className="chat-wrapper">
+        <aside className="chat">
+          <InfiniteScroll
+            atEnd={atEnd}
+            className="messages"
+            fetchData={loadMoreMessages}
+            isLoading={status === 'pending'}
+            itemCount={messageContentCount}
+          >
+            <div className={classNames({ 'h-100': shouldDisplayPlaceholder })}>
+              <h2 className="visually-hidden">{t('chat')}</h2>
+              <Suspense fallback={<Loading />}>
+                {status === 'pending' && <Loading />}
+                {shouldDisplayPlaceholder && <Placeholder />}
+                <MessageList
+                  messages={messages}
+                  resetUnreadMessages={markMessagesAsRead}
+                  newDesign
+                />
+              </Suspense>
+            </div>
+          </InfiniteScroll>
+          <MessageInput
+            resetUnreadMessages={markMessagesAsRead}
+            sendMessage={sendMessage}
+          />
+        </aside>
+      </div>
+    </div>
   )
   )
 }
 }
 
 
 function Placeholder() {
 function Placeholder() {
   const { t } = useTranslation()
   const { t } = useTranslation()
   return (
   return (
-    <>
-      <div className="no-messages text-center small">{t('no_messages')}</div>
-      <div className="first-message text-center">
-        {t('send_first_message')}
-        <br />
-        <MaterialIcon type="arrow_downward" />
+    <div className="chat-empty-state-placeholder">
+      <div>
+        <span className="chat-empty-state-icon">
+          <MaterialIcon type="forum" />
+        </span>
+      </div>
+      <div>
+        <div className="chat-empty-state-title">{t('no_messages_yet')}</div>
+        <div className="chat-empty-state-body">
+          {t('start_the_conversation_by_saying_hello_or_sharing_an_update')}
+        </div>
       </div>
       </div>
-    </>
+    </div>
   )
   )
 }
 }

+ 60 - 0
services/web/frontend/js/features/ide-redesign/components/chat/message.tsx

@@ -0,0 +1,60 @@
+import { MessageProps } from '@/features/chat/components/message'
+import { User } from '../../../../../../types/user'
+import { getHueForUserId } from '@/shared/utils/colors'
+import MessageContent from '@/features/chat/components/message-content'
+import classNames from 'classnames'
+
+function hue(user?: User) {
+  return user ? getHueForUserId(user.id) : 0
+}
+
+function getAvatarStyle(user?: User) {
+  return {
+    borderColor: `hsl(${hue(user)}, 85%, 40%)`,
+    backgroundColor: `hsl(${hue(user)}, 85%, 40%`,
+  }
+}
+
+function Message({ message, fromSelf }: MessageProps) {
+  return (
+    <div className="chat-message-redesign">
+      <div className="message-row">
+        <div className="message-avatar-placeholder" />
+        {!fromSelf && (
+          <div className="message-author">
+            <span>{message.user.first_name || message.user.email}</span>
+          </div>
+        )}
+      </div>
+      {message.contents.map((content, index) => (
+        <div key={index} className="message-row">
+          <>
+            {!fromSelf && index === message.contents.length - 1 ? (
+              <div className="message-avatar">
+                <div className="avatar" style={getAvatarStyle(message.user)}>
+                  {message.user.first_name?.charAt(0) ||
+                    message.user.email.charAt(0)}
+                </div>
+              </div>
+            ) : (
+              <div className="message-avatar-placeholder" />
+            )}
+            <div
+              className={classNames('message-container', {
+                'message-from-self': fromSelf,
+                'first-row-in-message': index === 0,
+                'last-row-in-message': index === message.contents.length - 1,
+              })}
+            >
+              <div className="message-content">
+                <MessageContent content={content} />
+              </div>
+            </div>
+          </>
+        </div>
+      ))}
+    </div>
+  )
+}
+
+export default Message

+ 4 - 10
services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx

@@ -1,24 +1,18 @@
 import { ElementType } from 'react'
 import { ElementType } from 'react'
 import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
 import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
-import MaterialIcon from '@/shared/components/material-icon'
-import OlButton from '@/features/ui/components/ol/ol-button'
-import { useRailContext } from '../../contexts/rail-context'
+import { RailPanelHeader } from '../rail'
+import { useTranslation } from 'react-i18next'
 
 
 const integrationPanelComponents = importOverleafModules(
 const integrationPanelComponents = importOverleafModules(
   'integrationPanelComponents'
   'integrationPanelComponents'
 ) as { import: { default: ElementType }; path: string }[]
 ) as { import: { default: ElementType }; path: string }[]
 
 
 export default function IntegrationsPanel() {
 export default function IntegrationsPanel() {
-  const { handlePaneCollapse } = useRailContext()
+  const { t } = useTranslation()
 
 
   return (
   return (
     <div className="integrations-panel">
     <div className="integrations-panel">
-      <header className="integrations-panel-header">
-        <h4 className="integrations-panel-title">Integrations</h4>
-        <OlButton onClick={handlePaneCollapse} variant="ghost" size="sm">
-          <MaterialIcon type="close" />
-        </OlButton>
-      </header>
+      <RailPanelHeader title={t('integrations')} />
       {integrationPanelComponents.map(
       {integrationPanelComponents.map(
         ({ import: { default: Component }, path }) => (
         ({ import: { default: Component }, path }) => (
           <Component key={path} />
           <Component key={path} />

+ 15 - 2
services/web/frontend/js/features/ide-redesign/components/rail.tsx

@@ -1,4 +1,4 @@
-import { ReactElement, useCallback, useMemo } from 'react'
+import { FC, ReactElement, useCallback, useMemo } from 'react'
 import { Nav, NavLink, Tab, TabContainer } from 'react-bootstrap-5'
 import { Nav, NavLink, Tab, TabContainer } from 'react-bootstrap-5'
 import MaterialIcon, {
 import MaterialIcon, {
   AvailableUnfilledIcon,
   AvailableUnfilledIcon,
@@ -8,13 +8,14 @@ import { useLayoutContext } from '@/shared/context/layout-context'
 import { ErrorIndicator, ErrorPane } from './errors'
 import { ErrorIndicator, ErrorPane } from './errors'
 import { RailTabKey, useRailContext } from '../contexts/rail-context'
 import { RailTabKey, useRailContext } from '../contexts/rail-context'
 import FileTreeOutlinePanel from './file-tree-outline-panel'
 import FileTreeOutlinePanel from './file-tree-outline-panel'
-import { ChatIndicator, ChatPane } from './chat'
+import { ChatIndicator, ChatPane } from './chat/chat'
 import getMeta from '@/utils/meta'
 import getMeta from '@/utils/meta'
 import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
 import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
 import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
 import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import classNames from 'classnames'
 import classNames from 'classnames'
 import IntegrationsPanel from './integrations-panel/integrations-panel'
 import IntegrationsPanel from './integrations-panel/integrations-panel'
+import OLButton from '@/features/ui/components/ol/ol-button'
 
 
 type RailElement = {
 type RailElement = {
   icon: AvailableUnfilledIcon
   icon: AvailableUnfilledIcon
@@ -234,3 +235,15 @@ const RailActionElement = ({ action }: { action: RailAction }) => {
     )
     )
   }
   }
 }
 }
+
+export const RailPanelHeader: FC<{ title: string }> = ({ title }) => {
+  const { handlePaneCollapse } = useRailContext()
+  return (
+    <header className="rail-panel-header">
+      <h4 className="rail-panel-title">{title}</h4>
+      <OLButton onClick={handlePaneCollapse} variant="ghost" size="sm">
+        <MaterialIcon type="close" />
+      </OLButton>
+    </header>
+  )
+}

+ 0 - 13
services/web/frontend/stylesheets/bootstrap-5/components/integrations-panel.scss

@@ -3,19 +3,6 @@
   height: 100%;
   height: 100%;
 }
 }
 
 
-.integrations-panel-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: var(--spacing-03) var(--spacing-04);
-}
-
-.integrations-panel-title {
-  font-size: var(--font-size-02);
-  color: var(--content-primary);
-  margin-bottom: 0;
-}
-
 .integrations-panel-card-button {
 .integrations-panel-card-button {
   all: unset;
   all: unset;
   background-color: var(--white);
   background-color: var(--white);

+ 141 - 4
services/web/frontend/stylesheets/bootstrap-5/pages/editor/chat.scss

@@ -5,6 +5,8 @@
   --chat-new-message-bg: var(--neutral-70);
   --chat-new-message-bg: var(--neutral-70);
   --chat-new-message-textarea-color: var(--neutral-90);
   --chat-new-message-textarea-color: var(--neutral-90);
   --chat-new-message-textarea-bg: var(--neutral-20);
   --chat-new-message-textarea-bg: var(--neutral-20);
+  --chat-new-message-textarea-border: var(--editor-border-color);
+  --chat-new-message-border: var(--editor-border-color);
   --chat-message-date-color: var(--neutral-40);
   --chat-message-date-color: var(--neutral-40);
   --chat-message-name-color: var(--white);
   --chat-message-name-color: var(--white);
 }
 }
@@ -16,6 +18,8 @@
   --chat-new-message-bg: var(--neutral-10);
   --chat-new-message-bg: var(--neutral-10);
   --chat-new-message-textarea-color: var(--neutral-90);
   --chat-new-message-textarea-color: var(--neutral-90);
   --chat-new-message-textarea-bg: var(--white);
   --chat-new-message-textarea-bg: var(--white);
+  --chat-new-message-textarea-border: var(--editor-border-color);
+  --chat-new-message-border: var(--editor-border-color);
   --chat-message-date-color: var(--neutral-70);
   --chat-message-date-color: var(--neutral-70);
   --chat-message-name-color: var(--neutral-70);
   --chat-message-name-color: var(--neutral-70);
 }
 }
@@ -24,11 +28,14 @@
   --chat-bg: var(--white);
   --chat-bg: var(--white);
   --chat-color: var(--neutral-70);
   --chat-color: var(--neutral-70);
   --chat-instructions-color: var(--neutral-70);
   --chat-instructions-color: var(--neutral-70);
-  --chat-new-message-bg: var(--neutral-10);
+  --chat-new-message-bg: var(--white);
   --chat-new-message-textarea-color: var(--neutral-90);
   --chat-new-message-textarea-color: var(--neutral-90);
   --chat-new-message-textarea-bg: var(--white);
   --chat-new-message-textarea-bg: var(--white);
+  --chat-new-message-textarea-border: var(--editor-border-color);
+  --chat-new-message-border: var(--white);
   --chat-message-date-color: var(--neutral-70);
   --chat-message-date-color: var(--neutral-70);
   --chat-message-name-color: var(--neutral-70);
   --chat-message-name-color: var(--neutral-70);
+  --chat-date-align: center;
 }
 }
 
 
 .chat {
 .chat {
@@ -72,7 +79,7 @@
         font-size: var(--font-size-01);
         font-size: var(--font-size-01);
         color: var(--chat-message-date-color);
         color: var(--chat-message-date-color);
         margin-bottom: calc(var(--line-height-03) / 2);
         margin-bottom: calc(var(--line-height-03) / 2);
-        text-align: right;
+        text-align: var(--chat-date-align, right);
       }
       }
 
 
       .message-wrapper {
       .message-wrapper {
@@ -158,13 +165,13 @@
     height: $new-message-height;
     height: $new-message-height;
     background-color: var(--chat-new-message-bg);
     background-color: var(--chat-new-message-bg);
     padding: calc(var(--line-height-03) / 4);
     padding: calc(var(--line-height-03) / 4);
-    border-top: 1px solid var(--editor-border-color);
+    border-top: 1px solid var(--chat-new-message-border);
 
 
     textarea {
     textarea {
       overflow: auto;
       overflow: auto;
       resize: none;
       resize: none;
       border-radius: var(--border-radius-base);
       border-radius: var(--border-radius-base);
-      border: 1px solid var(--editor-border-color);
+      border: 1px solid var(--chat-new-message-textarea-border);
       height: 100%;
       height: 100%;
       width: 100%;
       width: 100%;
       color: var(--chat-new-message-textarea-color);
       color: var(--chat-new-message-textarea-color);
@@ -174,3 +181,133 @@
     }
     }
   }
   }
 }
 }
+
+.chat-empty-state-placeholder {
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  text-align: center;
+  height: 100%;
+  gap: var(--spacing-06);
+  padding: var(--spacing-02);
+
+  .chat-empty-state-icon {
+    padding: var(--spacing-08);
+    font-size: var(--font-size-08);
+    height: 80px;
+    width: 80px;
+    border-radius: 50%;
+    display: inline-block;
+    line-height: 32px;
+    background-color: var(--bg-light-secondary);
+
+    .material-symbols {
+      font-size: 32px;
+    }
+  }
+
+  .chat-empty-state-title {
+    font-size: var(--font-size-02);
+    line-height: var(--line-height-02);
+    font-weight: bold;
+  }
+
+  .chat-empty-state-body {
+    font-size: var(--font-size-02);
+    line-height: var(--line-height-02);
+    color: var(--content-secondary);
+  }
+}
+
+.chat-message-redesign {
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-01);
+
+  .message-row {
+    display: flex;
+    align-items: flex-end;
+    gap: var(--spacing-03);
+  }
+
+  .message-avatar,
+  .message-avatar-placeholder {
+    flex: 0 0 24px;
+  }
+
+  .message-avatar .avatar {
+    width: 24px;
+    height: 24px;
+    line-height: 24px;
+    border-radius: 50%;
+    text-align: center;
+    color: var(--white);
+    text-transform: uppercase;
+  }
+
+  .message-author,
+  .message-container {
+    flex: 1 1 auto;
+    max-width: calc(100% - 24px - var(--spacing-03));
+  }
+
+  .message-container {
+    display: flex;
+    justify-content: flex-start;
+  }
+
+  .message-author {
+    font-size: var(--font-size-01);
+    line-height: var(--line-height-01);
+  }
+
+  .message-content {
+    background-color: var(--bg-light-secondary);
+    border-radius: var(--border-radius-large);
+    padding: var(--spacing-03) var(--spacing-04);
+    width: fit-content;
+    max-width: 100%;
+    overflow-x: auto;
+
+    p {
+      margin: 0;
+    }
+  }
+
+  .message-container.message-from-self {
+    justify-content: flex-end;
+
+    .message-content {
+      background-color: var(--bg-accent-03);
+    }
+
+    &:not(.first-row-in-message) .message-content {
+      border-top-right-radius: var(--border-radius-base);
+    }
+
+    &:not(.last-row-in-message) .message-content {
+      border-bottom-right-radius: var(--border-radius-base);
+    }
+  }
+
+  .message-container:not(.message-from-self) {
+    &:not(.first-row-in-message) .message-content {
+      border-top-left-radius: var(--border-radius-base);
+    }
+
+    &:not(.last-row-in-message) .message-content {
+      border-bottom-left-radius: var(--border-radius-base);
+    }
+  }
+}
+
+.chat-panel {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .chat-wrapper {
+    flex: 1 1 auto;
+    position: relative;
+  }
+}

+ 14 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss

@@ -6,6 +6,20 @@
   --ide-rail-link-active-indicator-background: var(--neutral-90);
   --ide-rail-link-active-indicator-background: var(--neutral-90);
 }
 }
 
 
+.rail-panel-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: var(--spacing-03) var(--spacing-04);
+  background-color: var(--ide-rail-background);
+}
+
+.rail-panel-title {
+  font-size: var(--font-size-02);
+  color: var(--content-primary);
+  margin-bottom: 0;
+}
+
 .ide-rail-tab-button {
 .ide-rail-tab-button {
   border: 0;
   border: 0;
   background: none;
   background: none;

+ 3 - 0
services/web/locales/en.json

@@ -337,6 +337,7 @@
   "collaborate_online_and_offline": "Collaborate online and offline, using your own workflow",
   "collaborate_online_and_offline": "Collaborate online and offline, using your own workflow",
   "collaboration": "Collaboration",
   "collaboration": "Collaboration",
   "collaborator": "Collaborator",
   "collaborator": "Collaborator",
+  "collaborator_chat": "Collaborator chat",
   "collabratec_account_not_registered": "IEEE Collabratec™ account not registered. Please connect to Overleaf from IEEE Collabratec™ or log in with a different account.",
   "collabratec_account_not_registered": "IEEE Collabratec™ account not registered. Please connect to Overleaf from IEEE Collabratec™ or log in with a different account.",
   "collabs_per_proj": "__collabcount__ collaborators per project",
   "collabs_per_proj": "__collabcount__ collaborators per project",
   "collabs_per_proj_single": "__collabcount__ collaborator per project",
   "collabs_per_proj_single": "__collabcount__ collaborator per project",
@@ -1381,6 +1382,7 @@
   "no_libraries_selected": "No libraries selected",
   "no_libraries_selected": "No libraries selected",
   "no_members": "No members",
   "no_members": "No members",
   "no_messages": "No messages",
   "no_messages": "No messages",
+  "no_messages_yet": "No messages yet",
   "no_new_commits_in_github": "No new commits in GitHub since last merge.",
   "no_new_commits_in_github": "No new commits in GitHub since last merge.",
   "no_one_has_commented_or_left_any_suggestions_yet": "No one has commented or left any suggestions yet.",
   "no_one_has_commented_or_left_any_suggestions_yet": "No one has commented or left any suggestions yet.",
   "no_other_projects_found": "No other projects found, please create another project first",
   "no_other_projects_found": "No other projects found, please create another project first",
@@ -2067,6 +2069,7 @@
   "start_by_fixing_the_first_error_in_your_doc": "Start by fixing the first error in your doc to avoid problems later on.",
   "start_by_fixing_the_first_error_in_your_doc": "Start by fixing the first error in your doc to avoid problems later on.",
   "start_free_trial": "Start Free Trial!",
   "start_free_trial": "Start Free Trial!",
   "start_free_trial_without_exclamation": "Start Free Trial",
   "start_free_trial_without_exclamation": "Start Free Trial",
+  "start_the_conversation_by_saying_hello_or_sharing_an_update": "Start the conversation by saying hello or sharing an update",
   "start_typing_find_your_company": " Start typing to find your company",
   "start_typing_find_your_company": " Start typing to find your company",
   "start_typing_find_your_organization": "Start typing to find your organization",
   "start_typing_find_your_organization": "Start typing to find your organization",
   "start_typing_find_your_university": "Start typing to find your university",
   "start_typing_find_your_university": "Start typing to find your university",