@adminforth/inline-create 1.0.1

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.
@@ -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/inline-create@1.0.0 build
3
+ > tsc && rsync -av --exclude 'node_modules' custom dist/
4
+
5
+ sending incremental file list
6
+ custom/
7
+ custom/InlineCreateForm.vue
8
+ custom/tsconfig.json
9
+
10
+ sent 7,474 bytes received 58 bytes 15,064.00 bytes/sec
11
+ total size is 7,257 speedup is 0.96
@@ -0,0 +1,221 @@
1
+ <template>
2
+ <template v-if="isCreating">
3
+ <td class="px-4 py-2"></td>
4
+ <td v-for="column in allVisibleColumns" :key="column.name" class="px-2 md:px-3 lg:px-6 py-2">
5
+ <div v-if="isEditableColumn(column)" class="flex gap-2">
6
+ <ColumnValueInputWrapper
7
+ ref="input"
8
+ class="min-w-24"
9
+ :source="'create'"
10
+ :column="column"
11
+ :currentValues="formData"
12
+ :mode="'create'"
13
+ :columnOptions="columnOptions"
14
+ :unmasked="unmasked"
15
+ :setCurrentValue="setCurrentValue"
16
+ @update:unmasked="handleUnmasked"
17
+ />
18
+ <div v-if="column.required?.create" ><IconExclamationCircleSolid class="w-4 h-4"/></div>
19
+ </div>
20
+ <div v-else></div>
21
+ </td>
22
+ <td class="px-4 pt-4 flex gap-2 items-center">
23
+ <button
24
+ @click="handleSave"
25
+ class="text-green-600 hover:text-green-800 disabled:opacity-50"
26
+ :disabled="saving || !isValid"
27
+ >
28
+ <IconCheckOutline v-if="!saving" class="w-5 h-5"/>
29
+ </button>
30
+ <button
31
+ @click="cancelCreate"
32
+ class="text-red-600 hover:text-red-800"
33
+ >
34
+ <IconXOutline class="w-5 h-5"/>
35
+ </button>
36
+ </td>
37
+ </template>
38
+ <template v-else>
39
+ <td :colspan="visibleColumns.length + 1" class="px-4 py-2">
40
+ <button
41
+ @click="startCreate"
42
+ class="w-full text-left text-sm text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
43
+ >
44
+ <div class="flex items-center">
45
+ <IconPlusOutline class="w-4 h-4 mr-2"/>
46
+ {{ t('Add new record') }}
47
+ </div>
48
+ </button>
49
+ </td>
50
+ </template>
51
+ </template>
52
+
53
+ <script setup>
54
+ import { ref, computed } from 'vue';
55
+ import { useCoreStore } from '@/stores/core';
56
+ import { callAdminForthApi } from '@/utils';
57
+ import { useI18n } from 'vue-i18n';
58
+ import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
59
+ import { IconCheckOutline, IconXOutline, IconPlusOutline, IconExclamationCircleSolid} from '@iconify-prerendered/vue-flowbite';
60
+ import { computedAsync } from '@vueuse/core';
61
+ import { useRouter } from 'vue-router';
62
+
63
+ const props = defineProps(['meta']);
64
+ const emit = defineEmits(['update:records']);
65
+ const { t } = useI18n();
66
+ const router = useRouter();
67
+
68
+ const coreStore = useCoreStore();
69
+ const isCreating = ref(false);
70
+ const saving = ref(false);
71
+ const formData = ref({});
72
+ const unmasked = ref({});
73
+ const invalidFields = ref({});
74
+ const emptyFields = ref({});
75
+
76
+ const visibleColumns = computed(() =>
77
+ coreStore.resource.columns.filter(c => !c.backendOnly && c.showIn?.create !== false && !c.primaryKey)
78
+ );
79
+ function handleUnmasked(columnName) {
80
+ unmasked.value[columnName] = !unmasked.value[columnName];
81
+ }
82
+
83
+ const allVisibleColumns = computed(() => {
84
+ const columnsMap = new Map();
85
+
86
+ coreStore.resource.columns.filter(c => c.showIn?.list).forEach(column => {
87
+ columnsMap.set(column.label, column);
88
+ });
89
+
90
+ visibleColumns.value.forEach(column => {
91
+ columnsMap.set(column.label, column);
92
+ });
93
+
94
+ return Array.from(columnsMap.values());
95
+ });
96
+
97
+ // Function to check if a column should be editable
98
+ function isEditableColumn(column) {
99
+ return !column.backendOnly && column.showIn?.create !== false && !column.primaryKey;
100
+ }
101
+
102
+ const columnOptions = computedAsync(async () => {
103
+ return (await Promise.all(
104
+ Object.values(coreStore.resource.columns).map(async (column) => {
105
+ if (column.foreignResource) {
106
+ const list = await callAdminForthApi({
107
+ method: 'POST',
108
+ path: `/get_resource_foreign_data`,
109
+ body: {
110
+ resourceId: coreStore.resource.resourceId,
111
+ column: column.name,
112
+ limit: 1000,
113
+ offset: 0,
114
+ },
115
+ });
116
+
117
+ if (!column.required?.create) list.items.push({ value: null, label: column.foreignResource.unsetLabel });
118
+
119
+ return { [column.name]: list.items };
120
+ }
121
+ })
122
+ )).reduce((acc, val) => Object.assign(acc, val), {})
123
+ }, {});
124
+
125
+ const isValid = computed(() => {
126
+ return !Object.values(invalidFields.value).some(invalid => invalid);
127
+ });
128
+
129
+ function initializeFormData() {
130
+ const newFormData = {};
131
+ visibleColumns.value.forEach(column => {
132
+ if (column.isArray?.enabled) {
133
+ newFormData[column.name] = []; // Initialize as empty array
134
+ } else if (column.type === 'json') {
135
+ newFormData[column.name] = null;
136
+ } else if (column.suggestOnCreate !== undefined) {
137
+ newFormData[column.name] = column.suggestOnCreate;
138
+ } else {
139
+ newFormData[column.name] = null;
140
+ }
141
+ });
142
+ formData.value = newFormData;
143
+ invalidFields.value = {};
144
+ emptyFields.value = {};
145
+ }
146
+
147
+ function startCreate() {
148
+ isCreating.value = true;
149
+ initializeFormData();
150
+ }
151
+
152
+ function cancelCreate() {
153
+ isCreating.value = false;
154
+ formData.value = {};
155
+ invalidFields.value = {};
156
+ emptyFields.value = {};
157
+ }
158
+
159
+ function setCurrentValue(field, value, arrayIndex = undefined) {
160
+ if (arrayIndex !== undefined) {
161
+ // Handle array updates
162
+ if (!Array.isArray(formData.value[field])) {
163
+ formData.value[field] = [];
164
+ }
165
+
166
+ const column = coreStore.resource.columns.find(c => c.name === field);
167
+ const newArray = [...formData.value[field]];
168
+
169
+ if (arrayIndex >= newArray.length) {
170
+ // When adding a new item, always add null
171
+ newArray.push(null);
172
+ } else {
173
+ // For existing items, handle type conversion
174
+ if (column?.isArray?.itemType && ['integer', 'float', 'decimal'].includes(column.isArray.itemType)) {
175
+ newArray[arrayIndex] = value !== null && value !== '' ? +value : null;
176
+ } else {
177
+ newArray[arrayIndex] = value;
178
+ }
179
+ }
180
+
181
+ // Assign the new array
182
+ formData.value[field] = newArray;
183
+ } else {
184
+ // Handle non-array updates
185
+ formData.value[field] = value;
186
+ }
187
+ }
188
+
189
+ async function handleSave() {
190
+ if (!isValid.value) return;
191
+
192
+ saving.value = true;
193
+ try {
194
+ const response = await callAdminForthApi({
195
+ method: 'POST',
196
+ path: `/plugin/${props.meta.pluginInstanceId}/create`,
197
+ body: {
198
+ resourceId: coreStore.resource.resourceId,
199
+ record: formData.value
200
+ }
201
+ });
202
+
203
+ if (response.error) {
204
+ adminforth.alert({
205
+ message: response.error,
206
+ variant: 'error'
207
+ });
208
+ return;
209
+ }
210
+ cancelCreate();
211
+
212
+ adminforth.alert({
213
+ message: t('Record created successfully!'),
214
+ variant: 'success'
215
+ });
216
+ await adminforth.list.refresh();
217
+ } finally {
218
+ saving.value = false;
219
+ }
220
+ }
221
+ </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,221 @@
1
+ <template>
2
+ <template v-if="isCreating">
3
+ <td class="px-4 py-2"></td>
4
+ <td v-for="column in allVisibleColumns" :key="column.name" class="px-2 md:px-3 lg:px-6 py-2">
5
+ <div v-if="isEditableColumn(column)" class="flex gap-2">
6
+ <ColumnValueInputWrapper
7
+ ref="input"
8
+ class="min-w-24"
9
+ :source="'create'"
10
+ :column="column"
11
+ :currentValues="formData"
12
+ :mode="'create'"
13
+ :columnOptions="columnOptions"
14
+ :unmasked="unmasked"
15
+ :setCurrentValue="setCurrentValue"
16
+ @update:unmasked="handleUnmasked"
17
+ />
18
+ <div v-if="column.required?.create" ><IconExclamationCircleSolid class="w-4 h-4"/></div>
19
+ </div>
20
+ <div v-else></div>
21
+ </td>
22
+ <td class="px-4 pt-4 flex gap-2 items-center">
23
+ <button
24
+ @click="handleSave"
25
+ class="text-green-600 hover:text-green-800 disabled:opacity-50"
26
+ :disabled="saving || !isValid"
27
+ >
28
+ <IconCheckOutline v-if="!saving" class="w-5 h-5"/>
29
+ </button>
30
+ <button
31
+ @click="cancelCreate"
32
+ class="text-red-600 hover:text-red-800"
33
+ >
34
+ <IconXOutline class="w-5 h-5"/>
35
+ </button>
36
+ </td>
37
+ </template>
38
+ <template v-else>
39
+ <td :colspan="visibleColumns.length + 1" class="px-4 py-2">
40
+ <button
41
+ @click="startCreate"
42
+ class="w-full text-left text-sm text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
43
+ >
44
+ <div class="flex items-center">
45
+ <IconPlusOutline class="w-4 h-4 mr-2"/>
46
+ {{ t('Add new record') }}
47
+ </div>
48
+ </button>
49
+ </td>
50
+ </template>
51
+ </template>
52
+
53
+ <script setup>
54
+ import { ref, computed } from 'vue';
55
+ import { useCoreStore } from '@/stores/core';
56
+ import { callAdminForthApi } from '@/utils';
57
+ import { useI18n } from 'vue-i18n';
58
+ import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
59
+ import { IconCheckOutline, IconXOutline, IconPlusOutline, IconExclamationCircleSolid} from '@iconify-prerendered/vue-flowbite';
60
+ import { computedAsync } from '@vueuse/core';
61
+ import { useRouter } from 'vue-router';
62
+
63
+ const props = defineProps(['meta']);
64
+ const emit = defineEmits(['update:records']);
65
+ const { t } = useI18n();
66
+ const router = useRouter();
67
+
68
+ const coreStore = useCoreStore();
69
+ const isCreating = ref(false);
70
+ const saving = ref(false);
71
+ const formData = ref({});
72
+ const unmasked = ref({});
73
+ const invalidFields = ref({});
74
+ const emptyFields = ref({});
75
+
76
+ const visibleColumns = computed(() =>
77
+ coreStore.resource.columns.filter(c => !c.backendOnly && c.showIn?.create !== false && !c.primaryKey)
78
+ );
79
+ function handleUnmasked(columnName) {
80
+ unmasked.value[columnName] = !unmasked.value[columnName];
81
+ }
82
+
83
+ const allVisibleColumns = computed(() => {
84
+ const columnsMap = new Map();
85
+
86
+ coreStore.resource.columns.filter(c => c.showIn?.list).forEach(column => {
87
+ columnsMap.set(column.label, column);
88
+ });
89
+
90
+ visibleColumns.value.forEach(column => {
91
+ columnsMap.set(column.label, column);
92
+ });
93
+
94
+ return Array.from(columnsMap.values());
95
+ });
96
+
97
+ // Function to check if a column should be editable
98
+ function isEditableColumn(column) {
99
+ return !column.backendOnly && column.showIn?.create !== false && !column.primaryKey;
100
+ }
101
+
102
+ const columnOptions = computedAsync(async () => {
103
+ return (await Promise.all(
104
+ Object.values(coreStore.resource.columns).map(async (column) => {
105
+ if (column.foreignResource) {
106
+ const list = await callAdminForthApi({
107
+ method: 'POST',
108
+ path: `/get_resource_foreign_data`,
109
+ body: {
110
+ resourceId: coreStore.resource.resourceId,
111
+ column: column.name,
112
+ limit: 1000,
113
+ offset: 0,
114
+ },
115
+ });
116
+
117
+ if (!column.required?.create) list.items.push({ value: null, label: column.foreignResource.unsetLabel });
118
+
119
+ return { [column.name]: list.items };
120
+ }
121
+ })
122
+ )).reduce((acc, val) => Object.assign(acc, val), {})
123
+ }, {});
124
+
125
+ const isValid = computed(() => {
126
+ return !Object.values(invalidFields.value).some(invalid => invalid);
127
+ });
128
+
129
+ function initializeFormData() {
130
+ const newFormData = {};
131
+ visibleColumns.value.forEach(column => {
132
+ if (column.isArray?.enabled) {
133
+ newFormData[column.name] = []; // Initialize as empty array
134
+ } else if (column.type === 'json') {
135
+ newFormData[column.name] = null;
136
+ } else if (column.suggestOnCreate !== undefined) {
137
+ newFormData[column.name] = column.suggestOnCreate;
138
+ } else {
139
+ newFormData[column.name] = null;
140
+ }
141
+ });
142
+ formData.value = newFormData;
143
+ invalidFields.value = {};
144
+ emptyFields.value = {};
145
+ }
146
+
147
+ function startCreate() {
148
+ isCreating.value = true;
149
+ initializeFormData();
150
+ }
151
+
152
+ function cancelCreate() {
153
+ isCreating.value = false;
154
+ formData.value = {};
155
+ invalidFields.value = {};
156
+ emptyFields.value = {};
157
+ }
158
+
159
+ function setCurrentValue(field, value, arrayIndex = undefined) {
160
+ if (arrayIndex !== undefined) {
161
+ // Handle array updates
162
+ if (!Array.isArray(formData.value[field])) {
163
+ formData.value[field] = [];
164
+ }
165
+
166
+ const column = coreStore.resource.columns.find(c => c.name === field);
167
+ const newArray = [...formData.value[field]];
168
+
169
+ if (arrayIndex >= newArray.length) {
170
+ // When adding a new item, always add null
171
+ newArray.push(null);
172
+ } else {
173
+ // For existing items, handle type conversion
174
+ if (column?.isArray?.itemType && ['integer', 'float', 'decimal'].includes(column.isArray.itemType)) {
175
+ newArray[arrayIndex] = value !== null && value !== '' ? +value : null;
176
+ } else {
177
+ newArray[arrayIndex] = value;
178
+ }
179
+ }
180
+
181
+ // Assign the new array
182
+ formData.value[field] = newArray;
183
+ } else {
184
+ // Handle non-array updates
185
+ formData.value[field] = value;
186
+ }
187
+ }
188
+
189
+ async function handleSave() {
190
+ if (!isValid.value) return;
191
+
192
+ saving.value = true;
193
+ try {
194
+ const response = await callAdminForthApi({
195
+ method: 'POST',
196
+ path: `/plugin/${props.meta.pluginInstanceId}/create`,
197
+ body: {
198
+ resourceId: coreStore.resource.resourceId,
199
+ record: formData.value
200
+ }
201
+ });
202
+
203
+ if (response.error) {
204
+ adminforth.alert({
205
+ message: response.error,
206
+ variant: 'error'
207
+ });
208
+ return;
209
+ }
210
+ cancelCreate();
211
+
212
+ adminforth.alert({
213
+ message: t('Record created successfully!'),
214
+ variant: 'success'
215
+ });
216
+ await adminforth.list.refresh();
217
+ } finally {
218
+ saving.value = false;
219
+ }
220
+ }
221
+ </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,87 @@
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 InlineCreatePlugin 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
+ if (!resourceConfig.options.pageInjections) {
23
+ resourceConfig.options.pageInjections = {};
24
+ }
25
+ if (!resourceConfig.options.pageInjections.list) {
26
+ resourceConfig.options.pageInjections.list = {};
27
+ }
28
+ resourceConfig.options.pageInjections.list.tableBodyStart = [{
29
+ file: this.componentPath('InlineCreateForm.vue'),
30
+ meta: {
31
+ pluginInstanceId: this.pluginInstanceId
32
+ }
33
+ }];
34
+ });
35
+ }
36
+ validateConfigAfterDiscover(adminforth, resourceConfig) {
37
+ var _a, _b, _c;
38
+ // Check each column for potential configuration issues
39
+ for (const column of resourceConfig.columns) {
40
+ if (column.backendOnly)
41
+ continue;
42
+ const isRequiredForCreate = ((_a = column.required) === null || _a === void 0 ? void 0 : _a.create) === true;
43
+ const isVisibleInList = ((_b = column.showIn) === null || _b === void 0 ? void 0 : _b.list) !== false;
44
+ const hasFillOnCreate = column.fillOnCreate !== undefined;
45
+ const isVisibleInCreate = ((_c = column.showIn) === null || _c === void 0 ? void 0 : _c.create) !== false;
46
+ if (isRequiredForCreate && !isVisibleInList && !hasFillOnCreate) {
47
+ throw new Error(`Column "${column.name}" in resource "${resourceConfig.resourceId}" is required for create but not visible in list view. ` +
48
+ 'Either:\n' +
49
+ '1) Set showIn.list: true, or\n' +
50
+ '2) Set required.create: false and ensure a database default exists, or\n' +
51
+ '3) Add fillOnCreate property and set showIn.create: false');
52
+ }
53
+ if (hasFillOnCreate && isVisibleInCreate) {
54
+ throw new Error(`Column "${column.name}" in resource "${resourceConfig.resourceId}" has fillOnCreate but is still visible in create form. ` +
55
+ 'When using fillOnCreate, set showIn.create: false');
56
+ }
57
+ }
58
+ }
59
+ instanceUniqueRepresentation() {
60
+ return 'inline-create';
61
+ }
62
+ setupEndpoints(server) {
63
+ server.endpoint({
64
+ method: 'POST',
65
+ path: `/plugin/${this.pluginInstanceId}/create`,
66
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminforth, adminUser }) {
67
+ const { record, resourceId } = body;
68
+ const resource = this.adminforth.config.resources.find(r => r.resourceId === resourceId);
69
+ const cleanRecord = resource.columns.reduce((acc, field) => {
70
+ if (record[field.name] !== undefined && record[field.name] !== null) {
71
+ acc[field.name] = record[field.name];
72
+ }
73
+ return acc;
74
+ }, {});
75
+ const result = yield this.adminforth.createResourceRecord({
76
+ resource,
77
+ record: cleanRecord,
78
+ adminUser
79
+ });
80
+ if (result.error) {
81
+ return { error: result.error };
82
+ }
83
+ return { record: result.createdRecord };
84
+ })
85
+ });
86
+ }
87
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/index.ts ADDED
@@ -0,0 +1,94 @@
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 InlineCreatePlugin 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
+ if (!resourceConfig.options.pageInjections) {
17
+ resourceConfig.options.pageInjections = {};
18
+ }
19
+
20
+ if (!resourceConfig.options.pageInjections.list) {
21
+ resourceConfig.options.pageInjections.list = {};
22
+ }
23
+
24
+ resourceConfig.options.pageInjections.list.tableBodyStart = [{
25
+ file: this.componentPath('InlineCreateForm.vue'),
26
+ meta: {
27
+ pluginInstanceId: this.pluginInstanceId
28
+ }
29
+ }];
30
+ }
31
+
32
+ validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
33
+ // Check each column for potential configuration issues
34
+ for (const column of resourceConfig.columns) {
35
+ if (column.backendOnly) continue;
36
+
37
+ const isRequiredForCreate = column.required?.create === true;
38
+ const isVisibleInList = column.showIn?.list !== false;
39
+ const hasFillOnCreate = column.fillOnCreate !== undefined;
40
+ const isVisibleInCreate = column.showIn?.create !== false;
41
+
42
+ if (isRequiredForCreate && !isVisibleInList && !hasFillOnCreate) {
43
+ throw new Error(
44
+ `Column "${column.name}" in resource "${resourceConfig.resourceId}" is required for create but not visible in list view. ` +
45
+ 'Either:\n' +
46
+ '1) Set showIn.list: true, or\n' +
47
+ '2) Set required.create: false and ensure a database default exists, or\n' +
48
+ '3) Add fillOnCreate property and set showIn.create: false'
49
+ );
50
+ }
51
+
52
+ if (hasFillOnCreate && isVisibleInCreate) {
53
+ throw new Error(
54
+ `Column "${column.name}" in resource "${resourceConfig.resourceId}" has fillOnCreate but is still visible in create form. ` +
55
+ 'When using fillOnCreate, set showIn.create: false'
56
+ );
57
+ }
58
+ }
59
+ }
60
+
61
+ instanceUniqueRepresentation() {
62
+ return 'inline-create';
63
+ }
64
+
65
+ setupEndpoints(server: IHttpServer) {
66
+ server.endpoint({
67
+ method: 'POST',
68
+ path: `/plugin/${this.pluginInstanceId}/create`,
69
+ handler: async ({ body, adminforth, adminUser }) => {
70
+ const { record, resourceId } = body;
71
+
72
+ const resource = this.adminforth.config.resources.find(r => r.resourceId === resourceId);
73
+
74
+ const cleanRecord = resource.columns.reduce((acc, field) => {
75
+ if (record[field.name] !== undefined && record[field.name] !== null) {
76
+ acc[field.name] = record[field.name];
77
+ }
78
+ return acc;
79
+ }, {});
80
+
81
+ const result = await this.adminforth.createResourceRecord({
82
+ resource,
83
+ record: cleanRecord,
84
+ adminUser
85
+ });
86
+
87
+ if (result.error) {
88
+ return { error: result.error };
89
+ }
90
+ return { record: result.createdRecord };
91
+ }
92
+ });
93
+ }
94
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@adminforth/inline-create",
3
+ "version": "1.0.1",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "type": "module",
7
+ "scripts": {
8
+ "prepare": "npm link adminforth",
9
+ "build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/devforth/adminforth-inline-create.git"
17
+ },
18
+ "keywords": [
19
+ "adminforth",
20
+ "inline create"
21
+ ],
22
+ "author": "devforth",
23
+ "license": "MIT",
24
+ "description": "Inline create plugin for adminforth",
25
+ "dependencies": {
26
+ "adminforth": "latest"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.10.7",
30
+ "semantic-release": "^24.2.1",
31
+ "semantic-release-slack-bot": "^4.0.2",
32
+ "typescript": "^5.7.3"
33
+ },
34
+ "release": {
35
+ "plugins": [
36
+ "@semantic-release/commit-analyzer",
37
+ "@semantic-release/release-notes-generator",
38
+ "@semantic-release/npm",
39
+ "@semantic-release/github",
40
+ [
41
+ "semantic-release-slack-bot",
42
+ {
43
+ "notifyOnSuccess": true,
44
+ "notifyOnFail": true,
45
+ "slackIcon": ":package:",
46
+ "markdownReleaseNotes": true
47
+ }
48
+ ]
49
+ ],
50
+ "branches": [
51
+ "main",
52
+ {
53
+ "name": "next",
54
+ "prerelease": true
55
+ }
56
+ ]
57
+ }
58
+ }
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
@@ -0,0 +1,3 @@
1
+ export interface PluginOptions {
2
+
3
+ }