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

EcrituresNumeriques / stylo / 14865094533

06 May 2025 04:42PM UTC coverage: 37.581% (-0.1%) from 37.729%
14865094533

push

github

web-flow
Prévisualisation en ligne (#1469)

Co-authored-by: Thomas Parisot <thom4parisot@users.noreply.github.com>

532 of 759 branches covered (70.09%)

Branch coverage included in aggregate %.

0 of 124 new or added lines in 14 files covered. (0.0%)

1 existing line in 1 file now uncovered.

5239 of 14597 relevant lines covered (35.89%)

2.51 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 React, { useCallback, useEffect, useMemo, useRef } from 'react'
×
NEW
2
import { Helmet } from 'react-helmet'
×
NEW
3
import throttle from 'lodash.throttle'
×
4
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
×
5
import { MonacoBinding } from 'y-monaco'
×
6

7
import { useArticleVersion, useEditableArticle } from '../../hooks/article.js'
×
NEW
8
import { useStyloExportPreview } from '../../hooks/stylo-export.js'
×
9
import { useBibliographyCompletion } from '../../hooks/bibliography.js'
×
10
import { useCollaboration } from '../../hooks/collaboration.js'
×
11

NEW
12
import CollaborativeEditorWebSocketStatus from './CollaborativeEditorWebSocketStatus.jsx'
×
NEW
13
import Alert from '../molecules/Alert.jsx'
×
14
import Loading from '../molecules/Loading.jsx'
×
15
import defaultEditorOptions from '../Write/providers/monaco/options.js'
×
16

17
import styles from './CollaborativeTextEditor.module.scss'
×
18
import MonacoEditor from '../molecules/MonacoEditor.jsx'
×
19

20
/**
21
 * @param {object} props
22
 * @param {string} props.articleId
23
 * @param {string|undefined} props.versionId
24
 * @param {'write' | 'compare' | 'preview'} props.mode
25
 * @returns {Element}
26
 */
NEW
27
export default function CollaborativeTextEditor({
×
NEW
28
  articleId,
×
NEW
29
  versionId,
×
NEW
30
  mode,
×
NEW
31
}) {
×
32
  const { yText, awareness, websocketStatus, dynamicStyles } = useCollaboration(
×
33
    { articleId, versionId }
×
34
  )
×
35

36
  const { version, error, isLoading } = useArticleVersion({ versionId })
×
37
  const { provider: bibliographyCompletionProvider } =
×
38
    useBibliographyCompletion()
×
NEW
39
  const { article, bibliography } = useEditableArticle({
×
40
    articleId,
×
41
    versionId,
×
42
  })
×
43

NEW
44
  const { html: __html, isLoading: isPreviewLoading } = useStyloExportPreview({
×
NEW
45
    ...(mode === 'preview'
×
NEW
46
      ? {
×
NEW
47
          md_content: versionId ? version.md : yText?.toString(),
×
NEW
48
          yaml_content: versionId
×
NEW
49
            ? version.yaml
×
NEW
50
            : article?.workingVersion?.yaml,
×
NEW
51
          bib_content: versionId ? version.bib : article?.workingVersion?.bib,
×
NEW
52
        }
×
NEW
53
      : {}),
×
NEW
54
    with_toc: true,
×
NEW
55
    with_nocite: true,
×
NEW
56
    with_link_citations: true,
×
NEW
57
  })
×
58

59
  const dispatch = useDispatch()
×
60
  const editorRef = useRef(null)
×
61
  const editorCursorPosition = useSelector(
×
62
    (state) => state.editorCursorPosition,
×
63
    shallowEqual
×
64
  )
×
65

66
  const hasVersion = useMemo(() => !!versionId, [versionId])
×
67

68
  const options = useMemo(
×
69
    () => ({
×
70
      ...defaultEditorOptions,
×
71
      contextmenu: hasVersion ? false : websocketStatus === 'connected',
×
72
      readOnly: hasVersion ? true : websocketStatus !== 'connected',
×
73
    }),
×
74
    [websocketStatus, hasVersion]
×
75
  )
×
76

77
  const handleUpdateArticleStructureAndStats = throttle(
×
78
    ({ text }) => {
×
79
      dispatch({ type: 'UPDATE_ARTICLE_STATS', md: text })
×
80
      dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md: text })
×
81
    },
×
82
    250,
×
83
    { leading: false, trailing: true }
×
84
  )
×
85

86
  const handleCollaborativeEditorDidMount = useCallback(
×
87
    (editor, monaco) => {
×
88
      editorRef.current = editor
×
89
      const completionProvider = bibliographyCompletionProvider.register(monaco)
×
90
      editor.onDidDispose(() => completionProvider.dispose())
×
91
      const model = editor.getModel()
×
92
      // Set EOL to LF otherwise it causes synchronization issues due to inconsistent EOL between Windows and Linux.
93
      // https://github.com/yjs/y-monaco/issues/27
94
      model.setEOL(monaco.editor.EndOfLineSequence.LF)
×
95
      if (yText && awareness) {
×
96
        new MonacoBinding(yText, model, new Set([editor]), awareness)
×
97
      }
×
98
    },
×
99
    [yText, awareness]
×
100
  )
×
101

102
  const handleEditorDidMount = useCallback((editor) => {
×
103
    editorRef.current = editor
×
104
  }, [])
×
105

106
  let timeoutId
×
107
  useEffect(() => {
×
108
    if (yText) {
×
109
      yText.observe(function (yTextEvent, transaction) {
×
110
        dispatch({
×
111
          type: 'UPDATE_ARTICLE_WORKING_COPY_STATUS',
×
112
          status: 'syncing',
×
113
        })
×
114
        if (timeoutId) {
×
115
          clearTimeout(timeoutId)
×
116
        }
×
117
        timeoutId = setTimeout(() => {
×
118
          dispatch({
×
119
            type: 'UPDATE_ARTICLE_WORKING_COPY_STATUS',
×
120
            status: 'synced',
×
121
          })
×
122
        }, 4000)
×
123

124
        handleUpdateArticleStructureAndStats({ text: yText.toString() })
×
125
      })
×
126
    }
×
127
  }, [yText])
×
128

129
  useEffect(() => {
×
130
    if (version) {
×
131
      dispatch({ type: 'UPDATE_ARTICLE_STATS', md: version.md })
×
132
      dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md: version.md })
×
133
    }
×
134
  }, [version])
×
135

136
  useEffect(() => {
×
137
    if (bibliography) {
×
138
      bibliographyCompletionProvider.bibTeXEntries = bibliography.entries
×
139
    }
×
140
  }, [bibliography])
×
141

142
  useEffect(() => {
×
143
    const line = editorCursorPosition.lineNumber
×
144
    const editor = editorRef.current
×
145
    editor?.focus()
×
146
    const endOfLineColumn = editor?.getModel()?.getLineMaxColumn(line + 1)
×
147
    editor?.setPosition({ lineNumber: line + 1, column: endOfLineColumn })
×
148
    editor?.revealLineNearTop(line + 1, 1) // smooth
×
149
  }, [editorRef, editorCursorPosition])
×
150

151
  if (!yText && !version) {
×
152
    return <Loading />
×
153
  }
×
154

NEW
155
  if (isLoading || isPreviewLoading) {
×
156
    return <Loading />
×
157
  }
×
158

159
  if (error) {
×
160
    return <Alert message={error.message} />
×
161
  }
×
162

163
  return (
×
164
    <>
×
165
      <style>{dynamicStyles}</style>
×
NEW
166
      <Helmet>
×
NEW
167
        <title>{article.title}</title>
×
NEW
168
      </Helmet>
×
169

NEW
170
      <CollaborativeEditorWebSocketStatus
×
NEW
171
        className={styles.inlineStatus}
×
NEW
172
        status={websocketStatus}
×
NEW
173
      />
×
174

175
      {version && (
×
176
        <MonacoEditor
×
177
          width={'100%'}
×
178
          height={'auto'}
×
179
          value={version.md}
×
180
          options={options}
×
181
          className={styles.editor}
×
182
          defaultLanguage="markdown"
×
183
          onMount={handleEditorDidMount}
×
184
        />
×
185
      )}
186

NEW
187
      {mode === 'preview' && (
×
NEW
188
        <section
×
NEW
189
          className={styles.previewPage}
×
NEW
190
          dangerouslySetInnerHTML={{ __html }}
×
NEW
191
        />
×
192
      )}
193

194
      <div
×
NEW
195
        className={styles.collaborativeEditor}
×
NEW
196
        hidden={Boolean(versionId) || mode !== 'write'}
×
197
      >
198
        <MonacoEditor
×
199
          width={'100%'}
×
200
          height={'auto'}
×
201
          options={options}
×
202
          className={styles.editor}
×
203
          defaultLanguage="markdown"
×
204
          onMount={handleCollaborativeEditorDidMount}
×
205
        />
×
206
      </div>
×
207
    </>
×
208
  )
209
}
×
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