1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
/**
* CodeMirror extension: "Add comment" tooltip on text selection.
* Inspired by Overleaf's review-tooltip.ts.
*/
import {
EditorView,
showTooltip,
type Tooltip,
} from '@codemirror/view'
import {
StateField,
type EditorState,
} from '@codemirror/state'
export type AddCommentCallback = (from: number, to: number, text: string) => void
let _addCommentCallback: AddCommentCallback | null = null
export function setAddCommentCallback(cb: AddCommentCallback | null) {
_addCommentCallback = cb
}
function buildTooltip(state: EditorState): Tooltip | null {
const sel = state.selection.main
if (sel.empty) return null
return {
pos: sel.head,
above: sel.head < sel.anchor,
create() {
const dom = document.createElement('div')
dom.className = 'cm-add-comment-tooltip'
const btn = document.createElement('button')
btn.className = 'cm-add-comment-btn'
btn.textContent = '+ Comment'
btn.addEventListener('mousedown', (e) => {
e.preventDefault() // prevent editor losing focus/selection
})
btn.addEventListener('click', () => {
if (_addCommentCallback) {
const from = Math.min(sel.from, sel.to)
const to = Math.max(sel.from, sel.to)
const text = state.sliceDoc(from, to)
_addCommentCallback(from, to, text)
}
})
dom.appendChild(btn)
return { dom, overlap: true, offset: { x: 0, y: 4 } }
},
}
}
const addCommentTooltipField = StateField.define<Tooltip | null>({
create(state) {
return buildTooltip(state)
},
update(tooltip, tr) {
if (!tr.docChanged && !tr.selection) return tooltip
return buildTooltip(tr.state)
},
provide: (field) => showTooltip.from(field),
})
const addCommentTooltipTheme = EditorView.baseTheme({
'.cm-add-comment-tooltip.cm-tooltip': {
backgroundColor: 'transparent',
border: 'none',
zIndex: '10',
},
'.cm-add-comment-btn': {
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
padding: '4px 10px',
fontSize: '12px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
fontWeight: '600',
color: '#5B4A28',
backgroundColor: '#FFF8E7',
border: '1px solid #D6CEBC',
borderRadius: '6px',
cursor: 'pointer',
boxShadow: '0 2px 8px rgba(0,0,0,0.12)',
transition: 'background 0.15s',
},
'.cm-add-comment-btn:hover': {
backgroundColor: '#F5EDD6',
borderColor: '#B8A070',
},
})
export const addCommentTooltip = () => [
addCommentTooltipField,
addCommentTooltipTheme,
]
|