@soave/nuxt-ui 0.1.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/dist/module.cjs +5 -0
- package/dist/module.d.mts +25 -0
- package/dist/module.d.ts +25 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +52 -0
- package/dist/runtime/components/Breadcrumbs.vue +78 -0
- package/dist/runtime/components/DataTable.vue +334 -0
- package/dist/runtime/components/Pagination.vue +154 -0
- package/dist/runtime/components/Table.vue +18 -0
- package/dist/runtime/components/TableBody.vue +15 -0
- package/dist/runtime/components/TableCell.vue +20 -0
- package/dist/runtime/components/TableHead.vue +44 -0
- package/dist/runtime/components/TableHeader.vue +15 -0
- package/dist/runtime/components/TableRow.vue +25 -0
- package/dist/runtime/components/index.d.ts +0 -0
- package/dist/runtime/components/index.js +9 -0
- package/dist/runtime/composables/index.d.ts +0 -0
- package/dist/runtime/composables/index.js +20 -0
- package/dist/runtime/composables/useBreadcrumbs.d.ts +0 -0
- package/dist/runtime/composables/useBreadcrumbs.js +75 -0
- package/dist/runtime/composables/useI18nUI.d.ts +0 -0
- package/dist/runtime/composables/useI18nUI.js +105 -0
- package/dist/runtime/composables/usePagination.d.ts +0 -0
- package/dist/runtime/composables/usePagination.js +114 -0
- package/dist/runtime/composables/useSoaveSeoMeta.d.ts +0 -0
- package/dist/runtime/composables/useSoaveSeoMeta.js +130 -0
- package/dist/runtime/composables/useTypedRoute.d.ts +0 -0
- package/dist/runtime/composables/useTypedRoute.js +22 -0
- package/dist/runtime/plugins/ui-provider.client.d.ts +0 -0
- package/dist/runtime/plugins/ui-provider.client.js +10 -0
- package/dist/runtime/plugins/ui-provider.server.d.ts +0 -0
- package/dist/runtime/plugins/ui-provider.server.js +10 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +52 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cn('relative w-full overflow-auto', wrapper_class)">
|
|
3
|
+
<table :class="cn('w-full caption-bottom text-sm', props.class)">
|
|
4
|
+
<slot />
|
|
5
|
+
</table>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { cn } from "@soave/ui"
|
|
11
|
+
|
|
12
|
+
export interface TableProps {
|
|
13
|
+
class?: string
|
|
14
|
+
wrapper_class?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = defineProps<TableProps>()
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<tbody :class="cn('[&_tr:last-child]:border-0', props.class)">
|
|
3
|
+
<slot />
|
|
4
|
+
</tbody>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { cn } from "@soave/ui"
|
|
9
|
+
|
|
10
|
+
export interface TableBodyProps {
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps<TableBodyProps>()
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<td
|
|
3
|
+
:class="cn(
|
|
4
|
+
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
|
|
5
|
+
props.class
|
|
6
|
+
)"
|
|
7
|
+
>
|
|
8
|
+
<slot />
|
|
9
|
+
</td>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { cn } from "@soave/ui"
|
|
14
|
+
|
|
15
|
+
export interface TableCellProps {
|
|
16
|
+
class?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = defineProps<TableCellProps>()
|
|
20
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<th
|
|
3
|
+
:class="cn(
|
|
4
|
+
'h-10 px-4 text-left align-middle font-medium text-muted-foreground',
|
|
5
|
+
'[&:has([role=checkbox])]:pr-0',
|
|
6
|
+
sortable && 'cursor-pointer select-none hover:text-foreground',
|
|
7
|
+
props.class
|
|
8
|
+
)"
|
|
9
|
+
:aria-sort="sort_direction ? (sort_direction === 'asc' ? 'ascending' : 'descending') : undefined"
|
|
10
|
+
@click="handleClick"
|
|
11
|
+
>
|
|
12
|
+
<div class="flex items-center gap-2">
|
|
13
|
+
<slot />
|
|
14
|
+
<span v-if="sortable && sort_direction" aria-hidden="true">
|
|
15
|
+
{{ sort_direction === "asc" ? "↑" : "↓" }}
|
|
16
|
+
</span>
|
|
17
|
+
</div>
|
|
18
|
+
</th>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { cn } from "@soave/ui"
|
|
23
|
+
|
|
24
|
+
export interface TableHeadProps {
|
|
25
|
+
sortable?: boolean
|
|
26
|
+
sort_direction?: "asc" | "desc" | null
|
|
27
|
+
class?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const props = withDefaults(defineProps<TableHeadProps>(), {
|
|
31
|
+
sortable: false,
|
|
32
|
+
sort_direction: null
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const emit = defineEmits<{
|
|
36
|
+
sort: []
|
|
37
|
+
}>()
|
|
38
|
+
|
|
39
|
+
const handleClick = (): void => {
|
|
40
|
+
if (props.sortable) {
|
|
41
|
+
emit("sort")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<thead :class="cn('[&_tr]:border-b', props.class)">
|
|
3
|
+
<slot />
|
|
4
|
+
</thead>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { cn } from "@soave/ui"
|
|
9
|
+
|
|
10
|
+
export interface TableHeaderProps {
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps<TableHeaderProps>()
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<tr
|
|
3
|
+
:class="cn(
|
|
4
|
+
'border-b transition-colors hover:bg-muted/50',
|
|
5
|
+
selected && 'bg-muted',
|
|
6
|
+
props.class
|
|
7
|
+
)"
|
|
8
|
+
:data-state="selected ? 'selected' : undefined"
|
|
9
|
+
>
|
|
10
|
+
<slot />
|
|
11
|
+
</tr>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { cn } from "@soave/ui"
|
|
16
|
+
|
|
17
|
+
export interface TableRowProps {
|
|
18
|
+
selected?: boolean
|
|
19
|
+
class?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<TableRowProps>(), {
|
|
23
|
+
selected: false
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as Breadcrumbs } from "./Breadcrumbs.vue";
|
|
2
|
+
export { default as Pagination } from "./Pagination.vue";
|
|
3
|
+
export { default as Table } from "./Table.vue";
|
|
4
|
+
export { default as TableHeader } from "./TableHeader.vue";
|
|
5
|
+
export { default as TableBody } from "./TableBody.vue";
|
|
6
|
+
export { default as TableRow } from "./TableRow.vue";
|
|
7
|
+
export { default as TableHead } from "./TableHead.vue";
|
|
8
|
+
export { default as TableCell } from "./TableCell.vue";
|
|
9
|
+
export { default as DataTable } from "./DataTable.vue";
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
defineRoute,
|
|
3
|
+
useTypedParams,
|
|
4
|
+
useTypedQuery,
|
|
5
|
+
useParam,
|
|
6
|
+
useQueryParam
|
|
7
|
+
} from "./useTypedRoute.js";
|
|
8
|
+
export {
|
|
9
|
+
useBreadcrumbs
|
|
10
|
+
} from "./useBreadcrumbs.js";
|
|
11
|
+
export {
|
|
12
|
+
useSoaveSeoMeta,
|
|
13
|
+
useStructuredData
|
|
14
|
+
} from "./useSoaveSeoMeta.js";
|
|
15
|
+
export { usePagination } from "./usePagination.js";
|
|
16
|
+
export {
|
|
17
|
+
useI18nUI,
|
|
18
|
+
createUITranslations,
|
|
19
|
+
getDefaultTranslations
|
|
20
|
+
} from "./useI18nUI.js";
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useRoute } from "#app";
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
const default_label_transformer = (segment) => {
|
|
4
|
+
return segment.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
5
|
+
};
|
|
6
|
+
export function useBreadcrumbs(options = {}) {
|
|
7
|
+
const route = useRoute();
|
|
8
|
+
const {
|
|
9
|
+
home_label = "Home",
|
|
10
|
+
home_href = "/",
|
|
11
|
+
home_icon,
|
|
12
|
+
include_home = true,
|
|
13
|
+
label_transformer = default_label_transformer,
|
|
14
|
+
custom_labels = {}
|
|
15
|
+
} = options;
|
|
16
|
+
const manual_items = ref(null);
|
|
17
|
+
const auto_breadcrumbs = computed(() => {
|
|
18
|
+
const path_segments = route.path.split("/").filter(Boolean);
|
|
19
|
+
const items = [];
|
|
20
|
+
if (include_home) {
|
|
21
|
+
items.push({
|
|
22
|
+
label: home_label,
|
|
23
|
+
href: home_href,
|
|
24
|
+
icon: home_icon,
|
|
25
|
+
current: path_segments.length === 0
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
let accumulated_path = "";
|
|
29
|
+
path_segments.forEach((segment, index) => {
|
|
30
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
31
|
+
const param_name = segment.slice(1, -1);
|
|
32
|
+
const param_value = route.params[param_name];
|
|
33
|
+
if (param_value) {
|
|
34
|
+
segment = String(param_value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
accumulated_path += `/${segment}`;
|
|
38
|
+
const is_last = index === path_segments.length - 1;
|
|
39
|
+
const label = custom_labels[segment] || custom_labels[accumulated_path] || label_transformer(segment);
|
|
40
|
+
items.push({
|
|
41
|
+
label,
|
|
42
|
+
href: is_last ? void 0 : accumulated_path,
|
|
43
|
+
current: is_last,
|
|
44
|
+
disabled: is_last
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return items;
|
|
48
|
+
});
|
|
49
|
+
const breadcrumbs = computed(() => {
|
|
50
|
+
return manual_items.value ?? auto_breadcrumbs.value;
|
|
51
|
+
});
|
|
52
|
+
const current = computed(() => {
|
|
53
|
+
return breadcrumbs.value.find((item) => item.current);
|
|
54
|
+
});
|
|
55
|
+
const set = (items) => {
|
|
56
|
+
manual_items.value = items;
|
|
57
|
+
};
|
|
58
|
+
const append = (item) => {
|
|
59
|
+
if (manual_items.value) {
|
|
60
|
+
manual_items.value = [...manual_items.value, item];
|
|
61
|
+
} else {
|
|
62
|
+
manual_items.value = [...auto_breadcrumbs.value, item];
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const reset = () => {
|
|
66
|
+
manual_items.value = null;
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
breadcrumbs,
|
|
70
|
+
current,
|
|
71
|
+
set,
|
|
72
|
+
append,
|
|
73
|
+
reset
|
|
74
|
+
};
|
|
75
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
|
+
const DEFAULT_TRANSLATIONS = {
|
|
3
|
+
pagination: {
|
|
4
|
+
previous: "Previous",
|
|
5
|
+
next: "Next",
|
|
6
|
+
first: "First",
|
|
7
|
+
last: "Last",
|
|
8
|
+
page: "Page",
|
|
9
|
+
showing: "Showing",
|
|
10
|
+
of: "of",
|
|
11
|
+
results: "results"
|
|
12
|
+
},
|
|
13
|
+
data_table: {
|
|
14
|
+
no_results: "No results found.",
|
|
15
|
+
select_all: "Select all",
|
|
16
|
+
select_row: "Select row",
|
|
17
|
+
actions: "Actions",
|
|
18
|
+
loading: "Loading...",
|
|
19
|
+
sort_ascending: "Sort ascending",
|
|
20
|
+
sort_descending: "Sort descending"
|
|
21
|
+
},
|
|
22
|
+
breadcrumbs: {
|
|
23
|
+
home: "Home",
|
|
24
|
+
navigation: "Breadcrumb"
|
|
25
|
+
},
|
|
26
|
+
common: {
|
|
27
|
+
loading: "Loading...",
|
|
28
|
+
error: "An error occurred",
|
|
29
|
+
empty: "No data available",
|
|
30
|
+
cancel: "Cancel",
|
|
31
|
+
confirm: "Confirm",
|
|
32
|
+
save: "Save",
|
|
33
|
+
delete: "Delete",
|
|
34
|
+
edit: "Edit",
|
|
35
|
+
create: "Create",
|
|
36
|
+
search: "Search",
|
|
37
|
+
filter: "Filter",
|
|
38
|
+
clear: "Clear",
|
|
39
|
+
close: "Close",
|
|
40
|
+
open: "Open",
|
|
41
|
+
submit: "Submit",
|
|
42
|
+
reset: "Reset"
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
let nuxt_i18n_available = false;
|
|
46
|
+
let use_i18n_function = null;
|
|
47
|
+
try {
|
|
48
|
+
const i18n_module = import.meta.glob("/node_modules/@nuxtjs/i18n/**/*.js", { eager: false });
|
|
49
|
+
nuxt_i18n_available = Object.keys(i18n_module).length > 0;
|
|
50
|
+
} catch {
|
|
51
|
+
nuxt_i18n_available = false;
|
|
52
|
+
}
|
|
53
|
+
export function useI18nUI(options = {}) {
|
|
54
|
+
const { translations: custom_translations = {}, use_nuxt_i18n = nuxt_i18n_available } = options;
|
|
55
|
+
const merged_translations = mergeDeep(DEFAULT_TRANSLATIONS, custom_translations);
|
|
56
|
+
const t = (namespace, key) => {
|
|
57
|
+
return computed(() => {
|
|
58
|
+
if (use_nuxt_i18n && use_i18n_function) {
|
|
59
|
+
const i18n = use_i18n_function();
|
|
60
|
+
const i18n_key = `soave_ui.${namespace}.${String(key)}`;
|
|
61
|
+
const translated = i18n.t(i18n_key);
|
|
62
|
+
if (translated !== i18n_key) {
|
|
63
|
+
return translated;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const namespace_translations = merged_translations[namespace];
|
|
67
|
+
if (namespace_translations && key in namespace_translations) {
|
|
68
|
+
return namespace_translations[key];
|
|
69
|
+
}
|
|
70
|
+
return String(key);
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
t,
|
|
75
|
+
translations: merged_translations
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function createUITranslations(translations) {
|
|
79
|
+
const resolved = toValue(translations);
|
|
80
|
+
return mergeDeep(DEFAULT_TRANSLATIONS, resolved);
|
|
81
|
+
}
|
|
82
|
+
export function getDefaultTranslations() {
|
|
83
|
+
return { ...DEFAULT_TRANSLATIONS };
|
|
84
|
+
}
|
|
85
|
+
function mergeDeep(target, source) {
|
|
86
|
+
const output = { ...target };
|
|
87
|
+
for (const key in source) {
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
89
|
+
const source_value = source[key];
|
|
90
|
+
const target_value = output[key];
|
|
91
|
+
if (isObject(source_value) && isObject(target_value)) {
|
|
92
|
+
output[key] = mergeDeep(
|
|
93
|
+
target_value,
|
|
94
|
+
source_value
|
|
95
|
+
);
|
|
96
|
+
} else if (source_value !== void 0) {
|
|
97
|
+
output[key] = source_value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return output;
|
|
102
|
+
}
|
|
103
|
+
function isObject(item) {
|
|
104
|
+
return item !== null && typeof item === "object" && !Array.isArray(item);
|
|
105
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
export function usePagination(options = {}) {
|
|
3
|
+
const {
|
|
4
|
+
initial_page = 1,
|
|
5
|
+
per_page: initial_per_page = 10,
|
|
6
|
+
total_items: initial_total_items = 0,
|
|
7
|
+
sibling_count = 1,
|
|
8
|
+
boundary_count = 1
|
|
9
|
+
} = options;
|
|
10
|
+
const current_page = ref(initial_page);
|
|
11
|
+
const per_page = ref(initial_per_page);
|
|
12
|
+
const total_items = ref(initial_total_items);
|
|
13
|
+
const total_pages = computed(() => {
|
|
14
|
+
return Math.max(1, Math.ceil(total_items.value / per_page.value));
|
|
15
|
+
});
|
|
16
|
+
const has_previous = computed(() => current_page.value > 1);
|
|
17
|
+
const has_next = computed(() => current_page.value < total_pages.value);
|
|
18
|
+
const state = computed(() => ({
|
|
19
|
+
current_page: current_page.value,
|
|
20
|
+
total_pages: total_pages.value,
|
|
21
|
+
total_items: total_items.value,
|
|
22
|
+
per_page: per_page.value,
|
|
23
|
+
has_previous: has_previous.value,
|
|
24
|
+
has_next: has_next.value
|
|
25
|
+
}));
|
|
26
|
+
const pages = computed(() => {
|
|
27
|
+
const total = total_pages.value;
|
|
28
|
+
const current = current_page.value;
|
|
29
|
+
const result = [];
|
|
30
|
+
const start_pages = range(1, Math.min(boundary_count, total));
|
|
31
|
+
const end_pages = range(Math.max(total - boundary_count + 1, boundary_count + 1), total);
|
|
32
|
+
const sibling_start = Math.max(
|
|
33
|
+
Math.min(
|
|
34
|
+
current - sibling_count,
|
|
35
|
+
total - boundary_count - sibling_count * 2 - 1
|
|
36
|
+
),
|
|
37
|
+
boundary_count + 2
|
|
38
|
+
);
|
|
39
|
+
const sibling_end = Math.min(
|
|
40
|
+
Math.max(
|
|
41
|
+
current + sibling_count,
|
|
42
|
+
boundary_count + sibling_count * 2 + 2
|
|
43
|
+
),
|
|
44
|
+
end_pages.length > 0 ? end_pages[0] - 2 : total - 1
|
|
45
|
+
);
|
|
46
|
+
result.push(...start_pages);
|
|
47
|
+
if (sibling_start > boundary_count + 2) {
|
|
48
|
+
result.push("ellipsis");
|
|
49
|
+
} else if (boundary_count + 1 < total - boundary_count) {
|
|
50
|
+
result.push(boundary_count + 1);
|
|
51
|
+
}
|
|
52
|
+
result.push(...range(sibling_start, sibling_end));
|
|
53
|
+
if (sibling_end < total - boundary_count - 1) {
|
|
54
|
+
result.push("ellipsis");
|
|
55
|
+
} else if (total - boundary_count > boundary_count) {
|
|
56
|
+
result.push(total - boundary_count);
|
|
57
|
+
}
|
|
58
|
+
result.push(...end_pages);
|
|
59
|
+
const unique = [...new Set(result.filter((p) => p === "ellipsis" || p >= 1 && p <= total))];
|
|
60
|
+
return unique;
|
|
61
|
+
});
|
|
62
|
+
const setPage = (page) => {
|
|
63
|
+
if (page >= 1 && page <= total_pages.value) {
|
|
64
|
+
current_page.value = page;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const nextPage = () => {
|
|
68
|
+
if (has_next.value) {
|
|
69
|
+
current_page.value++;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const previousPage = () => {
|
|
73
|
+
if (has_previous.value) {
|
|
74
|
+
current_page.value--;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const firstPage = () => {
|
|
78
|
+
current_page.value = 1;
|
|
79
|
+
};
|
|
80
|
+
const lastPage = () => {
|
|
81
|
+
current_page.value = total_pages.value;
|
|
82
|
+
};
|
|
83
|
+
const setPerPage = (count) => {
|
|
84
|
+
per_page.value = count;
|
|
85
|
+
current_page.value = 1;
|
|
86
|
+
};
|
|
87
|
+
const setTotalItems = (count) => {
|
|
88
|
+
total_items.value = count;
|
|
89
|
+
if (current_page.value > total_pages.value) {
|
|
90
|
+
current_page.value = total_pages.value;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
state,
|
|
95
|
+
current_page,
|
|
96
|
+
per_page,
|
|
97
|
+
total_items,
|
|
98
|
+
pages,
|
|
99
|
+
setPage,
|
|
100
|
+
nextPage,
|
|
101
|
+
previousPage,
|
|
102
|
+
firstPage,
|
|
103
|
+
lastPage,
|
|
104
|
+
setPerPage,
|
|
105
|
+
setTotalItems
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function range(start, end) {
|
|
109
|
+
const result = [];
|
|
110
|
+
for (let i = start; i <= end; i++) {
|
|
111
|
+
result.push(i);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useSeoMeta as useNuxtSeoMeta, useHead } from "#app";
|
|
2
|
+
import { computed, toValue } from "vue";
|
|
3
|
+
export function useSoaveSeoMeta(options) {
|
|
4
|
+
const title = computed(() => toValue(options.title));
|
|
5
|
+
const description = computed(() => toValue(options.description));
|
|
6
|
+
const image = computed(() => toValue(options.image));
|
|
7
|
+
const url = computed(() => toValue(options.url));
|
|
8
|
+
const type = computed(() => toValue(options.type) || "website");
|
|
9
|
+
const site_name = computed(() => toValue(options.site_name));
|
|
10
|
+
const locale = computed(() => toValue(options.locale));
|
|
11
|
+
const twitter_card = computed(() => toValue(options.twitter_card) || "summary_large_image");
|
|
12
|
+
const author = computed(() => toValue(options.author));
|
|
13
|
+
const keywords = computed(() => toValue(options.keywords));
|
|
14
|
+
const robots = computed(() => toValue(options.robots));
|
|
15
|
+
useNuxtSeoMeta({
|
|
16
|
+
title,
|
|
17
|
+
description,
|
|
18
|
+
ogTitle: title,
|
|
19
|
+
ogDescription: description,
|
|
20
|
+
ogImage: image,
|
|
21
|
+
ogUrl: url,
|
|
22
|
+
ogType: type,
|
|
23
|
+
ogSiteName: site_name,
|
|
24
|
+
ogLocale: locale,
|
|
25
|
+
twitterCard: twitter_card,
|
|
26
|
+
twitterTitle: title,
|
|
27
|
+
twitterDescription: description,
|
|
28
|
+
twitterImage: image,
|
|
29
|
+
author,
|
|
30
|
+
robots
|
|
31
|
+
});
|
|
32
|
+
if (options.keywords) {
|
|
33
|
+
useHead({
|
|
34
|
+
meta: [
|
|
35
|
+
{
|
|
36
|
+
name: "keywords",
|
|
37
|
+
content: keywords
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (options.published_time || options.modified_time) {
|
|
43
|
+
useHead({
|
|
44
|
+
meta: [
|
|
45
|
+
...options.published_time ? [{
|
|
46
|
+
property: "article:published_time",
|
|
47
|
+
content: computed(() => toValue(options.published_time))
|
|
48
|
+
}] : [],
|
|
49
|
+
...options.modified_time ? [{
|
|
50
|
+
property: "article:modified_time",
|
|
51
|
+
content: computed(() => toValue(options.modified_time))
|
|
52
|
+
}] : []
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function useStructuredData(options) {
|
|
58
|
+
const scripts = [];
|
|
59
|
+
if (options.breadcrumbs && options.breadcrumbs.length > 0) {
|
|
60
|
+
const breadcrumb_list = {
|
|
61
|
+
"@context": "https://schema.org",
|
|
62
|
+
"@type": "BreadcrumbList",
|
|
63
|
+
"itemListElement": options.breadcrumbs.map((item, index) => ({
|
|
64
|
+
"@type": "ListItem",
|
|
65
|
+
"position": index + 1,
|
|
66
|
+
"name": item.name,
|
|
67
|
+
"item": item.url
|
|
68
|
+
}))
|
|
69
|
+
};
|
|
70
|
+
scripts.push({
|
|
71
|
+
type: "application/ld+json",
|
|
72
|
+
innerHTML: JSON.stringify(breadcrumb_list)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (options.article) {
|
|
76
|
+
const article = {
|
|
77
|
+
"@context": "https://schema.org",
|
|
78
|
+
"@type": "Article",
|
|
79
|
+
"headline": options.article.headline,
|
|
80
|
+
"author": {
|
|
81
|
+
"@type": "Person",
|
|
82
|
+
"name": options.article.author
|
|
83
|
+
},
|
|
84
|
+
"datePublished": options.article.published_date,
|
|
85
|
+
...options.article.modified_date && { "dateModified": options.article.modified_date },
|
|
86
|
+
...options.article.image && { "image": options.article.image }
|
|
87
|
+
};
|
|
88
|
+
scripts.push({
|
|
89
|
+
type: "application/ld+json",
|
|
90
|
+
innerHTML: JSON.stringify(article)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (options.organization) {
|
|
94
|
+
const organization = {
|
|
95
|
+
"@context": "https://schema.org",
|
|
96
|
+
"@type": "Organization",
|
|
97
|
+
"name": options.organization.name,
|
|
98
|
+
"url": options.organization.url,
|
|
99
|
+
...options.organization.logo && { "logo": options.organization.logo }
|
|
100
|
+
};
|
|
101
|
+
scripts.push({
|
|
102
|
+
type: "application/ld+json",
|
|
103
|
+
innerHTML: JSON.stringify(organization)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (options.product) {
|
|
107
|
+
const product = {
|
|
108
|
+
"@context": "https://schema.org",
|
|
109
|
+
"@type": "Product",
|
|
110
|
+
"name": options.product.name,
|
|
111
|
+
"description": options.product.description,
|
|
112
|
+
...options.product.image && { "image": options.product.image },
|
|
113
|
+
"offers": {
|
|
114
|
+
"@type": "Offer",
|
|
115
|
+
"price": options.product.price,
|
|
116
|
+
"priceCurrency": options.product.currency,
|
|
117
|
+
...options.product.availability && { "availability": `https://schema.org/${options.product.availability}` }
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
scripts.push({
|
|
121
|
+
type: "application/ld+json",
|
|
122
|
+
innerHTML: JSON.stringify(product)
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (scripts.length > 0) {
|
|
126
|
+
useHead({
|
|
127
|
+
script: scripts
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useRoute } from "#app";
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
export function defineRoute() {
|
|
4
|
+
const route = useRoute();
|
|
5
|
+
return route;
|
|
6
|
+
}
|
|
7
|
+
export function useTypedParams() {
|
|
8
|
+
const route = useRoute();
|
|
9
|
+
return computed(() => route.params);
|
|
10
|
+
}
|
|
11
|
+
export function useTypedQuery() {
|
|
12
|
+
const route = useRoute();
|
|
13
|
+
return computed(() => route.query);
|
|
14
|
+
}
|
|
15
|
+
export function useParam(key) {
|
|
16
|
+
const route = useRoute();
|
|
17
|
+
return computed(() => route.params[key]);
|
|
18
|
+
}
|
|
19
|
+
export function useQueryParam(key) {
|
|
20
|
+
const route = useRoute();
|
|
21
|
+
return computed(() => route.query[key]);
|
|
22
|
+
}
|
|
File without changes
|
|
File without changes
|
package/dist/types.d.mts
ADDED
package/dist/types.d.ts
ADDED