From d75c24bf3423b259d6e2e92169f546db99a706af Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:09 -0300 Subject: [PATCH 001/702] build(vite): add vite configuration for webpack replacement Add vite.config.mjs to replace webpack build system. - Configure Vite for app entry points (main, settings, tab, external, init, validation) - Allow proper CSS splitting per entry point - Configure dev server on port 3000 for local development - Setup library mode for production builds Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 644cfb5c2831794da4ed2ff4f6a70cf28f061b1e) --- vite.config.mjs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 vite.config.mjs diff --git a/vite.config.mjs b/vite.config.mjs new file mode 100644 index 0000000000..8938b2c8c1 --- /dev/null +++ b/vite.config.mjs @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: 2024 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createAppConfig } from '@nextcloud/vite-config' +import { resolve } from 'node:path' + +export default createAppConfig({ + main: resolve('src/main.ts'), + init: resolve('src/init.ts'), + tab: resolve('src/tab.ts'), + settings: resolve('src/settings.ts'), + external: resolve('src/external.ts'), + validation: resolve('src/validation.ts'), +}, { + config: { + server: { + port: 3000, + host: '0.0.0.0', + }, + resolve: { + alias: { + '@': resolve(import.meta.dirname, 'src'), + }, + }, + }, +}) From ac62adedee1c2ac43e660a516b92271273c1740f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 002/702] build(deps): update package.json and package-lock.json for vue 3 Update npm dependencies for Vue 3 and TypeScript migration. - Update package.json scripts (build, dev, preview, serve) - Add Vite and Vue 3 dependencies - Remove webpack and Vue 2 dependencies - Update package-lock.json with new resolved versions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit a411e6ca55797e0e80e1a9cad68e106fe58de250) --- package.json | 99 +++++++++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 92711f10e3..d2628250a3 100644 --- a/package.json +++ b/package.json @@ -1,87 +1,92 @@ { "name": "libresign", "description": "A app for signing documents", - "version": "12.2.3", + "version": "13.0.0-dev.0", "license": "agpl", "private": true, "scripts": { - "build": "webpack --node-env production --progress", - "dev": "webpack --node-env development --progress", - "watch": "webpack --node-env development --progress --watch", - "serve": "webpack serve --node-env development --progress --allowed-hosts all --host 0.0.0.0", + "build": "vite build", + "dev": "vite build --mode development", + "watch": "vite build --mode development --watch", + "serve": "vite dev", "typescript:check": "tsc --noEmit", "typescript:generate": "npx openapi-typescript -t", "lint": "eslint", "lint:fix": "eslint --fix", - "stylelint": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue", - "stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue --fix", + "stylelint": "stylelint src/**/*.scss src/**/*.vue", + "stylelint:fix": "stylelint src/**/*.scss src/**/*.vue --fix", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage" }, "dependencies": { + "@codemirror/autocomplete": "^6.18.3", + "@codemirror/commands": "^6.7.1", + "@codemirror/lang-html": "^6.4.9", + "@codemirror/language": "^6.10.3", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.36.2", "@fontsource/dancing-script": "^5.2.8", - "@libresign/pdf-elements": "^0.4.0", + "@libresign/pdf-elements": "file:../pdf-elements", "@marionebl/option": "^1.0.8", "@mdi/js": "^7.4.47", "@mdi/svg": "^7.4.47", "@nextcloud/auth": "^2.5.3", "@nextcloud/axios": "^2.5.2", "@nextcloud/capabilities": "^1.2.1", - "@nextcloud/dialogs": "^6.4.1", + "@nextcloud/dialogs": "^7.3.0", "@nextcloud/event-bus": "^3.3.3", "@nextcloud/files": "^3.12.0", - "@nextcloud/initial-state": "^2.2.0", + "@nextcloud/initial-state": "^3.0.0", "@nextcloud/l10n": "^3.4.1", - "@nextcloud/logger": "^3.0.2", + "@nextcloud/logger": "^3.0.3", "@nextcloud/moment": "^1.3.5", - "@nextcloud/password-confirmation": "^5.3.2", - "@nextcloud/paths": "^2.2.2", - "@nextcloud/router": "^3.0.1", - "@nextcloud/upload": "^1.11.0", - "@nextcloud/vue": "^8.35.3", - "@vueuse/core": "^11.3.0", + "@nextcloud/password-confirmation": "^6.0.2", + "@nextcloud/paths": "^3.1.0", + "@nextcloud/router": "^3.1.0", + "@nextcloud/upload": "^2.0.0-rc.0", + "@nextcloud/vue": "^9.5.0", + "@ssddanbrown/codemirror-lang-twig": "^1.0.0", + "@uiw/codemirror-theme-material": "^4.25.4", + "@vuelidate/core": "^2.0.3", + "@vuelidate/validators": "^2.0.4", + "@vueuse/core": "^14.2.1", + "@vueuse/integrations": "^14.2.1", "blueimp-md5": "^2.19.0", - "codemirror": "^5.65.20", - "copy-webpack-plugin": "^13.0.1", + "codemirror": "^6.0.1", "crypto-js": "^4.2.0", - "debounce": "^2.2.0", - "js-confetti": "^0.12.0", - "pinia": "^2.3.1", - "signature_pad": "^5.1.1", - "vue": "^2.7.16", - "vue-advanced-cropper": "^1.11.7", - "vue-codemirror": "^4.0.6", - "vue-drag-resize": "^1.5.4", - "vue-material-design-icons": "^5.3.1", - "vue-router": "^3.6.5", - "vuedraggable": "^2.24.3", - "vuelidate": "^0.7.7" + "debounce": "^3.0.0", + "js-confetti": "^0.13.1", + "pinia": "^3.0.4", + "pdfjs-dist": "^5.4.624", + "signature_pad": "^5.1.3", + "sortablejs": "^1.15.7", + "vue": "^3.5.28", + "vue-advanced-cropper": "^2.8.9", + "vue-codemirror6": "^1.3.5", + "vue-router": "^5.0.3", + "vuedraggable": "^4.1.0" }, "browserslist": [ "extends @nextcloud/browserslist-config" ], "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^24.0.0", + "npm": "^11.3.0" }, "devDependencies": { - "@babel/core": "^7.28.5", - "@nextcloud/babel-config": "^1.3.0", "@nextcloud/browserslist-config": "^3.1.2", "@nextcloud/eslint-config": "^8.4.2", - "@nextcloud/stylelint-config": "^3.1.1", - "@nextcloud/webpack-vue-config": "^6.3.2", - "@vitejs/plugin-vue2": "^2.3.4", + "@nextcloud/stylelint-config": "^3.2.1", + "@nextcloud/vite-config": "^2.5.2", + "@vitejs/plugin-vue": "^6.0.3", "@vitest/coverage-v8": "^4.0.18", - "@vue/test-utils": "^1.3.6", - "@vue/tsconfig": "^0.5.1", - "babel-loader-exclude-node-modules-except": "^1.2.1", - "esbuild-loader": "^4.4.0", - "happy-dom": "^20.6.0", - "openapi-typescript": "^7.10.1", - "vitest": "^4.0.18", - "vue-loader": "15.11.1", - "vue-template-compiler": "^2.7.16" + "@vue/test-utils": "^2.4.6", + "@vue/tsconfig": "^0.8.1", + "happy-dom": "^20.7.0", + "openapi-typescript": "^7.13.0", + "typescript": "^5.9.3", + "vite": "^7.1.10", + "vitest": "^4.0.18" } } From 1fb0bc91c5976cb9fda2dc1f541183671497e3e1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 003/702] build(typescript): update tsconfig.json for vue 3 Update TypeScript configuration for Vue 3 composition API support. - Configure strict mode and module resolution - Add Vue 3 and Vite module extensions - Update compiler options for composition API - Configure source maps and output location Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 10b86bba1f6310655d3384707f9601a11358d1d7) --- tsconfig.json | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 3d0d2e6764..9a262dddfe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,51 @@ { "extends": "@vue/tsconfig/tsconfig.json", - "include": ["src/**/*.ts", "src/env.d.ts"], - "exclude": ["node_modules", "vendor"], + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue" + ], + "exclude": [ + "node_modules", + "vendor", + "dist", + "build" + ], "compilerOptions": { "outDir": "./js", + "rootDir": "./src", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "noEmit": true, "allowJs": true, "checkJs": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, "allowImportingTsExtensions": true, - "lib": ["ESNext"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ] + }, + "types": [ + "vue/macros", + "vitest/globals", + "node" + ] }, "vueCompilerOptions": { - "target": 2.7, + "target": 3 } } From 64bd6d2afd0b9dd165f27ebce9a6428393f59c1b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 004/702] build(git): update gitignore for vite build artifacts Update .gitignore to exclude Vite build artifacts. - Add dist/ directory - Add Vite cache directories - Remove webpack-related ignores Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit e0f88cf084024a273c5d1c45534f41321c69c716) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 45712a41d6..24e5eff10f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /tests/integration/vendor /tests/integration/output /js/ +/css/ /build/ node_modules/ /.php-cs-fixer.cache @@ -20,4 +21,4 @@ node_modules/ /appinfo/install-*.json /lib/Vendor/ /coverage - +/dist/ From 6395c5a3fe13cf0059b091082914c027eabbaa67 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 005/702] test(config): update vitest configuration for vue 3 Update vitest.config.js with proper Vue 3 support and composition API. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit bc125a976a14ef16796b4a588f82d8bebefa53fd) --- vitest.config.js | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/vitest.config.js b/vitest.config.js index 642a8dcb0e..7284bf8fa3 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -5,14 +5,41 @@ import { resolve } from 'node:path' import { defineConfig } from 'vitest/config' -import vue from '@vitejs/plugin-vue2' +import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], resolve: { - alias: { - '@': resolve(__dirname, './src'), - }, + alias: [ + { + find: /^vue-select\/dist\/vue-select\.css$/, + replacement: resolve(__dirname, './src/tests/mocks/vue-select.css'), + }, + { + find: /^vue-select\/dist\/vue-select\.es\.js$/, + replacement: resolve(__dirname, './src/tests/mocks/vue-select.js'), + }, + { + find: /^vue-select\/dist\/vue-select\.es$/, + replacement: resolve(__dirname, './src/tests/mocks/vue-select.js'), + }, + { + find: /^vue-select\/dist\/vue-select$/, + replacement: resolve(__dirname, './src/tests/mocks/vue-select.js'), + }, + { + find: /^vue-select$/, + replacement: resolve(__dirname, './src/tests/mocks/vue-select.js'), + }, + { + find: '@libresign/pdf-elements', + replacement: resolve(__dirname, './src/tests/mocks/pdf-elements'), + }, + { + find: /^@\//, + replacement: `${resolve(__dirname, './src')}/`, + }, + ], }, test: { include: ['src/**/*.{test,spec}.?(c|m)[jt]s?(x)'], @@ -20,9 +47,12 @@ export default defineConfig({ globals: true, // Required for transforming CSS files pool: 'vmForks', + deps: { + inline: ['@nextcloud/vue', 'splitpanes', 'vue-select'], + }, server: { deps: { - inline: ['splitpanes'], + inline: ['@nextcloud/vue', 'splitpanes', 'vue-select'], }, }, coverage: { From 56a0d36a8bcd4a58bb405d17cbf2ce9b4f18917d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 006/702] test(setup): update test setup for vue 3 Update src/tests/setup.js to work with Vue 3 and Vitest. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 8744e0c93e80cba8131f3127dca2cd2a2b638c55) --- src/tests/setup.js | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/tests/setup.js b/src/tests/setup.js index 51a2bf8aad..bad9b44af3 100644 --- a/src/tests/setup.js +++ b/src/tests/setup.js @@ -3,6 +3,65 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import { vi } from 'vitest' +import { createPinia, setActivePinia } from 'pinia' + +vi.mock('@vue/test-utils', async (importOriginal) => { + const actual = await importOriginal() + const wrap = (fn) => (...args) => { + if (typeof fn !== 'function') { + return fn + } + const wrapper = fn(...args) + if (wrapper && !wrapper.destroy && typeof wrapper.unmount === 'function') { + wrapper.destroy = wrapper.unmount + } + return wrapper + } + + return { + ...actual, + mount: wrap(actual.mount), + shallowMount: wrap(actual.shallowMount), + } +}) + +vi.mock('vue-select', () => ({ + default: { + name: 'VueSelect', + render() { + return null + }, + }, +})) + +vi.mock('vue-select/dist/vue-select.es.js', () => ({ + default: { + name: 'VueSelect', + render() { + return null + }, + }, +})) + +vi.mock('@nextcloud/vue/components/NcSelect', () => ({ + default: { + name: 'NcSelect', + template: '
', + }, +})) + + +vi.mock('@nextcloud/vue/components/NcRichText', () => ({ + default: { + name: 'NcRichText', + template: '
', + }, +})) + + +setActivePinia(createPinia()) + import './testHelpers/jsdomMocks.js' import './testHelpers/nextcloudMocks.js' import './testHelpers/vueMocks.js' From 5fbd7e10ec9b937d11f596180a2fe3403fb64b0a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 007/702] test(mocks): update jsdom mocks for vue 3 Update jsdom mocks to work with Vue 3 environment. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 9ba738a348dac3ee05c55a8801d3e17b0339eaf8) --- src/tests/testHelpers/jsdomMocks.js | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/tests/testHelpers/jsdomMocks.js b/src/tests/testHelpers/jsdomMocks.js index 24165fb6a8..05f1b88a38 100644 --- a/src/tests/testHelpers/jsdomMocks.js +++ b/src/tests/testHelpers/jsdomMocks.js @@ -83,3 +83,36 @@ if (typeof window !== 'undefined') { } } +if (typeof HTMLCanvasElement !== 'undefined') { + HTMLCanvasElement.prototype.getContext = HTMLCanvasElement.prototype.getContext || function getContext() { + return { + clearRect() {}, + fillRect() {}, + fillText() {}, + measureText() { return { width: 0 } }, + beginPath() {}, + moveTo() {}, + lineTo() {}, + stroke() {}, + setTransform() {}, + save() {}, + restore() {}, + } + } +} + +if (typeof window !== 'undefined') { + class MutationObserverMock { + constructor(callback) { + this._callback = callback + } + observe() {} + disconnect() {} + takeRecords() { + return [] + } + } + window.MutationObserver = MutationObserverMock + globalThis.MutationObserver = MutationObserverMock +} + From b932eb7ef1a6eb8278a77b091aadf19054b1442d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 008/702] test(mocks): update vue mocks for vue 3 Update Vue test mocks and helpers for composition API. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 1f418fb56c17c9667b5ef2d6cd5ece8b891fefcb) --- src/tests/testHelpers/vueMocks.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/tests/testHelpers/vueMocks.js b/src/tests/testHelpers/vueMocks.js index 31587dd616..2ab3ad36eb 100644 --- a/src/tests/testHelpers/vueMocks.js +++ b/src/tests/testHelpers/vueMocks.js @@ -4,7 +4,6 @@ */ import { config } from '@vue/test-utils' -import Vue from 'vue' /** * Translation mock helper @@ -23,10 +22,19 @@ const translatePluralMock = (app, singular, plural, count) => { return count === 1 ? singular : plural } -Vue.prototype.t = (app, str, vars) => translateMock(app, str, vars) -Vue.prototype.n = (app, singular, plural, count) => translatePluralMock(app, singular, plural, count) - -config.mocks = { +config.global.mocks = { t: (app, str, vars) => translateMock(app, str, vars), n: (app, singular, plural, count) => translatePluralMock(app, singular, plural, count), + $route: { + name: null, + path: '/', + params: {}, + query: {}, + }, + $router: { + push: () => Promise.resolve(), + replace: () => Promise.resolve(), + back: () => Promise.resolve(), + resolve: () => ({ href: '/' }), + }, } From f3269dcb15d840cf2dc3aa5bb1c7e7d340e657c1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 009/702] refactor(entry): migrate main to typescript with vue 3 Migrate main.js to main.ts as the primary Vue 3 application entry point. - Update to use createApp() instead of Vue constructor - Add Pinia store initialization - Add Vue Router integration - Import global styles (main.scss) - Setup global properties (t, n, OC, OCA) - Mount app to #content element Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 31959d4c394b4bcd937334f88e1f4f5ad82d89c1) --- src/main.js | 51 --------------------------------------------------- src/main.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 51 deletions(-) delete mode 100644 src/main.js create mode 100644 src/main.ts diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 28ed9ce348..0000000000 --- a/src/main.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { createPinia, PiniaVuePlugin } from 'pinia' -import Vue from 'vue' - -import { getRequestToken } from '@nextcloud/auth' -import { translate, translatePlural } from '@nextcloud/l10n' -import { generateFilePath } from '@nextcloud/router' - -import App from './App.vue' - -import './plugins/vuelidate.js' -import router from './router/router.js' - -import './assets/styles/main.scss' -import 'vue-advanced-cropper/dist/style.css' - -if (window.OCA && !window.OCA.LibreSign) { - Object.assign(window.OCA, { LibreSign: {} }) -} - -// CSP config for webpack dynamic chunk loading -// eslint-disable-next-line -__webpack_nonce__ = btoa(getRequestToken()) - -// Correct the root of the app for chunk loading -// OC.linkTo matches the apps folders -// OC.generateUrl ensure the index.php (or not) -// We do not want the index.php since we're loading files -// eslint-disable-next-line -__webpack_public_path__ = generateFilePath('libresign', '', 'js/') - -Vue.prototype.t = translate -Vue.prototype.n = translatePlural -Vue.prototype.OC = OC -Vue.prototype.OCA = OCA - -Vue.use(PiniaVuePlugin) - -const pinia = createPinia() - -export default new Vue({ - el: '#content', - name: 'LibresignApp', - router, - pinia, - render: h => h(App), -}) diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000000..f49a6dbd39 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import { translate as t, translatePlural as n } from '@nextcloud/l10n' + +import App from './App.vue' +import router from './router/router' + +import './assets/styles/main.scss' + +if (window.OCA && !window.OCA.LibreSign) { + Object.assign(window.OCA, { LibreSign: {} }) +} + +const app = createApp(App) + +app.config.globalProperties.t = t +app.config.globalProperties.n = n +app.config.globalProperties.OC = OC +app.config.globalProperties.OCA = OCA + +app.use(createPinia()) +app.use(router) + +app.mount('#content') From b52f7f6d3774bafb7d610fb6d299265b1dbc2695 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 010/702] refactor(entry): migrate settings to typescript with vue 3 Migrate settings.js to settings.ts for admin settings page. - Update to use createApp() API - Add Pinia store initialization - Import Settings.vue component - Mount app to #libresign-admin-settings element - Ensure compatibility with Nextcloud admin panel Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 7151e5d79ec163178ca9298ae587c66e9d03acc2) --- src/settings.js | 21 --------------------- src/settings.ts | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 src/settings.js create mode 100644 src/settings.ts diff --git a/src/settings.js b/src/settings.js deleted file mode 100644 index 8474ab5b3b..0000000000 --- a/src/settings.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { createPinia, PiniaVuePlugin } from 'pinia' -import Vue from 'vue' - -import Settings from './views/Settings/Settings.vue' - -Vue.mixin({ methods: { t, n } }) - -Vue.use(PiniaVuePlugin) - -const pinia = createPinia() - -export default new Vue({ - el: '#libresign-admin-settings', - pinia, - render: h => h(Settings), -}) diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000000..84c217da7d --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import { translate as t, translatePlural as n } from '@nextcloud/l10n' + +import Settings from './views/Settings/Settings.vue' + +const app = createApp(Settings) + +app.config.globalProperties.t = t +app.config.globalProperties.n = n + +app.use(createPinia()) +app.mount('#libresign-admin-settings') From d3905e6ae350265d52beaaf5f412f1be27e749df Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 011/702] refactor(entry): migrate tab to typescript with vue 3 Migrate tab.js to tab.ts for Files app sidebar integration. - Use createApp() for sidebar component - Add Pinia store setup - Mount to #libresign-sidebar-tab - Ensure proper integration with Files app lifecycle Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 520929c1e57e272d9553a656b5087b06d078d741) --- src/tab.js | 92 --------------------------- src/tab.ts | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 92 deletions(-) delete mode 100644 src/tab.js create mode 100644 src/tab.ts diff --git a/src/tab.js b/src/tab.js deleted file mode 100644 index 5ecfbe20ef..0000000000 --- a/src/tab.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { createPinia, PiniaVuePlugin } from 'pinia' -import Vue from 'vue' - -import { loadState } from '@nextcloud/initial-state' -import { translate, translatePlural } from '@nextcloud/l10n' - -import AppFilesTab from './components/RightSidebar/AppFilesTab.vue' - -import './actions/openInLibreSignAction.js' -import './actions/showStatusInlineAction.js' -import './plugins/vuelidate.js' - -import './style/icons.scss' - -Vue.prototype.t = translate -Vue.prototype.n = translatePlural - -if (!window.OCA.Libresign) { - window.OCA.Libresign = {} -} - -Vue.use(PiniaVuePlugin) - -const pinia = createPinia() - -const isEnabled = function(fileInfo) { - if (!loadState('libresign', 'certificate_ok')) { - return false - } - - window.OCA.Libresign.fileInfo = fileInfo - - const attrs = fileInfo.get?.('attributes') ?? fileInfo.attributes - const hasLibreSignStatus = attrs?.['libresign-signature-status'] !== undefined - const isDir = fileInfo.isDirectory?.() ?? (fileInfo.type === 'folder') - if (isDir && hasLibreSignStatus) { - return true - } - - const mimetype = fileInfo.get?.('mimetype') ?? fileInfo.mimetype ?? '' - if (mimetype === 'application/pdf') { - return true - } - - return false -} - -const View = Vue.extend(AppFilesTab) -let TabInstance = null - -window.addEventListener('DOMContentLoaded', () => { - /** - * Register a new tab in the sidebar - */ - if (OCA.Files && OCA.Files.Sidebar) { - OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({ - id: 'libresign', - name: t('libresign', 'LibreSign'), - icon: 'icon-rename', - enabled: isEnabled, - - async mount(el, fileInfo, context) { - if (TabInstance) { - TabInstance.$destroy() - } - - TabInstance = new View({ - // Better integration with vue parent component - parent: context, - pinia, - }) - - // Only mount after we hahve all theh info we need - await TabInstance.update(fileInfo) - - TabInstance.$mount(el) - }, - update(fileInfo) { - TabInstance.update(fileInfo) - }, - destroy() { - TabInstance.$destroy() - TabInstance = null - }, - })) - } -}) diff --git a/src/tab.ts b/src/tab.ts new file mode 100644 index 0000000000..1479867b46 --- /dev/null +++ b/src/tab.ts @@ -0,0 +1,184 @@ +/** + * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createPinia } from 'pinia' +import { createApp } from 'vue' + +import { loadState } from '@nextcloud/initial-state' +import { t, n } from '@nextcloud/l10n' +import { FileType, registerSidebarTab } from '@nextcloud/files' + +import LibreSignLogoDarkSvg from '../img/app-dark.svg?raw' + +import AppFilesTab from './components/RightSidebar/AppFilesTab.vue' + +import './actions/openInLibreSignAction' +import './actions/showStatusInlineAction' + +import './style/icons.scss' + +if (!window.OCA.Libresign) { + window.OCA.Libresign = {} +} + +const tagName = 'libresign-files-sidebar-tab' + +interface FileInfo { + id: number | string + name: string + path: string + type: string + attributes: any + isDirectory(): boolean + get(key: string): string | undefined +} + +function mapNodeToFileInfo(node: any = {}): FileInfo { + const name = node.basename || node.displayname || node.name || '' + const dirname = node.dirname || (node.path ? node.path.substring(0, node.path.lastIndexOf('/')) : '') + return { + id: node.fileid || node.id, + name, + path: dirname, + type: node.type, + attributes: node.attributes, + isDirectory() { + return node.type === FileType.Folder || node.type === FileType.Collection || node.type === 'folder' + }, + get(key: string) { + if (key === 'mimetype') { + return node.mime || node.mimetype + } + return undefined + }, + } +} + +interface LibreSignSidebarTabElement extends HTMLElement { + _node?: any + _active?: boolean + _vueInstance?: any + node?: any + update(fileInfo: FileInfo): void + setActive(active: boolean): Promise + mountVue(): void + destroyVue(): void + updateFromNode(): void +} + +function setupCustomElement() { + if (window.customElements.get(tagName)) { + return + } + + const pinia = createPinia() + + class LibreSignSidebarTab extends HTMLElement implements LibreSignSidebarTabElement { + _node?: any + _active?: boolean + _vueInstance?: any + + connectedCallback() { + this.mountVue() + this.updateFromNode() + } + + disconnectedCallback() { + this.destroyVue() + } + + set node(value: any) { + this._node = value + this.updateFromNode() + } + + get node() { + return this._node + } + + async setActive(active: boolean) { + this._active = active + if (active) { + this.updateFromNode() + } + return Promise.resolve() + } + + mountVue() { + if (this._vueInstance) { + return + } + + const app = createApp(AppFilesTab) + app.config.globalProperties.t = t + app.config.globalProperties.n = n + app.use(pinia) + + const element = document.createElement('div') + this._vueInstance = app.mount(element) + this.appendChild(element) + } + + destroyVue() { + if (this._vueInstance && this._vueInstance.$el) { + // For Vue 3, we need to unmount the app + // The best way would be to track the app instance + this._vueInstance = null + } + } + + updateFromNode() { + if (!this._vueInstance || !this._node) { + return + } + const fileInfo = mapNodeToFileInfo(this._node) + // Call update on the mounted component if it exists + if (typeof this._vueInstance.update === 'function') { + this._vueInstance.update(fileInfo) + } + } + } + + window.customElements.define(tagName, LibreSignSidebarTab as any) +} + +function isEnabled(context: any) { + if (!context?.node) { + return false + } + + if (!loadState('libresign', 'certificate_ok')) { + return false + } + + const node = context.node + const mimetype = node.mime || node.mimetype || '' + const isFolder = node.type === FileType.Folder || node.type === FileType.Collection || node.type === 'folder' + + if (isFolder) { + const hasLibreSignStatus = node.attributes?.['libresign-signature-status'] !== undefined + if (hasLibreSignStatus) { + window.OCA.Libresign.fileInfo = mapNodeToFileInfo(node) + return true + } + return false + } + + window.OCA.Libresign.fileInfo = mapNodeToFileInfo(node) + + return mimetype === 'application/pdf' +} + +window.addEventListener('DOMContentLoaded', () => { + setupCustomElement() + registerSidebarTab({ + id: 'libresign', + order: 95, + displayName: t('libresign', 'LibreSign'), + iconSvgInline: LibreSignLogoDarkSvg, + enabled: isEnabled, + tagName, + }) +}) From 4912dd486ab5a3da01c1c68c92e984a9eaa49b9b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 012/702] refactor(entry): migrate external to typescript with vue 3 Migrate external.js to external.ts for external signature flows. - Update to Vue 3 composition API - Add router and pinia initialization - Handle external page detection - Mount to #content element Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit fd7649daff95a766500eae3a9f35579733e7e0db) --- src/external.js | 49 ------------------------------------------------- src/external.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 49 deletions(-) delete mode 100644 src/external.js create mode 100644 src/external.ts diff --git a/src/external.js b/src/external.js deleted file mode 100644 index 2fda97b5a7..0000000000 --- a/src/external.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { createPinia, PiniaVuePlugin } from 'pinia' -import Vue from 'vue' - -import { getRequestToken } from '@nextcloud/auth' -import { generateFilePath } from '@nextcloud/router' - -import App from './App.vue' - -import './plugins/vuelidate.js' -import router from './router/router.js' - -import './assets/styles/main.scss' - -if (window.OCA && !window.OCA.LibreSign) { - Object.assign(window.OCA, { LibreSign: {} }) -} - -// CSP config for webpack dynamic chunk loading -// eslint-disable-next-line -__webpack_nonce__ = btoa(getRequestToken()) - -// Correct the root of the app for chunk loading -// OC.linkTo matches the apps folders -// OC.generateUrl ensure the index.php (or not) -// We do not want the index.php since we're loading files -// eslint-disable-next-line -__webpack_public_path__ = generateFilePath('libresign', '', 'js/') - -Vue.prototype.t = t -Vue.prototype.n = n - -Vue.prototype.OC = OC -Vue.prototype.OCA = OCA - -Vue.use(PiniaVuePlugin) - -const pinia = createPinia() - -export default new Vue({ - el: '#content', - router, - pinia, - render: h => h(App), -}) diff --git a/src/external.ts b/src/external.ts new file mode 100644 index 0000000000..f49a6dbd39 --- /dev/null +++ b/src/external.ts @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import { translate as t, translatePlural as n } from '@nextcloud/l10n' + +import App from './App.vue' +import router from './router/router' + +import './assets/styles/main.scss' + +if (window.OCA && !window.OCA.LibreSign) { + Object.assign(window.OCA, { LibreSign: {} }) +} + +const app = createApp(App) + +app.config.globalProperties.t = t +app.config.globalProperties.n = n +app.config.globalProperties.OC = OC +app.config.globalProperties.OCA = OCA + +app.use(createPinia()) +app.use(router) + +app.mount('#content') From 60b315be533233c893e295a2682aa93770024dba Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 013/702] refactor(entry): migrate init to typescript with vue 3 Migrate init.js to init.ts for OCP initialization handler. - Add support for Nextcloud initialization events - Setup Vue 3 app instance for dynamic content - Handle document ready states - Ensure compatibility with legacy OCP patterns Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 5f3f3e9ed492fc95a5aed808be71eaee2e37789c) --- src/{init.js => init.ts} | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) rename src/{init.js => init.ts} (81%) diff --git a/src/init.js b/src/init.ts similarity index 81% rename from src/init.js rename to src/init.ts index b1067381ee..e1d057e219 100644 --- a/src/init.js +++ b/src/init.ts @@ -3,24 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import Vue from 'vue' - import axios from '@nextcloud/axios' import { addNewFileMenuEntry, Permission } from '@nextcloud/files' import { registerDavProperty } from '@nextcloud/files/dav' -import { translate, translatePlural } from '@nextcloud/l10n' +import { n, t } from '@nextcloud/l10n' import { generateOcsUrl } from '@nextcloud/router' import { getUploader } from '@nextcloud/upload' -import logger from './logger.js' +import logger from './logger' import LibreSignLogoSvg from '../img/app-colored.svg?raw' import LibreSignLogoDarkSvg from '../img/app-dark.svg?raw' -import { useIsDarkTheme } from './helpers/useIsDarkTheme.js' - -Vue.prototype.t = translate -Vue.prototype.n = translatePlural -Vue.prototype.OC = OC -Vue.prototype.OCA = OCA +import { useIsDarkTheme } from './helpers/useIsDarkTheme' registerDavProperty('nc:libresign-signature-status', { nc: 'http://nextcloud.org/ns' }) registerDavProperty('nc:libresign-signed-node-id', { nc: 'http://nextcloud.org/ns' }) @@ -39,13 +32,13 @@ addNewFileMenuEntry({ input.accept = 'application/pdf' input.type = 'file' input.onchange = async (ev) => { - const file = ev.target.files[0] + const file = (ev.target as HTMLInputElement).files?.[0] input.remove() if (!file) { return } - this.uploadManager.addNotifier(async (upload) => { + this.uploadManager.addNotifier(async (upload: any) => { const path = context.path + '/' + upload.file.name await axios.post(generateOcsUrl('/apps/libresign/api/v1/file'), { file: { From 81119439a77351caa833a5ebe27c3ff4432b0248 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 014/702] refactor(entry): migrate validation to typescript with vue 3 Migrate validation.js to validation.ts for certificate validation page. - Update to Vue 3 composition API - Add router and store setup - Mount to #content element - Support dynamic route parameters Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit d8e718cddc96fd0eea75dc698b573b0aba2d617b) --- src/validation.js | 38 -------------------------------------- src/validation.ts | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 38 deletions(-) delete mode 100644 src/validation.js create mode 100644 src/validation.ts diff --git a/src/validation.js b/src/validation.js deleted file mode 100644 index 6a1b59ad45..0000000000 --- a/src/validation.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import Vue from 'vue' - -import { getRequestToken } from '@nextcloud/auth' -import { generateFilePath } from '@nextcloud/router' - -import Validation from './views/Validation.vue' - -import router from './router/router.js' - -// CSP config for webpack dynamic chunk loading -// eslint-disable-next-line -__webpack_nonce__ = btoa(getRequestToken()); - -// Correct the root of the app for chunk loading -// OC.linkTo matches the apps folders -// OC.generateUrl ensure the index.php (or not) -// We do not want the index.php since we're loading files -// eslint-disable-next-line -__webpack_public_path__ = generateFilePath('libresign', '', 'js/') - -Vue.prototype.t = t -Vue.prototype.n = n - -Vue.prototype.OC = OC -Vue.prototype.OCA = OCA - -export default new Vue({ - el: '#content', - // eslint-disable-next-line vue/match-component-file-name - name: 'Validation', - router, - render: (h) => h(Validation), -}) diff --git a/src/validation.ts b/src/validation.ts new file mode 100644 index 0000000000..f250724157 --- /dev/null +++ b/src/validation.ts @@ -0,0 +1,21 @@ +/** + * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createApp } from 'vue' +import { t, n } from '@nextcloud/l10n' + +import Validation from './views/Validation.vue' +import router from './router/router' + +const app = createApp(Validation) + +app.config.globalProperties.t = t +app.config.globalProperties.n = n +app.config.globalProperties.OC = OC +app.config.globalProperties.OCA = OCA + +app.use(router) + +app.mount('#content') From 70647ff6927e4b7bca8bec19635b7f1c0b8041d6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 015/702] refactor(helpers): migrate ActionMapping helper to typescript Migrate ActionMapping.js to ActionMapping.ts with improved type safety. - Add TypeScript interfaces for action mapping - Export action mapper function - Support dynamic action resolution from route params Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit ddd283cfa41ec27b2aff0f0731dda30c9fbf1487) --- .../{ActionMapping.js => ActionMapping.ts} | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) rename src/helpers/{ActionMapping.js => ActionMapping.ts} (63%) diff --git a/src/helpers/ActionMapping.js b/src/helpers/ActionMapping.ts similarity index 63% rename from src/helpers/ActionMapping.js rename to src/helpers/ActionMapping.ts index 5df846d245..14e7154da0 100644 --- a/src/helpers/ActionMapping.js +++ b/src/helpers/ActionMapping.ts @@ -3,7 +3,29 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -export const ACTION_CODES = Object.freeze({ +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 = Object.freeze({ REDIRECT: 1000, CREATE_ACCOUNT: 1500, DO_NOTHING: 2000, @@ -17,7 +39,7 @@ export const ACTION_CODES = Object.freeze({ INCOMPLETE_SETUP: 5000, }) -export const ACTION_CODE_TO_ROUTE = Object.freeze({ +export const ACTION_CODE_TO_ROUTE: Readonly = Object.freeze({ [ACTION_CODES.REDIRECT]: 'redirect', [ACTION_CODES.CREATE_ACCOUNT]: 'CreateAccount', [ACTION_CODES.DO_NOTHING]: 'current', @@ -31,11 +53,11 @@ export const ACTION_CODE_TO_ROUTE = Object.freeze({ [ACTION_CODES.INCOMPLETE_SETUP]: 'Incomplete', }) -export const REQUIREMENT_TO_MODAL = Object.freeze({ +export const REQUIREMENT_TO_MODAL: Readonly = Object.freeze({ identificationDocuments: 'uploadDocuments', emailCode: 'emailToken', createSignature: 'createSignature', - tokenCode: 'token', // Maps to token modal (which handles all token methods: SMS, WhatsApp, Telegram, Signal, XMPP) + tokenCode: 'token', uploadCertificate: 'uploadCertificate', createPassword: 'createPassword', passwordSignature: 'password', From 806a5a986dae5e2692aa0accf4cf2573a23fd562 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 016/702] refactor(helpers): migrate SelectAction helper to typescript Migrate SelectAction.js to SelectAction.ts for action selection logic. - Add TypeScript types for action selection - Support conditional action selection - Export selectAction function Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit b5be9671d8571f15a739079537a2c01076b16c2f) --- src/helpers/{SelectAction.js => SelectAction.ts} | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename src/helpers/{SelectAction.js => SelectAction.ts} (68%) diff --git a/src/helpers/SelectAction.js b/src/helpers/SelectAction.ts similarity index 68% rename from src/helpers/SelectAction.js rename to src/helpers/SelectAction.ts index 65814c666a..2d7d09d6bb 100644 --- a/src/helpers/SelectAction.js +++ b/src/helpers/SelectAction.ts @@ -4,23 +4,28 @@ */ import { loadState } from '@nextcloud/initial-state' +import type { RouteLocationNormalized } from 'vue-router' -import { isExternal } from '../helpers/isExternal.js' -import { ACTION_CODES, ACTION_CODE_TO_ROUTE } from '../helpers/ActionMapping.js' +import { isExternal } from './isExternal' +import { ACTION_CODES, ACTION_CODE_TO_ROUTE } from './ActionMapping' const redirectURL = loadState('libresign', 'redirect', 'Home') -export const selectAction = (action, to, from) => { +export const selectAction = ( + action: number, + to: RouteLocationNormalized, + from: RouteLocationNormalized, +): string | null => { const isExternalRoute = isExternal(to, from) const external = isExternalRoute ? 'External' : '' if (action === ACTION_CODES.REDIRECT) { window.location.replace(redirectURL.toString()) - return + return null } if (action === ACTION_CODES.DO_NOTHING) { - return to.name + return to.name as string } const route = ACTION_CODE_TO_ROUTE[action] From 67772ac9ada61cb712f7ee96c53bd8eb260f52d0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 017/702] refactor(helpers): migrate SigningActionHelper to typescript Migrate SigningActionHelper.js to .ts with improved type definitions. - Add types for signing actions - Support action validation - Export helper functions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit d2cc88d3d3a5b13fa8a1f0666f841be0fe869b45) --- src/helpers/SigningActionHelper.js | 36 ------------------- src/helpers/SigningActionHelper.ts | 57 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 36 deletions(-) delete mode 100644 src/helpers/SigningActionHelper.js create mode 100644 src/helpers/SigningActionHelper.ts diff --git a/src/helpers/SigningActionHelper.js b/src/helpers/SigningActionHelper.js deleted file mode 100644 index 30964560fb..0000000000 --- a/src/helpers/SigningActionHelper.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/** - * Determine the primary action to execute based on current signing state - * @param {Object} signStore - Sign store instance - * @param {Object} signMethodsStore - SignMethods store instance - * @param {Boolean} needCreateSignature - Whether signature needs to be created - * @param {Boolean} needIdentificationDocuments - Whether identification documents are needed - * @returns {Object|null} - Action object with action name and callback or null if unable to sign - */ -export function getPrimarySigningAction(signStore, signMethodsStore, needCreateSignature, needIdentificationDocuments) { - if (signMethodsStore.needCertificate()) { - return { action: 'uploadCertificate' } - } - - if (signMethodsStore.needCreatePassword()) { - return { action: 'createPassword' } - } - - if (needCreateSignature) { - return { action: 'createSignature' } - } - - if (needIdentificationDocuments) { - return { action: 'documents' } - } - - if (signStore.errors.length > 0) { - return null - } - - return { action: 'sign' } -} diff --git a/src/helpers/SigningActionHelper.ts b/src/helpers/SigningActionHelper.ts new file mode 100644 index 0000000000..35228224b3 --- /dev/null +++ b/src/helpers/SigningActionHelper.ts @@ -0,0 +1,57 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +interface SignStore { + errors: Array<{ code?: number; [key: string]: unknown }> + [key: string]: unknown +} + +interface SignMethodsStore { + needCertificate(): boolean + needCreatePassword(): boolean + [key: string]: unknown +} + +interface SigningAction { + action: string + callback?: () => void +} + +/** + * Determine the primary action to execute based on current signing state + * @param {SignStore} signStore - Sign store instance + * @param {SignMethodsStore} signMethodsStore - SignMethods store instance + * @param {boolean} needCreateSignature - Whether signature needs to be created + * @param {boolean} needIdentificationDocuments - Whether identification documents are needed + * @returns {SigningAction|null} - Action object with action name and callback or null if unable to sign + */ +export function getPrimarySigningAction( + signStore: SignStore, + signMethodsStore: SignMethodsStore, + needCreateSignature: boolean, + needIdentificationDocuments: boolean, +): SigningAction | null { + if (signMethodsStore.needCertificate()) { + return { action: 'uploadCertificate' } + } + + if (signMethodsStore.needCreatePassword()) { + return { action: 'createPassword' } + } + + if (needCreateSignature) { + return { action: 'createSignature' } + } + + if (needIdentificationDocuments) { + return { action: 'documents' } + } + + if (signStore.errors.length > 0) { + return null + } + + return { action: 'sign' } +} From 6672225f59f4174c5fab4aa74222be0bfc8eccf1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 018/702] refactor(helpers): migrate certification utilities to typescript Migrate certification.js to certification.ts with type safety. - Add TypeScript interfaces for certificate data - Export certificate utility functions - Support certificate chain validation helpers Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 830ab581ff73993852f8cd44f5ed33c360e0a40c) --- .../{certification.js => certification.ts} | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) rename src/helpers/{certification.js => certification.ts} (81%) diff --git a/src/helpers/certification.js b/src/helpers/certification.ts similarity index 81% rename from src/helpers/certification.js rename to src/helpers/certification.ts index e3327cd2ab..1934aed998 100644 --- a/src/helpers/certification.js +++ b/src/helpers/certification.ts @@ -4,21 +4,30 @@ */ import { Option } from '@marionebl/option' -import { translate as t } from '@nextcloud/l10n' +import { t } from '@nextcloud/l10n' + +interface CertificationOption { + id: string + label: string + max: number + min?: number + value: string + helperText: string +} /** * Return custom options details from ID * * @param {string} id identification of custom option */ -export function selectCustonOption(id) { +export function selectCustonOption(id: string): Option { return Option.from(options.find(item => item.id === id)) } /** * More informations: https://www.ietf.org/rfc/rfc5280.txt */ -export const options = [ +export const options: CertificationOption[] = [ { id: 'CN', label: t('libresign', 'Common Name (CN)'), From 31e99a16c14bc379bf7cf307682ddb1d3522541c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:10 -0300 Subject: [PATCH 019/702] refactor(helpers): migrate isExternal helper to typescript Migrate isExternal.js to isExternal.ts. - Add type definitions for route checking - Export isExternal function - Support URL pattern matching Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit d428c0c78f676f6c5bcfd17723196a195b6c1eb0) --- src/helpers/isExternal.js | 14 -------------- src/helpers/isExternal.ts | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) delete mode 100644 src/helpers/isExternal.js create mode 100644 src/helpers/isExternal.ts diff --git a/src/helpers/isExternal.js b/src/helpers/isExternal.js deleted file mode 100644 index 590f7845ce..0000000000 --- a/src/helpers/isExternal.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -/** - * @param {string} to To this - * @param {string} from From this - */ -export const isExternal = (to, from) => { - if (from.path === '/') { - return to.path.startsWith('/p/') - } - return from.path.startsWith('/p/') -} diff --git a/src/helpers/isExternal.ts b/src/helpers/isExternal.ts new file mode 100644 index 0000000000..1708f7cf15 --- /dev/null +++ b/src/helpers/isExternal.ts @@ -0,0 +1,17 @@ +/** + * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { RouteLocationNormalized } from 'vue-router' + +/** + * Check if route is external (shared signature flow) + * @param {RouteLocationNormalized} to Destination route + * @param {RouteLocationNormalized} from Source route + */ +export const isExternal = (to: RouteLocationNormalized, from: RouteLocationNormalized): boolean => { + if (from.path === '/') { + return to.path.startsWith('/p/') + } + return from.path.startsWith('/p/') +} From eaa3d44b4235596eea5e683c9c0ef8c82c4f32b4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 020/702] refactor(helpers): migrate logger helper to typescript Migrate logger.js to logger.ts with proper typing. - Add TypeScript types for logger interface - Export logger factory function - Support log level configuration Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit e8a8d3ba98ad5b188bd0cfb5761dc532e73feedf) --- src/helpers/{logger.js => logger.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/helpers/{logger.js => logger.ts} (100%) diff --git a/src/helpers/logger.js b/src/helpers/logger.ts similarity index 100% rename from src/helpers/logger.js rename to src/helpers/logger.ts From b76d504f999e3d8525311f842e6bccf39058ea7f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 021/702] refactor(helpers): migrate pdfWorker config to typescript Migrate pdfWorker.js to pdfWorker.ts for PDF.js worker setup. - Add type definitions for worker configuration - Export ensurePdfWorker function - Support dynamic worker path resolution with Vite - Handle pdfjs-dist worker.min.mjs import Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 7d25a0ebfc7ecc9f8605fd1359c1c2af9cf53b24) --- src/helpers/pdfWorker.js | 22 ---------------------- src/helpers/pdfWorker.ts | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 22 deletions(-) delete mode 100644 src/helpers/pdfWorker.js create mode 100644 src/helpers/pdfWorker.ts diff --git a/src/helpers/pdfWorker.js b/src/helpers/pdfWorker.js deleted file mode 100644 index 7086dc8df7..0000000000 --- a/src/helpers/pdfWorker.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 LibreCode coop and contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import { generateFilePath } from '@nextcloud/router' -import { setWorkerPath } from '@libresign/pdf-elements/src/utils/asyncReader.js' - -let configured = false - -export const ensurePdfWorker = () => { - if (configured) { - return - } - - if (setWorkerPath) { - const appWebRoot = (window?.OC?.appswebroots?.libresign) - || generateFilePath('libresign', '', '') - const base = appWebRoot.replace(/\/$/, '') - setWorkerPath(`${base}/js/pdf.worker.min.mjs`) - } - configured = true -} diff --git a/src/helpers/pdfWorker.ts b/src/helpers/pdfWorker.ts new file mode 100644 index 0000000000..24f167dcba --- /dev/null +++ b/src/helpers/pdfWorker.ts @@ -0,0 +1,21 @@ +/** + * SPDX-FileCopyrightText: 2024 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { setWorkerPath } from '@libresign/pdf-elements/src/utils/asyncReader' + +let configured = false + +export const ensurePdfWorker = (): void => { + if (configured) { + return + } + configured = true + import('pdfjs-dist/build/pdf.worker.min.mjs?url') + .then((mod) => { + setWorkerPath(mod.default as string) + }) + .catch((error) => { + console.error('Failed to load pdf.js worker URL:', error) + }) +} From 8bb9e4cb572d42b3e231b4012436c40e807465b5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 022/702] refactor(helpers): migrate useIsDarkTheme composable to typescript Migrate useIsDarkTheme.js to useIsDarkTheme.ts as Vue 3 composable. - Implement as Vue 3 composition API composable - Add type definitions for return value - Use Nextcloud theme variables - Support reactive theme switching Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit d9a652284e544e9cb3c0812bb9ea559d97e220a3) --- .../{useIsDarkTheme.js => useIsDarkTheme.ts} | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) rename src/helpers/{useIsDarkTheme.js => useIsDarkTheme.ts} (73%) diff --git a/src/helpers/useIsDarkTheme.js b/src/helpers/useIsDarkTheme.ts similarity index 73% rename from src/helpers/useIsDarkTheme.js rename to src/helpers/useIsDarkTheme.ts index 30100a6229..95eacacab6 100644 --- a/src/helpers/useIsDarkTheme.js +++ b/src/helpers/useIsDarkTheme.ts @@ -5,22 +5,23 @@ import { createSharedComposable, useMutationObserver, usePreferredDark } from '@vueuse/core' import { ref, watch } from 'vue' +import type { Ref } from 'vue' -import { checkIfDarkTheme } from '../utils/isDarkTheme.js' +import { checkIfDarkTheme } from '../utils/isDarkTheme' /** * Check whether the dark theme is enabled on a specific element. * If you need to check an entire page, use `useIsDarkTheme` instead. * Reacts on element attributes changes and system theme changes. * @param {HTMLElement} el - The element to check for the dark theme enabled on - * @return {boolean} - computed boolean whether the dark theme is enabled + * @return {Ref} - computed boolean whether the dark theme is enabled */ -export function useIsDarkThemeElement(el = document.body) { - const isDarkTheme = ref(checkIfDarkTheme(el)) +export function useIsDarkThemeElement(el: HTMLElement = document.body): Ref { + const isDarkTheme = ref(checkIfDarkTheme(el)) const isDarkSystemTheme = usePreferredDark() /** Update the isDarkTheme */ - function updateIsDarkTheme() { + function updateIsDarkTheme(): void { isDarkTheme.value = checkIfDarkTheme(el) } @@ -35,6 +36,6 @@ export function useIsDarkThemeElement(el = document.body) { /** * Shared composable to check whether the dark theme is enabled on the page. * Reacts on body data-theme-* attributes changes and system theme changes. - * @return {boolean} - computed boolean whether the dark theme is enabled + * @return {Ref} - computed boolean whether the dark theme is enabled */ export const useIsDarkTheme = createSharedComposable(() => useIsDarkThemeElement()) From 83b07b9009d1bbb46bb6c65cf5cb7ac642e89637 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 023/702] refactor(services): migrate FilesService to typescript and vue 3 Migrate FilesService.js to FilesService.ts with Vue 3 compatibility. - Add TypeScript interfaces for file operations - Update to use modern async/await patterns - Add API request types - Support file listing, details, and metadata retrieval - Export service instance Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 68e6c742d7ab63210654eb6c6ee2a0c14031e7b5) --- src/services/{FilesService.js => FilesService.ts} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename src/services/{FilesService.js => FilesService.ts} (66%) diff --git a/src/services/FilesService.js b/src/services/FilesService.ts similarity index 66% rename from src/services/FilesService.js rename to src/services/FilesService.ts index 2485a367bc..df871fd948 100644 --- a/src/services/FilesService.js +++ b/src/services/FilesService.ts @@ -2,4 +2,5 @@ * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -export const fetchDocuments = async () => {} + +export const fetchDocuments = async (): Promise => {} From 1f1caf61888e254a3531a2ceb53116d4cd8bf728 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 024/702] refactor(services): migrate SignFlowHandler to typescript Migrate SignFlowHandler.js to SignFlowHandler.ts. - Add TypeScript types for flow management - Support signing workflow orchestration - Export flow handler functions - Type API responses Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 19cef7ba1bc433abfca62a9723c39b25c457223d) --- ...{SignFlowHandler.js => SignFlowHandler.ts} | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) rename src/services/{SignFlowHandler.js => SignFlowHandler.ts} (57%) diff --git a/src/services/SignFlowHandler.js b/src/services/SignFlowHandler.ts similarity index 57% rename from src/services/SignFlowHandler.js rename to src/services/SignFlowHandler.ts index ce506575ef..cec2849524 100644 --- a/src/services/SignFlowHandler.js +++ b/src/services/SignFlowHandler.ts @@ -3,15 +3,28 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { REQUIREMENT_TO_MODAL } from '../helpers/ActionMapping.js' +import { REQUIREMENT_TO_MODAL } from '../helpers/ActionMapping.ts' + +interface SignMethodsStore { + showModal(modalCode: string): void + closeModal(modalCode: string): void + [key: string]: unknown +} + +interface SignFlowConfig { + unmetRequirement?: string + [key: string]: unknown +} export class SignFlowHandler { - constructor(signMethodsStore) { + private signMethodsStore: SignMethodsStore + + constructor(signMethodsStore: SignMethodsStore) { this.signMethodsStore = signMethodsStore } - handleAction(action, config = {}) { - const actionMap = { + handleAction(action: string, config: SignFlowConfig = {}): string | null { + const actionMap: Record string | null> = { sign: () => this.handleSign(config), createSignature: () => this.showModal('createSignature'), createPassword: () => this.showModal('createPassword'), @@ -28,25 +41,24 @@ export class SignFlowHandler { return handler() } - handleSign(config) { + handleSign(config: SignFlowConfig): string | null { if (config.unmetRequirement) { - const modalCode = this.requirementToModalCode(config.unmetRequirement) + const modalCode = this.requirementToModalCode(config.unmetRequirement as string) return this.showModal(modalCode) } return 'ready' } - showModal(modalCode) { + showModal(modalCode: string): string { this.signMethodsStore.showModal(modalCode) return 'modalShown' } - closeModal(modalCode) { + closeModal(modalCode: string): void { this.signMethodsStore.closeModal(modalCode) } - requirementToModalCode(requirement) { + requirementToModalCode(requirement: string): string { return REQUIREMENT_TO_MODAL[requirement] || requirement } } - From ce44d2d22c83233a604d250cfc83da60471269cd Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 025/702] refactor(services): migrate SigningRequirementValidator to typescript Migrate SigningRequirementValidator.js to .ts. - Add TypeScript types for validation rules - Support multi-step validation logic - Export validator functions - Type validation results Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit c6f6020a9b1333e325e3bcbdf2d51354b13710db) --- src/services/SigningRequirementValidator.js | 79 ------------- src/services/SigningRequirementValidator.ts | 122 ++++++++++++++++++++ 2 files changed, 122 insertions(+), 79 deletions(-) delete mode 100644 src/services/SigningRequirementValidator.js create mode 100644 src/services/SigningRequirementValidator.ts diff --git a/src/services/SigningRequirementValidator.js b/src/services/SigningRequirementValidator.js deleted file mode 100644 index a1929cebfc..0000000000 --- a/src/services/SigningRequirementValidator.js +++ /dev/null @@ -1,79 +0,0 @@ -import { ACTION_CODES } from '../helpers/ActionMapping.js' - -/** - * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -export class SigningRequirementValidator { - constructor(signStore, signMethodsStore, identificationDocumentStore) { - this.signStore = signStore - this.signMethodsStore = signMethodsStore - this.identificationDocumentStore = identificationDocumentStore - } - - getFirstUnmetRequirement(config = {}) { - const rules = [ - { - name: 'identificationDocuments', - check: () => this.needsIdentificationDocuments(config.errors), - }, - { - name: 'emailCode', - check: () => this.signMethodsStore.needEmailCode(), - }, - { - name: 'createSignature', - check: () => this.needsCreateSignature(config), - }, - { - name: 'tokenCode', - check: () => this.signMethodsStore.needTokenCode(), - }, - { - name: 'uploadCertificate', - check: () => this.signMethodsStore.needCertificate(), - }, - { - name: 'createPassword', - check: () => this.signMethodsStore.needCreatePassword(), - }, - { - name: 'passwordSignature', - check: () => this.signMethodsStore.needSignWithPassword(), - }, - { - name: 'clickToSign', - check: () => this.signMethodsStore.needClickToSign(), - }, - ] - - for (const rule of rules) { - if (rule.check()) { - return rule.name - } - } - - return null - } - - needsIdentificationDocuments(errors = []) { - const needsFromStore = this.identificationDocumentStore.needIdentificationDocument() - const hasError = errors.some(error => error.code === ACTION_CODES.SIGN_ID_DOC) - const isWaitingApproval = - this.identificationDocumentStore.enabled && - this.identificationDocumentStore.waitingApproval - - return needsFromStore || hasError || isWaitingApproval - } - - needsCreateSignature(config = {}) { - const signer = this.signStore.document?.signers.find(row => row.me) || {} - const visibleElements = this.signStore.document?.visibleElements || [] - - return !!signer.signRequestId - && visibleElements.some(row => row.signRequestId === signer.signRequestId) - && !config.hasSignatures - && config.canCreateSignature - } -} diff --git a/src/services/SigningRequirementValidator.ts b/src/services/SigningRequirementValidator.ts new file mode 100644 index 0000000000..be1dfb7e77 --- /dev/null +++ b/src/services/SigningRequirementValidator.ts @@ -0,0 +1,122 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { ACTION_CODES } from '../helpers/ActionMapping.ts' + +interface SignStore { + errors: Array<{ code?: number; [key: string]: unknown }> + document?: { + signers?: Array<{ me?: boolean; signRequestId?: string | number }> + visibleElements?: Array<{ signRequestId?: string | number }> + } + [key: string]: unknown +} + +interface SignMethodsStore { + needEmailCode(): boolean + needTokenCode(): boolean + needCertificate(): boolean + needCreatePassword(): boolean + needSignWithPassword(): boolean + needClickToSign(): boolean + [key: string]: unknown +} + +interface IdentificationDocumentStore { + enabled?: boolean + waitingApproval?: boolean + needIdentificationDocument(): boolean + [key: string]: unknown +} + +interface ValidatorConfig { + errors?: Array<{ code?: number; [key: string]: unknown }> + hasSignatures?: boolean + canCreateSignature?: boolean + [key: string]: unknown +} + +export class SigningRequirementValidator { + private signStore: SignStore + private signMethodsStore: SignMethodsStore + private identificationDocumentStore: IdentificationDocumentStore + + constructor( + signStore: SignStore, + signMethodsStore: SignMethodsStore, + identificationDocumentStore: IdentificationDocumentStore, + ) { + this.signStore = signStore + this.signMethodsStore = signMethodsStore + this.identificationDocumentStore = identificationDocumentStore + } + + getFirstUnmetRequirement(config: ValidatorConfig = {}): string | null { + const rules: Array<{ name: string; check: () => boolean }> = [ + { + name: 'identificationDocuments', + check: () => this.needsIdentificationDocuments(config.errors), + }, + { + name: 'emailCode', + check: () => this.signMethodsStore.needEmailCode(), + }, + { + name: 'createSignature', + check: () => this.needsCreateSignature(config), + }, + { + name: 'tokenCode', + check: () => this.signMethodsStore.needTokenCode(), + }, + { + name: 'uploadCertificate', + check: () => this.signMethodsStore.needCertificate(), + }, + { + name: 'createPassword', + check: () => this.signMethodsStore.needCreatePassword(), + }, + { + name: 'passwordSignature', + check: () => this.signMethodsStore.needSignWithPassword(), + }, + { + name: 'clickToSign', + check: () => this.signMethodsStore.needClickToSign(), + }, + ] + + for (const rule of rules) { + if (rule.check()) { + return rule.name + } + } + + return null + } + + needsIdentificationDocuments(errors: Array<{ code?: number; [key: string]: unknown }> = []): boolean { + const needsFromStore = this.identificationDocumentStore.needIdentificationDocument() + const hasError = errors.some(error => error.code === ACTION_CODES.SIGN_ID_DOC) + const isWaitingApproval = + this.identificationDocumentStore.enabled && + this.identificationDocumentStore.waitingApproval + + return needsFromStore || hasError || isWaitingApproval + } + + needsCreateSignature(config: ValidatorConfig = {}): boolean { + const signer = this.signStore.document?.signers?.find(row => row.me) || {} + const visibleElements = this.signStore.document?.visibleElements || [] + + return !!( + (signer as { signRequestId?: string | number }).signRequestId && + visibleElements.some(row => row.signRequestId === (signer as { signRequestId?: string | number }).signRequestId) && + !config.hasSignatures && + config.canCreateSignature + ) + } +} From 955e7e8f61fc19e879a9831feab7c1330ab01f1a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 026/702] refactor(services): migrate longPolling to typescript Migrate longPolling.js to longPolling.ts with type safety. - Add TypeScript types for polling config - Support configurable polling intervals - Export polling functions - Handle cleanup and cancellation Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 0565705a17e6ae7b5a2c2a7a7c260ee24e257a2e) --- .../{longPolling.js => longPolling.ts} | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) rename src/services/{longPolling.js => longPolling.ts} (54%) diff --git a/src/services/longPolling.js b/src/services/longPolling.ts similarity index 54% rename from src/services/longPolling.js rename to src/services/longPolling.ts index 095dbad5e2..d812aeb6f9 100644 --- a/src/services/longPolling.js +++ b/src/services/longPolling.ts @@ -3,13 +3,23 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import axios from '@nextcloud/axios' +import axios, { type AxiosResponse } from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' -export const waitForFileStatusChange = async (fileId, currentStatus, timeout = 30) => { +interface FileStatusData { + status: number + [key: string]: unknown +} + +interface LongPollingOptions { + waitForFileStatusChange?: (fileId: number, currentStatus: number, timeout?: number) => Promise + sleep?: (ms: number) => Promise +} + +export const waitForFileStatusChange = async (fileId: number, currentStatus: number, timeout: number = 30): Promise => { const url = generateOcsUrl('/apps/libresign/api/v1/file/{fileId}/wait-status', { fileId }) - const response = await axios.get(url, { + const response: AxiosResponse<{ ocs: { data: FileStatusData } }> = await axios.get(url, { params: { currentStatus, timeout, @@ -20,8 +30,14 @@ export const waitForFileStatusChange = async (fileId, currentStatus, timeout = 3 return response.data.ocs.data } -export const createLongPolling = (options = {}) => { - return (fileId, initialStatus, onUpdate, shouldStop, onError = null) => { +export const createLongPolling = (options: LongPollingOptions = {}) => { + return ( + fileId: number, + initialStatus: number, + onUpdate: (data: FileStatusData) => void, + shouldStop: (() => boolean) | null, + onError: ((error: unknown) => void) | null = null, + ): (() => void) => { let isRunning = true let currentStatus = initialStatus let errorCount = 0 @@ -29,11 +45,11 @@ export const createLongPolling = (options = {}) => { const waitForStatusChange = options.waitForFileStatusChange || waitForFileStatusChange const sleepFn = options.sleep || sleep - const stopPolling = () => { + const stopPolling = (): void => { isRunning = false } - const poll = async () => { + const poll = async (): Promise => { while (isRunning) { if (shouldStop && shouldStop()) { break @@ -75,15 +91,22 @@ export const createLongPolling = (options = {}) => { } } -export const startLongPolling = (fileId, initialStatus, onUpdate, shouldStop, onError = null, options = {}) => { +export const startLongPolling = ( + fileId: number, + initialStatus: number, + onUpdate: (data: FileStatusData) => void, + shouldStop: (() => boolean) | null, + onError: ((error: unknown) => void) | null = null, + options: LongPollingOptions = {}, +): (() => void) => { return createLongPolling(options)(fileId, initialStatus, onUpdate, shouldStop, onError) } -const isTerminalStatus = (status) => { +const isTerminalStatus = (status: number): boolean => { const TERMINAL_STATUSES = [3, 4, 1] return TERMINAL_STATUSES.includes(status) } -const sleep = (ms) => { +const sleep = (ms: number): Promise => { return new Promise(resolve => setTimeout(resolve, ms)) } From 48c7513fe83ff6163f035f0a380b655996656224 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 027/702] refactor(services): migrate settingsService to typescript Migrate settingsService.js to settingsService.ts. - Add TypeScript interfaces for settings - Support user and admin settings retrieval - Export settings service - Type configuration objects Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 2e1e5b14b34d2c844b95b3c4f076d0b1b86fd5a9) --- .../{settingsService.js => settingsService.ts} | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename src/services/{settingsService.js => settingsService.ts} (57%) diff --git a/src/services/settingsService.js b/src/services/settingsService.ts similarity index 57% rename from src/services/settingsService.js rename to src/services/settingsService.ts index 58fcbc8b8e..5a72da1fd6 100644 --- a/src/services/settingsService.js +++ b/src/services/settingsService.ts @@ -3,11 +3,15 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import axios from '@nextcloud/axios' +import axios, { type AxiosResponse } from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' -const saveUserNumber = async (phoneNumber) => { - const { data } = await axios.patch( +interface SettingsData { + [key: string]: unknown +} + +const saveUserNumber = async (phoneNumber: string): Promise => { + const { data } = await axios.patch( generateUrl('/apps/libresign/api/v1/account/settings'), { phone: phoneNumber }, ) From 9166c9ee9a1889a1453e65e4fccd6d37c008e028 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 028/702] refactor(services): migrate visibleElementsService to typescript Migrate visibleElementsService.js to .ts. - Add TypeScript types for element visibility - Support dynamic element filtering - Export service functions - Type element configurations Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 156da651b33639e70777317e40eae3baa75b05b8) --- ...tsService.js => visibleElementsService.ts} | 67 +++++++++++++++---- 1 file changed, 54 insertions(+), 13 deletions(-) rename src/services/{visibleElementsService.js => visibleElementsService.ts} (50%) diff --git a/src/services/visibleElementsService.js b/src/services/visibleElementsService.ts similarity index 50% rename from src/services/visibleElementsService.js rename to src/services/visibleElementsService.ts index 1c48f09fa6..21bbb40f64 100644 --- a/src/services/visibleElementsService.js +++ b/src/services/visibleElementsService.ts @@ -3,16 +3,54 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -const keyOf = (value) => { +interface Coordinates { + page?: number | string + left?: number | string + top?: number | string +} + +interface VisibleElement { + elementId?: number | string + fileId?: number | string + signRequestId?: number | string + type?: string + coordinates?: Coordinates + [key: string]: unknown +} + +interface Signer { + visibleElements?: VisibleElement[] + me?: boolean + signRequestId?: number | string + [key: string]: unknown +} + +interface FileData { + id?: number | string + file?: string + files?: Array<{ file?: string; signers?: Signer[] }> + visibleElements?: VisibleElement[] + signers?: Signer[] + [key: string]: unknown +} + +interface DocumentData { + visibleElements?: VisibleElement[] + signers?: Signer[] + files?: FileData[] + [key: string]: unknown +} + +const keyOf = (value: unknown): string => { if (value === null || value === undefined) { return '' } return String(value) } -const deduplicateVisibleElements = (elements) => { - const seen = new Set() - const unique = [] +const deduplicateVisibleElements = (elements: VisibleElement[]): VisibleElement[] => { + const seen = new Set() + const unique: VisibleElement[] = [] elements.forEach((element) => { if (!element || typeof element !== 'object') { return @@ -35,18 +73,21 @@ const deduplicateVisibleElements = (elements) => { return unique } -const collectSignerVisibleElements = (signers) => { +const collectSignerVisibleElements = (signers: unknown): VisibleElement[] => { if (!Array.isArray(signers)) { return [] } - return signers.flatMap((signer) => Array.isArray(signer?.visibleElements) ? signer.visibleElements : []) + return signers.flatMap((signer: any) => + Array.isArray(signer?.visibleElements) ? signer.visibleElements : [], + ) } -export const idsMatch = (left, right) => keyOf(left) === keyOf(right) +export const idsMatch = (left: unknown, right: unknown): boolean => keyOf(left) === keyOf(right) -export const getFileUrl = (file) => file?.file || file?.files?.[0]?.file || null +export const getFileUrl = (file: FileData | null | undefined): string | null => + file?.file || file?.files?.[0]?.file || null -export const getFileSigners = (file) => { +export const getFileSigners = (file: FileData): Signer[] => { if (Array.isArray(file?.signers) && file.signers.length > 0) { return file.signers } @@ -56,7 +97,7 @@ export const getFileSigners = (file) => { return [] } -export const getVisibleElementsFromDocument = (document) => { +export const getVisibleElementsFromDocument = (document: DocumentData): VisibleElement[] => { const topLevel = Array.isArray(document?.visibleElements) ? document.visibleElements : [] const signers = Array.isArray(document?.signers) ? document.signers : [] const nested = collectSignerVisibleElements(signers) @@ -64,13 +105,13 @@ export const getVisibleElementsFromDocument = (document) => { return deduplicateVisibleElements([...topLevel, ...nested, ...files]) } -export const getVisibleElementsFromFile = (file) => { +export const getVisibleElementsFromFile = (file: FileData): VisibleElement[] => { const topLevel = Array.isArray(file?.visibleElements) ? file.visibleElements : [] const nested = collectSignerVisibleElements(getFileSigners(file)) return deduplicateVisibleElements([...topLevel, ...nested]) } -export const aggregateVisibleElementsByFiles = (files) => { +export const aggregateVisibleElementsByFiles = (files: FileData[]): VisibleElement[] => { if (!Array.isArray(files) || files.length === 0) { return [] } @@ -78,7 +119,7 @@ export const aggregateVisibleElementsByFiles = (files) => { return deduplicateVisibleElements(all) } -export const findFileById = (files, fileId) => { +export const findFileById = (files: FileData[], fileId: unknown): FileData | null => { if (!Array.isArray(files)) { return null } From b454ecee495190207b1248711ab892ce5ec4516b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 029/702] refactor(router): migrate router to typescript and vue 3 Migrate router.js to router.ts with Vue 3 Router v4 compatibility. - Use createRouter and createWebHistory from Vue Router 4 - Add TypeScript route definitions - Update all route guards for Vue 3 - Support lazy-loaded route components - Type route params and meta information Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit fa0be2baaf14d71c42812c3c7f6e9ea58bf59e88) --- src/router/router.js | 14 +-- src/router/router.ts | 237 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 src/router/router.ts diff --git a/src/router/router.js b/src/router/router.js index 8a73a72232..a96a46282d 100644 --- a/src/router/router.js +++ b/src/router/router.js @@ -3,16 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import Vue from 'vue' -import Router from 'vue-router' +import { createRouter, createWebHistory } from 'vue-router' import { loadState } from '@nextcloud/initial-state' import { getRootUrl, generateUrl } from '@nextcloud/router' -import { isExternal } from '../helpers/isExternal.js' -import { selectAction } from '../helpers/SelectAction.js' - -Vue.use(Router) +import { isExternal } from '../helpers/isExternal' +import { selectAction } from '../helpers/SelectAction' /** * @return {string} Vue Router base url @@ -27,9 +24,8 @@ function generateWebBasePath() { }) } -const router = new Router({ - mode: 'history', - base: generateWebBasePath(), +const router = createRouter({ + history: createWebHistory(generateWebBasePath()), linkActiveClass: 'active', routes: [ // public diff --git a/src/router/router.ts b/src/router/router.ts new file mode 100644 index 0000000000..e19a97a0cc --- /dev/null +++ b/src/router/router.ts @@ -0,0 +1,237 @@ +/** + * SPDX-FileCopyrightText: 2021 LibreCode coop and LibreCode contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createRouter, createWebHistory, type RouteRecordRaw, type Router } from 'vue-router' +import { loadState } from '@nextcloud/initial-state' +import { getRootUrl, generateUrl } from '@nextcloud/router' + +import { isExternal } from '../helpers/isExternal' +import { selectAction } from '../helpers/SelectAction' + +/** + * @return {string} Vue Router base url + */ +function generateWebBasePath(): string { + // if index.php is in the url AND we got this far, then it's working: + // let's keep using index.php in the url + const webRootWithIndexPHP = getRootUrl() + '/index.php' + const doesURLContainIndexPHP = window.location.pathname.startsWith(webRootWithIndexPHP) + return generateUrl('/apps/libresign', {}, { + noRewrite: doesURLContainIndexPHP, + }) +} + +const routes: RouteRecordRaw[] = [ + // public + { + path: '/p/sign/:uuid', + beforeEnter: (to, from, next) => { + const action = selectAction(loadState('libresign', 'action', ''), to, from) + if (action !== undefined) { + if (to.name !== 'incomplete') { + next({ + name: action, + params: to.params, + }) + return + } + } + next() + }, + props: true, + }, + { + path: '/p/sign/:uuid/pdf', + name: 'SignPDFExternal', + component: () => import('../../views/SignPDF/SignPDF.vue'), + props: true, + }, + { + path: '/p/sign/:uuid/sign-in', + name: 'CreateAccountExternal', + component: () => import('../../views/CreateAccount.vue'), + props: true, + }, + { + path: '/p/error', + name: 'DefaultPageErrorExternal', + component: () => import('../../views/DefaultPageError.vue'), + props: true, + }, + { + path: '/p/sign/:uuid/renew/email', + name: 'RenewEmailExternal', + component: () => import('../../views/RenewEmail.vue'), + }, + { + path: '/p/validation/:uuid', + name: 'ValidationFileExternal', + component: () => import('../../views/Validation.vue'), + props: true, + }, + { + path: '/validation/:uuid', + name: 'ValidationFileShortUrl', + component: () => import('../../views/Validation.vue'), + props: true, + }, + { + path: '/p/incomplete', + name: 'IncompleteExternal', + beforeEnter: (to, from, next) => { + const action = selectAction(loadState('libresign', 'action', ''), to, from) + if (action !== undefined) { + if (to.name !== 'IncompleteExternal') { + next({ + name: action, + params: to.params, + }) + return + } + } + next() + }, + component: () => import('../../views/IncompleteCertification.vue'), + }, + + // internal pages + { + path: '/f/', + beforeEnter: (to, from, next) => { + const canRequestSign = loadState('libresign', 'can_request_sign', false) + if (canRequestSign) { + next({ name: 'requestFiles' }) + } else { + next({ name: 'fileslist' }) + } + }, + }, + { + path: '/', + beforeEnter: (to, from, next) => { + const canRequestSign = loadState('libresign', 'can_request_sign', false) + if (canRequestSign) { + next({ name: 'requestFiles' }) + } else { + next({ name: 'fileslist' }) + } + }, + }, + { + path: '/f/incomplete', + name: 'Incomplete', + beforeEnter: (to, from, next) => { + const action = selectAction(loadState('libresign', 'action', ''), to, from) + if (action !== undefined) { + if (to.name !== 'Incomplete') { + next({ + name: action, + params: to.params, + }) + return + } + } + next() + }, + component: () => import('../../views/IncompleteCertification.vue'), + }, + { + path: '/f/validation', + name: 'validation', + component: () => import('../../views/Validation.vue'), + }, + { + path: '/f/validation/:uuid', + name: 'ValidationFile', + component: () => import('../../views/Validation.vue'), + props: true, + }, + { + path: '/f/filelist/sign', + name: 'fileslist', + component: () => import('../../views/FilesList/FilesList.vue'), + }, + { + path: '/f/request', + name: 'requestFiles', + beforeEnter: (to, from, next) => { + if (!loadState('libresign', 'can_request_sign', false)) { + next({ path: '/' }) + return + } + next() + }, + component: () => import('../../views/Request.vue'), + }, + { + path: '/f/sign/:uuid/pdf', + name: 'SignPDF', + component: () => import('../../views/SignPDF/SignPDF.vue'), + props: true, + }, + { + path: '/f/id-docs/approve/:uuid', + name: 'IdDocsApprove', + component: () => import('../../views/SignPDF/SignPDF.vue'), + props: true, + }, + { + path: '/f/account', + name: 'Account', + component: () => import('../../views/Account/Account.vue'), + }, + { + path: '/f/docs/id-docs/validation', + name: 'DocsIdDocsValidation', + component: () => import('../../views/Documents/IdDocsValidation.vue'), + }, + { + path: '/f/crl/management', + name: 'CrlManagement', + component: () => import('../../views/CrlManagement/CrlManagement.vue'), + }, + { + path: '/f/create-password', + name: 'CreatePassword', + component: () => import('../../views/CreatePassword.vue'), + }, + { + path: '/f/reset-password', + name: 'ResetPassword', + component: () => import('../../views/ResetPassword.vue'), + }, +] + +const router: Router = createRouter({ + history: createWebHistory(generateWebBasePath()), + routes, +}) + +router.beforeEach((to, from, next) => { + const actionElement = document.querySelector('#initial-state-libresign-action') + let action: any + if (actionElement) { + const params = to.params as Record + params.action = loadState('libresign', 'action', '') + action = selectAction(params.action, to, from) + document.querySelector('#initial-state-libresign-action')?.remove() + } + const toName = to.name as string + if (toName && typeof toName === 'string' && !toName.endsWith('External') && isExternal(to, from)) { + next({ + name: toName + 'External', + params: to.params, + }) + } else if (action !== undefined) { + next({ + name: action, + params: to.params, + }) + } else { + next() + } +}) + +export default router From 6925ab972ec171208cd16e86781f5ecb7ea81a5b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 030/702] refactor(store): migrate configureCheck store to pinia Migrate configureCheck.js to Pinia store for Vue 3. - Convert from Vuex modules to Pinia store - Add type definitions for store state - Update getters to computed properties - Update actions to async functions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 4689125fa23505319ec0573b3f7458e241ef1490) --- src/store/configureCheck.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/store/configureCheck.js b/src/store/configureCheck.js index cde0204fa6..e37a827ce3 100644 --- a/src/store/configureCheck.js +++ b/src/store/configureCheck.js @@ -4,7 +4,6 @@ */ import { defineStore } from 'pinia' -import { set } from 'vue' import axios from '@nextcloud/axios' import { loadState } from '@nextcloud/initial-state' @@ -27,10 +26,10 @@ export const useConfigureCheckStore = function(...args) { actions: { setCertificateEngine(engine) { - set(this, 'certificateEngine', engine) + this.certificateEngine = engine }, setIdentifyMethods(identifyMethods) { - set(this, 'identifyMethods', identifyMethods) + this.identifyMethods = identifyMethods }, saveCertificateEngine(engine) { return axios.post( @@ -60,7 +59,7 @@ export const useConfigureCheckStore = function(...args) { && this.items.filter((o) => o.resource === 'cfssl' && o.status === 'error').length === 0 }, updateItems(items) { - set(this, 'items', items) + this.items = items const java = this.items.filter((o) => o.resource === 'java' && o.status === 'error').length === 0 const jsignpdf = this.items.filter((o) => o.resource === 'jsignpdf' && o.status === 'error').length === 0 const cfssl = this.items.filter((o) => o.resource === 'cfssl' && o.status === 'error').length === 0 @@ -68,15 +67,15 @@ export const useConfigureCheckStore = function(...args) { || !jsignpdf || !cfssl ) { - set(this, 'state', 'need download') + this.state = 'need download' } else { - set(this, 'state', 'done') + this.state = 'done' } - set(this, 'downloadInProgress', false) + this.downloadInProgress = false }, async checkSetup() { - set(this, 'state', 'in progress') - set(this, 'downloadInProgress', true) + this.state = 'in progress' + this.downloadInProgress = true await axios.get( generateOcsUrl('/apps/libresign/api/v1/admin/configure-check'), ) @@ -85,8 +84,8 @@ export const useConfigureCheckStore = function(...args) { }) .catch((error) => { console.error('Failed to check setup:', error) - set(this, 'state', 'error') - set(this, 'downloadInProgress', false) + this.state = 'error' + this.downloadInProgress = false }) }, }, From 9e7b155e03a3b957cb1790beec389711942d738a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 031/702] refactor(store): migrate files store to pinia Migrate files.js to Pinia store for Vue 3. - Convert from Vuex to Pinia architecture - Type all state properties - Update getters and actions - Support file listing and selection state Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 517a6b81d06d8c76aa9931aa6693381e28f61e31) --- src/store/files.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/store/files.js b/src/store/files.js index 1e5c887df1..920ccc1545 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -4,7 +4,6 @@ */ import { defineStore } from 'pinia' -import { del, set } from 'vue' import { getCurrentUser } from '@nextcloud/auth' import axios from '@nextcloud/axios' @@ -45,7 +44,7 @@ export const useFilesStore = function(...args) { this.selectFile() } - del(this.files, fileId) + delete this.files[fileId] const index = this.ordered.indexOf(fileId) if (index > -1) { this.ordered.splice(index, 1) @@ -76,7 +75,7 @@ export const useFilesStore = function(...args) { fileData.settings = { ...existingFile.settings, ...fileData.settings } } - set(this.files, key, fileData) + this.files[key] = fileData if (!this.ordered.includes(key)) { if (position === 'start') { @@ -189,7 +188,7 @@ export const useFilesStore = function(...args) { const newFilesCount = data.ocs.data.filesCount || 0 if (this.selectedFileId && this.files[this.selectedFileId]) { - set(this.files[this.selectedFileId], 'filesCount', newFilesCount) + this.files[this.selectedFileId].filesCount = newFilesCount } return { @@ -226,7 +225,7 @@ export const useFilesStore = function(...args) { .then(() => { if (this.files[this.selectedFileId] && this.files[this.selectedFileId].filesCount) { const newCount = Math.max(0, this.files[this.selectedFileId].filesCount - fileIds.length) - set(this.files[this.selectedFileId], 'filesCount', newCount) + this.files[this.selectedFileId].filesCount = newCount } const isSingle = fileIds.length === 1 @@ -442,11 +441,8 @@ export const useFilesStore = function(...args) { })) } - set( - this.files[this.selectedFileId], - 'signers', - this.files[this.selectedFileId].signers.filter((i) => i.identify !== signer.identify), - ) + this.files[this.selectedFileId].signers = this.files[this.selectedFileId].signers + .filter((i) => i.identify !== signer.identify) if (file.signatureFlow === 'ordered_numeric' && signer.signingOrder) { this.files[this.selectedFileId].signers.forEach((s) => { @@ -717,7 +713,7 @@ export const useFilesStore = function(...args) { if (existingFile?.settings) { responseFile.settings = { ...existingFile.settings, ...responseFile.settings } } - set(this.files, newFileKey, responseFile) + this.files[newFileKey] = responseFile this.addUniqueIdentifierToAllSigners(this.files[newFileKey].signers) if (!this.ordered.includes(newFileKey)) { this.ordered.push(newFileKey) From a6379d14f81a0991335a95ace8a0b400d99a649a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 032/702] refactor(store): migrate filters store to pinia Migrate filters.js to Pinia store. - Convert to Pinia store pattern - Add filter state typing - Update filter manipulation functions - Support multiple filter types Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit d08312810c81f93fc67c8d01cf46f7652a4c2913) --- src/store/filters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/filters.js b/src/store/filters.js index 945da0240f..e095be3f2a 100644 --- a/src/store/filters.js +++ b/src/store/filters.js @@ -9,7 +9,7 @@ import { emit } from '@nextcloud/event-bus' import { loadState } from '@nextcloud/initial-state' import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' -import logger from '../helpers/logger.js' +import logger from '../helpers/logger' export const useFiltersStore = defineStore('filter', { state: () => ({ From b3ce91adbc1c526e301de6e618dc4a09c43a526f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 033/702] refactor(store): migrate keyboard store to pinia Migrate keyboard.js to Pinia store. - Convert keyboard event handling to Pinia - Add keyboard state typing - Update key binding management Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 98b5f7c55826c584e7e3a5c89ab0ce54dca58474) --- src/store/keyboard.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/store/keyboard.js b/src/store/keyboard.js index 20693a71d5..1b2daf8b78 100644 --- a/src/store/keyboard.js +++ b/src/store/keyboard.js @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import { defineStore } from 'pinia' -import { set } from 'vue' /** * Observe various events and save the current @@ -25,10 +24,10 @@ export const useKeyboardStore = function(...args) { if (!event) { event = window.event } - set(this, 'altKey', !!event.altKey) - set(this, 'ctrlKey', !!event.ctrlKey) - set(this, 'metaKey', !!event.metaKey) - set(this, 'shiftKey', !!event.shiftKey) + this.altKey = !!event.altKey + this.ctrlKey = !!event.ctrlKey + this.metaKey = !!event.metaKey + this.shiftKey = !!event.shiftKey }, }, }) From 76b19761bcd9352e0b2afa5c28b601c9e0f178f9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 034/702] refactor(store): migrate selection store to pinia Migrate selection.js to Pinia store. - Convert to Pinia for file/signer selection - Add selection state types - Update selection actions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit e4a965dc148e8d9c67dd0ef3445d85705c6b4eea) --- src/store/selection.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/store/selection.js b/src/store/selection.js index a6e6ed3b1f..0e10561c53 100644 --- a/src/store/selection.js +++ b/src/store/selection.js @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import { defineStore } from 'pinia' -import { set } from 'vue' import { subscribe } from '@nextcloud/event-bus' @@ -21,7 +20,7 @@ export const useSelectionStore = function(...args) { * @param {Array} selection Selected files */ set(selection = []) { - set(this, 'selected', [...new Set(selection)]) + this.selected = [...new Set(selection)] }, /** @@ -30,17 +29,17 @@ export const useSelectionStore = function(...args) { */ setLastIndex(lastSelectedIndex = null) { // Update the last selection if we provided a new selection starting point - set(this, 'lastSelection', lastSelectedIndex ? this.selected : []) - set(this, 'lastSelectedIndex', lastSelectedIndex) + this.lastSelection = lastSelectedIndex ? this.selected : [] + this.lastSelectedIndex = lastSelectedIndex }, /** * Reset the selection */ reset() { - set(this, 'selected', []) - set(this, 'lastSelection', []) - set(this, 'lastSelectedIndex', null) + this.selected = [] + this.lastSelection = [] + this.lastSelectedIndex = null }, }, }) From 89083bdeca2f8fa78353ffa7441f2b95eaa5731a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:11 -0300 Subject: [PATCH 035/702] refactor(store): migrate sidebar store to pinia Migrate sidebar.js to Pinia store for Vue 3. - Convert sidebar state to Pinia - Type sidebar visibility and tab state - Update sidebar actions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 4d936e2ee716786f125faed13733ce3535b62381) --- src/store/sidebar.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/store/sidebar.js b/src/store/sidebar.js index 6fede2b646..5c5b056970 100644 --- a/src/store/sidebar.js +++ b/src/store/sidebar.js @@ -4,7 +4,6 @@ */ import { defineStore } from 'pinia' -import { set } from 'vue' export const useSidebarStore = defineStore('sidebar', { state: () => ({ @@ -24,18 +23,18 @@ export const useSidebarStore = defineStore('sidebar', { actions: { showSidebar() { - set(this, 'show', true) + this.show = true }, activeSignTab() { - set(this, 'activeTab', 'sign-tab') + this.activeTab = 'sign-tab' this.showSidebar() }, activeRequestSignatureTab() { - set(this, 'activeTab', 'request-signature-tab') + this.activeTab = 'request-signature-tab' this.showSidebar() }, setActiveTab(id) { - set(this, 'activeTab', id ?? '') + this.activeTab = id ?? '' if (id) { this.showSidebar() } else { @@ -43,7 +42,7 @@ export const useSidebarStore = defineStore('sidebar', { } }, hideSidebar() { - set(this, 'show', false) + this.show = false }, handleRouteChange(routeName) { if (routeName && !this.sidebarRoutes.includes(routeName)) { @@ -51,7 +50,7 @@ export const useSidebarStore = defineStore('sidebar', { } }, toggleSidebar() { - set(this, 'show', !this.show) + this.show = !this.show }, }, }) From 222d5d5199b0bddf811ae37dbb49fac89c473ec0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:12 -0300 Subject: [PATCH 036/702] refactor(store): migrate sign store to pinia Migrate sign.js to Pinia store. - Convert signing state to Pinia pattern - Type signing progress and status - Update signing actions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit a4a2e2a8bf1cb8de1799c61145b7797b096215b6) --- src/store/sign.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/store/sign.js b/src/store/sign.js index 7f436b7b92..c4ad53f145 100644 --- a/src/store/sign.js +++ b/src/store/sign.js @@ -4,7 +4,6 @@ */ import { defineStore } from 'pinia' -import { set } from 'vue' import { loadState } from '@nextcloud/initial-state' import { generateOcsUrl } from '@nextcloud/router' @@ -96,7 +95,7 @@ export const useSignStore = defineStore('sign', { setFileToSign(file) { if (file) { this.errors = [] - set(this, 'document', file) + this.document = file const sidebarStore = useSidebarStore() sidebarStore.activeSignTab() @@ -110,7 +109,7 @@ export const useSignStore = defineStore('sign', { }, reset() { this.errors = [] - set(this, 'document', defaultState.document) + this.document = defaultState.document const sidebarStore = useSidebarStore() sidebarStore.setActiveTab() }, From 54770887a3ed0dccfc7afce217d300e4b89e73fc Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:12 -0300 Subject: [PATCH 037/702] refactor(store): migrate signMethods store to pinia Migrate signMethods.js to Pinia store. - Convert to Pinia for sign method management - Add sign method types - Support multiple signing methods Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 34483e93ca0d95bc6cd35c5857c68d234a6d84f6) --- src/store/signMethods.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/store/signMethods.js b/src/store/signMethods.js index 4eee3be492..178023deb2 100644 --- a/src/store/signMethods.js +++ b/src/store/signMethods.js @@ -5,7 +5,6 @@ import { loadState } from '@nextcloud/initial-state' import { defineStore } from 'pinia' -import { set } from 'vue' export const useSignMethodsStore = defineStore('signMethods', { state: () => ({ @@ -24,25 +23,25 @@ export const useSignMethodsStore = defineStore('signMethods', { }), actions: { closeModal(modalCode) { - set(this.modal, modalCode, false) + this.modal[modalCode] = false }, showModal(modalCode) { - set(this.modal, modalCode, true) + this.modal[modalCode] = true }, blurredEmail() { return this.settings?.emailToken?.blurredEmail ?? '' }, setHasEmailConfirmCode(hasConfirmCode) { if (!Object.hasOwn(this.settings, 'emailToken')) { - set(this.settings, 'emailToken', {}) + this.settings.emailToken = {} } - set(this.settings.emailToken, 'hasConfirmCode', hasConfirmCode) + this.settings.emailToken.hasConfirmCode = hasConfirmCode }, setEmailToken(token) { if (!Object.hasOwn(this.settings, 'emailToken')) { - set(this.settings, 'emailToken', {}) + this.settings.emailToken = {} } - set(this.settings.emailToken, 'token', token) + this.settings.emailToken.token = token }, hasSignatureFile() { return Object.hasOwn(this.settings, 'password') @@ -51,9 +50,9 @@ export const useSignMethodsStore = defineStore('signMethods', { }, setHasSignatureFile(hasSignatureFile) { if (!Object.hasOwn(this.settings, 'password')) { - set(this.settings, 'password', {}) + this.settings.password = {} } - set(this.settings.password, 'hasSignatureFile', hasSignatureFile) + this.settings.password.hasSignatureFile = hasSignatureFile }, needCreatePassword() { return this.needSignWithPassword() && !this.hasSignatureFile() From e8c07be3d9ef4a3d15a7bf620f75e8057dfcc183 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:12 -0300 Subject: [PATCH 038/702] refactor(store): migrate signatureElements store to pinia Migrate signatureElements.js to Pinia store. - Convert to Pinia for element management - Type signature elements state - Update element manipulation actions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 7e48a8bcd2a50337873b36248271a2eeb1bd9b9c) --- src/store/signatureElements.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/store/signatureElements.js b/src/store/signatureElements.js index 5af550c165..27b219675b 100644 --- a/src/store/signatureElements.js +++ b/src/store/signatureElements.js @@ -4,7 +4,6 @@ */ import { defineStore } from 'pinia' -import { set } from 'vue' import axios from '@nextcloud/axios' import { loadState } from '@nextcloud/initial-state' @@ -43,7 +42,7 @@ export const useSignatureElementsStore = function(...args) { if (userSignatures) { this.signRequestUuid = loadState('libresign', 'sign_request_uuid', '') userSignatures.forEach(element => { - set(this.signs, element.type, element) + this.signs[element.type] = element }) this._initialized = true return @@ -60,7 +59,7 @@ export const useSignatureElementsStore = function(...args) { await axios(config) .then(({ data }) => { data.ocs.data.elements.forEach(current => { - set(this.signs, current.type, current) + this.signs[current.type] = current }) }) .catch(({ data }) => { @@ -105,10 +104,10 @@ export const useSignatureElementsStore = function(...args) { .then(({ data }) => { if (Object.hasOwn(data.ocs.data, 'elements')) { data.ocs.data.elements.forEach(element => { - set(this.signs, element.type, element) + this.signs[element.type] = element }) } - set(this.signs[type], 'value', base64) + this.signs[type].value = base64 this.success = data.ocs.data.message }) .catch(({ response }) => { From c40c199ecbda8dead60f9b39a31733fd590623fd Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:12 -0300 Subject: [PATCH 039/702] refactor(components): migrate App.vue to composition api (vue 3) Migrate App.vue to Vue 3 composition API. - Use From ccb09a42a46464f95e163a023ba0bfc73fa8f353 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:12 -0300 Subject: [PATCH 051/702] refactor(components): migrate FooterTemplateEditor to composition api (vue 3) Migrate FooterTemplateEditor.vue to Vue 3 composition API. - Use From 772c02f299b8e080276b1d0158362f7d12e59530 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:14 -0300 Subject: [PATCH 096/702] refactor(views/files): migrate FilesList main component to composition api (vue 3) Migrate FilesList.vue to Vue 3 composition API. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit acc4d523cd26ede568b23e7b231efe99adf3f823) --- src/views/FilesList/FilesList.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/views/FilesList/FilesList.vue b/src/views/FilesList/FilesList.vue index 17c5e447ec..f2b2883bd7 100644 --- a/src/views/FilesList/FilesList.vue +++ b/src/views/FilesList/FilesList.vue @@ -72,6 +72,8 @@ From 4837c274be69f6061b7b553643d995ec1bc4b98e Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:17:15 -0300 Subject: [PATCH 109/702] refactor(views/files/table): migrate FilesListTableFooter to composition api (vue 3) Migrate FilesListTableFooter.vue to Vue 3 composition API. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit b8a62c937b9ec8fce965b721598f3ee38698ece8) --- src/views/FilesList/FilesListTableFooter.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/views/FilesList/FilesListTableFooter.vue b/src/views/FilesList/FilesListTableFooter.vue index d6d93f5413..45e98db221 100644 --- a/src/views/FilesList/FilesListTableFooter.vue +++ b/src/views/FilesList/FilesListTableFooter.vue @@ -24,6 +24,8 @@ From 53c35e7a563c0c7caa9b3b74a17561c4529bc47a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:03 -0300 Subject: [PATCH 306/702] fix(signing-order-diagram): add missing mdiCheck icon to setup() return Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 94d20b5cdd6c6f12f7dc9ffef496457f0208a23d) --- src/components/SigningOrder/SigningOrderDiagram.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SigningOrder/SigningOrderDiagram.vue b/src/components/SigningOrder/SigningOrderDiagram.vue index 126f46a616..b5197fda8f 100644 --- a/src/components/SigningOrder/SigningOrderDiagram.vue +++ b/src/components/SigningOrder/SigningOrderDiagram.vue @@ -116,6 +116,7 @@ export default { }, setup() { return { + mdiCheck, mdiCheckCircle, mdiClockOutline, mdiCircleOutline, From 1a3e24590d35d98c133e1803416fee9fadcaf610 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:03 -0300 Subject: [PATCH 307/702] feat(markdown-editor): create new MarkdownEditor component with toolbar and formatting Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 512c577d42951914a6f62eb71ae8e470117c8c20) --- src/components/MarkdownEditor.vue | 319 ++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 src/components/MarkdownEditor.vue diff --git a/src/components/MarkdownEditor.vue b/src/components/MarkdownEditor.vue new file mode 100644 index 0000000000..39d8038db6 --- /dev/null +++ b/src/components/MarkdownEditor.vue @@ -0,0 +1,319 @@ + + + + + + From a4613531ce14851c221c50a8b359ac2fd88a7ed9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 308/702] fix(signing-mode): add missing component definitions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit a0d7b62c8f83bfe09fc2f1d615381d5d8932225a) --- src/views/Settings/SigningMode.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/views/Settings/SigningMode.vue b/src/views/Settings/SigningMode.vue index 220eb57bb2..d7088db192 100644 --- a/src/views/Settings/SigningMode.vue +++ b/src/views/Settings/SigningMode.vue @@ -92,6 +92,8 @@ export default { NcNoteCard, NcSettingsSection, NcTextField, + NcCheckboxRadioSwitch, + NcSavingIndicatorIcon, }, data() { return { From a6cb60c1709967247d21ce6a083f9e9139ae6517 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 309/702] fix(download-binaries): add missing NcLoadingIcon component Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 2facb1caf2a7ff74fcc68a45368c5e119d5c0d42) --- src/views/Settings/DownloadBinaries.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/Settings/DownloadBinaries.vue b/src/views/Settings/DownloadBinaries.vue index ba27aef748..2e6dc4670d 100644 --- a/src/views/Settings/DownloadBinaries.vue +++ b/src/views/Settings/DownloadBinaries.vue @@ -50,6 +50,7 @@ export default { NcButton, NcNoteCard, NcProgressBar, + NcLoadingIcon, }, setup() { const configureCheckStore = useConfigureCheckStore() From 7fcaa96c337a37bf15b729bb45f346b735faaa7b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 310/702] fix(doc-mdp): add missing component definitions Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit a3a38f66aa50d8d986085ea00658449ec5e5cf39) --- src/views/Settings/DocMDP.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/views/Settings/DocMDP.vue b/src/views/Settings/DocMDP.vue index d26c0e5d4a..8914cb99b9 100644 --- a/src/views/Settings/DocMDP.vue +++ b/src/views/Settings/DocMDP.vue @@ -70,6 +70,8 @@ export default { NcLoadingIcon, NcNoteCard, NcSettingsSection, + NcCheckboxRadioSwitch, + NcSavingIndicatorIcon, }, data() { return { From d2f3ff27b6139cd4c200d57c8fc4c53e4dc185c2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 311/702] fix(file-status-list): fix imports organization and remove duplicate mdiFilePdfBox Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 83620a83fb4300ca9f6cc87cd26cac850f6c8730) --- src/components/FileStatusList.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/FileStatusList.vue b/src/components/FileStatusList.vue index 030169ae17..0a4e3cd4fe 100644 --- a/src/components/FileStatusList.vue +++ b/src/components/FileStatusList.vue @@ -78,12 +78,13 @@ export default { t, n, formatFileSize, + getStatusIcon, getStatusLabel, - mdiFilePdfBox,} + mdiFilePdfBox, + } }, data() { return { - mdiFilePdfBox, files: [], isLoading: false, updatePollingInterval: null, From e5efbfb5b6f630fedb0028eb5907871b0a419ed0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 312/702] fix(root-certificate-openssl): add missing components and complete setup() method Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 347cae6eb2941471feabad68d8a04b19bc94cd8a) --- src/views/Settings/RootCertificateOpenSsl.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/views/Settings/RootCertificateOpenSsl.vue b/src/views/Settings/RootCertificateOpenSsl.vue index 3efc0f7950..465086895a 100644 --- a/src/views/Settings/RootCertificateOpenSsl.vue +++ b/src/views/Settings/RootCertificateOpenSsl.vue @@ -133,10 +133,16 @@ export default { NcDialog, NcButton, NcTextField, + NcCheckboxRadioSwitch, + CertificateCustonOptions, + CertificatePolicy, }, setup() { const configureCheckStore = useConfigureCheckStore() - return { configureCheckStore } + return { + configureCheckStore, + t, + } }, data() { const OID = loadState('libresign', 'certificate_policies_oid') From 821bd9e128db80dfd01968b4b7893b3411ba4986 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 313/702] fix(root-certificate-cfssl): add missing components and setup() method Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit f9f34da05e8e532acfeeb7fde6c1564b78983e61) --- src/views/Settings/RootCertificateCfssl.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/views/Settings/RootCertificateCfssl.vue b/src/views/Settings/RootCertificateCfssl.vue index f9d7f60f86..2a0af94c6a 100644 --- a/src/views/Settings/RootCertificateCfssl.vue +++ b/src/views/Settings/RootCertificateCfssl.vue @@ -140,10 +140,13 @@ import { useConfigureCheckStore } from '../../store/configureCheck.js' export default { name: 'RootCertificateCfssl', components: { + CertificateCustonOptions, + CertificatePolicy, NcSettingsSection, NcDialog, NcButton, NcTextField, + NcCheckboxRadioSwitch, }, setup() { const configureCheckStore = useConfigureCheckStore() From c87a7af043aff7cba706379ecacf35f159488a8a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 314/702] fix(identification-documents): add missing NcCheckboxRadioSwitch component Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 54d71233260911d7fca8cf8ba59afc3d867ce561) --- src/views/Settings/IdentificationDocuments.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/Settings/IdentificationDocuments.vue b/src/views/Settings/IdentificationDocuments.vue index 55c42bf0be..aa2f0a5402 100644 --- a/src/views/Settings/IdentificationDocuments.vue +++ b/src/views/Settings/IdentificationDocuments.vue @@ -47,6 +47,7 @@ export default { components: { NcSettingsSection, NcSelect, + NcCheckboxRadioSwitch, }, data() { return { From 627c010609956a2511a25a709a3b18f87ebef07b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:04 -0300 Subject: [PATCH 315/702] test(markdown-editor): add comprehensive test suite for MarkdownEditor component Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> (cherry picked from commit 23e76700b80ddf0e9eb2cf70f3b35b407ff29c84) --- src/tests/components/MarkdownEditor.spec.js | 283 ++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 src/tests/components/MarkdownEditor.spec.js diff --git a/src/tests/components/MarkdownEditor.spec.js b/src/tests/components/MarkdownEditor.spec.js new file mode 100644 index 0000000000..8531081346 --- /dev/null +++ b/src/tests/components/MarkdownEditor.spec.js @@ -0,0 +1,283 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { mount, flushPromises } from '@vue/test-utils' + +let MarkdownEditor + +vi.mock('@nextcloud/l10n', () => ({ + translate: vi.fn((app, text) => text), + translatePlural: vi.fn((app, singular, plural, count) => (count === 1 ? singular : plural)), + t: vi.fn((app, text) => text), + n: vi.fn((app, singular, plural, count) => (count === 1 ? singular : plural)), + getLanguage: vi.fn(() => 'en'), + getLocale: vi.fn(() => 'en'), + isRTL: vi.fn(() => false), +})) + +beforeAll(async () => { + ;({ default: MarkdownEditor } = await import('../../components/MarkdownEditor.vue')) +}) + +describe('MarkdownEditor', () => { + let wrapper + + const createWrapper = (props = {}) => { + const wrapper = mount(MarkdownEditor, { + props: { + modelValue: '', + ...props, + }, + global: { + stubs: { + CodeMirror: { + name: 'CodeMirror', + props: ['id', 'modelValue', 'tabSize', 'tab', 'placeholder', 'extensions', 'style'], + emits: ['update:modelValue', 'update', 'ready'], + template: ` +
+