@conduction/nextcloud-vue 2.108.0 → 2.109.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js +10 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js.map +1 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js +305 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js.map +1 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js +38 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js.map +1 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +10 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js.map +1 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +398 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +155 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js +9 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js +281 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js +137 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js +9 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js.map +1 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js +225 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js.map +1 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js +110 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +19 -3
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +513 -481
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +9 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js.map +1 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +181 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js +50 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +102 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -6
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js +9 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js +190 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js +91 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js +9 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js +64 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js +49 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js.map +1 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js +9 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js.map +1 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js +382 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js.map +1 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js +285 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js.map +1 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +9 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js +277 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +183 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +53 -22
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js +9 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js.map +1 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js +246 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js.map +1 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js +197 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +106 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +105 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +495 -61
- package/dist/esm/utils/actionsDispatcher.js +6 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/builtInModals.js +3 -1
- package/dist/esm/utils/builtInModals.js.map +1 -1
- package/dist/esm/utils/duplicateWarning.js +62 -0
- package/dist/esm/utils/duplicateWarning.js.map +1 -0
- package/dist/esm/utils/errors.js +4 -0
- package/dist/esm/utils/errors.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +4 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/openDuplicateWarning.js +39 -0
- package/dist/esm/utils/openDuplicateWarning.js.map +1 -0
- package/dist/esm/utils/optionsFrom.js +69 -2
- package/dist/esm/utils/optionsFrom.js.map +1 -1
- package/dist/esm/utils/planTree.js +57 -0
- package/dist/esm/utils/planTree.js.map +1 -0
- package/dist/esm/utils/redactionSegments.js +53 -0
- package/dist/esm/utils/redactionSegments.js.map +1 -0
- package/dist/esm/utils/registerNcDashboardWidgets.js +61 -0
- package/dist/esm/utils/registerNcDashboardWidgets.js.map +1 -0
- package/dist/esm/utils/searchRefusal.js +62 -0
- package/dist/esm/utils/searchRefusal.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +17409 -12832
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +495 -61
- package/l10n/en.json +106 -1
- package/l10n/nl.json +105 -1
- package/package.json +1 -1
- package/src/components/CnAddressSearch/CnAddressSearch.vue +343 -0
- package/src/components/CnAddressSearch/index.js +12 -0
- package/src/components/CnAssignDialog/CnAssignDialog.vue +525 -0
- package/src/components/CnAssignDialog/index.js +12 -0
- package/src/components/CnDecisionListWidget/CnDecisionListWidget.vue +388 -0
- package/src/components/CnDecisionListWidget/index.js +12 -0
- package/src/components/CnDuplicateWarning/CnDuplicateWarning.vue +318 -0
- package/src/components/CnDuplicateWarning/index.js +12 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +19 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +52 -0
- package/src/components/CnNcDashboardList/CnNcDashboardList.vue +218 -0
- package/src/components/CnNcDashboardList/index.js +12 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +107 -6
- package/src/components/CnPlanWidget/CnPlanWidget.vue +246 -0
- package/src/components/CnPlanWidget/CnPlanWidgetRow.vue +117 -0
- package/src/components/CnPlanWidget/index.js +12 -0
- package/src/components/CnRedactionEditor/CnRedactionEditor.vue +655 -0
- package/src/components/CnRedactionEditor/index.js +13 -0
- package/src/components/CnRetentionWidget/CnRetentionWidget.vue +413 -0
- package/src/components/CnRetentionWidget/index.js +12 -0
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +31 -0
- package/src/components/CnWorkQueue/CnWorkQueue.vue +456 -0
- package/src/components/CnWorkQueue/index.js +12 -0
- package/src/components/index.js +9 -0
- package/src/css/index-page.css +24 -0
- package/src/index.js +12 -0
- package/src/schemas/app-manifest-v2.schema.json +119 -3
- package/src/utils/actionsDispatcher.js +6 -1
- package/src/utils/builtInModals.js +3 -1
- package/src/utils/duplicateWarning.js +59 -0
- package/src/utils/errors.js +4 -0
- package/src/utils/libraryWidgetKeys.js +4 -0
- package/src/utils/openDuplicateWarning.js +35 -0
- package/src/utils/optionsFrom.js +68 -1
- package/src/utils/planTree.js +54 -0
- package/src/utils/redactionSegments.js +50 -0
- package/src/utils/registerNcDashboardWidgets.js +57 -0
- package/src/utils/searchRefusal.js +59 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import axios__default from '@nextcloud/axios';
|
|
2
|
+
import { emit } from '@nextcloud/event-bus';
|
|
3
|
+
import { translate, translatePlural } from '@nextcloud/l10n';
|
|
4
|
+
import { generateUrl } from '@nextcloud/router';
|
|
5
|
+
import { NcButton, NcSelect, NcTextField } from '@nextcloud/vue';
|
|
6
|
+
import { readPath } from '../../utils/readPath.js';
|
|
7
|
+
import { redactionSegments } from '../../utils/redactionSegments.js';
|
|
8
|
+
|
|
9
|
+
/** The page refresh channel the built-in widgets listen on. */
|
|
10
|
+
const PAGE_REFRESH = 'cn:page:refresh';
|
|
11
|
+
|
|
12
|
+
/** The verdicts a reviewer gives a passage. */
|
|
13
|
+
const VERDICTS = ['accept', 'adjust', 'skip'];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The server's sentence for a failure, or the fallback.
|
|
17
|
+
*
|
|
18
|
+
* @param {unknown} failure The rejection.
|
|
19
|
+
* @param {string} fallback What to say when the server said nothing.
|
|
20
|
+
* @return {string}
|
|
21
|
+
*/
|
|
22
|
+
function sentenceOf(failure, fallback) {
|
|
23
|
+
const data = failure && failure.response && failure.response.data;
|
|
24
|
+
return String((data && (data.message || data.error)) || fallback)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* CnRedactionEditor — redact the documents of a record, one at a time
|
|
29
|
+
* (board DqLakken): the document text with the passages marked in place,
|
|
30
|
+
* and beside it the proposal, a verdict per passage (agreed, adjust, do not
|
|
31
|
+
* redact), a passage the detection missed added by hand, and Record
|
|
32
|
+
* redaction. It names no procedure: the routes and the passage type labels
|
|
33
|
+
* come from `config`, so dossiq's Woo redaction and filinq's anonymisation
|
|
34
|
+
* use the same editor.
|
|
35
|
+
*
|
|
36
|
+
* The check is the reviewer's: Record redaction is enabled only once every
|
|
37
|
+
* passage has a verdict, and it first records the check of the passages to
|
|
38
|
+
* redact, in the reviewer's name, and only then asks for the redacted copy.
|
|
39
|
+
* A passage marked `locked` (a rule that always applies) starts agreed and
|
|
40
|
+
* cannot be changed. A refusal is shown in the server's words and the
|
|
41
|
+
* upload of a version redacted elsewhere stays offered.
|
|
42
|
+
*
|
|
43
|
+
* `config`:
|
|
44
|
+
* - `urls`: `{ list, detect, check, redact, upload? }`, templates with
|
|
45
|
+
* `{recordId}` and `{documentRef}`. `list` answers `{ documents: [{
|
|
46
|
+
* documentRef, title, redactedDocumentRef?, redaction? }] }`; `detect`
|
|
47
|
+
* answers `{ text, redaction: { entities } }`; `check` receives `{ entities
|
|
48
|
+
* }`; `redact` and `upload` (a `file` form field) answer `{
|
|
49
|
+
* redactedDocumentRef, redaction }`.
|
|
50
|
+
* - `typeLabels`: what a passage type is called (`{ PERSON: "Name" }`).
|
|
51
|
+
* - `groundsPath`: a document's grounds (a list) shown under each passage.
|
|
52
|
+
* - `title`, `emptyText`: texts. All through the host translate.
|
|
53
|
+
*/
|
|
54
|
+
var script = {
|
|
55
|
+
name: 'CnRedactionEditor',
|
|
56
|
+
|
|
57
|
+
components: { NcButton, NcSelect, NcTextField },
|
|
58
|
+
|
|
59
|
+
inject: {
|
|
60
|
+
/** The host app's translate (CnAppRoot `cnTranslate`). */
|
|
61
|
+
cnTranslate: { default: () => (key) => key },
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
props: {
|
|
65
|
+
/** The record whose documents are redacted, for `{recordId}`. */
|
|
66
|
+
recordId: {
|
|
67
|
+
type: [String, Number],
|
|
68
|
+
default: '',
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The editor's configuration (see the component docs).
|
|
73
|
+
*
|
|
74
|
+
* @type {object}
|
|
75
|
+
*/
|
|
76
|
+
config: {
|
|
77
|
+
type: Object,
|
|
78
|
+
default: () => ({}),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
emits: ['recorded'],
|
|
83
|
+
|
|
84
|
+
data() {
|
|
85
|
+
return {
|
|
86
|
+
VERDICTS,
|
|
87
|
+
loading: true,
|
|
88
|
+
loadError: '',
|
|
89
|
+
documents: [],
|
|
90
|
+
index: 0,
|
|
91
|
+
documentText: '',
|
|
92
|
+
passages: [],
|
|
93
|
+
detecting: false,
|
|
94
|
+
busy: false,
|
|
95
|
+
error: '',
|
|
96
|
+
notice: '',
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
computed: {
|
|
101
|
+
/** @return {object} The open document. */
|
|
102
|
+
current() {
|
|
103
|
+
return this.documents[this.index] || {}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/** @return {string} The heading. */
|
|
107
|
+
heading() {
|
|
108
|
+
const own = this.text(this.config.title);
|
|
109
|
+
if (own) {
|
|
110
|
+
return own
|
|
111
|
+
}
|
|
112
|
+
return this.current.title ? translate('nextcloud-vue', 'Redact: {title}', { title: this.current.title }) : translate('nextcloud-vue', 'Redact documents')
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/** @return {Array<{id: string, label: string}>} The passage types. */
|
|
116
|
+
typeOptions() {
|
|
117
|
+
return Object.keys(this.config.typeLabels || {}).map((type) => ({ id: type, label: this.labelOf(type) }))
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/** @return {string} The open document's grounds. */
|
|
121
|
+
grounds() {
|
|
122
|
+
const list = this.config.groundsPath ? readPath(this.current, this.config.groundsPath) : null;
|
|
123
|
+
return Array.isArray(list) ? list.join(', ') : ''
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/** @return {Array<object>} The entities to redact: agreed or adjusted, with text. */
|
|
127
|
+
toRedact() {
|
|
128
|
+
return this.passages
|
|
129
|
+
.filter((p) => p.verdict === 'accept' || p.verdict === 'adjust')
|
|
130
|
+
.map((p) => ({ ...p.entity, value: p.value.trim() }))
|
|
131
|
+
.filter((entity) => entity.value !== '')
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Whether the redaction may be recorded: every passage judged, at least one to redact.
|
|
136
|
+
*
|
|
137
|
+
* @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents
|
|
138
|
+
* @return {boolean}
|
|
139
|
+
*/
|
|
140
|
+
canRecord() {
|
|
141
|
+
return !this.busy && !this.detecting && !this.current.redactedDocumentRef
|
|
142
|
+
&& this.passages.length > 0
|
|
143
|
+
&& this.passages.every((p) => p.verdict !== null)
|
|
144
|
+
&& this.toRedact.length > 0
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/** @return {Array<{text: string, verdict: (string|null)}>} The text with the passages marked. */
|
|
148
|
+
segments() {
|
|
149
|
+
return redactionSegments(this.documentText, this.passages)
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
mounted() {
|
|
154
|
+
this.load();
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
methods: {
|
|
158
|
+
t: translate,
|
|
159
|
+
n: translatePlural,
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A config text through the host translate.
|
|
163
|
+
*
|
|
164
|
+
* @param {unknown} value The text.
|
|
165
|
+
* @return {string}
|
|
166
|
+
*/
|
|
167
|
+
text(value) {
|
|
168
|
+
return typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* A configured url with the record and the open document filled in.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} key The `urls` key.
|
|
175
|
+
* @return {string}
|
|
176
|
+
*/
|
|
177
|
+
urlOf(key) {
|
|
178
|
+
const template = String((this.config.urls || {})[key] || '');
|
|
179
|
+
return generateUrl(template
|
|
180
|
+
.replace('{recordId}', encodeURIComponent(String(this.recordId)))
|
|
181
|
+
.replace('{documentRef}', encodeURIComponent(String(this.current.documentRef || ''))))
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* What a passage type is called.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} type The type.
|
|
188
|
+
* @return {string}
|
|
189
|
+
*/
|
|
190
|
+
labelOf(type) {
|
|
191
|
+
const label = (this.config.typeLabels || {})[type];
|
|
192
|
+
return label ? this.cnTranslate(String(label)) : String(type || '')
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* What a verdict is called.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} verdict The verdict.
|
|
199
|
+
* @return {string}
|
|
200
|
+
*/
|
|
201
|
+
verdictLabel(verdict) {
|
|
202
|
+
return ({ accept: translate('nextcloud-vue', 'Agreed'), adjust: translate('nextcloud-vue', 'Adjust'), skip: translate('nextcloud-vue', 'Do not redact') })[verdict] || ''
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Read the documents and open the first.
|
|
207
|
+
*
|
|
208
|
+
* @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents
|
|
209
|
+
* @return {Promise<void>}
|
|
210
|
+
*/
|
|
211
|
+
async load() {
|
|
212
|
+
this.loading = true;
|
|
213
|
+
this.loadError = '';
|
|
214
|
+
try {
|
|
215
|
+
const { data } = await axios__default.get(this.urlOf('list'));
|
|
216
|
+
this.documents = Array.isArray(data && data.documents) ? data.documents : [];
|
|
217
|
+
} catch (failure) {
|
|
218
|
+
this.documents = [];
|
|
219
|
+
this.loadError = sentenceOf(failure, translate('nextcloud-vue', 'The documents could not be read.'));
|
|
220
|
+
} finally {
|
|
221
|
+
this.loading = false;
|
|
222
|
+
}
|
|
223
|
+
if (this.documents.length > 0) {
|
|
224
|
+
await this.open(Math.min(this.index, this.documents.length - 1));
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Open one document: its recorded redaction, or the passages detected in it.
|
|
230
|
+
*
|
|
231
|
+
* @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents
|
|
232
|
+
* @param {number} at The document's position.
|
|
233
|
+
* @return {Promise<void>}
|
|
234
|
+
*/
|
|
235
|
+
async open(at) {
|
|
236
|
+
this.index = at;
|
|
237
|
+
this.error = '';
|
|
238
|
+
this.notice = '';
|
|
239
|
+
this.documentText = '';
|
|
240
|
+
this.passages = [];
|
|
241
|
+
if (this.current.redactedDocumentRef) {
|
|
242
|
+
this.passages = this.passagesOf((this.current.redaction && this.current.redaction.entities) || [], 'accept');
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
this.detecting = true;
|
|
246
|
+
try {
|
|
247
|
+
const { data } = await axios__default.post(this.urlOf('detect'));
|
|
248
|
+
this.documentText = String((data && data.text) || '');
|
|
249
|
+
this.passages = this.passagesOf((data && data.redaction && data.redaction.entities) || [], null);
|
|
250
|
+
} catch (failure) {
|
|
251
|
+
this.error = sentenceOf(failure, translate('nextcloud-vue', 'The passages of this document could not be found. Upload a redacted version instead.'));
|
|
252
|
+
} finally {
|
|
253
|
+
this.detecting = false;
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Passages from entities; a locked entity starts agreed.
|
|
259
|
+
*
|
|
260
|
+
* @param {Array<object>} entities The entities.
|
|
261
|
+
* @param {string|null} verdict The verdict the others start with.
|
|
262
|
+
* @return {Array<object>}
|
|
263
|
+
*/
|
|
264
|
+
passagesOf(entities, verdict) {
|
|
265
|
+
return entities.map((entity) => ({
|
|
266
|
+
entity,
|
|
267
|
+
type: String((entity && entity.type) || ''),
|
|
268
|
+
value: String((entity && entity.value) || ''),
|
|
269
|
+
locked: Boolean(entity && entity.locked),
|
|
270
|
+
verdict: entity && entity.locked ? 'accept' : verdict,
|
|
271
|
+
}))
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Give a passage a verdict.
|
|
276
|
+
*
|
|
277
|
+
* @param {number} at The passage.
|
|
278
|
+
* @param {string} verdict The verdict.
|
|
279
|
+
*/
|
|
280
|
+
setVerdict(at, verdict) {
|
|
281
|
+
if (!this.passages[at].locked) {
|
|
282
|
+
this.passages[at] = { ...this.passages[at], verdict };
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Change an adjusted passage's text.
|
|
288
|
+
*
|
|
289
|
+
* @param {number} at The passage.
|
|
290
|
+
* @param {string} value The text.
|
|
291
|
+
*/
|
|
292
|
+
adjust(at, value) {
|
|
293
|
+
this.passages[at] = { ...this.passages[at], value: String(value ?? '') };
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Give a hand-added passage its type.
|
|
298
|
+
*
|
|
299
|
+
* @param {number} at The passage.
|
|
300
|
+
* @param {{id: string}|null} option The type.
|
|
301
|
+
*/
|
|
302
|
+
retype(at, option) {
|
|
303
|
+
const type = String((option && option.id) || 'UNKNOWN');
|
|
304
|
+
const passage = this.passages[at];
|
|
305
|
+
this.passages[at] = { ...passage, type, entity: { ...passage.entity, type } };
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
/** Add a passage the detection missed. */
|
|
309
|
+
addPassage() {
|
|
310
|
+
this.passages.push({ entity: { type: 'UNKNOWN', source: 'officer' }, type: 'UNKNOWN', value: '', locked: false, verdict: 'adjust' });
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Record the reviewer's check of the passages, then ask for the redacted copy.
|
|
315
|
+
*
|
|
316
|
+
* @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents
|
|
317
|
+
* @return {Promise<void>}
|
|
318
|
+
*/
|
|
319
|
+
async record() {
|
|
320
|
+
if (!this.canRecord) {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
this.busy = true;
|
|
324
|
+
this.error = '';
|
|
325
|
+
this.notice = '';
|
|
326
|
+
try {
|
|
327
|
+
await axios__default.post(this.urlOf('check'), { entities: this.toRedact });
|
|
328
|
+
const { data } = await axios__default.post(this.urlOf('redact'));
|
|
329
|
+
this.recorded(data);
|
|
330
|
+
} catch (failure) {
|
|
331
|
+
this.error = sentenceOf(failure, translate('nextcloud-vue', 'The redacted copy could not be made. Upload a redacted version instead.'));
|
|
332
|
+
} finally {
|
|
333
|
+
this.busy = false;
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Upload a version redacted elsewhere.
|
|
339
|
+
*
|
|
340
|
+
* @param {FileList|null} files The chosen file.
|
|
341
|
+
* @return {Promise<void>}
|
|
342
|
+
*/
|
|
343
|
+
async upload(files) {
|
|
344
|
+
const file = files && files[0];
|
|
345
|
+
if (!file) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
const form = new FormData();
|
|
349
|
+
form.append('file', file);
|
|
350
|
+
this.busy = true;
|
|
351
|
+
this.error = '';
|
|
352
|
+
this.notice = '';
|
|
353
|
+
try {
|
|
354
|
+
const { data } = await axios__default.post(this.urlOf('upload'), form);
|
|
355
|
+
this.recorded(data);
|
|
356
|
+
} catch (failure) {
|
|
357
|
+
this.error = sentenceOf(failure, translate('nextcloud-vue', 'The redacted version could not be stored. Try again.'));
|
|
358
|
+
} finally {
|
|
359
|
+
this.busy = false;
|
|
360
|
+
if (this.$refs.upload) {
|
|
361
|
+
this.$refs.upload.value = '';
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Show the recorded copy and refresh the page.
|
|
368
|
+
*
|
|
369
|
+
* @param {object} data The server's answer.
|
|
370
|
+
*/
|
|
371
|
+
recorded(data) {
|
|
372
|
+
this.documents[this.index] = { ...this.current, redactedDocumentRef: String((data && data.redactedDocumentRef) || ''), redaction: (data && data.redaction) || {} };
|
|
373
|
+
this.notice = translate('nextcloud-vue', 'The redacted copy is recorded. Only this copy will be published.');
|
|
374
|
+
emit(PAGE_REFRESH, { reason: 'redaction' });
|
|
375
|
+
/** @event recorded A redacted copy was recorded. Payload: `{ documentRef, redactedDocumentRef }`. */
|
|
376
|
+
this.$emit('recorded', { documentRef: this.current.documentRef, redactedDocumentRef: this.current.redactedDocumentRef });
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export { script as default };
|
|
382
|
+
//# sourceMappingURL=CnRedactionEditor.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnRedactionEditor.vue2.js","sources":["../../../../src/components/CnRedactionEditor/CnRedactionEditor.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<section class=\"cn-redaction\" data-testid=\"cn-redaction-editor\">\n\t\t<header class=\"cn-redaction__head\">\n\t\t\t<div>\n\t\t\t\t<h1 class=\"cn-redaction__title\">\n\t\t\t\t\t{{ heading }}\n\t\t\t\t</h1>\n\t\t\t\t<!-- @slot Under the title: a breadcrumb back to the record. -->\n\t\t\t\t<slot name=\"crumbs\" />\n\t\t\t</div>\n\t\t\t<div v-if=\"documents.length > 0\" class=\"cn-redaction__buttons\">\n\t\t\t\t<NcButton data-testid=\"cn-redaction-previous\" :disabled=\"index === 0 || busy\" @click=\"open(index - 1)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Previous document') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton data-testid=\"cn-redaction-next\" :disabled=\"index >= documents.length - 1 || busy\" @click=\"open(index + 1)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Next document') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-redaction-record\"\n\t\t\t\t\t:disabled=\"!canRecord\"\n\t\t\t\t\t@click=\"record\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Record redaction') }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<p v-if=\"loadError\"\n\t\t\tclass=\"cn-redaction__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-redaction-load-error\">\n\t\t\t{{ loadError }}\n\t\t</p>\n\t\t<p v-else-if=\"loading\" role=\"status\">\n\t\t\t{{ t('nextcloud-vue', 'Loading…') }}\n\t\t</p>\n\t\t<p v-else-if=\"documents.length === 0\"\n\t\t\trole=\"status\"\n\t\t\tclass=\"cn-redaction__muted\"\n\t\t\tdata-testid=\"cn-redaction-empty\">\n\t\t\t{{ text(config.emptyText) || t('nextcloud-vue', 'No document here needs redacting.') }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<div class=\"cn-redaction__bar\">\n\t\t\t\t<span class=\"cn-redaction__muted\" data-testid=\"cn-redaction-position\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Document {index} of {count}', { index: index + 1, count: documents.length }) }}\n\t\t\t\t\t<span v-if=\"current.redactedDocumentRef\" class=\"cn-redaction__pill cn-redaction__pill--accept\" data-testid=\"cn-redaction-done\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Redacted copy recorded') }}\n\t\t\t\t\t</span>\n\t\t\t\t</span>\n\t\t\t\t<template v-if=\"config.urls && config.urls.upload\">\n\t\t\t\t\t<NcButton data-testid=\"cn-redaction-upload\" :disabled=\"busy\" @click=\"$refs.upload.click()\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Upload redacted version') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<input ref=\"upload\"\n\t\t\t\t\t\ttype=\"file\"\n\t\t\t\t\t\tclass=\"hidden-visually\"\n\t\t\t\t\t\tdata-testid=\"cn-redaction-upload-input\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Upload redacted version')\"\n\t\t\t\t\t\t@change=\"upload($event.target.files)\">\n\t\t\t\t</template>\n\t\t\t</div>\n\t\t\t<p v-if=\"error\"\n\t\t\t\tclass=\"cn-redaction__error\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-redaction-error\">\n\t\t\t\t{{ error }}\n\t\t\t</p>\n\t\t\t<p v-if=\"notice\" role=\"status\" data-testid=\"cn-redaction-notice\">\n\t\t\t\t{{ notice }}\n\t\t\t</p>\n\n\t\t\t<div class=\"cn-redaction__columns\">\n\t\t\t\t<div class=\"cn-redaction__document\" data-testid=\"cn-redaction-text\">\n\t\t\t\t\t<strong>{{ current.title || t('nextcloud-vue', 'Document without a title') }}</strong>\n\t\t\t\t\t<p v-if=\"detecting\" role=\"status\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Finding the passages to redact…') }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<p v-else-if=\"documentText === ''\" class=\"cn-redaction__muted\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'No text was extracted from this document.') }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<p v-else class=\"cn-redaction__body\">\n\t\t\t\t\t\t<template v-for=\"(segment, at) in segments\" :key=\"at\">\n\t\t\t\t\t\t\t<span v-if=\"segment.verdict\" :class=\"`cn-redaction__mark cn-redaction__mark--${segment.verdict}`\">{{ segment.text }}</span>\n\t\t\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t\t\t{{ segment.text }}\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\n\t\t\t\t<aside class=\"cn-redaction__proposal\">\n\t\t\t\t\t<h2>{{ t('nextcloud-vue', 'Proposal') }}</h2>\n\t\t\t\t\t<span class=\"cn-redaction__muted\" data-testid=\"cn-redaction-count\">\n\t\t\t\t\t\t{{ n('nextcloud-vue', '%n passage found. Review each one.', '%n passages found. Review each one.', passages.length) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<ul class=\"cn-redaction__list\">\n\t\t\t\t\t\t<li v-for=\"(passage, at) in passages\"\n\t\t\t\t\t\t\t:key=\"at\"\n\t\t\t\t\t\t\tclass=\"cn-redaction__passage\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-redaction-passage-${at}`\">\n\t\t\t\t\t\t\t<span class=\"cn-redaction__line\">\n\t\t\t\t\t\t\t\t<strong>{{ labelOf(passage.type) }}: \"{{ passage.value }}\"</strong>\n\t\t\t\t\t\t\t\t<span v-if=\"passage.verdict\" :class=\"`cn-redaction__pill cn-redaction__pill--${passage.verdict}`\">{{ verdictLabel(passage.verdict) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span v-if=\"passage.locked\" class=\"cn-redaction__muted\">{{ t('nextcloud-vue', 'Always redacted by rule') }}</span>\n\t\t\t\t\t\t\t<span v-else-if=\"grounds\" class=\"cn-redaction__muted\">{{ grounds }}</span>\n\t\t\t\t\t\t\t<div v-if=\"!passage.locked\"\n\t\t\t\t\t\t\t\tclass=\"cn-redaction__verdicts\"\n\t\t\t\t\t\t\t\trole=\"group\"\n\t\t\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Verdict for {passage}', { passage: passage.value })\">\n\t\t\t\t\t\t\t\t<NcButton v-for=\"verdict in VERDICTS\"\n\t\t\t\t\t\t\t\t\t:key=\"verdict\"\n\t\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\t\t:pressed=\"passage.verdict === verdict\"\n\t\t\t\t\t\t\t\t\t:data-testid=\"`cn-redaction-verdict-${at}-${verdict}`\"\n\t\t\t\t\t\t\t\t\t@update:pressed=\"setVerdict(at, verdict)\">\n\t\t\t\t\t\t\t\t\t{{ verdictLabel(verdict) }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<NcSelect v-if=\"passage.entity.source === 'officer'\"\n\t\t\t\t\t\t\t\t:modelValue=\"typeOptions.find((option) => option.id === passage.type) || null\"\n\t\t\t\t\t\t\t\t:options=\"typeOptions\"\n\t\t\t\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Passage type')\"\n\t\t\t\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\t\t\t\t:data-testid=\"`cn-redaction-type-${at}`\"\n\t\t\t\t\t\t\t\t@update:modelValue=\"retype(at, $event)\" />\n\t\t\t\t\t\t\t<NcTextField v-if=\"passage.verdict === 'adjust'\"\n\t\t\t\t\t\t\t\t:modelValue=\"passage.value\"\n\t\t\t\t\t\t\t\t:label=\"t('nextcloud-vue', 'Passage text')\"\n\t\t\t\t\t\t\t\t:data-testid=\"`cn-redaction-value-${at}`\"\n\t\t\t\t\t\t\t\t@update:modelValue=\"adjust(at, $event)\" />\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t\t<NcButton data-testid=\"cn-redaction-add\" :disabled=\"busy || detecting || Boolean(current.redactedDocumentRef)\" @click=\"addPassage\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Add passage') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</aside>\n\t\t\t</div>\n\t\t</template>\n\t</section>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { emit } from '@nextcloud/event-bus'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcSelect, NcTextField } from '@nextcloud/vue'\nimport { readPath } from '../../utils/readPath.js'\nimport { redactionSegments } from '../../utils/redactionSegments.js'\n\n/** The page refresh channel the built-in widgets listen on. */\nconst PAGE_REFRESH = 'cn:page:refresh'\n\n/** The verdicts a reviewer gives a passage. */\nconst VERDICTS = ['accept', 'adjust', 'skip']\n\n/**\n * The server's sentence for a failure, or the fallback.\n *\n * @param {unknown} failure The rejection.\n * @param {string} fallback What to say when the server said nothing.\n * @return {string}\n */\nfunction sentenceOf(failure, fallback) {\n\tconst data = failure && failure.response && failure.response.data\n\treturn String((data && (data.message || data.error)) || fallback)\n}\n\n/**\n * CnRedactionEditor — redact the documents of a record, one at a time\n * (board DqLakken): the document text with the passages marked in place,\n * and beside it the proposal, a verdict per passage (agreed, adjust, do not\n * redact), a passage the detection missed added by hand, and Record\n * redaction. It names no procedure: the routes and the passage type labels\n * come from `config`, so dossiq's Woo redaction and filinq's anonymisation\n * use the same editor.\n *\n * The check is the reviewer's: Record redaction is enabled only once every\n * passage has a verdict, and it first records the check of the passages to\n * redact, in the reviewer's name, and only then asks for the redacted copy.\n * A passage marked `locked` (a rule that always applies) starts agreed and\n * cannot be changed. A refusal is shown in the server's words and the\n * upload of a version redacted elsewhere stays offered.\n *\n * `config`:\n * - `urls`: `{ list, detect, check, redact, upload? }`, templates with\n * `{recordId}` and `{documentRef}`. `list` answers `{ documents: [{\n * documentRef, title, redactedDocumentRef?, redaction? }] }`; `detect`\n * answers `{ text, redaction: { entities } }`; `check` receives `{ entities\n * }`; `redact` and `upload` (a `file` form field) answer `{\n * redactedDocumentRef, redaction }`.\n * - `typeLabels`: what a passage type is called (`{ PERSON: \"Name\" }`).\n * - `groundsPath`: a document's grounds (a list) shown under each passage.\n * - `title`, `emptyText`: texts. All through the host translate.\n */\nexport default {\n\tname: 'CnRedactionEditor',\n\n\tcomponents: { NcButton, NcSelect, NcTextField },\n\n\tinject: {\n\t\t/** The host app's translate (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The record whose documents are redacted, for `{recordId}`. */\n\t\trecordId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The editor's configuration (see the component docs).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tconfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['recorded'],\n\n\tdata() {\n\t\treturn {\n\t\t\tVERDICTS,\n\t\t\tloading: true,\n\t\t\tloadError: '',\n\t\t\tdocuments: [],\n\t\t\tindex: 0,\n\t\t\tdocumentText: '',\n\t\t\tpassages: [],\n\t\t\tdetecting: false,\n\t\t\tbusy: false,\n\t\t\terror: '',\n\t\t\tnotice: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The open document. */\n\t\tcurrent() {\n\t\t\treturn this.documents[this.index] || {}\n\t\t},\n\n\t\t/** @return {string} The heading. */\n\t\theading() {\n\t\t\tconst own = this.text(this.config.title)\n\t\t\tif (own) {\n\t\t\t\treturn own\n\t\t\t}\n\t\t\treturn this.current.title ? t('nextcloud-vue', 'Redact: {title}', { title: this.current.title }) : t('nextcloud-vue', 'Redact documents')\n\t\t},\n\n\t\t/** @return {Array<{id: string, label: string}>} The passage types. */\n\t\ttypeOptions() {\n\t\t\treturn Object.keys(this.config.typeLabels || {}).map((type) => ({ id: type, label: this.labelOf(type) }))\n\t\t},\n\n\t\t/** @return {string} The open document's grounds. */\n\t\tgrounds() {\n\t\t\tconst list = this.config.groundsPath ? readPath(this.current, this.config.groundsPath) : null\n\t\t\treturn Array.isArray(list) ? list.join(', ') : ''\n\t\t},\n\n\t\t/** @return {Array<object>} The entities to redact: agreed or adjusted, with text. */\n\t\ttoRedact() {\n\t\t\treturn this.passages\n\t\t\t\t.filter((p) => p.verdict === 'accept' || p.verdict === 'adjust')\n\t\t\t\t.map((p) => ({ ...p.entity, value: p.value.trim() }))\n\t\t\t\t.filter((entity) => entity.value !== '')\n\t\t},\n\n\t\t/**\n\t\t * Whether the redaction may be recorded: every passage judged, at least one to redact.\n\t\t *\n\t\t * @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents\n\t\t * @return {boolean}\n\t\t */\n\t\tcanRecord() {\n\t\t\treturn !this.busy && !this.detecting && !this.current.redactedDocumentRef\n\t\t\t\t&& this.passages.length > 0\n\t\t\t\t&& this.passages.every((p) => p.verdict !== null)\n\t\t\t\t&& this.toRedact.length > 0\n\t\t},\n\n\t\t/** @return {Array<{text: string, verdict: (string|null)}>} The text with the passages marked. */\n\t\tsegments() {\n\t\t\treturn redactionSegments(this.documentText, this.passages)\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\t\tn,\n\n\t\t/**\n\t\t * A config text through the host translate.\n\t\t *\n\t\t * @param {unknown} value The text.\n\t\t * @return {string}\n\t\t */\n\t\ttext(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''\n\t\t},\n\n\t\t/**\n\t\t * A configured url with the record and the open document filled in.\n\t\t *\n\t\t * @param {string} key The `urls` key.\n\t\t * @return {string}\n\t\t */\n\t\turlOf(key) {\n\t\t\tconst template = String((this.config.urls || {})[key] || '')\n\t\t\treturn generateUrl(template\n\t\t\t\t.replace('{recordId}', encodeURIComponent(String(this.recordId)))\n\t\t\t\t.replace('{documentRef}', encodeURIComponent(String(this.current.documentRef || ''))))\n\t\t},\n\n\t\t/**\n\t\t * What a passage type is called.\n\t\t *\n\t\t * @param {string} type The type.\n\t\t * @return {string}\n\t\t */\n\t\tlabelOf(type) {\n\t\t\tconst label = (this.config.typeLabels || {})[type]\n\t\t\treturn label ? this.cnTranslate(String(label)) : String(type || '')\n\t\t},\n\n\t\t/**\n\t\t * What a verdict is called.\n\t\t *\n\t\t * @param {string} verdict The verdict.\n\t\t * @return {string}\n\t\t */\n\t\tverdictLabel(verdict) {\n\t\t\treturn ({ accept: t('nextcloud-vue', 'Agreed'), adjust: t('nextcloud-vue', 'Adjust'), skip: t('nextcloud-vue', 'Do not redact') })[verdict] || ''\n\t\t},\n\n\t\t/**\n\t\t * Read the documents and open the first.\n\t\t *\n\t\t * @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.loadError = ''\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(this.urlOf('list'))\n\t\t\t\tthis.documents = Array.isArray(data && data.documents) ? data.documents : []\n\t\t\t} catch (failure) {\n\t\t\t\tthis.documents = []\n\t\t\t\tthis.loadError = sentenceOf(failure, t('nextcloud-vue', 'The documents could not be read.'))\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t\tif (this.documents.length > 0) {\n\t\t\t\tawait this.open(Math.min(this.index, this.documents.length - 1))\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open one document: its recorded redaction, or the passages detected in it.\n\t\t *\n\t\t * @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents\n\t\t * @param {number} at The document's position.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync open(at) {\n\t\t\tthis.index = at\n\t\t\tthis.error = ''\n\t\t\tthis.notice = ''\n\t\t\tthis.documentText = ''\n\t\t\tthis.passages = []\n\t\t\tif (this.current.redactedDocumentRef) {\n\t\t\t\tthis.passages = this.passagesOf((this.current.redaction && this.current.redaction.entities) || [], 'accept')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.detecting = true\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.post(this.urlOf('detect'))\n\t\t\t\tthis.documentText = String((data && data.text) || '')\n\t\t\t\tthis.passages = this.passagesOf((data && data.redaction && data.redaction.entities) || [], null)\n\t\t\t} catch (failure) {\n\t\t\t\tthis.error = sentenceOf(failure, t('nextcloud-vue', 'The passages of this document could not be found. Upload a redacted version instead.'))\n\t\t\t} finally {\n\t\t\t\tthis.detecting = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Passages from entities; a locked entity starts agreed.\n\t\t *\n\t\t * @param {Array<object>} entities The entities.\n\t\t * @param {string|null} verdict The verdict the others start with.\n\t\t * @return {Array<object>}\n\t\t */\n\t\tpassagesOf(entities, verdict) {\n\t\t\treturn entities.map((entity) => ({\n\t\t\t\tentity,\n\t\t\t\ttype: String((entity && entity.type) || ''),\n\t\t\t\tvalue: String((entity && entity.value) || ''),\n\t\t\t\tlocked: Boolean(entity && entity.locked),\n\t\t\t\tverdict: entity && entity.locked ? 'accept' : verdict,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Give a passage a verdict.\n\t\t *\n\t\t * @param {number} at The passage.\n\t\t * @param {string} verdict The verdict.\n\t\t */\n\t\tsetVerdict(at, verdict) {\n\t\t\tif (!this.passages[at].locked) {\n\t\t\t\tthis.passages[at] = { ...this.passages[at], verdict }\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Change an adjusted passage's text.\n\t\t *\n\t\t * @param {number} at The passage.\n\t\t * @param {string} value The text.\n\t\t */\n\t\tadjust(at, value) {\n\t\t\tthis.passages[at] = { ...this.passages[at], value: String(value ?? '') }\n\t\t},\n\n\t\t/**\n\t\t * Give a hand-added passage its type.\n\t\t *\n\t\t * @param {number} at The passage.\n\t\t * @param {{id: string}|null} option The type.\n\t\t */\n\t\tretype(at, option) {\n\t\t\tconst type = String((option && option.id) || 'UNKNOWN')\n\t\t\tconst passage = this.passages[at]\n\t\t\tthis.passages[at] = { ...passage, type, entity: { ...passage.entity, type } }\n\t\t},\n\n\t\t/** Add a passage the detection missed. */\n\t\taddPassage() {\n\t\t\tthis.passages.push({ entity: { type: 'UNKNOWN', source: 'officer' }, type: 'UNKNOWN', value: '', locked: false, verdict: 'adjust' })\n\t\t},\n\n\t\t/**\n\t\t * Record the reviewer's check of the passages, then ask for the redacted copy.\n\t\t *\n\t\t * @spec openspec/changes/redaction-editor/specs/redaction-editor/spec.md#requirement-a-reviewer-redacts-a-records-documents\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync record() {\n\t\t\tif (!this.canRecord) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tawait axios.post(this.urlOf('check'), { entities: this.toRedact })\n\t\t\t\tconst { data } = await axios.post(this.urlOf('redact'))\n\t\t\t\tthis.recorded(data)\n\t\t\t} catch (failure) {\n\t\t\t\tthis.error = sentenceOf(failure, t('nextcloud-vue', 'The redacted copy could not be made. Upload a redacted version instead.'))\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Upload a version redacted elsewhere.\n\t\t *\n\t\t * @param {FileList|null} files The chosen file.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync upload(files) {\n\t\t\tconst file = files && files[0]\n\t\t\tif (!file) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst form = new FormData()\n\t\t\tform.append('file', file)\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\tthis.notice = ''\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.post(this.urlOf('upload'), form)\n\t\t\t\tthis.recorded(data)\n\t\t\t} catch (failure) {\n\t\t\t\tthis.error = sentenceOf(failure, t('nextcloud-vue', 'The redacted version could not be stored. Try again.'))\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t\tif (this.$refs.upload) {\n\t\t\t\t\tthis.$refs.upload.value = ''\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Show the recorded copy and refresh the page.\n\t\t *\n\t\t * @param {object} data The server's answer.\n\t\t */\n\t\trecorded(data) {\n\t\t\tthis.documents[this.index] = { ...this.current, redactedDocumentRef: String((data && data.redactedDocumentRef) || ''), redaction: (data && data.redaction) || {} }\n\t\t\tthis.notice = t('nextcloud-vue', 'The redacted copy is recorded. Only this copy will be published.')\n\t\t\temit(PAGE_REFRESH, { reason: 'redaction' })\n\t\t\t/** @event recorded A redacted copy was recorded. Payload: `{ documentRef, redactedDocumentRef }`. */\n\t\t\tthis.$emit('recorded', { documentRef: this.current.documentRef, redactedDocumentRef: this.current.redactedDocumentRef })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-redaction {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-redaction__head,\n.cn-redaction__bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n}\n\n.cn-redaction__title {\n\tmargin: 0;\n\tfont-size: 28px;\n\tfont-weight: 700;\n}\n\n.cn-redaction__buttons,\n.cn-redaction__verdicts {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n\n.cn-redaction__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-redaction__error {\n\tmargin: 0;\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-redaction__columns {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-start;\n\tgap: 20px;\n}\n\n.cn-redaction__document,\n.cn-redaction__proposal {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-redaction__document {\n\tflex: 999 1 520px;\n\tgap: 12px;\n\tpadding: 28px 32px;\n}\n\n.cn-redaction__proposal {\n\tflex: 1 1 300px;\n\tgap: 10px;\n\tpadding: 20px 22px;\n}\n\n.cn-redaction__body {\n\twhite-space: pre-wrap;\n\tline-height: 1.6;\n}\n\n.cn-redaction__mark--accept {\n\tbackground: var(--color-main-text);\n\tcolor: var(--color-main-text);\n}\n\n.cn-redaction__mark--adjust,\n.cn-redaction__mark--open {\n\toutline: 2px solid var(--color-warning-text, var(--color-warning));\n\tbackground: var(--color-background-hover);\n}\n\n.cn-redaction__mark--skip {\n\ttext-decoration: underline dotted;\n}\n\n.cn-redaction__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-redaction__passage {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-redaction__line {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-redaction__pill {\n\tdisplay: inline-block;\n\tpadding: 3px 10px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-dark);\n\tfont-size: 13px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n\n.cn-redaction__pill--accept {\n\tcolor: var(--color-success-text, var(--color-success));\n}\n\n.cn-redaction__pill--adjust {\n\tcolor: var(--color-warning-text, var(--color-warning));\n}\n</style>\n"],"names":["t","n","axios"],"mappings":"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"}
|