Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ce09506
feat(helpers): add shared reactive initialActionCode ref
vitormattos Feb 26, 2026
8087437
feat(composables): add useFileListWidth composable
vitormattos Feb 26, 2026
f545fb9
feat(utils): add timePresets utility with getTimePresets and getTimeP…
vitormattos Feb 26, 2026
f46d632
refactor(FileListFilter): replace NcActions with NcPopover-based layout
vitormattos Feb 26, 2026
96add05
feat(FileListFilterChips): add dedicated active filter chips component
vitormattos Feb 26, 2026
c113e74
refactor(FileListFilters): split filter buttons and active chips into…
vitormattos Feb 26, 2026
f3925f4
refactor(FileListFilterModified): use shared timePresets utility
vitormattos Feb 26, 2026
5f2cd93
refactor(FileListFilterStatus): replace NcActionButton with NcButton
vitormattos Feb 26, 2026
54dfe27
feat(FilesList): add FileListFilters to file list header
vitormattos Feb 26, 2026
dd8ba18
fix(FilesListVirtual): fix empty space when no active filters; use Fi…
vitormattos Feb 26, 2026
284bf6a
feat(store/filters): add filterModifiedRange getter; persist filter_m…
vitormattos Feb 26, 2026
4607f7c
fix(store/files): restore saved filters on page load using persisted …
vitormattos Feb 26, 2026
36b4b86
test(FileListFilter): add component tests
vitormattos Feb 26, 2026
7d55d26
test(FileListFilterChips): add component tests
vitormattos Feb 26, 2026
c3ab3cd
test(FileListFilterModified): add component tests
vitormattos Feb 26, 2026
a59fb23
test(FileListFilterStatus): add component tests
vitormattos Feb 26, 2026
b0242d0
test(FileListFilters): add component tests
vitormattos Feb 26, 2026
911b372
test(timePresets): add utility tests
vitormattos Feb 26, 2026
7ab6e43
test(store/filters): add filterModifiedRange getter and filter_modifi…
vitormattos Feb 26, 2026
7e96b2b
test(store/files): update filters mock to match new store interface
vitormattos Feb 26, 2026
467364e
fix(test/Signer): use vi.hoisted for translation helpers to fix mock …
vitormattos Feb 26, 2026
d7dfaef
test(FilesList): add test for FileListFilters placement in header
vitormattos Feb 26, 2026
b0d122e
fix(store/filters): remove premature emit from onFilterUpdateChips
vitormattos Feb 26, 2026
2099b1b
fix(store/filters): use files_list_filter_* keys to match PHP config
vitormattos Feb 26, 2026
217ad4c
test(store/filters): add initialisation tests for files_list_filter_*…
vitormattos Feb 26, 2026
81fa244
fix: add pending file
vitormattos Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions ActionMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { ref } from '@vue/reactivity'

interface ActionCodes {
REDIRECT: number
CREATE_ACCOUNT: number
DO_NOTHING: number
SIGN: number
SIGN_INTERNAL: number
SIGN_ID_DOC: number
SHOW_ERROR: number
SIGNED: number
CREATE_SIGNATURE_PASSWORD: number
RENEW_EMAIL: number
INCOMPLETE_SETUP: number
}

interface ActionCodeToRoute {
[key: number]: string
}

interface RequirementToModal {
[key: string]: string
}

export const ACTION_CODES: Readonly<ActionCodes> = Object.freeze({
REDIRECT: 1000,
CREATE_ACCOUNT: 1500,
DO_NOTHING: 2000,
SIGN: 2500,
SIGN_INTERNAL: 2625,
SIGN_ID_DOC: 2750,
SHOW_ERROR: 3000,
SIGNED: 3500,
CREATE_SIGNATURE_PASSWORD: 4000,
RENEW_EMAIL: 4500,
INCOMPLETE_SETUP: 5000,
})

export const ACTION_CODE_TO_ROUTE: Readonly<ActionCodeToRoute> = Object.freeze({
[ACTION_CODES.REDIRECT]: 'redirect',
[ACTION_CODES.CREATE_ACCOUNT]: 'CreateAccount',
[ACTION_CODES.DO_NOTHING]: 'current',
[ACTION_CODES.SIGN]: 'SignPDF',
[ACTION_CODES.SIGN_INTERNAL]: 'SignPDF',
[ACTION_CODES.SIGN_ID_DOC]: 'IdDocsApprove',
[ACTION_CODES.SHOW_ERROR]: 'DefaultPageError',
[ACTION_CODES.SIGNED]: 'ValidationFile',
[ACTION_CODES.CREATE_SIGNATURE_PASSWORD]: 'CreatePassword',
[ACTION_CODES.RENEW_EMAIL]: 'RenewEmail',
[ACTION_CODES.INCOMPLETE_SETUP]: 'Incomplete',
})

/**
* Shared reactive ref for the initial action code injected by the server
* (#initial-state-libresign-action). Written once by router.ts beforeEach,
* read by App.vue. Lives here (not in router.ts) to avoid App.vue triggering
* the router module's side effects (createRouter, generateUrl) on import.
*/
export const initialActionCode = ref(0)

export const REQUIREMENT_TO_MODAL: Readonly<RequirementToModal> = Object.freeze({
identificationDocuments: 'uploadDocuments',
emailCode: 'emailToken',
createSignature: 'createSignature',
tokenCode: 'token',
uploadCertificate: 'uploadCertificate',
createPassword: 'createPassword',
passwordSignature: 'password',
clickToSign: 'clickToSign',
})
64 changes: 64 additions & 0 deletions src/composables/useFileListWidth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { computed, readonly, ref } from '@vue/reactivity'

/** The element we observe */
let element: HTMLElement | undefined

/** The current width of the element */
const width = ref<number>(0)

const isWide = computed(() => width.value >= 1024)
const isMedium = computed(() => width.value >= 512 && width.value < 1024)
const isNarrow = computed(() => width.value < 512)

const observer = new ResizeObserver(([el]) => {
if (!el) {
return
}

const contentBoxSize = el.contentBoxSize?.[0]
if (contentBoxSize) {
// use the newer `contentBoxSize` property if available
width.value = contentBoxSize.inlineSize
} else {
// fall back to `contentRect`
width.value = el.contentRect.width
}
})

/**
* Update the observed element if needed and reconfigure the observer
*/
function updateObserver() {
const el = document.querySelector<HTMLElement>('#app-content-vue') ?? document.body
if (el !== element) {
// if already observing: stop observing the old element
if (element) {
observer.unobserve(element)
}
// observe the new element
observer.observe(el)
element = el
}
}

/**
* Get the reactive width of the file list
*/
export function useFileListWidth() {
// Update the observer in setup context so we already have an initial value
updateObserver()

return {
width: readonly(width),

isWide,
isMedium,
isNarrow,
}
}
18 changes: 8 additions & 10 deletions src/store/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,16 +567,14 @@ export const useFilesStore = function(...args) {
params.set(key, value)
}
}
const { chips } = useFiltersStore()
if (chips?.status) {
chips.status.forEach(status => {
params.append('status[]', status.id)
})
}
if (chips?.modified?.length) {
const { start, end } = chips.modified[0]
params.set('start', Math.floor(start / 1000))
params.set('end', Math.floor(end / 1000))
const filtersStore = useFiltersStore()
filtersStore.filterStatusArray.forEach(id => {
params.append('status[]', id)
})
const modifiedRange = filtersStore.filterModifiedRange
if (modifiedRange) {
params.set('start', Math.floor(modifiedRange.start / 1000))
params.set('end', Math.floor(modifiedRange.end / 1000))
}
const { sortingMode, sortingDirection } = useFilesSortingStore()
if (sortingMode) {
Expand Down
20 changes: 15 additions & 5 deletions src/store/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import logger from '../helpers/logger'
import { getTimePresetRange } from '../utils/timePresets.js'

export const useFiltersStore = defineStore('filter', {
state: () => ({
chips: {},
filter_modified: loadState('libresign', 'filters', { filter_modified: '' }).filter_modified,
filter_status: loadState('libresign', 'filters', { filter_status: '' }).filter_status,
filter_modified: loadState('libresign', 'filters', {}).files_list_filter_modified ?? '',
filter_status: loadState('libresign', 'filters', {}).files_list_filter_status ?? ''
}),

getters: {
Expand All @@ -29,13 +30,20 @@ export const useFiltersStore = defineStore('filter', {
return []
}
},
/**
* Returns { start, end } in ms for the saved modified preset, or null.
* Computed fresh on each access so date boundaries are always current.
*/
filterModifiedRange(state) {
return getTimePresetRange(state.filter_modified)
},
},


actions: {
async onFilterUpdateChips(event) {
this.chips = { ...this.chips, [event.id]: [...event.detail] }

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

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

await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'filter_modified' }), {
await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'files_list_filter_modified' }), {
value,
})

this.filter_modified = value

emit('libresign:filters:update')
}

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

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

await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'filter_status' }), {
await axios.put(generateOcsUrl('/apps/libresign/api/v1/account/config/{key}', { key: 'files_list_filter_status' }), {
value,
})

Expand Down
16 changes: 10 additions & 6 deletions src/tests/components/Signers/Signer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ type FilesStoreMock = ReturnType<typeof useFilesStore> & {
isOriginalFileDeleted: MockedFunction<() => boolean>
}

const t: TranslationFunction = (_app, text, vars) => {
if (vars) {
return text.replace(/{(\w+)}/g, (_m, key) => String(vars[key]))
const { t, n } = vi.hoisted(() => {
const t: TranslationFunction = (_app, text, vars) => {
if (vars) {
return text.replace(/{(\w+)}/g, (_m, key) => String(vars[key]))
}
return text
}
return text
}

const n: PluralTranslationFunction = (_app, singular, plural, count) => (count === 1 ? singular : plural)
const n: PluralTranslationFunction = (_app, singular, plural, count) => (count === 1 ? singular : plural)

return { t, n }
})

let Signer: SignerComponent

Expand Down
7 changes: 6 additions & 1 deletion src/tests/store/files.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ vi.mock('./filesSorting.js', () => ({
}))

vi.mock('./filters.js', () => ({
useFiltersStore: vi.fn(() => ({ filters: {} })),
useFiltersStore: vi.fn(() => ({
filterStatusArray: [],
filterModifiedRange: null,
filter_modified: '',
filter_status: '',
})),
}))

vi.mock('./identificationDocument.js', () => ({
Expand Down
Loading
Loading