Skip to content

Commit 0b6fcab

Browse files
authored
Merge pull request #7128 from LibreSign/fix/pending-code-from-main
fix: add pending code from main
2 parents 85a8ca9 + e53d52f commit 0b6fcab

6 files changed

Lines changed: 77 additions & 32 deletions

File tree

src/components/Request/RequestPicker.vue

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,32 @@
44
-->
55
<template>
66
<div v-if="canRequestSign">
7-
<NcActions :menu-name="t('libresign', 'Request')"
8-
:inline="inline ? 3 : 0"
9-
:force-name="inline"
10-
:class="{column: inline}"
7+
<div v-if="inline" class="request-picker-buttons">
8+
<NcButton variant="secondary"
9+
@click="showModalUploadFromUrl()">
10+
<template #icon>
11+
<NcIconSvgWrapper :path="mdiLink" :size="20" />
12+
</template>
13+
{{ t('libresign', 'Upload from URL') }}
14+
</NcButton>
15+
<NcButton variant="secondary"
16+
:title="envelopeEnabled ? t('libresign', 'Multiple files allowed') : null"
17+
@click="openFilePicker">
18+
<template #icon>
19+
<NcIconSvgWrapper :path="mdiFolder" :size="20" />
20+
</template>
21+
{{ t('libresign', 'Choose from Files') }}
22+
</NcButton>
23+
<NcButton variant="secondary"
24+
@click="uploadFile">
25+
<template #icon>
26+
<NcIconSvgWrapper :path="mdiUpload" :size="20" />
27+
</template>
28+
{{ t('libresign', 'Upload') }}
29+
</NcButton>
30+
</div>
31+
<NcActions v-else
32+
:menu-name="t('libresign', 'Request')"
1133
:variant="variant"
1234
v-model:open="openedMenu">
1335
<template #icon>
@@ -16,22 +38,22 @@
1638
<NcActionButton :wide="true"
1739
@click="showModalUploadFromUrl()">
1840
<template #icon>
19-
<NcIconSvgWrapper :path="mdiLink" :size="20" />
41+
<NcIconSvgWrapper :path="mdiLink" :size="20" />
2042
</template>
2143
{{ t('libresign', 'Upload from URL') }}
2244
</NcActionButton>
2345
<NcActionButton :wide="true"
2446
:title="envelopeEnabled ? t('libresign', 'Multiple files allowed') : null"
2547
@click="openFilePicker">
2648
<template #icon>
27-
<NcIconSvgWrapper :path="mdiFolder" :size="20" />
49+
<NcIconSvgWrapper :path="mdiFolder" :size="20" />
2850
</template>
2951
{{ t('libresign', 'Choose from Files') }}
3052
</NcActionButton>
3153
<NcActionButton :wide="true"
3254
@click="uploadFile">
3355
<template #icon>
34-
<NcIconSvgWrapper :path="mdiUpload" :size="20" />
56+
<NcIconSvgWrapper :path="mdiUpload" :size="20" />
3557
</template>
3658
{{ t('libresign', 'Upload') }}
3759
</NcActionButton>
@@ -451,9 +473,9 @@ export default {
451473
</script>
452474
453475
<style lang="scss" scoped>
454-
.column {
476+
.request-picker-buttons {
455477
display: flex;
456-
gap: 12px; flex: 1;
478+
gap: 12px;
457479
flex-direction: column;
458480
align-items: stretch;
459481

src/services/SigningRequirementValidator.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ interface ValidatorConfig {
3535
errors?: Array<{ code?: number; [key: string]: unknown }>
3636
hasSignatures?: boolean
3737
canCreateSignature?: boolean
38-
signerHasSignRequest?: boolean
3938
[key: string]: unknown
4039
}
4140

@@ -122,10 +121,6 @@ export class SigningRequirementValidator {
122121
return false
123122
}
124123

125-
if (config.signerHasSignRequest) {
126-
return true
127-
}
128-
129124
const visibleElements = this.signStore.document?.visibleElements || []
130125
return visibleElements.some(row => String(row.signRequestId) === String(signRequestId))
131126
}

src/tests/services/SigningRequirementValidator.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ describe('SigningRequirementValidator', () => {
162162
expect(result).toBe(false)
163163
})
164164

165-
it('requires createSignature even when document has no placed elements but canCreateSignature is true and user is signer', () => {
166-
// When admin configures GRAPHIC_ONLY mode, canCreateSignature=true.
167-
// The signer must draw their signature even if no element box was placed on the document.
165+
it('does not require createSignature when signer has no placed visibleElements (clickToSign scenario)', () => {
166+
// Regression: signerHasSignRequest shortcut was bypassing the visibleElements check,
167+
// causing the draw modal to appear for clickToSign documents with no placed element boxes.
168168
const stores = createStores({
169169
signStore: {
170170
document: {
@@ -179,10 +179,10 @@ describe('SigningRequirementValidator', () => {
179179
stores.identificationDocumentStore,
180180
)
181181

182-
// With no placed element AND canCreateSignature=true, we still need to create the signature
183-
const result = validator.needsCreateSignature({ hasSignatures: false, canCreateSignature: true, signerHasSignRequest: true })
182+
// No placed element → should NOT require createSignature, so the sign button is reachable
183+
const result = validator.needsCreateSignature({ hasSignatures: false, canCreateSignature: true })
184184

185-
expect(result).toBe(true)
185+
expect(result).toBe(false)
186186
})
187187

188188
it('returns createSignature before clickToSign when no signature exists', () => {

src/tests/views/FilesList/FilesList.spec.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { useUserConfigStore } from '../../../store/userconfig.js'
1313

1414
vi.mock('@nextcloud/l10n', () => ({
1515
t: vi.fn((_app: string, text: string) => text),
16-
isRTL: vi.fn(() => false),
1716
}))
1817

1918
vi.mock('@nextcloud/logger', () => ({
@@ -115,6 +114,13 @@ vi.mock('../../../views/FilesList/FilesListVirtual.vue', () => ({
115114
},
116115
}))
117116

117+
vi.mock('../../../views/FilesList/FileListFilters.vue', () => ({
118+
default: {
119+
name: 'FileListFilters',
120+
template: '<div class="file-list-filters-stub" />',
121+
},
122+
}))
123+
118124
vi.mock('../../../components/Request/RequestPicker.vue', () => ({
119125
default: {
120126
name: 'RequestPicker',
@@ -150,8 +156,8 @@ describe('FilesList.vue rendering rules', () => {
150156
await flushPromises()
151157

152158
expect(wrapper.vm.mdiFolder).toBeTruthy()
153-
expect(wrapper.vm.mdiViewGrid).toBeTruthy()
154-
expect(wrapper.vm.mdiViewList).toBeTruthy()
159+
expect(wrapper.vm.mdiViewGridOutline).toBeTruthy()
160+
expect(wrapper.vm.mdiFormatListBulletedSquare).toBeTruthy()
155161
expect(wrapper.vm.mdiChevronDown).toBeTruthy()
156162
expect(wrapper.vm.mdiChevronUp).toBeTruthy()
157163
expect(wrapper.vm.mdiReload).toBeTruthy()
@@ -179,6 +185,27 @@ describe('FilesList.vue rendering rules', () => {
179185
expect(firstChild.classList.contains('request-picker-stub')).toBe(true)
180186
})
181187

188+
it('renders FileListFilters in the header before the grid toggle button', async () => {
189+
const filesStore = useFilesStore()
190+
vi.spyOn(filesStore, 'getAllFiles').mockResolvedValue({})
191+
192+
const wrapper = mountComponent()
193+
await flushPromises()
194+
195+
const header = wrapper.find('.files-list__header')
196+
const filterStub = header.find('.file-list-filters-stub')
197+
const gridButton = header.find('.files-list__header-grid-button')
198+
199+
expect(filterStub.exists()).toBe(true)
200+
expect(gridButton.exists()).toBe(true)
201+
202+
// FileListFilters must appear before the grid button in the DOM
203+
const children = Array.from(header.element.children)
204+
const filterIndex = children.findIndex(el => el.classList.contains('file-list-filters-stub'))
205+
const gridIndex = children.findIndex(el => el.classList.contains('files-list__header-grid-button'))
206+
expect(filterIndex).toBeLessThan(gridIndex)
207+
})
208+
182209
it('calls filesStore.updateAllFiles once more when reload button is clicked', async () => {
183210
const filesStore = useFilesStore()
184211
vi.spyOn(filesStore, 'getAllFiles').mockResolvedValue({})
@@ -238,7 +265,7 @@ describe('FilesList.vue rendering rules', () => {
238265

239266
const gridButton = wrapper.find('.files-list__header-grid-button')
240267
const iconWithPath = gridButton.findAll('.nc-icon').find((node) => !!node.attributes('data-path'))
241-
expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiViewGrid)
268+
expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiViewGridOutline)
242269
})
243270

244271
it('renders list toggle icon path when in grid mode', async () => {
@@ -252,6 +279,6 @@ describe('FilesList.vue rendering rules', () => {
252279

253280
const gridButton = wrapper.find('.files-list__header-grid-button')
254281
const iconWithPath = gridButton.findAll('.nc-icon').find((node) => !!node.attributes('data-path'))
255-
expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiViewList)
282+
expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiFormatListBulletedSquare)
256283
})
257284
})

src/views/FilesList/FilesList.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
variant="tertiary"
5050
@click="toggleGridView">
5151
<template #icon>
52-
<NcIconSvgWrapper v-if="userConfigStore.files_list_grid_view" :path="mdiViewList" />
53-
<NcIconSvgWrapper v-else :path="mdiViewGrid" />
52+
<NcIconSvgWrapper v-if="userConfigStore.files_list_grid_view" :path="mdiFormatListBulletedSquare" />
53+
<NcIconSvgWrapper v-else :path="mdiViewGridOutline" />
5454
</template>
5555
</NcButton>
5656
</div>
@@ -96,9 +96,9 @@ import {
9696
mdiChevronDown,
9797
mdiChevronUp,
9898
mdiFolder,
99+
mdiFormatListBulletedSquare,
99100
mdiReload,
100-
mdiViewGrid,
101-
mdiViewList,
101+
mdiViewGridOutline,
102102
} from '@mdi/js'
103103
104104
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
@@ -147,9 +147,9 @@ export default {
147147
mdiChevronDown,
148148
mdiChevronUp,
149149
mdiFolder,
150+
mdiFormatListBulletedSquare,
150151
mdiReload,
151-
mdiViewGrid,
152-
mdiViewList,
152+
mdiViewGridOutline,
153153
}
154154
},
155155
data() {

src/views/Settings/SignatureEngine.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export default {
4242
options() {
4343
return [
4444
{ id: 'JSignPdf', label: 'JSignPdf' },
45-
{ id: 'PhpNative', label: t('libresign', 'PHP native') },
45+
// TRANSLATORS "Native" refers to a signature engine that runs directly with PHP, without requiring external runtimes like Java
46+
{ id: 'PhpNative', label: t('libresign', 'Native') },
4647
]
4748
},
4849
selectedOption: {

0 commit comments

Comments
 (0)