@conduction/nextcloud-vue 0.1.0-beta.10 → 0.1.0-beta.12

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 (60) hide show
  1. package/dist/nextcloud-vue.cjs.js +64663 -63467
  2. package/dist/nextcloud-vue.cjs.js.map +1 -1
  3. package/dist/nextcloud-vue.css +443 -444
  4. package/dist/nextcloud-vue.esm.js +64637 -63443
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/l10n/en.json +164 -0
  7. package/l10n/nl.json +164 -0
  8. package/package.json +19 -3
  9. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +8 -7
  10. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +2 -2
  11. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +5 -5
  12. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +2 -2
  13. package/src/components/CnCardGrid/CnCardGrid.vue +2 -1
  14. package/src/components/CnChartWidget/CnChartWidget.vue +29 -1
  15. package/src/components/CnCopyDialog/CnCopyDialog.vue +15 -6
  16. package/src/components/CnDashboardPage/CnDashboardPage.vue +5 -4
  17. package/src/components/CnDetailGrid/CnDetailGrid.vue +3 -1
  18. package/src/components/CnDetailPage/CnDetailPage.vue +5 -4
  19. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +3 -2
  20. package/src/components/CnFilterBar/CnFilterBar.vue +3 -2
  21. package/src/components/CnFormDialog/CnFormDialog.vue +122 -9
  22. package/src/components/CnIndexPage/CnIndexPage.vue +1 -0
  23. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +8 -7
  24. package/src/components/CnJsonViewer/CnJsonViewer.vue +33 -4
  25. package/src/components/CnMassActionBar/CnMassActionBar.vue +1 -1
  26. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +2 -2
  27. package/src/components/CnNotesCard/CnNotesCard.vue +7 -6
  28. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +11 -10
  29. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +3 -2
  30. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +8 -7
  31. package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -5
  32. package/src/components/CnObjectSidebar/CnNotesTab.vue +8 -7
  33. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +6 -5
  34. package/src/components/CnObjectSidebar/CnTagsTab.vue +3 -2
  35. package/src/components/CnObjectSidebar/CnTasksTab.vue +11 -10
  36. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +14 -13
  37. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +15 -14
  38. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +4 -4
  39. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +10 -10
  40. package/src/components/CnSettingsSection/CnSettingsSection.vue +5 -4
  41. package/src/components/CnStatsBlock/CnStatsBlock.vue +5 -4
  42. package/src/components/CnStatsPanel/CnStatsPanel.vue +3 -2
  43. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +9 -8
  44. package/src/components/CnTableWidget/CnTableWidget.vue +3 -2
  45. package/src/components/CnTasksCard/CnTasksCard.vue +5 -4
  46. package/src/components/CnTimelineStages/CnTimelineStages.vue +3 -1
  47. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +7 -6
  48. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
  49. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +3 -1
  50. package/src/index.js +4 -0
  51. package/src/l10n/index.js +12 -0
  52. package/src/store/createCrudStore.d.ts +350 -0
  53. package/src/store/createCrudStore.js +58 -5
  54. package/src/store/pluginMerge.js +55 -0
  55. package/src/store/plugins/index.js +1 -0
  56. package/src/store/plugins/logs.d.ts +22 -0
  57. package/src/store/plugins/logs.js +172 -0
  58. package/src/store/useObjectStore.js +19 -49
  59. package/src/types/index.d.ts +32 -0
  60. package/src/utils/schema.js +3 -2
@@ -114,6 +114,7 @@
114
114
  </template>
115
115
 
116
116
  <script>
117
+ import { translate as t } from '@nextcloud/l10n'
117
118
  import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
118
119
  import Pencil from 'vue-material-design-icons/Pencil.vue'
119
120
  import Check from 'vue-material-design-icons/Check.vue'
@@ -236,22 +237,22 @@ export default {
236
237
  /** Label for the edit button */
237
238
  editLabel: {
238
239
  type: String,
239
- default: 'Edit',
240
+ default: () => t('nextcloud-vue', 'Edit'),
240
241
  },
241
242
  /** Label for the done button (when editing) */
242
243
  doneLabel: {
243
244
  type: String,
244
- default: 'Done',
245
+ default: () => t('nextcloud-vue', 'Done'),
245
246
  },
246
247
  /** Label for the empty state */
247
248
  emptyLabel: {
248
249
  type: String,
249
- default: 'No widgets configured',
250
+ default: () => t('nextcloud-vue', 'No widgets configured'),
250
251
  },
251
252
  /** Label for unavailable widgets */
252
253
  unavailableLabel: {
253
254
  type: String,
254
- default: 'Widget not available',
255
+ default: () => t('nextcloud-vue', 'Widget not available'),
255
256
  },
256
257
  },
257
258
 
@@ -52,6 +52,8 @@
52
52
  </template>
53
53
 
54
54
  <script>
55
+ import { translate as t } from '@nextcloud/l10n'
56
+
55
57
  /**
56
58
  * CnDetailGrid — Data-driven label-value grid for detail/info sections.
57
59
  *
@@ -132,7 +134,7 @@ export default {
132
134
  */
133
135
  emptyLabel: {
134
136
  type: String,
135
- default: 'No details available',
137
+ default: () => t('nextcloud-vue', 'No details available'),
136
138
  },
137
139
  },
138
140
 
@@ -158,6 +158,7 @@
158
158
  </template>
159
159
 
160
160
  <script>
161
+ import { translate as t } from '@nextcloud/l10n'
161
162
  import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
162
163
  import { CnIcon } from '../CnIcon/index.js'
163
164
  import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
@@ -278,7 +279,7 @@ export default {
278
279
  /** Message shown during loading */
279
280
  loadingLabel: {
280
281
  type: String,
281
- default: 'Loading...',
282
+ default: () => t('nextcloud-vue', 'Loading...'),
282
283
  },
283
284
  /** Whether to activate the external sidebar (via objectSidebarState inject) */
284
285
  sidebar: {
@@ -318,7 +319,7 @@ export default {
318
319
  /** Error message shown in error state */
319
320
  errorMessage: {
320
321
  type: String,
321
- default: 'An error occurred',
322
+ default: () => t('nextcloud-vue', 'An error occurred'),
322
323
  },
323
324
  /** Callback for retry button in error state. If null, no retry button is shown. */
324
325
  onRetry: {
@@ -328,7 +329,7 @@ export default {
328
329
  /** Label for the retry button */
329
330
  retryLabel: {
330
331
  type: String,
331
- default: 'Retry',
332
+ default: () => t('nextcloud-vue', 'Retry'),
332
333
  },
333
334
  /** Whether the page has no data to show */
334
335
  empty: {
@@ -338,7 +339,7 @@ export default {
338
339
  /** Message shown when page is empty */
339
340
  emptyLabel: {
340
341
  type: String,
341
- default: 'No data available',
342
+ default: () => t('nextcloud-vue', 'No data available'),
342
343
  },
343
344
  /** Title shown above the statistics table */
344
345
  statsTitle: {
@@ -59,6 +59,7 @@
59
59
  </template>
60
60
 
61
61
  <script>
62
+ import { translate as t } from '@nextcloud/l10n'
62
63
  import { NcButton, NcSelect, NcTextField, NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'
63
64
  import { filtersFromSchema } from '../../utils/schema.js'
64
65
 
@@ -112,12 +113,12 @@ export default {
112
113
  /** Sidebar title */
113
114
  title: {
114
115
  type: String,
115
- default: 'Filters',
116
+ default: () => t('nextcloud-vue', 'Filters'),
116
117
  },
117
118
  /** Clear all button label */
118
119
  clearLabel: {
119
120
  type: String,
120
- default: 'Clear all',
121
+ default: () => t('nextcloud-vue', 'Clear all'),
121
122
  },
122
123
  /**
123
124
  * Whether the current user is an admin.
@@ -63,6 +63,7 @@
63
63
  </template>
64
64
 
65
65
  <script>
66
+ import { translate as t } from '@nextcloud/l10n'
66
67
  import { NcTextField, NcSelect, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue'
67
68
  import Magnify from 'vue-material-design-icons/Magnify.vue'
68
69
 
@@ -113,7 +114,7 @@ export default {
113
114
  /** Search input placeholder text */
114
115
  searchPlaceholder: {
115
116
  type: String,
116
- default: 'Search...',
117
+ default: () => t('nextcloud-vue', 'Search...'),
117
118
  },
118
119
  /** Whether to show the "Clear all" button */
119
120
  showClearAll: {
@@ -123,7 +124,7 @@ export default {
123
124
  /** Clear all button label */
124
125
  clearAllLabel: {
125
126
  type: String,
126
- default: 'Clear filters',
127
+ default: () => t('nextcloud-vue', 'Clear filters'),
127
128
  },
128
129
  },
129
130
 
@@ -227,6 +227,43 @@
227
227
  :disabled="field.readOnly"
228
228
  @update:value="value => updateField(field.key, value)" />
229
229
 
230
+ <!-- JSON (type: 'object'|'array'|... with widget: 'json'): parses on input, stores parsed value in formData -->
231
+ <div v-else-if="field.widget === 'json'" class="cn-form-dialog__json-wrapper">
232
+ <label :for="'cn-form-' + field.key" class="cn-form-dialog__label">
233
+ {{ field.label }}{{ field.required ? ' *' : '' }}
234
+ </label>
235
+ <CnJsonViewer
236
+ :value="jsonStringFor(field)"
237
+ language="json"
238
+ :read-only="field.readOnly"
239
+ :error-text="jsonErrors[field.key] || ''"
240
+ @update:value="value => onJsonFieldInput(field, value)" />
241
+ <span
242
+ v-if="errors[field.key] || field.description"
243
+ class="cn-form-dialog__helper"
244
+ :class="{ 'cn-form-dialog__helper--error': errors[field.key] }">
245
+ {{ errors[field.key] || field.description }}
246
+ </span>
247
+ </div>
248
+
249
+ <!-- Code (freeform editor, stored as raw string; optional `field.language` chooses highlighting) -->
250
+ <div v-else-if="field.widget === 'code'" class="cn-form-dialog__json-wrapper">
251
+ <label :for="'cn-form-' + field.key" class="cn-form-dialog__label">
252
+ {{ field.label }}{{ field.required ? ' *' : '' }}
253
+ </label>
254
+ <CnJsonViewer
255
+ :value="formData[field.key] != null ? String(formData[field.key]) : ''"
256
+ :language="field.language || 'auto'"
257
+ :read-only="field.readOnly"
258
+ @update:value="value => updateField(field.key, value)" />
259
+ <span
260
+ v-if="errors[field.key] || field.description"
261
+ class="cn-form-dialog__helper"
262
+ :class="{ 'cn-form-dialog__helper--error': errors[field.key] }">
263
+ {{ errors[field.key] || field.description }}
264
+ </span>
265
+ </div>
266
+
230
267
  <!-- Fallback: text input -->
231
268
  <NcTextField
232
269
  v-else
@@ -251,7 +288,7 @@
251
288
  <NcButton
252
289
  v-if="result === null"
253
290
  type="primary"
254
- :disabled="loading || !requiredFieldsFilled"
291
+ :disabled="loading || !requiredFieldsFilled || !jsonFieldsValid"
255
292
  @click="executeConfirm">
256
293
  <template #icon>
257
294
  <NcLoadingIcon v-if="loading" :size="20" />
@@ -265,7 +302,9 @@
265
302
  </template>
266
303
 
267
304
  <script>
305
+ import { translate as t } from '@nextcloud/l10n'
268
306
  import { NcDialog, NcButton, NcNoteCard, NcLoadingIcon, NcTextField, NcSelect, NcCheckboxRadioSwitch } from '@nextcloud/vue'
307
+ import CnJsonViewer from '../CnJsonViewer/CnJsonViewer.vue'
269
308
  import Plus from 'vue-material-design-icons/Plus.vue'
270
309
  import ContentSaveOutline from 'vue-material-design-icons/ContentSaveOutline.vue'
271
310
  import { fieldsFromSchema } from '../../utils/schema.js'
@@ -297,6 +336,17 @@ import { fieldsFromSchema } from '../../utils/schema.js'
297
336
  * (with empty query) and on each search input (debounced, default 300ms, configurable
298
337
  * via `field.debounce`). Async selects store the full option object in formData.
299
338
  *
339
+ * ## JSON / code fields
340
+ *
341
+ * Two widgets render a CnJsonViewer-powered editor:
342
+ *
343
+ * - `widget: 'json'` — Parses on input. formData holds the parsed value (object,
344
+ * array, number, string, boolean, or `null` for empty). Invalid JSON displays an
345
+ * inline error and blocks the confirm button until fixed. Pair with `type: 'object'`
346
+ * (or any type) to opt a property out of the default object-filter in `fieldsFromSchema`.
347
+ * - `widget: 'code'` — Stores the raw string. Optional `field.language` chooses
348
+ * syntax highlighting (`'json'|'xml'|'html'|'text'|'auto'`, default `'auto'`).
349
+ *
300
350
  * The dialog does NOT perform the save itself — it emits a `confirm` event
301
351
  * with the form data. The parent performs the actual API call and calls
302
352
  * `setResult()` via a ref.
@@ -359,6 +409,7 @@ export default {
359
409
  NcTextField,
360
410
  NcSelect,
361
411
  NcCheckboxRadioSwitch,
412
+ CnJsonViewer,
362
413
  Plus,
363
414
  ContentSaveOutline,
364
415
  },
@@ -414,8 +465,8 @@ export default {
414
465
  type: String,
415
466
  default: '',
416
467
  },
417
- cancelLabel: { type: String, default: 'Cancel' },
418
- closeLabel: { type: String, default: 'Close' },
468
+ cancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },
469
+ closeLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },
419
470
  /** Confirm button label. Defaults to "Create" or "Save". */
420
471
  confirmLabel: {
421
472
  type: String,
@@ -432,6 +483,10 @@ export default {
432
483
  closeTimeout: null,
433
484
  /** Per-field async state: { [fieldKey]: { options: [], loading: false, searchTimeout: null } } */
434
485
  asyncState: {},
486
+ /** Per-field editor string for `json` widgets (preserves input between keystrokes even while invalid) */
487
+ jsonDrafts: {},
488
+ /** Per-field parse-error messages for `json` widgets (blocks confirm) */
489
+ jsonErrors: {},
435
490
  }
436
491
  },
437
492
 
@@ -441,24 +496,24 @@ export default {
441
496
  },
442
497
 
443
498
  schemaTitle() {
444
- return (this.schema && this.schema.title) || 'Item'
499
+ return (this.schema && this.schema.title) || t('nextcloud-vue', 'Item')
445
500
  },
446
501
 
447
502
  resolvedTitle() {
448
503
  if (this.dialogTitle) return this.dialogTitle
449
504
  return this.isCreateMode
450
- ? `Create ${this.schemaTitle}`
451
- : `Edit ${this.schemaTitle}`
505
+ ? t('nextcloud-vue', 'Create {title}', { title: this.schemaTitle })
506
+ : t('nextcloud-vue', 'Edit {title}', { title: this.schemaTitle })
452
507
  },
453
508
 
454
509
  resolvedConfirmLabel() {
455
510
  if (this.confirmLabel) return this.confirmLabel
456
- return this.isCreateMode ? 'Create' : 'Save'
511
+ return this.isCreateMode ? t('nextcloud-vue', 'Create') : t('nextcloud-vue', 'Save')
457
512
  },
458
513
 
459
514
  resolvedSuccessText() {
460
515
  if (this.successText) return this.successText
461
- return `${this.schemaTitle} saved successfully.`
516
+ return t('nextcloud-vue', '{title} saved successfully.', { title: this.schemaTitle })
462
517
  },
463
518
 
464
519
  /** Whether all required fields have a non-empty value */
@@ -473,6 +528,11 @@ export default {
473
528
  })
474
529
  },
475
530
 
531
+ /** Whether every `json` widget currently parses successfully */
532
+ jsonFieldsValid() {
533
+ return Object.keys(this.jsonErrors).every((k) => !this.jsonErrors[k])
534
+ },
535
+
476
536
  resolvedFields() {
477
537
  // Manual fields take priority
478
538
  if (this.fields) return this.fields
@@ -517,6 +577,8 @@ export default {
517
577
  data[field.key] = false
518
578
  } else if (field.widget === 'tags' || field.widget === 'multiselect') {
519
579
  data[field.key] = []
580
+ } else if (field.widget === 'code') {
581
+ data[field.key] = ''
520
582
  } else {
521
583
  data[field.key] = null
522
584
  }
@@ -524,6 +586,8 @@ export default {
524
586
  this.formData = data
525
587
  }
526
588
  this.errors = {}
589
+ this.jsonDrafts = {}
590
+ this.jsonErrors = {}
527
591
  this.initAsyncFields()
528
592
  },
529
593
 
@@ -535,6 +599,53 @@ export default {
535
599
  }
536
600
  },
537
601
 
602
+ /**
603
+ * Resolve the string shown in the CnJsonViewer for a `json`-widget field.
604
+ * Prefers the unparsed draft (so invalid typing isn't clobbered), falling
605
+ * back to a pretty-printed stringification of the parsed value in formData.
606
+ *
607
+ * @param {object} field Field definition.
608
+ * @return {string} JSON string for the editor.
609
+ */
610
+ jsonStringFor(field) {
611
+ if (Object.prototype.hasOwnProperty.call(this.jsonDrafts, field.key)) {
612
+ return this.jsonDrafts[field.key]
613
+ }
614
+ const value = this.formData[field.key]
615
+ if (value === null || value === undefined) return ''
616
+ try {
617
+ return JSON.stringify(value, null, 2)
618
+ } catch (e) {
619
+ return String(value)
620
+ }
621
+ },
622
+
623
+ /**
624
+ * Handle input in a `json`-widget CnJsonViewer. Parses on the fly:
625
+ * on success, the parsed value lands in formData and any previous error
626
+ * is cleared; on failure, formData keeps its last-known-good value and
627
+ * `jsonErrors[key]` is set, which surfaces inline and disables confirm.
628
+ *
629
+ * @param {object} field Field definition.
630
+ * @param {string} newString Current editor content.
631
+ */
632
+ onJsonFieldInput(field, newString) {
633
+ this.$set(this.jsonDrafts, field.key, newString)
634
+ const trimmed = (newString || '').trim()
635
+ if (!trimmed) {
636
+ this.updateField(field.key, null)
637
+ this.$delete(this.jsonErrors, field.key)
638
+ return
639
+ }
640
+ try {
641
+ const parsed = JSON.parse(trimmed)
642
+ this.updateField(field.key, parsed)
643
+ this.$delete(this.jsonErrors, field.key)
644
+ } catch (e) {
645
+ this.$set(this.jsonErrors, field.key, t('nextcloud-vue', 'Invalid JSON: {msg}', { msg: e.message }))
646
+ }
647
+ },
648
+
538
649
  getEnumOptions(field) {
539
650
  if (!field.enum) return []
540
651
  const labels = field.enumLabels || {}
@@ -835,6 +946,7 @@ export default {
835
946
 
836
947
  executeConfirm() {
837
948
  if (!this.validate()) return
949
+ if (!this.jsonFieldsValid) return
838
950
 
839
951
  this.loading = true
840
952
  /**
@@ -888,7 +1000,8 @@ export default {
888
1000
  }
889
1001
 
890
1002
  .cn-form-dialog__textarea-wrapper,
891
- .cn-form-dialog__select-wrapper {
1003
+ .cn-form-dialog__select-wrapper,
1004
+ .cn-form-dialog__json-wrapper {
892
1005
  display: flex;
893
1006
  flex-direction: column;
894
1007
  gap: 4px;
@@ -122,6 +122,7 @@
122
122
  <!-- Form dialog for create/edit (overridable via slot) -->
123
123
  <slot
124
124
  name="form-dialog"
125
+ :show="showFormDialogVisible"
125
126
  :item="editItem"
126
127
  :schema="schema"
127
128
  :close="closeFormDialog">
@@ -164,6 +164,7 @@
164
164
  </template>
165
165
 
166
166
  <script>
167
+ import { translate as t } from '@nextcloud/l10n'
167
168
  import { NcAppSidebar, NcAppSidebarTab, NcTextField, NcSelect, NcCheckboxRadioSwitch, NcPopover, NcButton } from '@nextcloud/vue'
168
169
  import Magnify from 'vue-material-design-icons/Magnify.vue'
169
170
  import FormatColumns from 'vue-material-design-icons/FormatColumns.vue'
@@ -272,37 +273,37 @@ export default {
272
273
  /** Search input placeholder */
273
274
  searchPlaceholder: {
274
275
  type: String,
275
- default: 'Type to search...',
276
+ default: () => t('nextcloud-vue', 'Type to search...'),
276
277
  },
277
278
  /** Search tab label */
278
279
  searchTabLabel: {
279
280
  type: String,
280
- default: 'Search',
281
+ default: () => t('nextcloud-vue', 'Search'),
281
282
  },
282
283
  /** Columns tab label */
283
284
  columnsTabLabel: {
284
285
  type: String,
285
- default: 'Columns',
286
+ default: () => t('nextcloud-vue', 'Columns'),
286
287
  },
287
288
  /** Search section heading */
288
289
  searchLabel: {
289
290
  type: String,
290
- default: 'Search',
291
+ default: () => t('nextcloud-vue', 'Search'),
291
292
  },
292
293
  /** Filters section heading */
293
294
  filtersLabel: {
294
295
  type: String,
295
- default: 'Filters',
296
+ default: () => t('nextcloud-vue', 'Filters'),
296
297
  },
297
298
  /** Columns section heading */
298
299
  columnsHeading: {
299
300
  type: String,
300
- default: 'Column Visibility',
301
+ default: () => t('nextcloud-vue', 'Column visibility'),
301
302
  },
302
303
  /** Columns section description */
303
304
  columnsDescription: {
304
305
  type: String,
305
- default: 'Select which columns to display in the table',
306
+ default: () => t('nextcloud-vue', 'Select which columns to display in the table'),
306
307
  },
307
308
  /** Override label for the schema properties group. Defaults to schema.title. */
308
309
  propertiesGroupLabel: {
@@ -27,8 +27,8 @@
27
27
  Format JSON
28
28
  </NcButton>
29
29
  </div>
30
- <span v-if="!readOnly && resolvedLanguage === 'json' && !isValidJson(value)" class="cn-json-viewer__error">
31
- Invalid JSON format
30
+ <span v-if="shouldShowError" class="cn-json-viewer__error">
31
+ {{ resolvedErrorText }}
32
32
  </span>
33
33
  </div>
34
34
  </template>
@@ -89,6 +89,15 @@ export default {
89
89
  default: 'auto',
90
90
  validator: (v) => ['json', 'xml', 'html', 'text', 'auto'].includes(v),
91
91
  },
92
+ /**
93
+ * Custom text for the error banner rendered below the editor.
94
+ * - `null` (default): the built-in "Invalid JSON format" banner renders
95
+ * whenever `language === 'json'` and the content fails to parse.
96
+ * - Any string: the caller owns the banner — it renders when this
97
+ * string is non-empty, and is hidden when empty. Use this to surface
98
+ * a richer parse error (e.g. the exception message).
99
+ */
100
+ errorText: { type: String, default: null },
92
101
  },
93
102
 
94
103
  data() {
@@ -169,6 +178,28 @@ export default {
169
178
  if (this.langExtension) exts.push(this.langExtension)
170
179
  return exts
171
180
  },
181
+ /**
182
+ * Error text displayed in the banner. Caller-provided `errorText` wins;
183
+ * otherwise falls back to the built-in "Invalid JSON format" message.
184
+ * @return {string} Message to show.
185
+ */
186
+ resolvedErrorText() {
187
+ if (this.errorText !== null) return this.errorText
188
+ return 'Invalid JSON format'
189
+ },
190
+ /**
191
+ * Whether to show the error banner.
192
+ * - If `errorText` is supplied, the caller controls visibility via its
193
+ * emptiness.
194
+ * - Otherwise, show iff the content is editable JSON and fails to parse.
195
+ * @return {boolean} Visibility flag.
196
+ */
197
+ shouldShowError() {
198
+ if (this.errorText !== null) return this.errorText !== ''
199
+ return !this.readOnly
200
+ && this.resolvedLanguage === 'json'
201
+ && !this.isValidJson(this.value)
202
+ },
172
203
  },
173
204
 
174
205
  methods: {
@@ -214,8 +245,6 @@ export default {
214
245
  }
215
246
 
216
247
  .cn-json-viewer__codemirror {
217
- border: 1px solid var(--color-border);
218
- border-radius: var(--border-radius);
219
248
  position: relative;
220
249
  }
221
250
 
@@ -144,7 +144,7 @@ export default {
144
144
  /** Label template for the menu button. Use {count} for the count. */
145
145
  menuLabelTemplate: {
146
146
  type: String,
147
- default: () => t('nextcloud-vue', 'Mass Actions ({count})'),
147
+ default: () => t('nextcloud-vue', 'Mass actions ({count})'),
148
148
  },
149
149
  importLabel: { type: String, default: () => t('nextcloud-vue', 'Import') },
150
150
  exportLabel: { type: String, default: () => t('nextcloud-vue', 'Export') },
@@ -228,7 +228,7 @@ export default {
228
228
  /** Dialog title */
229
229
  dialogTitle: {
230
230
  type: String,
231
- default: 'Import Data',
231
+ default: () => t('nextcloud-vue', 'Import data'),
232
232
  },
233
233
  /** Accepted file types (input accept attribute) */
234
234
  acceptedTypes: {
@@ -271,7 +271,7 @@ export default {
271
271
  },
272
272
  summaryTitle: { type: String, default: () => t('nextcloud-vue', 'Import summary') },
273
273
  supportedFormatsLabel: { type: String, default: () => t('nextcloud-vue', 'Supported file types:') },
274
- selectFileLabel: { type: String, default: () => t('nextcloud-vue', 'Select File') },
274
+ selectFileLabel: { type: String, default: () => t('nextcloud-vue', 'Select file') },
275
275
  cancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },
276
276
  closeLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },
277
277
  confirmLabel: { type: String, default: () => t('nextcloud-vue', 'Import') },
@@ -83,6 +83,7 @@
83
83
  </template>
84
84
 
85
85
  <script>
86
+ import { translate as t } from '@nextcloud/l10n'
86
87
  import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
87
88
  import CommentTextOutline from 'vue-material-design-icons/CommentTextOutline.vue'
88
89
  import Send from 'vue-material-design-icons/Send.vue'
@@ -158,12 +159,12 @@ export default {
158
159
  },
159
160
 
160
161
  // --- Pre-translated labels ---
161
- titleLabel: { type: String, default: 'Notes' },
162
- addNoteLabel: { type: String, default: 'Add note' },
163
- addNotePlaceholder: { type: String, default: 'Write a note...' },
164
- noNotesLabel: { type: String, default: 'No notes yet' },
165
- showAllLabel: { type: String, default: 'Show all' },
166
- deleteLabel: { type: String, default: 'Delete note' },
162
+ titleLabel: { type: String, default: () => t('nextcloud-vue', 'Notes') },
163
+ addNoteLabel: { type: String, default: () => t('nextcloud-vue', 'Add note') },
164
+ addNotePlaceholder: { type: String, default: () => t('nextcloud-vue', 'Write a note...') },
165
+ noNotesLabel: { type: String, default: () => t('nextcloud-vue', 'No notes yet') },
166
+ showAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },
167
+ deleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete note') },
167
168
  },
168
169
 
169
170
  emits: ['note-added', 'note-deleted', 'show-all'],
@@ -219,6 +219,7 @@
219
219
  </template>
220
220
 
221
221
  <script>
222
+ import { translate as t } from '@nextcloud/l10n'
222
223
  import { NcButton, NcLoadingIcon, NcTextField, NcSelect, NcCheckboxRadioSwitch } from '@nextcloud/vue'
223
224
  import { CnDetailCard } from '../CnDetailCard/index.js'
224
225
  import ContentSaveOutline from 'vue-material-design-icons/ContentSaveOutline.vue'
@@ -274,7 +275,7 @@ export default {
274
275
  /** Widget title shown in the card header */
275
276
  title: {
276
277
  type: String,
277
- default: 'Data',
278
+ default: () => t('nextcloud-vue', 'Data'),
278
279
  },
279
280
  /** Optional MDI icon component for the header */
280
281
  icon: {
@@ -326,7 +327,7 @@ export default {
326
327
  * - `label` (string) — Override the display label
327
328
  * - `widget` (string) — Override the widget type for editing
328
329
  *
329
- * @type {Object<string, { order?: number, gridColumn?: number, gridRow?: number, hidden?: boolean, editable?: boolean, label?: string, widget?: string }>}
330
+ * @type {{ [key: string]: { order?: number, gridColumn?: number, gridRow?: number, hidden?: boolean, editable?: boolean, label?: string, widget?: string } }}
330
331
  */
331
332
  overrides: {
332
333
  type: Object,
@@ -366,17 +367,17 @@ export default {
366
367
  /** Label for the save button */
367
368
  saveLabel: {
368
369
  type: String,
369
- default: 'Save',
370
+ default: () => t('nextcloud-vue', 'Save'),
370
371
  },
371
372
  /** Label for the discard button */
372
373
  discardLabel: {
373
374
  type: String,
374
- default: 'Discard',
375
+ default: () => t('nextcloud-vue', 'Discard'),
375
376
  },
376
377
  /** Label shown when no properties to display */
377
378
  emptyLabel: {
378
379
  type: String,
379
- default: 'No data available',
380
+ default: () => t('nextcloud-vue', 'No data available'),
380
381
  },
381
382
  },
382
383
 
@@ -502,7 +503,7 @@ export default {
502
503
  methods: {
503
504
  /**
504
505
  * Check if a field is editable.
505
- * @param field
506
+ * @param {object} field - Resolved field definition from resolvedFields
506
507
  */
507
508
  isEditable(field) {
508
509
  if (!this.editable) return false
@@ -517,7 +518,7 @@ export default {
517
518
 
518
519
  /**
519
520
  * Check if a field's current value is empty.
520
- * @param key
521
+ * @param {string} key - Field key to check
521
522
  */
522
523
  isValueEmpty(key) {
523
524
  const val = key in this.dirtyFields
@@ -528,7 +529,7 @@ export default {
528
529
 
529
530
  /**
530
531
  * Start inline editing for a field.
531
- * @param field
532
+ * @param {object} field - Resolved field definition from resolvedFields
532
533
  */
533
534
  startEdit(field) {
534
535
  // Set working value: dirty value > current object value
@@ -555,8 +556,8 @@ export default {
555
556
 
556
557
  /**
557
558
  * Update the working edit value for a field.
558
- * @param key
559
- * @param value
559
+ * @param {string} key - Field key to update
560
+ * @param {*} value - New value for the field
560
561
  */
561
562
  updateField(key, value) {
562
563
  this.editData = { ...this.editData, [key]: value }
@@ -27,6 +27,7 @@
27
27
  </template>
28
28
 
29
29
  <script>
30
+ import { translate as t } from '@nextcloud/l10n'
30
31
  import { CnDetailCard } from '../CnDetailCard/index.js'
31
32
  import { CnDetailGrid } from '../CnDetailGrid/index.js'
32
33
 
@@ -87,7 +88,7 @@ export default {
87
88
  /** Widget title shown in the card header */
88
89
  title: {
89
90
  type: String,
90
- default: 'Metadata',
91
+ default: () => t('nextcloud-vue', 'Metadata'),
91
92
  },
92
93
  /** Optional MDI icon component for the header */
93
94
  icon: {
@@ -160,7 +161,7 @@ export default {
160
161
  /** Label shown when no metadata available */
161
162
  emptyLabel: {
162
163
  type: String,
163
- default: 'No metadata available',
164
+ default: () => t('nextcloud-vue', 'No metadata available'),
164
165
  },
165
166
  },
166
167