@hywax/cms 0.0.3 → 0.0.5
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/.nuxt/cms/autocomplete-select.ts +5 -0
- package/.nuxt/cms/button-copy.ts +5 -0
- package/.nuxt/cms/button-delete.ts +5 -0
- package/.nuxt/cms/form-panel-aside-section.ts +9 -0
- package/.nuxt/cms/form-panel-section.ts +8 -0
- package/.nuxt/cms/form-panel.ts +15 -0
- package/.nuxt/cms/http-codes.ts +8 -0
- package/.nuxt/cms/index.ts +16 -0
- package/.nuxt/cms/input-seo.ts +5 -0
- package/.nuxt/cms/input-slug.ts +5 -0
- package/.nuxt/cms/modal-confirm.ts +5 -0
- package/.nuxt/cms/table-cell-preview.ts +9 -0
- package/.nuxt/cms/table-cell-seo.ts +5 -0
- package/.nuxt/cms/table-cell-user.ts +5 -0
- package/.nuxt/cms/table-panel-column-sorting.ts +5 -0
- package/.nuxt/cms/table-panel-column-visibility.ts +5 -0
- package/.nuxt/cms/table-panel-filters.ts +5 -0
- package/.nuxt/cms/table-panel.ts +8 -0
- package/cli/templates.mjs +3 -2
- package/dist/module.d.mts +22 -13
- package/dist/module.json +1 -1
- package/dist/module.mjs +261 -35
- package/dist/runtime/components/AutocompleteSelect.vue +170 -0
- package/dist/runtime/components/AutocompleteSelect.vue.d.ts +42 -0
- package/dist/runtime/components/ButtonCopy.vue +40 -0
- package/dist/runtime/components/ButtonCopy.vue.d.ts +23 -0
- package/dist/runtime/components/ButtonDelete.vue +52 -0
- package/dist/runtime/components/ButtonDelete.vue.d.ts +28 -0
- package/dist/runtime/components/FormPanel.vue +70 -0
- package/dist/runtime/components/FormPanel.vue.d.ts +41 -0
- package/dist/runtime/components/FormPanelAsideSection.vue +41 -0
- package/dist/runtime/components/FormPanelAsideSection.vue.d.ts +23 -0
- package/dist/runtime/components/FormPanelSection.vue +31 -0
- package/dist/runtime/components/FormPanelSection.vue.d.ts +20 -0
- package/dist/runtime/components/InputSeo.vue +73 -0
- package/dist/runtime/components/InputSeo.vue.d.ts +19 -0
- package/dist/runtime/components/InputSlug.vue +70 -0
- package/dist/runtime/components/InputSlug.vue.d.ts +29 -0
- package/dist/runtime/components/ModalConfirm.vue +91 -0
- package/dist/runtime/components/ModalConfirm.vue.d.ts +26 -0
- package/dist/runtime/components/TableCellPreview.vue +40 -0
- package/dist/runtime/components/TableCellPreview.vue.d.ts +18 -0
- package/dist/runtime/components/TableCellSeo.vue +34 -0
- package/dist/runtime/components/TableCellSeo.vue.d.ts +13 -0
- package/dist/runtime/components/TableCellUser.vue +33 -0
- package/dist/runtime/components/TableCellUser.vue.d.ts +15 -0
- package/dist/runtime/components/TablePanel.vue +153 -0
- package/dist/runtime/components/TablePanel.vue.d.ts +50 -0
- package/dist/runtime/components/TablePanelColumnSorting.vue +72 -0
- package/dist/runtime/components/TablePanelColumnSorting.vue.d.ts +20 -0
- package/dist/runtime/components/TablePanelColumnVisibility.vue +49 -0
- package/dist/runtime/components/TablePanelColumnVisibility.vue.d.ts +20 -0
- package/dist/runtime/components/TablePanelFilters.vue +79 -0
- package/dist/runtime/components/TablePanelFilters.vue.d.ts +34 -0
- package/dist/runtime/components/prose/UploraImage.vue +8 -3
- package/dist/runtime/composables/useDeleteConfirm.d.ts +15 -0
- package/dist/runtime/composables/useDeleteConfirm.js +29 -0
- package/dist/runtime/composables/useSeoStats.d.ts +2 -2
- package/dist/runtime/composables/useSeoStats.js +1 -1
- package/dist/runtime/composables/useTable.d.ts +19 -0
- package/dist/runtime/composables/useTable.js +90 -0
- package/dist/runtime/editor/markdown/index.d.ts +3 -0
- package/dist/runtime/editor/markdown/index.js +47 -0
- package/dist/runtime/editor/markdown/nodes/callout.d.ts +2 -0
- package/dist/runtime/editor/markdown/nodes/callout.js +21 -0
- package/dist/runtime/editor/markdown/nodes/uploraImage.d.ts +2 -0
- package/dist/runtime/editor/markdown/nodes/uploraImage.js +31 -0
- package/dist/runtime/plugins/api.js +1 -4
- package/dist/runtime/server/errors/InternalHttpError.d.ts +4 -0
- package/dist/runtime/server/errors/InternalHttpError.js +6 -0
- package/dist/runtime/server/errors/TimeoutError.d.ts +2 -0
- package/dist/runtime/server/errors/TimeoutError.js +2 -0
- package/dist/runtime/server/errors/index.d.ts +2 -0
- package/dist/runtime/server/errors/index.js +2 -0
- package/dist/runtime/server/types/errors.d.ts +8 -0
- package/dist/runtime/server/types/errors.js +0 -0
- package/dist/runtime/server/types/index.d.ts +1 -0
- package/dist/runtime/server/types/index.js +1 -0
- package/dist/runtime/server/utils/errors.d.ts +8 -0
- package/dist/runtime/server/utils/errors.js +57 -0
- package/dist/runtime/server/utils/httpHandler.d.ts +10 -0
- package/dist/runtime/server/utils/httpHandler.js +15 -0
- package/dist/runtime/server/utils/pagination.d.ts +11 -0
- package/dist/runtime/server/utils/pagination.js +12 -0
- package/dist/runtime/server/utils/timeout.d.ts +7 -0
- package/dist/runtime/server/utils/timeout.js +9 -0
- package/dist/runtime/server/utils/validation.d.ts +3 -0
- package/dist/runtime/server/utils/validation.js +26 -0
- package/dist/runtime/types/index.d.ts +17 -0
- package/dist/runtime/types/index.js +17 -0
- package/dist/runtime/types/query.d.ts +22 -0
- package/dist/runtime/types/query.js +0 -0
- package/dist/runtime/types/utils.d.ts +4 -1
- package/dist/runtime/utils/avatar.d.ts +1 -0
- package/dist/runtime/utils/avatar.js +9 -0
- package/dist/runtime/utils/dictionaries.d.ts +4 -0
- package/dist/runtime/utils/dictionaries.js +6 -0
- package/dist/runtime/utils/image.js +1 -1
- package/dist/runtime/utils/index.d.ts +3 -0
- package/dist/runtime/utils/index.js +3 -0
- package/dist/runtime/utils/slugify.d.ts +1 -0
- package/dist/runtime/utils/slugify.js +12 -0
- package/dist/types.d.mts +6 -2
- package/package.json +12 -7
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<USelectMenu
|
|
3
|
+
v-model:search-term="searchTerm"
|
|
4
|
+
v-model:open="isOpen"
|
|
5
|
+
:model-value="modelValue"
|
|
6
|
+
:items="items"
|
|
7
|
+
:loading="isLoading"
|
|
8
|
+
ignore-filter
|
|
9
|
+
:value-key="valueKey"
|
|
10
|
+
:label-key="labelKey"
|
|
11
|
+
:placeholder="placeholder"
|
|
12
|
+
:content="{ 'data-autocomplete-select-content': autocompleteSelectId }"
|
|
13
|
+
:multiple="multiple"
|
|
14
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
15
|
+
>
|
|
16
|
+
<template v-if="modelValue && !props.multiple">
|
|
17
|
+
<span class="truncate pointer-events-none">
|
|
18
|
+
{{ getCachedLabel(modelValue) }}
|
|
19
|
+
</span>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<template v-if="!multiple" #trailing>
|
|
23
|
+
<ButtonClear
|
|
24
|
+
:model-value="modelValue"
|
|
25
|
+
:reset-value="resetValue"
|
|
26
|
+
@update:model-value="$emit('update:modelValue', $event)"
|
|
27
|
+
/>
|
|
28
|
+
</template>
|
|
29
|
+
</USelectMenu>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
import theme from "#build/cms/autocomplete-select";
|
|
34
|
+
import { computed, ref, refDebounced, shallowRef, toRaw, triggerRef, useAppConfig, useAsyncData, useId, useNuxtData, watch } from "#imports";
|
|
35
|
+
import { useInfiniteScroll, useOffsetPagination } from "@vueuse/core";
|
|
36
|
+
import { tv } from "../utils/tv";
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
const props = defineProps({
|
|
41
|
+
modelValue: { type: null, required: true },
|
|
42
|
+
cacheKey: { type: String, required: true },
|
|
43
|
+
handler: { type: Function, required: true },
|
|
44
|
+
valueKey: { type: null, required: false },
|
|
45
|
+
labelKey: { type: null, required: false },
|
|
46
|
+
multiple: { type: null, required: false },
|
|
47
|
+
placeholder: { type: String, required: false },
|
|
48
|
+
resetValue: { type: [String, Number, Boolean, null], required: false, skipCheck: true },
|
|
49
|
+
class: { type: null, required: false },
|
|
50
|
+
ui: { type: null, required: false }
|
|
51
|
+
});
|
|
52
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
53
|
+
const appConfig = useAppConfig();
|
|
54
|
+
const autocompleteSelectId = `autocomplete-select-${useId()}`;
|
|
55
|
+
const { initialPagination, initialData, initialStatus } = useInitialData();
|
|
56
|
+
const { fetchPage, nextPage, isLastPage, pageDataStatus, allPagesData } = usePageData({
|
|
57
|
+
pagination: initialPagination,
|
|
58
|
+
total: () => initialData.value?.pagination.total ?? 0
|
|
59
|
+
});
|
|
60
|
+
const { searchTerm, searchData, searchStatus } = useSearch({ pagination: initialPagination });
|
|
61
|
+
const { isOpen } = useInfiniteScrollData({ nextPage, isLastPage, fetchPage, searchTerm });
|
|
62
|
+
const isLoading = computed(() => [
|
|
63
|
+
initialStatus.value,
|
|
64
|
+
pageDataStatus.value,
|
|
65
|
+
searchStatus.value
|
|
66
|
+
].includes("pending"));
|
|
67
|
+
const items = computed(() => {
|
|
68
|
+
if (searchTerm.value) {
|
|
69
|
+
return searchData.value?.items ?? [];
|
|
70
|
+
}
|
|
71
|
+
return [
|
|
72
|
+
...initialData.value?.items ?? [],
|
|
73
|
+
...allPagesData.value
|
|
74
|
+
];
|
|
75
|
+
});
|
|
76
|
+
const { getCachedLabel } = useAutocompleteCache({ items });
|
|
77
|
+
function useInitialData() {
|
|
78
|
+
const initialPagination2 = { page: "1", perPage: "25" };
|
|
79
|
+
const { data: initialData2, status: initialStatus2 } = useAsyncData(
|
|
80
|
+
`autocomplete-select:${props.cacheKey}`,
|
|
81
|
+
() => props.handler({ ...initialPagination2, search: "" }),
|
|
82
|
+
{
|
|
83
|
+
lazy: true,
|
|
84
|
+
getCachedData: (key) => useNuxtData(key).data.value ?? void 0,
|
|
85
|
+
dedupe: "defer"
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
initialPagination: initialPagination2,
|
|
90
|
+
initialData: initialData2,
|
|
91
|
+
initialStatus: initialStatus2
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function usePageData(options) {
|
|
95
|
+
const { currentPage, currentPageSize, next, isLastPage: isLastPage2 } = useOffsetPagination({
|
|
96
|
+
total: options.total,
|
|
97
|
+
pageSize: Number(options.pagination.perPage),
|
|
98
|
+
page: Number(options.pagination.page)
|
|
99
|
+
});
|
|
100
|
+
const { execute: fetchPage2, data: pageData, status: pageDataStatus2 } = useAsyncData(() => {
|
|
101
|
+
return props.handler({
|
|
102
|
+
page: String(currentPage.value),
|
|
103
|
+
perPage: String(currentPageSize.value),
|
|
104
|
+
search: ""
|
|
105
|
+
});
|
|
106
|
+
}, { immediate: false });
|
|
107
|
+
const allPagesData2 = shallowRef([]);
|
|
108
|
+
watch(pageData, (value) => {
|
|
109
|
+
if (value?.items) {
|
|
110
|
+
allPagesData2.value.push(...value.items);
|
|
111
|
+
triggerRef(allPagesData2);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return { fetchPage: fetchPage2, pageData, nextPage: next, isLastPage: isLastPage2, pageDataStatus: pageDataStatus2, allPagesData: allPagesData2 };
|
|
115
|
+
}
|
|
116
|
+
function useInfiniteScrollData(options) {
|
|
117
|
+
const isOpen2 = ref(false);
|
|
118
|
+
const contentDiv = ref(null);
|
|
119
|
+
watch(isOpen2, (value) => {
|
|
120
|
+
if (value) {
|
|
121
|
+
contentDiv.value = document.querySelector(`
|
|
122
|
+
[data-autocomplete-select-content="${autocompleteSelectId}"] [data-reka-combobox-viewport]
|
|
123
|
+
`) ?? null;
|
|
124
|
+
}
|
|
125
|
+
}, { flush: "post" });
|
|
126
|
+
useInfiniteScroll(contentDiv, () => {
|
|
127
|
+
options.nextPage();
|
|
128
|
+
return fetchPage();
|
|
129
|
+
}, { distance: 10, canLoadMore: () => !searchTerm.value && !isLastPage.value });
|
|
130
|
+
return { isOpen: isOpen2 };
|
|
131
|
+
}
|
|
132
|
+
function useSearch(options) {
|
|
133
|
+
const searchTerm2 = ref("");
|
|
134
|
+
const searchTermDebounced = refDebounced(searchTerm2, 500);
|
|
135
|
+
let controller;
|
|
136
|
+
const { data: searchData2, status: searchStatus2, execute: search } = useAsyncData(() => {
|
|
137
|
+
controller?.abort();
|
|
138
|
+
controller = new AbortController();
|
|
139
|
+
return props.handler({
|
|
140
|
+
...options.pagination,
|
|
141
|
+
search: searchTermDebounced.value
|
|
142
|
+
}, controller.signal);
|
|
143
|
+
}, { lazy: true, immediate: false });
|
|
144
|
+
watch(searchTermDebounced, (value) => {
|
|
145
|
+
value && search();
|
|
146
|
+
});
|
|
147
|
+
return { searchData: searchData2, searchStatus: searchStatus2, searchTerm: searchTerm2 };
|
|
148
|
+
}
|
|
149
|
+
function useAutocompleteCache(options) {
|
|
150
|
+
const selectedItemCache = shallowRef(/* @__PURE__ */ new Map());
|
|
151
|
+
watch(() => [props.modelValue, options.items.value], ([value]) => {
|
|
152
|
+
const cacheSize = selectedItemCache.value.size;
|
|
153
|
+
(Array.isArray(value) ? value : [value]).forEach((code) => {
|
|
154
|
+
if (!code || selectedItemCache.value.has(code)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const item = options.items.value.find((item2) => item2[props.valueKey] === code);
|
|
158
|
+
if (item) {
|
|
159
|
+
selectedItemCache.value.set(code, structuredClone(toRaw(item)));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
cacheSize !== selectedItemCache.value.size && triggerRef(selectedItemCache);
|
|
163
|
+
}, { immediate: true });
|
|
164
|
+
const getCachedLabel2 = (value) => {
|
|
165
|
+
return selectedItemCache.value.get(value)?.[props.labelKey] ?? value;
|
|
166
|
+
};
|
|
167
|
+
return { selectedItemCache, getCachedLabel: getCachedLabel2 };
|
|
168
|
+
}
|
|
169
|
+
const _ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.autocompleteSelect || {} })());
|
|
170
|
+
</script>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AppConfig } from '@nuxt/schema';
|
|
2
|
+
import type { ComponentConfig, Pagination, PaginationQueryRaw } from '../types';
|
|
3
|
+
import type { ResetValue } from './ButtonClear.vue';
|
|
4
|
+
import theme from '#build/cms/autocomplete-select';
|
|
5
|
+
type AutocompleteSelect = ComponentConfig<typeof theme, AppConfig, 'autocompleteSelect'>;
|
|
6
|
+
type PaginationQuery = Required<PaginationQueryRaw>;
|
|
7
|
+
type HandlerQuery = PaginationQuery & {
|
|
8
|
+
search: string;
|
|
9
|
+
};
|
|
10
|
+
export interface AutocompleteSelectProps<T extends object, M extends boolean = true> {
|
|
11
|
+
modelValue: M extends true ? (string | number)[] : (string | number | null | undefined);
|
|
12
|
+
cacheKey: string;
|
|
13
|
+
handler: (query: HandlerQuery, signal?: AbortSignal) => Promise<{
|
|
14
|
+
items: T[];
|
|
15
|
+
pagination: Pagination;
|
|
16
|
+
}>;
|
|
17
|
+
valueKey?: keyof T;
|
|
18
|
+
labelKey?: keyof T;
|
|
19
|
+
multiple?: M;
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
resetValue?: ResetValue;
|
|
22
|
+
class?: any;
|
|
23
|
+
ui?: AutocompleteSelect['slots'];
|
|
24
|
+
}
|
|
25
|
+
export interface AutocompleteSelectEmits {
|
|
26
|
+
'update:modelValue': [string[]];
|
|
27
|
+
}
|
|
28
|
+
declare const _default: <T extends object, M extends boolean = true>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
29
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
30
|
+
readonly "onUpdate:modelValue"?: ((args_0: string[]) => any) | undefined;
|
|
31
|
+
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:modelValue"> & AutocompleteSelectProps<T, M> & Partial<{}>> & import("vue").PublicProps;
|
|
32
|
+
expose(exposed: import("vue").ShallowUnwrapRef<{}>): void;
|
|
33
|
+
attrs: any;
|
|
34
|
+
slots: {};
|
|
35
|
+
emit: (evt: "update:modelValue", args_0: string[]) => void;
|
|
36
|
+
}>) => import("vue").VNode & {
|
|
37
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
38
|
+
};
|
|
39
|
+
export default _default;
|
|
40
|
+
type __VLS_PrettifyLocal<T> = {
|
|
41
|
+
[K in keyof T]: T[K];
|
|
42
|
+
} & {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UTooltip
|
|
3
|
+
:text="tooltipText"
|
|
4
|
+
:content="{ side: 'top' }"
|
|
5
|
+
disable-hoverable-content
|
|
6
|
+
disable-closing-trigger
|
|
7
|
+
>
|
|
8
|
+
<UButton
|
|
9
|
+
:icon="copied ? appConfig.ui.icons.copyCheck : appConfig.ui.icons.copy"
|
|
10
|
+
:color="color"
|
|
11
|
+
:variant="variant"
|
|
12
|
+
:size="size"
|
|
13
|
+
:class="ui.root({ class: [props.ui?.root, props.class] })"
|
|
14
|
+
@click="copy(value)"
|
|
15
|
+
/>
|
|
16
|
+
</UTooltip>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import theme from "#build/cms/button-copy";
|
|
21
|
+
import { computed, useAppConfig, useClipboard } from "#imports";
|
|
22
|
+
import { tv } from "../utils/tv";
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
const props = defineProps({
|
|
27
|
+
label: { type: String, required: false, default: "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C" },
|
|
28
|
+
copiedLabel: { type: String, required: false, default: "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E" },
|
|
29
|
+
value: { type: String, required: true },
|
|
30
|
+
color: { type: null, required: false, default: "neutral" },
|
|
31
|
+
variant: { type: null, required: false, default: "link" },
|
|
32
|
+
size: { type: null, required: false, default: "sm" },
|
|
33
|
+
class: { type: null, required: false },
|
|
34
|
+
ui: { type: null, required: false }
|
|
35
|
+
});
|
|
36
|
+
const appConfig = useAppConfig();
|
|
37
|
+
const { copy, copied } = useClipboard();
|
|
38
|
+
const tooltipText = computed(() => copied.value ? props.copiedLabel : props.label);
|
|
39
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.buttonCopy || {} })());
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AppConfig } from '@nuxt/schema';
|
|
2
|
+
import type { ButtonProps } from '@nuxt/ui';
|
|
3
|
+
import type { ComponentConfig } from '../types';
|
|
4
|
+
import theme from '#build/cms/button-copy';
|
|
5
|
+
type ButtonCopy = ComponentConfig<typeof theme, AppConfig, 'buttonCopy'>;
|
|
6
|
+
export interface ButtonCopyProps {
|
|
7
|
+
label?: string;
|
|
8
|
+
copiedLabel?: string;
|
|
9
|
+
value: string;
|
|
10
|
+
color?: ButtonProps['color'];
|
|
11
|
+
variant?: ButtonProps['variant'];
|
|
12
|
+
size?: ButtonProps['size'];
|
|
13
|
+
class?: any;
|
|
14
|
+
ui?: ButtonCopy['slots'];
|
|
15
|
+
}
|
|
16
|
+
declare const _default: import("vue").DefineComponent<ButtonCopyProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ButtonCopyProps> & Readonly<{}>, {
|
|
17
|
+
size: "xs" | "sm" | "md" | "lg" | "xl";
|
|
18
|
+
color: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral";
|
|
19
|
+
variant: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link";
|
|
20
|
+
label: string;
|
|
21
|
+
copiedLabel: string;
|
|
22
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UTooltip
|
|
3
|
+
:text="tooltipText"
|
|
4
|
+
:content="{ side: 'top' }"
|
|
5
|
+
:disabled="!tooltipText"
|
|
6
|
+
disable-hoverable-content
|
|
7
|
+
disable-closing-trigger
|
|
8
|
+
>
|
|
9
|
+
<UButton
|
|
10
|
+
v-bind="buttonProps"
|
|
11
|
+
color="error"
|
|
12
|
+
auto-loading
|
|
13
|
+
:class="ui.root({ class: [props.ui?.root, props.class] })"
|
|
14
|
+
@click="handleDeleteClick()"
|
|
15
|
+
/>
|
|
16
|
+
</UTooltip>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import theme from "#build/cms/button-delete";
|
|
21
|
+
import { computed, useAppConfig, useOverlay } from "#imports";
|
|
22
|
+
import { tv } from "../utils/tv";
|
|
23
|
+
import ModalConfirm from "./ModalConfirm.vue";
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script setup>
|
|
27
|
+
const props = defineProps({
|
|
28
|
+
modalProps: { type: Object, required: false },
|
|
29
|
+
buttonProps: { type: Object, required: false },
|
|
30
|
+
confirmText: { type: String, required: false },
|
|
31
|
+
title: { type: String, required: false },
|
|
32
|
+
message: { type: String, required: false },
|
|
33
|
+
tooltipText: { type: String, required: false },
|
|
34
|
+
class: { type: null, required: false },
|
|
35
|
+
ui: { type: null, required: false },
|
|
36
|
+
onConfirm: { type: Function, required: false }
|
|
37
|
+
});
|
|
38
|
+
defineEmits(["confirm", "close"]);
|
|
39
|
+
const appConfig = useAppConfig();
|
|
40
|
+
const modal = useOverlay().create(ModalConfirm);
|
|
41
|
+
function handleDeleteClick() {
|
|
42
|
+
return modal.open({
|
|
43
|
+
...props.modalProps,
|
|
44
|
+
title: props.title ?? "\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435",
|
|
45
|
+
message: props.message ?? "\u0412\u044B \u0434\u0435\u0438\u0306\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0445\u043E\u0442\u0438\u0442\u0435 \u0443\u0434\u0430\u043B\u0438\u0442\u044C?",
|
|
46
|
+
confirmButton: { color: "error", label: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C" },
|
|
47
|
+
confirmText: props.confirmText,
|
|
48
|
+
onConfirm: props.onConfirm
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.buttonDelete || {} })());
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AppConfig } from '@nuxt/schema';
|
|
2
|
+
import type { ButtonProps, ModalProps } from '@nuxt/ui';
|
|
3
|
+
import type { ComponentProps } from 'vue-component-type-helpers';
|
|
4
|
+
import type { ComponentConfig } from '../types';
|
|
5
|
+
import type { ModalConfirmEmits, ModalConfirmProps } from './ModalConfirm.vue';
|
|
6
|
+
import theme from '#build/cms/button-delete';
|
|
7
|
+
import ModalConfirm from './ModalConfirm.vue';
|
|
8
|
+
type ButtonDelete = ComponentConfig<typeof theme, AppConfig, 'buttonDelete'>;
|
|
9
|
+
export interface ButtonDeleteProps extends Pick<ModalConfirmProps, 'onConfirm'> {
|
|
10
|
+
modalProps?: Omit<ModalProps & ComponentProps<typeof ModalConfirm>, 'title' | 'message' | 'onConfirm' | 'confirmButton'>;
|
|
11
|
+
buttonProps?: Omit<ButtonProps, 'color'>;
|
|
12
|
+
confirmText?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
tooltipText?: string;
|
|
16
|
+
class?: any;
|
|
17
|
+
ui?: ButtonDelete['slots'];
|
|
18
|
+
}
|
|
19
|
+
export interface ButtonDeleteEmits extends ModalConfirmEmits {
|
|
20
|
+
}
|
|
21
|
+
declare const _default: import("vue").DefineComponent<ButtonDeleteProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
22
|
+
close: () => any;
|
|
23
|
+
confirm: () => any;
|
|
24
|
+
}, string, import("vue").PublicProps, Readonly<ButtonDeleteProps> & Readonly<{
|
|
25
|
+
onClose?: (() => any) | undefined;
|
|
26
|
+
onConfirm?: (() => any) | undefined;
|
|
27
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
|
+
export default _default;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UDashboardPanel
|
|
3
|
+
:ui="{
|
|
4
|
+
body: 'flex-row p-0 sm:p-0 gap-0 sm:gap-0'
|
|
5
|
+
}"
|
|
6
|
+
>
|
|
7
|
+
<template #header>
|
|
8
|
+
<UDashboardNavbar :title="title" :ui="{ right: 'gap-2' }">
|
|
9
|
+
<template v-if="toBack" #leading>
|
|
10
|
+
<UButton :to="toBack" variant="link" :icon="appConfig.ui.icons.arrowLeft" />
|
|
11
|
+
</template>
|
|
12
|
+
<template #right>
|
|
13
|
+
<slot name="actions" />
|
|
14
|
+
<UButton
|
|
15
|
+
type="submit"
|
|
16
|
+
label="Сохранить"
|
|
17
|
+
:form="formId"
|
|
18
|
+
:loading="form?.loading"
|
|
19
|
+
loading-auto
|
|
20
|
+
/>
|
|
21
|
+
</template>
|
|
22
|
+
</UDashboardNavbar>
|
|
23
|
+
</template>
|
|
24
|
+
<template #body>
|
|
25
|
+
<UForm
|
|
26
|
+
:id="formId"
|
|
27
|
+
ref="form"
|
|
28
|
+
:state="state"
|
|
29
|
+
:schema="schema"
|
|
30
|
+
:class="ui.form({ class: props.ui?.form })"
|
|
31
|
+
@submit="submitHandler"
|
|
32
|
+
>
|
|
33
|
+
<div :class="ui.body({ class: props.ui?.body })">
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
<div v-if="$slots.sidebar" :class="ui.sidebar({ class: props.ui?.sidebar })">
|
|
37
|
+
<slot name="sidebar" />
|
|
38
|
+
</div>
|
|
39
|
+
</UForm>
|
|
40
|
+
</template>
|
|
41
|
+
</UDashboardPanel>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script>
|
|
45
|
+
import theme from "#build/cms/form-panel";
|
|
46
|
+
import { computed, useAppConfig, useTemplateRef } from "#imports";
|
|
47
|
+
import { tv } from "../utils/tv";
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script setup>
|
|
51
|
+
const props = defineProps({
|
|
52
|
+
title: { type: String, required: false },
|
|
53
|
+
toBack: { type: null, required: false },
|
|
54
|
+
formId: { type: String, required: true },
|
|
55
|
+
schema: { type: Object, required: true },
|
|
56
|
+
state: { type: Object, required: true },
|
|
57
|
+
asideDivide: { type: Boolean, required: false },
|
|
58
|
+
handler: { type: Function, required: false },
|
|
59
|
+
class: { type: null, required: false },
|
|
60
|
+
ui: { type: null, required: false }
|
|
61
|
+
});
|
|
62
|
+
defineEmits(["update:state"]);
|
|
63
|
+
defineSlots();
|
|
64
|
+
const appConfig = useAppConfig();
|
|
65
|
+
const form = useTemplateRef("form");
|
|
66
|
+
async function submitHandler(event) {
|
|
67
|
+
await props.handler?.(event);
|
|
68
|
+
}
|
|
69
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formPanel || {} })());
|
|
70
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AppConfig } from '@nuxt/schema';
|
|
2
|
+
import type { FormSubmitEvent } from '@nuxt/ui';
|
|
3
|
+
import type { RouteLocationRaw } from 'vue-router';
|
|
4
|
+
import type { ZodSchema } from 'zod';
|
|
5
|
+
import type { ComponentConfig } from '../types';
|
|
6
|
+
import theme from '#build/cms/form-panel';
|
|
7
|
+
type FormPanel = ComponentConfig<typeof theme, AppConfig, 'formPanel'>;
|
|
8
|
+
export interface FormPanelProps<T> {
|
|
9
|
+
title?: string;
|
|
10
|
+
toBack?: RouteLocationRaw;
|
|
11
|
+
formId: string;
|
|
12
|
+
schema: ZodSchema<any>;
|
|
13
|
+
state: Partial<T>;
|
|
14
|
+
asideDivide?: boolean;
|
|
15
|
+
handler?: (event: FormSubmitEvent<T>) => Promise<void>;
|
|
16
|
+
class?: any;
|
|
17
|
+
ui?: FormPanel['slots'];
|
|
18
|
+
}
|
|
19
|
+
export interface FormPanelEmits<T> {
|
|
20
|
+
'update:state': [value: T];
|
|
21
|
+
}
|
|
22
|
+
export interface FormPanelSlots {
|
|
23
|
+
default?: () => any;
|
|
24
|
+
sidebar?: () => any;
|
|
25
|
+
actions?: () => any;
|
|
26
|
+
}
|
|
27
|
+
declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
28
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
29
|
+
readonly "onUpdate:state"?: ((value: T) => any) | undefined;
|
|
30
|
+
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:state"> & FormPanelProps<T> & Partial<{}>> & import("vue").PublicProps;
|
|
31
|
+
expose(exposed: import("vue").ShallowUnwrapRef<{}>): void;
|
|
32
|
+
attrs: any;
|
|
33
|
+
slots: FormPanelSlots;
|
|
34
|
+
emit: (evt: "update:state", value: T) => void;
|
|
35
|
+
}>) => import("vue").VNode & {
|
|
36
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
37
|
+
};
|
|
38
|
+
export default _default;
|
|
39
|
+
type __VLS_PrettifyLocal<T> = {
|
|
40
|
+
[K in keyof T]: T[K];
|
|
41
|
+
} & {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UCollapsible :class="ui.root({ class: [props.ui?.root, props.class] })" default-open>
|
|
3
|
+
<div v-if="title || $slots.title || icon || $slots.icon" :class="ui.header({ class: props.ui?.header })">
|
|
4
|
+
<div v-if="icon || $slots.icon" :class="ui.icon({ class: props.ui?.icon })">
|
|
5
|
+
<slot name="icon">
|
|
6
|
+
<UIcon v-if="icon" :name="icon" />
|
|
7
|
+
</slot>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-if="title || $slots.title" :class="ui.title({ class: props.ui?.title })">
|
|
10
|
+
<slot name="title">
|
|
11
|
+
{{ title }}
|
|
12
|
+
</slot>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<template #content>
|
|
17
|
+
<div :class="ui.body({ class: props.ui?.body })">
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
</UCollapsible>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
import theme from "#build/cms/form-panel-aside-section";
|
|
26
|
+
import { computed, useAppConfig } from "#imports";
|
|
27
|
+
import { tv } from "../utils/tv";
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
const props = defineProps({
|
|
32
|
+
title: { type: String, required: false },
|
|
33
|
+
icon: { type: String, required: false },
|
|
34
|
+
divide: { type: Boolean, required: false },
|
|
35
|
+
class: { type: null, required: false },
|
|
36
|
+
ui: { type: null, required: false }
|
|
37
|
+
});
|
|
38
|
+
defineSlots();
|
|
39
|
+
const appConfig = useAppConfig();
|
|
40
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formPanelAsideSection || {} })());
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AppConfig } from '@nuxt/schema';
|
|
2
|
+
import type { ComponentConfig } from '../types';
|
|
3
|
+
import theme from '#build/cms/form-panel-aside-section';
|
|
4
|
+
type FormPanelAsideSection = ComponentConfig<typeof theme, AppConfig, 'formPanelAsideSection'>;
|
|
5
|
+
export interface FormPanelAsideSectionProps {
|
|
6
|
+
title?: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
divide?: boolean;
|
|
9
|
+
class?: any;
|
|
10
|
+
ui?: FormPanelAsideSection['slots'];
|
|
11
|
+
}
|
|
12
|
+
export interface FormPanelAsideSectionSlots {
|
|
13
|
+
default?: () => any;
|
|
14
|
+
icon?: () => any;
|
|
15
|
+
title?: () => any;
|
|
16
|
+
}
|
|
17
|
+
declare const _default: __VLS_WithSlots<import("vue").DefineComponent<FormPanelAsideSectionProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FormPanelAsideSectionProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, FormPanelAsideSectionSlots>;
|
|
18
|
+
export default _default;
|
|
19
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
20
|
+
new (): {
|
|
21
|
+
$slots: S;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="ui.root({ class: [props.ui?.root, props.class] })">
|
|
3
|
+
<div :class="ui.title({ class: props.ui?.title })">
|
|
4
|
+
{{ title }}
|
|
5
|
+
</div>
|
|
6
|
+
<div v-if="description" :class="ui.description({ class: props.ui?.description })">
|
|
7
|
+
{{ description }}
|
|
8
|
+
</div>
|
|
9
|
+
<div :class="ui.body({ class: props.ui?.body })">
|
|
10
|
+
<slot />
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
import theme from "#build/cms/form-panel-section";
|
|
17
|
+
import { computed, useAppConfig } from "#imports";
|
|
18
|
+
import { tv } from "../utils/tv";
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
const props = defineProps({
|
|
23
|
+
title: { type: String, required: true },
|
|
24
|
+
description: { type: String, required: false },
|
|
25
|
+
class: { type: null, required: false },
|
|
26
|
+
ui: { type: null, required: false }
|
|
27
|
+
});
|
|
28
|
+
defineSlots();
|
|
29
|
+
const appConfig = useAppConfig();
|
|
30
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formPanelSection || {} })());
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AppConfig } from '@nuxt/schema';
|
|
2
|
+
import type { ComponentConfig } from '../types';
|
|
3
|
+
import theme from '#build/cms/form-panel-section';
|
|
4
|
+
type FormPanelSection = ComponentConfig<typeof theme, AppConfig, 'formPanelSection'>;
|
|
5
|
+
export interface FormPanelSectionProps {
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
class?: any;
|
|
9
|
+
ui?: FormPanelSection['slots'];
|
|
10
|
+
}
|
|
11
|
+
export interface FormPanelSectionSlots {
|
|
12
|
+
default?: () => any;
|
|
13
|
+
}
|
|
14
|
+
declare const _default: __VLS_WithSlots<import("vue").DefineComponent<FormPanelSectionProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FormPanelSectionProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, FormPanelSectionSlots>;
|
|
15
|
+
export default _default;
|
|
16
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
17
|
+
new (): {
|
|
18
|
+
$slots: S;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
|
3
|
+
<UFormField
|
|
4
|
+
name="seo.title"
|
|
5
|
+
label="Заголовок"
|
|
6
|
+
:hint="`${model.title.length}/60`"
|
|
7
|
+
:ui="{ hint: 'text-xs' }"
|
|
8
|
+
>
|
|
9
|
+
<UInput v-model="model.title" placeholder="Введите заголовок..." class="w-full" />
|
|
10
|
+
|
|
11
|
+
<template #help>
|
|
12
|
+
<p class="text-xs mb-2">
|
|
13
|
+
Рекомендуемая длина: 45-60 символов
|
|
14
|
+
</p>
|
|
15
|
+
<UProgress
|
|
16
|
+
:model-value="title.progress"
|
|
17
|
+
:color="title.color"
|
|
18
|
+
size="sm"
|
|
19
|
+
/>
|
|
20
|
+
</template>
|
|
21
|
+
</UFormField>
|
|
22
|
+
|
|
23
|
+
<UFormField
|
|
24
|
+
name="seo.description"
|
|
25
|
+
label="Описание"
|
|
26
|
+
:hint="`${model.description.length}/160`"
|
|
27
|
+
:ui="{ hint: 'text-xs' }"
|
|
28
|
+
>
|
|
29
|
+
<UTextarea
|
|
30
|
+
v-model="model.description"
|
|
31
|
+
placeholder="Введите описание..."
|
|
32
|
+
class="w-full"
|
|
33
|
+
autoresize
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<template #help>
|
|
37
|
+
<p class="text-xs mb-2">
|
|
38
|
+
Рекомендуемая длина: 130-160 символов
|
|
39
|
+
</p>
|
|
40
|
+
<UProgress
|
|
41
|
+
:model-value="description.progress"
|
|
42
|
+
:color="description.color"
|
|
43
|
+
size="sm"
|
|
44
|
+
/>
|
|
45
|
+
</template>
|
|
46
|
+
</UFormField>
|
|
47
|
+
</Primitive>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
import theme from "#build/cms/input-seo";
|
|
52
|
+
import { computed, useAppConfig } from "#imports";
|
|
53
|
+
import { Primitive } from "reka-ui";
|
|
54
|
+
import { useSeoStats } from "../composables/useSeoStats";
|
|
55
|
+
import { tv } from "../utils/tv";
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<script setup>
|
|
59
|
+
const props = defineProps({
|
|
60
|
+
as: { type: null, required: false },
|
|
61
|
+
class: { type: null, required: false },
|
|
62
|
+
ui: { type: null, required: false }
|
|
63
|
+
});
|
|
64
|
+
const model = defineModel({ type: Object, ...{
|
|
65
|
+
default: () => ({
|
|
66
|
+
title: "",
|
|
67
|
+
description: ""
|
|
68
|
+
})
|
|
69
|
+
} });
|
|
70
|
+
const appConfig = useAppConfig();
|
|
71
|
+
const { title, description } = useSeoStats(model);
|
|
72
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.inputSeo || {} })());
|
|
73
|
+
</script>
|