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

EcrituresNumeriques / stylo / 14886888435

07 May 2025 03:11PM UTC coverage: 37.293% (-0.3%) from 37.615%
14886888435

push

github

web-flow
feat(writer): charge les images sur imgur lors d'un drag'n'drop (#1493)

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

532 of 759 branches covered (70.09%)

Branch coverage included in aggregate %.

1 of 94 new or added lines in 4 files covered. (1.06%)

42 existing lines in 2 files now uncovered.

5241 of 14721 relevant lines covered (35.6%)

2.49 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'
×
2
import { Helmet } from 'react-helmet'
×
3
import throttle from 'lodash.throttle'
×
4
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
×
5
import { MonacoBinding } from 'y-monaco'
×
UNCOV
6

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

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

×
17
import styles from './CollaborativeTextEditor.module.scss'
×
18
import MonacoEditor from '../molecules/MonacoEditor.jsx'
×
NEW
19
import * as vscode from 'monaco-editor'
×
NEW
20
import { onDropIntoEditor } from '../Write/providers/monaco/support.js'
×
UNCOV
21

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

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

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

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

×
68
  const hasVersion = useMemo(() => !!versionId, [versionId])
×
UNCOV
69

×
70
  const options = useMemo(
×
71
    () => ({
×
72
      ...defaultEditorOptions,
×
73
      contextmenu: hasVersion ? false : websocketStatus === 'connected',
×
74
      readOnly: hasVersion ? true : websocketStatus !== 'connected',
×
NEW
75
      dropIntoEditor: {
×
NEW
76
        enabled: true,
×
NEW
77
      },
×
78
    }),
×
79
    [websocketStatus, hasVersion]
×
80
  )
×
UNCOV
81

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

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

×
108
  const handleEditorDidMount = useCallback((editor) => {
×
109
    editorRef.current = editor
×
110
  }, [])
×
UNCOV
111

×
112
  let timeoutId
×
113
  useEffect(() => {
×
114
    if (yText) {
×
115
      yText.observe(function (yTextEvent, transaction) {
×
116
        dispatch({
×
117
          type: 'UPDATE_ARTICLE_WORKING_COPY_STATUS',
×
118
          status: 'syncing',
×
119
        })
×
120
        if (timeoutId) {
×
121
          clearTimeout(timeoutId)
×
122
        }
×
123
        timeoutId = setTimeout(() => {
×
124
          dispatch({
×
125
            type: 'UPDATE_ARTICLE_WORKING_COPY_STATUS',
×
126
            status: 'synced',
×
127
          })
×
128
        }, 4000)
×
UNCOV
129

×
130
        handleUpdateArticleStructureAndStats({ text: yText.toString() })
×
131
      })
×
132
    }
×
133
  }, [yText])
×
UNCOV
134

×
135
  useEffect(() => {
×
136
    if (version) {
×
137
      dispatch({ type: 'UPDATE_ARTICLE_STATS', md: version.md })
×
138
      dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md: version.md })
×
139
    }
×
140
  }, [version])
×
UNCOV
141

×
142
  useEffect(() => {
×
143
    if (bibliography) {
×
144
      bibliographyCompletionProvider.bibTeXEntries = bibliography.entries
×
145
    }
×
146
  }, [bibliography])
×
UNCOV
147

×
148
  useEffect(() => {
×
149
    const line = editorCursorPosition.lineNumber
×
150
    const editor = editorRef.current
×
151
    editor?.focus()
×
152
    const endOfLineColumn = editor?.getModel()?.getLineMaxColumn(line + 1)
×
153
    editor?.setPosition({ lineNumber: line + 1, column: endOfLineColumn })
×
154
    editor?.revealLineNearTop(line + 1, 1) // smooth
×
155
  }, [editorRef, editorCursorPosition])
×
UNCOV
156

×
157
  if (!yText && !version) {
×
158
    return <Loading />
×
159
  }
×
UNCOV
160

×
161
  if (isLoading || isPreviewLoading) {
×
162
    return <Loading />
×
163
  }
×
UNCOV
164

×
165
  if (error) {
×
166
    return <Alert message={error.message} />
×
167
  }
×
UNCOV
168

×
169
  return (
×
170
    <>
×
171
      <style>{dynamicStyles}</style>
×
172
      <Helmet>
×
173
        <title>{article.title}</title>
×
174
      </Helmet>
×
UNCOV
175

×
176
      <CollaborativeEditorWebSocketStatus
×
177
        className={styles.inlineStatus}
×
178
        status={websocketStatus}
×
179
      />
×
UNCOV
180

×
181
      {version && (
×
182
        <MonacoEditor
×
183
          width={'100%'}
×
184
          height={'auto'}
×
185
          value={version.md}
×
186
          options={options}
×
187
          className={styles.editor}
×
188
          defaultLanguage="markdown"
×
189
          onMount={handleEditorDidMount}
×
190
        />
×
UNCOV
191
      )}
×
UNCOV
192

×
193
      {mode === 'preview' && (
×
194
        <section
×
195
          className={styles.previewPage}
×
196
          dangerouslySetInnerHTML={{ __html }}
×
197
        />
×
UNCOV
198
      )}
×
UNCOV
199

×
200
      <div
×
201
        className={styles.collaborativeEditor}
×
202
        hidden={Boolean(versionId) || mode !== 'write'}
×
UNCOV
203
      >
×
204
        <MonacoEditor
×
205
          width={'100%'}
×
206
          height={'auto'}
×
207
          options={options}
×
208
          className={styles.editor}
×
209
          defaultLanguage="markdown"
×
210
          onMount={handleCollaborativeEditorDidMount}
×
211
        />
×
212
      </div>
×
213
    </>
×
UNCOV
214
  )
×
215
}
×
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