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

EcrituresNumeriques / stylo / 19702085718

26 Nov 2025 11:25AM UTC coverage: 40.197% (-0.1%) from 40.324%
19702085718

push

github

web-flow
fix(style): supprime l'utilisation de calc afin de positionner le pied de page de l'éditeur (#1791)

634 of 887 branches covered (71.48%)

Branch coverage included in aggregate %.

19 of 73 new or added lines in 14 files covered. (26.03%)

227 existing lines in 17 files now uncovered.

6267 of 16281 relevant lines covered (38.49%)

2.56 hits per line

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

0.0
/front/src/components/collaborative/CollaborativeTextEditor.jsx
NEW
1
import clsx from 'clsx'
×
2
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
×
3
import { Helmet } from 'react-helmet-async'
×
4
import { useTranslation } from 'react-i18next'
×
5
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
×
6
import { MonacoBinding } from 'y-monaco'
×
7

NEW
8
import {
×
9
  MarkdownMenu,
10
  MetopesMenu,
11
  Separator,
12
  actions,
13
  registerActions,
14
} from './actions'
15
import { DiffEditor } from '@monaco-editor/react'
×
16
import throttle from 'lodash.throttle'
×
17
import 'monaco-editor/esm/vs/base/browser/ui/codicons/codicon/codicon.css'
×
18

19
import { useArticleVersion, useEditableArticle } from '../../hooks/article.js'
×
20
import { useBibliographyCompletion } from '../../hooks/bibliography.js'
×
21
import { useCollaboration } from '../../hooks/collaboration.js'
×
22
import { useStyloExportPreview } from '../../hooks/stylo-export.js'
×
23
import defaultEditorOptions from '../Write/providers/monaco/options.js'
×
24
import { onDropIntoEditor } from '../Write/providers/monaco/support.js'
×
25

26
import Alert from '../molecules/Alert.jsx'
×
27
import Loading from '../molecules/Loading.jsx'
×
28
import MonacoEditor from '../molecules/MonacoEditor.jsx'
×
29
import CollaborativeEditorArticleHeader from './CollaborativeEditorArticleHeader.jsx'
×
30
import CollaborativeEditorWebSocketStatus from './CollaborativeEditorWebSocketStatus.jsx'
×
31

32
import styles from './CollaborativeTextEditor.module.scss'
×
33

34
/**
35
 * @typedef {import('monaco-editor').editor.IStandaloneCodeEditor} IStandaloneCodeEditor
36
 * @typedef {import('monaco-editor')} monaco
37
 */
38

39
/**
40
 * @param {object} props
41
 * @param {string} props.articleId
42
 * @param {string|undefined} props.versionId
43
 * @param {'write' | 'compare' | 'preview'} props.mode
44
 * @returns {Element}
45
 */
46
export default function CollaborativeTextEditor({
×
47
  articleId,
×
48
  versionId,
×
49
  mode,
×
50
}) {
×
51
  const { yText, awareness, websocketStatus, dynamicStyles } = useCollaboration(
×
52
    { articleId, versionId }
×
53
  )
×
54
  const { t } = useTranslation('editor')
×
55

56
  const {
×
57
    version,
×
58
    error,
×
59
    isLoading: isVersionLoading,
×
60
  } = useArticleVersion({ versionId })
×
61
  const { provider: bibliographyCompletionProvider } =
×
62
    useBibliographyCompletion()
×
63
  const {
×
64
    article,
×
65
    bibliography,
×
66
    isLoading: isWorkingVersionLoading,
×
67
  } = useEditableArticle({
×
68
    articleId,
×
69
    versionId,
×
70
  })
×
71

72
  const { html: __html, isLoading: isPreviewLoading } = useStyloExportPreview({
×
73
    ...(mode === 'preview'
×
74
      ? {
×
75
          md_content: versionId ? version.md : yText?.toString(),
×
76
          yaml_content: versionId
×
77
            ? version.yaml
×
78
            : article?.workingVersion?.yaml,
×
79
          bib_content: versionId ? version.bib : article?.workingVersion?.bib,
×
80
        }
×
81
      : {}),
×
82
    with_toc: true,
×
83
    with_nocite: true,
×
84
    with_link_citations: true,
×
85
  })
×
86

87
  const dispatch = useDispatch()
×
88
  const editorRef = useRef(null)
×
89
  const editorCursorPosition = useSelector(
×
90
    (state) => state.editorCursorPosition,
×
91
    shallowEqual
×
92
  )
×
93

94
  const hasVersion = useMemo(() => !!versionId, [versionId])
×
95
  const isLoading =
×
96
    yText === null ||
×
97
    isPreviewLoading ||
×
98
    isWorkingVersionLoading ||
×
99
    isVersionLoading
×
100

101
  const options = useMemo(
×
102
    () => ({
×
103
      ...defaultEditorOptions,
×
104
      contextmenu: hasVersion ? false : websocketStatus === 'connected',
×
105
      readOnly: hasVersion ? true : websocketStatus !== 'connected',
×
106
      dropIntoEditor: {
×
107
        enabled: true,
×
108
      },
×
109
    }),
×
110
    [websocketStatus, hasVersion]
×
111
  )
×
112

113
  const updateArticleStructureAndStats = useCallback(
×
114
    throttle(
×
115
      ({ text: md }) => {
×
116
        dispatch({ type: 'UPDATE_ARTICLE_STATS', md })
×
117
        dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md })
×
118
      },
×
119
      250,
×
120
      { leading: false, trailing: true }
×
121
    ),
×
122
    []
×
123
  )
×
124

125
  const handleCollaborativeEditorDidMount = useCallback(
×
126
    (
×
127
      /** @type {IStandaloneCodeEditor} */ editor,
×
128
      /** @type {monaco} */ monaco
×
129
    ) => {
×
130
      editorRef.current = editor
×
131

132
      editor.onDropIntoEditor(onDropIntoEditor(editor))
×
133

134
      const contextMenu = editor.getContribution('editor.contrib.contextmenu')
×
135
      const originalMenuActions = contextMenu._getMenuActions(
×
136
        editor.getModel(),
×
137
        editor.contextMenuId
×
138
      )
×
139

NEW
140
      contextMenu._getMenuActions = function _getStyloCustomMenuActions() {
×
141
        return [
×
NEW
142
          ...originalMenuActions,
×
NEW
143
          new Separator(),
×
NEW
144
          MetopesMenu({ editor, t }),
×
NEW
145
          MarkdownMenu({ editor, t }),
×
NEW
146
        ]
×
UNCOV
147
      }
×
148

149
      // Command Palette commands
150
      registerActions(editor, t, actions.metopes)
×
151
      registerActions(editor, t, actions.md, { palette: false })
×
152

153
      const completionProvider = bibliographyCompletionProvider.register(monaco)
×
154
      editor.onDidDispose(() => completionProvider.dispose())
×
155

156
      const model = editor.getModel()
×
157
      // Set EOL to LF otherwise it causes synchronization issues due to inconsistent EOL between Windows and Linux.
158
      // https://github.com/yjs/y-monaco/issues/27
159
      model.setEOL(monaco.editor.EndOfLineSequence.LF)
×
160
      if (yText && awareness) {
×
161
        new MonacoBinding(yText, model, new Set([editor]), awareness)
×
162
      }
×
163
    },
×
164
    [yText, awareness]
×
165
  )
×
166

167
  const handleEditorDidMount = useCallback((editor) => {
×
168
    editorRef.current = editor
×
169
  }, [])
×
170

171
  let timeoutId
×
172
  useEffect(() => {
×
173
    if (yText) {
×
174
      updateArticleStructureAndStats({ text: yText.toString() })
×
175
      yText.observe(function (yTextEvent, transaction) {
×
176
        dispatch({
×
177
          type: 'UPDATE_ARTICLE_WORKING_COPY_STATUS',
×
178
          status: 'syncing',
×
179
        })
×
180
        if (timeoutId) {
×
181
          clearTimeout(timeoutId)
×
182
        }
×
183
        timeoutId = setTimeout(() => {
×
184
          dispatch({
×
185
            type: 'UPDATE_ARTICLE_WORKING_COPY_STATUS',
×
186
            status: 'synced',
×
187
          })
×
188
        }, 4000)
×
189

190
        updateArticleStructureAndStats({ text: yText.toString() })
×
191
      })
×
192
    }
×
193
  }, [articleId, versionId, yText])
×
194

195
  useEffect(() => {
×
196
    if (versionId) {
×
197
      dispatch({ type: 'UPDATE_ARTICLE_STATS', md: version.md })
×
198
      dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md: version.md })
×
199
    }
×
200
  }, [versionId])
×
201

202
  useEffect(() => {
×
203
    if (bibliography) {
×
204
      bibliographyCompletionProvider.bibTeXEntries = bibliography.entries
×
205
    }
×
206
  }, [bibliography])
×
207

208
  useEffect(() => {
×
209
    const line = editorCursorPosition.lineNumber
×
210
    const editor = editorRef.current
×
211
    editor?.focus()
×
212
    const endOfLineColumn = editor?.getModel()?.getLineMaxColumn(line + 1)
×
213
    editor?.setPosition({ lineNumber: line + 1, column: endOfLineColumn })
×
214
    editor?.revealLineNearTop(line + 1, 1) // smooth
×
215
  }, [editorRef, editorCursorPosition])
×
216

217
  if (isLoading) {
×
218
    return <Loading />
×
219
  }
×
220

221
  if (error) {
×
222
    return <Alert message={error.message} />
×
223
  }
×
224

225
  return (
×
226
    <>
×
227
      <style>{dynamicStyles}</style>
×
228
      <Helmet>
×
229
        <title>{article.title}</title>
×
230
      </Helmet>
×
231

232
      <CollaborativeEditorArticleHeader
×
233
        articleTitle={article.title}
×
234
        versionId={versionId}
×
235
      />
×
236

237
      <CollaborativeEditorWebSocketStatus
×
238
        className={styles.inlineStatus}
×
239
        status={websocketStatus}
×
240
      />
×
241

242
      {mode === 'preview' && (
×
243
        <section
×
244
          className={styles.previewPage}
×
245
          dangerouslySetInnerHTML={{ __html }}
×
246
        />
×
247
      )}
248

249
      {mode === 'compare' && (
×
250
        <div className={styles.collaborativeEditor}>
×
251
          <DiffEditor
×
252
            className={styles.editor}
×
253
            width={'100%'}
×
254
            height={'auto'}
×
255
            modified={article.workingVersion?.md}
×
256
            original={version.md}
×
257
            language="markdown"
×
258
            options={defaultEditorOptions}
×
259
          />
×
260
        </div>
×
261
      )}
262

NEW
263
      <div
×
NEW
264
        className={clsx(
×
NEW
265
          styles.collaborativeEditor,
×
NEW
266
          mode !== 'write' && styles.hidden
×
NEW
267
        )}
×
268
      >
269
        <MonacoEditor
×
270
          width={'100%'}
×
271
          height={'auto'}
×
272
          options={options}
×
273
          className={styles.editor}
×
274
          defaultLanguage="markdown"
×
275
          {...(hasVersion
×
276
            ? { value: version.md, onMount: handleEditorDidMount }
×
277
            : { onMount: handleCollaborativeEditorDidMount })}
×
278
        />
×
279
      </div>
×
280
    </>
×
281
  )
282
}
×
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