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

EcrituresNumeriques / stylo / 14591892548

22 Apr 2025 09:52AM UTC coverage: 37.471% (+1.0%) from 36.441%
14591892548

push

github

web-flow
feat: supprime l'éditeur de texte legacy (#1426)

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

530 of 757 branches covered (70.01%)

Branch coverage included in aggregate %.

3 of 57 new or added lines in 15 files covered. (5.26%)

214 existing lines in 12 files now uncovered.

5221 of 14591 relevant lines covered (35.78%)

2.49 hits per line

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

53.52
/front/src/createReduxStore.js
1
import * as Sentry from '@sentry/react'
1✔
2
import { applyMiddleware, compose, createStore } from 'redux'
1✔
3

4
const sentryReduxEnhancer = Sentry.createReduxEnhancer()
1✔
5
const sessionTokenName = 'sessionToken'
1✔
6

7
// Définition du store Redux et de l'ensemble des actions
1✔
8
export const initialState = {
1✔
9
  sessionToken: localStorage.getItem(sessionTokenName),
1✔
10
  articleWorkingCopy: {
1✔
11
    status: 'synced',
1✔
12
  },
1✔
13
  articleStructure: [],
1✔
14
  articleWriters: [],
1✔
15
  articlePreferences: localStorage.getItem('articlePreferences')
1✔
16
    ? JSON.parse(localStorage.getItem('articlePreferences'))
1!
17
    : {
1✔
18
        expandSidebarLeft: true,
1✔
19
        expandSidebarRight: false,
1✔
20
        metadataFormMode: 'basic',
1✔
21
        expandVersions: false,
1✔
22
      },
1✔
23
  articleFilters: {
1✔
24
    tagIds: [],
1✔
25
    text: '',
1✔
26
  },
1✔
27
  articleStats: {
1✔
28
    wordCount: 0,
1✔
29
    charCountNoSpace: 0,
1✔
30
    charCountPlusSpace: 0,
1✔
31
    citationNb: 0,
1✔
32
  },
1✔
33
  // Active user (authenticated)
1✔
34
  activeUser: {
1✔
35
    authTypes: [],
1✔
36
    authProviders: {},
1✔
37
    selectedTagIds: [],
1✔
38
    workspaces: [],
1✔
39
    activeWorkspaceId: null,
1✔
40
  },
1✔
41
  userPreferences: localStorage.getItem('userPreferences')
1✔
42
    ? JSON.parse(localStorage.getItem('userPreferences'))
1!
43
    : {
1✔
44
        // The user we impersonate
1✔
45
        currentUser: null,
1✔
46
        trackingConsent: true /* default value should be false */,
1✔
47
      },
1✔
48
  exportPreferences: localStorage.getItem('exportPreferences')
1✔
49
    ? JSON.parse(localStorage.getItem('exportPreferences'))
1!
50
    : {
1✔
51
        bibliography_style: 'chicagomodified',
1✔
52
        with_toc: 0,
1✔
53
        link_citations: 0,
1✔
54
        with_nocite: 0,
1✔
55
        formats: 'html',
1✔
56
        unnumbered: 0,
1✔
57
        book_division: 'part',
1✔
58
      },
1✔
59
  editorCursorPosition: {
1✔
60
    lineNumber: 0,
1✔
61
    column: 0,
1✔
62
  },
1✔
63
}
1✔
64

65
/**
1✔
66
 *
1✔
67
 * @param {*} state
1✔
68
 * @returns
1✔
69
 */
1✔
70
function createReducer(initialState, handlers) {
26✔
71
  return function reducer(state = initialState, action) {
26✔
72
    if (Object.prototype.hasOwnProperty.call(handlers, action.type)) {
29✔
73
      return handlers[action.type](state, action)
3✔
74
    } else {
29✔
75
      return state
26✔
76
    }
26✔
77
  }
29✔
78
}
26✔
79

80
/**
1✔
81
 *
1✔
82
 * @param {*} state
1✔
83
 * @returns
1✔
84
 */
1✔
85
function createRootReducer(state) {
26✔
86
  return createReducer(state, {
26✔
87
    PROFILE: setProfile,
26✔
88
    LOGIN: loginUser,
26✔
89
    UPDATE_SESSION_TOKEN: setSessionToken,
26✔
90
    UPDATE_ACTIVE_USER_DETAILS: updateActiveUserDetails,
26✔
91
    LOGOUT: logoutUser,
26✔
92

93
    // article reducers
26✔
94
    UPDATE_ARTICLE_STATS: updateArticleStats,
26✔
95
    UPDATE_ARTICLE_STRUCTURE: updateArticleStructure,
26✔
96
    UPDATE_ARTICLE_WRITERS: updateArticleWriters,
26✔
97
    UPDATE_ARTICLE_WORKING_COPY_STATUS: updateArticleWorkingCopyStatus,
26✔
98

99
    // user preferences reducers
26✔
100
    USER_PREFERENCES_TOGGLE: toggleUserPreferences,
26✔
101
    SET_EXPORT_PREFERENCES: setExportPreferences,
26✔
102

103
    ARTICLE_PREFERENCES_TOGGLE: toggleArticlePreferences,
26✔
104

105
    UPDATE_EDITOR_CURSOR_POSITION: updateEditorCursorPosition,
26✔
106

107
    SET_ACTIVE_WORKSPACE: setActiveWorkspace,
26✔
108

109
    UPDATE_SELECTED_TAG: updateSelectedTag,
26✔
110
  })
26✔
111
}
26✔
112

113
function persistStateIntoLocalStorage({ getState }) {
26✔
114
  const actionStateMap = new Map([
26✔
115
    ['ARTICLE_PREFERENCES_TOGGLE', 'articlePreferences'],
26✔
116
    ['USER_PREFERENCES_TOGGLE', 'userPreferences'],
26✔
117
    ['SET_EXPORT_PREFERENCES', 'exportPreferences'],
26✔
118
  ])
26✔
119

120
  return (next) => {
26✔
121
    return (action) => {
26✔
122
      if (actionStateMap.has(action.type)) {
3!
123
        const key = actionStateMap.get(action.type)
×
124
        // we run the reducer first
×
125
        next(action)
×
126
        // we fetch the updated state
×
127
        const state = getState()[key]
×
128

129
        // we persist it for a later page reload
×
130
        localStorage.setItem(key, JSON.stringify(state))
×
131

132
        return
×
133
      } else if (action.type === 'LOGOUT') {
3!
134
        localStorage.removeItem('articlePreferences')
×
135
        localStorage.removeItem('userPreferences')
×
136
      }
×
137

138
      if (action.type === 'LOGIN' || action.type === 'UPDATE_SESSION_TOKEN') {
3✔
139
        next(action)
1✔
140
        const { sessionToken } = getState()
1✔
141
        localStorage.setItem(sessionTokenName, sessionToken)
1✔
142
        return
1✔
143
      }
1✔
144

145
      if (action.type === 'LOGOUT') {
2!
146
        localStorage.removeItem(sessionTokenName)
×
147
        return next(action)
×
148
      }
×
149

150
      return next(action)
2✔
151
    }
3✔
152
  }
26✔
153
}
26✔
154

155
function setProfile(state, action) {
×
156
  const { user } = action
×
157

158
  if (!user) {
×
NEW
159
    return { ...state, activeUser: structuredClone(initialState.activeUser) }
×
160
  }
×
161

162
  return {
×
163
    ...state,
×
164
    activeUser: {
×
165
      ...state.activeUser,
×
166
      activeWorkspaceId: action.activeWorkspaceId,
×
167
      ...user,
×
168
    },
×
169
  }
×
170
}
×
171

172
function setSessionToken(state, { token: sessionToken }) {
×
173
  return {
×
174
    ...state,
×
175
    sessionToken,
×
176
  }
×
177
}
×
178

179
function loginUser(state, { user, token: sessionToken }) {
1✔
180
  if (sessionToken) {
1!
181
    Sentry.setUser({ id: user._id })
×
182
    return {
×
183
      ...state,
×
184
      sessionToken,
×
185
      activeUser: {
×
NEW
186
        ...state.activeUser,
×
187
        ...user,
×
188
        // dates are expected to be in timestamp string format (including milliseconds)
×
189
        createdAt: String(new Date(user.createdAt).getTime()),
×
190
        updatedAt: String(new Date(user.updatedAt).getTime()),
×
191
      },
×
192
    }
×
193
  }
×
194

195
  return state
1✔
196
}
1✔
197

198
function updateActiveUserDetails(state, action) {
2✔
199
  return {
2✔
200
    ...state,
2✔
201
    activeUser: { ...state.activeUser, ...action.payload },
2✔
202
  }
2✔
203
}
2✔
204

205
function logoutUser() {
×
206
  return structuredClone(initialState)
×
207
}
×
208

209
const SPACE_RE = /\s+/gi
1✔
210
const CITATION_RE = /(\[@[\w-]+)/gi
1✔
211
const REMOVE_MARKDOWN_RE = /[#_*]+\s?/gi
1✔
212

213
function updateArticleStats(state, { md }) {
×
214
  const text = (md || '').trim()
×
215

216
  const textWithoutMarkdown = text.replace(REMOVE_MARKDOWN_RE, '')
×
217
  const wordCount = textWithoutMarkdown.replace(SPACE_RE, ' ').split(' ').length
×
218

219
  const charCountNoSpace = textWithoutMarkdown.replace(SPACE_RE, '').length
×
220
  const charCountPlusSpace = textWithoutMarkdown.length
×
221
  const citationNb = text.match(CITATION_RE)?.length || 0
×
222

223
  return {
×
224
    ...state,
×
225
    articleStats: {
×
226
      wordCount,
×
227
      charCountNoSpace,
×
228
      charCountPlusSpace,
×
229
      citationNb,
×
230
    },
×
231
  }
×
232
}
×
233

234
function updateArticleStructure(state, { md }) {
×
235
  const text = (md || '').trim()
×
236
  const articleStructure = text
×
237
    .split('\n')
×
238
    .map((line, index) => ({ line, index }))
×
239
    .filter((lineWithIndex) => lineWithIndex.line.match(/^##+ /))
×
240
    .map((lineWithIndex) => {
×
241
      const title = lineWithIndex.line
×
242
        .replace(/##/, '')
×
243
        //arrow backspace (\u21B3)
×
244
        .replace(/#\s/g, '\u21B3')
×
245
        // middle dot (\u00B7) + non-breaking space (\xa0)
×
246
        .replace(/#/g, '\u00B7\xa0')
×
247
      return { ...lineWithIndex, title }
×
248
    })
×
249

250
  return { ...state, articleStructure }
×
251
}
×
252

253
function updateArticleWriters(state, { articleWriters }) {
×
254
  return { ...state, articleWriters }
×
255
}
×
256

257
function updateArticleWorkingCopyStatus(state, { status }) {
×
258
  return {
×
259
    ...state,
×
260
    articleWorkingCopy: { ...state.articleWorkingCopy, status },
×
261
  }
×
262
}
×
263

264
function togglePreferences(storeKey) {
38✔
265
  return function togglePreferencesReducer(state, { key, value }) {
38✔
266
    const preferences = state[storeKey]
×
267

268
    return {
×
269
      ...state,
×
270
      [storeKey]: {
×
271
        ...preferences,
×
272
        [key]: value === undefined ? !preferences[key] : value,
×
273
      },
×
274
    }
×
275
  }
×
276
}
38✔
277

278
function setPreferences(storeKey) {
19✔
279
  return function setPreferencesReducer(state, { key, value }) {
19✔
280
    const preferences = state[storeKey]
×
281

282
    return {
×
283
      ...state,
×
284
      [storeKey]: {
×
285
        ...preferences,
×
286
        [key]: value,
×
287
      },
×
288
    }
×
289
  }
×
290
}
19✔
291

292
const toggleArticlePreferences = togglePreferences('articlePreferences')
1✔
293
const toggleUserPreferences = togglePreferences('userPreferences')
1✔
294
const setExportPreferences = setPreferences('exportPreferences')
1✔
295

296
function updateEditorCursorPosition(state, { lineNumber, column }) {
×
297
  return {
×
298
    ...state,
×
299
    editorCursorPosition: {
×
300
      lineNumber,
×
301
      column,
×
302
    },
×
303
  }
×
304
}
×
305

306
function setActiveWorkspace(state, { workspaceId }) {
×
307
  return {
×
308
    ...state,
×
309
    activeUser: {
×
310
      ...state.activeUser,
×
311
      activeWorkspaceId: workspaceId,
×
312
    },
×
313
  }
×
314
}
×
315

316
function updateSelectedTag(state, { tagId }) {
×
317
  const { selectedTagIds } = state.activeUser
×
318
  return {
×
319
    ...state,
×
320
    activeUser: {
×
321
      ...state.activeUser,
×
322
      selectedTagIds: selectedTagIds.includes(tagId)
×
323
        ? selectedTagIds.filter((selectedTagId) => selectedTagId !== tagId)
×
324
        : [...selectedTagIds, tagId],
×
325
    },
×
326
  }
×
327
}
×
328

329
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
1✔
330
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
1!
331
      trace: true,
×
332
      traceLimit: 25,
×
333
    })
×
334
  : compose
1✔
335

336
export default function createReduxStore(state = {}) {
1✔
337
  return createStore(
26✔
338
    createRootReducer({
26✔
339
      ...structuredClone(initialState),
26✔
340
      ...structuredClone(state),
26✔
341
    }),
26✔
342
    composeEnhancers(
26✔
343
      applyMiddleware(persistStateIntoLocalStorage),
26✔
344
      sentryReduxEnhancer
26✔
345
    )
26✔
346
  )
26✔
347
}
26✔
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