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

EcrituresNumeriques / stylo / 12925854411

23 Jan 2025 09:11AM UTC coverage: 25.831% (-4.7%) from 30.523%
12925854411

push

github

web-flow
Merge pull request #1192 from EcrituresNumeriques/feat/vite6

322 of 518 branches covered (62.16%)

Branch coverage included in aggregate %.

3448 of 14077 relevant lines covered (24.49%)

1.66 hits per line

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

0.0
/front/src/components/Write/Versions.jsx
1
import React, { useCallback, useMemo, useState } from 'react'
×
2
import PropTypes from 'prop-types'
×
3
import { useTranslation } from 'react-i18next'
×
4
import { Link } from 'react-router-dom'
×
5
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
×
6
import { useLocation } from 'react-router-dom'
×
7
import {
×
8
  ArrowLeft,
9
  Check,
10
  ChevronDown,
11
  ChevronRight,
12
  Edit3,
13
} from 'react-feather'
14
import TimeAgo from '../TimeAgo.jsx'
×
15

16
import styles from './versions.module.scss'
×
17
import menuStyles from './menu.module.scss'
×
18
import buttonStyles from '../button.module.scss'
×
19

20
import { useGraphQL } from '../../helpers/graphQL'
×
21
import { renameVersion } from './Write.graphql'
×
22

23
import Modal from '../Modal'
×
24
import Export from '../Export'
×
25
import Button from '../Button'
×
26
import Field from '../Field'
×
27
import CreateVersion from './CreateVersion'
×
28
import clsx from 'clsx'
×
29

30
function Version({ articleId, compareTo, readOnly, selectedVersion, v }) {
×
31
  const { t } = useTranslation()
×
32

33
  const articleVersionId = v._id
×
34

35
  const isComparing = useMemo(
×
36
    () => compareTo || selectedVersion,
×
37
    [compareTo, selectedVersion]
×
38
  )
×
39
  const isSelected = useMemo(
×
40
    () => articleVersionId === selectedVersion,
×
41
    [articleVersionId, selectedVersion]
×
42
  )
×
43
  const versionPart = useMemo(
×
44
    () => (selectedVersion ? `version/${selectedVersion}/` : ''),
×
45
    [selectedVersion]
×
46
  )
×
47
  const compareLink = useMemo(
×
48
    () => `/article/${articleId}/${versionPart}compare/${articleVersionId}`,
×
49
    [articleId, versionPart, articleVersionId]
×
50
  )
×
51
  const canCompare = useMemo(
×
52
    () => ![compareTo, selectedVersion].includes(articleVersionId),
×
53
    [compareTo, selectedVersion, articleVersionId]
×
54
  )
×
55
  const canStopCompare = useMemo(
×
56
    () => isComparing && compareTo && articleVersionId === compareTo,
×
57
    [compareTo, articleVersionId]
×
58
  )
×
59

60
  const className = clsx({
×
61
    [styles.selected]: isSelected,
×
62
    [styles.compareTo]: isComparing && articleVersionId === compareTo,
×
63
  })
×
64

65
  const runQuery = useGraphQL()
×
66
  const [renaming, setRenaming] = useState(false)
×
67
  const [title, setTitle] = useState(v.message)
×
68
  const versionType = v.type || 'userAction'
×
69
  const manualVersion = versionType === 'userAction'
×
70
  const startRenaming = useCallback(
×
71
    (event) => event.preventDefault() || setRenaming(true),
×
72
    []
×
73
  )
×
74
  const cancelRenaming = useCallback(
×
75
    () => setTitle(v.message) || setRenaming(false),
×
76
    []
×
77
  )
×
78

79
  const handleRename = useCallback(
×
80
    async (event) => {
×
81
      event.preventDefault()
×
82
      const variables = { version: articleVersionId, name: title }
×
83
      await runQuery({ query: renameVersion, variables })
×
84
      setTitle(title)
×
85
      setRenaming(false)
×
86
    },
×
87
    [title]
×
88
  )
×
89

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

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

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

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

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
    </li>
×
201
  )
202
}
×
203

204
Version.propTypes = {
×
205
  articleId: PropTypes.string.isRequired,
×
206
  v: PropTypes.object.isRequired,
×
207
  selectedVersion: PropTypes.string,
×
208
  compareTo: PropTypes.string,
×
209
  readOnly: PropTypes.bool,
×
210
}
×
211

212
export function WorkingVersion({
×
213
  articleId,
×
214
  selectedVersion = null,
×
215
  compareTo = null,
×
216
  readOnly,
×
217
}) {
×
218
  const updatedAt = useSelector((state) => state.workingArticle.updatedAt)
×
219
  const { t } = useTranslation()
×
220
  const { pathname } = useLocation()
×
221

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

225
  const isSelected = useMemo(
×
226
    () => articleVersionId === selectedVersion,
×
227
    [articleVersionId, selectedVersion]
×
228
  )
×
229
  const compareLink = useMemo(
×
230
    () =>
×
231
      selectedVersion
×
232
        ? `/article/${articleId}/version/${selectedVersion}/compare/working-copy`
×
233
        : `/article/${articleId}/compare/${selectedVersion}`,
×
234
    [articleId, selectedVersion, articleVersionId]
×
235
  )
×
236
  const canCompare = useMemo(
×
237
    () =>
×
238
      isComparing
×
239
        ? ![compareTo, selectedVersion].includes(articleVersionId)
×
240
        : selectedVersion,
×
241
    [isComparing, compareTo, selectedVersion, articleVersionId]
×
242
  )
×
243
  const canStopCompare = useMemo(
×
244
    () => isComparing && compareTo === articleVersionId,
×
245
    [isComparing, isSelected]
×
246
  )
×
247

248
  const className = clsx({
×
249
    [styles.selected]: isSelected,
×
250
    [styles.compareTo]: isComparing && articleVersionId === compareTo,
×
251
  })
×
252

253
  return (
×
254
    <li
×
255
      className={clsx(styles.version, className, styles.automaticVersion)}
×
256
      aria-current={isSelected}
×
257
    >
258
      <Link
×
259
        to={`/article/${articleId}`}
×
260
        className={clsx(
×
261
          styles.versionLink,
×
262
          selectedVersion && styles.versionLinkCompare
×
263
        )}
×
264
      >
265
        <header>
×
266
          <span className={styles.versionLabel}>
×
267
            {t('workingVersion.spanWorkingCopy.text')}
×
268
          </span>
×
269
        </header>
×
270

271
        <p>
×
272
          <span className={styles.momentsAgo}>
×
273
            <TimeAgo date={updatedAt} />
×
274
          </span>
×
275
        </p>
×
276
      </Link>
×
277

278
      <ul className={styles.actions}>
×
279
        <li hidden={!canCompare}>
×
280
          <Link
×
281
            className={clsx(
×
282
              buttonStyles.button,
×
283
              buttonStyles.secondary,
×
284
              styles.action
×
285
            )}
×
286
            to={compareLink}
×
287
          >
288
            {t('write.compareVersion.button')}
×
289
          </Link>
×
290
        </li>
×
291
        <li hidden={!canStopCompare}>
×
292
          <Link
×
293
            className={clsx(
×
294
              buttonStyles.button,
×
295
              buttonStyles.secondary,
×
296
              styles.action
×
297
            )}
×
298
            to={`/article/${articleId}`}
×
299
          >
300
            {t('write.stopCompareVersion.button')}
×
301
          </Link>
×
302
        </li>
×
303
      </ul>
×
304
    </li>
×
305
  )
306
}
×
307

308
WorkingVersion.propTypes = {
×
309
  articleId: PropTypes.string.isRequired,
×
310
  selectedVersion: PropTypes.string,
×
311
  compareTo: PropTypes.string,
×
312
  readOnly: PropTypes.bool,
×
313
}
×
314

315
export default function Versions({
×
316
  article,
×
317
  selectedVersion,
×
318
  compareTo,
×
319
  readOnly,
×
320
}) {
×
321
  const articleVersions = useSelector(
×
322
    (state) => state.articleVersions,
×
323
    shallowEqual
×
324
  )
×
325
  const expand = useSelector((state) => state.articlePreferences.expandVersions)
×
326
  const dispatch = useDispatch()
×
327
  const [exportParams, setExportParams] = useState({})
×
328
  const [expandCreateForm, setExpandCreateForm] = useState(false)
×
329

330
  const toggleExpand = useCallback(
×
331
    () =>
×
332
      dispatch({ type: 'ARTICLE_PREFERENCES_TOGGLE', key: 'expandVersions' }),
×
333
    []
×
334
  )
×
335
  const closeNewVersion = useCallback(() => setExpandCreateForm(false), [])
×
336
  const createNewVersion = useCallback((event) => {
×
337
    event.preventDefault()
×
338
    dispatch({
×
339
      type: 'ARTICLE_PREFERENCES_TOGGLE',
×
340
      key: 'expandVersions',
×
341
      value: false,
×
342
    })
×
343
    setExpandCreateForm(true)
×
344
  }, [])
×
345
  const cancelExport = useCallback(() => setExportParams({}), [])
×
346
  const { t } = useTranslation()
×
347

348
  return (
×
349
    <section className={clsx(menuStyles.section)}>
×
350
      <h1 className={expand ? null : styles.closed} onClick={toggleExpand}>
×
351
        {expand ? <ChevronDown /> : <ChevronRight />}
×
352
        {t('write.titleVersion.sidebar')}
×
353

354
        {!readOnly && (
×
355
          <Button
×
356
            className={styles.headingAction}
×
357
            small={true}
×
358
            disabled={readOnly}
×
359
            onClick={createNewVersion}
×
360
          >
361
            {t('write.newVersion.button')}
×
362
          </Button>
×
363
        )}
364
        {readOnly && (
×
365
          <Link
×
366
            className={clsx(
×
367
              buttonStyles.button,
×
368
              buttonStyles.secondary,
×
369
              styles.editMode,
×
370
              styles.headingAction
×
371
            )}
×
372
            to={`/article/${article._id}`}
×
373
          >
374
            {' '}
×
375
            <ArrowLeft /> Edit Mode
×
376
          </Link>
×
377
        )}
378
      </h1>
×
379
      {exportParams.articleId && (
×
380
        <Modal title="Export" cancel={cancelExport}>
×
381
          <Export {...exportParams} />
×
382
        </Modal>
×
383
      )}
384
      {expand && (
×
385
        <>
×
386
          {expandCreateForm && (
×
387
            <CreateVersion
×
388
              articleId={article._id}
×
389
              readOnly={readOnly}
×
390
              onClose={closeNewVersion}
×
391
            />
×
392
          )}
393

394
          {articleVersions.length === 0 && (
×
395
            <p>
×
396
              <strong>All changes are automatically saved.</strong>
×
397
              <br />
×
398
              Create a new version to keep track of particular changes.
399
            </p>
×
400
          )}
401

402
          <ul className={styles.versionsList}>
×
403
            <WorkingVersion
×
404
              articleId={article._id}
×
405
              selectedVersion={selectedVersion}
×
406
              compareTo={compareTo}
×
407
              readOnly={readOnly}
×
408
            />
×
409

410
            {articleVersions.map((v) => (
×
411
              <Version
×
412
                key={`showVersion-${v._id}`}
×
413
                articleId={article._id}
×
414
                selectedVersion={selectedVersion}
×
415
                compareTo={compareTo}
×
416
                readOnly={readOnly}
×
417
                v={v}
×
418
              />
×
419
            ))}
×
420
          </ul>
×
421
        </>
×
422
      )}
423
    </section>
×
424
  )
425
}
×
426

427
Versions.propTypes = {
×
428
  article: PropTypes.object.isRequired,
×
429
  selectedVersion: PropTypes.string,
×
430
  compareTo: PropTypes.string,
×
431
  readOnly: PropTypes.bool,
×
432
}
×
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