From 9e8be31bdf75cd640ebc8b66de56f4810e1eaeb2 Mon Sep 17 00:00:00 2001
From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com>
Date: Thu, 29 Jun 2023 15:56:05 +0300
Subject: [PATCH] Merge pull request #13573 from
overleaf/ii-review-panel-migration-comment-entry
[web] Create comment entries
GitOrigin-RevId: 7f3fbe672d18d57a0f5e683e5456ea79ed295e2d
---
.../web/frontend/extracted-translations.json | 5 +
.../review-panel/current-file-container.tsx | 28 ++-
.../review-panel/entries/comment-entry.tsx | 192 +++++++++++++++++-
.../review-panel/entries/comment.tsx | 119 +++++++++++
.../review-panel/entries/entry-actions.tsx | 19 ++
.../hooks/use-angular-review-panel-state.ts | 56 ++++-
.../review-panel/types/review-panel-state.ts | 18 ++
.../components/auto-expanding-text-area.tsx | 73 +++++++
.../stylesheets/app/editor/review-panel.less | 14 ++
.../review-panel/review-panel.spec.tsx | 23 +++
services/web/types/review-panel/entry.ts | 7 +-
.../web/types/review-panel/review-panel.ts | 37 +++-
12 files changed, 579 insertions(+), 12 deletions(-)
create mode 100644 services/web/frontend/js/features/source-editor/components/review-panel/entries/comment.tsx
create mode 100644 services/web/frontend/js/features/source-editor/components/review-panel/entries/entry-actions.tsx
create mode 100644 services/web/frontend/js/shared/components/auto-expanding-text-area.tsx
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 282692528d..e2431ad7fc 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -58,6 +58,7 @@
"are_you_affiliated_with_an_institution": "",
"are_you_getting_an_undefined_control_sequence_error": "",
"are_you_still_at": "",
+ "are_you_sure": "",
"ascending": "",
"ask_proj_owner_to_upgrade_for_full_history": "",
"ask_proj_owner_to_upgrade_for_longer_compiles": "",
@@ -425,6 +426,7 @@
"history_view_a11y_description": "",
"history_view_all": "",
"history_view_labels": "",
+ "hit_enter_to_reply": "",
"hotkey_add_a_comment": "",
"hotkey_autocomplete_menu": "",
"hotkey_beginning_of_document": "",
@@ -621,6 +623,7 @@
"new_to_latex_look_at": "",
"newsletter": "",
"next_payment_of_x_collectected_on_y": "",
+ "no_comments": "",
"no_existing_password": "",
"no_folder": "",
"no_image_files_found": "",
@@ -797,11 +800,13 @@
"replace_from_computer": "",
"replace_from_project_files": "",
"replace_from_url": "",
+ "reply": "",
"repository_name": "",
"republish": "",
"resend": "",
"resend_confirmation_email": "",
"resending_confirmation_email": "",
+ "resolve": "",
"resolved_comments": "",
"restore_file": "",
"restoring": "",
diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx
index 8a9afb1107..6ecb25e170 100644
--- a/services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx
+++ b/services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx
@@ -1,3 +1,4 @@
+import { useMemo } from 'react'
import ChangeEntry from './entries/change-entry'
import AggregateChangeEntry from './entries/aggregate-change-entry'
import CommentEntry from './entries/comment-entry'
@@ -5,9 +6,12 @@ import AddCommentEntry from './entries/add-comment-entry'
import BulkActionsEntry from './entries/bulk-actions-entry'
import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context'
import useCodeMirrorContentHeight from '../../hooks/use-codemirror-content-height'
+import { ReviewPanelEntry } from '../../../../../../types/review-panel/entry'
+import { ThreadId } from '../../../../../../types/review-panel/review-panel'
function CurrentFileContainer() {
- const { entries, openDocId, permissions } = useReviewPanelValueContext()
+ const { commentThreads, entries, openDocId, permissions, loadingThreads } =
+ useReviewPanelValueContext()
const contentHeight = useCodeMirrorContentHeight()
console.log('Review panel got content height', contentHeight)
@@ -15,6 +19,12 @@ function CurrentFileContainer() {
const currentDocEntries =
openDocId && openDocId in entries ? entries[openDocId] : undefined
+ const objectEntries = useMemo(() => {
+ return Object.entries(currentDocEntries || {}) as Array<
+ [ThreadId, ReviewPanelEntry]
+ >
+ }, [currentDocEntries])
+
return (
- {currentDocEntries &&
- Object.entries(currentDocEntries).map(([id, entry]) => {
+ {openDocId &&
+ objectEntries.map(([id, entry]) => {
if (!entry.visible) {
return null
}
@@ -40,8 +50,16 @@ function CurrentFileContainer() {
return
}
- if (entry.type === 'comment') {
- return
+ if (entry.type === 'comment' && !loadingThreads) {
+ return (
+
+ )
}
if (entry.type === 'add-comment' && permissions.comment) {
diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/entries/comment-entry.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/entries/comment-entry.tsx
index dad228e191..4aed2535ef 100644
--- a/services/web/frontend/js/features/source-editor/components/review-panel/entries/comment-entry.tsx
+++ b/services/web/frontend/js/features/source-editor/components/review-panel/entries/comment-entry.tsx
@@ -1,7 +1,195 @@
+import { useState, useRef } from 'react'
+import { useTranslation } from 'react-i18next'
import EntryContainer from './entry-container'
+import Comment from './comment'
+import EntryActions from './entry-actions'
+import AutoExpandingTextArea, {
+ resetHeight,
+} from '../../../../../shared/components/auto-expanding-text-area'
+import Icon from '../../../../../shared/components/icon'
+import {
+ useReviewPanelUpdaterFnsContext,
+ useReviewPanelValueContext,
+} from '../../../context/review-panel/review-panel-context'
+import classnames from 'classnames'
+import { ReviewPanelCommentEntry } from '../../../../../../../types/review-panel/entry'
+import {
+ DocId,
+ ReviewPanelCommentThreads,
+ ThreadId,
+} from '../../../../../../../types/review-panel/review-panel'
-function CommentEntry() {
- return
Comment entry
+type CommentEntryProps = {
+ docId: DocId
+ entry: ReviewPanelCommentEntry
+ entryId: ThreadId
+ threads: ReviewPanelCommentThreads
+}
+
+function CommentEntry({ docId, entry, entryId, threads }: CommentEntryProps) {
+ const { t } = useTranslation()
+ const {
+ permissions,
+ gotoEntry,
+ toggleReviewPanel,
+ resolveComment,
+ submitReply,
+ handleLayoutChange,
+ } = useReviewPanelValueContext()
+ const { setEntryHover } = useReviewPanelUpdaterFnsContext()
+
+ const [replyContent, setReplyContent] = useState('')
+ const [animating, setAnimating] = useState(false)
+ const entryDivRef = useRef
(null)
+
+ const thread =
+ entry.thread_id in threads ? threads[entry.thread_id] : undefined
+
+ const handleEntryClick = (e: React.MouseEvent) => {
+ const target = e.target as Element
+ if (
+ [
+ 'rp-entry',
+ 'rp-comment-loaded',
+ 'rp-comment-content',
+ 'rp-comment-reply',
+ 'rp-entry-metadata',
+ ].some(className => [...target.classList].includes(className))
+ ) {
+ gotoEntry(docId, entry.offset)
+ }
+ }
+
+ const handleAnimateAndCallOnResolve = () => {
+ setAnimating(true)
+
+ if (entryDivRef.current) {
+ entryDivRef.current.style.top = '0'
+ }
+
+ setTimeout(() => {
+ resolveComment(docId, entryId)
+ }, 350)
+ }
+
+ const handleCommentReplyKeyPress = (
+ e: React.KeyboardEvent
+ ) => {
+ if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
+ e.preventDefault()
+
+ if (replyContent.length) {
+ ;(e.target as HTMLTextAreaElement).blur()
+ submitReply(entry, replyContent)
+ setReplyContent('')
+ resetHeight(e)
+ }
+ }
+ }
+
+ const handleOnReply = () => {
+ if (replyContent.length) {
+ submitReply(entry, replyContent)
+ setReplyContent('')
+ }
+ }
+
+ if (!thread) {
+ return null
+ }
+
+ return (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
+ setEntryHover(true)}
+ onMouseLeave={() => setEntryHover(false)}
+ onClick={handleEntryClick}
+ >
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
+
+
+
+
+ {!thread.submitting && (!thread || thread.messages.length === 0) && (
+
{t('no_comments')}
+ )}
+
+ {thread.messages.map(comment => (
+
+ ))}
+
+ {thread.submitting && (
+
+
+
+ )}
+ {permissions.comment && (
+
+
setReplyContent(e.target.value)}
+ onKeyPress={handleCommentReplyKeyPress}
+ onClick={e => e.stopPropagation()}
+ onResize={handleLayoutChange}
+ placeholder={t('hit_enter_to_reply')}
+ value={replyContent}
+ />
+
+ )}
+
+ {permissions.comment && permissions.write && (
+
+ {t('resolve')}
+
+ )}
+ {permissions.comment && (
+
+ {t('reply')}
+
+ )}
+
+
+
+
+ )
}
export default CommentEntry
diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/entries/comment.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/entries/comment.tsx
new file mode 100644
index 0000000000..5553f8672a
--- /dev/null
+++ b/services/web/frontend/js/features/source-editor/components/review-panel/entries/comment.tsx
@@ -0,0 +1,119 @@
+import { useTranslation } from 'react-i18next'
+import { useState } from 'react'
+import AutoExpandingTextArea from '../../../../../shared/components/auto-expanding-text-area'
+import { formatTime } from '../../../../utils/format-date'
+import { useReviewPanelValueContext } from '../../../context/review-panel/review-panel-context'
+import {
+ ReviewPanelCommentThread,
+ ReviewPanelCommentThreadMessage,
+ ThreadId,
+} from '../../../../../../../types/review-panel/review-panel'
+
+type CommentProps = {
+ thread: ReviewPanelCommentThread
+ threadId: ThreadId
+ comment: ReviewPanelCommentThreadMessage
+}
+
+function Comment({ thread, threadId, comment }: CommentProps) {
+ const { t } = useTranslation()
+ const { deleteComment, handleLayoutChange, saveEdit } =
+ useReviewPanelValueContext()
+ const [deleting, setDeleting] = useState(false)
+ const [editing, setEditing] = useState(false)
+
+ const handleConfirmDelete = () => {
+ setDeleting(true)
+ handleLayoutChange()
+ }
+
+ const handleDoDelete = () => {
+ setDeleting(false)
+ deleteComment(threadId, comment.id)
+ handleLayoutChange()
+ }
+
+ const handleCancelDelete = () => {
+ setDeleting(false)
+ handleLayoutChange()
+ }
+
+ const handleStartEditing = () => {
+ setEditing(true)
+ handleLayoutChange()
+ }
+
+ const handleSaveEditOnEnter = (
+ e: React.KeyboardEvent
+ ) => {
+ if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
+ e.preventDefault()
+ handleSaveEdit(e)
+ }
+ }
+
+ const handleSaveEdit = (
+ e:
+ | React.FocusEvent
+ | React.KeyboardEvent
+ ) => {
+ setEditing(false)
+ saveEdit(threadId, comment.id, (e.target as HTMLTextAreaElement).value)
+ }
+
+ return (
+
+
+ {editing ? (
+ e.stopPropagation()}
+ onResize={handleLayoutChange}
+ />
+ ) : (
+ <>
+
+ {comment.user.name}:
+
+
+ {comment.content}
+ >
+ )}
+
+ {!editing && (
+
+ {!deleting && formatTime(comment.timestamp, 'MMM d, y h:mm a')}
+ {comment.user.isSelf && !deleting && (
+
+ •
+
+ {thread.messages.length > 1 && (
+ <>
+ •
+
+ >
+ )}
+
+ )}
+ {comment.user.isSelf && deleting && (
+
+ {t('are_you_sure')} •
+
+ •
+
+
+ )}
+
+ )}
+
+ )
+}
+
+export default Comment
diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/entries/entry-actions.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/entries/entry-actions.tsx
new file mode 100644
index 0000000000..270214d417
--- /dev/null
+++ b/services/web/frontend/js/features/source-editor/components/review-panel/entries/entry-actions.tsx
@@ -0,0 +1,19 @@
+import classnames from 'classnames'
+
+function EntryActions({
+ className,
+ ...rest
+}: React.ComponentPropsWithoutRef<'div'>) {
+ return
+}
+
+EntryActions.Button = function EntryActionsButton({
+ className,
+ ...rest
+}: React.ComponentPropsWithoutRef<'button'>) {
+ return (
+
+ )
+}
+
+export default EntryActions
diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts
index 646d85d8fc..bebd736d2a 100644
--- a/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts
+++ b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts
@@ -1,20 +1,30 @@
-import { useMemo, useCallback } from 'react'
+import { useState, useMemo, useCallback } from 'react'
import useScopeValue from '../../../../../shared/hooks/use-scope-value'
+import useScopeEventEmitter from '../../../../../shared/hooks/use-scope-event-emitter'
import { ReviewPanelState } from '../types/review-panel-state'
import { sendMB } from '../../../../../infrastructure/event-tracking'
import * as ReviewPanel from '../types/review-panel-state'
import { SubView } from '../../../../../../../types/review-panel/review-panel'
+import { ReviewPanelCommentEntry } from '../../../../../../../types/review-panel/entry'
function useAngularReviewPanelState(): ReviewPanelState {
+ const emitLayoutChange = useScopeEventEmitter('review-panel:layout', false)
+
const [subView, setSubView] = useScopeValue>(
'reviewPanel.subView'
)
const [collapsed, setCollapsed] = useScopeValue<
ReviewPanel.Value<'collapsed'>
>('reviewPanel.overview.docsCollapsedState')
+ const [commentThreads] = useScopeValue>(
+ 'reviewPanel.commentThreads',
+ true
+ )
const [entries] = useScopeValue>(
'reviewPanel.entries'
)
+ const [loadingThreads] =
+ useScopeValue>('loadingThreads')
const [permissions] =
useScopeValue>('permissions')
@@ -50,6 +60,14 @@ function useAngularReviewPanelState(): ReviewPanelState {
const [trackChangesForGuestsAvailable] = useScopeValue<
ReviewPanel.Value<'trackChangesForGuestsAvailable'>
>('reviewPanel.trackChangesForGuestsAvailable')
+ const [resolveComment] =
+ useScopeValue>('resolveComment')
+ const [deleteComment] =
+ useScopeValue>('deleteComment')
+ const [gotoEntry] = useScopeValue>('gotoEntry')
+ const [saveEdit] = useScopeValue>('saveEdit')
+ const [submitReplyAngular] =
+ useScopeValue<(entry: ReviewPanelCommentEntry) => void>('submitReply')
const [formattedProjectMembers] = useScopeValue<
ReviewPanel.Value<'formattedProjectMembers'>
@@ -66,12 +84,36 @@ function useAngularReviewPanelState(): ReviewPanelState {
[setSubView]
)
+ const handleLayoutChange = useCallback(() => {
+ window.requestAnimationFrame(() => {
+ emitLayoutChange()
+ })
+ }, [emitLayoutChange])
+
+ const submitReply = useCallback(
+ (entry: ReviewPanelCommentEntry, replyContent: string) => {
+ submitReplyAngular({ ...entry, replyContent })
+ },
+ [submitReplyAngular]
+ )
+
+ const [entryHover, setEntryHover] = useState(false)
+
const values = useMemo(
() => ({
collapsed,
+ commentThreads,
+ deleteComment,
entries,
+ entryHover,
+ gotoEntry,
+ handleLayoutChange,
+ loadingThreads,
permissions,
+ resolveComment,
+ saveEdit,
shouldCollapse,
+ submitReply,
subView,
wantTrackChanges,
openDocId,
@@ -87,9 +129,18 @@ function useAngularReviewPanelState(): ReviewPanelState {
}),
[
collapsed,
+ commentThreads,
+ deleteComment,
entries,
+ entryHover,
+ gotoEntry,
+ handleLayoutChange,
+ loadingThreads,
permissions,
+ resolveComment,
+ saveEdit,
shouldCollapse,
+ submitReply,
subView,
wantTrackChanges,
openDocId,
@@ -108,10 +159,11 @@ function useAngularReviewPanelState(): ReviewPanelState {
const updaterFns = useMemo(
() => ({
handleSetSubview,
+ setEntryHover,
setCollapsed,
setShouldCollapse,
}),
- [handleSetSubview, setCollapsed, setShouldCollapse]
+ [handleSetSubview, setCollapsed, setEntryHover, setShouldCollapse]
)
return { values, updaterFns }
diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts b/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts
index 8ab8d040c9..04f5700769 100644
--- a/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts
+++ b/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts
@@ -1,16 +1,33 @@
import {
+ CommentId,
DocId,
+ ReviewPanelCommentThreads,
ReviewPanelEntries,
ReviewPanelPermissions,
SubView,
+ ThreadId,
} from '../../../../../../../types/review-panel/review-panel'
+import { ReviewPanelCommentEntry } from '../../../../../../../types/review-panel/entry'
export interface ReviewPanelState {
values: {
collapsed: Record
+ commentThreads: ReviewPanelCommentThreads
+ deleteComment: (threadId: ThreadId, commentId: CommentId) => void
entries: ReviewPanelEntries
+ entryHover: boolean
+ gotoEntry: (docId: DocId, entryOffset: number) => void
+ handleLayoutChange: () => void
+ loadingThreads: boolean
permissions: ReviewPanelPermissions
+ resolveComment: (docId: DocId, entryId: ThreadId) => void
+ saveEdit: (
+ threadId: ThreadId,
+ commentId: CommentId,
+ content: string
+ ) => void
shouldCollapse: boolean
+ submitReply: (entry: ReviewPanelCommentEntry, replyContent: string) => void
subView: SubView
wantTrackChanges: boolean
openDocId: DocId | null
@@ -32,6 +49,7 @@ export interface ReviewPanelState {
}
updaterFns: {
handleSetSubview: (subView: SubView) => void
+ setEntryHover: React.Dispatch>
setCollapsed: React.Dispatch<
React.SetStateAction
>
diff --git a/services/web/frontend/js/shared/components/auto-expanding-text-area.tsx b/services/web/frontend/js/shared/components/auto-expanding-text-area.tsx
new file mode 100644
index 0000000000..995008b663
--- /dev/null
+++ b/services/web/frontend/js/shared/components/auto-expanding-text-area.tsx
@@ -0,0 +1,73 @@
+import { useEffect, useRef } from 'react'
+import { callFnsInSequence } from '../../utils/functions'
+import { MergeAndOverride } from '../../../../types/utils'
+
+export const resetHeight = (
+ e:
+ | React.ChangeEvent
+ | React.KeyboardEvent
+) => {
+ const el = e.target as HTMLTextAreaElement
+
+ window.requestAnimationFrame(() => {
+ const curHeight = el.offsetHeight
+ const fitHeight = el.scrollHeight
+ // clear height if text area is empty
+ if (!el.value.length) {
+ el.style.removeProperty('height')
+ }
+ // otherwise expand to fit text
+ else if (fitHeight > curHeight) {
+ el.style.height = `${fitHeight}px`
+ }
+ })
+}
+
+type AutoExpandingTextAreaProps = MergeAndOverride<
+ React.ComponentProps<'textarea'>,
+ {
+ onResize?: () => void
+ }
+>
+
+function AutoExpandingTextArea({
+ onChange,
+ onResize,
+ ...rest
+}: AutoExpandingTextAreaProps) {
+ const ref = useRef(null)
+
+ useEffect(() => {
+ if (!ref.current || !onResize || !('ResizeObserver' in window)) {
+ return
+ }
+
+ let isFirstResize = true
+
+ const resizeObserver = new ResizeObserver(() => {
+ // Ignore the resize that is triggered when the element is first
+ // inserted into the DOM
+ if (isFirstResize) {
+ isFirstResize = false
+ } else {
+ onResize()
+ }
+ })
+
+ resizeObserver.observe(ref.current)
+
+ return () => {
+ resizeObserver.disconnect()
+ }
+ }, [onResize])
+
+ return (
+
+ )
+}
+
+export default AutoExpandingTextArea
diff --git a/services/web/frontend/stylesheets/app/editor/review-panel.less b/services/web/frontend/stylesheets/app/editor/review-panel.less
index c8981088d4..e1f3b7e2b5 100644
--- a/services/web/frontend/stylesheets/app/editor/review-panel.less
+++ b/services/web/frontend/stylesheets/app/editor/review-panel.less
@@ -1294,4 +1294,18 @@ button when (@is-overleaf-light = true) {
height: 0;
transition: height 150ms;
}
+
+ .rp-entry-metadata {
+ button {
+ padding: 0;
+ border: 0;
+ background-color: transparent;
+ color: @ol-blue;
+
+ &:hover,
+ &:focus {
+ text-decoration: underline;
+ }
+ }
+ }
}
diff --git a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
index dbed086cfa..fcaa331267 100644
--- a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
+++ b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
@@ -166,4 +166,27 @@ describe('', function () {
})
})
})
+
+ describe('comment entries', function () {
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('shows threads and comments', function () {})
+
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('edits comment', function () {})
+
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('deletes comment', function () {})
+
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('cancels comment editing', function () {})
+
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('cancels comment deletion', function () {})
+
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('adds new comment (replies) to a thread', function () {})
+
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('resolves comment', function () {})
+ })
})
diff --git a/services/web/types/review-panel/entry.ts b/services/web/types/review-panel/entry.ts
index f695b582c2..800c559c27 100644
--- a/services/web/types/review-panel/entry.ts
+++ b/services/web/types/review-panel/entry.ts
@@ -1,3 +1,5 @@
+import { ThreadId } from './review-panel'
+
interface ReviewPanelEntryScreenPos {
y: number
height: number
@@ -8,14 +10,15 @@ interface ReviewPanelBaseEntry {
visible: boolean
}
-interface ReviewPanelCommentEntry extends ReviewPanelBaseEntry {
+export interface ReviewPanelCommentEntry extends ReviewPanelBaseEntry {
type: 'comment'
content: string
entry_ids: string[]
focused: boolean
offset: number
screenPos: ReviewPanelEntryScreenPos
- thread_id: string
+ thread_id: ThreadId
+ replyContent?: string // angular specific
}
interface ReviewPanelInsertEntry extends ReviewPanelBaseEntry {
diff --git a/services/web/types/review-panel/review-panel.ts b/services/web/types/review-panel/review-panel.ts
index 8465bbe307..858c47d8a6 100644
--- a/services/web/types/review-panel/review-panel.ts
+++ b/services/web/types/review-panel/review-panel.ts
@@ -10,7 +10,42 @@ export interface ReviewPanelPermissions {
comment: boolean
}
-export type ReviewPanelDocEntries = Record
+export type ThreadId = Brand
+type ReviewPanelDocEntries = Record
export type DocId = Brand
export type ReviewPanelEntries = Record
+
+type UserId = Brand
+
+interface ReviewPanelUser {
+ avatar_text: string
+ email: string
+ hue: number
+ id: UserId
+ isSelf: boolean
+ name: string
+}
+
+export type CommentId = Brand
+export interface ReviewPanelCommentThreadMessage {
+ content: string
+ id: CommentId
+ timestamp: number
+ user: ReviewPanelUser
+ user_id: UserId
+}
+
+export interface ReviewPanelCommentThread {
+ messages: Array
+ // resolved: boolean
+ // resolved_at: number
+ // resolved_by_user_id: string
+ // resolved_by_user: ReviewPanelUser
+ submitting?: boolean // angular specific (to be made into a local state)
+}
+
+export type ReviewPanelCommentThreads = Record<
+ ThreadId,
+ ReviewPanelCommentThread
+>