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

EcrituresNumeriques / stylo / 14472767041

15 Apr 2025 03:02PM UTC coverage: 33.491% (+2.1%) from 31.374%
14472767041

push

github

ggrossetie
fix: surcharge le style de liens externes pour ne pas afficher l'icone par défaut

515 of 777 branches covered (66.28%)

Branch coverage included in aggregate %.

5016 of 15738 relevant lines covered (31.87%)

2.3 hits per line

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

66.76
/front/src/components/Write/Versions.jsx
1
import clsx from 'clsx'
1✔
2
import { ArrowLeft, Check, Edit3 } from 'lucide-react'
1✔
3
import React, { useCallback, useMemo, useState } from 'react'
1✔
4
import { useTranslation } from 'react-i18next'
1✔
5
import { Link, useLocation } from 'react-router-dom'
1✔
6

7
import {
1✔
8
  useArticleVersionActions,
9
  useArticleVersions,
10
} from '../../hooks/article.js'
11
import { useModal } from '../../hooks/modal.js'
1✔
12
import Button from '../Button'
1✔
13

14
import buttonStyles from '../button.module.scss'
1✔
15
import Field from '../Field'
1✔
16
import Modal from '../Modal.jsx'
1✔
17
import Alert from '../molecules/Alert.jsx'
1✔
18
import Loading from '../molecules/Loading.jsx'
1✔
19

20
import TimeAgo from '../TimeAgo.jsx'
1✔
21
import CreateVersion from './CreateVersion'
1✔
22
import styles from './versions.module.scss'
1✔
23

24
/**
25
 *
26
 * @param props
27
 * @param props.articleId
28
 * @param props.compareTo
29
 * @param props.readOnly
30
 * @param props.selectedVersion
31
 * @param props.v
32
 */
33
function Version({ articleId, compareTo, readOnly, selectedVersion, v }) {
3✔
34
  const { t } = useTranslation()
3✔
35
  const { updateDescription } = useArticleVersionActions({ articleId })
3✔
36

37
  const articleVersionId = v._id
3✔
38

39
  const isComparing = useMemo(
3✔
40
    () => compareTo || selectedVersion,
3✔
41
    [compareTo, selectedVersion]
3✔
42
  )
3✔
43
  const isSelected = useMemo(
3✔
44
    () => articleVersionId === selectedVersion,
3✔
45
    [articleVersionId, selectedVersion]
3✔
46
  )
3✔
47
  const versionPart = useMemo(
3✔
48
    () => (selectedVersion ? `version/${selectedVersion}/` : ''),
3!
49
    [selectedVersion]
3✔
50
  )
3✔
51
  const compareLink = useMemo(
3✔
52
    () => `/article/${articleId}/${versionPart}compare/${articleVersionId}`,
3✔
53
    [articleId, versionPart, articleVersionId]
3✔
54
  )
3✔
55
  const canCompare = false
3✔
56
  const canStopCompare = false
3✔
57

58
  const className = clsx({
3✔
59
    [styles.selected]: isSelected,
3✔
60
    [styles.compareTo]: isComparing && articleVersionId === compareTo,
3!
61
  })
3✔
62

63
  const [renaming, setRenaming] = useState(false)
3✔
64
  const [title, setTitle] = useState(v.message)
3✔
65
  const versionType = v.type || 'userAction'
3!
66
  const manualVersion = versionType === 'userAction'
3✔
67
  const startRenaming = useCallback(
3✔
68
    (event) => event.preventDefault() || setRenaming(true),
3✔
69
    []
3✔
70
  )
3✔
71
  const cancelRenaming = useCallback(
3✔
72
    () => setTitle(v.message) || setRenaming(false),
3✔
73
    []
3✔
74
  )
3✔
75

76
  const handleRename = useCallback(
3✔
77
    async (event) => {
3✔
78
      event.preventDefault()
×
79
      await updateDescription({
×
80
        versionId: articleVersionId,
×
81
        description: title,
×
82
      })
×
83
      setTitle(title)
×
84
      setRenaming(false)
×
85
    },
×
86
    [title]
3✔
87
  )
3✔
88

89
  return (
3✔
90
    <li
3✔
91
      aria-current={isSelected}
3✔
92
      className={clsx(
3✔
93
        className,
3✔
94
        styles.version,
3✔
95
        manualVersion ? styles.manualVersion : styles.automaticVersion
3!
96
      )}
3✔
97
    >
98
      {renaming && (
3!
99
        <form className={styles.renamingForm} onSubmit={handleRename}>
×
100
          <Field
×
101
            autoFocus={true}
×
102
            type="text"
×
103
            value={title}
×
104
            onChange={(event) => setTitle(event.target.value)}
×
105
            placeholder={'Label of the version'}
×
106
          />
×
107
          <div className={styles.actions}>
×
108
            <Button
×
109
              title={t('write.saveVersionName.buttonTitle')}
×
110
              primary={true}
×
111
            >
112
              <Check /> {t('write.saveVersionName.buttonText')}
×
113
            </Button>
×
114
            <Button
×
115
              title={t('write.cancelVersionName.buttonTitle')}
×
116
              type="button"
×
117
              onClick={cancelRenaming}
×
118
            >
119
              {t('write.cancelVersionName.buttonText')}
×
120
            </Button>
×
121
          </div>
×
122
        </form>
×
123
      )}
124
      <Link
3✔
125
        to={`/article/${articleId}/version/${v._id}`}
3✔
126
        className={clsx(
3✔
127
          styles.versionLink,
3✔
128
          selectedVersion && styles.versionLinkCompare
3!
129
        )}
3✔
130
      >
131
        {!renaming && versionType === 'editingSessionEnded' && (
3!
132
          <header>{t('version.editingSessionEnded.text')}</header>
×
133
        )}
134

135
        {!renaming && versionType === 'collaborativeSessionEnded' && (
3!
136
          <header>{t('version.collaborativeSessionEnded.text')}</header>
×
137
        )}
138

139
        {!renaming && versionType === 'userAction' && (
3✔
140
          <header>
3✔
141
            <span className={styles.versionLabel}>
3✔
142
              v{v.version}.{v.revision} {title || ''}
3!
143
            </span>
3✔
144
            {!readOnly && (
3✔
145
              <Button
3✔
146
                title={t('write.editVersionName.buttonTitle')}
3✔
147
                icon={true}
3✔
148
                className={styles.editTitleButton}
3✔
149
                onClick={startRenaming}
3✔
150
              >
151
                <Edit3 size="20" />
3✔
152
              </Button>
3✔
153
            )}
154
          </header>
3✔
155
        )}
156

157
        {!renaming && (
3✔
158
          <p>
3✔
159
            {v.owner && (
3✔
160
              <span className={styles.author}>
3✔
161
                {t('version.by.text', {
3✔
162
                  owner: v.owner.displayName || v.owner.username,
3!
163
                })}
3✔
164
              </span>
3✔
165
            )}
166
            <span className={styles.momentsAgo}>
3✔
167
              <TimeAgo date={v.updatedAt} />
3✔
168
            </span>
3✔
169
          </p>
3✔
170
        )}
171
      </Link>
3✔
172

173
      {(canCompare || canStopCompare) && (
3!
174
        <ul className={styles.actions}>
×
175
          <li hidden={!canCompare}>
×
176
            <Link
×
177
              className={clsx(
×
178
                buttonStyles.button,
×
179
                buttonStyles.secondary,
×
180
                styles.action
×
181
              )}
×
182
              to={compareLink}
×
183
            >
184
              {t('write.compareVersion.button')}
×
185
            </Link>
×
186
          </li>
×
187
          <li hidden={!canStopCompare}>
×
188
            <Link
×
189
              className={clsx(
×
190
                buttonStyles.button,
×
191
                buttonStyles.secondary,
×
192
                styles.action
×
193
              )}
×
194
              to={`/article/${articleId}/${versionPart}`}
×
195
            >
196
              {t('write.stopCompareVersion.button')}
×
197
            </Link>
×
198
          </li>
×
199
        </ul>
×
200
      )}
201
    </li>
3✔
202
  )
203
}
3✔
204

205
/**
206
 * @param props
207
 * @param props.articleId
208
 * @param props.selectedVersion
209
 * @param props.compareTo
210
 * @param props.updatedAt
211
 * @return {Element}
212
 */
213
export function WorkingVersion({
1✔
214
  articleId,
4✔
215
  selectedVersion = null,
4✔
216
  compareTo = null,
4✔
217
  updatedAt,
4✔
218
}) {
4✔
219
  const { t } = useTranslation()
4✔
220
  const { pathname } = useLocation()
4✔
221

222
  const articleVersionId = null
4✔
223
  const isComparing = useMemo(() => pathname.includes('/compare'), [pathname])
4✔
224

225
  const isSelected = useMemo(
4✔
226
    () => articleVersionId === selectedVersion,
4✔
227
    [articleVersionId, selectedVersion]
4✔
228
  )
4✔
229
  const compareLink = useMemo(
4✔
230
    () =>
4✔
231
      selectedVersion
2!
232
        ? `/article/${articleId}/version/${selectedVersion}/compare/working-copy`
×
233
        : `/article/${articleId}/compare/${selectedVersion}`,
2✔
234
    [articleId, selectedVersion, articleVersionId]
4✔
235
  )
4✔
236
  const canCompare = false
4✔
237
  const canStopCompare = false
4✔
238

239
  const className = clsx({
4✔
240
    [styles.selected]: isSelected,
4✔
241
    [styles.compareTo]: isComparing && articleVersionId === compareTo,
4!
242
  })
4✔
243

244
  return (
4✔
245
    <li
4✔
246
      className={clsx(styles.version, className, styles.automaticVersion)}
4✔
247
      aria-current={isSelected}
4✔
248
    >
249
      <Link
4✔
250
        to={`/article/${articleId}`}
4✔
251
        className={clsx(
4✔
252
          styles.versionLink,
4✔
253
          selectedVersion && styles.versionLinkCompare
4!
254
        )}
4✔
255
      >
256
        <header>
4✔
257
          <span className={styles.versionLabel}>
4✔
258
            {t('workingVersion.spanWorkingCopy.text')}
4✔
259
          </span>
4✔
260
        </header>
4✔
261

262
        <p>
4✔
263
          <span className={styles.momentsAgo}>
4✔
264
            <TimeAgo date={updatedAt} />
4✔
265
          </span>
4✔
266
        </p>
4✔
267
      </Link>
4✔
268

269
      {(canCompare || canStopCompare) && (
4!
270
        <ul className={styles.actions}>
×
271
          <li hidden={!canCompare}>
×
272
            <Link
×
273
              className={clsx(
×
274
                buttonStyles.button,
×
275
                buttonStyles.secondary,
×
276
                styles.action
×
277
              )}
×
278
              to={compareLink}
×
279
            >
280
              {t('write.compareVersion.button')}
×
281
            </Link>
×
282
          </li>
×
283
          <li hidden={!canStopCompare}>
×
284
            <Link
×
285
              className={clsx(
×
286
                buttonStyles.button,
×
287
                buttonStyles.secondary,
×
288
                styles.action
×
289
              )}
×
290
              to={`/article/${articleId}`}
×
291
            >
292
              {t('write.stopCompareVersion.button')}
×
293
            </Link>
×
294
          </li>
×
295
        </ul>
×
296
      )}
297
    </li>
4✔
298
  )
299
}
4✔
300

301
/**
302
 * @param {object} props
303
 * @param {boolean} props.showTitle
304
 * @param {() => void} props.onBack
305
 * @param {string} props.articleId
306
 * @param {string} props.selectedVersion
307
 * @param {string} props.compareTo
308
 * @param {boolean} props.readOnly
309
 * @returns {Element}
310
 */
311
export default function Versions({
6✔
312
  showTitle,
6✔
313
  onBack,
6✔
314
  articleId,
6✔
315
  selectedVersion,
6✔
316
  compareTo,
6✔
317
  readOnly,
6✔
318
}) {
6✔
319
  const { article, isLoading, error } = useArticleVersions({ articleId })
6✔
320
  const articleVersions = article?.versions
6✔
321
  const updatedAt = article?.updatedAt
6✔
322
  const createVersionModal = useModal()
6✔
323
  const { t } = useTranslation()
6✔
324

325
  if (isLoading) {
6✔
326
    return <Loading />
1✔
327
  }
1✔
328

329
  if (error) {
6!
330
    return <Alert message={error.message} />
×
331
  }
✔
332

333
  const title = onBack ? (
4!
334
    <h2
×
335
      className={styles.title}
×
336
      onClick={onBack}
×
337
      style={{ cursor: 'pointer', userSelect: 'none' }}
×
338
    >
339
      <span onClick={onBack} style={{ display: 'flex' }}>
×
340
        <ArrowLeft style={{ strokeWidth: 3 }} />
×
341
      </span>
×
342
      <span>{t('versions.title')}</span>
×
343
    </h2>
✔
344
  ) : (
345
    <h2 className={styles.title}>{t('versions.title')}</h2>
4✔
346
  )
347

348
  return (
6✔
349
    <section>
6✔
350
      {showTitle && title}
6!
351
      {!readOnly && (
6✔
352
        <Button
4✔
353
          className={styles.headingAction}
4✔
354
          small={true}
4✔
355
          disabled={readOnly}
4✔
356
          onClick={() => createVersionModal.show()}
4✔
357
          testId="create-version-button"
4✔
358
        >
359
          {t('versions.createVersion.button')}
4✔
360
        </Button>
4✔
361
      )}
362
      {readOnly && (
6!
363
        <Link
×
364
          className={clsx(
×
365
            buttonStyles.button,
×
366
            buttonStyles.secondary,
×
367
            styles.editMode,
×
368
            styles.headingAction
×
369
          )}
×
370
          to={`/article/${article._id}`}
×
371
        >
372
          <ArrowLeft /> Edit Mode
×
373
        </Link>
×
374
      )}
375
      {articleVersions.length === 0 && (
6✔
376
        <p>
1✔
377
          <strong>All changes are automatically saved.</strong>
1✔
378
          <br />
1✔
379
          Create a new version to keep track of particular changes.
380
        </p>
1✔
381
      )}
382
      <Modal
6✔
383
        title={t('versions.createVersion.title')}
6✔
384
        {...createVersionModal.bindings}
6✔
385
      >
386
        <CreateVersion
6✔
387
          articleId={article._id}
6✔
388
          readOnly={readOnly}
6✔
389
          onClose={() => createVersionModal.close()}
6✔
390
          onSubmit={() => createVersionModal.close()}
6✔
391
        />
6✔
392
      </Modal>
6✔
393
      <ul className={styles.versionsList} data-testid="versions">
6✔
394
        <WorkingVersion
6✔
395
          articleId={article._id}
6✔
396
          selectedVersion={selectedVersion}
6✔
397
          compareTo={compareTo}
6✔
398
          readOnly={readOnly}
6✔
399
          updatedAt={updatedAt}
6✔
400
        />
6✔
401

402
        {articleVersions.map((v) => (
6✔
403
          <Version
3✔
404
            key={`showVersion-${v._id}`}
3✔
405
            articleId={article._id}
3✔
406
            selectedVersion={selectedVersion}
3✔
407
            compareTo={compareTo}
3✔
408
            readOnly={readOnly}
3✔
409
            v={v}
3✔
410
          />
3✔
411
        ))}
6✔
412
      </ul>
6✔
413
    </section>
6✔
414
  )
415
}
6✔
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