overleaf/services/web/frontend/js/features/source-editor/extensions/add-comment.ts
David a323f3af75 Implement a floating "Add comment" button for the redesigned review panel (#19891)
* Implement floating Add comment button

* Fix comment typo

* Remove unused imports

* Make tooltip always appear above cursor

Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com>

* Refactor how new comment form is positioned

* Add missing file

* Create new map when rendering positions

* Use codemirror state to manage ranges and allow for mutliple in-progress comments

* Memoise sorting

* Create new ranges map each time it is changed

* Add back mutation observer

* Only allow single tooltip

* Fix typo

* Convert state field to store a single tooltip

* Make add comment tooltip content a react component

* Refactor to remove usages of !important

* Use RangeSet to keep track of new comment ranges

* Fix logic broken in rebase

* Map ranges through document changes

* Add decorations for in-progress comments

* Use set-review-panel-open rather than an editor event to open review panel

* Implement new designs for add comment form

* Add padding to textarea

* Fix bug where comment was being submitted for incorrect range

* Add missing key to ReviewPanelAddComment

* Store new comment ranges as a DecorationSet

* Small refactor to how ReviewPanelAddCommens are rendered

* Make op prop to ReviewPanelEntry required

* Add handling for disabling of add comemnt form buttons

* Move viewer check inside AddCommentTooltip

* Ensure that add comment button doesn't reshow when collaborators edit the document

* Remove unneeded op check in ReviewPanelEntry

* Update services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx

Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com>

---------

Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com>
GitOrigin-RevId: 3110845f6a557310f3bf72014689e2f2ab53e966
2024-09-17 08:04:58 +00:00

134 lines
3 KiB
TypeScript

import {
Decoration,
DecorationSet,
EditorView,
showTooltip,
Tooltip,
} from '@codemirror/view'
import {
EditorState,
Extension,
StateField,
StateEffect,
Range,
SelectionRange,
} from '@codemirror/state'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { v4 as uuid } from 'uuid'
export const addNewCommentRangeEffect = StateEffect.define<Range<Decoration>>()
export const removeNewCommentRangeEffect = StateEffect.define<Decoration>()
export const buildAddNewCommentRangeEffect = (range: SelectionRange) => {
return addNewCommentRangeEffect.of(
Decoration.mark({
tagName: 'span',
class: `ol-cm-change ol-cm-change-c`,
opType: 'c',
id: uuid(),
}).range(range.from, range.to)
)
}
export const addComment = (): Extension => {
if (!isSplitTestEnabled('review-panel-redesign')) {
return []
}
return [addCommentTheme, addCommentStateField]
}
export const addCommentStateField = StateField.define<{
tooltip: Tooltip | null
ranges: DecorationSet
}>({
create() {
return { tooltip: null, ranges: Decoration.none }
},
update(field, tr) {
let { tooltip, ranges } = field
ranges = ranges.map(tr.changes)
for (const effect of tr.effects) {
if (effect.is(removeNewCommentRangeEffect)) {
const rangeToRemove = effect.value
ranges = ranges.update({
// eslint-disable-next-line no-unused-vars
filter: (from, to, value) => {
return value.spec.id !== rangeToRemove.spec.id
},
})
}
if (effect.is(addNewCommentRangeEffect)) {
const rangeToAdd = effect.value
ranges = ranges.update({
add: [rangeToAdd],
})
}
}
if (tr.docChanged || tr.selection) {
tooltip = buildTooltip(tr.state)
}
return { tooltip, ranges }
},
provide: field => [
EditorView.decorations.from(field, field => field.ranges),
showTooltip.compute([field], state => state.field(field).tooltip),
],
})
function buildTooltip(state: EditorState): Tooltip | null {
const range = state.selection.main
if (range.empty) {
return null
}
return {
pos: range.assoc < 0 ? range.to : range.from,
above: true,
strictSide: true,
arrow: false,
create() {
const dom = document.createElement('div')
dom.className = 'review-panel-add-comment-tooltip-container'
return { dom, overlap: true, offset: { x: 0, y: 8 } }
},
}
}
/**
* Styles for the tooltip
*/
const addCommentTheme = EditorView.baseTheme({
'.review-panel-add-comment-tooltip-container.cm-tooltip': {
backgroundColor: 'transparent',
border: 'none',
},
'&light': {
'& .review-panel-add-comment-tooltip': {
backgroundColor: 'white',
border: '1px solid #e7e9ee',
'&:hover': {
backgroundColor: '#e7e9ee',
},
},
},
'&dark': {
'& .review-panel-add-comment-tooltip': {
backgroundColor: '#1b222c',
border: '1px solid #2f3a4c',
'&:hover': {
backgroundColor: '#2f3a4c',
},
},
},
})