@adminforth/i18n 1.2.3 → 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 +62 -1
- package/index.ts +75 -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';
|
|
@@ -217,6 +222,9 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
217
222
|
name: 'fully_translated',
|
|
218
223
|
label: 'Fully translated',
|
|
219
224
|
virtual: true,
|
|
225
|
+
filterOptions: {
|
|
226
|
+
multiselect: false,
|
|
227
|
+
},
|
|
220
228
|
showIn: {
|
|
221
229
|
show: true,
|
|
222
230
|
list: true,
|
|
@@ -613,6 +621,17 @@ ${JSON.stringify(strings.reduce((acc, s) => {
|
|
|
613
621
|
throw new Error(`Field ${this.trFieldNames[lang]} should be not required in resource ${resourceConfig.resourceId}`);
|
|
614
622
|
}
|
|
615
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
|
+
}
|
|
616
635
|
// ensure categoryFieldName defined and is string
|
|
617
636
|
if (!this.options.categoryFieldName) {
|
|
618
637
|
throw new Error(`categoryFieldName option is not defined. It is used to categorize translations and return only specific category e.g. to frontend`);
|
|
@@ -794,5 +813,47 @@ ${JSON.stringify(strings.reduce((acc, s) => {
|
|
|
794
813
|
return translations;
|
|
795
814
|
})
|
|
796
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
|
+
});
|
|
797
858
|
}
|
|
798
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
|
|
|
@@ -245,6 +252,9 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
245
252
|
name: 'fully_translated',
|
|
246
253
|
label: 'Fully translated',
|
|
247
254
|
virtual: true,
|
|
255
|
+
filterOptions: {
|
|
256
|
+
multiselect: false,
|
|
257
|
+
},
|
|
248
258
|
showIn: {
|
|
249
259
|
show: true,
|
|
250
260
|
list: true,
|
|
@@ -729,6 +739,19 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
|
|
|
729
739
|
}
|
|
730
740
|
}
|
|
731
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
|
+
|
|
732
755
|
// ensure categoryFieldName defined and is string
|
|
733
756
|
if (!this.options.categoryFieldName) {
|
|
734
757
|
throw new Error(`categoryFieldName option is not defined. It is used to categorize translations and return only specific category e.g. to frontend`);
|
|
@@ -937,6 +960,57 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
|
|
|
937
960
|
}
|
|
938
961
|
});
|
|
939
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
|
+
|
|
940
1014
|
}
|
|
941
1015
|
|
|
942
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
|
}
|