@conduction/nextcloud-vue 2.71.0 → 2.73.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/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/CnFlowDetail/CnRunDetailSidebar.vue2.js +2 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js +7 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js +10 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js +150 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js +54 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js.map +1 -0
- 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/dialogs/CnFlowNodeEditModal.vue2.js +7 -10
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js +23 -13
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js +8 -6
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue3.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +15 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +15 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +64 -0
- package/dist/esm/utils/selectOptions.js +57 -0
- package/dist/esm/utils/selectOptions.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +6050 -5649
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +64 -0
- package/l10n/en.json +15 -1
- package/l10n/nl.json +15 -1
- package/package.json +1 -1
- package/scripts/openregister-reliance.js +206 -0
- package/src/components/CnFlowDetail/CnFlowDetail.vue +27 -0
- package/src/components/CnFlowDetail/CnRunDetailSidebar.vue +6 -0
- package/src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue +250 -0
- package/src/components/CnFlowStepOutcomes/index.js +8 -0
- package/src/components/CnGraphCanvas/CnFlowNode.vue +36 -1
- package/src/components/CnGraphCanvas/CnGraphCanvas.vue +40 -1
- package/src/components/index.js +1 -0
- package/src/dialogs/CnFlowNodeEditModal.vue +41 -20
- package/src/dialogs/CnRunNodeDialog.vue +8 -6
- package/src/index.js +1 -0
- package/src/utils/selectOptions.js +54 -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: 35692438 bytes
|
|
4
|
+
sha256: 9054c705b23062b8dca68969cf561ce87cf95a1f2c6dea28e8e5cb78a2ca437c
|
package/dist/nextcloud-vue.css
CHANGED
|
@@ -26582,6 +26582,18 @@ a.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name[data-v-75628
|
|
|
26582
26582
|
font-size: 0.85em;
|
|
26583
26583
|
color: var(--color-text-maxcontrast);
|
|
26584
26584
|
}
|
|
26585
|
+
|
|
26586
|
+
/* Full width, like the text fields around it: at its natural width the
|
|
26587
|
+
picker cut every option label in half. */
|
|
26588
|
+
.cn-flow-node-edit__select[data-v-e107484a] {
|
|
26589
|
+
width: 100%;
|
|
26590
|
+
}
|
|
26591
|
+
.cn-flow-node-edit__field-help[data-v-e107484a] {
|
|
26592
|
+
margin: 4px 0 0;
|
|
26593
|
+
padding-inline-start: 8px;
|
|
26594
|
+
font-size: 0.85em;
|
|
26595
|
+
color: var(--color-text-maxcontrast);
|
|
26596
|
+
}
|
|
26585
26597
|
.cn-flow-node-edit__advanced summary[data-v-e107484a] {
|
|
26586
26598
|
cursor: pointer;
|
|
26587
26599
|
color: var(--color-text-maxcontrast);
|
|
@@ -28036,6 +28048,58 @@ to {
|
|
|
28036
28048
|
}
|
|
28037
28049
|
|
|
28038
28050
|
|
|
28051
|
+
.cn-flow-step-outcomes[data-v-5ac5ad4f] {
|
|
28052
|
+
margin: 4px 0 0;
|
|
28053
|
+
font-size: 0.9em;
|
|
28054
|
+
}
|
|
28055
|
+
.cn-flow-step-outcomes__list[data-v-5ac5ad4f] {
|
|
28056
|
+
margin: 0;
|
|
28057
|
+
padding: 0;
|
|
28058
|
+
}
|
|
28059
|
+
|
|
28060
|
+
/* Stacked, name over value: the run sidebar is narrow, and a side-by-side
|
|
28061
|
+
row wrapped mid-list. The host's dt/dd margins and alignment are reset. */
|
|
28062
|
+
.cn-flow-step-outcomes__row[data-v-5ac5ad4f] {
|
|
28063
|
+
display: block;
|
|
28064
|
+
margin-block: 4px;
|
|
28065
|
+
padding-block: 2px;
|
|
28066
|
+
padding-inline-start: 8px;
|
|
28067
|
+
border-inline-start: 3px solid var(--color-border);
|
|
28068
|
+
text-align: start;
|
|
28069
|
+
}
|
|
28070
|
+
.cn-flow-step-outcomes__row--success[data-v-5ac5ad4f] {
|
|
28071
|
+
border-inline-start-color: var(--color-success);
|
|
28072
|
+
}
|
|
28073
|
+
.cn-flow-step-outcomes__row--warning[data-v-5ac5ad4f] {
|
|
28074
|
+
border-inline-start-color: var(--color-warning);
|
|
28075
|
+
}
|
|
28076
|
+
.cn-flow-step-outcomes__row--error[data-v-5ac5ad4f] {
|
|
28077
|
+
border-inline-start-color: var(--color-error);
|
|
28078
|
+
}
|
|
28079
|
+
.cn-flow-step-outcomes__name[data-v-5ac5ad4f] {
|
|
28080
|
+
display: block;
|
|
28081
|
+
white-space: normal;
|
|
28082
|
+
overflow-wrap: anywhere;
|
|
28083
|
+
margin: 0;
|
|
28084
|
+
padding: 0;
|
|
28085
|
+
font-weight: bold;
|
|
28086
|
+
text-align: start;
|
|
28087
|
+
}
|
|
28088
|
+
.cn-flow-step-outcomes__value[data-v-5ac5ad4f] {
|
|
28089
|
+
display: block;
|
|
28090
|
+
white-space: normal;
|
|
28091
|
+
margin: 0;
|
|
28092
|
+
padding: 0;
|
|
28093
|
+
text-align: start;
|
|
28094
|
+
color: var(--color-main-text);
|
|
28095
|
+
overflow-wrap: anywhere;
|
|
28096
|
+
}
|
|
28097
|
+
.cn-flow-step-outcomes__truncated[data-v-5ac5ad4f] {
|
|
28098
|
+
margin: 4px 0 0;
|
|
28099
|
+
color: var(--color-text-maxcontrast);
|
|
28100
|
+
}
|
|
28101
|
+
|
|
28102
|
+
|
|
28039
28103
|
.cn-run-sidebar[data-v-48e781f0] {
|
|
28040
28104
|
display: flex;
|
|
28041
28105
|
flex-direction: column;
|
package/l10n/en.json
CHANGED
|
@@ -1106,7 +1106,21 @@
|
|
|
1106
1106
|
"Loading…": "Loading…",
|
|
1107
1107
|
"A register and schema are required": "A register and schema are required",
|
|
1108
1108
|
"Change the search or try again.": "Change the search or try again.",
|
|
1109
|
-
"Try again later.": "Try again later."
|
|
1109
|
+
"Try again later.": "Try again later.",
|
|
1110
|
+
"Who this step reached": "Who this step reached",
|
|
1111
|
+
"Each list shows its first entries only.": "Each list shows its first entries only.",
|
|
1112
|
+
"Delivered": "Delivered",
|
|
1113
|
+
"Opted out": "Opted out",
|
|
1114
|
+
"Not sent, the opt-out check did not answer": "Not sent, the opt-out check did not answer",
|
|
1115
|
+
"Skipped by their notification settings": "Skipped by their notification settings",
|
|
1116
|
+
"Skipped, sending is switched off": "Skipped, sending is switched off",
|
|
1117
|
+
"Held back by the send limit": "Held back by the send limit",
|
|
1118
|
+
"Refused by the step’s address rule": "Refused by the step’s address rule",
|
|
1119
|
+
"Not a known user or group": "Not a known user or group",
|
|
1120
|
+
"Failed": "Failed",
|
|
1121
|
+
"external recipients are off": "external recipients are off",
|
|
1122
|
+
"not on the item": "not on the item",
|
|
1123
|
+
"not a valid address": "not a valid address"
|
|
1110
1124
|
},
|
|
1111
1125
|
"plurals": {
|
|
1112
1126
|
"%n year": ["%n year", "%n years"],
|
package/l10n/nl.json
CHANGED
|
@@ -1150,7 +1150,21 @@
|
|
|
1150
1150
|
"Loading…": "Laden…",
|
|
1151
1151
|
"A register and schema are required": "Een register en schema zijn verplicht",
|
|
1152
1152
|
"Change the search or try again.": "Pas de zoekopdracht aan of probeer het opnieuw.",
|
|
1153
|
-
"Try again later.": "Probeer het later opnieuw."
|
|
1153
|
+
"Try again later.": "Probeer het later opnieuw.",
|
|
1154
|
+
"Who this step reached": "Wie deze stap bereikte",
|
|
1155
|
+
"Each list shows its first entries only.": "Elke lijst toont alleen de eerste vermeldingen.",
|
|
1156
|
+
"Delivered": "Bezorgd",
|
|
1157
|
+
"Opted out": "Afgemeld",
|
|
1158
|
+
"Not sent, the opt-out check did not answer": "Niet verstuurd, de afmeldcontrole gaf geen antwoord",
|
|
1159
|
+
"Skipped by their notification settings": "Overgeslagen door hun meldingsinstellingen",
|
|
1160
|
+
"Skipped, sending is switched off": "Overgeslagen, verzenden staat uit",
|
|
1161
|
+
"Held back by the send limit": "Tegengehouden door de verzendlimiet",
|
|
1162
|
+
"Refused by the step’s address rule": "Geweigerd door de adresregel van de stap",
|
|
1163
|
+
"Not a known user or group": "Geen bekende gebruiker of groep",
|
|
1164
|
+
"Failed": "Mislukt",
|
|
1165
|
+
"external recipients are off": "externe ontvangers staan uit",
|
|
1166
|
+
"not on the item": "staat niet op het item",
|
|
1167
|
+
"not a valid address": "geen geldig adres"
|
|
1154
1168
|
},
|
|
1155
1169
|
"plurals": {
|
|
1156
1170
|
"%n year": ["%n jaar", "%n jaar"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.73.0",
|
|
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
|
+
}
|
|
@@ -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
|
*
|
|
@@ -226,6 +226,10 @@
|
|
|
226
226
|
<strong>{{ step.transition }}</strong>
|
|
227
227
|
<span class="cn-run-sidebar__hint"> · {{ step.status }}</span>
|
|
228
228
|
<span v-if="step.error" class="cn-run-sidebar__error"> · {{ step.error }}</span>
|
|
229
|
+
<!-- A send step's outcome lists, by name: who got it, who
|
|
230
|
+
opted out, who the opt-out check could not answer for. -->
|
|
231
|
+
<CnFlowStepOutcomes v-if="step.report && step.report.messaging"
|
|
232
|
+
:report="step.report.messaging" />
|
|
229
233
|
</li>
|
|
230
234
|
</ol>
|
|
231
235
|
</component>
|
|
@@ -241,6 +245,7 @@ import CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutli
|
|
|
241
245
|
import DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'
|
|
242
246
|
import FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'
|
|
243
247
|
import Replay from 'vue-material-design-icons/Replay.vue'
|
|
248
|
+
import CnFlowStepOutcomes from '../CnFlowStepOutcomes/CnFlowStepOutcomes.vue'
|
|
244
249
|
import { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'
|
|
245
250
|
|
|
246
251
|
export default {
|
|
@@ -249,6 +254,7 @@ export default {
|
|
|
249
254
|
components: {
|
|
250
255
|
ArrowLeft,
|
|
251
256
|
CheckboxMarkedOutline,
|
|
257
|
+
CnFlowStepOutcomes,
|
|
252
258
|
DatabaseOutline,
|
|
253
259
|
FormatListBulleted,
|
|
254
260
|
NcAppSidebarTab,
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnFlowStepOutcomes: who a send step reached, and who it did not, by name.
|
|
3
|
+
|
|
4
|
+
A messaging step (send email, send notification, post to Talk) writes its
|
|
5
|
+
per-recipient outcome onto its run-log entry as `report.messaging`: one
|
|
6
|
+
bucket per outcome, each `{count, sample}`. Nothing rendered it, so the run
|
|
7
|
+
log could not show that a person was skipped because they opted out, or
|
|
8
|
+
because the opt-out check did not answer.
|
|
9
|
+
|
|
10
|
+
Only buckets with someone in them are listed. A bucket this component does
|
|
11
|
+
not know is still shown, under its own key made readable, so a new outcome
|
|
12
|
+
on the server is never hidden by an older library.
|
|
13
|
+
|
|
14
|
+
SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
|
|
15
|
+
SPDX-License-Identifier: EUPL-1.2
|
|
16
|
+
-->
|
|
17
|
+
<template>
|
|
18
|
+
<div v-if="rows.length" class="cn-flow-step-outcomes" data-testid="flow-step-outcomes">
|
|
19
|
+
<dl class="cn-flow-step-outcomes__list" :aria-label="t('nextcloud-vue', 'Who this step reached')">
|
|
20
|
+
<div v-for="row in rows"
|
|
21
|
+
:key="row.key"
|
|
22
|
+
class="cn-flow-step-outcomes__row"
|
|
23
|
+
:class="`cn-flow-step-outcomes__row--${row.tone}`"
|
|
24
|
+
:data-testid="`flow-step-outcome-${row.key}`">
|
|
25
|
+
<dt class="cn-flow-step-outcomes__name">
|
|
26
|
+
{{ row.label }}
|
|
27
|
+
</dt>
|
|
28
|
+
<dd class="cn-flow-step-outcomes__value">
|
|
29
|
+
<span class="cn-flow-step-outcomes__count">{{ row.count }}</span>
|
|
30
|
+
<span v-if="row.sample.length" class="cn-flow-step-outcomes__sample"> · {{ row.sample.join(', ') }}</span>
|
|
31
|
+
</dd>
|
|
32
|
+
</div>
|
|
33
|
+
</dl>
|
|
34
|
+
<p v-if="report && report.truncated"
|
|
35
|
+
class="cn-flow-step-outcomes__truncated"
|
|
36
|
+
data-testid="flow-step-outcomes-truncated">
|
|
37
|
+
{{ t('nextcloud-vue', 'Each list shows its first entries only.') }}
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script>
|
|
43
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The buckets a messaging report carries, in reading order: who got it first,
|
|
47
|
+
* then each reason someone did not. `tone` marks the row for styling only.
|
|
48
|
+
*/
|
|
49
|
+
const KNOWN_BUCKETS = [
|
|
50
|
+
{ key: 'delivered', tone: 'success' },
|
|
51
|
+
{ key: 'optedOut', tone: 'neutral' },
|
|
52
|
+
{ key: 'authorityUnavailable', tone: 'warning' },
|
|
53
|
+
{ key: 'skippedByPreference', tone: 'neutral' },
|
|
54
|
+
{ key: 'skippedByKillSwitch', tone: 'neutral' },
|
|
55
|
+
{ key: 'rateLimited', tone: 'warning' },
|
|
56
|
+
{ key: 'refusedRecipients', tone: 'warning' },
|
|
57
|
+
{ key: 'unknownRecipients', tone: 'warning' },
|
|
58
|
+
{ key: 'failed', tone: 'error' },
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
/** Report keys that are facts about the send, not outcome buckets. */
|
|
62
|
+
const NOT_BUCKETS = ['channel', 'actor', 'recipients', 'truncated']
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
name: 'CnFlowStepOutcomes',
|
|
66
|
+
|
|
67
|
+
props: {
|
|
68
|
+
/**
|
|
69
|
+
* A step's messaging report: the run-log entry's `report.messaging`.
|
|
70
|
+
* Each outcome bucket is `{count, sample}`; a `refusedRecipients`
|
|
71
|
+
* sample entry is `{recipient, reason}`. `null` renders nothing.
|
|
72
|
+
*/
|
|
73
|
+
report: {
|
|
74
|
+
type: Object,
|
|
75
|
+
default: null,
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Names to use instead of the built-in ones, keyed by bucket
|
|
80
|
+
* (`{ optedOut: 'Unsubscribed' }`). Pass already-translated text.
|
|
81
|
+
*/
|
|
82
|
+
labels: {
|
|
83
|
+
type: Object,
|
|
84
|
+
default: () => ({}),
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
computed: {
|
|
89
|
+
/**
|
|
90
|
+
* The non-empty buckets, known ones first in reading order, then any
|
|
91
|
+
* bucket the server added that this component does not know.
|
|
92
|
+
*
|
|
93
|
+
* @return {Array<object>} One row per bucket: key, label, count, sample, tone.
|
|
94
|
+
*/
|
|
95
|
+
rows() {
|
|
96
|
+
const report = this.report
|
|
97
|
+
if (!report || typeof report !== 'object') {
|
|
98
|
+
return []
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const known = KNOWN_BUCKETS.map((bucket) => bucket.key)
|
|
102
|
+
const extra = Object.keys(report).filter((key) => !known.includes(key) && !NOT_BUCKETS.includes(key))
|
|
103
|
+
const ordered = [
|
|
104
|
+
...KNOWN_BUCKETS,
|
|
105
|
+
...extra.map((key) => ({ key, tone: 'neutral' })),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
return ordered
|
|
109
|
+
.map(({ key, tone }) => ({ key, tone, bucket: report[key] }))
|
|
110
|
+
.filter(({ bucket }) => bucket && typeof bucket === 'object' && Number(bucket.count) > 0)
|
|
111
|
+
.map(({ key, tone, bucket }) => ({
|
|
112
|
+
key,
|
|
113
|
+
tone,
|
|
114
|
+
label: this.labelFor(key),
|
|
115
|
+
count: Number(bucket.count),
|
|
116
|
+
sample: (Array.isArray(bucket.sample) ? bucket.sample : []).map((entry) => this.describeEntry(entry)),
|
|
117
|
+
}))
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
methods: {
|
|
122
|
+
t,
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The readable name of a bucket: the host's, then the built-in one,
|
|
126
|
+
* then the key itself (`heldForReview` reads "Held for review").
|
|
127
|
+
*
|
|
128
|
+
* @param {string} key The bucket key.
|
|
129
|
+
* @return {string} The name.
|
|
130
|
+
*/
|
|
131
|
+
labelFor(key) {
|
|
132
|
+
if (this.labels && this.labels[key]) {
|
|
133
|
+
return String(this.labels[key])
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const builtIn = {
|
|
137
|
+
delivered: t('nextcloud-vue', 'Delivered'),
|
|
138
|
+
optedOut: t('nextcloud-vue', 'Opted out'),
|
|
139
|
+
authorityUnavailable: t('nextcloud-vue', 'Not sent, the opt-out check did not answer'),
|
|
140
|
+
skippedByPreference: t('nextcloud-vue', 'Skipped by their notification settings'),
|
|
141
|
+
skippedByKillSwitch: t('nextcloud-vue', 'Skipped, sending is switched off'),
|
|
142
|
+
rateLimited: t('nextcloud-vue', 'Held back by the send limit'),
|
|
143
|
+
refusedRecipients: t('nextcloud-vue', 'Refused by the step’s address rule'),
|
|
144
|
+
unknownRecipients: t('nextcloud-vue', 'Not a known user or group'),
|
|
145
|
+
failed: t('nextcloud-vue', 'Failed'),
|
|
146
|
+
}
|
|
147
|
+
if (builtIn[key]) {
|
|
148
|
+
return builtIn[key]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const words = String(key).replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[-_]+/g, ' ').trim().toLowerCase()
|
|
152
|
+
|
|
153
|
+
return words.charAt(0).toUpperCase() + words.slice(1)
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* One sample entry as text. A refused address carries its reason.
|
|
158
|
+
*
|
|
159
|
+
* @param {string|object} entry The sample entry.
|
|
160
|
+
* @return {string} The text.
|
|
161
|
+
*/
|
|
162
|
+
describeEntry(entry) {
|
|
163
|
+
if (entry && typeof entry === 'object') {
|
|
164
|
+
const who = String(entry.recipient ?? entry.id ?? '')
|
|
165
|
+
const reason = this.reasonFor(entry.reason)
|
|
166
|
+
|
|
167
|
+
return reason ? `${who} (${reason})` : who
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return String(entry)
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Why the step's address rule refused an address, in words.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} [code] The refusal code.
|
|
177
|
+
* @return {string} The reason, or the code when it is not known.
|
|
178
|
+
*/
|
|
179
|
+
reasonFor(code) {
|
|
180
|
+
const reasons = {
|
|
181
|
+
'external-recipients-off': t('nextcloud-vue', 'external recipients are off'),
|
|
182
|
+
'not-on-item': t('nextcloud-vue', 'not on the item'),
|
|
183
|
+
'invalid-address': t('nextcloud-vue', 'not a valid address'),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return code ? (reasons[code] || String(code)) : ''
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
</script>
|
|
191
|
+
|
|
192
|
+
<style scoped>
|
|
193
|
+
.cn-flow-step-outcomes {
|
|
194
|
+
margin: 4px 0 0;
|
|
195
|
+
font-size: 0.9em;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.cn-flow-step-outcomes__list {
|
|
199
|
+
margin: 0;
|
|
200
|
+
padding: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Stacked, name over value: the run sidebar is narrow, and a side-by-side
|
|
204
|
+
row wrapped mid-list. The host's dt/dd margins and alignment are reset. */
|
|
205
|
+
.cn-flow-step-outcomes__row {
|
|
206
|
+
display: block;
|
|
207
|
+
margin-block: 4px;
|
|
208
|
+
padding-block: 2px;
|
|
209
|
+
padding-inline-start: 8px;
|
|
210
|
+
border-inline-start: 3px solid var(--color-border);
|
|
211
|
+
text-align: start;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.cn-flow-step-outcomes__row--success {
|
|
215
|
+
border-inline-start-color: var(--color-success);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.cn-flow-step-outcomes__row--warning {
|
|
219
|
+
border-inline-start-color: var(--color-warning);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.cn-flow-step-outcomes__row--error {
|
|
223
|
+
border-inline-start-color: var(--color-error);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.cn-flow-step-outcomes__name {
|
|
227
|
+
display: block;
|
|
228
|
+
white-space: normal;
|
|
229
|
+
overflow-wrap: anywhere;
|
|
230
|
+
margin: 0;
|
|
231
|
+
padding: 0;
|
|
232
|
+
font-weight: bold;
|
|
233
|
+
text-align: start;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.cn-flow-step-outcomes__value {
|
|
237
|
+
display: block;
|
|
238
|
+
white-space: normal;
|
|
239
|
+
margin: 0;
|
|
240
|
+
padding: 0;
|
|
241
|
+
text-align: start;
|
|
242
|
+
color: var(--color-main-text);
|
|
243
|
+
overflow-wrap: anywhere;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.cn-flow-step-outcomes__truncated {
|
|
247
|
+
margin: 4px 0 0;
|
|
248
|
+
color: var(--color-text-maxcontrast);
|
|
249
|
+
}
|
|
250
|
+
</style>
|