@motor-cms/ui-admin 1.0.1-alpha.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/README.md +77 -0
- package/app/components/form/inputs/CategoryTreeInput.vue +154 -0
- package/app/components/form/inputs/CategoryTreePicker.vue +355 -0
- package/app/components/form/inputs/NestedDraggable.vue +217 -0
- package/app/components/form/inputs/QuicklinksInput.vue +186 -0
- package/app/lang/de/motor-admin/CLAUDE.md +21 -0
- package/app/lang/de/motor-admin/ai_system_prompts.json +12 -0
- package/app/lang/de/motor-admin/categories.json +12 -0
- package/app/lang/de/motor-admin/category_trees.json +14 -0
- package/app/lang/de/motor-admin/clients.json +26 -0
- package/app/lang/de/motor-admin/config_variables.json +14 -0
- package/app/lang/de/motor-admin/domains.json +19 -0
- package/app/lang/de/motor-admin/email_templates.json +38 -0
- package/app/lang/de/motor-admin/global.json +5 -0
- package/app/lang/de/motor-admin/languages.json +16 -0
- package/app/lang/de/motor-admin/permissions.json +14 -0
- package/app/lang/de/motor-admin/roles.json +15 -0
- package/app/lang/de/motor-admin/users.json +22 -0
- package/app/lang/en/motor-admin/CLAUDE.md +7 -0
- package/app/lang/en/motor-admin/ai_system_prompts.json +12 -0
- package/app/lang/en/motor-admin/categories.json +12 -0
- package/app/lang/en/motor-admin/category_trees.json +14 -0
- package/app/lang/en/motor-admin/clients.json +26 -0
- package/app/lang/en/motor-admin/config_variables.json +14 -0
- package/app/lang/en/motor-admin/domains.json +18 -0
- package/app/lang/en/motor-admin/email_templates.json +33 -0
- package/app/lang/en/motor-admin/global.json +5 -0
- package/app/lang/en/motor-admin/languages.json +16 -0
- package/app/lang/en/motor-admin/permissions.json +14 -0
- package/app/lang/en/motor-admin/roles.json +15 -0
- package/app/lang/en/motor-admin/users.json +22 -0
- package/app/pages/dashboard.vue +5 -0
- package/app/pages/index.vue +39 -0
- package/app/pages/login.vue +85 -0
- package/app/pages/motor-admin/ai-system-prompts/CLAUDE.md +7 -0
- package/app/pages/motor-admin/ai-system-prompts/[id]/edit.vue +48 -0
- package/app/pages/motor-admin/ai-system-prompts/create.vue +40 -0
- package/app/pages/motor-admin/ai-system-prompts/index.vue +68 -0
- package/app/pages/motor-admin/category-trees/CLAUDE.md +7 -0
- package/app/pages/motor-admin/category-trees/[id]/CLAUDE.md +7 -0
- package/app/pages/motor-admin/category-trees/[id]/categories/[categoryId]/edit.vue +73 -0
- package/app/pages/motor-admin/category-trees/[id]/categories/create.vue +64 -0
- package/app/pages/motor-admin/category-trees/[id]/edit.vue +45 -0
- package/app/pages/motor-admin/category-trees/[id]/index.vue +81 -0
- package/app/pages/motor-admin/category-trees/create.vue +37 -0
- package/app/pages/motor-admin/category-trees/index.vue +54 -0
- package/app/pages/motor-admin/clients/CLAUDE.md +11 -0
- package/app/pages/motor-admin/clients/[id]/CLAUDE.md +11 -0
- package/app/pages/motor-admin/clients/[id]/edit.vue +45 -0
- package/app/pages/motor-admin/clients/create.vue +37 -0
- package/app/pages/motor-admin/clients/index.vue +46 -0
- package/app/pages/motor-admin/config-variables/CLAUDE.md +11 -0
- package/app/pages/motor-admin/config-variables/[id]/edit.vue +44 -0
- package/app/pages/motor-admin/config-variables/create.vue +36 -0
- package/app/pages/motor-admin/config-variables/index.vue +66 -0
- package/app/pages/motor-admin/domains/CLAUDE.md +11 -0
- package/app/pages/motor-admin/domains/[id]/edit.vue +54 -0
- package/app/pages/motor-admin/domains/create.vue +46 -0
- package/app/pages/motor-admin/domains/index.vue +98 -0
- package/app/pages/motor-admin/email-templates/CLAUDE.md +12 -0
- package/app/pages/motor-admin/email-templates/[id]/CLAUDE.md +7 -0
- package/app/pages/motor-admin/email-templates/[id]/edit.vue +56 -0
- package/app/pages/motor-admin/email-templates/create.vue +48 -0
- package/app/pages/motor-admin/email-templates/index.vue +67 -0
- package/app/pages/motor-admin/index.vue +12 -0
- package/app/pages/motor-admin/languages/CLAUDE.md +7 -0
- package/app/pages/motor-admin/languages/[id]/edit.vue +44 -0
- package/app/pages/motor-admin/languages/create.vue +36 -0
- package/app/pages/motor-admin/languages/index.vue +44 -0
- package/app/pages/motor-admin/permission-groups/CLAUDE.md +14 -0
- package/app/pages/motor-admin/permission-groups/[id]/CLAUDE.md +11 -0
- package/app/pages/motor-admin/permission-groups/[id]/edit.vue +49 -0
- package/app/pages/motor-admin/permission-groups/create.vue +41 -0
- package/app/pages/motor-admin/permission-groups/index.vue +43 -0
- package/app/pages/motor-admin/roles/CLAUDE.md +7 -0
- package/app/pages/motor-admin/roles/[id]/edit.vue +47 -0
- package/app/pages/motor-admin/roles/create.vue +40 -0
- package/app/pages/motor-admin/roles/index.vue +45 -0
- package/app/pages/motor-admin/theme-preview/CLAUDE.md +7 -0
- package/app/pages/motor-admin/theme-preview/index.vue +4801 -0
- package/app/pages/motor-admin/theme-preview/themes/CLAUDE.md +11 -0
- package/app/pages/motor-admin/theme-preview/themes/asymmetric-brutalist.md +381 -0
- package/app/pages/motor-admin/theme-preview/themes/bold-modern.md +231 -0
- package/app/pages/motor-admin/theme-preview/themes/geometric-minimal.md +778 -0
- package/app/pages/motor-admin/theme-preview/themes/gradient-flow.md +1057 -0
- package/app/pages/motor-admin/theme-preview/themes/liquid-glass.md +823 -0
- package/app/pages/motor-admin/theme-preview/themes/neon-amber.md +1223 -0
- package/app/pages/motor-admin/theme-preview/themes/neon-terminal.md +779 -0
- package/app/pages/motor-admin/theme-preview/themes/neon-violet.md +1134 -0
- package/app/pages/motor-admin/theme-preview/themes/professional-clean.md +232 -0
- package/app/pages/motor-admin/theme-preview/themes/refined-brutalist.md +462 -0
- package/app/pages/motor-admin/theme-preview/themes/wild-card.md +263 -0
- package/app/pages/motor-admin/users/CLAUDE.md +17 -0
- package/app/pages/motor-admin/users/[id]/CLAUDE.md +11 -0
- package/app/pages/motor-admin/users/[id]/edit.vue +83 -0
- package/app/pages/motor-admin/users/create.vue +40 -0
- package/app/pages/motor-admin/users/index.vue +66 -0
- package/app/pages/profile.vue +363 -0
- package/app/pages/search.vue +91 -0
- package/app/types/generated/form-meta.ts +258 -0
- package/app/types/generated/grid-meta.ts +172 -0
- package/nuxt.config.ts +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<!-- app/components/form/inputs/NestedDraggable.vue -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import type { TreeNode } from '@motor-cms/ui-core/app/types/tree'
|
|
4
|
+
|
|
5
|
+
defineOptions({ name: 'FormInputsNestedDraggable' })
|
|
6
|
+
|
|
7
|
+
defineProps<{
|
|
8
|
+
items: TreeNode[]
|
|
9
|
+
parentId: number | null
|
|
10
|
+
depth: number
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
currentId, nonSelectableIds, moveItem,
|
|
15
|
+
isEditMode, isDragging, expandedIds, toggleNode,
|
|
16
|
+
handleDragOver, cleanupAutoExpand
|
|
17
|
+
} = inject<{
|
|
18
|
+
currentId: ComputedRef<number | undefined>
|
|
19
|
+
nonSelectableIds: ComputedRef<Set<number>>
|
|
20
|
+
moveItem: (itemId: number, newParentId: number | null, newIndex: number) => void
|
|
21
|
+
isEditMode: ComputedRef<boolean>
|
|
22
|
+
isDragging: Ref<boolean>
|
|
23
|
+
expandedIds: ComputedRef<Set<number>>
|
|
24
|
+
toggleNode: (id: number) => void
|
|
25
|
+
handleDragOver: (itemId: number) => void
|
|
26
|
+
cleanupAutoExpand: (dropParentId: number | null) => void
|
|
27
|
+
}>('categoryTree')!
|
|
28
|
+
|
|
29
|
+
function isExpanded(id: number): boolean {
|
|
30
|
+
return expandedIds.value.has(id)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasChildren(item: TreeNode): boolean {
|
|
34
|
+
return item.children.length > 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onItemDragOver(item: TreeNode) {
|
|
38
|
+
if (!isDragging.value) return
|
|
39
|
+
handleDragOver(item.id)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const listRef = ref<HTMLElement | null>(null)
|
|
43
|
+
let sortableInstance: { destroy: () => void } | null = null
|
|
44
|
+
|
|
45
|
+
onMounted(async () => {
|
|
46
|
+
if (!listRef.value || !isEditMode.value) return
|
|
47
|
+
|
|
48
|
+
const { default: Sortable } = await import('sortablejs')
|
|
49
|
+
|
|
50
|
+
sortableInstance = Sortable.create(listRef.value, {
|
|
51
|
+
group: { name: 'categories' },
|
|
52
|
+
animation: 200,
|
|
53
|
+
fallbackOnBody: true,
|
|
54
|
+
swapThreshold: 0.5,
|
|
55
|
+
invertSwap: true,
|
|
56
|
+
invertedSwapThreshold: 0.5,
|
|
57
|
+
emptyInsertThreshold: 8,
|
|
58
|
+
filter: '.no-drag',
|
|
59
|
+
preventOnFilter: false,
|
|
60
|
+
ghostClass: 'opacity-30',
|
|
61
|
+
onStart() {
|
|
62
|
+
isDragging.value = true
|
|
63
|
+
},
|
|
64
|
+
onMove(evt) {
|
|
65
|
+
// Prevent dropping into the current item's own descendants
|
|
66
|
+
const targetParentIdStr = (evt.to as HTMLElement).dataset.parentId
|
|
67
|
+
if (targetParentIdStr) {
|
|
68
|
+
const targetParentId = parseInt(targetParentIdStr)
|
|
69
|
+
if (!isNaN(targetParentId) && nonSelectableIds.value.has(targetParentId)) {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return true
|
|
74
|
+
},
|
|
75
|
+
onEnd(evt) {
|
|
76
|
+
isDragging.value = false
|
|
77
|
+
|
|
78
|
+
if (evt.newIndex === undefined || evt.oldIndex === undefined) {
|
|
79
|
+
cleanupAutoExpand(null)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const itemId = parseInt(evt.item.dataset.id!)
|
|
84
|
+
if (isNaN(itemId)) {
|
|
85
|
+
cleanupAutoExpand(null)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const targetParentIdStr = (evt.to as HTMLElement).dataset.parentId
|
|
90
|
+
const newParentId = targetParentIdStr ? parseInt(targetParentIdStr) : null
|
|
91
|
+
const newIndex = evt.newIndex
|
|
92
|
+
|
|
93
|
+
// Revert DOM change — let Vue handle re-rendering from reactive data
|
|
94
|
+
if (evt.from !== evt.to) {
|
|
95
|
+
evt.to.removeChild(evt.item)
|
|
96
|
+
const ref = evt.from.children[evt.oldIndex] || null
|
|
97
|
+
evt.from.insertBefore(evt.item, ref)
|
|
98
|
+
} else {
|
|
99
|
+
evt.from.removeChild(evt.item)
|
|
100
|
+
const ref = evt.from.children[evt.oldIndex] || null
|
|
101
|
+
evt.from.insertBefore(evt.item, ref)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
moveItem(itemId, newParentId, newIndex)
|
|
105
|
+
cleanupAutoExpand(newParentId)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
onBeforeUnmount(() => {
|
|
111
|
+
sortableInstance?.destroy()
|
|
112
|
+
})
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<template>
|
|
116
|
+
<ul
|
|
117
|
+
ref="listRef"
|
|
118
|
+
:data-parent-id="parentId ?? ''"
|
|
119
|
+
:class="[
|
|
120
|
+
'list-none',
|
|
121
|
+
depth > 0 ? 'ml-4 pl-3' : '',
|
|
122
|
+
depth > 0 && items.length > 0 ? 'border-l border-[var(--ui-border)]' : '',
|
|
123
|
+
items.length === 0 ? 'min-h-[8px]' : '',
|
|
124
|
+
isEditMode && items.length > 0 ? 'pb-1' : ''
|
|
125
|
+
]"
|
|
126
|
+
>
|
|
127
|
+
<li
|
|
128
|
+
v-for="item in items"
|
|
129
|
+
:key="item.id"
|
|
130
|
+
:data-id="item.id"
|
|
131
|
+
:class="{ 'no-drag': item.id !== currentId }"
|
|
132
|
+
>
|
|
133
|
+
<!-- Current item: highlighted + draggable -->
|
|
134
|
+
<div
|
|
135
|
+
v-if="item.id === currentId"
|
|
136
|
+
class="flex items-center gap-1 px-2 py-1.5 my-0.5 cursor-grab active:cursor-grabbing"
|
|
137
|
+
>
|
|
138
|
+
<span class="shrink-0 w-4" />
|
|
139
|
+
<span class="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 -my-0.5 ring-2 ring-[var(--ui-primary)] bg-[var(--ui-primary)]/10 font-semibold text-[var(--ui-primary)]">
|
|
140
|
+
<UIcon
|
|
141
|
+
name="i-lucide-grip-vertical"
|
|
142
|
+
class="shrink-0 size-4 opacity-60"
|
|
143
|
+
/>
|
|
144
|
+
{{ item.name || '…' }}
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<!-- Non-selectable descendants (dimmed) -->
|
|
149
|
+
<div
|
|
150
|
+
v-else-if="nonSelectableIds.has(item.id)"
|
|
151
|
+
class="flex items-center gap-1 px-2 py-1.5 my-0.5 opacity-40"
|
|
152
|
+
@dragover="onItemDragOver(item)"
|
|
153
|
+
>
|
|
154
|
+
<button
|
|
155
|
+
v-if="hasChildren(item)"
|
|
156
|
+
class="shrink-0 size-6 sm:size-4 flex items-center justify-center"
|
|
157
|
+
type="button"
|
|
158
|
+
@click.stop="toggleNode(item.id)"
|
|
159
|
+
>
|
|
160
|
+
<UIcon
|
|
161
|
+
name="i-lucide-chevron-right"
|
|
162
|
+
class="size-3.5 transition-transform duration-200"
|
|
163
|
+
:class="{ 'rotate-90': isExpanded(item.id) }"
|
|
164
|
+
/>
|
|
165
|
+
</button>
|
|
166
|
+
<span
|
|
167
|
+
v-else
|
|
168
|
+
class="shrink-0 w-6 sm:w-4"
|
|
169
|
+
/>
|
|
170
|
+
<UIcon
|
|
171
|
+
:name="hasChildren(item) ? (isExpanded(item.id) ? 'i-lucide-folder-open' : 'i-lucide-folder') : 'i-lucide-file'"
|
|
172
|
+
class="shrink-0 size-4"
|
|
173
|
+
/>
|
|
174
|
+
{{ item.name }}
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<!-- Other items (static) -->
|
|
178
|
+
<div
|
|
179
|
+
v-else
|
|
180
|
+
class="flex items-center gap-1 px-2 py-1.5 my-0.5"
|
|
181
|
+
@dragover="onItemDragOver(item)"
|
|
182
|
+
>
|
|
183
|
+
<button
|
|
184
|
+
v-if="hasChildren(item)"
|
|
185
|
+
class="shrink-0 size-6 sm:size-4 flex items-center justify-center rounded hover:bg-[var(--ui-bg-elevated)] transition-colors cursor-pointer"
|
|
186
|
+
type="button"
|
|
187
|
+
@click.stop="toggleNode(item.id)"
|
|
188
|
+
>
|
|
189
|
+
<UIcon
|
|
190
|
+
name="i-lucide-chevron-right"
|
|
191
|
+
class="size-3.5 transition-transform duration-200"
|
|
192
|
+
:class="{ 'rotate-90': isExpanded(item.id) }"
|
|
193
|
+
/>
|
|
194
|
+
</button>
|
|
195
|
+
<span
|
|
196
|
+
v-else
|
|
197
|
+
class="shrink-0 w-6 sm:w-4"
|
|
198
|
+
/>
|
|
199
|
+
<UIcon
|
|
200
|
+
:name="hasChildren(item) ? (isExpanded(item.id) ? 'i-lucide-folder-open' : 'i-lucide-folder') : 'i-lucide-file'"
|
|
201
|
+
class="shrink-0 size-4 opacity-60"
|
|
202
|
+
/>
|
|
203
|
+
{{ item.name }}
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- Children container:
|
|
207
|
+
- Skip for current item (its children move with it, no need to render)
|
|
208
|
+
- Always render in drag mode (expanded shows items, collapsed is empty drop target) -->
|
|
209
|
+
<FormInputsNestedDraggable
|
|
210
|
+
v-if="item.id !== currentId && isEditMode"
|
|
211
|
+
:items="isExpanded(item.id) ? item.children : []"
|
|
212
|
+
:parent-id="item.id"
|
|
213
|
+
:depth="depth + 1"
|
|
214
|
+
/>
|
|
215
|
+
</li>
|
|
216
|
+
</ul>
|
|
217
|
+
</template>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<!-- app/components/form/inputs/QuicklinksInput.vue -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
interface Quicklink {
|
|
4
|
+
url: string
|
|
5
|
+
url_label: string
|
|
6
|
+
position: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = defineProps<{
|
|
10
|
+
modelValue: Quicklink[]
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const emit = defineEmits<{
|
|
14
|
+
'update:modelValue': [value: Quicklink[]]
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const { t } = useI18n()
|
|
18
|
+
|
|
19
|
+
// Input state for adding a new link
|
|
20
|
+
const newUrl = ref('')
|
|
21
|
+
const newLabel = ref('')
|
|
22
|
+
const urlError = ref('')
|
|
23
|
+
|
|
24
|
+
const canAdd = computed(() => {
|
|
25
|
+
if (!newUrl.value || !newLabel.value) return false
|
|
26
|
+
try {
|
|
27
|
+
new URL(newUrl.value)
|
|
28
|
+
return true
|
|
29
|
+
} catch {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
function validateUrl() {
|
|
35
|
+
if (!newUrl.value) {
|
|
36
|
+
urlError.value = ''
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
new URL(newUrl.value)
|
|
41
|
+
urlError.value = ''
|
|
42
|
+
} catch {
|
|
43
|
+
urlError.value = t('motor-core.global.validation_url')
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function addLink() {
|
|
48
|
+
if (!canAdd.value) return
|
|
49
|
+
const links = [...props.modelValue]
|
|
50
|
+
links.push({
|
|
51
|
+
url: newUrl.value,
|
|
52
|
+
url_label: newLabel.value,
|
|
53
|
+
position: links.length
|
|
54
|
+
})
|
|
55
|
+
emit('update:modelValue', links)
|
|
56
|
+
newUrl.value = ''
|
|
57
|
+
newLabel.value = ''
|
|
58
|
+
urlError.value = ''
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function removeLink(index: number) {
|
|
62
|
+
const links = props.modelValue
|
|
63
|
+
.filter((_, i) => i !== index)
|
|
64
|
+
.map((link, i) => ({ ...link, position: i }))
|
|
65
|
+
emit('update:modelValue', links)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Drag reorder
|
|
69
|
+
const listRef = ref<HTMLElement | null>(null)
|
|
70
|
+
let sortableInstance: { destroy: () => void } | null = null
|
|
71
|
+
|
|
72
|
+
async function initSortable() {
|
|
73
|
+
if (!listRef.value) return
|
|
74
|
+
sortableInstance?.destroy()
|
|
75
|
+
const { default: Sortable } = await import('sortablejs')
|
|
76
|
+
sortableInstance = Sortable.create(listRef.value, {
|
|
77
|
+
animation: 150,
|
|
78
|
+
handle: '.drag-handle',
|
|
79
|
+
onEnd(evt) {
|
|
80
|
+
const { oldIndex, newIndex } = evt
|
|
81
|
+
if (oldIndex == null || newIndex == null || oldIndex === newIndex) return
|
|
82
|
+
|
|
83
|
+
// Revert DOM swap — let Vue re-render from updated data
|
|
84
|
+
const parent = evt.item.parentNode!
|
|
85
|
+
if (oldIndex < newIndex) {
|
|
86
|
+
const refNode = parent.children[oldIndex] ?? null
|
|
87
|
+
parent.insertBefore(evt.item, refNode)
|
|
88
|
+
} else {
|
|
89
|
+
const refNode = parent.children[oldIndex + 1] ?? null
|
|
90
|
+
parent.insertBefore(evt.item, refNode)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const links = [...props.modelValue]
|
|
94
|
+
const [moved] = links.splice(oldIndex, 1)
|
|
95
|
+
if (moved) {
|
|
96
|
+
links.splice(newIndex, 0, moved)
|
|
97
|
+
emit('update:modelValue', links.map((l, i) => ({ ...l, position: i })))
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
watch(() => props.modelValue.length, () => {
|
|
104
|
+
nextTick(() => initSortable())
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
onMounted(() => {
|
|
108
|
+
if (props.modelValue.length > 0) {
|
|
109
|
+
nextTick(() => initSortable())
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
onBeforeUnmount(() => {
|
|
114
|
+
sortableInstance?.destroy()
|
|
115
|
+
})
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<div>
|
|
120
|
+
<!-- Existing links -->
|
|
121
|
+
<div
|
|
122
|
+
v-if="modelValue.length > 0"
|
|
123
|
+
ref="listRef"
|
|
124
|
+
class="mb-4 divide-y divide-default rounded-lg ring-1 ring-[var(--ui-border)]"
|
|
125
|
+
>
|
|
126
|
+
<div
|
|
127
|
+
v-for="(link, index) in modelValue"
|
|
128
|
+
:key="index"
|
|
129
|
+
class="flex items-center gap-3 px-3 py-2 group"
|
|
130
|
+
>
|
|
131
|
+
<UIcon
|
|
132
|
+
name="i-lucide-grip-vertical"
|
|
133
|
+
class="drag-handle size-4 text-muted cursor-grab shrink-0"
|
|
134
|
+
/>
|
|
135
|
+
<div class="flex-1 min-w-0">
|
|
136
|
+
<div class="text-sm font-medium truncate">
|
|
137
|
+
{{ link.url_label }}
|
|
138
|
+
</div>
|
|
139
|
+
<div class="text-xs text-muted truncate">
|
|
140
|
+
{{ link.url }}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
<UButton
|
|
144
|
+
icon="i-lucide-trash-2"
|
|
145
|
+
variant="ghost"
|
|
146
|
+
color="error"
|
|
147
|
+
size="xs"
|
|
148
|
+
class="opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
|
|
149
|
+
@click="removeLink(index)"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<!-- Add new link -->
|
|
155
|
+
<div class="flex items-start gap-3">
|
|
156
|
+
<div class="flex-1">
|
|
157
|
+
<UInput
|
|
158
|
+
v-model="newUrl"
|
|
159
|
+
:placeholder="t('motor-builder.search_configs.link_url')"
|
|
160
|
+
class="w-full"
|
|
161
|
+
@blur="validateUrl"
|
|
162
|
+
@keydown.enter.prevent="addLink"
|
|
163
|
+
/>
|
|
164
|
+
<p
|
|
165
|
+
v-if="urlError"
|
|
166
|
+
class="text-xs text-[var(--ui-error)] mt-1"
|
|
167
|
+
>
|
|
168
|
+
{{ urlError }}
|
|
169
|
+
</p>
|
|
170
|
+
</div>
|
|
171
|
+
<UInput
|
|
172
|
+
v-model="newLabel"
|
|
173
|
+
:placeholder="t('motor-builder.search_configs.link_url_label')"
|
|
174
|
+
class="flex-1"
|
|
175
|
+
@keydown.enter.prevent="addLink"
|
|
176
|
+
/>
|
|
177
|
+
<UButton
|
|
178
|
+
icon="i-lucide-plus"
|
|
179
|
+
variant="outline"
|
|
180
|
+
:disabled="!canAdd"
|
|
181
|
+
class="shrink-0"
|
|
182
|
+
@click="addLink"
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Feb 12, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #24269 | 3:11 PM | 🟣 | Permissions Multi-Select Field Added to Permission-Groups Form | ~435 |
|
|
11
|
+
| #24251 | 1:14 PM | 🔵 | Comprehensive Application Refactoring Staged for Commit | ~515 |
|
|
12
|
+
|
|
13
|
+
### Feb 18, 2026
|
|
14
|
+
|
|
15
|
+
| ID | Time | T | Title | Read |
|
|
16
|
+
|----|------|---|-------|------|
|
|
17
|
+
| #27326 | 5:36 PM | ✅ | Password UX Translation Keys Added | ~171 |
|
|
18
|
+
| #27302 | 5:30 PM | ✅ | Translation Key Added for Filter Count Badge | ~147 |
|
|
19
|
+
| #27301 | " | 🔵 | Grid Translation Keys Inventory | ~233 |
|
|
20
|
+
| #27271 | 5:27 PM | 🔵 | i18n Translation Keys Structure Reviewed | ~251 |
|
|
21
|
+
</claude-mem-context>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ai_system_prompts": "KI-System-Prompts",
|
|
3
|
+
"title": "KI-System-Prompts",
|
|
4
|
+
"subtitle": "KI-System-Prompts verwalten",
|
|
5
|
+
"add": "KI-System-Prompt hinzufügen",
|
|
6
|
+
"prompt": "Prompt",
|
|
7
|
+
"prompt_description": "Der System-Prompt-Text, der an das KI-Modell gesendet wird",
|
|
8
|
+
"create_title": "KI-System-Prompt erstellen",
|
|
9
|
+
"edit_title": "KI-System-Prompt bearbeiten",
|
|
10
|
+
"created_success": "KI-System-Prompt erfolgreich erstellt.",
|
|
11
|
+
"updated_success": "KI-System-Prompt erfolgreich aktualisiert."
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": "Kategorien",
|
|
3
|
+
"category": "Kategorie",
|
|
4
|
+
"title": "Kategorien",
|
|
5
|
+
"add": "Kategorie hinzufügen",
|
|
6
|
+
"create_title": "Kategorie erstellen",
|
|
7
|
+
"edit_title": "Kategorie bearbeiten",
|
|
8
|
+
"created_success": "Kategorie wurde erfolgreich erstellt",
|
|
9
|
+
"updated_success": "Kategorie wurde erfolgreich aktualisiert",
|
|
10
|
+
"parent": "Übergeordnete Kategorie",
|
|
11
|
+
"root": "Stamm (oberste Ebene)"
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"category_trees": "Kategoriebäume",
|
|
3
|
+
"category_tree": "Kategoriebaum",
|
|
4
|
+
"title": "Kategoriebäume",
|
|
5
|
+
"subtitle": "Kategoriebäume und ihre Kategorien verwalten",
|
|
6
|
+
"add": "Kategoriebaum hinzufügen",
|
|
7
|
+
"create_title": "Kategoriebaum erstellen",
|
|
8
|
+
"edit_title": "Kategoriebaum bearbeiten",
|
|
9
|
+
"created_success": "Kategoriebaum wurde erfolgreich erstellt",
|
|
10
|
+
"updated_success": "Kategoriebaum wurde erfolgreich aktualisiert",
|
|
11
|
+
"children": "Kinder",
|
|
12
|
+
"children_title": "Kategorien in \"{name}\"",
|
|
13
|
+
"children_subtitle": "Kategorien in diesem Baum verwalten"
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"clients": "Mandanten",
|
|
3
|
+
"client": "Mandant",
|
|
4
|
+
"title": "Mandanten",
|
|
5
|
+
"subtitle": "Systemmandanten verwalten",
|
|
6
|
+
"add": "Mandant hinzufügen",
|
|
7
|
+
"create_title": "Mandant erstellen",
|
|
8
|
+
"edit_title": "Mandant bearbeiten",
|
|
9
|
+
"created_success": "Mandant erfolgreich erstellt.",
|
|
10
|
+
"updated_success": "Mandant erfolgreich aktualisiert.",
|
|
11
|
+
"slug": "Slug",
|
|
12
|
+
"slug_description": "URL-freundlicher Bezeichner, automatisch aus Name generiert",
|
|
13
|
+
"address": "Adresse",
|
|
14
|
+
"zip": "PLZ",
|
|
15
|
+
"city": "Stadt",
|
|
16
|
+
"country": "Land",
|
|
17
|
+
"country_iso_3166_1": "Ländercode",
|
|
18
|
+
"website": "Webseite",
|
|
19
|
+
"description": "Beschreibung",
|
|
20
|
+
"contact_name": "Kontaktperson",
|
|
21
|
+
"contact_email": "Kontakt-E-Mail",
|
|
22
|
+
"contact_phone": "Telefon",
|
|
23
|
+
"group_address": "Adresse",
|
|
24
|
+
"group_contact": "Kontakt",
|
|
25
|
+
"group_other": "Sonstiges"
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config_variables": "Konfigurationsvariablen",
|
|
3
|
+
"title": "Konfigurationsvariablen",
|
|
4
|
+
"subtitle": "Konfigurationsvariablen verwalten",
|
|
5
|
+
"add": "Konfigurationsvariable hinzufügen",
|
|
6
|
+
"package": "Paket",
|
|
7
|
+
"group": "Gruppe",
|
|
8
|
+
"value": "Wert",
|
|
9
|
+
"is_invisible": "Versteckt",
|
|
10
|
+
"create_title": "Konfigurationsvariable erstellen",
|
|
11
|
+
"edit_title": "Konfigurationsvariable bearbeiten",
|
|
12
|
+
"created_success": "Konfigurationsvariable erfolgreich erstellt.",
|
|
13
|
+
"updated_success": "Konfigurationsvariable erfolgreich aktualisiert."
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"domain": "Domain",
|
|
3
|
+
"domains": "Domains",
|
|
4
|
+
"title": "Domains",
|
|
5
|
+
"subtitle": "Systemdomains verwalten",
|
|
6
|
+
"add": "Domain hinzufügen",
|
|
7
|
+
"create_title": "Domain erstellen",
|
|
8
|
+
"edit_title": "Domain bearbeiten",
|
|
9
|
+
"created_success": "Domain erfolgreich erstellt.",
|
|
10
|
+
"updated_success": "Domain erfolgreich aktualisiert.",
|
|
11
|
+
"host": "Host",
|
|
12
|
+
"protocol": "Protokoll",
|
|
13
|
+
"port": "Port",
|
|
14
|
+
"path": "Pfad",
|
|
15
|
+
"target": "Ziel",
|
|
16
|
+
"parameters": "Parameter",
|
|
17
|
+
"group_connection": "Verbindung",
|
|
18
|
+
"group_routing": "Routing"
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"email_templates": "E-Mail-Vorlagen",
|
|
3
|
+
"title": "E-Mail-Vorlagen",
|
|
4
|
+
"subtitle": "E-Mail-Vorlagen verwalten",
|
|
5
|
+
"add": "E-Mail-Vorlage hinzufügen",
|
|
6
|
+
"create_title": "E-Mail-Vorlage erstellen",
|
|
7
|
+
"edit_title": "E-Mail-Vorlage bearbeiten",
|
|
8
|
+
"slug": "Slug",
|
|
9
|
+
"slug_description": "URL-freundlicher Bezeichner, automatisch aus dem Namen generiert",
|
|
10
|
+
"subject": "Betreff",
|
|
11
|
+
"subject_description": "Betreffzeile der E-Mail",
|
|
12
|
+
"body_text": "Inhalt (Text)",
|
|
13
|
+
"body_text_description": "Nur-Text-Version des E-Mail-Inhalts",
|
|
14
|
+
"body_html": "Inhalt (HTML)",
|
|
15
|
+
"body_html_description": "HTML-Version des E-Mail-Inhalts",
|
|
16
|
+
"has_body_html": "HTML",
|
|
17
|
+
"default_sender_name": "Absendername",
|
|
18
|
+
"default_sender_email": "Absender-E-Mail",
|
|
19
|
+
"default_sender_email_description": "Wird als Absenderadresse verwendet",
|
|
20
|
+
"default_recipient_name": "Empfängername",
|
|
21
|
+
"default_recipient_email": "Empfänger-E-Mail",
|
|
22
|
+
"default_cc_email": "CC-E-Mail",
|
|
23
|
+
"default_cc_email_description": "Kommagetrennte Liste von CC-Adressen",
|
|
24
|
+
"default_bcc_email": "BCC-E-Mail",
|
|
25
|
+
"default_bcc_email_description": "Kommagetrennte Liste von BCC-Adressen",
|
|
26
|
+
"default_replyto_email": "Antwort-an-E-Mail",
|
|
27
|
+
"default_replyto_name": "Antwort-an-Name",
|
|
28
|
+
"group_content": "Inhalt",
|
|
29
|
+
"group_sender": "Absender-Standardwerte",
|
|
30
|
+
"group_recipient": "Empfänger-Standardwerte",
|
|
31
|
+
"usage_title": "Verwendung",
|
|
32
|
+
"created_success": "E-Mail-Vorlage erfolgreich erstellt.",
|
|
33
|
+
"updated_success": "E-Mail-Vorlage erfolgreich aktualisiert.",
|
|
34
|
+
"help": {
|
|
35
|
+
"body_text": "Geben Sie {ALLE_FORMULARFELDER} im Textfeld ein, um alle Variablen eines Formulars auszugeben, die dem Template übermittelt werden.",
|
|
36
|
+
"body_html": "Geben Sie {ALLE_FORMULARFELDER} im Textfeld ein, um alle Variablen eines Formulars auszugeben, die dem Template übermittelt werden."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"languages": "Sprachen",
|
|
3
|
+
"language": "Sprache",
|
|
4
|
+
"title": "Sprachen",
|
|
5
|
+
"subtitle": "Systemsprachen verwalten",
|
|
6
|
+
"add": "Sprache hinzufügen",
|
|
7
|
+
"code": "Code",
|
|
8
|
+
"english_name": "Englischer Name",
|
|
9
|
+
"native_name": "Eigenname",
|
|
10
|
+
"create_title": "Sprache erstellen",
|
|
11
|
+
"edit_title": "Sprache bearbeiten",
|
|
12
|
+
"created_success": "Sprache erfolgreich erstellt.",
|
|
13
|
+
"updated_success": "Sprache erfolgreich aktualisiert.",
|
|
14
|
+
"iso_639_1": "ISO 639-1 Code",
|
|
15
|
+
"iso_639_1_description": "Zweibuchstabiger Sprachcode (z.B. en, de, fr)"
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": "Berechtigungen",
|
|
3
|
+
"permission_group": "Berechtigungsgruppe",
|
|
4
|
+
"permission_groups": "Berechtigungsgruppen",
|
|
5
|
+
"title": "Berechtigungsgruppen",
|
|
6
|
+
"subtitle": "Berechtigungsgruppen verwalten",
|
|
7
|
+
"add": "Berechtigungsgruppe hinzufügen",
|
|
8
|
+
"position": "Position",
|
|
9
|
+
"create_title": "Berechtigungsgruppe erstellen",
|
|
10
|
+
"edit_title": "Berechtigungsgruppe bearbeiten",
|
|
11
|
+
"created_success": "Berechtigungsgruppe erfolgreich erstellt.",
|
|
12
|
+
"updated_success": "Berechtigungsgruppe erfolgreich aktualisiert.",
|
|
13
|
+
"group_permissions": "Berechtigungen"
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"roles": "Rollen",
|
|
3
|
+
"role": "Rolle",
|
|
4
|
+
"title": "Rollen",
|
|
5
|
+
"subtitle": "Benutzerrollen verwalten",
|
|
6
|
+
"add": "Rolle hinzufügen",
|
|
7
|
+
"guard": "Guard",
|
|
8
|
+
"permissions": "Berechtigungen",
|
|
9
|
+
"create_title": "Rolle erstellen",
|
|
10
|
+
"edit_title": "Rolle bearbeiten",
|
|
11
|
+
"created_success": "Rolle erfolgreich erstellt.",
|
|
12
|
+
"updated_success": "Rolle erfolgreich aktualisiert.",
|
|
13
|
+
"guard_name": "Guard-Name",
|
|
14
|
+
"group_access": "Berechtigungen"
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"users": "Benutzer",
|
|
3
|
+
"user": "Benutzer",
|
|
4
|
+
"title": "Benutzer",
|
|
5
|
+
"subtitle": "Systembenutzer verwalten",
|
|
6
|
+
"add": "Benutzer hinzufügen",
|
|
7
|
+
"avatar": "",
|
|
8
|
+
"clients": "Mandanten",
|
|
9
|
+
"roles": "Rollen",
|
|
10
|
+
"permissions": "Berechtigungen",
|
|
11
|
+
"create_title": "Benutzer erstellen",
|
|
12
|
+
"edit_title": "Benutzer bearbeiten",
|
|
13
|
+
"created_success": "Benutzer wurde erfolgreich erstellt",
|
|
14
|
+
"updated_success": "Benutzer wurde erfolgreich aktualisiert",
|
|
15
|
+
"password": "Passwort",
|
|
16
|
+
"password_description": "Leer lassen, um das aktuelle Passwort beizubehalten",
|
|
17
|
+
"change_password": "Passwort ändern",
|
|
18
|
+
"password_confirmation": "Passwort bestätigen",
|
|
19
|
+
"password_mismatch": "Passwörter stimmen nicht überein",
|
|
20
|
+
"group_security": "Sicherheit",
|
|
21
|
+
"group_access": "Zugriff"
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ai_system_prompts": "AI System Prompts",
|
|
3
|
+
"title": "AI System Prompts",
|
|
4
|
+
"subtitle": "Manage AI system prompts",
|
|
5
|
+
"add": "Add AI System Prompt",
|
|
6
|
+
"prompt": "Prompt",
|
|
7
|
+
"prompt_description": "The system prompt text sent to the AI model",
|
|
8
|
+
"create_title": "Create AI System Prompt",
|
|
9
|
+
"edit_title": "Edit AI System Prompt",
|
|
10
|
+
"created_success": "AI system prompt created successfully.",
|
|
11
|
+
"updated_success": "AI system prompt updated successfully."
|
|
12
|
+
}
|