@adminforth/i18n 1.2.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.log +2 -2
- package/custom/ListCell.vue +187 -12
- package/dist/custom/ListCell.vue +187 -12
- package/dist/index.js +59 -1
- package/index.ts +72 -1
- package/package.json +1 -1
- package/types.ts +7 -0
package/build.log
CHANGED
|
@@ -16,5 +16,5 @@ custom/package-lock.json
|
|
|
16
16
|
custom/package.json
|
|
17
17
|
custom/tsconfig.json
|
|
18
18
|
|
|
19
|
-
sent
|
|
20
|
-
total size is
|
|
19
|
+
sent 30,826 bytes received 229 bytes 62,110.00 bytes/sec
|
|
20
|
+
total size is 29,971 speedup is 0.97
|
package/custom/ListCell.vue
CHANGED
|
@@ -1,25 +1,200 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
|
|
2
|
+
<div class="relative group flex items-center" @click.stop>
|
|
3
|
+
<!-- Normal value display -->
|
|
4
|
+
<div v-if="!isEditing" class="flex items-center" :class="limitedText?.length > 50 ? 'min-w-48 max-w-full' : 'min-w-32'">
|
|
5
|
+
{{ limitedText? limitedText : '-' }}
|
|
6
|
+
|
|
7
|
+
<span v-if="meta?.reviewedCheckboxesFieldName && limitedText" class="flex items-center ml-2">
|
|
8
|
+
<Tooltip
|
|
9
|
+
>
|
|
10
|
+
<template #tooltip>
|
|
11
|
+
{{ record[meta?.reviewedCheckboxesFieldName]?.[props.column.name] ? t('Translation is reviewed') : t('Translation is not reviewed') }}
|
|
12
|
+
</template>
|
|
13
|
+
<IconCheckOutline
|
|
14
|
+
v-if="record[meta?.reviewedCheckboxesFieldName]?.[props.column.name]"
|
|
15
|
+
class="w-5 h-5 text-green-500"
|
|
16
|
+
/>
|
|
17
|
+
<IconQuestionCircleSolid
|
|
18
|
+
v-else
|
|
19
|
+
class="w-5 h-5 text-yellow-500"
|
|
20
|
+
/>
|
|
21
|
+
</Tooltip>
|
|
22
|
+
</span>
|
|
23
|
+
|
|
24
|
+
<button
|
|
25
|
+
v-if="!column.editReadonly"
|
|
26
|
+
@click="startEdit"
|
|
27
|
+
class="ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
28
|
+
>
|
|
29
|
+
<IconPenSolid class="w-5 h-5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"/>
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Edit mode -->
|
|
34
|
+
<div v-else class="flex flex-col gap-2">
|
|
35
|
+
<div class="flex items-center max-w-full gap-2"
|
|
36
|
+
:class="limitedText?.length > 50 ? 'min-w-72' : 'min-w-64'"
|
|
37
|
+
ref="inputHolder"
|
|
38
|
+
>
|
|
39
|
+
<ColumnValueInputWrapper
|
|
40
|
+
class="flex-grow"
|
|
41
|
+
ref="input"
|
|
42
|
+
:source="'edit'"
|
|
43
|
+
:column="column"
|
|
44
|
+
:currentValues="currentValues"
|
|
45
|
+
:mode="mode"
|
|
46
|
+
:columnOptions="columnOptions"
|
|
47
|
+
:unmasked="unmasked"
|
|
48
|
+
:setCurrentValue="setCurrentValue"
|
|
49
|
+
/>
|
|
50
|
+
<div class="flex gap-1">
|
|
51
|
+
<button
|
|
52
|
+
@click="saveEdit"
|
|
53
|
+
:disabled="saving || (originalReviewed === reviewed && originalValue === currentValues[props.column.name])"
|
|
54
|
+
class="text-green-600 hover:text-green-700 dark:text-green-500 dark:hover:text-green-400 disabled:opacity-50"
|
|
55
|
+
|
|
56
|
+
>
|
|
57
|
+
<IconCheckOutline class="w-5 h-5" />
|
|
58
|
+
</button>
|
|
59
|
+
<button
|
|
60
|
+
@click="cancelEdit"
|
|
61
|
+
:disabled="saving"
|
|
62
|
+
class="text-red-600 hover:text-red-700 dark:text-red-500 dark:hover:text-red-400"
|
|
63
|
+
>
|
|
64
|
+
<IconXOutline class="w-5 h-5" />
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div v-if="meta?.reviewedCheckboxesFieldName">
|
|
69
|
+
<label class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-0 cursor-pointer select-none">
|
|
70
|
+
<Checkbox
|
|
71
|
+
v-model="reviewed"
|
|
72
|
+
:disabled="saving"
|
|
73
|
+
/>
|
|
74
|
+
{{ t('Translation is reviewed') }}
|
|
75
|
+
</label>
|
|
76
|
+
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
4
80
|
</div>
|
|
5
81
|
</template>
|
|
6
82
|
|
|
7
83
|
<script setup lang="ts">
|
|
8
|
-
import { computed
|
|
9
|
-
import {
|
|
84
|
+
import { ref, Ref, computed, nextTick } from 'vue';
|
|
85
|
+
import { IconPenSolid, IconCheckOutline, IconXOutline, IconQuestionCircleSolid } from '@iconify-prerendered/vue-flowbite';
|
|
86
|
+
import { callAdminForthApi } from '@/utils';
|
|
87
|
+
import { showErrorTost, showSuccesTost } from '@/composables/useFrontendApi';
|
|
88
|
+
import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
|
|
89
|
+
import { useI18n } from 'vue-i18n';
|
|
90
|
+
import Tooltip from '@/afcl/Tooltip.vue';
|
|
91
|
+
import Checkbox from '@/afcl/Checkbox.vue';
|
|
10
92
|
|
|
93
|
+
const { t } = useI18n();
|
|
94
|
+
const props = defineProps(['column', 'record', 'resource', 'adminUser', 'meta']);
|
|
95
|
+
const isEditing = ref(false);
|
|
96
|
+
const editValue = ref(null);
|
|
97
|
+
const saving = ref(false);
|
|
98
|
+
const input = ref(null);
|
|
99
|
+
const columnOptions = ref({});
|
|
100
|
+
const mode = ref('edit');
|
|
101
|
+
const currentValues = ref({});
|
|
102
|
+
const unmasked = ref({});
|
|
103
|
+
|
|
104
|
+
const inputHolder = ref(null);
|
|
11
105
|
|
|
12
106
|
const limitedText = computed(() => {
|
|
13
107
|
const text = props.record[props.column.name];
|
|
14
108
|
return text?.length > 50 ? text.slice(0, 50) + '...' : text;
|
|
15
109
|
});
|
|
16
110
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
111
|
+
const reviewed: Ref<boolean> = ref(false);
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
const originalReviewed = ref(false);
|
|
115
|
+
const originalValue = ref(null);
|
|
116
|
+
|
|
117
|
+
async function startEdit() {
|
|
118
|
+
const value = props.record[props.column.name];
|
|
119
|
+
currentValues.value = {
|
|
120
|
+
[props.column.name]: props.column.isArray?.enabled
|
|
121
|
+
? (Array.isArray(value) ? value : [value]).filter(v => v !== null && v !== undefined)
|
|
122
|
+
: value,
|
|
123
|
+
};
|
|
124
|
+
reviewed.value = props.record[props.meta?.reviewedCheckboxesFieldName]?.[props.column.name] || false;
|
|
125
|
+
originalReviewed.value = reviewed.value;
|
|
126
|
+
originalValue.value = value;
|
|
127
|
+
isEditing.value = true;
|
|
128
|
+
await nextTick();
|
|
129
|
+
if (inputHolder.value) {
|
|
130
|
+
inputHolder.value.querySelector('input, textarea, select')?.focus();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cancelEdit() {
|
|
135
|
+
isEditing.value = false;
|
|
136
|
+
editValue.value = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function setCurrentValue(field, value, arrayIndex = undefined) {
|
|
140
|
+
if (arrayIndex !== undefined && props.column.isArray?.enabled) {
|
|
141
|
+
// Handle array updates
|
|
142
|
+
if (!Array.isArray(currentValues.value[field])) {
|
|
143
|
+
currentValues.value[field] = [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const newArray = [...currentValues.value[field]];
|
|
147
|
+
|
|
148
|
+
if (arrayIndex >= newArray.length) {
|
|
149
|
+
// When adding a new item, always add null
|
|
150
|
+
newArray.push(null);
|
|
151
|
+
} else {
|
|
152
|
+
// For existing items, handle type conversion
|
|
153
|
+
if (props.column.isArray?.itemType && ['integer', 'float', 'decimal'].includes(props.column.isArray.itemType)) {
|
|
154
|
+
newArray[arrayIndex] = value !== null && value !== '' ? +value : null;
|
|
155
|
+
} else {
|
|
156
|
+
newArray[arrayIndex] = value;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Assign the new array
|
|
161
|
+
currentValues.value[field] = newArray;
|
|
162
|
+
editValue.value = newArray;
|
|
163
|
+
} else {
|
|
164
|
+
// Handle non-array updates
|
|
165
|
+
currentValues.value[field] = value;
|
|
166
|
+
editValue.value = value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function saveEdit() {
|
|
171
|
+
saving.value = true;
|
|
172
|
+
try {
|
|
173
|
+
const result = await callAdminForthApi({
|
|
174
|
+
method: 'POST',
|
|
175
|
+
path: `/plugin/${props.meta.pluginInstanceId}/update-field`,
|
|
176
|
+
body: {
|
|
177
|
+
resourceId: props.resource.resourceId,
|
|
178
|
+
recordId: props.record._primaryKeyValue,
|
|
179
|
+
field: props.column.name,
|
|
180
|
+
value: currentValues.value[props.column.name],
|
|
181
|
+
reviewed: reviewed.value,
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (result.error) {
|
|
186
|
+
showErrorTost(result.error);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
24
189
|
|
|
25
|
-
|
|
190
|
+
showSuccesTost(t('Field updated successfully'));
|
|
191
|
+
props.record[props.column.name] = result.record[props.column.name];
|
|
192
|
+
if (props.meta?.reviewedCheckboxesFieldName) {
|
|
193
|
+
props.record[props.meta?.reviewedCheckboxesFieldName] = result.record[props.meta?.reviewedCheckboxesFieldName];
|
|
194
|
+
}
|
|
195
|
+
isEditing.value = false;
|
|
196
|
+
} finally {
|
|
197
|
+
saving.value = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
</script>
|
package/dist/custom/ListCell.vue
CHANGED
|
@@ -1,25 +1,200 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
|
|
2
|
+
<div class="relative group flex items-center" @click.stop>
|
|
3
|
+
<!-- Normal value display -->
|
|
4
|
+
<div v-if="!isEditing" class="flex items-center" :class="limitedText?.length > 50 ? 'min-w-48 max-w-full' : 'min-w-32'">
|
|
5
|
+
{{ limitedText? limitedText : '-' }}
|
|
6
|
+
|
|
7
|
+
<span v-if="meta?.reviewedCheckboxesFieldName && limitedText" class="flex items-center ml-2">
|
|
8
|
+
<Tooltip
|
|
9
|
+
>
|
|
10
|
+
<template #tooltip>
|
|
11
|
+
{{ record[meta?.reviewedCheckboxesFieldName]?.[props.column.name] ? t('Translation is reviewed') : t('Translation is not reviewed') }}
|
|
12
|
+
</template>
|
|
13
|
+
<IconCheckOutline
|
|
14
|
+
v-if="record[meta?.reviewedCheckboxesFieldName]?.[props.column.name]"
|
|
15
|
+
class="w-5 h-5 text-green-500"
|
|
16
|
+
/>
|
|
17
|
+
<IconQuestionCircleSolid
|
|
18
|
+
v-else
|
|
19
|
+
class="w-5 h-5 text-yellow-500"
|
|
20
|
+
/>
|
|
21
|
+
</Tooltip>
|
|
22
|
+
</span>
|
|
23
|
+
|
|
24
|
+
<button
|
|
25
|
+
v-if="!column.editReadonly"
|
|
26
|
+
@click="startEdit"
|
|
27
|
+
class="ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
28
|
+
>
|
|
29
|
+
<IconPenSolid class="w-5 h-5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"/>
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Edit mode -->
|
|
34
|
+
<div v-else class="flex flex-col gap-2">
|
|
35
|
+
<div class="flex items-center max-w-full gap-2"
|
|
36
|
+
:class="limitedText?.length > 50 ? 'min-w-72' : 'min-w-64'"
|
|
37
|
+
ref="inputHolder"
|
|
38
|
+
>
|
|
39
|
+
<ColumnValueInputWrapper
|
|
40
|
+
class="flex-grow"
|
|
41
|
+
ref="input"
|
|
42
|
+
:source="'edit'"
|
|
43
|
+
:column="column"
|
|
44
|
+
:currentValues="currentValues"
|
|
45
|
+
:mode="mode"
|
|
46
|
+
:columnOptions="columnOptions"
|
|
47
|
+
:unmasked="unmasked"
|
|
48
|
+
:setCurrentValue="setCurrentValue"
|
|
49
|
+
/>
|
|
50
|
+
<div class="flex gap-1">
|
|
51
|
+
<button
|
|
52
|
+
@click="saveEdit"
|
|
53
|
+
:disabled="saving || (originalReviewed === reviewed && originalValue === currentValues[props.column.name])"
|
|
54
|
+
class="text-green-600 hover:text-green-700 dark:text-green-500 dark:hover:text-green-400 disabled:opacity-50"
|
|
55
|
+
|
|
56
|
+
>
|
|
57
|
+
<IconCheckOutline class="w-5 h-5" />
|
|
58
|
+
</button>
|
|
59
|
+
<button
|
|
60
|
+
@click="cancelEdit"
|
|
61
|
+
:disabled="saving"
|
|
62
|
+
class="text-red-600 hover:text-red-700 dark:text-red-500 dark:hover:text-red-400"
|
|
63
|
+
>
|
|
64
|
+
<IconXOutline class="w-5 h-5" />
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div v-if="meta?.reviewedCheckboxesFieldName">
|
|
69
|
+
<label class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-0 cursor-pointer select-none">
|
|
70
|
+
<Checkbox
|
|
71
|
+
v-model="reviewed"
|
|
72
|
+
:disabled="saving"
|
|
73
|
+
/>
|
|
74
|
+
{{ t('Translation is reviewed') }}
|
|
75
|
+
</label>
|
|
76
|
+
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
4
80
|
</div>
|
|
5
81
|
</template>
|
|
6
82
|
|
|
7
83
|
<script setup lang="ts">
|
|
8
|
-
import { computed
|
|
9
|
-
import {
|
|
84
|
+
import { ref, Ref, computed, nextTick } from 'vue';
|
|
85
|
+
import { IconPenSolid, IconCheckOutline, IconXOutline, IconQuestionCircleSolid } from '@iconify-prerendered/vue-flowbite';
|
|
86
|
+
import { callAdminForthApi } from '@/utils';
|
|
87
|
+
import { showErrorTost, showSuccesTost } from '@/composables/useFrontendApi';
|
|
88
|
+
import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
|
|
89
|
+
import { useI18n } from 'vue-i18n';
|
|
90
|
+
import Tooltip from '@/afcl/Tooltip.vue';
|
|
91
|
+
import Checkbox from '@/afcl/Checkbox.vue';
|
|
10
92
|
|
|
93
|
+
const { t } = useI18n();
|
|
94
|
+
const props = defineProps(['column', 'record', 'resource', 'adminUser', 'meta']);
|
|
95
|
+
const isEditing = ref(false);
|
|
96
|
+
const editValue = ref(null);
|
|
97
|
+
const saving = ref(false);
|
|
98
|
+
const input = ref(null);
|
|
99
|
+
const columnOptions = ref({});
|
|
100
|
+
const mode = ref('edit');
|
|
101
|
+
const currentValues = ref({});
|
|
102
|
+
const unmasked = ref({});
|
|
103
|
+
|
|
104
|
+
const inputHolder = ref(null);
|
|
11
105
|
|
|
12
106
|
const limitedText = computed(() => {
|
|
13
107
|
const text = props.record[props.column.name];
|
|
14
108
|
return text?.length > 50 ? text.slice(0, 50) + '...' : text;
|
|
15
109
|
});
|
|
16
110
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
111
|
+
const reviewed: Ref<boolean> = ref(false);
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
const originalReviewed = ref(false);
|
|
115
|
+
const originalValue = ref(null);
|
|
116
|
+
|
|
117
|
+
async function startEdit() {
|
|
118
|
+
const value = props.record[props.column.name];
|
|
119
|
+
currentValues.value = {
|
|
120
|
+
[props.column.name]: props.column.isArray?.enabled
|
|
121
|
+
? (Array.isArray(value) ? value : [value]).filter(v => v !== null && v !== undefined)
|
|
122
|
+
: value,
|
|
123
|
+
};
|
|
124
|
+
reviewed.value = props.record[props.meta?.reviewedCheckboxesFieldName]?.[props.column.name] || false;
|
|
125
|
+
originalReviewed.value = reviewed.value;
|
|
126
|
+
originalValue.value = value;
|
|
127
|
+
isEditing.value = true;
|
|
128
|
+
await nextTick();
|
|
129
|
+
if (inputHolder.value) {
|
|
130
|
+
inputHolder.value.querySelector('input, textarea, select')?.focus();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cancelEdit() {
|
|
135
|
+
isEditing.value = false;
|
|
136
|
+
editValue.value = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function setCurrentValue(field, value, arrayIndex = undefined) {
|
|
140
|
+
if (arrayIndex !== undefined && props.column.isArray?.enabled) {
|
|
141
|
+
// Handle array updates
|
|
142
|
+
if (!Array.isArray(currentValues.value[field])) {
|
|
143
|
+
currentValues.value[field] = [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const newArray = [...currentValues.value[field]];
|
|
147
|
+
|
|
148
|
+
if (arrayIndex >= newArray.length) {
|
|
149
|
+
// When adding a new item, always add null
|
|
150
|
+
newArray.push(null);
|
|
151
|
+
} else {
|
|
152
|
+
// For existing items, handle type conversion
|
|
153
|
+
if (props.column.isArray?.itemType && ['integer', 'float', 'decimal'].includes(props.column.isArray.itemType)) {
|
|
154
|
+
newArray[arrayIndex] = value !== null && value !== '' ? +value : null;
|
|
155
|
+
} else {
|
|
156
|
+
newArray[arrayIndex] = value;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Assign the new array
|
|
161
|
+
currentValues.value[field] = newArray;
|
|
162
|
+
editValue.value = newArray;
|
|
163
|
+
} else {
|
|
164
|
+
// Handle non-array updates
|
|
165
|
+
currentValues.value[field] = value;
|
|
166
|
+
editValue.value = value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function saveEdit() {
|
|
171
|
+
saving.value = true;
|
|
172
|
+
try {
|
|
173
|
+
const result = await callAdminForthApi({
|
|
174
|
+
method: 'POST',
|
|
175
|
+
path: `/plugin/${props.meta.pluginInstanceId}/update-field`,
|
|
176
|
+
body: {
|
|
177
|
+
resourceId: props.resource.resourceId,
|
|
178
|
+
recordId: props.record._primaryKeyValue,
|
|
179
|
+
field: props.column.name,
|
|
180
|
+
value: currentValues.value[props.column.name],
|
|
181
|
+
reviewed: reviewed.value,
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (result.error) {
|
|
186
|
+
showErrorTost(result.error);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
24
189
|
|
|
25
|
-
|
|
190
|
+
showSuccesTost(t('Field updated successfully'));
|
|
191
|
+
props.record[props.column.name] = result.record[props.column.name];
|
|
192
|
+
if (props.meta?.reviewedCheckboxesFieldName) {
|
|
193
|
+
props.record[props.meta?.reviewedCheckboxesFieldName] = result.record[props.meta?.reviewedCheckboxesFieldName];
|
|
194
|
+
}
|
|
195
|
+
isEditing.value = false;
|
|
196
|
+
} finally {
|
|
197
|
+
saving.value = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
</script>
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes } from "adminforth";
|
|
10
|
+
import { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes, RAMLock } from "adminforth";
|
|
11
11
|
import iso6391 from 'iso-639-1';
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import fs from 'fs-extra';
|
|
@@ -162,6 +162,11 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
162
162
|
// set ListCell for list
|
|
163
163
|
column.components.list = {
|
|
164
164
|
file: this.componentPath('ListCell.vue'),
|
|
165
|
+
meta: {
|
|
166
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
167
|
+
lang,
|
|
168
|
+
reviewedCheckboxesFieldName: this.options.reviewedCheckboxesFieldName,
|
|
169
|
+
},
|
|
165
170
|
};
|
|
166
171
|
}
|
|
167
172
|
this.enFieldName = this.trFieldNames['en'] || 'en_string';
|
|
@@ -616,6 +621,17 @@ ${JSON.stringify(strings.reduce((acc, s) => {
|
|
|
616
621
|
throw new Error(`Field ${this.trFieldNames[lang]} should be not required in resource ${resourceConfig.resourceId}`);
|
|
617
622
|
}
|
|
618
623
|
}
|
|
624
|
+
if (this.options.reviewedCheckboxesFieldName) {
|
|
625
|
+
// ensure type is JSON
|
|
626
|
+
const column = resourceConfig.columns.find(c => c.name === this.options.reviewedCheckboxesFieldName);
|
|
627
|
+
if (!column) {
|
|
628
|
+
const similar = suggestIfTypo(resourceConfig.columns.map((col) => col.name), this.options.reviewedCheckboxesFieldName);
|
|
629
|
+
throw new Error(`Field ${this.options.reviewedCheckboxesFieldName} not found in resource ${resourceConfig.resourceId}${similar ? `Did you mean '${similar}'?` : ''}`);
|
|
630
|
+
}
|
|
631
|
+
if (column.type !== AdminForthDataTypes.JSON) {
|
|
632
|
+
throw new Error(`Field ${this.options.reviewedCheckboxesFieldName} should be of type JSON in resource ${resourceConfig.resourceId}, but it is ${column.type}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
619
635
|
// ensure categoryFieldName defined and is string
|
|
620
636
|
if (!this.options.categoryFieldName) {
|
|
621
637
|
throw new Error(`categoryFieldName option is not defined. It is used to categorize translations and return only specific category e.g. to frontend`);
|
|
@@ -797,5 +813,47 @@ ${JSON.stringify(strings.reduce((acc, s) => {
|
|
|
797
813
|
return translations;
|
|
798
814
|
})
|
|
799
815
|
});
|
|
816
|
+
const lock = new RAMLock();
|
|
817
|
+
server.endpoint({
|
|
818
|
+
method: 'POST',
|
|
819
|
+
path: `/plugin/${this.pluginInstanceId}/update-field`,
|
|
820
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, headers }) {
|
|
821
|
+
const { resourceId, recordId, field, value, reviewed } = body;
|
|
822
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === resourceId);
|
|
823
|
+
// Create update object with just the single field
|
|
824
|
+
const updateRecord = { [field]: value };
|
|
825
|
+
// Use AdminForth's built-in update method
|
|
826
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
827
|
+
let oldRecord;
|
|
828
|
+
let result;
|
|
829
|
+
yield lock.run(`edit-trans-${recordId}`, () => __awaiter(this, void 0, void 0, function* () {
|
|
830
|
+
// put into lock so 2 editors will not update the same record at the same time
|
|
831
|
+
oldRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
832
|
+
if (this.options.reviewedCheckboxesFieldName) {
|
|
833
|
+
let oldValue;
|
|
834
|
+
if (!oldRecord[this.options.reviewedCheckboxesFieldName]) {
|
|
835
|
+
oldValue = {};
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
oldValue = Object.assign({}, oldRecord[this.options.reviewedCheckboxesFieldName]);
|
|
839
|
+
}
|
|
840
|
+
oldValue[field] = reviewed;
|
|
841
|
+
updateRecord[this.options.reviewedCheckboxesFieldName] = Object.assign({}, oldValue);
|
|
842
|
+
}
|
|
843
|
+
result = yield this.adminforth.updateResourceRecord({
|
|
844
|
+
resource,
|
|
845
|
+
recordId,
|
|
846
|
+
record: updateRecord,
|
|
847
|
+
oldRecord,
|
|
848
|
+
adminUser
|
|
849
|
+
});
|
|
850
|
+
}));
|
|
851
|
+
if (result.error) {
|
|
852
|
+
return { error: result.error };
|
|
853
|
+
}
|
|
854
|
+
const updatedRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
855
|
+
return { record: updatedRecord };
|
|
856
|
+
})
|
|
857
|
+
});
|
|
800
858
|
}
|
|
801
859
|
}
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import AdminForth, { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes } from "adminforth";
|
|
1
|
+
import AdminForth, { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes, RAMLock } from "adminforth";
|
|
2
2
|
import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResourceColumn, AdminForthResource, BeforeLoginConfirmationFunction, AdminForthConfigMenuItem } from "adminforth";
|
|
3
3
|
import type { PluginOptions } from './types.js';
|
|
4
4
|
import iso6391, { LanguageCode } from 'iso-639-1';
|
|
@@ -6,6 +6,8 @@ import path from 'path';
|
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
7
|
import chokidar from 'chokidar';
|
|
8
8
|
import { AsyncQueue } from '@sapphire/async-queue';
|
|
9
|
+
|
|
10
|
+
|
|
9
11
|
console.log = (...args) => {
|
|
10
12
|
process.stdout.write(args.join(" ") + "\n");
|
|
11
13
|
};
|
|
@@ -182,6 +184,11 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
182
184
|
// set ListCell for list
|
|
183
185
|
column.components.list = {
|
|
184
186
|
file: this.componentPath('ListCell.vue'),
|
|
187
|
+
meta: {
|
|
188
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
189
|
+
lang,
|
|
190
|
+
reviewedCheckboxesFieldName: this.options.reviewedCheckboxesFieldName,
|
|
191
|
+
},
|
|
185
192
|
};
|
|
186
193
|
}
|
|
187
194
|
|
|
@@ -732,6 +739,19 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
|
|
|
732
739
|
}
|
|
733
740
|
}
|
|
734
741
|
|
|
742
|
+
if (this.options.reviewedCheckboxesFieldName) {
|
|
743
|
+
// ensure type is JSON
|
|
744
|
+
const column = resourceConfig.columns.find(c => c.name === this.options.reviewedCheckboxesFieldName);
|
|
745
|
+
if (!column) {
|
|
746
|
+
const similar = suggestIfTypo(resourceConfig.columns.map((col) => col.name), this.options.reviewedCheckboxesFieldName);
|
|
747
|
+
throw new Error(`Field ${this.options.reviewedCheckboxesFieldName} not found in resource ${resourceConfig.resourceId}${similar ? `Did you mean '${similar}'?` : ''}`);
|
|
748
|
+
}
|
|
749
|
+
if (column.type !== AdminForthDataTypes.JSON) {
|
|
750
|
+
throw new Error(`Field ${this.options.reviewedCheckboxesFieldName} should be of type JSON in resource ${resourceConfig.resourceId}, but it is ${column.type}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
|
|
735
755
|
// ensure categoryFieldName defined and is string
|
|
736
756
|
if (!this.options.categoryFieldName) {
|
|
737
757
|
throw new Error(`categoryFieldName option is not defined. It is used to categorize translations and return only specific category e.g. to frontend`);
|
|
@@ -940,6 +960,57 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
|
|
|
940
960
|
}
|
|
941
961
|
});
|
|
942
962
|
|
|
963
|
+
const lock = new RAMLock();
|
|
964
|
+
|
|
965
|
+
server.endpoint({
|
|
966
|
+
method: 'POST',
|
|
967
|
+
path: `/plugin/${this.pluginInstanceId}/update-field`,
|
|
968
|
+
handler: async ({ body, adminUser, headers }) => {
|
|
969
|
+
const { resourceId, recordId, field, value, reviewed } = body;
|
|
970
|
+
|
|
971
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === resourceId);
|
|
972
|
+
// Create update object with just the single field
|
|
973
|
+
const updateRecord = { [field]: value };
|
|
974
|
+
|
|
975
|
+
// Use AdminForth's built-in update method
|
|
976
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
977
|
+
|
|
978
|
+
let oldRecord;
|
|
979
|
+
let result;
|
|
980
|
+
await lock.run(`edit-trans-${recordId}`, async () => {
|
|
981
|
+
// put into lock so 2 editors will not update the same record at the same time
|
|
982
|
+
oldRecord = await connector.getRecordByPrimaryKey(resource, recordId)
|
|
983
|
+
|
|
984
|
+
if (this.options.reviewedCheckboxesFieldName) {
|
|
985
|
+
let oldValue;
|
|
986
|
+
if (!oldRecord[this.options.reviewedCheckboxesFieldName]) {
|
|
987
|
+
oldValue = {}
|
|
988
|
+
} else {
|
|
989
|
+
oldValue = {...oldRecord[this.options.reviewedCheckboxesFieldName]};
|
|
990
|
+
}
|
|
991
|
+
oldValue[field] = reviewed;
|
|
992
|
+
updateRecord[this.options.reviewedCheckboxesFieldName] = { ...oldValue };
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
result = await this.adminforth.updateResourceRecord({
|
|
996
|
+
resource,
|
|
997
|
+
recordId,
|
|
998
|
+
record: updateRecord,
|
|
999
|
+
oldRecord,
|
|
1000
|
+
adminUser
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
if (result.error) {
|
|
1005
|
+
return { error: result.error };
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const updatedRecord = await connector.getRecordByPrimaryKey(resource, recordId);
|
|
1009
|
+
|
|
1010
|
+
return { record: updatedRecord };
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
|
|
943
1014
|
}
|
|
944
1015
|
|
|
945
1016
|
}
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -42,4 +42,11 @@ export interface PluginOptions {
|
|
|
42
42
|
* not AdminForth applications
|
|
43
43
|
*/
|
|
44
44
|
externalAppOnly?: boolean;
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* You can enable "Reviewed" checkbox for each translation string by defing this field,
|
|
49
|
+
* it should be a JSON field (underlyng database type should be TEXT or JSON)
|
|
50
|
+
*/
|
|
51
|
+
reviewedCheckboxesFieldName?: string;
|
|
45
52
|
}
|