@conduction/nextcloud-vue 2.73.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/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 +473 -43
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/package.json +1 -1
- 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/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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* The label a person reads for one notification rule in their settings.
|
|
6
|
+
*
|
|
7
|
+
* A rule is declared in a schema's `x-openregister-notifications` map under a
|
|
8
|
+
* KEY (`caseAssigned`, `clientUpdated`, `onCreate`). The key is an identifier
|
|
9
|
+
* for a developer. The settings pane used to print it as is, so a person saw
|
|
10
|
+
* "substitutionRegisteredForSubstitute" next to a switch and had to guess.
|
|
11
|
+
*
|
|
12
|
+
* The label is taken from the first source that has one, most specific first:
|
|
13
|
+
*
|
|
14
|
+
* 1. the rule's own words, when OpenRegister passes them on the preference
|
|
15
|
+
* entry (`label`, then `title`), as a plain string or a per-locale map
|
|
16
|
+
* like the dialect's `subject` (`{ "nl": "…", "en": "…" }`);
|
|
17
|
+
* 2. the app's own label for the rule, from the `labels` map the app hands
|
|
18
|
+
* to CnAppRoot (`notificationLabels`), keyed `<schema>.<key>` or `<key>`;
|
|
19
|
+
* 3. the library's wording for the generic object-event keys;
|
|
20
|
+
* 4. the key made readable: `caseAssigned` becomes "Case assigned".
|
|
21
|
+
*
|
|
22
|
+
* The rule's `subject` is used only when it carries no `{{placeholder}}`: it
|
|
23
|
+
* is the notification TITLE template, and "Case "{{title}}" assigned to you"
|
|
24
|
+
* reads worse as a setting than the readable key does.
|
|
25
|
+
*
|
|
26
|
+
* Pure: no store, no fetch, no Vue.
|
|
27
|
+
*
|
|
28
|
+
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Pick the string for the current language out of a string or per-locale map.
|
|
33
|
+
*
|
|
34
|
+
* @param {string|object|null|undefined} value A string, or `{ nl, en, … }`.
|
|
35
|
+
* @param {string} [language] The current language, e.g. `nl` or `de_DE`.
|
|
36
|
+
* @return {string} The resolved text, or '' when there is none.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveLocalised(value, language = 'en') {
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
return value.trim()
|
|
41
|
+
}
|
|
42
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
43
|
+
return ''
|
|
44
|
+
}
|
|
45
|
+
const lang = String(language || 'en')
|
|
46
|
+
const base = lang.split(/[-_]/)[0]
|
|
47
|
+
for (const candidate of [lang, base, 'en']) {
|
|
48
|
+
if (typeof value[candidate] === 'string' && value[candidate].trim() !== '') {
|
|
49
|
+
return value[candidate].trim()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const first = Object.values(value).find((text) => typeof text === 'string' && text.trim() !== '')
|
|
53
|
+
return first ? first.trim() : ''
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Make a rule key readable: split camelCase, snake_case and kebab-case into
|
|
58
|
+
* words, keep acronyms, and capitalise only the first word.
|
|
59
|
+
*
|
|
60
|
+
* `caseAssigned` → "Case assigned", `object_created` → "Object created",
|
|
61
|
+
* `newBRPRecord` → "New BRP record".
|
|
62
|
+
*
|
|
63
|
+
* @param {string} key The rule key.
|
|
64
|
+
* @return {string} A readable sentence-case label, or the key when it has no words.
|
|
65
|
+
*/
|
|
66
|
+
export function humaniseRuleKey(key) {
|
|
67
|
+
const words = String(key || '')
|
|
68
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
69
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
70
|
+
.split(/[\s_\-.]+/)
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
if (words.length === 0) {
|
|
73
|
+
return String(key || '')
|
|
74
|
+
}
|
|
75
|
+
return words
|
|
76
|
+
.map((word, index) => {
|
|
77
|
+
const isAcronym = word.length > 1 && word === word.toUpperCase() && /[A-Z]/.test(word)
|
|
78
|
+
if (isAcronym) {
|
|
79
|
+
return word
|
|
80
|
+
}
|
|
81
|
+
const lower = word.toLowerCase()
|
|
82
|
+
return index === 0 ? lower.charAt(0).toUpperCase() + lower.slice(1) : lower
|
|
83
|
+
})
|
|
84
|
+
.join(' ')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The label for one preference entry.
|
|
89
|
+
*
|
|
90
|
+
* @param {object} entry A preference entry: `{ schema, notification, label?, title?, subject? }`.
|
|
91
|
+
* @param {object} [options] Where else a label may come from.
|
|
92
|
+
* @param {object} [options.labels] App-supplied labels keyed `<schema>.<key>` or `<key>`;
|
|
93
|
+
* values are strings or per-locale maps.
|
|
94
|
+
* @param {object} [options.known] Library wording for generic keys, keyed by rule key.
|
|
95
|
+
* @param {string} [options.language] The current language.
|
|
96
|
+
* @return {string} The label to show.
|
|
97
|
+
*/
|
|
98
|
+
export function notificationRuleLabel(entry, { labels = {}, known = {}, language = 'en' } = {}) {
|
|
99
|
+
const key = String(entry?.notification ?? '')
|
|
100
|
+
const schema = String(entry?.schema ?? '')
|
|
101
|
+
|
|
102
|
+
for (const field of ['label', 'title']) {
|
|
103
|
+
const own = resolveLocalised(entry?.[field], language)
|
|
104
|
+
if (own) {
|
|
105
|
+
return own
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const map = labels && typeof labels === 'object' ? labels : {}
|
|
110
|
+
for (const lookup of [schema ? `${schema}.${key}` : '', key]) {
|
|
111
|
+
if (lookup && Object.hasOwn(map, lookup)) {
|
|
112
|
+
const fromApp = resolveLocalised(map[lookup], language)
|
|
113
|
+
if (fromApp) {
|
|
114
|
+
return fromApp
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (known && typeof known[key] === 'string' && known[key]) {
|
|
120
|
+
return known[key]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const subject = resolveLocalised(entry?.subject, language)
|
|
124
|
+
if (subject && !subject.includes('{{')) {
|
|
125
|
+
return subject
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return humaniseRuleKey(key)
|
|
129
|
+
}
|
package/webpack/index.d.ts
CHANGED
|
@@ -28,3 +28,19 @@ export interface CnPublicPathOptions {
|
|
|
28
28
|
* @param options Where the public path is read from at runtime.
|
|
29
29
|
*/
|
|
30
30
|
export function withPublicPath<T extends object | object[]>(config: T, options?: CnPublicPathOptions): T
|
|
31
|
+
|
|
32
|
+
export interface CnAppVersionDefineOptions {
|
|
33
|
+
/** The initial-state key the app's page provides its version under. Defaults to `version`. */
|
|
34
|
+
key?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A `webpack.DefinePlugin` expression for `appVersion` that reads the
|
|
39
|
+
* installed app version from the page's initial state at runtime, falling
|
|
40
|
+
* back to `fallback` (the build-time version) when the state is absent.
|
|
41
|
+
*
|
|
42
|
+
* @param appId The app id.
|
|
43
|
+
* @param fallback The build-time version.
|
|
44
|
+
* @param options Which initial-state key to read.
|
|
45
|
+
*/
|
|
46
|
+
export function appVersionDefine(appId: string, fallback?: string, options?: CnAppVersionDefineOptions): string
|
package/webpack/index.js
CHANGED
|
@@ -116,7 +116,77 @@ function withPublicPath(config, options = {}) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* The `appVersion` define, resolved in the browser from the INSTALLED app.
|
|
121
|
+
*
|
|
122
|
+
* THE PROBLEM
|
|
123
|
+
* -----------
|
|
124
|
+
* `@nextcloud/vue` prints `<app name> <appVersion>` in the footer of every
|
|
125
|
+
* NcAppSettingsDialog, and reads `appVersion` as a global the app's webpack
|
|
126
|
+
* config defines. Apps defined it from `package.json` (a `0.1.0` nobody bumps,
|
|
127
|
+
* so pipelinq read "pipelinq 0.1.0") or from `appinfo/info.xml` at build time.
|
|
128
|
+
* The second is still wrong on every release: the release workflow writes the
|
|
129
|
+
* release version into `info.xml` after the bundle is built, so dossiq
|
|
130
|
+
* 0.4.48-beta said "dossiq 0.4.47-unstable". A build cannot know the version
|
|
131
|
+
* it will be installed as.
|
|
132
|
+
*
|
|
133
|
+
* THE FIX
|
|
134
|
+
* -------
|
|
135
|
+
* Define `appVersion` as an EXPRESSION, not a literal. It runs once in the
|
|
136
|
+
* browser and reads the app's `version` initial state, which the app's page
|
|
137
|
+
* controller provides from what Nextcloud has installed
|
|
138
|
+
* (`IAppConfig::getValueString($appId, 'installed_version')`). It reads the
|
|
139
|
+
* same hidden input `@nextcloud/initial-state` `loadState()` reads; it cannot
|
|
140
|
+
* import that package because a define is pasted into every module as code.
|
|
141
|
+
* When the state is missing (a page that does not provide it, a test, a
|
|
142
|
+
* worker without a document) it falls back to the build-time version.
|
|
143
|
+
*
|
|
144
|
+
* USAGE
|
|
145
|
+
* -----
|
|
146
|
+
* ```js
|
|
147
|
+
* // webpack.config.js
|
|
148
|
+
* const { appVersionDefine } = require('@conduction/nextcloud-vue/webpack')
|
|
149
|
+
* new webpack.DefinePlugin({ appVersion: appVersionDefine('dossiq', readAppVersion()) })
|
|
150
|
+
* ```
|
|
151
|
+
* ```php
|
|
152
|
+
* // the controller that renders the app's page
|
|
153
|
+
* $this->initialState->provideInitialState('version', $this->appConfig->getValueString('dossiq', 'installed_version', ''));
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* @param {string} appId The app id, as used for its initial state.
|
|
157
|
+
* @param {string} [fallback] The version to show when no initial state is
|
|
158
|
+
* present, normally the build-time `info.xml` version.
|
|
159
|
+
* @param {object} [options] Options.
|
|
160
|
+
* @param {string} [options.key] The initial-state key. Defaults to `version`.
|
|
161
|
+
*
|
|
162
|
+
* @return {string} A JavaScript expression for `webpack.DefinePlugin`.
|
|
163
|
+
*
|
|
164
|
+
* @throws {TypeError} When `appId` is empty.
|
|
165
|
+
*
|
|
166
|
+
* @spec openspec/changes/notification-rule-labels-and-runtime-version/specs/notification-preferences/spec.md
|
|
167
|
+
*/
|
|
168
|
+
function appVersionDefine(appId, fallback = '', options = {}) {
|
|
169
|
+
const id = String(appId || '').trim()
|
|
170
|
+
if (id === '') {
|
|
171
|
+
throw new TypeError('appVersionDefine() needs the app id whose installed version it reads')
|
|
172
|
+
}
|
|
173
|
+
const key = String(options.key || 'version')
|
|
174
|
+
const elementId = JSON.stringify('initial-state-' + id + '-' + key)
|
|
175
|
+
const fallbackLiteral = JSON.stringify(String(fallback ?? ''))
|
|
176
|
+
return '(function () {'
|
|
177
|
+
+ ' try {'
|
|
178
|
+
+ ' var el = typeof document !== "undefined" && document.getElementById(' + elementId + ');'
|
|
179
|
+
+ ' if (el && el.value) {'
|
|
180
|
+
+ ' var v = JSON.parse(atob(el.value));'
|
|
181
|
+
+ ' if (typeof v === "string" && v !== "") { return v; }'
|
|
182
|
+
+ ' }'
|
|
183
|
+
+ ' } catch (e) {}'
|
|
184
|
+
+ ' return ' + fallbackLiteral + ';'
|
|
185
|
+
+ ' })()'
|
|
186
|
+
}
|
|
187
|
+
|
|
119
188
|
module.exports = {
|
|
120
189
|
AUTO_PUBLIC_PATH,
|
|
190
|
+
appVersionDefine,
|
|
121
191
|
withPublicPath,
|
|
122
192
|
}
|