@conduction/nextcloud-vue 2.72.0 → 2.73.1
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/CnAppRoot/CnAppRoot.vue2.js +29 -0
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +18 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +9 -6
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +16 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +5 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +25 -0
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +3 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js +36 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue3.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +37 -0
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -2
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +16 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +53 -5
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +133 -0
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +22 -14
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +15 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +3 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue2.js +15 -1
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue3.js +2 -2
- package/dist/esm/components/CnWidgetGrid/CnWidgetGrid.vue3.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/notificationRuleLabel.js +132 -0
- package/dist/esm/utils/notificationRuleLabel.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +578 -47
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/package.json +1 -1
- package/scripts/openregister-reliance.js +206 -0
- package/src/components/CnAppRoot/CnAppRoot.md +1 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +29 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +24 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +22 -4
- package/src/components/CnFlowDetail/CnFlowDetail.vue +27 -0
- package/src/components/CnGraphCanvas/CnFlowNode.vue +36 -1
- package/src/components/CnGraphCanvas/CnGraphCanvas.vue +40 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +17 -2
- package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +54 -6
- package/src/components/CnPageRenderer/CnPageRenderer.vue +147 -6
- package/src/components/CnTabsWidget/CnTabsWidget.vue +17 -2
- package/src/components/CnWidgetGrid/CnWidgetGrid.vue +19 -3
- package/src/index.js +2 -0
- package/src/utils/notificationRuleLabel.js +129 -0
- package/webpack/index.d.ts +16 -0
- package/webpack/index.js +70 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/nextcloud-vue.cjs.js.map
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35731122 bytes
|
|
4
|
+
sha256: 2f00e1767fdb1c72a109ba42b159ca43fb36fbd6a93a33087f4c8529da77b80b
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.73.1",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scores every component under src/components on how much it relies on
|
|
8
|
+
* OpenRegister and writes the result to docs/components/_generated/reliance.json
|
|
9
|
+
* for the documentation site.
|
|
10
|
+
*
|
|
11
|
+
* Two numbers per component:
|
|
12
|
+
* - direct: lines inside the component's own directory that reference
|
|
13
|
+
* OpenRegister (store imports, API paths, composables, the register and
|
|
14
|
+
* schema selector).
|
|
15
|
+
* - transitive: files outside the directory, reached by following the actual
|
|
16
|
+
* import statements, that carry such a reference. A wrapper with no
|
|
17
|
+
* reference of its own still counts the children it cannot render without,
|
|
18
|
+
* while importing the widget registry alone does not drag in every widget.
|
|
19
|
+
*
|
|
20
|
+
* The tier follows the sum: none (0), light (1 to 3), medium (4 to 10) and
|
|
21
|
+
* heavy (11 and more). A component with tier none does not touch OpenRegister
|
|
22
|
+
* itself nor through anything it imports.
|
|
23
|
+
*
|
|
24
|
+
* Usage: node scripts/openregister-reliance.js
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs')
|
|
28
|
+
const path = require('path')
|
|
29
|
+
|
|
30
|
+
/** Patterns that mark a direct OpenRegister reliance; each hit counts once per line. */
|
|
31
|
+
const DIRECT_MARKERS = [
|
|
32
|
+
/\/apps\/openregister/,
|
|
33
|
+
/\bopenregister\b/i,
|
|
34
|
+
/useObjectStore|useRegisterStore|useSchemaStore|createObjectStore|createCrudStore/,
|
|
35
|
+
/from '[^']*\/store(\/index\.js)?'/,
|
|
36
|
+
/from '[^']*\/store\/[^']+'/,
|
|
37
|
+
/objectService|ObjectService/,
|
|
38
|
+
/CnRegisterSchemaSelect/,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const SOURCE_FILE = /\.(vue|js|ts|mjs)$/
|
|
42
|
+
const TEST_FILE = /\.(spec|test)\./
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} dir directory to walk
|
|
46
|
+
* @return {string[]} source files below it, tests excluded
|
|
47
|
+
*/
|
|
48
|
+
function walk(dir) {
|
|
49
|
+
const files = []
|
|
50
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
51
|
+
const full = path.join(dir, entry)
|
|
52
|
+
if (fs.statSync(full).isDirectory()) {
|
|
53
|
+
if (entry !== 'node_modules' && entry !== '__tests__') {
|
|
54
|
+
files.push(...walk(full))
|
|
55
|
+
}
|
|
56
|
+
} else if (SOURCE_FILE.test(entry) && !TEST_FILE.test(entry)) {
|
|
57
|
+
files.push(full)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return files
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} source file contents
|
|
65
|
+
* @return {number} lines with a direct marker, comments excluded
|
|
66
|
+
*/
|
|
67
|
+
function countDirect(source) {
|
|
68
|
+
let count = 0
|
|
69
|
+
for (const line of source.split('\n')) {
|
|
70
|
+
const trimmed = line.trimStart()
|
|
71
|
+
if (trimmed.startsWith('*') || trimmed.startsWith('//') || trimmed.startsWith('/*')) {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
if (DIRECT_MARKERS.some((marker) => marker.test(line))) {
|
|
75
|
+
count++
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return count
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolves a relative import to a file on disk the way the bundler does.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} fromFile importing file
|
|
85
|
+
* @param {string} specifier the relative import path
|
|
86
|
+
* @return {string|null}
|
|
87
|
+
*/
|
|
88
|
+
function resolveImport(fromFile, specifier) {
|
|
89
|
+
const base = path.resolve(path.dirname(fromFile), specifier)
|
|
90
|
+
const candidates = [base, base + '.js', base + '.mjs', base + '.ts', base + '.vue', path.join(base, 'index.js'), path.join(base, 'index.mjs'), path.join(base, 'index.ts')]
|
|
91
|
+
return candidates.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile()) ?? null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} source file contents
|
|
96
|
+
* @return {string[]} relative import specifiers, static and dynamic
|
|
97
|
+
*/
|
|
98
|
+
function relativeImports(source) {
|
|
99
|
+
return [...source.matchAll(/(?:from\s+|import\s*\(\s*|import\s+)'(\.{1,2}\/[^']+)'/g)].map((match) => match[1])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {number} score direct lines plus transitive files
|
|
104
|
+
* @return {'none'|'light'|'medium'|'heavy'}
|
|
105
|
+
*/
|
|
106
|
+
function tierOf(score) {
|
|
107
|
+
if (score === 0) {
|
|
108
|
+
return 'none'
|
|
109
|
+
}
|
|
110
|
+
if (score <= 3) {
|
|
111
|
+
return 'light'
|
|
112
|
+
}
|
|
113
|
+
if (score <= 10) {
|
|
114
|
+
return 'medium'
|
|
115
|
+
}
|
|
116
|
+
return 'heavy'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} componentName e.g. CnDeleteDialog
|
|
121
|
+
* @return {string} the docs page slug, e.g. cn-delete-dialog
|
|
122
|
+
*/
|
|
123
|
+
function pageSlug(componentName) {
|
|
124
|
+
return componentName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {string} srcDir the src directory holding components/
|
|
129
|
+
* @param {string|null} docsComponentsDir where the component pages live, to link only existing pages
|
|
130
|
+
* @return {Record<string, {direct: number, transitive: number, tier: string, via: string[], page: string|null}>}
|
|
131
|
+
*/
|
|
132
|
+
function computeReliance(srcDir, docsComponentsDir = null) {
|
|
133
|
+
const componentsDir = path.join(srcDir, 'components')
|
|
134
|
+
const files = new Map()
|
|
135
|
+
for (const file of walk(srcDir)) {
|
|
136
|
+
const source = fs.readFileSync(file, 'utf8')
|
|
137
|
+
files.set(file, {
|
|
138
|
+
direct: countDirect(source),
|
|
139
|
+
imports: relativeImports(source).map((specifier) => resolveImport(file, specifier)).filter(Boolean),
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const reachable = (startFiles) => {
|
|
144
|
+
const own = new Set(startFiles)
|
|
145
|
+
const seen = new Set()
|
|
146
|
+
const queue = [...startFiles]
|
|
147
|
+
while (queue.length > 0) {
|
|
148
|
+
const file = queue.pop()
|
|
149
|
+
for (const next of (files.get(file) ?? { imports: [] }).imports) {
|
|
150
|
+
if (!own.has(next) && !seen.has(next)) {
|
|
151
|
+
seen.add(next)
|
|
152
|
+
queue.push(next)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [...seen]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result = {}
|
|
160
|
+
for (const entry of fs.readdirSync(componentsDir).sort()) {
|
|
161
|
+
const dir = path.join(componentsDir, entry)
|
|
162
|
+
if (!fs.statSync(dir).isDirectory() || !/^Cn[A-Z]/.test(entry)) {
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
const ownFiles = walk(dir)
|
|
166
|
+
const direct = ownFiles.reduce((sum, file) => sum + (files.get(file)?.direct ?? 0), 0)
|
|
167
|
+
const relyingFiles = reachable(ownFiles).filter((file) => (files.get(file)?.direct ?? 0) > 0)
|
|
168
|
+
const via = new Set(relyingFiles
|
|
169
|
+
.filter((file) => file.startsWith(componentsDir + path.sep))
|
|
170
|
+
.map((file) => file.slice(componentsDir.length + 1).split(path.sep)[0])
|
|
171
|
+
.filter((name) => name !== entry))
|
|
172
|
+
const slug = pageSlug(entry)
|
|
173
|
+
const hasPage = docsComponentsDir !== null && fs.existsSync(path.join(docsComponentsDir, slug + '.md'))
|
|
174
|
+
result[entry] = {
|
|
175
|
+
direct,
|
|
176
|
+
transitive: relyingFiles.length,
|
|
177
|
+
tier: tierOf(direct + relyingFiles.length),
|
|
178
|
+
via: [...via].sort(),
|
|
179
|
+
page: hasPage ? slug : null,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return result
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {string} repoRoot the repository root
|
|
187
|
+
* @return {{outFile: string, components: object}}
|
|
188
|
+
*/
|
|
189
|
+
function writeReliance(repoRoot) {
|
|
190
|
+
const components = computeReliance(path.join(repoRoot, 'src'), path.join(repoRoot, 'docs/components'))
|
|
191
|
+
const outFile = path.join(repoRoot, 'docs/components/_generated/reliance.json')
|
|
192
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true })
|
|
193
|
+
fs.writeFileSync(outFile, JSON.stringify({ generatedBy: 'scripts/openregister-reliance.js', components }, null, '\t') + '\n')
|
|
194
|
+
return { outFile, components }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { computeReliance, countDirect, tierOf, pageSlug, writeReliance }
|
|
198
|
+
|
|
199
|
+
if (require.main === module) {
|
|
200
|
+
const { outFile, components } = writeReliance(path.resolve(__dirname, '..'))
|
|
201
|
+
const tiers = {}
|
|
202
|
+
for (const item of Object.values(components)) {
|
|
203
|
+
tiers[item.tier] = (tiers[item.tier] ?? 0) + 1
|
|
204
|
+
}
|
|
205
|
+
console.log(`wrote ${path.relative(process.cwd(), outFile)}: ${Object.keys(components).length} components`, tiers)
|
|
206
|
+
}
|
|
@@ -78,6 +78,7 @@ With loading state, custom components, translate, permissions, and custom page t
|
|
|
78
78
|
| `cellWidgets` | Object | No | `{}` | Cell-widget registry — map of widget-id → Vue component, rendered for a column that declares `pages[].config.columns[].widget`. The component receives `{ value, row, property, formatted, ...widgetProps }`. The library ships one built-in id, `"badge"` (renders `CnStatusBadge`); consumer entries cover everything else (status pills with custom colour maps, inline toggles, link cells, …). Provided to descendant `CnDataTable` / `CnCellRenderer` as `cnCellWidgets`. See `docs/migrating-to-manifest.md` → "Column widgets". |
|
|
79
79
|
| `kbSearchProviders` | Object | No | `{}` | Pluggable knowledge-base search providers (#91 Wave 3) — map of provider-key → `{ search(query, opts), externalOpen? }`, merged OVER the library built-in `default` (endpoint) provider and provided to descendant `CnKbSearchWidget` as `cnKbSearchProviders`. A `kb-search` widget selects its provider via `content.provider`; an app talking to a bespoke KB backend (the xwiki proxy) registers its client here — the library ships only the seam. |
|
|
80
80
|
| `translate` | Function | No | `key => key` | Translate function from the consuming app, typically `(key) => t(appId, key)`. Provided to descendants as `cnTranslate` |
|
|
81
|
+
| `notificationLabels` | Object | No | `{}` | Labels for the app's notification rules in the user-settings notification pane, keyed `<schema>.<key>` or `<key>`; values are translated strings or `{ nl, en }` maps. A rule without a label reads as its key split into words ("Case assigned"), never as the raw key. Provided to descendants as `cnNotificationLabels` |
|
|
81
82
|
| `permissions` | Array | No | `[]` | Permission strings for the current user; forwarded to `CnAppNav` for menu filtering |
|
|
82
83
|
| `pageTypes` | Object | No | `null` | Page-type registry map (`type → component`). Falls back to the library's `defaultPageTypes`. Extend with `{ ...defaultPageTypes, report: MyReportPage }` |
|
|
83
84
|
| `userSettingsTitle` | String | No | `''` | Title shown at the top of the hosted `NcAppSettingsDialog`. Empty (the default) resolves to `translate('User settings')`. Override per app to brand the modal (e.g. `'Decidiq preferences'`). |
|
|
@@ -996,6 +996,18 @@ export default {
|
|
|
996
996
|
* wiring.
|
|
997
997
|
*/
|
|
998
998
|
cnAppId: this.appId,
|
|
999
|
+
/**
|
|
1000
|
+
* The app's labels for its notification rules, read by the
|
|
1001
|
+
* notification-preferences pane in the user-settings modal. A
|
|
1002
|
+
* getter so a late prop update is still seen by a pane that
|
|
1003
|
+
* mounts after it.
|
|
1004
|
+
*
|
|
1005
|
+
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
|
|
1006
|
+
*/
|
|
1007
|
+
get cnNotificationLabels() {
|
|
1008
|
+
return self.notificationLabels || {}
|
|
1009
|
+
},
|
|
1010
|
+
|
|
999
1011
|
/**
|
|
1000
1012
|
* Target repo slug for the in-product feature-request deep
|
|
1001
1013
|
* link (e.g. `ConductionNL/pipelinq`). Read from the
|
|
@@ -1411,6 +1423,23 @@ export default {
|
|
|
1411
1423
|
default: (key) => key,
|
|
1412
1424
|
},
|
|
1413
1425
|
|
|
1426
|
+
/**
|
|
1427
|
+
* Labels for the app's notification rules, shown in the
|
|
1428
|
+
* user-settings notification pane instead of the rule keys. Keyed
|
|
1429
|
+
* `<schema>.<key>` (or just `<key>`); values are already translated
|
|
1430
|
+
* strings, e.g. `{ 'case.caseAssigned': t('dossiq', 'A case is
|
|
1431
|
+
* assigned to me') }`, or per-locale maps `{ nl, en }`. A rule's own
|
|
1432
|
+
* `label` from OpenRegister wins; a rule with no label anywhere reads
|
|
1433
|
+
* as its key split into words, never as the raw key.
|
|
1434
|
+
*
|
|
1435
|
+
* @type {{[key: string]: (string|object)}}
|
|
1436
|
+
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
|
|
1437
|
+
*/
|
|
1438
|
+
notificationLabels: {
|
|
1439
|
+
type: Object,
|
|
1440
|
+
default: () => ({}),
|
|
1441
|
+
},
|
|
1442
|
+
|
|
1414
1443
|
/**
|
|
1415
1444
|
* List of permission strings the current user holds. Forwarded
|
|
1416
1445
|
* to CnAppNav's permission filter.
|
|
@@ -454,7 +454,8 @@
|
|
|
454
454
|
:cnRegistry="cnRegistry"
|
|
455
455
|
:availableWidgets="widgets"
|
|
456
456
|
@geoSaved="onGeoSaved"
|
|
457
|
-
@openIntegration="onAutoBodyOpenIntegration"
|
|
457
|
+
@openIntegration="onAutoBodyOpenIntegration"
|
|
458
|
+
@selectObject="onRelatedObjectSelect" />
|
|
458
459
|
</slot>
|
|
459
460
|
</div>
|
|
460
461
|
</div>
|
|
@@ -764,7 +765,8 @@
|
|
|
764
765
|
:cnRegistry="cnRegistry"
|
|
765
766
|
:availableWidgets="bodyGridWidgets"
|
|
766
767
|
@geoSaved="onGeoSaved"
|
|
767
|
-
@openIntegration="onAutoBodyOpenIntegration"
|
|
768
|
+
@openIntegration="onAutoBodyOpenIntegration"
|
|
769
|
+
@selectObject="onRelatedObjectSelect" />
|
|
768
770
|
</slot>
|
|
769
771
|
</div>
|
|
770
772
|
</template>
|
|
@@ -975,7 +977,8 @@
|
|
|
975
977
|
:cnRegistry="cnRegistry"
|
|
976
978
|
:availableWidgets="widgets"
|
|
977
979
|
@geoSaved="onGeoSaved"
|
|
978
|
-
@openIntegration="onAutoBodyOpenIntegration"
|
|
980
|
+
@openIntegration="onAutoBodyOpenIntegration"
|
|
981
|
+
@selectObject="onRelatedObjectSelect" />
|
|
979
982
|
</slot>
|
|
980
983
|
</div>
|
|
981
984
|
</aside>
|
|
@@ -2334,6 +2337,7 @@ export default {
|
|
|
2334
2337
|
'layout-change',
|
|
2335
2338
|
'open-integration',
|
|
2336
2339
|
'refresh',
|
|
2340
|
+
'related-object-click',
|
|
2337
2341
|
'related-row-click',
|
|
2338
2342
|
'relation-linked',
|
|
2339
2343
|
'request-feature',
|
|
@@ -4315,6 +4319,23 @@ export default {
|
|
|
4315
4319
|
this.$emit('open-integration', integrationId)
|
|
4316
4320
|
},
|
|
4317
4321
|
|
|
4322
|
+
/**
|
|
4323
|
+
* Re-emit a click on an object in the Related widget. The page does not
|
|
4324
|
+
* know the routes; the host (CnPageRenderer on a manifest page) opens
|
|
4325
|
+
* the object's own detail page.
|
|
4326
|
+
*
|
|
4327
|
+
* @param {object} raw The clicked object; `@self` names its register and schema.
|
|
4328
|
+
*/
|
|
4329
|
+
onRelatedObjectSelect(raw) {
|
|
4330
|
+
/**
|
|
4331
|
+
* @event related-object-click An object in the Related widget was
|
|
4332
|
+
* clicked. Payload is the raw object; `@self` carries its register,
|
|
4333
|
+
* schema and id.
|
|
4334
|
+
* @type {object}
|
|
4335
|
+
*/
|
|
4336
|
+
this.$emit('related-object-click', raw)
|
|
4337
|
+
},
|
|
4338
|
+
|
|
4318
4339
|
/**
|
|
4319
4340
|
* Re-fetch the object after the geo widget persists `@self.geo` (so the
|
|
4320
4341
|
* rest of the page — metadata, other widgets — reflects the new value),
|
|
@@ -114,7 +114,8 @@
|
|
|
114
114
|
:includeGroups="content.groups || []"
|
|
115
115
|
:hideSingleTabTitle="content.hideSingleTabTitle !== false"
|
|
116
116
|
:showTotalCount="content.showTotalCount !== false"
|
|
117
|
-
@openIntegration="onOpenIntegration"
|
|
117
|
+
@openIntegration="onOpenIntegration"
|
|
118
|
+
@selectObject="onSelectObject" />
|
|
118
119
|
|
|
119
120
|
<!-- `type: 'object-geo'` — view/edit the object's `@self.geo` on a map. -->
|
|
120
121
|
<CnObjectGeoWidget
|
|
@@ -183,7 +184,8 @@
|
|
|
183
184
|
<component
|
|
184
185
|
:is="renderer"
|
|
185
186
|
ref="renderer"
|
|
186
|
-
v-bind="rendererProps"
|
|
187
|
+
v-bind="rendererProps"
|
|
188
|
+
@selectObject="onSelectObject" />
|
|
187
189
|
</CnWidgetWrapper>
|
|
188
190
|
|
|
189
191
|
<!-- Registry "card" widgets (stat / gauge / delta) render bare tile
|
|
@@ -218,7 +220,8 @@
|
|
|
218
220
|
:is="renderer"
|
|
219
221
|
v-else-if="renderer"
|
|
220
222
|
ref="renderer"
|
|
221
|
-
v-bind="rendererProps"
|
|
223
|
+
v-bind="rendererProps"
|
|
224
|
+
@selectObject="onSelectObject" />
|
|
222
225
|
|
|
223
226
|
<!-- No renderer resolves: render nothing, which is what CnDetailPage has
|
|
224
227
|
always done here. Surfacing an "unknown widget" box instead would be
|
|
@@ -501,7 +504,7 @@ export default {
|
|
|
501
504
|
},
|
|
502
505
|
},
|
|
503
506
|
|
|
504
|
-
emits: ['geo-saved', 'open-integration'],
|
|
507
|
+
emits: ['geo-saved', 'open-integration', 'select-object'],
|
|
505
508
|
|
|
506
509
|
setup() {
|
|
507
510
|
// Every integration component this host renders goes through the
|
|
@@ -1006,6 +1009,21 @@ export default {
|
|
|
1006
1009
|
this.$emit('open-integration', integrationId)
|
|
1007
1010
|
},
|
|
1008
1011
|
|
|
1012
|
+
/**
|
|
1013
|
+
* Pass a related-object click up; the page decides where it opens. Heard
|
|
1014
|
+
* from the Related widget itself and from a registry widget that nests a
|
|
1015
|
+
* host of its own (a Tabs widget with a Related tab).
|
|
1016
|
+
*
|
|
1017
|
+
* @param {object} raw The clicked object as the related widget holds it.
|
|
1018
|
+
*/
|
|
1019
|
+
onSelectObject(raw) {
|
|
1020
|
+
/**
|
|
1021
|
+
* @event select-object A related object was clicked in the related widget. Payload is the raw object; its `@self` names the register and schema.
|
|
1022
|
+
* @type {object}
|
|
1023
|
+
*/
|
|
1024
|
+
this.$emit('select-object', raw)
|
|
1025
|
+
},
|
|
1026
|
+
|
|
1009
1027
|
/**
|
|
1010
1028
|
* Delegate to the rendered list widget's own create affordance, so the
|
|
1011
1029
|
* card's Add action and the widget's footer button open the same form.
|
|
@@ -200,6 +200,8 @@
|
|
|
200
200
|
:maxZoom="maxZoom"
|
|
201
201
|
@zoomChange="zoom = $event"
|
|
202
202
|
@nodeSelect="onNodeSelect"
|
|
203
|
+
@nodeActivate="onNodeActivate"
|
|
204
|
+
@nodeMenu="onNodeMenu"
|
|
203
205
|
@edgeSelect="onEdgeSelect"
|
|
204
206
|
@edgeLabelClick="onEdgeLabelClick"
|
|
205
207
|
@edgeLabelContext="onEdgeLabelContext"
|
|
@@ -1713,6 +1715,31 @@ export default {
|
|
|
1713
1715
|
this.openNodeMenu({ item: id, event: mouse })
|
|
1714
1716
|
},
|
|
1715
1717
|
|
|
1718
|
+
/**
|
|
1719
|
+
* Enter or Space on a step: open its editor, as a double-click does.
|
|
1720
|
+
*
|
|
1721
|
+
* @param {string} id The step id.
|
|
1722
|
+
* @return {void}
|
|
1723
|
+
*/
|
|
1724
|
+
onNodeActivate(id) {
|
|
1725
|
+
this.store.selectedNodeId = id
|
|
1726
|
+
this.store.editingNodeId = id
|
|
1727
|
+
},
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
* Shift+F10 or the menu key on a step: its menu, at the step.
|
|
1731
|
+
*
|
|
1732
|
+
* @param {object} request The node's request.
|
|
1733
|
+
* @param {string} request.id The step id.
|
|
1734
|
+
* @param {number} request.clientX Where to place the menu.
|
|
1735
|
+
* @param {number} request.clientY Where to place the menu.
|
|
1736
|
+
* @return {void}
|
|
1737
|
+
*/
|
|
1738
|
+
onNodeMenu({ id, clientX, clientY }) {
|
|
1739
|
+
this.store.selectedNodeId = id
|
|
1740
|
+
this.openNodeMenu({ item: id, event: { clientX, clientY } })
|
|
1741
|
+
},
|
|
1742
|
+
|
|
1716
1743
|
/**
|
|
1717
1744
|
* Place a palette node where it was dropped.
|
|
1718
1745
|
*
|
|
@@ -224,6 +224,19 @@ export default {
|
|
|
224
224
|
* edges, its undo stack, its persistence — so nothing is removed here.
|
|
225
225
|
*/
|
|
226
226
|
'remove',
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @event activate Enter or Space on the focused node: open this step.
|
|
230
|
+
* Carries the node id. The pointer equivalent is a double-click.
|
|
231
|
+
*/
|
|
232
|
+
'activate',
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @event menu Shift+F10 or the menu key on the focused node: show this
|
|
236
|
+
* step's actions. Carries `{ id, clientX, clientY }`, a point at the
|
|
237
|
+
* node, so a host positions the menu as it would for a click.
|
|
238
|
+
*/
|
|
239
|
+
'menu',
|
|
227
240
|
],
|
|
228
241
|
|
|
229
242
|
setup() {
|
|
@@ -478,7 +491,8 @@ export default {
|
|
|
478
491
|
* The keyboard contract.
|
|
479
492
|
*
|
|
480
493
|
* Arrow keys move; `c` arms an exit and, on a second node, completes the
|
|
481
|
-
* connection; `Escape` cancels.
|
|
494
|
+
* connection; `Escape` cancels. Enter or Space opens the step, Shift+F10
|
|
495
|
+
* or the menu key opens its actions. Repeated `c` on the SAME node steps
|
|
482
496
|
* through its exits rather than re-arming the first — the behaviour a
|
|
483
497
|
* mouse gets for free by pointing.
|
|
484
498
|
*
|
|
@@ -527,6 +541,27 @@ export default {
|
|
|
527
541
|
return
|
|
528
542
|
}
|
|
529
543
|
|
|
544
|
+
// Opening the step, and its menu, from the keyboard. Only for a key
|
|
545
|
+
// pressed ON the node: a port inside it is a control of its own.
|
|
546
|
+
if (event.target === event.currentTarget) {
|
|
547
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
548
|
+
event.preventDefault()
|
|
549
|
+
this.$emit('activate', this.id)
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if ((event.key === 'F10' && event.shiftKey === true) || event.key === 'ContextMenu') {
|
|
554
|
+
event.preventDefault()
|
|
555
|
+
const rect = event.currentTarget.getBoundingClientRect()
|
|
556
|
+
this.$emit('menu', {
|
|
557
|
+
id: this.id,
|
|
558
|
+
clientX: rect.left + rect.width / 2,
|
|
559
|
+
clientY: rect.bottom,
|
|
560
|
+
})
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
530
565
|
// Delete and Backspace both, because which one removes a thing is a
|
|
531
566
|
// platform habit rather than a preference: Backspace is the delete
|
|
532
567
|
// key on a Mac keyboard, and a user who learned one does not think
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
v-bind="nodeProps"
|
|
38
38
|
:deletable="!readOnly"
|
|
39
39
|
@connect="onConnect"
|
|
40
|
-
@remove="onNodeRemove"
|
|
40
|
+
@remove="onNodeRemove"
|
|
41
|
+
@activate="onNodeActivate"
|
|
42
|
+
@menu="onNodeMenu">
|
|
41
43
|
<template #default="slotProps">
|
|
42
44
|
<!-- @slot node The body of a step, rendered inside the
|
|
43
45
|
focusable node wrapper. Receives `{ node }` with the
|
|
@@ -266,6 +268,8 @@ export default {
|
|
|
266
268
|
'canvas-drop',
|
|
267
269
|
|
|
268
270
|
'node-remove',
|
|
271
|
+
'node-activate',
|
|
272
|
+
'node-menu',
|
|
269
273
|
|
|
270
274
|
/**
|
|
271
275
|
* The canvas zoom changed, by any route — a control here, the host's own
|
|
@@ -415,6 +419,41 @@ export default {
|
|
|
415
419
|
return port === 'in' ? null : port
|
|
416
420
|
},
|
|
417
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Forward Enter or Space on a node: the keyboard double-click.
|
|
424
|
+
*
|
|
425
|
+
* @param {string} id The node id.
|
|
426
|
+
* @return {void}
|
|
427
|
+
*/
|
|
428
|
+
onNodeActivate(id) {
|
|
429
|
+
if (this.readOnly === true) {
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* @event node-activate Enter or Space on a node. Payload is the id.
|
|
435
|
+
*/
|
|
436
|
+
this.$emit('node-activate', id)
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Forward Shift+F10 or the menu key on a node: the keyboard click menu.
|
|
441
|
+
*
|
|
442
|
+
* @param {object} request `{ id, clientX, clientY }`, a point at the node.
|
|
443
|
+
* @return {void}
|
|
444
|
+
*/
|
|
445
|
+
onNodeMenu(request) {
|
|
446
|
+
if (this.readOnly === true) {
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* @event node-menu Shift+F10 or the menu key on a node. Payload is
|
|
452
|
+
* `{ id, clientX, clientY }`.
|
|
453
|
+
*/
|
|
454
|
+
this.$emit('node-menu', request)
|
|
455
|
+
},
|
|
456
|
+
|
|
418
457
|
/**
|
|
419
458
|
* Forward a node click.
|
|
420
459
|
*
|
|
@@ -731,7 +731,7 @@
|
|
|
731
731
|
:currentPageSize="effectivePagination.limit || 20"
|
|
732
732
|
class="cn-index-page__pagination"
|
|
733
733
|
@pageChanged="onPageEvent"
|
|
734
|
-
@pageSizeChanged="
|
|
734
|
+
@pageSizeChanged="onPageSizeEvent" />
|
|
735
735
|
</div>
|
|
736
736
|
|
|
737
737
|
<!-- The open record, beside the list on a wide screen and instead
|
|
@@ -1057,7 +1057,7 @@ function namedFilterToQuery(values) {
|
|
|
1057
1057
|
* @event {object} row-aux-click — Table row or card middle-clicked. Payload: (row, event) — the row object and the native auxclick event
|
|
1058
1058
|
* @event {{ key: string, order: string }} sort — Column sort changed
|
|
1059
1059
|
* @event {number} page-changed — Pagination page changed
|
|
1060
|
-
* @event {number} page-size-changed — Pagination page size changed
|
|
1060
|
+
* @event {number} page-size-changed — Pagination page size changed. In self-fetch mode the list refetches page 1 at the new size first.
|
|
1061
1061
|
* @event {string[]} select — Selection changed. Payload: array of selected IDs
|
|
1062
1062
|
* @event {object} action — Row action triggered. Payload: { action, row }
|
|
1063
1063
|
* @event {{ action: string, id: string, selectedIds: Array, count: number }} bulk-action — A declarative bulk action was triggered from the selection strip. The SELECTION travels with it: an action that has to go and find out what was selected is one re-render away from acting on a different set than the user saw highlighted.
|
|
@@ -5482,6 +5482,21 @@ export default {
|
|
|
5482
5482
|
this.$emit('page-changed', page)
|
|
5483
5483
|
},
|
|
5484
5484
|
|
|
5485
|
+
/**
|
|
5486
|
+
* Page-size pick from CnPagination. In self-fetch mode the list refetches
|
|
5487
|
+
* page 1 at the new size — the event alone left the select changing and
|
|
5488
|
+
* the table not, since a manifest page has no host listening.
|
|
5489
|
+
*
|
|
5490
|
+
* @param {number} size Requested page size.
|
|
5491
|
+
* @return {void}
|
|
5492
|
+
*/
|
|
5493
|
+
onPageSizeEvent(size) {
|
|
5494
|
+
if (this.isSelfFetchMode && typeof this.list.onPageSizeChange === 'function') {
|
|
5495
|
+
this.list.onPageSizeChange(size)
|
|
5496
|
+
}
|
|
5497
|
+
this.$emit('page-size-changed', size)
|
|
5498
|
+
},
|
|
5499
|
+
|
|
5485
5500
|
/**
|
|
5486
5501
|
* @param {{key: string, values: Array}} payload Facet-filter change from the sidebar.
|
|
5487
5502
|
* @return {void}
|