Просмотр исходного кода

Merge pull request #13767 from overleaf/ii-review-panel-migration-in-editor-widgets

[web] Create in editor widgets

GitOrigin-RevId: 53dfb9935ee59bbdedc353aad5e5b19f389a513c
ilkin-overleaf 3 лет назад
Родитель
Сommit
84bbdf9772

+ 1 - 0
services/web/app/src/Features/Project/ProjectController.js

@@ -953,6 +953,7 @@ const ProjectController = {
               historyViewReact: historyViewAssignment.variant === 'react',
               isReviewPanelReact: reviewPanelAssignment.variant === 'react',
               showPersonalAccessToken,
+              hasTrackChangesFeature: Features.hasFeature('track-changes'),
             })
             timer.done()
           }

+ 1 - 0
services/web/app/views/project/editor/meta.pug

@@ -41,6 +41,7 @@ meta(name="ol-showCM6SwitchAwaySurvey", data-type="boolean" content=showCM6Switc
 meta(name="ol-richTextVariant" content=richTextVariant)
 meta(name="ol-showPersonalAccessToken", data-type="boolean" content=showPersonalAccessToken)
 meta(name="ol-isReviewPanelReact", data-type="boolean" content=isReviewPanelReact)
+meta(name="ol-hasTrackChangesFeature", data-type="boolean" content=hasTrackChangesFeature)
 if (richTextVariant === 'cm6')
 	meta(name="ol-mathJax3Path" content=mathJax3Path)
 

+ 5 - 0
services/web/frontend/js/features/source-editor/components/review-panel/add-comment-button.tsx

@@ -0,0 +1,5 @@
+function AddCommentButton(props: React.ComponentPropsWithoutRef<'button'>) {
+  return <button className="rp-add-comment-btn" {...props} />
+}
+
+export default AddCommentButton

+ 2 - 3
services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx

@@ -24,14 +24,13 @@ function CurrentFileContainer() {
     permissions,
     loadingThreads,
     users,
+    entryHover,
     nVisibleSelectedChanges: nChanges,
     toggleReviewPanel,
   } = useReviewPanelValueContext()
   const { setEntryHover } = useReviewPanelUpdaterFnsContext()
   const contentHeight = useCodeMirrorContentHeight()
 
-  console.log('Review panel got content height', contentHeight)
-
   const currentDocEntries =
     openDocId && openDocId in entries ? entries[openDocId] : undefined
 
@@ -42,7 +41,7 @@ function CurrentFileContainer() {
   }, [currentDocEntries])
 
   return (
-    <Container>
+    <Container classNames={{ 'rp-collapsed-displaying-entry': entryHover }}>
       <div className="review-panel-tools">
         <Toolbar />
         <Nav />

+ 110 - 0
services/web/frontend/js/features/source-editor/components/review-panel/editor-widgets/editor-widgets.tsx

@@ -0,0 +1,110 @@
+import ReactDOM from 'react-dom'
+import { useTranslation } from 'react-i18next'
+import ToggleWidget from './toggle-widget'
+import BulkActions from '../entries/bulk-actions-entry/bulk-actions'
+import AddCommentButton from '../add-comment-button'
+import Icon from '../../../../../shared/components/icon'
+import {
+  useReviewPanelUpdaterFnsContext,
+  useReviewPanelValueContext,
+} from '../../../context/review-panel/review-panel-context'
+import { useCodeMirrorViewContext } from '../../codemirror-editor'
+import Modal, { useBulkActionsModal } from '../entries/bulk-actions-entry/modal'
+import getMeta from '../../../../../utils/meta'
+import useScopeValue from '../../../../../shared/hooks/use-scope-value'
+import { MergeAndOverride } from '../../../../../../../types/utils'
+import { ReviewPanelBulkActionsEntry } from '../../../../../../../types/review-panel/entry'
+
+function EditorWidgets() {
+  const { t } = useTranslation()
+  const {
+    show,
+    setShow,
+    isAccept,
+    handleShowBulkAcceptDialog,
+    handleShowBulkRejectDialog,
+    handleConfirmDialog,
+  } = useBulkActionsModal()
+  const { setIsAddingComment } = useReviewPanelUpdaterFnsContext()
+  const [addNewComment] =
+    useScopeValue<(e: React.MouseEvent<HTMLButtonElement>) => void>(
+      'addNewComment'
+    )
+  const view = useCodeMirrorViewContext()
+
+  type UseReviewPanelValueContextReturnType = ReturnType<
+    typeof useReviewPanelValueContext
+  >
+  const {
+    entries,
+    openDocId,
+    nVisibleSelectedChanges: nChanges,
+    wantTrackChanges,
+    permissions,
+    // Remapping entries as they may contain `add-comment` and `bulk-actions` props along with DocIds
+    // Ideally the `add-comment` and `bulk-actions` objects should not be within the entries object
+    // as the doc data, but this is what currently angular returns.
+  } = useReviewPanelValueContext() as MergeAndOverride<
+    UseReviewPanelValueContextReturnType,
+    {
+      entries: {
+        // eslint-disable-next-line no-use-before-define
+        [Entry in UseReviewPanelValueContextReturnType['entries'] as keyof Entry]: Entry & {
+          'add-comment': ReviewPanelBulkActionsEntry
+          'bulk-actions': ReviewPanelBulkActionsEntry
+        }
+      }
+    }
+  >
+
+  const hasTrackChangesFeature = getMeta('ol-hasTrackChangesFeature')
+
+  const currentDocEntries =
+    openDocId && openDocId in entries ? entries[openDocId] : undefined
+
+  const handleAddNewCommentClick = (e: React.MouseEvent<HTMLButtonElement>) => {
+    addNewComment(e)
+    setTimeout(() => {
+      // Re-render the comment box in order to add autofocus every time
+      setIsAddingComment(false)
+      setIsAddingComment(true)
+    }, 0)
+  }
+
+  return ReactDOM.createPortal(
+    <>
+      <div className="rp-in-editor-widgets react-rp-in-editor-widgets">
+        <div className="rp-in-editor-widgets-inner">
+          {wantTrackChanges && <ToggleWidget />}
+          {nChanges > 1 && (
+            <>
+              <BulkActions.Button onClick={handleShowBulkAcceptDialog}>
+                <Icon type="check" /> {t('accept_all')} ({nChanges})
+              </BulkActions.Button>
+              <BulkActions.Button onClick={handleShowBulkRejectDialog}>
+                <Icon type="times" /> {t('reject_all')} ({nChanges})
+              </BulkActions.Button>
+            </>
+          )}
+          {hasTrackChangesFeature &&
+            permissions.comment &&
+            currentDocEntries?.['add-comment'] && (
+              <AddCommentButton onClick={handleAddNewCommentClick}>
+                <Icon type="comment" /> {t('add_comment')}
+              </AddCommentButton>
+            )}
+        </div>
+      </div>
+      <Modal
+        show={show}
+        setShow={setShow}
+        isAccept={isAccept}
+        nChanges={nChanges}
+        onConfirm={handleConfirmDialog}
+      />
+    </>,
+    view.scrollDOM
+  )
+}
+
+export default EditorWidgets

+ 25 - 0
services/web/frontend/js/features/source-editor/components/review-panel/editor-widgets/toggle-widget.tsx

@@ -0,0 +1,25 @@
+import { Trans } from 'react-i18next'
+import { useReviewPanelValueContext } from '../../../context/review-panel/review-panel-context'
+import { useCodeMirrorStateContext } from '../../codemirror-editor'
+import { EditorView } from '@codemirror/view'
+import classnames from 'classnames'
+
+function ToggleWidget() {
+  const { toggleReviewPanel } = useReviewPanelValueContext()
+  const state = useCodeMirrorStateContext()
+  const darkTheme = state.facet(EditorView.darkTheme)
+
+  return (
+    <button
+      className={classnames('rp-track-changes-indicator', {
+        'rp-track-changes-indicator-on-dark': darkTheme,
+      })}
+      onClick={toggleReviewPanel}
+    >
+      {/* eslint-disable-next-line react/jsx-key */}
+      <Trans i18nKey="track_changes_is_on" components={[<strong />]} />
+    </button>
+  )
+}
+
+export default ToggleWidget

+ 15 - 13
services/web/frontend/js/features/source-editor/components/review-panel/entries/add-comment-entry.tsx

@@ -4,8 +4,12 @@ import EntryContainer from './entry-container'
 import EntryCallout from './entry-callout'
 import EntryActions from './entry-actions'
 import AutoExpandingTextArea from '../../../../../shared/components/auto-expanding-text-area'
+import AddCommentButton from '../add-comment-button'
 import Icon from '../../../../../shared/components/icon'
-import { useReviewPanelValueContext } from '../../../context/review-panel/review-panel-context'
+import {
+  useReviewPanelUpdaterFnsContext,
+  useReviewPanelValueContext,
+} from '../../../context/review-panel/review-panel-context'
 import classnames from 'classnames'
 import { ReviewPanelAddCommentEntry } from '../../../../../../../types/review-panel/entry'
 
@@ -15,24 +19,25 @@ type AddCommentEntryProps = {
 
 function AddCommentEntry({ entry }: AddCommentEntryProps) {
   const { t } = useTranslation()
-  const { submitNewComment, handleLayoutChange } = useReviewPanelValueContext()
+  const { isAddingComment, submitNewComment, handleLayoutChange } =
+    useReviewPanelValueContext()
+  const { setIsAddingComment } = useReviewPanelUpdaterFnsContext()
 
   const [content, setContent] = useState('')
-  const [isAdding, setIsAdding] = useState(false)
 
   const handleStartNewComment = () => {
-    setIsAdding(true)
+    setIsAddingComment(true)
     handleLayoutChange()
   }
 
   const handleSubmitNewComment = () => {
     submitNewComment(content)
-    setIsAdding(false)
+    setIsAddingComment(false)
     setContent('')
   }
 
   const handleCancelNewComment = () => {
-    setIsAdding(false)
+    setIsAddingComment(false)
     setContent('')
     handleLayoutChange()
   }
@@ -61,14 +66,14 @@ function AddCommentEntry({ entry }: AddCommentEntryProps) {
       <EntryCallout className="rp-entry-callout-add-comment" />
       <div
         className={classnames('rp-entry', 'rp-entry-add-comment', {
-          'rp-entry-adding-comment': isAdding,
+          'rp-entry-adding-comment': isAddingComment,
         })}
         style={{
           top: entry.screenPos.y + 'px',
           visibility: entry.visible ? 'visible' : 'hidden',
         }}
       >
-        {isAdding ? (
+        {isAddingComment ? (
           <>
             <div className="rp-new-comment">
               <AutoExpandingTextArea
@@ -97,12 +102,9 @@ function AddCommentEntry({ entry }: AddCommentEntryProps) {
             </EntryActions>
           </>
         ) : (
-          <button
-            className="rp-add-comment-btn"
-            onClick={handleStartNewComment}
-          >
+          <AddCommentButton onClick={handleStartNewComment}>
             <Icon type="comment" /> {t('add_comment')}
-          </button>
+          </AddCommentButton>
         )}
       </div>
     </EntryContainer>

+ 2 - 0
services/web/frontend/js/features/source-editor/components/review-panel/review-panel.tsx

@@ -1,4 +1,5 @@
 import ReactDOM from 'react-dom'
+import EditorWidgets from './editor-widgets/editor-widgets'
 import CurrentFileContainer from './current-file-container'
 import OverviewContainer from './overview-container'
 import { useCodeMirrorViewContext } from '../codemirror-editor'
@@ -17,6 +18,7 @@ function ReviewPanelView({ parentDomNode }: ReviewPanelViewProps) {
 
   return ReactDOM.createPortal(
     <>
+      <EditorWidgets />
       {isCurrentFileView(subView) ? (
         <CurrentFileContainer />
       ) : (

+ 11 - 1
services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts

@@ -127,6 +127,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
   )
 
   const [entryHover, setEntryHover] = useState(false)
+  const [isAddingComment, setIsAddingComment] = useState(false)
 
   const values = useMemo<ReviewPanelState['values']>(
     () => ({
@@ -136,6 +137,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
       docs,
       entries,
       entryHover,
+      isAddingComment,
       gotoEntry,
       handleLayoutChange,
       loadingThreads,
@@ -176,6 +178,7 @@ function useAngularReviewPanelState(): ReviewPanelState {
       docs,
       entries,
       entryHover,
+      isAddingComment,
       gotoEntry,
       handleLayoutChange,
       loadingThreads,
@@ -217,8 +220,15 @@ function useAngularReviewPanelState(): ReviewPanelState {
       setEntryHover,
       setCollapsed,
       setShouldCollapse,
+      setIsAddingComment,
     }),
-    [handleSetSubview, setCollapsed, setEntryHover, setShouldCollapse]
+    [
+      handleSetSubview,
+      setCollapsed,
+      setEntryHover,
+      setShouldCollapse,
+      setIsAddingComment,
+    ]
   )
 
   return { values, updaterFns }

+ 8 - 4
services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts

@@ -13,6 +13,7 @@ import {
   MainDocument,
 } from '../../../../../../../types/project-settings'
 
+/* eslint-disable no-use-before-define */
 export interface ReviewPanelState {
   values: {
     collapsed: Record<DocId, boolean>
@@ -21,6 +22,7 @@ export interface ReviewPanelState {
     docs: MainDocument[] | undefined
     entries: ReviewPanelEntries
     entryHover: boolean
+    isAddingComment: boolean
     gotoEntry: (docId: DocId, entryOffset: number) => void
     handleLayoutChange: () => void
     loadingThreads: boolean
@@ -66,15 +68,17 @@ export interface ReviewPanelState {
   }
   updaterFns: {
     handleSetSubview: (subView: SubView) => void
-    setEntryHover: React.Dispatch<React.SetStateAction<boolean>>
-    setCollapsed: React.Dispatch<
-      React.SetStateAction<ReviewPanelState['values']['collapsed']>
+    setEntryHover: React.Dispatch<React.SetStateAction<Value<'entryHover'>>>
+    setIsAddingComment: React.Dispatch<
+      React.SetStateAction<Value<'isAddingComment'>>
     >
+    setCollapsed: React.Dispatch<React.SetStateAction<Value<'collapsed'>>>
     setShouldCollapse: React.Dispatch<
-      React.SetStateAction<ReviewPanelState['values']['shouldCollapse']>
+      React.SetStateAction<Value<'shouldCollapse'>>
     >
   }
 }
+/* eslint-enable no-use-before-define */
 
 // Getter for values
 export type Value<T extends keyof ReviewPanelState['values']> =

+ 17 - 0
services/web/frontend/stylesheets/app/editor/review-panel.less

@@ -1304,3 +1304,20 @@ button when (@is-overleaf-light = true) {
     }
   }
 }
+
+.react-rp-in-editor-widgets {
+  position: sticky;
+  font-family: @font-family-sans-serif;
+
+  .rp-in-editor-widgets-inner {
+    position: absolute;
+    top: 0;
+    right: 0;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .rp-track-changes-indicator {
+    border: 0;
+  }
+}

+ 14 - 0
services/web/test/frontend/features/review-panel/review-panel.spec.tsx

@@ -250,4 +250,18 @@ describe('<ReviewPanel />', function () {
     // eslint-disable-next-line mocha/no-skipped-tests
     it.skip('renders comments', function () {})
   })
+
+  describe('in editor widgets', function () {
+    // eslint-disable-next-line mocha/no-skipped-tests
+    it.skip('toggle review panel', function () {})
+
+    // eslint-disable-next-line mocha/no-skipped-tests
+    it.skip('accepts all changes', function () {})
+
+    // eslint-disable-next-line mocha/no-skipped-tests
+    it.skip('rejects all changes', function () {})
+
+    // eslint-disable-next-line mocha/no-skipped-tests
+    it.skip('add comment', function () {})
+  })
 })