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

EcrituresNumeriques / stylo / 15421390032

03 Jun 2025 03:23PM UTC coverage: 38.118% (+0.4%) from 37.711%
15421390032

push

github

web-flow
feat: EntĂȘte responsive (#1531)

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

560 of 791 branches covered (70.8%)

Branch coverage included in aggregate %.

36 of 309 new or added lines in 17 files covered. (11.65%)

307 existing lines in 17 files now uncovered.

5458 of 14997 relevant lines covered (36.39%)

2.58 hits per line

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

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

4
import { computeTextStats } from './helpers/markdown.js'
1✔
5

6
const sessionTokenName = 'sessionToken'
1✔
7

8
// Définition du store Redux et de l'ensemble des actions
1✔
9
export const initialState = {
1✔
10
  sessionToken: localStorage.getItem(sessionTokenName),
1✔
11
  articleWorkingCopy: {
1✔
12
    status: 'synced',
1✔
13
  },
1✔
14
  articleStructure: [],
1✔
15
  articleWriters: [],
1✔
16
  articlePreferences: localStorage.getItem('articlePreferences')
1✔
17
    ? JSON.parse(localStorage.getItem('articlePreferences'))
1!
18
    : {
1✔
19
        expandSidebarRight: true,
1✔
20
        activePanel: null,
1✔
21
        metadataFormMode: 'basic',
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
  },
1✔
40
  activeWorkspaceId: null,
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
 * @param initialState
1✔
69
 * @param handlers
1✔
70
 * @returns
1✔
71
 */
1✔
72
function createReducer(initialState, handlers) {
27✔
73
  return function reducer(state = initialState, action) {
27✔
74
    if (Object.prototype.hasOwnProperty.call(handlers, action.type)) {
30✔
75
      return handlers[action.type](state, action)
3✔
76
    } else {
30✔
77
      return state
27✔
78
    }
27✔
79
  }
30✔
80
}
27✔
81

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

95
    // article reducers
27✔
96
    UPDATE_ARTICLE_STATS: updateArticleStats,
27✔
97
    UPDATE_ARTICLE_STRUCTURE: updateArticleStructure,
27✔
98
    UPDATE_ARTICLE_WRITERS: updateArticleWriters,
27✔
99
    UPDATE_ARTICLE_WORKING_COPY_STATUS: updateArticleWorkingCopyStatus,
27✔
100

101
    // user preferences reducers
27✔
102
    USER_PREFERENCES_TOGGLE: toggleUserPreferences,
27✔
103
    SET_EXPORT_PREFERENCES: setExportPreferences,
27✔
104

105
    ARTICLE_PREFERENCES_TOGGLE: toggleArticlePreferences,
27✔
106
    SET_ARTICLE_PREFERENCES: setArticlePreferences,
27✔
107

108
    UPDATE_EDITOR_CURSOR_POSITION: updateEditorCursorPosition,
27✔
109

110
    SET_ACTIVE_WORKSPACE: setActiveWorkspace,
27✔
111

112
    UPDATE_SELECTED_TAG: updateSelectedTag,
27✔
113
  })
27✔
114
}
27✔
115

116
function persistStateIntoLocalStorage({ getState }) {
27✔
117
  const actionStateMap = new Map([
27✔
118
    ['ARTICLE_PREFERENCES_TOGGLE', 'articlePreferences'],
27✔
119
    ['SET_ARTICLE_PREFERENCES', 'articlePreferences'],
27✔
120
    ['USER_PREFERENCES_TOGGLE', 'userPreferences'],
27✔
121
    ['SET_EXPORT_PREFERENCES', 'exportPreferences'],
27✔
122
  ])
27✔
123

124
  return (next) => {
27✔
125
    return (action) => {
27✔
126
      if (actionStateMap.has(action.type)) {
3!
UNCOV
127
        const key = actionStateMap.get(action.type)
×
128
        // we run the reducer first
×
129
        next(action)
×
UNCOV
130
        // we fetch the updated state
×
131
        const state = getState()[key]
×
132

133
        // we persist it for a later page reload
×
134
        localStorage.setItem(key, JSON.stringify(state))
×
135

UNCOV
136
        return
×
137
      } else if (action.type === 'LOGOUT') {
3!
UNCOV
138
        localStorage.removeItem('articlePreferences')
×
UNCOV
139
        localStorage.removeItem('userPreferences')
×
UNCOV
140
      }
×
141

142
      if (action.type === 'LOGIN' || action.type === 'UPDATE_SESSION_TOKEN') {
3✔
143
        next(action)
1✔
144
        const { sessionToken } = getState()
1✔
145
        localStorage.setItem(sessionTokenName, sessionToken)
1✔
146
        return
1✔
147
      }
1✔
148

149
      if (action.type === 'LOGOUT') {
2!
UNCOV
150
        localStorage.removeItem(sessionTokenName)
×
UNCOV
151
        return next(action)
×
UNCOV
152
      }
×
153

154
      return next(action)
2✔
155
    }
3✔
156
  }
27✔
157
}
27✔
158

159
function setProfile(state, action) {
×
UNCOV
160
  const { user } = action
×
161

162
  if (!user) {
×
163
    return { ...state, activeUser: structuredClone(initialState.activeUser) }
×
164
  }
×
165

166
  return {
×
167
    ...state,
×
168
    activeUser: {
×
UNCOV
169
      ...state.activeUser,
×
170
      ...user,
×
171
    },
×
172
  }
×
173
}
×
174

175
function setSessionToken(state, { token: sessionToken }) {
×
UNCOV
176
  return {
×
UNCOV
177
    ...state,
×
UNCOV
178
    sessionToken,
×
179
  }
×
180
}
×
181

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

197
  return state
1✔
198
}
1✔
199

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

207
function logoutUser() {
×
208
  return structuredClone(initialState)
×
209
}
×
210

211
function updateArticleStats(state, { md }) {
×
UNCOV
212
  return {
×
213
    ...state,
×
214
    articleStats: computeTextStats(md),
×
215
  }
×
216
}
×
217

218
function updateArticleStructure(state, { md }) {
×
219
  const text = (md || '').trim()
×
220
  const articleStructure = text
×
221
    .split('\n')
×
222
    .map((line, index) => ({ line, index }))
×
223
    .filter((lineWithIndex) => lineWithIndex.line.match(/^##+ /))
×
224
    .map((lineWithIndex) => {
×
225
      const title = lineWithIndex.line
×
226
        .replace(/##/, '')
×
227
        //arrow backspace (\u21B3)
×
UNCOV
228
        .replace(/#\s/g, '\u21B3')
×
229
        // middle dot (\u00B7) + non-breaking space (\xa0)
×
230
        .replace(/#/g, '\u00B7\xa0')
×
UNCOV
231
      return { ...lineWithIndex, title }
×
232
    })
×
233

234
  return { ...state, articleStructure }
×
UNCOV
235
}
×
236

237
function updateArticleWriters(state, { articleWriters }) {
×
238
  return { ...state, articleWriters }
×
239
}
×
240

241
function updateArticleWorkingCopyStatus(state, { status }) {
×
UNCOV
242
  return {
×
UNCOV
243
    ...state,
×
UNCOV
244
    articleWorkingCopy: { ...state.articleWorkingCopy, status },
×
245
  }
×
UNCOV
246
}
×
247

248
function togglePreferences(storeKey) {
40✔
249
  return function togglePreferencesReducer(state, { key, value }) {
40✔
250
    const preferences = state[storeKey]
×
251

252
    return {
×
253
      ...state,
×
254
      [storeKey]: {
×
UNCOV
255
        ...preferences,
×
UNCOV
256
        [key]: value === undefined ? !preferences[key] : value,
×
UNCOV
257
      },
×
UNCOV
258
    }
×
259
  }
×
260
}
40✔
261

262
function setPreferences(storeKey) {
40✔
263
  return function setPreferencesReducer(state, { key, value }) {
40✔
264
    const preferences = state[storeKey]
×
265

266
    return {
×
267
      ...state,
×
268
      [storeKey]: {
×
UNCOV
269
        ...preferences,
×
UNCOV
270
        [key]: value,
×
UNCOV
271
      },
×
UNCOV
272
    }
×
UNCOV
273
  }
×
274
}
40✔
275

276
const toggleArticlePreferences = togglePreferences('articlePreferences')
1✔
277
const setArticlePreferences = setPreferences('articlePreferences')
1✔
278
const toggleUserPreferences = togglePreferences('userPreferences')
1✔
279
const setExportPreferences = setPreferences('exportPreferences')
1✔
280

281
function updateEditorCursorPosition(state, { lineNumber, column }) {
×
282
  return {
×
283
    ...state,
×
284
    editorCursorPosition: {
×
UNCOV
285
      lineNumber,
×
286
      column,
×
287
    },
×
288
  }
×
289
}
×
290

291
function setActiveWorkspace(state, { workspaceId }) {
×
292
  return {
×
293
    ...state,
×
294
    activeWorkspaceId: workspaceId,
×
295
  }
×
296
}
×
297

UNCOV
298
function updateSelectedTag(state, { tagId }) {
×
UNCOV
299
  const { selectedTagIds } = state.activeUser
×
UNCOV
300
  return {
×
301
    ...state,
×
302
    activeUser: {
×
303
      ...state.activeUser,
×
UNCOV
304
      selectedTagIds: selectedTagIds.includes(tagId)
×
UNCOV
305
        ? selectedTagIds.filter((selectedTagId) => selectedTagId !== tagId)
×
UNCOV
306
        : [...selectedTagIds, tagId],
×
UNCOV
307
    },
×
UNCOV
308
  }
×
UNCOV
309
}
×
310

311
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
1✔
312
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
1!
UNCOV
313
      trace: true,
×
UNCOV
314
      traceLimit: 25,
×
UNCOV
315
    })
×
316
  : compose
1✔
317

318
export default function createReduxStore(state = {}) {
1✔
319
  return createStore(
27✔
320
    createRootReducer({
27✔
321
      ...structuredClone(initialState),
27✔
322
      ...structuredClone(state),
27✔
323
    }),
27✔
324
    composeEnhancers(
27✔
325
      applyMiddleware(persistStateIntoLocalStorage),
27✔
326
      createSentryReduxEnhancer()
27✔
327
    )
27✔
328
  )
27✔
329
}
27✔
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