mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-23 01:46:22 +00:00
Merge pull request #13767 from overleaf/ii-review-panel-migration-in-editor-widgets
[web] Create in editor widgets GitOrigin-RevId: 53dfb9935ee59bbdedc353aad5e5b19f389a513c
This commit is contained in:
parent
3297506021
commit
84bbdf9772
12 changed files with 211 additions and 21 deletions
services/web
app
frontend
js/features/source-editor
components/review-panel
context/review-panel
stylesheets/app/editor
test/frontend/features/review-panel
|
@ -953,6 +953,7 @@ const ProjectController = {
|
|||
historyViewReact: historyViewAssignment.variant === 'react',
|
||||
isReviewPanelReact: reviewPanelAssignment.variant === 'react',
|
||||
showPersonalAccessToken,
|
||||
hasTrackChangesFeature: Features.hasFeature('track-changes'),
|
||||
})
|
||||
timer.done()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
function AddCommentButton(props: React.ComponentPropsWithoutRef<'button'>) {
|
||||
return <button className="rp-add-comment-btn" {...props} />
|
||||
}
|
||||
|
||||
export default AddCommentButton
|
|
@ -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 />
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
) : (
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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']> =
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 () {})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue