• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

EcrituresNumeriques / stylo / 14472219216

15 Apr 2025 02:37PM UTC coverage: 31.374% (-0.01%) from 31.388%
14472219216

push

github

web-flow
chore: utilise SWR pour gérer les versions d'un article (#1370)

470 of 708 branches covered (66.38%)

Branch coverage included in aggregate %.

53 of 436 new or added lines in 16 files covered. (12.16%)

233 existing lines in 11 files now uncovered.

4521 of 15200 relevant lines covered (29.74%)

2.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/front/src/components/collaborative/CollaborativeTextEditor.jsx
1
import Editor from '@monaco-editor/react'
×
NEW
2
import throttle from 'lodash.throttle'
×
3
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
×
NEW
4
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
×
5
import { MonacoBinding } from 'y-monaco'
×
6
import { applicationConfig } from '../../config.js'
×
NEW
7
import Loading from '../molecules/Loading.jsx'
×
NEW
8
import defaultEditorOptions from '../Write/providers/monaco/options.js'
×
NEW
9
import * as collaborating from './collaborating.js'
×
NEW
10
import CollaborativeEditorStatus from './CollaborativeEditorStatus.jsx'
×
11
import CollaborativeEditorWebSocketStatus from './CollaborativeEditorWebSocketStatus.jsx'
×
12

13
import styles from './CollaborativeTextEditor.module.scss'
×
14

UNCOV
15
const colors = [
×
16
  // navy
UNCOV
17
  '#70b8ff',
×
18
  // blue
NEW
19
  '#75bfff',
×
20
  // aqua
NEW
21
  '#7FDBFF',
×
22
  // teal
23
  '#39CCCC',
×
24
  // olive
NEW
25
  '#92d3b6',
×
26
  // green
27
  '#97e7a0',
×
28
  // yellow
29
  '#ffeb66',
×
30
  // orange
31
  '#ffbb80',
×
32
  // red
33
  '#ff726b',
×
34
  // maroon
UNCOV
35
  '#ff6666',
×
36
  // fuchsia
NEW
37
  '#f674d8',
×
38
  // purple
39
  '#e46ff6',
×
40
  // gray
NEW
41
  '#AAAAAA',
×
42
  // silver
43
  '#DDDDDD',
×
NEW
44
]
×
45

46
/**
47
 * @param props
48
 * @param props.articleId
49
 * @return {Element}
50
 */
51
export default function CollaborativeTextEditor({ articleId }) {
×
52
  const connectingRef = useRef(false)
×
53
  const [dynamicStyles, setDynamicStyles] = useState('')
×
54
  const [websocketStatus, setWebsocketStatus] = useState('')
×
UNCOV
55
  const [yText, setYText] = useState(null)
×
NEW
56
  const [awareness, setAwareness] = useState(null)
×
57
  const { websocketEndpoint } = applicationConfig
×
58
  const activeUser = useSelector(
×
NEW
59
    (state) => ({
×
NEW
60
      _id: state.activeUser._id,
×
NEW
61
      email: state.activeUser.email,
×
NEW
62
      displayName: state.activeUser.displayName,
×
NEW
63
      username: state.activeUser.username,
×
NEW
64
    }),
×
NEW
65
    shallowEqual
×
NEW
66
  )
×
67
  const dispatch = useDispatch()
×
68
  const editorRef = useRef(null)
×
69
  const editorCursorPosition = useSelector(
×
UNCOV
70
    (state) => state.editorCursorPosition,
×
NEW
71
    shallowEqual
×
NEW
72
  )
×
73

NEW
74
  const options = useMemo(
×
NEW
75
    () => ({
×
76
      ...defaultEditorOptions,
×
NEW
77
      contextmenu: websocketStatus === 'connected',
×
NEW
78
      readOnly: websocketStatus !== 'connected',
×
NEW
79
    }),
×
NEW
80
    [websocketStatus]
×
NEW
81
  )
×
82

NEW
83
  const handleUpdateArticleStructureAndStats = throttle(
×
NEW
84
    ({ text }) => {
×
NEW
85
      dispatch({ type: 'UPDATE_ARTICLE_STATS', md: text })
×
NEW
86
      dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md: text })
×
NEW
87
    },
×
NEW
88
    250,
×
NEW
89
    { leading: false, trailing: true }
×
NEW
90
  )
×
91

NEW
92
  const writerInfo = useMemo(
×
NEW
93
    () => ({
×
NEW
94
      id: activeUser._id,
×
95
      email: activeUser.email,
×
NEW
96
      displayName: activeUser.displayName,
×
NEW
97
      username: activeUser.username,
×
NEW
98
      color: colors[Math.floor(Math.random() * 14)],
×
NEW
99
    }),
×
NEW
100
    [activeUser]
×
NEW
101
  )
×
102

NEW
103
  const handleWritersUpdated = useCallback(
×
UNCOV
104
    ({ states }) => {
×
105
      const writers = Object.fromEntries(states)
×
106
      dispatch({ type: 'UPDATE_ARTICLE_WRITERS', articleWriters: writers })
×
107
      setDynamicStyles(
×
108
        Object.entries(writers)
×
109
          .map(([key, writer]) => {
×
110
            const color = writer.user.color
×
111
            return `
×
112
.yRemoteSelection-${key} {
×
UNCOV
113
  background-color: ${color};
×
114
}
NEW
115
.yRemoteSelectionHead-${key} {
×
NEW
116
  border-left: ${color} solid 2px;
×
NEW
117
  border-top: ${color} solid 2px;
×
NEW
118
  border-bottom: ${color} solid 2px;
×
119
}`
120
          })
×
UNCOV
121
          .join('\n')
×
NEW
122
      )
×
NEW
123
    },
×
NEW
124
    [setDynamicStyles]
×
NEW
125
  )
×
126

127
  const handleWebsocketStatusUpdated = useCallback(
×
128
    (status) => {
×
NEW
129
      setWebsocketStatus(status)
×
130
    },
×
131
    [setWebsocketStatus]
×
132
  )
×
133

NEW
134
  const handleEditorDidMount = useCallback(
×
NEW
135
    (editor) => {
×
NEW
136
      editorRef.current = editor
×
NEW
137
      new MonacoBinding(yText, editor.getModel(), new Set([editor]), awareness)
×
NEW
138
    },
×
NEW
139
    [yText, awareness]
×
NEW
140
  )
×
141

NEW
142
  useEffect(() => {
×
NEW
143
    if (connectingRef.current) {
×
NEW
144
      return
×
NEW
145
    }
×
NEW
146
    connectingRef.current = true
×
NEW
147
    const {
×
NEW
148
      awareness,
×
NEW
149
      doc: yDocument,
×
NEW
150
      wsProvider,
×
NEW
151
    } = collaborating.connect({
×
NEW
152
      roomName: articleId,
×
NEW
153
      websocketEndpoint,
×
NEW
154
      user: writerInfo,
×
NEW
155
      onChange: handleWritersUpdated,
×
UNCOV
156
      onStatusUpdated: handleWebsocketStatusUpdated,
×
UNCOV
157
    })
×
158
    const yText = yDocument.getText('main')
×
UNCOV
159
    yText.observe(function () {
×
UNCOV
160
      handleUpdateArticleStructureAndStats({ text: yText.toString() })
×
UNCOV
161
    })
×
UNCOV
162
    setAwareness(awareness)
×
UNCOV
163
    setYText(yText)
×
UNCOV
164
    return () => {
×
UNCOV
165
      connectingRef.current = false
×
UNCOV
166
      awareness.destroy()
×
UNCOV
167
      if (wsProvider.wsconnected) {
×
UNCOV
168
        wsProvider.disconnect()
×
UNCOV
169
        wsProvider.destroy()
×
UNCOV
170
      }
×
UNCOV
171
    }
×
UNCOV
172
  }, [articleId, websocketEndpoint, writerInfo])
×
173

UNCOV
174
  useEffect(() => {
×
UNCOV
175
    const line = editorCursorPosition.lineNumber
×
UNCOV
176
    const editor = editorRef.current
×
UNCOV
177
    editor?.focus()
×
UNCOV
178
    const endOfLineColumn = editor?.getModel()?.getLineMaxColumn(line + 1)
×
UNCOV
179
    editor?.setPosition({ lineNumber: line + 1, column: endOfLineColumn })
×
UNCOV
180
    editor?.revealLineNearTop(line + 1, 1) // smooth
×
UNCOV
181
  }, [editorRef, editorCursorPosition])
×
182

UNCOV
183
  if (!yText) {
×
UNCOV
184
    return <Loading />
×
UNCOV
185
  }
×
186

UNCOV
187
  return (
×
UNCOV
188
    <>
×
UNCOV
189
      <style>{dynamicStyles}</style>
×
UNCOV
190
      <CollaborativeEditorStatus />
×
UNCOV
191
      <div className={styles.inlineStatus}>
×
UNCOV
192
        <CollaborativeEditorWebSocketStatus status={websocketStatus} />
×
UNCOV
193
      </div>
×
UNCOV
194
      <Editor
×
UNCOV
195
        width={'100%'}
×
UNCOV
196
        height={'auto'}
×
UNCOV
197
        options={options}
×
UNCOV
198
        className={styles.editor}
×
UNCOV
199
        defaultLanguage="markdown"
×
UNCOV
200
        onMount={handleEditorDidMount}
×
UNCOV
201
      />
×
UNCOV
202
    </>
×
203
  )
UNCOV
204
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc