mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-04 23:05:56 -05:00
2304536844
* Tidy up review panel components * Add ReviewPanel providers * [web] new design for review panel track change (#19544) * [web] new design for review panel track change * fixed mini view * mini icon style change * fix icon size * format date * useRangesUserContext hook * remove useRangesUserContext hook * using full class names * fix action icons hover * change wording for tooltips * added ReviewPanelChangeUser component * Update header in new review panel * Extract ReviewPanelTrackChangesMenuButton as a separate component * Remove wrapper div * Replace h2 with div for review panel label * Rename ReviewPanelTools to ReviewPanelHeader * Rename trackChangesExpanded -> trackChangesMenuExpanded * Dont break memoisation of ReviewPanelTrackChangesMenuButton * Fix the width of the track changes arrow icon * Update how prop types are declared * Remove new empty state from old review panel * Add empty state to new review panel * Add project members and owner to ChangesUsers context (#19624) --------- Co-authored-by: Alf Eaton <alf.eaton@overleaf.com> * Redesign comment entry in review panel (#19678) * Redesign comment entry in review panel * ReviewPanelCommentOptions component * remove unused prop * Tidying * Add conditional import * Optional changeManager * Add more split test compatibility * More split test compatibility * Fixes * Improve overview scrolling * Fix overview scrolling * Fix & simplify track changes toggle * Fix overview scrolling * Fix current file container * ExpandableContent component for messages in review panel (#19738) * ExpandableContent component for messages in review panel * remove isExpanded dependancy * Delete comment option for new review panel (#19772) * Delete comment option for new review panel * dont show thread warning if there are no replies * fix hasReplies issue * Implement initial collapsing overview files * Fix positioning of overview panel * Small styling changes * Add count of unresolved comments and tracked chanegs * More style adjustments * Move review-panel-overview styles into css file * Remove unused var --------- Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com> Co-authored-by: David Powell <david.powell@overleaf.com> Co-authored-by: David <33458145+davidmcpowell@users.noreply.github.com> GitOrigin-RevId: e67463443d541f88445a86eed5e2b6ec6040f9c7
251 lines
6.7 KiB
TypeScript
251 lines
6.7 KiB
TypeScript
import {
|
|
Extension,
|
|
Facet,
|
|
StateEffect,
|
|
StateField,
|
|
TransactionSpec,
|
|
} from '@codemirror/state'
|
|
import {
|
|
Decoration,
|
|
EditorView,
|
|
ViewPlugin,
|
|
ViewUpdate,
|
|
WidgetType,
|
|
} from '@codemirror/view'
|
|
|
|
/**
|
|
* A custom extension which stores values for padding needed
|
|
* a) at the top and bottom of the editor, to match the height of the review panel, and
|
|
* b) at the bottom of the editor content, so the last line of the document can be scrolled to the top of the editor.
|
|
*/
|
|
export function verticalOverflow(): Extension {
|
|
return [
|
|
overflowPaddingState,
|
|
minimumBottomPaddingState,
|
|
bottomPadding,
|
|
topPadding,
|
|
contentAttributes,
|
|
topPaddingDecoration,
|
|
bottomPaddingPlugin,
|
|
topPaddingPlugin,
|
|
]
|
|
}
|
|
|
|
type VerticalPadding = { top: number; bottom: number }
|
|
|
|
const setOverflowPaddingEffect = StateEffect.define<VerticalPadding>()
|
|
|
|
// Store extra padding needed at the top and bottom of the editor to match the height of the review panel.
|
|
// The padding needs to allow enough space for tracked changes/comments at the top and/or bottom of the review panel.
|
|
const overflowPaddingState = StateField.define<VerticalPadding>({
|
|
create() {
|
|
return { top: 0, bottom: 0 }
|
|
},
|
|
update(value, tr) {
|
|
for (const effect of tr.effects) {
|
|
if (effect.is(setOverflowPaddingEffect)) {
|
|
const { top, bottom } = effect.value
|
|
// only update the state when the values actually change
|
|
if (top !== value.top || bottom !== value.bottom) {
|
|
value = { top, bottom }
|
|
}
|
|
}
|
|
}
|
|
return value
|
|
},
|
|
})
|
|
|
|
const setMinimumBottomPaddingEffect = StateEffect.define<number>()
|
|
|
|
// Store extra padding needed at the bottom of the editor content.
|
|
// The content must have a space at the bottom equivalent to the
|
|
// height of the editor content minus one line, so that the last
|
|
// line in the document can be scrolled to the top of the editor.
|
|
const minimumBottomPaddingState = StateField.define<number>({
|
|
create() {
|
|
return 0
|
|
},
|
|
update(value, tr) {
|
|
for (const effect of tr.effects) {
|
|
if (effect.is(setMinimumBottomPaddingEffect)) {
|
|
value = effect.value
|
|
}
|
|
}
|
|
return value
|
|
},
|
|
})
|
|
|
|
// Set scrollTop to counteract changes to the top padding.
|
|
// This view plugin is needed because the overflowPaddingState StateField doesn't have access to the view.
|
|
const topPaddingPlugin = ViewPlugin.define(view => {
|
|
let previousTop = 0
|
|
|
|
return {
|
|
update: update => {
|
|
const { top } = update.state.field(overflowPaddingState)
|
|
if (top !== previousTop) {
|
|
const diff = top - previousTop
|
|
|
|
if (diff < 0) {
|
|
// padding is decreasing, scroll now
|
|
view.scrollDOM.scrollTop += diff
|
|
} else {
|
|
// padding is increasing, scroll after it has been applied
|
|
view.requestMeasure({
|
|
key: 'vertical-overflow-scroll-top',
|
|
read() {
|
|
// do nothing
|
|
},
|
|
write(measure, view) {
|
|
view.scrollDOM.scrollTop += diff
|
|
},
|
|
})
|
|
}
|
|
|
|
previousTop = top
|
|
}
|
|
},
|
|
}
|
|
})
|
|
|
|
/**
|
|
* When the editor geometry changes, recalculate the amount of padding needed at
|
|
* the end of the doc: (the scrollDOM height - 1 line height).
|
|
* Adapted from the CodeMirror 6 scrollPastEnd extension, licensed under the MIT
|
|
* license:
|
|
* https://github.com/codemirror/view/blob/main/src/scrollpastend.ts
|
|
*/
|
|
const bottomPaddingPlugin = ViewPlugin.define(view => {
|
|
let previousHeight = 0
|
|
|
|
const measure = {
|
|
key: 'vertical-overflow-bottom-padding',
|
|
read(view: EditorView) {
|
|
return view.scrollDOM.clientHeight - view.defaultLineHeight
|
|
},
|
|
write(height: number, view: EditorView) {
|
|
if (height !== previousHeight) {
|
|
// dispatch must be wrapped in a timeout to avoid clashing with the current update
|
|
window.setTimeout(() =>
|
|
view.dispatch({
|
|
effects: setMinimumBottomPaddingEffect.of(height),
|
|
})
|
|
)
|
|
previousHeight = height
|
|
}
|
|
},
|
|
}
|
|
|
|
view.requestMeasure(measure)
|
|
|
|
return {
|
|
update: update => {
|
|
if (update.geometryChanged) {
|
|
update.view.requestMeasure(measure)
|
|
}
|
|
},
|
|
}
|
|
})
|
|
|
|
const topPaddingFacet = Facet.define<number, number>({
|
|
combine(values) {
|
|
return Math.max(0, ...values)
|
|
},
|
|
})
|
|
const topPadding = topPaddingFacet.from(overflowPaddingState, state => {
|
|
return state.top
|
|
})
|
|
|
|
const bottomPaddingFacet = Facet.define<number, number>({
|
|
combine(values) {
|
|
return Math.max(0, ...values)
|
|
},
|
|
})
|
|
const bottomPadding = bottomPaddingFacet.computeN(
|
|
[overflowPaddingState, minimumBottomPaddingState],
|
|
state => {
|
|
return [
|
|
state.field(minimumBottomPaddingState),
|
|
state.field(overflowPaddingState).bottom,
|
|
]
|
|
}
|
|
)
|
|
|
|
// Set a style attribute on the contentDOM containing the calculated bottom padding.
|
|
// This value will be concatenated with style values from any other extensions.
|
|
const contentAttributes = EditorView.contentAttributes.compute(
|
|
[bottomPaddingFacet],
|
|
state => {
|
|
const bottom = state.facet(bottomPaddingFacet)
|
|
const style = `padding-bottom: ${bottom}px;`
|
|
return { style }
|
|
}
|
|
)
|
|
|
|
class TopPaddingWidget extends WidgetType {
|
|
constructor(private readonly height: number) {
|
|
super()
|
|
this.height = height
|
|
}
|
|
|
|
toDOM(view: EditorView): HTMLElement {
|
|
const element = document.createElement('div')
|
|
element.style.height = this.height + 'px'
|
|
return element
|
|
}
|
|
|
|
get estimatedHeight() {
|
|
return this.height
|
|
}
|
|
|
|
eq(widget: TopPaddingWidget) {
|
|
return this.height === widget.height
|
|
}
|
|
|
|
updateDOM(element: HTMLElement, view: EditorView): boolean {
|
|
element.style.height = this.height + 'px'
|
|
view.requestMeasure()
|
|
return true
|
|
}
|
|
}
|
|
|
|
const topPaddingDecoration = EditorView.decorations.compute(
|
|
[topPaddingFacet],
|
|
state => {
|
|
const top = state.facet(topPaddingFacet)
|
|
|
|
return Decoration.set([
|
|
Decoration.widget({
|
|
widget: new TopPaddingWidget(top),
|
|
block: true,
|
|
}).range(0),
|
|
])
|
|
}
|
|
)
|
|
|
|
export function setVerticalOverflow(padding: VerticalPadding): TransactionSpec {
|
|
return {
|
|
effects: [setOverflowPaddingEffect.of(padding)],
|
|
}
|
|
}
|
|
|
|
export function updateSetsVerticalOverflow(update: ViewUpdate): boolean {
|
|
return update.transactions.some(tr => {
|
|
return tr.effects.some(effect => effect.is(setOverflowPaddingEffect))
|
|
})
|
|
}
|
|
|
|
export function updateChangesTopPadding(update: ViewUpdate): boolean {
|
|
return (
|
|
update.state.field(overflowPaddingState).top !==
|
|
update.startState.field(overflowPaddingState).top
|
|
)
|
|
}
|
|
|
|
export function editorVerticalTopPadding(view: EditorView): number {
|
|
return view.state.field(overflowPaddingState, false)?.top ?? 0
|
|
}
|
|
|
|
export function editorOverflowPadding(view: EditorView) {
|
|
return view.state.field(overflowPaddingState, false)
|
|
}
|