@conduction/nextcloud-vue 0.1.0-beta.13 → 0.1.0-beta.14

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 (30) hide show
  1. package/dist/nextcloud-vue.cjs.js +20518 -1290
  2. package/dist/nextcloud-vue.cjs.js.map +1 -1
  3. package/dist/nextcloud-vue.css +470 -185
  4. package/dist/nextcloud-vue.esm.js +20518 -1290
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/l10n/en.json +255 -2
  7. package/l10n/nl.json +247 -2
  8. package/package.json +2 -1
  9. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +1 -4
  10. package/src/components/CnContextMenu/CnContextMenu.vue +1 -1
  11. package/src/components/CnDataTable/CnDataTable.vue +7 -2
  12. package/src/components/CnInfoWidget/CnInfoWidget.vue +0 -1
  13. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +2 -2
  14. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +2 -2
  15. package/src/components/CnRowActions/CnRowActions.vue +1 -1
  16. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +36 -34
  17. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +47 -36
  18. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +29 -22
  19. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +170 -163
  20. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +473 -116
  21. package/src/components/CnStatsBlock/CnStatsBlock.vue +18 -18
  22. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +12 -0
  23. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +7 -7
  24. package/src/composables/useContextMenu.js +1 -1
  25. package/src/css/CnSchemaFormDialog.css +258 -2
  26. package/src/css/dashboard.css +1 -0
  27. package/src/css/detail-page.css +5 -5
  28. package/src/css/index.css +1 -0
  29. package/src/css/patches.css +20 -0
  30. package/src/store/plugins/search.js +7 -7
@@ -1,18 +1,18 @@
1
1
  <template>
2
2
  <div class="cn-schema-form__form-editor">
3
3
  <NcTextArea :disabled="loading"
4
- label="Description"
4
+ :label="t('nextcloud-vue', 'Description')"
5
5
  :value.sync="schema.description" />
6
6
  <NcTextArea :disabled="loading"
7
- label="Summary"
7
+ :label="t('nextcloud-vue', 'Summary')"
8
8
  :value.sync="schema.summary" />
9
9
  <NcTextField :disabled="loading"
10
- label="Slug"
10
+ :label="t('nextcloud-vue', 'Slug')"
11
11
  :value.sync="schema.slug" />
12
12
 
13
13
  <!-- Schema Composition Section -->
14
14
  <div>
15
- <h3>Schema Composition (JSON Schema)</h3>
15
+ <h3>{{ t('nextcloud-vue', 'Schema composition (JSON Schema)') }}</h3>
16
16
 
17
17
  <!-- allOf - Multiple Inheritance (Recommended) -->
18
18
  <NcSelect
@@ -24,8 +24,8 @@
24
24
  :close-on-select="false"
25
25
  label="title"
26
26
  track-by="id"
27
- input-label="allOf - Inherits from ALL schemas (Recommended)"
28
- placeholder="Select schemas to inherit from (supports multiple parents)">
27
+ :input-label="t('nextcloud-vue', 'allOf - Inherits from ALL schemas (Recommended)')"
28
+ :placeholder="t('nextcloud-vue', 'Select schemas to inherit from (supports multiple parents)')">
29
29
  <template #option="{ title, description }">
30
30
  <div class="cn-schema-form__schema-option">
31
31
  <span class="cn-schema-form__option-title">{{ title }}</span>
@@ -34,11 +34,11 @@
34
34
  </template>
35
35
  </NcSelect>
36
36
  <NcNoteCard v-if="schema.allOf && schema.allOf.length > 0" type="info">
37
- <p><strong>allOf - Multiple Inheritance</strong></p>
38
- <p>Instance must validate against ALL selected schemas. Properties from all parent schemas are merged. This is the recommended pattern for schema extension and multiple inheritance.</p>
39
- <p><strong>Important:</strong> Child schemas can only ADD constraints, never relax them (Liskov Substitution Principle). Metadata (title, description, order) can be overridden.</p>
37
+ <p><strong>{{ t('nextcloud-vue', 'allOf - Multiple inheritance') }}</strong></p>
38
+ <p>{{ t('nextcloud-vue', 'Instance must validate against all selected schemas. Properties from all parent schemas are merged. This is the recommended pattern for schema extension and multiple inheritance.') }}</p>
39
+ <p><strong>{{ t('nextcloud-vue', 'Important:') }}</strong> {{ t('nextcloud-vue', 'Child schemas can only add constraints, never relax them (Liskov substitution principle). Metadata (title, description, order) can be overridden.') }}</p>
40
40
  <div v-if="allOfSchemaNames.length > 0">
41
- <strong>Parent Schemas:</strong>
41
+ <strong>{{ t('nextcloud-vue', 'Parent schemas:') }}</strong>
42
42
  <ul>
43
43
  <li v-for="name in allOfSchemaNames" :key="name">
44
44
  {{ name }}
@@ -57,8 +57,8 @@
57
57
  :close-on-select="false"
58
58
  label="title"
59
59
  track-by="id"
60
- input-label="oneOf - Exactly ONE schema must match"
61
- placeholder="Select schemas (instance must match exactly one)">
60
+ :input-label="t('nextcloud-vue', 'oneOf - Exactly one schema must match')"
61
+ :placeholder="t('nextcloud-vue', 'Select schemas (instance must match exactly one)')">
62
62
  <template #option="{ title, description }">
63
63
  <div class="cn-schema-form__schema-option">
64
64
  <span class="cn-schema-form__option-title">{{ title }}</span>
@@ -67,8 +67,8 @@
67
67
  </template>
68
68
  </NcSelect>
69
69
  <NcNoteCard v-if="schema.oneOf && schema.oneOf.length > 0" type="info">
70
- <p><strong>oneOf - Mutually Exclusive</strong></p>
71
- <p>Instance must validate against EXACTLY ONE of the selected schemas. Properties are NOT merged. Use for discriminated unions or type variants.</p>
70
+ <p><strong>{{ t('nextcloud-vue', 'oneOf - Mutually exclusive') }}</strong></p>
71
+ <p>{{ t('nextcloud-vue', 'Instance must validate against exactly one of the selected schemas. Properties are not merged. Use for discriminated unions or type variants.') }}</p>
72
72
  </NcNoteCard>
73
73
 
74
74
  <!-- anyOf - Flexible Composition -->
@@ -81,8 +81,8 @@
81
81
  :close-on-select="false"
82
82
  label="title"
83
83
  track-by="id"
84
- input-label="anyOf - At least ONE schema must match"
85
- placeholder="Select schemas (instance must match at least one)">
84
+ :input-label="t('nextcloud-vue', 'anyOf - At least one schema must match')"
85
+ :placeholder="t('nextcloud-vue', 'Select schemas (instance must match at least one)')">
86
86
  <template #option="{ title, description }">
87
87
  <div class="cn-schema-form__schema-option">
88
88
  <span class="cn-schema-form__option-title">{{ title }}</span>
@@ -91,73 +91,74 @@
91
91
  </template>
92
92
  </NcSelect>
93
93
  <NcNoteCard v-if="schema.anyOf && schema.anyOf.length > 0" type="info">
94
- <p><strong>anyOf - Flexible Composition</strong></p>
95
- <p>Instance must validate against AT LEAST ONE of the selected schemas. Properties are NOT merged. More permissive than oneOf.</p>
94
+ <p><strong>{{ t('nextcloud-vue', 'anyOf - Flexible composition') }}</strong></p>
95
+ <p>{{ t('nextcloud-vue', 'Instance must validate against at least one of the selected schemas. Properties are not merged. More permissive than oneOf.') }}</p>
96
96
  </NcNoteCard>
97
97
  </div>
98
98
  <NcSelect
99
99
  v-model="schema.configuration.objectNameField"
100
100
  :disabled="loading"
101
101
  :options="propertyOptions"
102
- input-label="Object Name Field"
103
- placeholder="Select a property to use as object name" />
102
+ :input-label="t('nextcloud-vue', 'Object name field')"
103
+ :placeholder="t('nextcloud-vue', 'Select a property to use as object name')" />
104
104
  <NcSelect
105
105
  v-model="schema.configuration.objectDescriptionField"
106
106
  :disabled="loading"
107
107
  :options="propertyOptions"
108
- input-label="Object Description Field"
109
- placeholder="Select a property to use as object description" />
108
+ :input-label="t('nextcloud-vue', 'Object description field')"
109
+ :placeholder="t('nextcloud-vue', 'Select a property to use as object description')" />
110
110
  <NcSelect
111
111
  v-model="schema.configuration.objectImageField"
112
112
  :disabled="loading"
113
113
  :options="propertyOptions"
114
- input-label="Object Image Field"
115
- placeholder="Select a property to use as object image representing the object. e.g. logo (should contain base64 encoded image)" />
114
+ :input-label="t('nextcloud-vue', 'Object image field')"
115
+ :placeholder="t('nextcloud-vue', 'Select a property to use as object image representing the object. e.g. logo (should contain base64 encoded image)')" />
116
116
  <NcSelect
117
117
  v-model="schema.configuration.objectSummaryField"
118
118
  :disabled="loading"
119
119
  :options="propertyOptions"
120
- input-label="Object Summary Field"
121
- placeholder="Select a property to use as object summary. e.g. summary, abstract, or excerpt" />
120
+ :input-label="t('nextcloud-vue', 'Object summary field')"
121
+ :placeholder="t('nextcloud-vue', 'Select a property to use as object summary. e.g. summary, abstract, or excerpt')" />
122
122
  <NcCheckboxRadioSwitch
123
123
  :disabled="loading"
124
124
  :checked.sync="schema.configuration.allowFiles">
125
- Allow Files
125
+ {{ t('nextcloud-vue', 'Allow files') }}
126
126
  </NcCheckboxRadioSwitch>
127
127
  <NcCheckboxRadioSwitch
128
128
  :disabled="loading"
129
129
  :checked.sync="schema.configuration.autoPublish">
130
- Auto-Publish Objects
130
+ {{ t('nextcloud-vue', 'Auto-publish objects') }}
131
131
  </NcCheckboxRadioSwitch>
132
132
  <NcTextField
133
133
  v-model="allowedTagsInput"
134
134
  :disabled="loading"
135
- label="Allowed Tags (comma-separated)"
136
- placeholder="image, document, audio, video"
135
+ :label="t('nextcloud-vue', 'Allowed tags (comma-separated)')"
136
+ :placeholder="t('nextcloud-vue', 'image, document, audio, video')"
137
137
  @update:value="updateAllowedTags" />
138
138
  <NcCheckboxRadioSwitch
139
139
  :disabled="loading"
140
140
  :checked.sync="schema.hardValidation">
141
- Hard Validation
141
+ {{ t('nextcloud-vue', 'Hard validation') }}
142
142
  </NcCheckboxRadioSwitch>
143
143
  <NcTextField :disabled="loading"
144
- label="Max Depth"
144
+ :label="t('nextcloud-vue', 'Max depth')"
145
145
  type="number"
146
146
  :value.sync="schema.maxDepth" />
147
147
  <NcCheckboxRadioSwitch
148
148
  :disabled="loading"
149
149
  :checked.sync="schema.immutable">
150
- Immutable
150
+ {{ t('nextcloud-vue', 'Immutable') }}
151
151
  </NcCheckboxRadioSwitch>
152
152
  <NcCheckboxRadioSwitch
153
153
  :disabled="loading"
154
154
  :checked.sync="schema.searchable">
155
- Searchable in SOLR
155
+ {{ t('nextcloud-vue', 'Searchable in SOLR') }}
156
156
  </NcCheckboxRadioSwitch>
157
157
  </div>
158
158
  </template>
159
159
 
160
160
  <script>
161
+ import { translate as t } from '@nextcloud/l10n'
161
162
  import {
162
163
  NcTextField,
163
164
  NcTextArea,
@@ -213,6 +214,7 @@ export default {
213
214
  },
214
215
  },
215
216
  methods: {
217
+ t,
216
218
  updateAllowedTags(value) {
217
219
  if (!value || value.trim() === '') {
218
220
  this.$set(this.schema.configuration, 'allowedTags', [])
@@ -6,7 +6,8 @@
6
6
  :dialog-title="dialogTitle"
7
7
  entity-name="Schema"
8
8
  :size="size"
9
- :disable-save="!schemaItem.title"
9
+ :disable-save="!!saveDisabledReason"
10
+ :disable-save-tooltip="saveDisabledReason"
10
11
  :success-text="resolvedSuccessText"
11
12
  :cancel-label="cancelLabel"
12
13
  :close-label="closeLabel"
@@ -19,14 +20,14 @@
19
20
  <div v-if="schemaItem.id"
20
21
  class="cn-schema-form__detail-item cn-schema-form__id-card">
21
22
  <div class="cn-schema-form__id-card-header">
22
- <span class="cn-schema-form__detail-label">ID / UUID:</span>
23
+ <span class="cn-schema-form__detail-label">{{ t('nextcloud-vue', 'ID / UUID:') }}</span>
23
24
  <NcButton class="cn-schema-form__copy-button"
24
25
  @click="copyToClipboard(schemaItem.uuid || schemaItem.id)">
25
26
  <template #icon>
26
27
  <Check v-if="isCopied" :size="20" />
27
28
  <ContentCopy v-else :size="20" />
28
29
  </template>
29
- {{ isCopied ? 'Copied' : 'Copy' }}
30
+ {{ isCopied ? t('nextcloud-vue', 'Copied') : t('nextcloud-vue', 'Copy') }}
30
31
  </NcButton>
31
32
  </div>
32
33
  <span class="cn-schema-form__detail-value">{{ schemaItem.id }}</span>
@@ -35,7 +36,7 @@
35
36
  </div>
36
37
  <div class="cn-schema-form__detail-item cn-schema-form__title-with-badge">
37
38
  <NcTextField :disabled="dialogLoading"
38
- label="Title *"
39
+ :label="t('nextcloud-vue', 'Title *')"
39
40
  :value.sync="schemaItem.title" />
40
41
  <span v-if="schemaItem.allOf && schemaItem.allOf.length > 0"
41
42
  class="cn-schema-form__statusPill cn-schema-form__statusPill--success">
@@ -51,20 +52,20 @@
51
52
  </span>
52
53
  </div>
53
54
  <div v-if="schemaItem.created" class="cn-schema-form__detail-item">
54
- <span class="cn-schema-form__detail-label">Created:</span>
55
+ <span class="cn-schema-form__detail-label">{{ t('nextcloud-vue', 'Created:') }}</span>
55
56
  <span class="cn-schema-form__detail-value">{{ new Date(schemaItem.created).toLocaleString() }}</span>
56
57
  </div>
57
58
  <div v-if="schemaItem.updated" class="cn-schema-form__detail-item">
58
- <span class="cn-schema-form__detail-label">Updated:</span>
59
+ <span class="cn-schema-form__detail-label">{{ t('nextcloud-vue', 'Updated:') }}</span>
59
60
  <span class="cn-schema-form__detail-value">{{ new Date(schemaItem.updated).toLocaleString() }}</span>
60
61
  </div>
61
62
  <div class="cn-schema-form__detail-item">
62
- <span class="cn-schema-form__detail-label">Version:</span>
63
- <span class="cn-schema-form__detail-value">{{ schemaItem.version || 'Not set' }}</span>
63
+ <span class="cn-schema-form__detail-label">{{ t('nextcloud-vue', 'Version:') }}</span>
64
+ <span class="cn-schema-form__detail-value">{{ schemaItem.version || t('nextcloud-vue', 'Not set') }}</span>
64
65
  </div>
65
66
  <div class="cn-schema-form__detail-item">
66
- <span class="cn-schema-form__detail-label">Owner:</span>
67
- <span class="cn-schema-form__detail-value">{{ schemaItem.owner || 'Not set' }}</span>
67
+ <span class="cn-schema-form__detail-label">{{ t('nextcloud-vue', 'Owner:') }}</span>
68
+ <span class="cn-schema-form__detail-value">{{ schemaItem.owner || t('nextcloud-vue', 'Not set') }}</span>
68
69
  </div>
69
70
  </div>
70
71
  </template>
@@ -327,9 +328,9 @@ export default {
327
328
  */
328
329
  dialogTabs() {
329
330
  return [
330
- { id: 'properties', title: 'Properties' },
331
- { id: 'configuration', title: 'Configuration' },
332
- { id: 'security', title: 'Security' },
331
+ { id: 'properties', title: t('nextcloud-vue', 'Properties') },
332
+ { id: 'configuration', title: t('nextcloud-vue', 'Configuration') },
333
+ { id: 'security', title: t('nextcloud-vue', 'Security') },
333
334
  ]
334
335
  },
335
336
  sortedUserGroups() {
@@ -357,15 +358,15 @@ export default {
357
358
  },
358
359
  typeOptionsForSelect() {
359
360
  return [
360
- { id: 'string', label: 'String' },
361
- { id: 'number', label: 'Number' },
362
- { id: 'integer', label: 'Integer' },
363
- { id: 'boolean', label: 'Boolean' },
364
- { id: 'array', label: 'Array' },
365
- { id: 'object', label: 'Object' },
366
- { id: 'dictionary', label: 'Dictionary' },
367
- { id: 'file', label: 'File' },
368
- { id: 'oneOf', label: 'One Of' },
361
+ { id: 'string', label: t('nextcloud-vue', 'String') },
362
+ { id: 'number', label: t('nextcloud-vue', 'Number') },
363
+ { id: 'integer', label: t('nextcloud-vue', 'Integer') },
364
+ { id: 'boolean', label: t('nextcloud-vue', 'Boolean') },
365
+ { id: 'array', label: t('nextcloud-vue', 'Array') },
366
+ { id: 'object', label: t('nextcloud-vue', 'Object') },
367
+ { id: 'dictionary', label: t('nextcloud-vue', 'Dictionary') },
368
+ { id: 'file', label: t('nextcloud-vue', 'File') },
369
+ { id: 'oneOf', label: t('nextcloud-vue', 'One of') },
369
370
  ]
370
371
  },
371
372
  propertyOptions() {
@@ -385,8 +386,25 @@ export default {
385
386
  */
386
387
  resolvedSuccessText() {
387
388
  if (this.successText) return this.successText
388
- return 'Schema saved successfully.'
389
+ return t('nextcloud-vue', '{title} saved successfully.', { title: t('nextcloud-vue', 'Schema') })
389
390
  },
391
+ /**
392
+ * Returns a human-readable reason the save button is disabled, or '' when saving is allowed.
393
+ * Used for both :disable-save and the WCAG tooltip/aria-label on the button.
394
+ *
395
+ * @return {string}
396
+ */
397
+ saveDisabledReason() {
398
+ if (!this.schemaItem.title) {
399
+ return t('nextcloud-vue', 'A schema title is required before saving')
400
+ }
401
+ const hasUnnamedProperty = Object.keys(this.schemaItem.properties || {}).some(key => key === '')
402
+ if (hasUnnamedProperty) {
403
+ return t('nextcloud-vue', 'All properties must have a name before saving')
404
+ }
405
+ return ''
406
+ },
407
+
390
408
  allOfSchemaNames() {
391
409
  if (!this.schemaItem.allOf || !Array.isArray(this.schemaItem.allOf) || this.schemaItem.allOf.length === 0) {
392
410
  return []
@@ -485,6 +503,7 @@ export default {
485
503
  },
486
504
  },
487
505
  methods: {
506
+ t,
488
507
  findSchemaBySlug(schemaSlug) {
489
508
  if (!schemaSlug) return undefined
490
509
  return this.availableSchemas.find(schema =>
@@ -623,29 +642,21 @@ export default {
623
642
  },
624
643
 
625
644
  addProperty() {
626
- let newPropertyName = 'new'
627
- let counter = 1
628
-
629
- while (this.schemaItem.properties[newPropertyName]) {
630
- counter++
631
- newPropertyName = `new_${counter}`
632
- }
633
-
634
- this.$set(this.schemaItem.properties, newPropertyName, {
645
+ this.$set(this.schemaItem.properties, '', {
635
646
  type: 'string',
636
647
  format: '',
637
- title: newPropertyName,
648
+ title: '',
638
649
  description: '',
639
650
  facetable: false,
640
651
  })
641
652
 
642
653
  this.checkPropertiesModified()
643
- this.selectedProperty = newPropertyName
654
+ this.selectedProperty = ''
644
655
  },
645
656
 
646
657
  updatePropertyKey(oldKey, newKey) {
647
- if (!newKey || newKey === oldKey) return
648
- if (this.schemaItem.properties[newKey] && newKey !== oldKey) return
658
+ if (newKey === oldKey) return
659
+ if (this.schemaItem.properties[newKey] !== undefined && newKey !== oldKey) return
649
660
 
650
661
  const propertyData = { ...this.schemaItem.properties[oldKey] }
651
662
 
@@ -7,7 +7,8 @@
7
7
  row-key="_id"
8
8
  :selectable="false"
9
9
  :row-class="getRowClass"
10
- :empty-text="'No properties found. Click &quot;Add property&quot; to create one.'"
10
+ :cell-class="getCellClass"
11
+ :empty-text="t('nextcloud-vue', 'No properties found. Click &quot;Add property&quot; to create one.')"
11
12
  @row-click="onRowClick">
12
13
  <template #actions-header>
13
14
  <NcButton
@@ -17,7 +18,7 @@
17
18
  <template #icon>
18
19
  <Plus :size="20" />
19
20
  </template>
20
- Add property
21
+ {{ t('nextcloud-vue', 'Add property') }}
21
22
  </NcButton>
22
23
  </template>
23
24
 
@@ -26,11 +27,11 @@
26
27
  <AlertOutline v-if="isPropertyModified(row._key)"
27
28
  :size="16"
28
29
  class="cn-schema-form__warning-icon"
29
- :title="'Property has been modified. Changes will only take effect after the schema is saved.'" />
30
+ :title="t('nextcloud-vue', 'Property has been modified. Changes will only take effect after the schema is saved.')" />
30
31
  <NcTextField
31
32
  ref="propertyNameInput"
32
33
  :value="row._key"
33
- label="(technical) Property Name"
34
+ :label="t('nextcloud-vue', '(technical) Property name')"
34
35
  @update:value="onPropertyKeyUpdate(row._key, $event)"
35
36
  @click.stop />
36
37
  </div>
@@ -38,30 +39,30 @@
38
39
  <AlertOutline v-if="isPropertyModified(row._key)"
39
40
  :size="16"
40
41
  class="cn-schema-form__warning-icon"
41
- :title="'Property has been modified. Changes will only take effect after the schema is saved.'" />
42
+ :title="t('nextcloud-vue', 'Property has been modified. Changes will only take effect after the schema is saved.')" />
42
43
  <div class="cn-schema-form__name-with-chips">
43
44
  <span class="cn-schema-form__property-name">{{ row._key }}</span>
44
45
  <div class="cn-schema-form__inline-chips">
45
46
  <span v-if="isPropertyRequired(schema, row._key)"
46
- class="cn-schema-form__property-chip cn-schema-form__chip-primary">Required</span>
47
+ class="cn-schema-form__property-chip cn-schema-form__chip-primary">{{ t('nextcloud-vue', 'Required') }}</span>
47
48
  <span v-if="row.immutable"
48
- class="cn-schema-form__property-chip cn-schema-form__chip-secondary">Immutable</span>
49
+ class="cn-schema-form__property-chip cn-schema-form__chip-secondary">{{ t('nextcloud-vue', 'Immutable') }}</span>
49
50
  <span v-if="row.deprecated"
50
- class="cn-schema-form__property-chip cn-schema-form__chip-warning">Deprecated</span>
51
+ class="cn-schema-form__property-chip cn-schema-form__chip-warning">{{ t('nextcloud-vue', 'Deprecated') }}</span>
51
52
  <span v-if="row.visible === false"
52
- class="cn-schema-form__property-chip cn-schema-form__chip-secondary">Hidden in view</span>
53
+ class="cn-schema-form__property-chip cn-schema-form__chip-secondary">{{ t('nextcloud-vue', 'Hidden in view') }}</span>
53
54
  <span v-if="row.hideOnCollection"
54
- class="cn-schema-form__property-chip cn-schema-form__chip-secondary">Hidden in Collection</span>
55
+ class="cn-schema-form__property-chip cn-schema-form__chip-secondary">{{ t('nextcloud-vue', 'Hidden in collection') }}</span>
55
56
  <span v-if="row.hideOnForm"
56
- class="cn-schema-form__property-chip cn-schema-form__chip-secondary">Hidden in Form</span>
57
+ class="cn-schema-form__property-chip cn-schema-form__chip-secondary">{{ t('nextcloud-vue', 'Hidden in form') }}</span>
57
58
  <span v-if="row.const !== undefined"
58
- class="cn-schema-form__property-chip cn-schema-form__chip-success">Constant</span>
59
+ class="cn-schema-form__property-chip cn-schema-form__chip-success">{{ t('nextcloud-vue', 'Constant') }}</span>
59
60
  <span v-if="row.enum && row.enum.length > 0"
60
- class="cn-schema-form__property-chip cn-schema-form__chip-success">Enumeration ({{ row.enum.length }})</span>
61
+ class="cn-schema-form__property-chip cn-schema-form__chip-success">{{ t('nextcloud-vue', 'Enumeration ({count})', { count: row.enum.length }) }}</span>
61
62
  <span v-if="row.facetable === true || (typeof row.facetable === 'object' && row.facetable !== null)"
62
- class="cn-schema-form__property-chip cn-schema-form__chip-info">Facetable</span>
63
+ class="cn-schema-form__property-chip cn-schema-form__chip-info">{{ t('nextcloud-vue', 'Facetable') }}</span>
63
64
  <span v-if="hasCustomTableSettings(row._key)"
64
- class="cn-schema-form__property-chip cn-schema-form__chip-table">Table</span>
65
+ class="cn-schema-form__property-chip cn-schema-form__chip-table">{{ t('nextcloud-vue', 'Table') }}</span>
65
66
  </div>
66
67
  </div>
67
68
  </div>
@@ -72,7 +73,7 @@
72
73
  v-if="selectedProperty === row._key"
73
74
  v-model="schema.properties[row._key].type"
74
75
  :options="typeOptionsForSelect"
75
- input-label="Property Type"
76
+ :input-label="t('nextcloud-vue', 'Property type')"
76
77
  @click.stop />
77
78
  <span v-else>{{ row.type }}</span>
78
79
  </template>
@@ -95,13 +96,14 @@
95
96
  </CnDataTable>
96
97
  </div>
97
98
  <CnNoteCard v-if="propertiesModified && !loading" type="warning" class="cn-schema-form__properties-warning">
98
- <p>Properties have been modified. Changes will only take effect after the schema is saved.</p>
99
+ <p>{{ t('nextcloud-vue', 'Properties have been modified. Changes will only take effect after the schema is saved.') }}</p>
99
100
  </CnNoteCard>
100
101
  </Fragment>
101
102
  </template>
102
103
 
103
104
  <!-- eslint-disable jsdoc/valid-types -->
104
105
  <script>
106
+ import { translate as t } from '@nextcloud/l10n'
105
107
  import { NcButton, NcTextField, NcSelect } from '@nextcloud/vue'
106
108
  import { CnDataTable } from '../CnDataTable/index.js'
107
109
  import { CnNoteCard } from '../CnNoteCard/index.js'
@@ -166,8 +168,8 @@ export default {
166
168
  nextPropertyId: 1,
167
169
  isRenaming: false,
168
170
  tableColumns: [
169
- { key: '_key', label: 'Name', sortable: false },
170
- { key: 'type', label: 'Type', sortable: false },
171
+ { key: '_key', label: t('nextcloud-vue', 'Name'), sortable: false },
172
+ { key: 'type', label: t('nextcloud-vue', 'Type'), sortable: false },
171
173
  ],
172
174
  }
173
175
  },
@@ -202,7 +204,7 @@ export default {
202
204
  },
203
205
  watch: {
204
206
  selectedProperty(newKey) {
205
- if (newKey) {
207
+ if (newKey !== null) {
206
208
  // Skip focus+select when the change comes from a rename —
207
209
  // onPropertyKeyUpdate handles its own cursor positioning.
208
210
  if (this.isRenaming) {
@@ -227,6 +229,7 @@ export default {
227
229
  },
228
230
  },
229
231
  methods: {
232
+ t,
230
233
  getStablePropertyId(propertyName) {
231
234
  if (!this.propertyStableIds[propertyName]) {
232
235
  this.propertyStableIds[propertyName] = this.nextPropertyId++
@@ -265,14 +268,18 @@ export default {
265
268
  return classes.join(' ')
266
269
  },
267
270
 
271
+ getCellClass(row) {
272
+ return this.selectedProperty === row._key ? 'cn-schema-form__editing-cell' : ''
273
+ },
274
+
268
275
  onRowClick(row) {
269
276
  if (this.selectedProperty === row._key) return
270
277
  this.$emit('update:selected-property', row._key)
271
278
  },
272
279
 
273
280
  onPropertyKeyUpdate(oldKey, newKey) {
274
- if (!newKey || newKey === oldKey) return
275
- if (this.schema.properties[newKey] && newKey !== oldKey) return
281
+ if (newKey === oldKey) return
282
+ if (this.schema.properties[newKey] !== undefined && newKey !== oldKey) return
276
283
 
277
284
  this.isRenaming = true
278
285