Skip to content

Commit 7ffc039

Browse files
authored
Merge pull request #7019 from LibreSign/backport/7018/stable33
[stable33] feat: file list filters
2 parents ec16eb7 + 81fa244 commit 7ffc039

22 files changed

Lines changed: 1578 additions & 153 deletions

ActionMapping.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { ref } from '@vue/reactivity'
7+
8+
interface ActionCodes {
9+
REDIRECT: number
10+
CREATE_ACCOUNT: number
11+
DO_NOTHING: number
12+
SIGN: number
13+
SIGN_INTERNAL: number
14+
SIGN_ID_DOC: number
15+
SHOW_ERROR: number
16+
SIGNED: number
17+
CREATE_SIGNATURE_PASSWORD: number
18+
RENEW_EMAIL: number
19+
INCOMPLETE_SETUP: number
20+
}
21+
22+
interface ActionCodeToRoute {
23+
[key: number]: string
24+
}
25+
26+
interface RequirementToModal {
27+
[key: string]: string
28+
}
29+
30+
export const ACTION_CODES: Readonly<ActionCodes> = Object.freeze({
31+
REDIRECT: 1000,
32+
CREATE_ACCOUNT: 1500,
33+
DO_NOTHING: 2000,
34+
SIGN: 2500,
35+
SIGN_INTERNAL: 2625,
36+
SIGN_ID_DOC: 2750,
37+
SHOW_ERROR: 3000,
38+
SIGNED: 3500,
39+
CREATE_SIGNATURE_PASSWORD: 4000,
40+
RENEW_EMAIL: 4500,
41+
INCOMPLETE_SETUP: 5000,
42+
})
43+
44+
export const ACTION_CODE_TO_ROUTE: Readonly<ActionCodeToRoute> = Object.freeze({
45+
[ACTION_CODES.REDIRECT]: 'redirect',
46+
[ACTION_CODES.CREATE_ACCOUNT]: 'CreateAccount',
47+
[ACTION_CODES.DO_NOTHING]: 'current',
48+
[ACTION_CODES.SIGN]: 'SignPDF',
49+
[ACTION_CODES.SIGN_INTERNAL]: 'SignPDF',
50+
[ACTION_CODES.SIGN_ID_DOC]: 'IdDocsApprove',
51+
[ACTION_CODES.SHOW_ERROR]: 'DefaultPageError',
52+
[ACTION_CODES.SIGNED]: 'ValidationFile',
53+
[ACTION_CODES.CREATE_SIGNATURE_PASSWORD]: 'CreatePassword',
54+
[ACTION_CODES.RENEW_EMAIL]: 'RenewEmail',
55+
[ACTION_CODES.INCOMPLETE_SETUP]: 'Incomplete',
56+
})
57+
58+
/**
59+
* Shared reactive ref for the initial action code injected by the server
60+
* (#initial-state-libresign-action). Written once by router.ts beforeEach,
61+
* read by App.vue. Lives here (not in router.ts) to avoid App.vue triggering
62+
* the router module's side effects (createRouter, generateUrl) on import.
63+
*/
64+
export const initialActionCode = ref(0)
65+
66+
export const REQUIREMENT_TO_MODAL: Readonly<RequirementToModal> = Object.freeze({
67+
identificationDocuments: 'uploadDocuments',
68+
emailCode: 'emailToken',
69+
createSignature: 'createSignature',
70+
tokenCode: 'token',
71+
uploadCertificate: 'uploadCertificate',
72+
createPassword: 'createPassword',
73+
passwordSignature: 'password',
74+
clickToSign: 'clickToSign',
75+
})
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors
4+
* SPDX-License-Identifier: AGPL-3.0-or-later
5+
*/
6+
7+
import { computed, readonly, ref } from '@vue/reactivity'
8+
9+
/** The element we observe */
10+
let element: HTMLElement | undefined
11+
12+
/** The current width of the element */
13+
const width = ref<number>(0)
14+
15+
const isWide = computed(() => width.value >= 1024)
16+
const isMedium = computed(() => width.value >= 512 && width.value < 1024)
17+
const isNarrow = computed(() => width.value < 512)
18+
19+
const observer = new ResizeObserver(([el]) => {
20+
if (!el) {
21+
return
22+
}
23+
24+
const contentBoxSize = el.contentBoxSize?.[0]
25+
if (contentBoxSize) {
26+
// use the newer `contentBoxSize` property if available
27+
width.value = contentBoxSize.inlineSize
28+
} else {
29+
// fall back to `contentRect`
30+
width.value = el.contentRect.width
31+
}
32+
})
33+
34+
/**
35+
* Update the observed element if needed and reconfigure the observer
36+
*/
37+
function updateObserver() {
38+
const el = document.querySelector<HTMLElement>('#app-content-vue') ?? document.body
39+
if (el !== element) {
40+
// if already observing: stop observing the old element
41+
if (element) {
42+
observer.unobserve(element)
43+
}
44+
// observe the new element
45+
observer.observe(el)
46+
element = el
47+
}
48+
}
49+
50+
/**
51+
* Get the reactive width of the file list
52+
*/
53+
export function useFileListWidth() {
54+
// Update the observer in setup context so we already have an initial value
55+
updateObserver()
56+
57+
return {
58+
width: readonly(width),
59+
60+
isWide,
61+
isMedium,
62+
isNarrow,
63+
}
64+
}

src/store/files.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -567,16 +567,14 @@ export const useFilesStore = function(...args) {
567567
params.set(key, value)
568568
}
569569
}
570-
const { chips } = useFiltersStore()
571-
if (chips?.status) {
572-
chips.status.forEach(status => {
573-
params.append('status[]', status.id)
574-
})
575-
}
576-
if (chips?.modified?.length) {
577-
const { start, end } = chips.modified[0]
578-
params.set('start', Math.floor(start / 1000))
579-
params.set('end', Math.floor(end / 1000))
570+
const filtersStore = useFiltersStore()
571+
filtersStore.filterStatusArray.forEach(id => {
572+
params.append('status[]', id)
573+
})
574+
const modifiedRange = filtersStore.filterModifiedRange
575+
if (modifiedRange) {
576+
params.set('start', Math.floor(modifiedRange.start / 1000))
577+
params.set('end', Math.floor(modifiedRange.end / 1000))
580578
}
581579
const { sortingMode, sortingDirection } = useFilesSortingStore()
582580
if (sortingMode) {

src/store/filters.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import { loadState } from '@nextcloud/initial-state'
1010
import axios from '@nextcloud/axios'
1111
import { generateOcsUrl } from '@nextcloud/router'
1212
import logger from '../helpers/logger'
13+
import { getTimePresetRange } from '../utils/timePresets.js'
1314

1415
export const useFiltersStore = defineStore('filter', {
1516
state: () => ({
1617
chips: {},
17-
filter_modified: loadState('libresign', 'filters', { filter_modified: '' }).filter_modified,
18-
filter_status: loadState('libresign', 'filters', { filter_status: '' }).filter_status,
18+
filter_modified: loadState('libresign', 'filters', {}).files_list_filter_modified ?? '',
19+
filter_status: loadState('libresign', 'filters', {}).files_list_filter_status ?? ''
1920
}),
2021

2122
getters: {
@@ -29,13 +30,20 @@ export const useFiltersStore = defineStore('filter', {
2930
return []
3031
}
3132
},
33+
/**
34+
* Returns { start, end } in ms for the saved modified preset, or null.
35+
* Computed fresh on each access so date boundaries are always current.
36+
*/
37+
filterModifiedRange(state) {
38+
return getTimePresetRange(state.filter_modified)
39+
},
3240
},
3341

42+
3443
actions: {
3544
async onFilterUpdateChips(event) {
3645
this.chips = { ...this.chips, [event.id]: [...event.detail] }
3746

38-
emit('libresign:filters:update')
3947
logger.debug('File list filter chips updated', { chips: event.detail })
4048

4149
},
@@ -47,18 +55,20 @@ export const useFiltersStore = defineStore('filter', {
4755
if(event.id == 'modified'){
4856
let value = this.chips['modified'][0]?.id || '';
4957

50-
await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'filter_modified' }), {
58+
await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'files_list_filter_modified' }), {
5159
value,
5260
})
5361

62+
this.filter_modified = value
63+
5464
emit('libresign:filters:update')
5565
}
5666

5767
if(event.id == 'status'){
5868

5969
const value = event.detail.length > 0 ? JSON.stringify(event.detail.map(item => item.id)) : '';
6070

61-
await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'filter_status' }), {
71+
await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'files_list_filter_status' }), {
6272
value,
6373
})
6474

src/tests/components/Signers/Signer.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@ type FilesStoreMock = ReturnType<typeof useFilesStore> & {
3333
isOriginalFileDeleted: MockedFunction<() => boolean>
3434
}
3535

36-
const t: TranslationFunction = (_app, text, vars) => {
37-
if (vars) {
38-
return text.replace(/{(\w+)}/g, (_m, key) => String(vars[key]))
36+
const { t, n } = vi.hoisted(() => {
37+
const t: TranslationFunction = (_app, text, vars) => {
38+
if (vars) {
39+
return text.replace(/{(\w+)}/g, (_m, key) => String(vars[key]))
40+
}
41+
return text
3942
}
40-
return text
41-
}
4243

43-
const n: PluralTranslationFunction = (_app, singular, plural, count) => (count === 1 ? singular : plural)
44+
const n: PluralTranslationFunction = (_app, singular, plural, count) => (count === 1 ? singular : plural)
45+
46+
return { t, n }
47+
})
4448

4549
let Signer: SignerComponent
4650

src/tests/store/files.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,12 @@ vi.mock('./filesSorting.js', () => ({
107107
}))
108108

109109
vi.mock('./filters.js', () => ({
110-
useFiltersStore: vi.fn(() => ({ filters: {} })),
110+
useFiltersStore: vi.fn(() => ({
111+
filterStatusArray: [],
112+
filterModifiedRange: null,
113+
filter_modified: '',
114+
filter_status: '',
115+
})),
111116
}))
112117

113118
vi.mock('./identificationDocument.js', () => ({

0 commit comments

Comments
 (0)