diff --git a/services/web/app/views/project/editor/review-panel.pug b/services/web/app/views/project/editor/review-panel.pug index 2c65090687..83a7602d38 100644 --- a/services/web/app/views/project/editor/review-panel.pug +++ b/services/web/app/views/project/editor/review-panel.pug @@ -128,7 +128,7 @@ on-indicator-click="toggleReviewPanel();" on-mouse-enter="mouseEnterIndicator()" on-mouse-leave="mouseLeaveIndicator()" - on-body-click="gotoEntry(editor.open_doc_id, entry)" + on-body-click="gotoEntry(editor.open_doc_id, entry.offset)" permissions="permissions" ) @@ -141,7 +141,7 @@ on-indicator-click="toggleReviewPanel();" on-mouse-enter="mouseEnterIndicator()" on-mouse-leave="mouseLeaveIndicator()" - on-body-click="gotoEntry(editor.open_doc_id, entry)" + on-body-click="gotoEntry(editor.open_doc_id, entry.offset)" permissions="permissions" ) @@ -149,14 +149,14 @@ comment-entry( entry="entry" threads="reviewPanel.commentThreads" - on-resolve="resolveComment(entry, entry_id)" + on-resolve="resolveComment(editor.open_doc_id, entry_id)" on-reply="submitReply(entry, entry_id);" on-indicator-click="toggleReviewPanel();" on-mouse-enter="mouseEnterIndicator()" on-mouse-leave="mouseLeaveIndicator()" - on-save-edit="saveEdit(entry.thread_id, comment)" - on-delete="deleteComment(entry.thread_id, comment)" - on-body-click="gotoEntry(editor.open_doc_id, entry)" + on-save-edit="saveEdit(entry.thread_id, comment.id, comment.content)" + on-delete="deleteComment(entry.thread_id, comment.id)" + on-body-click="gotoEntry(editor.open_doc_id, entry.offset)" permissions="permissions" ng-if="!loadingThreads" ) @@ -207,7 +207,7 @@ change-entry( entry="entry" user="users[entry.metadata.user_id]" - ng-click="gotoEntry(doc.doc.id, entry)" + ng-click="gotoEntry(doc.doc.id, entry.offset)" permissions="permissions" ) @@ -215,7 +215,7 @@ aggregate-change-entry( entry="entry" user="users[entry.metadata.user_id]" - ng-click="gotoEntry(doc.doc.id, entry)" + ng-click="gotoEntry(doc.doc.id, entry.offset)" permissions="permissions" ) @@ -224,9 +224,9 @@ entry="entry" threads="reviewPanel.commentThreads" on-reply="submitReply(entry, entry_id);" - on-save-edit="saveEdit(entry.thread_id, comment)" - on-delete="deleteComment(entry.thread_id, comment)" - ng-click="gotoEntry(doc.doc.id, entry)" + on-save-edit="saveEdit(entry.thread_id, comment.id, comment.content)" + on-delete="deleteComment(entry.thread_id, comment.id)" + ng-click="gotoEntry(doc.doc.id, entry.offset)" permissions="permissions" ) @@ -325,7 +325,7 @@ script(type='text/ng-template', id='aggregateChangeEntryTemplate') .rp-entry-description | #{translate("aggregate_changed")}  del.rp-content-highlight - | {{ entry.metadata.replaced_content | limitTo:(isDeletionCollapsed ? contentLimit : entry.metadata.replaced_contentlength) }} + | {{ entry.metadata.replaced_content | limitTo:(isDeletionCollapsed ? contentLimit : entry.metadata.replaced_content.length) }} a.rp-collapse-toggle( href ng-if="deletionNeedsCollapsing" diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/container.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/container.tsx new file mode 100644 index 0000000000..48dd244bd6 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/review-panel/container.tsx @@ -0,0 +1,19 @@ +import classnames from 'classnames' + +const reviewPanelClasses = ['ol-cm-review-panel'] + +type ContainerProps = { + children?: React.ReactNode + classNames?: Record + style?: React.CSSProperties +} + +function Container({ children, classNames, ...rest }: ContainerProps) { + return ( +
+ {children} +
+ ) +} + +export default Container 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 new file mode 100644 index 0000000000..92447b34ef --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx @@ -0,0 +1,12 @@ +import Container from './container' + +function CurrentFileContainer() { + return ( + + ReviewPanelCurrentFileContainer +
Tools
+
+ ) +} + +export default CurrentFileContainer diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/nav.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/nav.tsx new file mode 100644 index 0000000000..b6d90ff8d6 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/review-panel/nav.tsx @@ -0,0 +1 @@ +export type SubView = 'cur_file' | 'overview' diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx new file mode 100644 index 0000000000..744d34c4d2 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx @@ -0,0 +1,11 @@ +import Container from './container' + +function OverviewContainer() { + return ( + + ReviewPanelOverviewContainer + + ) +} + +export default OverviewContainer diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/review-panel.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/review-panel.tsx index 2ce6e2cf57..ce006c450f 100644 --- a/services/web/frontend/js/features/source-editor/components/review-panel/review-panel.tsx +++ b/services/web/frontend/js/features/source-editor/components/review-panel/review-panel.tsx @@ -1,5 +1,39 @@ +import ReactDOM from 'react-dom' +import { useCodeMirrorViewContext } from '../codemirror-editor' +import { + ReviewPanelProvider, + useReviewPanelValueContext, +} from '../../context/review-panel/review-panel-context' +import CurrentFileContainer from './current-file-container' +import OverviewContainer from './overview-container' + +type ReviewPanelViewProps = { + parentDomNode: Element +} + +function ReviewPanelView({ parentDomNode }: ReviewPanelViewProps) { + const { subView } = useReviewPanelValueContext() + + return ReactDOM.createPortal( + <> + {subView === 'cur_file' ? ( + + ) : ( + + )} + , + parentDomNode + ) +} + function ReviewPanel() { - return
Content to be added.
+ const view = useCodeMirrorViewContext() + + return ( + + + + ) } export default ReviewPanel diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel.ts b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel.ts new file mode 100644 index 0000000000..44091df1c7 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react' +import useScopeValue from '../../../../../shared/hooks/use-scope-value' +import { ReviewPanelState } from '../types/review-panel-state' +import * as ReviewPanel from '../types/review-panel-state' + +function useAngularReviewPanelState(): ReviewPanelState { + const [subView, setSubView] = useScopeValue>( + 'reviewPanel.subView' + ) + const [collapsed, setCollapsed] = useScopeValue< + ReviewPanel.Value<'collapsed'> + >('reviewPanel.overview.docsCollapsedState') + + const values = useMemo( + () => ({ + subView, + collapsed, + }), + [subView, collapsed] + ) + + const updaterFns = useMemo( + () => ({ + setSubView, + setCollapsed, + }), + [setSubView, setCollapsed] + ) + + return { values, updaterFns } +} + +export default useAngularReviewPanelState diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/review-panel-context.tsx b/services/web/frontend/js/features/source-editor/context/review-panel/review-panel-context.tsx new file mode 100644 index 0000000000..51858caf47 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/context/review-panel/review-panel-context.tsx @@ -0,0 +1,47 @@ +import { createContext, useContext } from 'react' +import useAngularReviewPanelState from './hooks/use-angular-review-panel' +import { ReviewPanelState } from './types/review-panel-state' + +const ReviewPanelValueContext = createContext< + ReviewPanelState['values'] | undefined +>(undefined) + +const ReviewPanelUpdaterFnsContext = createContext< + ReviewPanelState['updaterFns'] | undefined +>(undefined) + +type ReviewPanelProviderProps = { + children?: React.ReactNode +} + +export function ReviewPanelProvider({ children }: ReviewPanelProviderProps) { + const { values, updaterFns } = useAngularReviewPanelState() + + return ( + + + {children} + + + ) +} + +export function useReviewPanelValueContext() { + const context = useContext(ReviewPanelValueContext) + if (!context) { + throw new Error( + 'ReviewPanelValueContext is only available inside ReviewPanelProvider' + ) + } + return context +} + +export function useReviewPanelUpdaterFnsContext() { + const context = useContext(ReviewPanelUpdaterFnsContext) + if (!context) { + throw new Error( + 'ReviewPanelUpdaterFnsContext is only available inside ReviewPanelProvider' + ) + } + return context +} 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 new file mode 100644 index 0000000000..85bc486c6d --- /dev/null +++ b/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts @@ -0,0 +1,20 @@ +import { SubView } from '../../../components/review-panel/nav' + +export interface ReviewPanelState { + values: { + collapsed: Record + subView: SubView + } + updaterFns: { + setCollapsed: React.Dispatch< + React.SetStateAction + > + setSubView: React.Dispatch< + React.SetStateAction + > + } +} + +// Getter for values +export type Value = + ReviewPanelState['values'][T] diff --git a/services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js b/services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js index e654ab89c0..3b6e402e93 100644 --- a/services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js +++ b/services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js @@ -71,7 +71,7 @@ export default App.controller( PENDING: 'pending', } - $scope.reviewPanel = { + ide.$scope.reviewPanel = { trackChangesState: {}, trackChangesOnForEveryone: false, trackChangesOnForGuests: false, @@ -111,8 +111,8 @@ export default App.controller( window.addEventListener('beforeunload', function () { const collapsedStates = {} - for (const doc in $scope.reviewPanel.overview.docsCollapsedState) { - const state = $scope.reviewPanel.overview.docsCollapsedState[doc] + for (const doc in ide.$scope.reviewPanel.overview.docsCollapsedState) { + const state = ide.$scope.reviewPanel.overview.docsCollapsedState[doc] if (state) { collapsedStates[doc] = state } @@ -132,7 +132,7 @@ export default App.controller( ) $scope.$on('layout:pdf:resize', (event, state) => { - $scope.reviewPanel.layoutToLeft = + ide.$scope.reviewPanel.layoutToLeft = state.east?.size < 220 || state.east?.initClosed $scope.$broadcast('review-panel:layout', false) }) @@ -156,10 +156,11 @@ export default App.controller( }) $scope.$watch('project.members', function (members) { - $scope.reviewPanel.formattedProjectMembers = {} + ide.$scope.reviewPanel.formattedProjectMembers = {} if (($scope.project != null ? $scope.project.owner : undefined) != null) { - $scope.reviewPanel.formattedProjectMembers[$scope.project.owner._id] = - formatUser($scope.project.owner) + ide.$scope.reviewPanel.formattedProjectMembers[ + $scope.project.owner._id + ] = formatUser($scope.project.owner) } if ( ($scope.project != null ? $scope.project.members : undefined) != null @@ -168,16 +169,18 @@ export default App.controller( const result = [] for (const member of Array.from(members)) { if (member.privileges === 'readAndWrite') { - if ($scope.reviewPanel.trackChangesState[member._id] == null) { + if ( + ide.$scope.reviewPanel.trackChangesState[member._id] == null + ) { // An added member will have track changes enabled if track changes is on for everyone _setUserTCState( member._id, - $scope.reviewPanel.trackChangesOnForEveryone, + ide.$scope.reviewPanel.trackChangesOnForEveryone, true ) } result.push( - ($scope.reviewPanel.formattedProjectMembers[member._id] = + (ide.$scope.reviewPanel.formattedProjectMembers[member._id] = formatUser(member)) ) } else { @@ -194,9 +197,9 @@ export default App.controller( content: '', } - $scope.users = {} + ide.$scope.users = $scope.users = {} - $scope.reviewPanelEventsBridge = new EventEmitter() + ide.$scope.reviewPanelEventsBridge = new EventEmitter() ide.socket.on('new-comment', function (thread_id, comment) { const thread = getThread(thread_id) @@ -241,37 +244,37 @@ export default App.controller( const rangesTrackers = {} const getDocEntries = function (doc_id) { - if ($scope.reviewPanel.entries[doc_id] == null) { - $scope.reviewPanel.entries[doc_id] = {} + if (ide.$scope.reviewPanel.entries[doc_id] == null) { + ide.$scope.reviewPanel.entries[doc_id] = {} } - return $scope.reviewPanel.entries[doc_id] + return ide.$scope.reviewPanel.entries[doc_id] } const getDocResolvedComments = function (doc_id) { - if ($scope.reviewPanel.resolvedComments[doc_id] == null) { - $scope.reviewPanel.resolvedComments[doc_id] = {} + if (ide.$scope.reviewPanel.resolvedComments[doc_id] == null) { + ide.$scope.reviewPanel.resolvedComments[doc_id] = {} } - return $scope.reviewPanel.resolvedComments[doc_id] + return ide.$scope.reviewPanel.resolvedComments[doc_id] } function getThread(thread_id) { - if ($scope.reviewPanel.commentThreads[thread_id] == null) { - $scope.reviewPanel.commentThreads[thread_id] = { messages: [] } + if (ide.$scope.reviewPanel.commentThreads[thread_id] == null) { + ide.$scope.reviewPanel.commentThreads[thread_id] = { messages: [] } } - return $scope.reviewPanel.commentThreads[thread_id] + return ide.$scope.reviewPanel.commentThreads[thread_id] } function getChangeTracker(doc_id) { if (rangesTrackers[doc_id] == null) { rangesTrackers[doc_id] = new RangesTracker() rangesTrackers[doc_id].resolvedThreadIds = - $scope.reviewPanel.resolvedThreadIds + ide.$scope.reviewPanel.resolvedThreadIds } return rangesTrackers[doc_id] } let scrollbar = {} - $scope.reviewPanelEventsBridge.on( + ide.$scope.reviewPanelEventsBridge.on( 'aceScrollbarVisibilityChanged', function (isVisible, scrollbarWidth) { scrollbar = { isVisible, scrollbarWidth } @@ -282,7 +285,7 @@ export default App.controller( function updateScrollbar() { if ( scrollbar.isVisible && - $scope.reviewPanel.subView === $scope.SubViews.CUR_FILE && + ide.$scope.reviewPanel.subView === $scope.SubViews.CUR_FILE && !$scope.editor.showRichText ) { return $reviewPanelEl.css('right', `${scrollbar.scrollbarWidth}px`) @@ -310,11 +313,11 @@ export default App.controller( } if (!open) { // Always show current file when not open, but save current state - $scope.reviewPanel.openSubView = $scope.reviewPanel.subView - $scope.reviewPanel.subView = $scope.SubViews.CUR_FILE + ide.$scope.reviewPanel.openSubView = ide.$scope.reviewPanel.subView + ide.$scope.reviewPanel.subView = $scope.SubViews.CUR_FILE } else { // Reset back to what we had when previously open - $scope.reviewPanel.subView = $scope.reviewPanel.openSubView + ide.$scope.reviewPanel.subView = ide.$scope.reviewPanel.openSubView } return $timeout(function () { $scope.$broadcast('review-panel:toggle') @@ -342,8 +345,8 @@ export default App.controller( // replace any outdated info with this rangesTrackers[doc.doc_id] = doc.ranges rangesTrackers[doc.doc_id].resolvedThreadIds = - $scope.reviewPanel.resolvedThreadIds - $scope.reviewPanel.rangesTracker = rangesTrackers[doc.doc_id] + ide.$scope.reviewPanel.resolvedThreadIds + ide.$scope.reviewPanel.rangesTracker = rangesTrackers[doc.doc_id] if (old_doc != null) { old_doc.off('flipped_pending_to_inflight') } @@ -354,7 +357,7 @@ export default App.controller( $scope.$watch( function () { const entries = - $scope.reviewPanel.entries[$scope.editor.open_doc_id] || {} + ide.$scope.reviewPanel.entries[$scope.editor.open_doc_id] || {} const permEntries = {} for (const entry in entries) { const entryData = entries[entry] @@ -365,7 +368,7 @@ export default App.controller( return Object.keys(permEntries).length }, nEntries => - ($scope.reviewPanel.hasEntries = + (ide.$scope.reviewPanel.hasEntries = nEntries > 0 && $scope.project.features.trackChangesVisible) ) @@ -385,9 +388,12 @@ export default App.controller( const result = [] for (const doc of Array.from(docs)) { if ( - $scope.reviewPanel.overview.docsCollapsedState[doc.id] == null + ide.$scope.reviewPanel.overview.docsCollapsedState[doc.id] == + null ) { - $scope.reviewPanel.overview.docsCollapsedState[doc.id] = false + ide.$scope.reviewPanel.overview.docsCollapsedState[ + doc.id + ] = false } if (doc.id !== $scope.editor.open_doc_id) { // this is kept up to date in real-time, don't overwrite @@ -404,17 +410,17 @@ export default App.controller( }) function refreshOverviewPanel() { - $scope.reviewPanel.overview.loading = true + ide.$scope.reviewPanel.overview.loading = true return refreshRanges() - .then(() => ($scope.reviewPanel.overview.loading = false)) - .catch(() => ($scope.reviewPanel.overview.loading = false)) + .then(() => (ide.$scope.reviewPanel.overview.loading = false)) + .catch(() => (ide.$scope.reviewPanel.overview.loading = false)) } - $scope.refreshResolvedCommentsDropdown = function () { - $scope.reviewPanel.dropdown.loading = true + ide.$scope.refreshResolvedCommentsDropdown = function () { + ide.$scope.reviewPanel.dropdown.loading = true const q = refreshRanges() - q.then(() => ($scope.reviewPanel.dropdown.loading = false)) - q.catch(() => ($scope.reviewPanel.dropdown.loading = false)) + q.then(() => (ide.$scope.reviewPanel.dropdown.loading = false)) + q.catch(() => (ide.$scope.reviewPanel.dropdown.loading = false)) return q } @@ -504,7 +510,7 @@ export default App.controller( let new_comment changed = true delete delete_changes[comment.id] - if ($scope.reviewPanel.resolvedThreadIds[comment.op.t]) { + if (ide.$scope.reviewPanel.resolvedThreadIds[comment.op.t]) { new_comment = resolvedComments[comment.id] != null ? resolvedComments[comment.id] @@ -567,9 +573,9 @@ export default App.controller( const doc_id = $scope.editor.open_doc_id const entries = getDocEntries(doc_id) // All selected changes will be added to this array. - $scope.reviewPanel.selectedEntryIds = [] + ide.$scope.reviewPanel.selectedEntryIds = [] // Count of user-visible changes, i.e. an aggregated change will count as one. - $scope.reviewPanel.nVisibleSelectedChanges = 0 + ide.$scope.reviewPanel.nVisibleSelectedChanges = 0 delete entries['add-comment'] delete entries['bulk-actions'] @@ -591,7 +597,7 @@ export default App.controller( let isChangeEntryAndWithinSelection = false if ( entry.type === 'comment' && - !$scope.reviewPanel.resolvedThreadIds[entry.thread_id] + !ide.$scope.reviewPanel.resolvedThreadIds[entry.thread_id] ) { entry.focused = entry.offset <= selection_offset_start && @@ -624,9 +630,9 @@ export default App.controller( if (isChangeEntryAndWithinSelection) { for (const entry_id of Array.from(entry.entry_ids)) { - $scope.reviewPanel.selectedEntryIds.push(entry_id) + ide.$scope.reviewPanel.selectedEntryIds.push(entry_id) } - $scope.reviewPanel.nVisibleSelectedChanges++ + ide.$scope.reviewPanel.nVisibleSelectedChanges++ } } @@ -638,17 +644,21 @@ export default App.controller( } ) - $scope.acceptChanges = function (change_ids) { + ide.$scope.acceptChanges = function (change_ids) { _doAcceptChanges(change_ids) eventTracking.sendMB('rp-changes-accepted', { - view: $scope.ui.reviewPanelOpen ? $scope.reviewPanel.subView : 'mini', + view: $scope.ui.reviewPanelOpen + ? ide.$scope.reviewPanel.subView + : 'mini', }) } - $scope.rejectChanges = function (change_ids) { + ide.$scope.rejectChanges = function (change_ids) { _doRejectChanges(change_ids) eventTracking.sendMB('rp-changes-rejected', { - view: $scope.ui.reviewPanelOpen ? $scope.reviewPanel.subView : 'mini', + view: $scope.ui.reviewPanelOpen + ? ide.$scope.reviewPanel.subView + : 'mini', }) } @@ -657,11 +667,11 @@ export default App.controller( // the review panel is visible when hovering over its indicator when the // review panel is minimized. See issue #8057. $scope.mouseEnterIndicator = function () { - $scope.reviewPanel.entryHover = true + ide.$scope.reviewPanel.entryHover = true } $scope.mouseLeaveIndicator = function () { - $scope.reviewPanel.entryHover = false + ide.$scope.reviewPanel.entryHover = false } function _doAcceptChanges(change_ids) { @@ -679,24 +689,28 @@ export default App.controller( } const bulkAccept = function () { - _doAcceptChanges($scope.reviewPanel.selectedEntryIds.slice()) + _doAcceptChanges(ide.$scope.reviewPanel.selectedEntryIds.slice()) eventTracking.sendMB('rp-bulk-accept', { - view: $scope.ui.reviewPanelOpen ? $scope.reviewPanel.subView : 'mini', - nEntries: $scope.reviewPanel.nVisibleSelectedChanges, + view: $scope.ui.reviewPanelOpen + ? ide.$scope.reviewPanel.subView + : 'mini', + nEntries: ide.$scope.reviewPanel.nVisibleSelectedChanges, }) } const bulkReject = function () { - _doRejectChanges($scope.reviewPanel.selectedEntryIds.slice()) + _doRejectChanges(ide.$scope.reviewPanel.selectedEntryIds.slice()) eventTracking.sendMB('rp-bulk-reject', { - view: $scope.ui.reviewPanelOpen ? $scope.reviewPanel.subView : 'mini', - nEntries: $scope.reviewPanel.nVisibleSelectedChanges, + view: $scope.ui.reviewPanelOpen + ? ide.$scope.reviewPanel.subView + : 'mini', + nEntries: ide.$scope.reviewPanel.nVisibleSelectedChanges, }) } - $scope.showBulkAcceptDialog = () => showBulkActionsDialog(true) + ide.$scope.showBulkAcceptDialog = () => showBulkActionsDialog(true) - $scope.showBulkRejectDialog = () => showBulkActionsDialog(false) + ide.$scope.showBulkRejectDialog = () => showBulkActionsDialog(false) const showBulkActionsDialog = isAccept => $modal @@ -708,7 +722,7 @@ export default App.controller( return isAccept }, nChanges() { - return $scope.reviewPanel.nVisibleSelectedChanges + return ide.$scope.reviewPanel.nVisibleSelectedChanges }, }, scope: $scope.$new(), @@ -726,7 +740,7 @@ export default App.controller( return $scope.toggleReviewPanel() } - $scope.addNewComment = function (e) { + ide.$scope.addNewComment = function (e) { e.preventDefault() $scope.$broadcast('comment:start_adding') return $scope.toggleReviewPanel() @@ -754,7 +768,7 @@ export default App.controller( return $timeout(() => $scope.$broadcast('review-panel:layout')) } - $scope.submitNewComment = function (content) { + ide.$scope.submitNewComment = function (content) { if (content == null || content === '') { return } @@ -798,7 +812,7 @@ export default App.controller( return $timeout(() => $scope.$broadcast('review-panel:layout')) } - $scope.submitReply = function (entry, entry_id) { + ide.$scope.submitReply = function (entry, entry_id) { const { thread_id } = entry const content = entry.replyContent $http @@ -814,7 +828,9 @@ export default App.controller( ) const trackingMetadata = { - view: $scope.ui.reviewPanelOpen ? $scope.reviewPanel.subView : 'mini', + view: $scope.ui.reviewPanelOpen + ? ide.$scope.reviewPanel.subView + : 'mini', size: entry.replyContent.length, thread: thread_id, } @@ -833,7 +849,8 @@ export default App.controller( return $scope.$broadcast('review-panel:layout') } - $scope.resolveComment = function (entry, entry_id) { + ide.$scope.resolveComment = function (doc_id, entry_id) { + const entry = getDocEntries(doc_id)[entry_id] entry.focused = false $http.post( `/project/${$scope.project_id}/thread/${entry.thread_id}/resolve`, @@ -841,11 +858,13 @@ export default App.controller( ) _onCommentResolved(entry.thread_id, ide.$scope.user) eventTracking.sendMB('rp-comment-resolve', { - view: $scope.ui.reviewPanelOpen ? $scope.reviewPanel.subView : 'mini', + view: $scope.ui.reviewPanelOpen + ? ide.$scope.reviewPanel.subView + : 'mini', }) } - $scope.unresolveComment = function (thread_id) { + ide.$scope.unresolveComment = function (thread_id) { _onCommentReopened(thread_id) $http.post(`/project/${$scope.project_id}/thread/${thread_id}/reopen`, { _csrf: window.csrfToken, @@ -861,7 +880,7 @@ export default App.controller( thread.resolved = true thread.resolved_by_user = formatUser(user) thread.resolved_at = new Date().toISOString() - $scope.reviewPanel.resolvedThreadIds[thread_id] = true + ide.$scope.reviewPanel.resolvedThreadIds[thread_id] = true $scope.$broadcast('comment:resolve_threads', [thread_id]) dispatchReviewPanelEvent('comment:resolve_threads', [thread_id]) } @@ -874,14 +893,14 @@ export default App.controller( delete thread.resolved delete thread.resolved_by_user delete thread.resolved_at - delete $scope.reviewPanel.resolvedThreadIds[thread_id] + delete ide.$scope.reviewPanel.resolvedThreadIds[thread_id] $scope.$broadcast('comment:unresolve_thread', thread_id) dispatchReviewPanelEvent('comment:unresolve_thread', thread_id) } function _onThreadDeleted(thread_id) { - delete $scope.reviewPanel.resolvedThreadIds[thread_id] - delete $scope.reviewPanel.commentThreads[thread_id] + delete ide.$scope.reviewPanel.resolvedThreadIds[thread_id] + delete ide.$scope.reviewPanel.commentThreads[thread_id] $scope.$broadcast('comment:remove', thread_id) dispatchReviewPanelEvent('comment:remove', thread_id) } @@ -908,7 +927,7 @@ export default App.controller( return updateEntries() } - $scope.deleteThread = function (entry_id, doc_id, thread_id) { + ide.$scope.deleteThread = function (entry_id, doc_id, thread_id) { _onThreadDeleted(thread_id) $http({ method: 'DELETE', @@ -920,22 +939,22 @@ export default App.controller( eventTracking.sendMB('rp-comment-delete') } - $scope.saveEdit = function (thread_id, comment) { + ide.$scope.saveEdit = function (thread_id, comment_id, content) { $http.post( - `/project/${$scope.project_id}/thread/${thread_id}/messages/${comment.id}/edit`, + `/project/${$scope.project_id}/thread/${thread_id}/messages/${comment_id}/edit`, { - content: comment.content, + content, _csrf: window.csrfToken, } ) return $timeout(() => $scope.$broadcast('review-panel:layout')) } - $scope.deleteComment = function (thread_id, comment) { - _onCommentDeleted(thread_id, comment.id) + ide.$scope.deleteComment = function (thread_id, comment_id) { + _onCommentDeleted(thread_id, comment_id) $http({ method: 'DELETE', - url: `/project/${$scope.project_id}/thread/${thread_id}/messages/${comment.id}`, + url: `/project/${$scope.project_id}/thread/${thread_id}/messages/${comment_id}`, headers: { 'X-CSRF-Token': window.csrfToken, }, @@ -944,17 +963,17 @@ export default App.controller( } $scope.setSubView = function (subView) { - $scope.reviewPanel.subView = subView + ide.$scope.reviewPanel.subView = subView eventTracking.sendMB('rp-subview-change', { subView }) } - $scope.gotoEntry = (doc_id, entry) => - ide.editorManager.openDocId(doc_id, { gotoOffset: entry.offset }) + ide.$scope.gotoEntry = (doc_id, entry_offset) => + ide.editorManager.openDocId(doc_id, { gotoOffset: entry_offset }) $scope.toggleFullTCStateCollapse = function () { if ($scope.project.features.trackChanges) { - return ($scope.reviewPanel.fullTCStateCollapsed = - !$scope.reviewPanel.fullTCStateCollapsed) + return (ide.$scope.reviewPanel.fullTCStateCollapsed = + !ide.$scope.reviewPanel.fullTCStateCollapsed) } else { _sendAnalytics() return $scope.openTrackChangesUpgradeModal() @@ -976,10 +995,10 @@ export default App.controller( if (isLocal == null) { isLocal = false } - if ($scope.reviewPanel.trackChangesState[userId] == null) { - $scope.reviewPanel.trackChangesState[userId] = {} + if (ide.$scope.reviewPanel.trackChangesState[userId] == null) { + ide.$scope.reviewPanel.trackChangesState[userId] = {} } - const state = $scope.reviewPanel.trackChangesState[userId] + const state = ide.$scope.reviewPanel.trackChangesState[userId] if ( state.syncState == null || @@ -1006,7 +1025,7 @@ export default App.controller( if (isLocal == null) { isLocal = false } - $scope.reviewPanel.trackChangesOnForEveryone = newValue + ide.$scope.reviewPanel.trackChangesOnForEveryone = newValue const { project } = $scope for (const member of Array.from(project.members)) { _setUserTCState(member._id, newValue, isLocal) @@ -1019,7 +1038,7 @@ export default App.controller( if (isLocal == null) { isLocal = false } - $scope.reviewPanel.trackChangesOnForGuests = newValue + ide.$scope.reviewPanel.trackChangesOnForGuests = newValue if ( currentUserType() === UserTypes.GUEST || currentUserType() === UserTypes.ANONYMOUS @@ -1030,15 +1049,15 @@ export default App.controller( const applyClientTrackChangesStateToServer = function () { const data = {} - if ($scope.reviewPanel.trackChangesOnForEveryone) { + if (ide.$scope.reviewPanel.trackChangesOnForEveryone) { data.on = true } else { data.on_for = {} - for (const userId in $scope.reviewPanel.trackChangesState) { - const userState = $scope.reviewPanel.trackChangesState[userId] + for (const userId in ide.$scope.reviewPanel.trackChangesState) { + const userState = ide.$scope.reviewPanel.trackChangesState[userId] data.on_for[userId] = userState.value } - if ($scope.reviewPanel.trackChangesOnForGuests) { + if (ide.$scope.reviewPanel.trackChangesOnForGuests) { data.on_for_guests = true } } @@ -1052,7 +1071,7 @@ export default App.controller( return _setGuestsTCState(state) } else { const { project } = $scope - $scope.reviewPanel.trackChangesOnForEveryone = false + ide.$scope.reviewPanel.trackChangesOnForEveryone = false _setGuestsTCState(state.__guests__ === true) for (const member of Array.from(project.members)) { _setUserTCState( @@ -1069,18 +1088,18 @@ export default App.controller( } } - $scope.toggleTrackChangesForEveryone = function (onForEveryone) { + ide.$scope.toggleTrackChangesForEveryone = function (onForEveryone) { _setEveryoneTCState(onForEveryone, true) _setGuestsTCState(onForEveryone, true) return applyClientTrackChangesStateToServer() } - $scope.toggleTrackChangesForGuests = function (onForGuests) { + ide.$scope.toggleTrackChangesForGuests = function (onForGuests) { _setGuestsTCState(onForGuests, true) return applyClientTrackChangesStateToServer() } - $scope.toggleTrackChangesForUser = function (onForUser, userId) { + ide.$scope.toggleTrackChangesForUser = function (onForUser, userId) { _setUserTCState(userId, onForUser, true) return applyClientTrackChangesStateToServer() } @@ -1099,13 +1118,13 @@ export default App.controller( return } return $scope.toggleTrackChangesForUser( - !$scope.reviewPanel.trackChangesState[ide.$scope.user.id].value, + !ide.$scope.reviewPanel.trackChangesState[ide.$scope.user.id].value, ide.$scope.user.id ) } const setGuestFeatureBasedOnProjectAccessLevel = projectPublicAccessLevel => - ($scope.reviewPanel.trackChangesForGuestsAvailable = + (ide.$scope.reviewPanel.trackChangesForGuestsAvailable = projectPublicAccessLevel === 'tokenBased') const onToggleTrackChangesForGuestsAvailability = function (available) { @@ -1113,10 +1132,10 @@ export default App.controller( if (available) { return } - if (!$scope.reviewPanel.trackChangesOnForGuests) { + if (!ide.$scope.reviewPanel.trackChangesOnForGuests) { return } // Already turned off - if ($scope.reviewPanel.trackChangesOnForEveryone) { + if (ide.$scope.reviewPanel.trackChangesOnForEveryone) { return } // Overrides guest setting return $scope.toggleTrackChangesForGuests(false) @@ -1153,7 +1172,6 @@ export default App.controller( let _refreshingRangeUsers = false const _refreshedForUserIds = {} - function refreshChangeUsers(refresh_for_user_id) { if (refresh_for_user_id != null) { if (_refreshedForUserIds[refresh_for_user_id] != null) { @@ -1174,7 +1192,7 @@ export default App.controller( .then(function (response) { const users = response.data _refreshingRangeUsers = false - $scope.users = {} + ide.$scope.users = $scope.users = {} // Always include ourself, since if we submit an op, we might need to display info // about it locally before it has been flushed through the server if ( @@ -1198,7 +1216,6 @@ export default App.controller( } let _threadsLoaded = false - function ensureThreadsAreLoaded() { if (_threadsLoaded) { // We get any updates in real time so only need to load them once. @@ -1211,9 +1228,8 @@ export default App.controller( .then(function (response) { const threads = response.data ide.$scope.loadingThreads = false - for (const thread_id in $scope.reviewPanel.resolvedThreadIds) { - const _ = $scope.reviewPanel.resolvedThreadIds[thread_id] - delete $scope.reviewPanel.resolvedThreadIds[thread_id] + for (const thread_id in ide.$scope.reviewPanel.resolvedThreadIds) { + delete ide.$scope.reviewPanel.resolvedThreadIds[thread_id] } for (const thread_id in threads) { const thread = threads[thread_id] @@ -1222,11 +1238,11 @@ export default App.controller( } if (thread.resolved_by_user != null) { thread.resolved_by_user = formatUser(thread.resolved_by_user) - $scope.reviewPanel.resolvedThreadIds[thread_id] = true + ide.$scope.reviewPanel.resolvedThreadIds[thread_id] = true $scope.$broadcast('comment:resolve_threads', [thread_id]) } } - $scope.reviewPanel.commentThreads = threads + ide.$scope.reviewPanel.commentThreads = threads dispatchReviewPanelEvent('loaded_threads') return $timeout(() => $scope.$broadcast('review-panel:layout')) }) @@ -1293,7 +1309,7 @@ export default App.controller( switch (type) { case 'line-height': { - $scope.reviewPanel.rendererData.lineHeight = payload + ide.$scope.reviewPanel.rendererData.lineHeight = payload $scope.$broadcast('review-panel:layout') break } @@ -1325,11 +1341,14 @@ export default App.controller( } case 'toggle-review-panel': { - ide.toggleReviewPanel() + $scope.toggleReviewPanel() break } } }) + + // Add methods somewhere that React can see them + $scope.reviewPanel.saveEdit = $scope.saveEdit } ) diff --git a/services/web/frontend/stylesheets/app/editor/review-panel.less b/services/web/frontend/stylesheets/app/editor/review-panel.less index 2dbbe7d04a..fc864ca0d4 100644 --- a/services/web/frontend/stylesheets/app/editor/review-panel.less +++ b/services/web/frontend/stylesheets/app/editor/review-panel.less @@ -1109,3 +1109,115 @@ button when (@is-overleaf-light = true) { .rp-flex-block { display: block; } + +// CM6-specific review panel rules +.ol-cm-review-panel { + display: block; + flex-shrink: 0; + + .rp-size-expanded & { + display: flex; + flex-direction: column; + width: @review-panel-width; + overflow: visible; + border-left-width: 1px; + } + + .rp-size-mini & { + width: @review-off-width; + z-index: 6; + border-left-width: 1px; + } + + background-color: @rp-bg-blue; + border-left: solid 0 @rp-border-grey; + font-family: @font-family-base; + line-height: @line-height-base; + font-size: @rp-base-font-size; + color: @rp-type-blue; + z-index: 6; + + position: relative; + box-sizing: content-box; + flex-direction: column; + + .review-panel-toolbar { + & .review-panel-toolbar-collapse-button { + border: none; + background: none; + padding: 0; + } + } + + .rp-state-current-file & { + .review-panel-tools { + display: flex; + flex-direction: column; + justify-content: space-between; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + .review-panel-toolbar { + position: sticky; + top: 0; + } + + .rp-nav { + position: sticky; + bottom: 0; + } + + .review-panel-toggler { + position: sticky; + top: 0; + } + } + + .rp-state-overview & { + position: sticky; + top: 0; + display: flex; + flex-direction: column; + + .review-panel-toggler { + position: absolute; + top: 0; + bottom: 0; + left: 0; + } + } + + .rp-nav-item { + background: none; + } + + .rp-entry-list { + display: inline-flex; + flex-direction: column; + + .rp-overview-file { + .rp-overview-file-entries { + //height: auto; + transition: height ease-in-out 0.15s; //, display 0.15s 0s; + } + + .rp-overview-file-num-entries { + display: none; + } + + &.rp-overview-file-collapse { + .rp-overview-file-num-entries { + display: inline; + } + + .rp-overview-file-entries { + // height: 0; + } + } + } + } +}