@conduction/nextcloud-vue 2.108.0 → 2.110.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/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +115 -12
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +36 -9
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- 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 +253 -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 +510 -76
- package/dist/esm/utils/actionsDispatcher.js +11 -3
- 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 +17560 -12844
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +510 -76
- package/l10n/en.json +106 -1
- package/l10n/nl.json +105 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +134 -13
- 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 +463 -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 +10 -2
- 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,343 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div class="cn-address-search" data-testid="cn-address-search">
|
|
7
|
+
<CnSearchSelect
|
|
8
|
+
:modelValue="selectedOption ? selectedOption.id : null"
|
|
9
|
+
:options="shownOptions"
|
|
10
|
+
:inputLabel="inputLabel || t('nextcloud-vue', 'Search an address')"
|
|
11
|
+
:labelOutside="labelOutside"
|
|
12
|
+
:placeholder="placeholder || t('nextcloud-vue', 'Street, postcode or place')"
|
|
13
|
+
:loading="searching"
|
|
14
|
+
:filterable="false"
|
|
15
|
+
:clearable="clearable"
|
|
16
|
+
:disabled="disabled"
|
|
17
|
+
@search="onSearch"
|
|
18
|
+
@update:modelValue="onPick" />
|
|
19
|
+
<p v-if="unavailable"
|
|
20
|
+
class="cn-address-search__note"
|
|
21
|
+
role="status"
|
|
22
|
+
data-testid="cn-address-search-unavailable">
|
|
23
|
+
{{ t('nextcloud-vue', 'Address search is not available right now.') }}
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
import axios from '@nextcloud/axios'
|
|
30
|
+
import { t } from '@nextcloud/l10n'
|
|
31
|
+
import { generateUrl } from '@nextcloud/router'
|
|
32
|
+
import { prefixUrl } from '../../utils/headers.js'
|
|
33
|
+
import { CnSearchSelect } from '../CnSearchSelect/index.js'
|
|
34
|
+
|
|
35
|
+
/** How long typing rests before a search runs, in ms. */
|
|
36
|
+
const SEARCH_DELAY = 250
|
|
37
|
+
|
|
38
|
+
/** The id the current value carries in the option list. */
|
|
39
|
+
const CURRENT_ID = '__cn-address-current'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* WGS84 coordinates from a Locatieserver document: a normalised
|
|
43
|
+
* `location.coordinates` (`[lng, lat]`) or a raw `centroide_ll` WKT point.
|
|
44
|
+
*
|
|
45
|
+
* @param {object|null} doc The document.
|
|
46
|
+
* @return {{lat: number, lng: number}|null} The coordinates, or null.
|
|
47
|
+
*/
|
|
48
|
+
export function addressCoordinates(doc) {
|
|
49
|
+
if (!doc) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(doc.location?.coordinates)) {
|
|
53
|
+
const [lng, lat] = doc.location.coordinates
|
|
54
|
+
return { lat, lng }
|
|
55
|
+
}
|
|
56
|
+
const match = typeof doc.centroide_ll === 'string' ? doc.centroide_ll.match(/POINT\(([^ ]+) ([^ ]+)\)/) : null
|
|
57
|
+
return match ? { lng: parseFloat(match[1]), lat: parseFloat(match[2]) } : null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A Locatieserver document's display text.
|
|
62
|
+
*
|
|
63
|
+
* @param {object|null} doc The document.
|
|
64
|
+
* @return {string} The address text.
|
|
65
|
+
*/
|
|
66
|
+
export function addressText(doc) {
|
|
67
|
+
return doc ? doc.displayName || doc.weergavenaam || doc.display || '' : ''
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* CnAddressSearch — an address field over the PDOK Locatieserver (BAG
|
|
72
|
+
* addresses, streets, places and postcodes): suggestions as the user types,
|
|
73
|
+
* a free-text search when nothing is suggested, and on a pick the full
|
|
74
|
+
* document with its coordinates. Built on CnSearchSelect.
|
|
75
|
+
*
|
|
76
|
+
* Browsers never call api.pdok.nl directly (ADR-022): requests go to a
|
|
77
|
+
* PDOK proxy with `suggest`, `free` and `lookup/{id}` routes. By default
|
|
78
|
+
* that is the `/api/pdok` route of the first installed app among
|
|
79
|
+
* `appCandidates` (integriq, then openconnector); `endpoint` names another
|
|
80
|
+
* base URL. When the proxy answers 404 or 503 the field says address search
|
|
81
|
+
* is unavailable and stays usable, so a form around it can still be saved.
|
|
82
|
+
*
|
|
83
|
+
* `select` emits `{ result, coordinates, address }`: the looked-up document,
|
|
84
|
+
* `{ lat, lng }` or null, and the display text. `update:modelValue` emits
|
|
85
|
+
* the display text, or null when cleared.
|
|
86
|
+
*
|
|
87
|
+
* ```vue
|
|
88
|
+
* <CnAddressSearch v-model="caseObject.address" @select="onAddress" />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export default {
|
|
92
|
+
name: 'CnAddressSearch',
|
|
93
|
+
|
|
94
|
+
components: { CnSearchSelect },
|
|
95
|
+
|
|
96
|
+
props: {
|
|
97
|
+
/** The chosen address text. */
|
|
98
|
+
modelValue: {
|
|
99
|
+
type: String,
|
|
100
|
+
default: null,
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/** Base URL of the PDOK proxy; empty resolves it from `appCandidates`. */
|
|
104
|
+
endpoint: {
|
|
105
|
+
type: String,
|
|
106
|
+
default: '',
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/** App ids that serve `/api/pdok`, newest first; the first installed one wins. */
|
|
110
|
+
appCandidates: {
|
|
111
|
+
type: Array,
|
|
112
|
+
default: () => ['integriq', 'openconnector'],
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/** The field's label; defaults to "Search an address". */
|
|
116
|
+
inputLabel: {
|
|
117
|
+
type: String,
|
|
118
|
+
default: '',
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/** Draw the label above the field. */
|
|
122
|
+
labelOutside: {
|
|
123
|
+
type: Boolean,
|
|
124
|
+
default: false,
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/** Placeholder; defaults to "Street, postcode or place". */
|
|
128
|
+
placeholder: {
|
|
129
|
+
type: String,
|
|
130
|
+
default: '',
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/** Offer a clear button. */
|
|
134
|
+
clearable: {
|
|
135
|
+
type: Boolean,
|
|
136
|
+
default: true,
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/** Disable the field. */
|
|
140
|
+
disabled: {
|
|
141
|
+
type: Boolean,
|
|
142
|
+
default: false,
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/** The fewest characters a search needs. */
|
|
146
|
+
minLength: {
|
|
147
|
+
type: Number,
|
|
148
|
+
default: 3,
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/** The most results a free-text search returns. */
|
|
152
|
+
rows: {
|
|
153
|
+
type: Number,
|
|
154
|
+
default: 10,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
emits: ['update:modelValue', 'select'],
|
|
159
|
+
|
|
160
|
+
data() {
|
|
161
|
+
return {
|
|
162
|
+
options: [],
|
|
163
|
+
searching: false,
|
|
164
|
+
unavailable: false,
|
|
165
|
+
timer: null,
|
|
166
|
+
searchSeq: 0,
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
computed: {
|
|
171
|
+
/**
|
|
172
|
+
* The current value as an option, so the field shows it.
|
|
173
|
+
*
|
|
174
|
+
* @return {object|null}
|
|
175
|
+
*/
|
|
176
|
+
selectedOption() {
|
|
177
|
+
return this.modelValue ? { id: CURRENT_ID, label: this.modelValue, icon: 'MapMarkerOutline' } : null
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The suggestions, with the current value kept in the list.
|
|
182
|
+
*
|
|
183
|
+
* @return {Array<object>}
|
|
184
|
+
*/
|
|
185
|
+
shownOptions() {
|
|
186
|
+
return this.selectedOption ? [this.selectedOption, ...this.options] : this.options
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The proxy's base URL.
|
|
191
|
+
*
|
|
192
|
+
* @spec openspec/changes/address-search/specs/address-search/spec.md#requirement-an-address-field-over-the-pdok-locatieserver
|
|
193
|
+
* @return {string}
|
|
194
|
+
*/
|
|
195
|
+
baseUrl() {
|
|
196
|
+
if (this.endpoint) {
|
|
197
|
+
return this.endpoint.replace(/\/+$/, '')
|
|
198
|
+
}
|
|
199
|
+
const roots = (typeof window !== 'undefined' && window.OC && window.OC.appswebroots) || {}
|
|
200
|
+
const app = this.appCandidates.find((id) => Object.hasOwn(roots, id)) || this.appCandidates[0] || 'integriq'
|
|
201
|
+
return generateUrl(`/apps/${app}/api/pdok`)
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
beforeUnmount() {
|
|
206
|
+
clearTimeout(this.timer)
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
methods: {
|
|
210
|
+
t,
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* A document as a picker option.
|
|
214
|
+
*
|
|
215
|
+
* @param {object} doc The Locatieserver document.
|
|
216
|
+
* @param {number} index Its place in the list, for documents without an id.
|
|
217
|
+
* @return {object|null} The option, or null without a text.
|
|
218
|
+
*/
|
|
219
|
+
optionOf(doc, index) {
|
|
220
|
+
const label = addressText(doc)
|
|
221
|
+
if (!label) {
|
|
222
|
+
return null
|
|
223
|
+
}
|
|
224
|
+
const kinds = {
|
|
225
|
+
adres: t('nextcloud-vue', 'Address'),
|
|
226
|
+
address: t('nextcloud-vue', 'Address'),
|
|
227
|
+
weg: t('nextcloud-vue', 'Street'),
|
|
228
|
+
woonplaats: t('nextcloud-vue', 'Place'),
|
|
229
|
+
city: t('nextcloud-vue', 'Place'),
|
|
230
|
+
postcode: t('nextcloud-vue', 'Postcode'),
|
|
231
|
+
}
|
|
232
|
+
const municipality = doc.gemeentenaam && !label.includes(doc.gemeentenaam) ? doc.gemeentenaam : ''
|
|
233
|
+
return {
|
|
234
|
+
id: doc.id || `${CURRENT_ID}-${index}`,
|
|
235
|
+
label,
|
|
236
|
+
secondary: [kinds[doc.type] || '', municipality].filter(Boolean).join(' · '),
|
|
237
|
+
icon: 'MapMarkerOutline',
|
|
238
|
+
doc,
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Call the proxy; 404 or 503 marks search unavailable and yields `fallback`.
|
|
244
|
+
*
|
|
245
|
+
* @param {string} path The route under the base URL.
|
|
246
|
+
* @param {object} params The query parameters.
|
|
247
|
+
* @param {Array<object>} fallback What a degraded answer yields.
|
|
248
|
+
* @return {Promise<Array<object>>} The documents.
|
|
249
|
+
*/
|
|
250
|
+
async request(path, params, fallback) {
|
|
251
|
+
try {
|
|
252
|
+
const { data } = await axios.get(prefixUrl(`${this.baseUrl}/${path}`), { params })
|
|
253
|
+
this.unavailable = false
|
|
254
|
+
return Array.isArray(data?.docs) ? data.docs : fallback
|
|
255
|
+
} catch (error) {
|
|
256
|
+
const status = error?.response?.status
|
|
257
|
+
if (status === 404 || status === 503) {
|
|
258
|
+
this.unavailable = true
|
|
259
|
+
}
|
|
260
|
+
return fallback
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Search after typing rests.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} query The typed text.
|
|
268
|
+
*/
|
|
269
|
+
onSearch(query) {
|
|
270
|
+
clearTimeout(this.timer)
|
|
271
|
+
const term = String(query || '').trim()
|
|
272
|
+
if (term.length < this.minLength) {
|
|
273
|
+
this.options = []
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
this.timer = setTimeout(() => this.search(term), SEARCH_DELAY)
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Suggest addresses for the term; without suggestions, search free text.
|
|
281
|
+
*
|
|
282
|
+
* @spec openspec/changes/address-search/specs/address-search/spec.md#requirement-an-address-field-over-the-pdok-locatieserver
|
|
283
|
+
* @param {string} term The search term.
|
|
284
|
+
* @return {Promise<void>}
|
|
285
|
+
*/
|
|
286
|
+
async search(term) {
|
|
287
|
+
const seq = ++this.searchSeq
|
|
288
|
+
this.searching = true
|
|
289
|
+
let docs = await this.request('suggest', { q: term }, [])
|
|
290
|
+
if (!docs.length && !this.unavailable && seq === this.searchSeq) {
|
|
291
|
+
docs = await this.request('free', { q: term, rows: this.rows }, [])
|
|
292
|
+
}
|
|
293
|
+
if (seq !== this.searchSeq) {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
this.options = docs.map((doc, index) => this.optionOf(doc, index)).filter(Boolean)
|
|
297
|
+
this.searching = false
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Take the picked suggestion: look up the full document, then emit it.
|
|
302
|
+
*
|
|
303
|
+
* @spec openspec/changes/address-search/specs/address-search/spec.md#requirement-an-address-field-over-the-pdok-locatieserver
|
|
304
|
+
* @param {string|null} id The picked option's id.
|
|
305
|
+
* @return {Promise<void>}
|
|
306
|
+
*/
|
|
307
|
+
async onPick(id) {
|
|
308
|
+
if (id === CURRENT_ID) {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
const option = id === null ? null : this.options.find((o) => String(o.id) === String(id))
|
|
312
|
+
if (!option) {
|
|
313
|
+
/** @event update:modelValue The chosen address text, or null when cleared. */
|
|
314
|
+
this.$emit('update:modelValue', null)
|
|
315
|
+
/** @event select `{ result, coordinates, address }` for the chosen address, or null when cleared. */
|
|
316
|
+
this.$emit('select', null)
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
let result = option.doc
|
|
320
|
+
if (option.doc.id) {
|
|
321
|
+
const [full] = await this.request(`lookup/${encodeURIComponent(option.doc.id)}`, {}, [])
|
|
322
|
+
result = full || option.doc
|
|
323
|
+
}
|
|
324
|
+
const address = addressText(result) || option.label
|
|
325
|
+
this.options = []
|
|
326
|
+
this.$emit('update:modelValue', address)
|
|
327
|
+
this.$emit('select', { result, coordinates: addressCoordinates(result), address })
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
}
|
|
331
|
+
</script>
|
|
332
|
+
|
|
333
|
+
<style scoped>
|
|
334
|
+
.cn-address-search {
|
|
335
|
+
width: 100%;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.cn-address-search__note {
|
|
339
|
+
margin: 4px 0 0;
|
|
340
|
+
color: var(--color-text-maxcontrast);
|
|
341
|
+
font-size: var(--font-size-small, 13px);
|
|
342
|
+
}
|
|
343
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* CnAddressSearch — an address field over the PDOK Locatieserver, through
|
|
6
|
+
* a PDOK proxy app, that emits the chosen address with its coordinates.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import CnAddressSearch from './CnAddressSearch.vue'
|
|
10
|
+
|
|
11
|
+
export { CnAddressSearch }
|
|
12
|
+
export default CnAddressSearch
|