@conduction/nextcloud-vue 2.112.0 → 2.113.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.
Files changed (47) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue.js +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +4 -6
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  6. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  7. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  8. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -3
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +20 -5
  14. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  15. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +56 -13
  17. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -3
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  21. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +29 -12
  22. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  23. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +3 -1
  24. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  25. package/dist/esm/library-style.js +1 -1
  26. package/dist/esm/nextcloud-vue.css +152 -181
  27. package/dist/esm/utils/actionLink.js +104 -1
  28. package/dist/esm/utils/actionLink.js.map +1 -1
  29. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  30. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  31. package/dist/nextcloud-vue.cjs.js +241 -50
  32. package/dist/nextcloud-vue.cjs.js.map +2 -2
  33. package/dist/nextcloud-vue.css +152 -181
  34. package/package.json +1 -1
  35. package/src/components/CnActionButtons/CnActionButtons.vue +4 -6
  36. package/src/components/CnChoiceCards/CnChoiceCards.vue +0 -3
  37. package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -5
  38. package/src/components/CnDetailPage/CnDetailPage.vue +20 -5
  39. package/src/components/CnIndexPage/CnIndexPage.vue +59 -14
  40. package/src/components/CnReportsPage/CnReportsPage.vue +31 -12
  41. package/src/css/badge.css +15 -13
  42. package/src/css/board.css +0 -1
  43. package/src/css/look-board-index.css +3 -17
  44. package/src/css/look-board.css +2 -16
  45. package/src/schemas/app-manifest-v2.schema.json +51 -9
  46. package/src/utils/actionLink.js +103 -0
  47. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -353,11 +353,8 @@
353
353
  end of a tab strip) is the board's neutral control: the main background, a
354
354
  grey border, the main text at 600 (DqZaak), not Nextcloud's primary tint.
355
355
  NcActions marks a secondary menu `action-item--secondary` on its root and
356
- keeps a class given to it off, so the rule reads that marker. The label
357
- hands its ink to --nldesign-color-on-surface for a theme that forces the
358
- body ink onto every span (thematiq). */
356
+ keeps a class given to it off, so the rule reads that marker. */
359
357
  .cn-look-board .action-item.action-item--secondary .action-item__menutoggle.button-vue--secondary:not(.cn-look-nextcloud *) {
360
- --nldesign-color-on-surface: var(--color-main-text);
361
358
  border: 1px solid var(--cn-board-control-border);
362
359
  border-radius: var(--cn-board-control-radius);
363
360
  background: var(--color-main-background);
@@ -1164,14 +1161,6 @@
1164
1161
  color: var(--cn-status-badge-ink);
1165
1162
  }
1166
1163
 
1167
- /* A pill paints its own pairing of ink and fill. A theme that forces the body
1168
- ink onto every span (thematiq's text rule, `!important` at id level) reads
1169
- that ink from --nldesign-color-on-surface when the surface sets it, so the
1170
- pill hands it its own ink: "In behandeling" stays purple on purple. */
1171
- .cn-look-board .cn-status-badge:not(.cn-look-nextcloud *) {
1172
- --nldesign-color-on-surface: var(--cn-status-badge-ink);
1173
- }
1174
-
1175
1164
  /* ---- Info notes (NcNoteCard type="info") -------------------------------- */
1176
1165
  /* The board's info note is a light-blue rounded box: no edge bar, 12px 14px
1177
1166
  of padding, radius 8, an 18px icon 10px before the text, 14px text in the
@@ -1223,9 +1212,7 @@
1223
1212
  hairline under each pair (DqZaak "Zaakgegevens"), the horizontal layout as
1224
1213
  two columns of 14px text, the label column as wide as its longest label
1225
1214
  (the side cards). Both at the board's line height, 1.2, not Nextcloud's 1.5:
1226
- a pair is 47px tall on the board. The muted label also hands its ink to
1227
- --nldesign-color-on-surface, which a theme that forces the body ink onto
1228
- every div (thematiq) reads. */
1215
+ a pair is 47px tall on the board. */
1229
1216
  .cn-look-board .cn-detail-grid.cn-detail-grid--grid:not(.cn-look-nextcloud *) {
1230
1217
  gap: 12px 24px;
1231
1218
  font-size: 15px;
@@ -1256,7 +1243,6 @@
1256
1243
  }
1257
1244
 
1258
1245
  .cn-look-board .cn-detail-grid .cn-detail-grid__label:not(.cn-look-nextcloud *) {
1259
- --nldesign-color-on-surface: var(--color-text-maxcontrast);
1260
1246
  min-width: 0;
1261
1247
  color: var(--color-text-maxcontrast);
1262
1248
  font-size: 13px;
@@ -3,7 +3,7 @@
3
3
  "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
4
4
  "title": "Conduction App Manifest v2",
5
5
  "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
6
- "version": "2.134.0",
6
+ "version": "2.135.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -3104,10 +3104,17 @@
3104
3104
  }
3105
3105
  },
3106
3106
  "action": {
3107
+ "description": "A page action: the fields of $defs/actionFields, nothing else, with children that are actions too. Row, bulk and navigation actions use it; header actions use $defs/headerAction.",
3108
+ "$ref": "#/$defs/actionFields",
3109
+ "properties": {
3110
+ "children": { "items": { "$ref": "#/$defs/action" } }
3111
+ },
3112
+ "unevaluatedProperties": false
3113
+ },
3114
+ "actionFields": {
3107
3115
  "type": "object",
3108
3116
  "required": ["id", "label"],
3109
- "additionalProperties": false,
3110
- "description": "A typed page action with a discriminator field. The type field determines behaviour: handler (registry function), open-modal (modal by id), open-page (named route), navigate (URL), object-op (declarative OpenRegister mutation via useObjectStore), export (opens the shared CnMassExportDialog export launcher), open-form (schema-driven create dialog — CnActionButtons), refresh (bumps the cn:page:refresh signal), api-call (POST/PUT an app endpoint + toast + refresh — CnActionButtons/dispatchAction), agent (run a governed hermiq agent), run-node (invoke one OpenRegister flow node against the page object, config collected via a dialog sourced from the node's own declared form — CnActionButtons/dispatchAction, or-flow-run-node; distinct from the UNRELATED setup-wizard step type of a similar name, 'run-action'), toggle (two-way state button — CnActionButtons only, NOT dispatchable). When type is omitted, it defaults to 'handler' (back-compatibility with v1.3.0 action declarations). The Ajv instance is compiled with useDefaults: true so the default is applied during validation.",
3117
+ "description": "The fields of an action, shared by $defs/action and the non-link $defs/headerAction, which close it and constrain `children`. A typed page action with a discriminator field. The type field determines behaviour: handler (registry function), open-modal (modal by id), open-page (named route), navigate (URL), object-op (declarative OpenRegister mutation via useObjectStore), export (opens the shared CnMassExportDialog export launcher), open-form (schema-driven create dialog — CnActionButtons), refresh (bumps the cn:page:refresh signal), api-call (POST/PUT an app endpoint + toast + refresh — CnActionButtons/dispatchAction), agent (run a governed hermiq agent), run-node (invoke one OpenRegister flow node against the page object, config collected via a dialog sourced from the node's own declared form — CnActionButtons/dispatchAction, or-flow-run-node; distinct from the UNRELATED setup-wizard step type of a similar name, 'run-action'), toggle (two-way state button — CnActionButtons only, NOT dispatchable). When type is omitted, it defaults to 'handler' (back-compatibility with v1.3.0 action declarations). The Ajv instance is compiled with useDefaults: true so the default is applied during validation.",
3111
3118
  "properties": {
3112
3119
  "id": {
3113
3120
  "type": "string",
@@ -3365,8 +3372,7 @@
3365
3372
  },
3366
3373
  "children": {
3367
3374
  "type": "array",
3368
- "description": "Sub-actions, rendered by CnActionButtons as this action's OWN chevron dropdown beside its button — deliberately not folded into the page's single overflow menu, because NcActions cannot nest and a list that grows per record (app versions, say) would swamp it. Children take the same shape as any action, `visibleWhen` included, and an action carrying them consumes none of the `inline` slots. The parent itself still renders its own button beside the chevron (`type` defaults to 'handler' when omitted), so a parent meant only to group its children needs a `handler` that does something meaningful — an untyped, handler-less parent renders a button that dispatches to nothing.",
3369
- "items": { "$ref": "#/$defs/action" }
3375
+ "description": "Sub-actions, rendered by CnActionButtons as this action's OWN chevron dropdown beside its button — deliberately not folded into the page's single overflow menu, because NcActions cannot nest and a list that grows per record (app versions, say) would swamp it. Children take the same shape as any action, `visibleWhen` included, and an action carrying them consumes none of the `inline` slots. The parent itself still renders its own button beside the chevron (`type` defaults to 'handler' when omitted), so a parent meant only to group its children needs a `handler` that does something meaningful — an untyped, handler-less parent renders a button that dispatches to nothing. Each child is an action; a header action's child is a header action, so it may be a link with `href`."
3370
3376
  },
3371
3377
  "childrenLabel": {
3372
3378
  "type": "string",
@@ -3438,6 +3444,42 @@
3438
3444
  }
3439
3445
  ]
3440
3446
  },
3447
+ "headerAction": {
3448
+ "description": "A page header action (`headerActions[]`, a detail page's `quickActions[]` / `primaryActionByStage` entry, and their `children[]`): an action ($ref action), or with `href` a link ($ref headerLinkAction). Only header actions take `href`; row, bulk and navigation actions do not (2.135.0).",
3449
+ "if": { "type": "object", "required": ["href"] },
3450
+ "then": { "$ref": "#/$defs/headerLinkAction" },
3451
+ "else": {
3452
+ "$ref": "#/$defs/actionFields",
3453
+ "properties": {
3454
+ "children": { "items": { "$ref": "#/$defs/headerAction" } }
3455
+ },
3456
+ "unevaluatedProperties": false
3457
+ }
3458
+ },
3459
+ "headerLinkAction": {
3460
+ "type": "object",
3461
+ "required": ["id", "label", "href"],
3462
+ "additionalProperties": false,
3463
+ "description": "A header action that is a real link (`<a href>`), so it can be middle-clicked, opened in a new tab, copied and bookmarked. It only navigates: it runs no handler and emits no `header-action`, so it carries no `handler` and no other `type` (2.135.0).",
3464
+ "properties": {
3465
+ "id": { "type": "string", "description": "Unique action identifier within the page's actions array." },
3466
+ "label": { "type": "string", "description": "i18n key resolved by the consuming app's t() function at render time." },
3467
+ "href": {
3468
+ "type": "string",
3469
+ "pattern": "^(https?://|mailto:|tel:|/)",
3470
+ "description": "The link. An absolute `http(s)`, `mailto:` or `tel:` URL and a protocol-relative `//host/...` URL are used as written. A path starting with a Nextcloud entry point (`/index.php/`, `/remote.php/`, `/public.php/`, `/ocs/`) is prefixed with the instance root unless it already starts with it; any other instance path (leading `/`, e.g. `/apps/integriq/connections`) goes through `generateUrl()`."
3471
+ },
3472
+ "target": { "type": "string", "enum": ["_blank", "_self"], "default": "_self", "description": "`_blank` opens the link in a new tab; `_self` opens it in place." },
3473
+ "type": { "const": "handler", "description": "Only the default; a link has no other behaviour." },
3474
+ "icon": { "type": "string", "description": "Optional icon: an MDI icon name or a CSS icon class, as on any action." },
3475
+ "variant": { "type": "string", "description": "CnActionButtons button variant, as on any action." },
3476
+ "group": { "type": "string", "minLength": 1, "description": "Detail page header actions only: the overflow menu group, as on any action." },
3477
+ "adminOnly": { "type": "boolean", "default": false, "description": "Detail page header actions only: show the link to instance admins alone, as on any action." },
3478
+ "visibleWhen": { "$ref": "#/$defs/visibleWhen", "description": "An optional predicate gating whether the link renders, as on any action." },
3479
+ "children": { "type": "array", "description": "Sub-actions in the link's own chevron dropdown, as on any action.", "items": { "$ref": "#/$defs/headerAction" } },
3480
+ "childrenLabel": { "type": "string", "description": "Name of the sub-action chevron, as on any action." }
3481
+ }
3482
+ },
3441
3483
  "fieldValidation": {
3442
3484
  "type": "object",
3443
3485
  "additionalProperties": false,
@@ -4354,7 +4396,7 @@
4354
4396
  "additionalProperties": {
4355
4397
  "oneOf": [
4356
4398
  { "type": "string", "minLength": 1 },
4357
- { "$ref": "#/$defs/action" }
4399
+ { "$ref": "#/$defs/headerAction" }
4358
4400
  ]
4359
4401
  }
4360
4402
  },
@@ -4365,7 +4407,7 @@
4365
4407
  "items": {
4366
4408
  "oneOf": [
4367
4409
  { "type": "string", "minLength": 1 },
4368
- { "$ref": "#/$defs/action" }
4410
+ { "$ref": "#/$defs/headerAction" }
4369
4411
  ]
4370
4412
  }
4371
4413
  },
@@ -4489,8 +4531,8 @@
4489
4531
  },
4490
4532
  "headerActions": {
4491
4533
  "type": "array",
4492
- "description": "Page-level header actions rendered inside CnActionsBar's overflow dropdown (between the built-in Refresh action and the `#action-items` slot). On a `type:'detail'` page the same entries render in the header's Actions menu, above the Request-a-feature / Report-a-bug / Documentation trio. Same shape as the row-level `actions[]` ($ref action), but handlers receive NO row context — they are page-level. Reserved ids (`refresh`, `import`, `export`, `copy`, `delete`) are dropped at render time to avoid shadowing built-ins. See manifest-icons-and-page-actions for the dispatch semantics.",
4493
- "items": { "$ref": "#/$defs/action" }
4534
+ "description": "Page-level header actions rendered inside CnActionsBar's overflow dropdown (between the built-in Refresh action and the `#action-items` slot). On a `type:'detail'` page the same entries render in the header's Actions menu, above the Request-a-feature / Report-a-bug / Documentation trio. Same shape as the row-level `actions[]` ($ref action), but handlers receive NO row context — they are page-level. Reserved ids (`refresh`, `import`, `export`, `copy`, `delete`) are dropped at render time to avoid shadowing built-ins. An entry with `href` (instead of `handler`) renders as a real link to that URL and is never dispatched ($ref headerAction). See manifest-icons-and-page-actions for the dispatch semantics.",
4535
+ "items": { "$ref": "#/$defs/headerAction" }
4494
4536
  },
4495
4537
  "bulkActions": {
4496
4538
  "type": "array",
@@ -9,11 +9,114 @@
9
9
  * The rendering component turns it into an `<a href>`; a plain click on an
10
10
  * in-app `to` is routed through the router, anything else is the browser's.
11
11
  *
12
+ * A manifest `headerActions[]` entry may carry `href` too; the page turns it
13
+ * into the final URL with {@link headerActionAsLink} before it renders.
14
+ *
12
15
  * @module utils/actionLink
13
16
  */
14
17
 
18
+ import { generateUrl, getRootUrl } from '@nextcloud/router'
15
19
  import { followLinkClick } from './linkNavigation.js'
16
20
 
21
+ /** An absolute URL a manifest link may name, matching the schema's lowercase pattern. */
22
+ const ABSOLUTE_HREF = /^(?:https?:\/\/|mailto:|tel:)/
23
+
24
+ /** An instance path that already names a Nextcloud entry point, so generateUrl() must not add `/index.php`. */
25
+ const ROUTED_PATH = /^\/(?:index\.php|remote\.php|public\.php|ocs)(?:[/?#]|$)/
26
+
27
+ /** Bad hrefs already warned about, so a recomputing page warns once per value. */
28
+ const warnedHrefs = new Set()
29
+
30
+ /**
31
+ * Entries whose function handler was dropped for their href, warned about
32
+ * once. Keyed by `id`, else by the declared href and label (behind a NUL, so
33
+ * no real id collides), so entries without an id each get their own warning
34
+ * and a recomputing page does not repeat it.
35
+ */
36
+ const warnedHandlers = new Set()
37
+
38
+ /**
39
+ * The URL a manifest `href` points at: an absolute `http(s)`, `mailto:` or
40
+ * `tel:` URL as written, `//host/...` as written, an instance path that
41
+ * names an entry point (`/index.php/`, `/remote.php/`, `/public.php/`,
42
+ * `/ocs/`) under `getRootUrl()` (as written when it already starts with the
43
+ * root), any other instance path (leading `/`) through `generateUrl()`.
44
+ *
45
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-href-resolves-to-a-url-on-this-instance-or-an-allowed-scheme
46
+ * @param {unknown} href The manifest `href`.
47
+ * @return {string} The URL, or '' for anything else (a relative path, another scheme).
48
+ */
49
+ export function resolveManifestHref(href) {
50
+ if (typeof href !== 'string') {
51
+ return ''
52
+ }
53
+ const value = href.trim()
54
+ if (ABSOLUTE_HREF.test(value) || value.startsWith('//')) {
55
+ return value
56
+ }
57
+ const root = getRootUrl()
58
+ if (root && value.startsWith(root) && ROUTED_PATH.test(value.slice(root.length))) {
59
+ return value
60
+ }
61
+ if (ROUTED_PATH.test(value)) {
62
+ return root + value
63
+ }
64
+ if (value.startsWith('/')) {
65
+ return generateUrl(value)
66
+ }
67
+ return ''
68
+ }
69
+
70
+ /**
71
+ * A manifest header action in its link form. An entry with `href` gets the
72
+ * resolved URL and an anchor `target` (`_blank` when the manifest says so,
73
+ * else `_self`), and loses its `handler`: a link only navigates. Its
74
+ * `children` are resolved the same way. An `href` that resolves to nothing is
75
+ * dropped with a warning, along with a handler name, so the page renders its
76
+ * usual button for the entry (an index or reports page still emits
77
+ * `header-action` for it); a function `handler` (JS composition) is kept
78
+ * then and still runs. An entry with nothing to resolve, a function-valued
79
+ * `href` included, is returned as is. When the `href` does resolve, a
80
+ * function `handler` is dropped like any other, with a one-time warning.
81
+ *
82
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-with-href-is-a-link-that-runs-and-emits-nothing
83
+ * @param {object} entry The manifest header action.
84
+ * @return {object} The entry, or a copy in link form.
85
+ */
86
+ export function headerActionAsLink(entry) {
87
+ if (!entry || typeof entry !== 'object') {
88
+ return entry
89
+ }
90
+ let resolved = entry
91
+ if (Array.isArray(entry.children)) {
92
+ const children = entry.children.map(headerActionAsLink)
93
+ if (children.some((child, index) => child !== entry.children[index])) {
94
+ resolved = { ...entry, children }
95
+ }
96
+ }
97
+ if (typeof entry.href !== 'string' || entry.href === '') {
98
+ return resolved
99
+ }
100
+ const { href: declared, ...withoutHref } = resolved
101
+ const { handler: _handler, ...rest } = withoutHref
102
+ const href = resolveManifestHref(declared)
103
+ if (!href) {
104
+ if (!warnedHrefs.has(declared)) {
105
+ warnedHrefs.add(declared)
106
+ // eslint-disable-next-line no-console
107
+ console.warn(`headerActions[].id "${entry.id}": href "${declared}" is neither an http(s), mailto or tel URL nor an instance path starting with "/"; the entry renders without a link`)
108
+ }
109
+ return typeof entry.handler === 'function' ? withoutHref : rest
110
+ }
111
+ const handlerKey = entry.id ?? `\u0000${declared}|${entry.label ?? ''}`
112
+ if (typeof entry.handler === 'function' && !warnedHandlers.has(handlerKey)) {
113
+ warnedHandlers.add(handlerKey)
114
+ // eslint-disable-next-line no-console
115
+ console.warn(`headerActions[].id "${entry.id}": an entry with href never runs a handler, so its function handler is dropped`)
116
+ }
117
+ return { ...rest, href, target: entry.target === '_blank' ? '_blank' : '_self' }
118
+ }
119
+
17
120
  /**
18
121
  * The href of an in-app router location. A string is a route PATH and goes
19
122
  * through the router as well, so a hash-history app gets its `#`.