• 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/Write/Write.jsx
1
import React, { useCallback, useEffect, useMemo, useState } from 'react'
×
NEW
2
import { Code, Text } from '@geist-ui/core'
×
3
import clsx from 'clsx'
×
4
import debounce from 'lodash.debounce'
×
5
import throttle from 'lodash.throttle'
×
6
import { Helmet } from 'react-helmet'
×
7
import { useTranslation } from 'react-i18next'
×
NEW
8
import { batch, useDispatch } from 'react-redux'
×
9
import { Route, Switch, useParams, useRouteMatch } from 'react-router-dom'
×
10

11
import { useGraphQLClient } from '../../helpers/graphQL'
×
12
import { useActiveUserId } from '../../hooks/user'
×
13

14
import ArticleStats from '../ArticleStats.jsx'
×
15
import ErrorMessageCard from '../ErrorMessageCard.jsx'
×
16
import Loading from '../molecules/Loading.jsx'
×
17
import ArticleEditorMenu from './ArticleEditorMenu.jsx'
×
18
import ArticleEditorMetadata from './ArticleEditorMetadata.jsx'
×
19

20
import PreviewHtml from './PreviewHtml'
×
21
import PreviewPaged from './PreviewPaged'
×
22
import MonacoEditor from './providers/monaco/Editor'
×
23
import WorkingVersion from './WorkingVersion'
×
24

NEW
25
import { getEditableArticle as getEditableArticleQuery } from './Write.graphql'
×
26

27
import styles from './write.module.scss'
×
28

29
const MODES_PREVIEW = 'preview'
×
30
const MODES_READONLY = 'readonly'
×
31
const MODES_WRITE = 'write'
×
32

33
export function deriveModeFrom({ path, currentVersion }) {
×
34
  if (path === '/article/:id/preview') {
×
35
    return MODES_PREVIEW
×
36
  } else if (currentVersion) {
×
37
    return MODES_READONLY
×
38
  }
×
39

40
  return MODES_WRITE
×
41
}
×
42

43
/**
44
 * @return {Element}
45
 */
46
export default function Write() {
×
47
  const { t } = useTranslation()
×
48
  const { version: currentVersion, id: articleId, compareTo } = useParams()
×
49
  const userId = useActiveUserId()
×
50
  const dispatch = useDispatch()
×
51
  const { query } = useGraphQLClient()
×
52
  const routeMatch = useRouteMatch()
×
53
  const mode = useMemo(() => {
×
54
    return deriveModeFrom({ currentVersion, path: routeMatch.path })
×
NEW
55
  }, [currentVersion, routeMatch.path])
×
56
  const [graphQLError, setGraphQLError] = useState()
×
57
  const [isLoading, setIsLoading] = useState(true)
×
58
  const [live, setLive] = useState({})
×
59
  const [articleInfos, setArticleInfos] = useState({
×
60
    title: '',
×
61
    owner: '',
×
62
    contributors: [],
×
63
    zoteroLink: '',
×
64
    preview: {},
×
65
  })
×
66

67
  const PreviewComponent = useMemo(
×
68
    () => (articleInfos.preview.stylesheet ? PreviewPaged : PreviewHtml),
×
69
    [articleInfos.preview.stylesheet, currentVersion]
×
70
  )
×
71

72
  const deriveArticleStructureAndStats = useCallback(
×
73
    throttle(
×
74
      ({ text }) => {
×
75
        dispatch({ type: 'UPDATE_ARTICLE_STATS', md: text })
×
76
        dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md: text })
×
77
      },
×
78
      250,
×
79
      { leading: false, trailing: true }
×
80
    ),
×
81
    []
×
82
  )
×
83
  const setWorkingArticleDirty = useCallback(
×
84
    debounce(
×
85
      async () => {
×
86
        dispatch({
×
87
          type: 'SET_WORKING_ARTICLE_STATE',
×
88
          workingArticleState: 'saving',
×
89
        })
×
90
      },
×
91
      1000,
×
92
      { leading: true, trailing: false }
×
93
    ),
×
94
    []
×
95
  )
×
96
  const updateWorkingArticleText = useCallback(
×
97
    debounce(
×
98
      async ({ text }) => {
×
99
        dispatch({ type: 'UPDATE_WORKING_ARTICLE_TEXT', articleId, text })
×
100
      },
×
101
      1000,
×
102
      { leading: false, trailing: true }
×
103
    ),
×
104
    []
×
105
  )
×
106
  const updateWorkingArticleMetadata = useCallback(
×
107
    debounce(
×
108
      ({ metadata }) => {
×
109
        dispatch({
×
110
          type: 'UPDATE_WORKING_ARTICLE_METADATA',
×
111
          articleId,
×
112
          metadata,
×
113
        })
×
114
      },
×
115
      1000,
×
116
      { leading: false, trailing: true }
×
117
    ),
×
118
    []
×
119
  )
×
120

121
  const handleMDCM = (text) => {
×
122
    deriveArticleStructureAndStats({ text })
×
123
    updateWorkingArticleText({ text })
×
124
    setWorkingArticleDirty()
×
125
    return setLive({ ...live, md: text })
×
126
  }
×
127

128
  const handleMetadataChange = (metadata) => {
×
129
    updateWorkingArticleMetadata({ metadata })
×
130
    setWorkingArticleDirty()
×
131
    return setLive({ ...live, metadata })
×
132
  }
×
133

134
  // Reload when version switching
135
  useEffect(() => {
×
136
    const variables = {
×
137
      user: userId,
×
138
      article: articleId,
×
139
      version: currentVersion || 'latest',
×
140
      hasVersion: typeof currentVersion === 'string',
×
141
      isPreview: mode === MODES_PREVIEW,
×
142
    }
×
143

144
    setIsLoading(true)
×
145
    ;(async () => {
×
146
      const data = await query({
×
147
        query: getEditableArticleQuery,
×
148
        variables,
×
149
      }).catch((error) => {
×
150
        setGraphQLError(error)
×
151
        return {}
×
152
      })
×
153

154
      if (data?.article) {
×
155
        const article = data.article
×
156
        let currentArticle
×
157
        if (currentVersion) {
×
158
          currentArticle = {
×
159
            bib: data.version.bib,
×
160
            md: data.version.md,
×
161
            metadata: data.version.metadata,
×
162
            bibPreview: data.version.bibPreview,
×
163
            version: {
×
164
              message: data.version.message,
×
165
              major: data.version.version,
×
166
              minor: data.version.revision,
×
167
            },
×
168
          }
×
169
        } else {
×
170
          currentArticle = article.workingVersion
×
171
        }
×
172
        setLive(currentArticle)
×
173
        setArticleInfos({
×
174
          _id: article._id,
×
175
          title: article.title,
×
176
          owner: article.owner,
×
177
          contributors: article.contributors,
×
178
          zoteroLink: article.zoteroLink,
×
179
          preview: article.preview,
×
180
          updatedAt: article.updatedAt,
×
181
        })
×
182

183
        const { md, bib, metadata } = currentArticle
×
184

185
        batch(() => {
×
186
          dispatch({ type: 'SET_ARTICLE_VERSIONS', versions: article.versions })
×
187
          dispatch({ type: 'UPDATE_ARTICLE_STATS', md })
×
188
          dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md })
×
189
          dispatch({ type: 'SET_WORKING_ARTICLE_TEXT', text: md })
×
190
          dispatch({ type: 'SET_WORKING_ARTICLE_METADATA', metadata })
×
191
          dispatch({
×
192
            type: 'SET_WORKING_ARTICLE_BIBLIOGRAPHY',
×
193
            bibliography: bib,
×
194
          })
×
195
          dispatch({
×
196
            type: 'SET_WORKING_ARTICLE_UPDATED_AT',
×
197
            updatedAt: article.updatedAt,
×
198
          })
×
199
        })
×
200
      }
×
201

202
      setIsLoading(false)
×
203
    })()
×
UNCOV
204
  }, [currentVersion, articleId])
×
205

206
  if (graphQLError) {
×
207
    return (
×
208
      <section className={styles.errorContainer}>
×
209
        <ErrorMessageCard title="Error">
×
210
          <Text>
×
211
            <Code>{graphQLError?.message || graphQLError.toString()}</Code>
×
212
          </Text>
×
213
        </ErrorMessageCard>
×
214
      </section>
×
215
    )
216
  }
×
217

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

222
  return (
×
223
    <section className={styles.container}>
×
224
      <Helmet>
×
225
        <title>{t('article.page.title', { title: articleInfos.title })}</title>
×
226
      </Helmet>
×
227
      <ArticleEditorMenu
×
228
        articleInfos={articleInfos}
×
229
        compareTo={compareTo}
×
230
        selectedVersion={currentVersion}
×
231
        readOnly={mode === MODES_READONLY}
×
232
      />
×
233
      <article className={clsx({ [styles.article]: mode !== MODES_PREVIEW })}>
×
234
        <WorkingVersion
×
235
          articleInfos={articleInfos}
×
236
          live={live}
×
237
          selectedVersion={currentVersion}
×
238
          mode={mode}
×
239
        />
×
240

241
        <Switch>
×
242
          <Route path="*/preview" exact>
×
243
            <PreviewComponent
×
244
              preview={articleInfos.preview}
×
245
              metadata={live.metadata}
×
246
            />
×
247
          </Route>
×
248
          <Route path="*">
×
249
            <MonacoEditor
×
250
              text={live.md}
×
251
              readOnly={mode === MODES_READONLY}
×
252
              onTextUpdate={handleMDCM}
×
253
              articleId={articleInfos._id}
×
254
              selectedVersion={currentVersion}
×
255
              compareTo={compareTo}
×
256
              currentArticleVersion={live.version}
×
257
            />
×
258

259
            <ArticleStats />
×
260
          </Route>
×
261
        </Switch>
×
262
      </article>
×
263
      <ArticleEditorMetadata
×
264
        metadata={live.metadata}
×
265
        onChange={handleMetadataChange}
×
266
        readOnly={mode === MODES_READONLY}
×
267
      />
×
268
    </section>
×
269
  )
270
}
×
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