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

EcrituresNumeriques / stylo / 14198984569

01 Apr 2025 02:57PM UTC coverage: 32.834% (+1.1%) from 31.772%
14198984569

push

github

web-flow
feat: l'éditeur collaboratif devient l'éditeur par défaut! (#1378)

* feat: les changements des éditions collaboratives sont enregistrés periodiquement dans la copie de travail

* chore: supprime la notion de session collaborative

* chore: supprime le code lié aux sessions

* chore: met à jour la date de dernière modification du document

* fix: supprime useHistory (non utilisé)

* chore: ordre des imports

* fix: corrige les imports (linter)

* chore: corrige la migration des données

* chore: mise à jour de la configuration de yjs/collaboration

Utilise un nouveau dossier pour la persistence des données afin de
sécuriser la migration des données

* chore: plus besoin de faire un calc avec la position relative

* chore: supprime la validation pour les tests

470 of 701 branches covered (67.05%)

Branch coverage included in aggregate %.

15 of 81 new or added lines in 12 files covered. (18.52%)

229 existing lines in 7 files now uncovered.

4483 of 14384 relevant lines covered (31.17%)

2.34 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'
×
2
import throttle from 'lodash.throttle'
×
NEW
3
import PropTypes from 'prop-types'
×
4
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
×
5
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
×
6
import { MonacoBinding } from 'y-monaco'
×
7
import { applicationConfig } from '../../config.js'
×
8
import Loading from '../molecules/Loading.jsx'
×
9
import defaultEditorOptions from '../Write/providers/monaco/options.js'
×
NEW
10
import * as collaborating from './collaborating.js'
×
11
import CollaborativeEditorStatus from './CollaborativeEditorStatus.jsx'
×
12
import CollaborativeEditorWebSocketStatus from './CollaborativeEditorWebSocketStatus.jsx'
×
13

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

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

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

79
  const options = useMemo(
×
80
    () => ({
×
81
      ...defaultEditorOptions,
×
82
      contextmenu: websocketStatus === 'connected',
×
NEW
83
      readOnly: websocketStatus !== 'connected',
×
84
    }),
×
NEW
85
    [websocketStatus]
×
86
  )
×
87

88
  const handleUpdateArticleStructureAndStats = throttle(
×
89
    ({ text }) => {
×
90
      dispatch({ type: 'UPDATE_ARTICLE_STATS', md: text })
×
91
      dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md: text })
×
92
    },
×
93
    250,
×
94
    { leading: false, trailing: true }
×
95
  )
×
96

97
  const writerInfo = useMemo(
×
98
    () => ({
×
99
      id: activeUser._id,
×
100
      email: activeUser.email,
×
101
      displayName: activeUser.displayName,
×
102
      username: activeUser.username,
×
103
      color: colors[Math.floor(Math.random() * 14)],
×
104
    }),
×
105
    [activeUser]
×
106
  )
×
107

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

132
  const handleWebsocketStatusUpdated = useCallback(
×
133
    (status) => {
×
134
      setWebsocketStatus(status)
×
135
    },
×
136
    [setWebsocketStatus]
×
137
  )
×
138

139
  const handleEditorDidMount = useCallback(
×
140
    (editor) => {
×
141
      editorRef.current = editor
×
142
      new MonacoBinding(yText, editor.getModel(), new Set([editor]), awareness)
×
143
    },
×
144
    [yText, awareness]
×
145
  )
×
146

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

183
  useEffect(() => {
×
184
    const line = editorCursorPosition.lineNumber
×
185
    const editor = editorRef.current
×
186
    editor?.focus()
×
187
    const endOfLineColumn = editor?.getModel()?.getLineMaxColumn(line + 1)
×
188
    editor?.setPosition({ lineNumber: line + 1, column: endOfLineColumn })
×
189
    editor?.revealLineNearTop(line + 1, 1) // smooth
×
190
  }, [editorRef, editorCursorPosition])
×
191

192
  if (!yText) {
×
193
    return <Loading />
×
194
  }
×
195

196
  return (
×
197
    <>
×
198
      <style>{dynamicStyles}</style>
×
NEW
199
      <CollaborativeEditorStatus />
×
200
      <div className={styles.inlineStatus}>
×
NEW
201
        <CollaborativeEditorWebSocketStatus status={websocketStatus} />
×
202
      </div>
×
203
      <Editor
×
204
        width={'100%'}
×
205
        height={'auto'}
×
206
        options={options}
×
207
        className={styles.editor}
×
208
        defaultLanguage="markdown"
×
209
        onMount={handleEditorDidMount}
×
210
      />
×
211
    </>
×
212
  )
213
}
×
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