@adminforth/list-in-place-edit 1.0.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/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +44 -0
- package/.woodpecker/release.yml +42 -0
- package/build.log +11 -0
- package/custom/InPlaceEdit.vue +137 -0
- package/custom/tsconfig.json +19 -0
- package/dist/custom/InPlaceEdit.vue +137 -0
- package/dist/custom/tsconfig.json +19 -0
- package/dist/index.js +80 -0
- package/dist/types.js +1 -0
- package/index.ts +83 -0
- package/package.json +57 -0
- package/tsconfig.json +13 -0
- package/types.ts +3 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
#!/bin/bash
|
|
3
|
+
|
|
4
|
+
# write npm run output both to console and to build.log
|
|
5
|
+
npm run build 2>&1 | tee build.log
|
|
6
|
+
build_status=${PIPESTATUS[0]}
|
|
7
|
+
|
|
8
|
+
# if exist status from the npm run build is not 0
|
|
9
|
+
# then exit with the status code from the npm run build
|
|
10
|
+
if [ $build_status -ne 0 ]; then
|
|
11
|
+
echo "Build failed. Exiting with status code $build_status"
|
|
12
|
+
exit $build_status
|
|
13
|
+
fi
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
set -x
|
|
4
|
+
|
|
5
|
+
COMMIT_SHORT_SHA=$(echo $CI_COMMIT_SHA | cut -c1-8)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if [ "$CI_STEP_STATUS" = "success" ]; then
|
|
9
|
+
MESSAGE="Did a build without issues on \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\`. Commit: _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
|
|
10
|
+
|
|
11
|
+
curl -s -X POST -H "Content-Type: application/json" -d '{
|
|
12
|
+
"username": "'"$CI_COMMIT_AUTHOR"'",
|
|
13
|
+
"icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
|
|
14
|
+
"attachments": [
|
|
15
|
+
{
|
|
16
|
+
"mrkdwn_in": ["text", "pretext"],
|
|
17
|
+
"color": "#36a64f",
|
|
18
|
+
"text": "'"$MESSAGE"'"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}' "$DEVELOPERS_SLACK_WEBHOOK"
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
export BUILD_LOG=$(cat ./build.log)
|
|
25
|
+
|
|
26
|
+
BUILD_LOG=$(echo $BUILD_LOG | sed 's/"/\\"/g')
|
|
27
|
+
|
|
28
|
+
MESSAGE="Broke \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\` with commit _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
|
|
29
|
+
CODE_BLOCK="\`\`\`$BUILD_LOG\n\`\`\`"
|
|
30
|
+
|
|
31
|
+
echo "Sending slack message to developers $MESSAGE"
|
|
32
|
+
# Send the message
|
|
33
|
+
curl -sS -X POST -H "Content-Type: application/json" -d '{
|
|
34
|
+
"username": "'"$CI_COMMIT_AUTHOR"'",
|
|
35
|
+
"icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
|
|
36
|
+
"attachments": [
|
|
37
|
+
{
|
|
38
|
+
"mrkdwn_in": ["text", "pretext"],
|
|
39
|
+
"color": "#8A1C12",
|
|
40
|
+
"text": "'"$CODE_BLOCK"'",
|
|
41
|
+
"pretext": "'"$MESSAGE"'"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}' "$DEVELOPERS_SLACK_WEBHOOK" 2>&1
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
clone:
|
|
2
|
+
git:
|
|
3
|
+
image: woodpeckerci/plugin-git
|
|
4
|
+
settings:
|
|
5
|
+
partial: false
|
|
6
|
+
depth: 5
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
init-secrets:
|
|
10
|
+
when:
|
|
11
|
+
- event: push
|
|
12
|
+
image: infisical/cli
|
|
13
|
+
environment:
|
|
14
|
+
INFISICAL_TOKEN:
|
|
15
|
+
from_secret: VAULT_TOKEN
|
|
16
|
+
commands:
|
|
17
|
+
- infisical export --domain https://vault.devforth.io/api --format=dotenv-export --env="prod" > /woodpecker/deploy.vault.env
|
|
18
|
+
secrets:
|
|
19
|
+
- VAULT_TOKEN
|
|
20
|
+
|
|
21
|
+
release:
|
|
22
|
+
image: node:20
|
|
23
|
+
when:
|
|
24
|
+
- event: push
|
|
25
|
+
commands:
|
|
26
|
+
- apt update && apt install -y rsync
|
|
27
|
+
- export $(cat /woodpecker/deploy.vault.env | xargs)
|
|
28
|
+
- npm clean-install
|
|
29
|
+
- /bin/bash ./.woodpecker/buildRelease.sh
|
|
30
|
+
- npm audit signatures
|
|
31
|
+
- npx semantic-release
|
|
32
|
+
|
|
33
|
+
slack-on-failure:
|
|
34
|
+
when:
|
|
35
|
+
- event: push
|
|
36
|
+
status: [failure, success]
|
|
37
|
+
- event: push
|
|
38
|
+
image: curlimages/curl
|
|
39
|
+
commands:
|
|
40
|
+
- export $(cat /woodpecker/deploy.vault.env | xargs)
|
|
41
|
+
- /bin/sh ./.woodpecker/buildSlackNotify.sh
|
|
42
|
+
|
package/build.log
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
> @adminforth/list-in-place-edit@1.0.2 build
|
|
3
|
+
> tsc && rsync -av --exclude 'node_modules' custom dist/
|
|
4
|
+
|
|
5
|
+
sending incremental file list
|
|
6
|
+
custom/
|
|
7
|
+
custom/InPlaceEdit.vue
|
|
8
|
+
custom/tsconfig.json
|
|
9
|
+
|
|
10
|
+
sent 4,900 bytes received 58 bytes 9,916.00 bytes/sec
|
|
11
|
+
total size is 4,685 speedup is 0.94
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative group flex items-center" @click.stop>
|
|
3
|
+
<!-- Normal value display -->
|
|
4
|
+
<div v-if="!isEditing" class="flex items-center">
|
|
5
|
+
<ValueRenderer :column="column" :record="record" />
|
|
6
|
+
<button
|
|
7
|
+
v-if="!column.editReadonly"
|
|
8
|
+
@click="startEdit"
|
|
9
|
+
class="ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
10
|
+
>
|
|
11
|
+
<IconPenSolid class="w-5 h-5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"/>
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Edit mode -->
|
|
16
|
+
<div v-else class="flex items-center min-w-full max-w-full gap-2">
|
|
17
|
+
<ColumnValueInputWrapper
|
|
18
|
+
ref="input"
|
|
19
|
+
:source="'edit'"
|
|
20
|
+
:column="column"
|
|
21
|
+
:currentValues="currentValues"
|
|
22
|
+
:mode="mode"
|
|
23
|
+
:columnOptions="columnOptions"
|
|
24
|
+
:unmasked="unmasked"
|
|
25
|
+
:setCurrentValue="setCurrentValue"
|
|
26
|
+
/>
|
|
27
|
+
<div class="flex gap-1">
|
|
28
|
+
<button
|
|
29
|
+
@click="saveEdit"
|
|
30
|
+
:disabled="saving"
|
|
31
|
+
class="text-green-600 hover:text-green-700 dark:text-green-500 dark:hover:text-green-400"
|
|
32
|
+
>
|
|
33
|
+
<IconCheckOutline class="w-5 h-5" />
|
|
34
|
+
</button>
|
|
35
|
+
<button
|
|
36
|
+
@click="cancelEdit"
|
|
37
|
+
:disabled="saving"
|
|
38
|
+
class="text-red-600 hover:text-red-700 dark:text-red-500 dark:hover:text-red-400"
|
|
39
|
+
>
|
|
40
|
+
<IconXOutline class="w-5 h-5" />
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { ref } from 'vue';
|
|
49
|
+
import { IconPenSolid, IconCheckOutline, IconXOutline } from '@iconify-prerendered/vue-flowbite';
|
|
50
|
+
import { callAdminForthApi } from '@/utils';
|
|
51
|
+
import { showErrorTost, showSuccesTost } from '@/composables/useFrontendApi';
|
|
52
|
+
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
53
|
+
import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
|
|
54
|
+
|
|
55
|
+
const props = defineProps(['column', 'record', 'resource', 'adminUser', 'meta']);
|
|
56
|
+
const isEditing = ref(false);
|
|
57
|
+
const editValue = ref(null);
|
|
58
|
+
const saving = ref(false);
|
|
59
|
+
const input = ref(null);
|
|
60
|
+
const columnOptions = ref({});
|
|
61
|
+
const mode = ref('edit');
|
|
62
|
+
const currentValues = ref({});
|
|
63
|
+
const unmasked = ref({});
|
|
64
|
+
|
|
65
|
+
function startEdit() {
|
|
66
|
+
const value = props.record[props.column.name];
|
|
67
|
+
currentValues.value = {
|
|
68
|
+
[props.column.name]: props.column.isArray?.enabled
|
|
69
|
+
? (Array.isArray(value) ? value : [value]).filter(v => v !== null && v !== undefined)
|
|
70
|
+
: value
|
|
71
|
+
};
|
|
72
|
+
isEditing.value = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function cancelEdit() {
|
|
76
|
+
isEditing.value = false;
|
|
77
|
+
editValue.value = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function setCurrentValue(field, value, arrayIndex = undefined) {
|
|
81
|
+
if (arrayIndex !== undefined && props.column.isArray?.enabled) {
|
|
82
|
+
// Handle array updates
|
|
83
|
+
if (!Array.isArray(currentValues.value[field])) {
|
|
84
|
+
currentValues.value[field] = [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const newArray = [...currentValues.value[field]];
|
|
88
|
+
|
|
89
|
+
if (arrayIndex >= newArray.length) {
|
|
90
|
+
// When adding a new item, always add null
|
|
91
|
+
newArray.push(null);
|
|
92
|
+
} else {
|
|
93
|
+
// For existing items, handle type conversion
|
|
94
|
+
if (props.column.isArray?.itemType && ['integer', 'float', 'decimal'].includes(props.column.isArray.itemType)) {
|
|
95
|
+
newArray[arrayIndex] = value !== null && value !== '' ? +value : null;
|
|
96
|
+
} else {
|
|
97
|
+
newArray[arrayIndex] = value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Assign the new array
|
|
102
|
+
currentValues.value[field] = newArray;
|
|
103
|
+
editValue.value = newArray;
|
|
104
|
+
} else {
|
|
105
|
+
// Handle non-array updates
|
|
106
|
+
currentValues.value[field] = value;
|
|
107
|
+
editValue.value = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function saveEdit() {
|
|
112
|
+
saving.value = true;
|
|
113
|
+
try {
|
|
114
|
+
const result = await callAdminForthApi({
|
|
115
|
+
method: 'POST',
|
|
116
|
+
path: `/plugin/${props.meta.pluginInstanceId}/update-field`,
|
|
117
|
+
body: {
|
|
118
|
+
resourceId: props.resource.resourceId,
|
|
119
|
+
recordId: props.record._primaryKeyValue,
|
|
120
|
+
field: props.column.name,
|
|
121
|
+
value: currentValues.value[props.column.name]
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (result.error) {
|
|
126
|
+
showErrorTost(result.error);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
showSuccesTost('Field updated successfully');
|
|
131
|
+
props.record[props.column.name] = currentValues.value[props.column.name];
|
|
132
|
+
isEditing.value = false;
|
|
133
|
+
} finally {
|
|
134
|
+
saving.value = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".", // This should point to your project root
|
|
4
|
+
"paths": {
|
|
5
|
+
"@/*": [
|
|
6
|
+
// "node_modules/adminforth/dist/spa/src/*"
|
|
7
|
+
"../../../spa/src/*"
|
|
8
|
+
],
|
|
9
|
+
"*": [
|
|
10
|
+
// "node_modules/adminforth/dist/spa/node_modules/*"
|
|
11
|
+
"../../../spa/node_modules/*"
|
|
12
|
+
],
|
|
13
|
+
"@@/*": [
|
|
14
|
+
// "node_modules/adminforth/dist/spa/src/*"
|
|
15
|
+
"."
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative group flex items-center" @click.stop>
|
|
3
|
+
<!-- Normal value display -->
|
|
4
|
+
<div v-if="!isEditing" class="flex items-center">
|
|
5
|
+
<ValueRenderer :column="column" :record="record" />
|
|
6
|
+
<button
|
|
7
|
+
v-if="!column.editReadonly"
|
|
8
|
+
@click="startEdit"
|
|
9
|
+
class="ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
10
|
+
>
|
|
11
|
+
<IconPenSolid class="w-5 h-5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"/>
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Edit mode -->
|
|
16
|
+
<div v-else class="flex items-center min-w-full max-w-full gap-2">
|
|
17
|
+
<ColumnValueInputWrapper
|
|
18
|
+
ref="input"
|
|
19
|
+
:source="'edit'"
|
|
20
|
+
:column="column"
|
|
21
|
+
:currentValues="currentValues"
|
|
22
|
+
:mode="mode"
|
|
23
|
+
:columnOptions="columnOptions"
|
|
24
|
+
:unmasked="unmasked"
|
|
25
|
+
:setCurrentValue="setCurrentValue"
|
|
26
|
+
/>
|
|
27
|
+
<div class="flex gap-1">
|
|
28
|
+
<button
|
|
29
|
+
@click="saveEdit"
|
|
30
|
+
:disabled="saving"
|
|
31
|
+
class="text-green-600 hover:text-green-700 dark:text-green-500 dark:hover:text-green-400"
|
|
32
|
+
>
|
|
33
|
+
<IconCheckOutline class="w-5 h-5" />
|
|
34
|
+
</button>
|
|
35
|
+
<button
|
|
36
|
+
@click="cancelEdit"
|
|
37
|
+
:disabled="saving"
|
|
38
|
+
class="text-red-600 hover:text-red-700 dark:text-red-500 dark:hover:text-red-400"
|
|
39
|
+
>
|
|
40
|
+
<IconXOutline class="w-5 h-5" />
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { ref } from 'vue';
|
|
49
|
+
import { IconPenSolid, IconCheckOutline, IconXOutline } from '@iconify-prerendered/vue-flowbite';
|
|
50
|
+
import { callAdminForthApi } from '@/utils';
|
|
51
|
+
import { showErrorTost, showSuccesTost } from '@/composables/useFrontendApi';
|
|
52
|
+
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
53
|
+
import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
|
|
54
|
+
|
|
55
|
+
const props = defineProps(['column', 'record', 'resource', 'adminUser', 'meta']);
|
|
56
|
+
const isEditing = ref(false);
|
|
57
|
+
const editValue = ref(null);
|
|
58
|
+
const saving = ref(false);
|
|
59
|
+
const input = ref(null);
|
|
60
|
+
const columnOptions = ref({});
|
|
61
|
+
const mode = ref('edit');
|
|
62
|
+
const currentValues = ref({});
|
|
63
|
+
const unmasked = ref({});
|
|
64
|
+
|
|
65
|
+
function startEdit() {
|
|
66
|
+
const value = props.record[props.column.name];
|
|
67
|
+
currentValues.value = {
|
|
68
|
+
[props.column.name]: props.column.isArray?.enabled
|
|
69
|
+
? (Array.isArray(value) ? value : [value]).filter(v => v !== null && v !== undefined)
|
|
70
|
+
: value
|
|
71
|
+
};
|
|
72
|
+
isEditing.value = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function cancelEdit() {
|
|
76
|
+
isEditing.value = false;
|
|
77
|
+
editValue.value = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function setCurrentValue(field, value, arrayIndex = undefined) {
|
|
81
|
+
if (arrayIndex !== undefined && props.column.isArray?.enabled) {
|
|
82
|
+
// Handle array updates
|
|
83
|
+
if (!Array.isArray(currentValues.value[field])) {
|
|
84
|
+
currentValues.value[field] = [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const newArray = [...currentValues.value[field]];
|
|
88
|
+
|
|
89
|
+
if (arrayIndex >= newArray.length) {
|
|
90
|
+
// When adding a new item, always add null
|
|
91
|
+
newArray.push(null);
|
|
92
|
+
} else {
|
|
93
|
+
// For existing items, handle type conversion
|
|
94
|
+
if (props.column.isArray?.itemType && ['integer', 'float', 'decimal'].includes(props.column.isArray.itemType)) {
|
|
95
|
+
newArray[arrayIndex] = value !== null && value !== '' ? +value : null;
|
|
96
|
+
} else {
|
|
97
|
+
newArray[arrayIndex] = value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Assign the new array
|
|
102
|
+
currentValues.value[field] = newArray;
|
|
103
|
+
editValue.value = newArray;
|
|
104
|
+
} else {
|
|
105
|
+
// Handle non-array updates
|
|
106
|
+
currentValues.value[field] = value;
|
|
107
|
+
editValue.value = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function saveEdit() {
|
|
112
|
+
saving.value = true;
|
|
113
|
+
try {
|
|
114
|
+
const result = await callAdminForthApi({
|
|
115
|
+
method: 'POST',
|
|
116
|
+
path: `/plugin/${props.meta.pluginInstanceId}/update-field`,
|
|
117
|
+
body: {
|
|
118
|
+
resourceId: props.resource.resourceId,
|
|
119
|
+
recordId: props.record._primaryKeyValue,
|
|
120
|
+
field: props.column.name,
|
|
121
|
+
value: currentValues.value[props.column.name]
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (result.error) {
|
|
126
|
+
showErrorTost(result.error);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
showSuccesTost('Field updated successfully');
|
|
131
|
+
props.record[props.column.name] = currentValues.value[props.column.name];
|
|
132
|
+
isEditing.value = false;
|
|
133
|
+
} finally {
|
|
134
|
+
saving.value = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".", // This should point to your project root
|
|
4
|
+
"paths": {
|
|
5
|
+
"@/*": [
|
|
6
|
+
// "node_modules/adminforth/dist/spa/src/*"
|
|
7
|
+
"../../../spa/src/*"
|
|
8
|
+
],
|
|
9
|
+
"*": [
|
|
10
|
+
// "node_modules/adminforth/dist/spa/node_modules/*"
|
|
11
|
+
"../../../spa/node_modules/*"
|
|
12
|
+
],
|
|
13
|
+
"@@/*": [
|
|
14
|
+
// "node_modules/adminforth/dist/spa/src/*"
|
|
15
|
+
"."
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AdminForthPlugin } from "adminforth";
|
|
11
|
+
export default class ListInPlaceEditPlugin extends AdminForthPlugin {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options, import.meta.url);
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
modifyResourceConfig(adminforth, resourceConfig) {
|
|
17
|
+
const _super = Object.create(null, {
|
|
18
|
+
modifyResourceConfig: { get: () => super.modifyResourceConfig }
|
|
19
|
+
});
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
_super.modifyResourceConfig.call(this, adminforth, resourceConfig);
|
|
22
|
+
const targetColumns = resourceConfig.columns.filter(col => this.options.columns.includes(col.name));
|
|
23
|
+
targetColumns.forEach(column => {
|
|
24
|
+
var _a;
|
|
25
|
+
if ((_a = column.components) === null || _a === void 0 ? void 0 : _a.list) {
|
|
26
|
+
throw new Error(`Column ${column.name} already has a list component defined. ListInplaceEdit plugin cannot be used on columns that already have list components.`);
|
|
27
|
+
}
|
|
28
|
+
if (!column.components) {
|
|
29
|
+
column.components = {};
|
|
30
|
+
}
|
|
31
|
+
column.components.list = {
|
|
32
|
+
file: this.componentPath('InPlaceEdit.vue'),
|
|
33
|
+
meta: {
|
|
34
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
35
|
+
columnName: column.name
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
42
|
+
// Validate that columns exist if specific columns were specified
|
|
43
|
+
this.options.columns.forEach(colName => {
|
|
44
|
+
if (!resourceConfig.columns.find(c => c.name === colName)) {
|
|
45
|
+
throw new Error(`Column ${colName} specified in ListInplaceEdit plugin not found in resource ${resourceConfig.label}`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
instanceUniqueRepresentation(pluginOptions) {
|
|
50
|
+
return 'single';
|
|
51
|
+
}
|
|
52
|
+
setupEndpoints(server) {
|
|
53
|
+
// Add endpoint to update single field
|
|
54
|
+
server.endpoint({
|
|
55
|
+
method: 'POST',
|
|
56
|
+
path: `/plugin/${this.pluginInstanceId}/update-field`,
|
|
57
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
|
|
58
|
+
const { resourceId, recordId, field, value } = body;
|
|
59
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === resourceId);
|
|
60
|
+
// Create update object with just the single field
|
|
61
|
+
const updateRecord = { [field]: value };
|
|
62
|
+
// Use AdminForth's built-in update method
|
|
63
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
64
|
+
const oldRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
65
|
+
const result = yield this.adminforth.updateResourceRecord({
|
|
66
|
+
resource,
|
|
67
|
+
recordId,
|
|
68
|
+
record: updateRecord,
|
|
69
|
+
oldRecord,
|
|
70
|
+
adminUser
|
|
71
|
+
});
|
|
72
|
+
if (result.error) {
|
|
73
|
+
return { error: result.error };
|
|
74
|
+
}
|
|
75
|
+
const updatedRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
76
|
+
return { record: updatedRecord };
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { AdminForthPlugin } from "adminforth";
|
|
2
|
+
import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource } from "adminforth";
|
|
3
|
+
import type { PluginOptions } from './types.js';
|
|
4
|
+
|
|
5
|
+
export default class ListInPlaceEditPlugin extends AdminForthPlugin {
|
|
6
|
+
options: PluginOptions;
|
|
7
|
+
|
|
8
|
+
constructor(options: PluginOptions) {
|
|
9
|
+
super(options, import.meta.url);
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
14
|
+
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
15
|
+
|
|
16
|
+
const targetColumns = resourceConfig.columns.filter(col =>
|
|
17
|
+
this.options.columns.includes(col.name)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
targetColumns.forEach(column => {
|
|
21
|
+
if (column.components?.list) {
|
|
22
|
+
throw new Error(`Column ${column.name} already has a list component defined. ListInplaceEdit plugin cannot be used on columns that already have list components.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!column.components) {
|
|
26
|
+
column.components = {};
|
|
27
|
+
}
|
|
28
|
+
column.components.list = {
|
|
29
|
+
file: this.componentPath('InPlaceEdit.vue'),
|
|
30
|
+
meta: {
|
|
31
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
32
|
+
columnName: column.name
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
39
|
+
// Validate that columns exist if specific columns were specified
|
|
40
|
+
this.options.columns.forEach(colName => {
|
|
41
|
+
if (!resourceConfig.columns.find(c => c.name === colName)) {
|
|
42
|
+
throw new Error(`Column ${colName} specified in ListInplaceEdit plugin not found in resource ${resourceConfig.label}`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
instanceUniqueRepresentation(pluginOptions: any): string {
|
|
48
|
+
return 'single';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setupEndpoints(server: IHttpServer) {
|
|
52
|
+
// Add endpoint to update single field
|
|
53
|
+
server.endpoint({
|
|
54
|
+
method: 'POST',
|
|
55
|
+
path: `/plugin/${this.pluginInstanceId}/update-field`,
|
|
56
|
+
handler: async ({ body, adminUser }) => {
|
|
57
|
+
const { resourceId, recordId, field, value } = body;
|
|
58
|
+
|
|
59
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === resourceId);
|
|
60
|
+
// Create update object with just the single field
|
|
61
|
+
const updateRecord = { [field]: value };
|
|
62
|
+
|
|
63
|
+
// Use AdminForth's built-in update method
|
|
64
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
65
|
+
const oldRecord = await connector.getRecordByPrimaryKey(resource, recordId)
|
|
66
|
+
const result = await this.adminforth.updateResourceRecord({
|
|
67
|
+
resource,
|
|
68
|
+
recordId,
|
|
69
|
+
record: updateRecord,
|
|
70
|
+
oldRecord,
|
|
71
|
+
adminUser
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (result.error) {
|
|
75
|
+
return { error: result.error };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const updatedRecord = await connector.getRecordByPrimaryKey(resource, recordId);
|
|
79
|
+
return { record: updatedRecord };
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adminforth/list-in-place-edit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/",
|
|
9
|
+
"prepare": "npm link adminforth"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"adminforth"
|
|
16
|
+
],
|
|
17
|
+
"author": "devforth",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"description": "AdminForth List In Place Edit Plugin",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22.10.7",
|
|
22
|
+
"semantic-release": "^24.2.1",
|
|
23
|
+
"semantic-release-slack-bot": "^4.0.2",
|
|
24
|
+
"typescript": "^5.7.3"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/devforth/adminforth-list-in-place-edit"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"adminforth": "latest"
|
|
32
|
+
},
|
|
33
|
+
"release": {
|
|
34
|
+
"plugins": [
|
|
35
|
+
"@semantic-release/commit-analyzer",
|
|
36
|
+
"@semantic-release/release-notes-generator",
|
|
37
|
+
"@semantic-release/npm",
|
|
38
|
+
"@semantic-release/github",
|
|
39
|
+
[
|
|
40
|
+
"semantic-release-slack-bot",
|
|
41
|
+
{
|
|
42
|
+
"notifyOnSuccess": true,
|
|
43
|
+
"notifyOnFail": true,
|
|
44
|
+
"slackIcon": ":package:",
|
|
45
|
+
"markdownReleaseNotes": true
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
],
|
|
49
|
+
"branches": [
|
|
50
|
+
"main",
|
|
51
|
+
{
|
|
52
|
+
"name": "next",
|
|
53
|
+
"prerelease": true
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include*/
|
|
4
|
+
"module": "node16", /* Specify what module code is generated. */
|
|
5
|
+
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
|
6
|
+
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. */
|
|
7
|
+
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
8
|
+
"strict": false, /* Enable all strict type-checking options. */
|
|
9
|
+
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
|
10
|
+
},
|
|
11
|
+
"exclude": ["node_modules", "dist", "custom"], /* Exclude files from compilation. */
|
|
12
|
+
}
|
|
13
|
+
|
package/types.ts
ADDED