@fy-/fws-vue 0.3.4 → 0.3.8
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/components/fws/UserData.vue +97 -0
- package/components/fws/UserFlow.vue +1 -11
- package/components/fws/UserOAuth2.vue +174 -0
- package/components/ui/DefaultDropdownLink.vue +1 -0
- package/components/ui/DefaultGallery.vue +53 -10
- package/components/ui/DefaultInput.vue +1 -0
- package/components/ui/DefaultModal.vue +3 -4
- package/components/ui/DefaultPaging.vue +91 -80
- package/components/ui/DefaultTagInput.vue +22 -15
- package/composables/rest.ts +24 -8
- package/composables/seo.ts +3 -2
- package/composables/templating.ts +16 -1
- package/env.d.ts +0 -2
- package/index.ts +10 -2
- package/package.json +1 -1
- package/stores/serverRouter.ts +14 -0
- package/stores/user.ts +28 -1
- package/stores/rest.ts +0 -27
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import useVuelidate from "@vuelidate/core";
|
|
3
|
+
import DefaultInput from "../ui/DefaultInput.vue";
|
|
4
|
+
import { useUserStore } from "../../stores/user";
|
|
5
|
+
import { useRest } from "../../composables/rest";
|
|
6
|
+
import { computed, reactive, watchEffect } from "vue";
|
|
7
|
+
import { required } from "@vuelidate/validators";
|
|
8
|
+
const rest = useRest();
|
|
9
|
+
const userStore = useUserStore();
|
|
10
|
+
const userData = computed(() => userStore.user);
|
|
11
|
+
const state = reactive({
|
|
12
|
+
userData: {
|
|
13
|
+
Firstname: userData.value?.Firstname || "",
|
|
14
|
+
Lastname: userData.value?.Lastname || "",
|
|
15
|
+
Phone: userData.value?.Phone || "",
|
|
16
|
+
Bio: userData.value?.Bio || "",
|
|
17
|
+
Username: userData.value?.Username || "",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
watchEffect(() => {
|
|
21
|
+
state.userData = {
|
|
22
|
+
Firstname: userData.value?.Firstname || "",
|
|
23
|
+
Lastname: userData.value?.Lastname || "",
|
|
24
|
+
Phone: userData.value?.Phone || "",
|
|
25
|
+
Bio: userData.value?.Bio || "",
|
|
26
|
+
Username: userData.value?.Username || "",
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
const rules = {
|
|
30
|
+
userData: {
|
|
31
|
+
Username: {
|
|
32
|
+
required: required,
|
|
33
|
+
},
|
|
34
|
+
Firstname: {},
|
|
35
|
+
Lastname: {},
|
|
36
|
+
Phone: {},
|
|
37
|
+
Bio: {},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const v$ = useVuelidate(rules, state);
|
|
41
|
+
|
|
42
|
+
const patchUser = async () => {
|
|
43
|
+
if (await v$.value.userData.$validate()) {
|
|
44
|
+
const response = await rest("User", "PATCH", state.userData);
|
|
45
|
+
if (response && response.result == "success") {
|
|
46
|
+
window.location.reload();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<form @submit.prevent="patchUser">
|
|
54
|
+
<DefaultInput
|
|
55
|
+
id="usernameFWS"
|
|
56
|
+
v-model="state.userData.Username"
|
|
57
|
+
class="mb-4"
|
|
58
|
+
type="text"
|
|
59
|
+
:label="$t('fws_username_label')"
|
|
60
|
+
:help="$t('fws_username_help')"
|
|
61
|
+
:error-vuelidate="v$.userData.Username.$errors"
|
|
62
|
+
:req="true"
|
|
63
|
+
/>
|
|
64
|
+
<DefaultInput
|
|
65
|
+
id="firstnameFWS"
|
|
66
|
+
v-model="state.userData.Firstname"
|
|
67
|
+
class="mb-4"
|
|
68
|
+
type="text"
|
|
69
|
+
:label="$t('fws_firstname_label')"
|
|
70
|
+
:help="$t('fws_firstname_help')"
|
|
71
|
+
:error-vuelidate="v$.userData.Firstname.$errors"
|
|
72
|
+
/>
|
|
73
|
+
<DefaultInput
|
|
74
|
+
id="lastnameFWS"
|
|
75
|
+
v-model="state.userData.Lastname"
|
|
76
|
+
class="mb-4"
|
|
77
|
+
type="text"
|
|
78
|
+
:label="$t('fws_lastname_label')"
|
|
79
|
+
:help="$t('fws_lastname_help')"
|
|
80
|
+
:error-vuelidate="v$.userData.Lastname.$errors"
|
|
81
|
+
/>
|
|
82
|
+
<DefaultInput
|
|
83
|
+
id="phoneFWS"
|
|
84
|
+
v-model="state.userData.Phone"
|
|
85
|
+
class="mb-4"
|
|
86
|
+
type="text"
|
|
87
|
+
:label="$t('fws_phone_label')"
|
|
88
|
+
:help="$t('fws_phone_help')"
|
|
89
|
+
:error-vuelidate="v$.userData.Phone.$errors"
|
|
90
|
+
/>
|
|
91
|
+
<div class="flex">
|
|
92
|
+
<button type="submit" class="btn defaults primary">
|
|
93
|
+
{{ $t("fws_save_user_cta") }}
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
</form>
|
|
97
|
+
</template>
|
|
@@ -176,16 +176,6 @@ const userFlow = async (params: paramsType = { initial: false }) => {
|
|
|
176
176
|
eventBus.emit("login-loading", false);
|
|
177
177
|
};
|
|
178
178
|
|
|
179
|
-
const getContrastingTextColor = (backgroundColor: string) => {
|
|
180
|
-
const r = parseInt(backgroundColor.substring(1, 3), 16);
|
|
181
|
-
const g = parseInt(backgroundColor.substring(3, 5), 16);
|
|
182
|
-
const b = parseInt(backgroundColor.substring(5, 7), 16);
|
|
183
|
-
|
|
184
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
185
|
-
|
|
186
|
-
return luminance > 0.5 ? "#000000" : "#FFFFFF";
|
|
187
|
-
};
|
|
188
|
-
|
|
189
179
|
onMounted(async () => {
|
|
190
180
|
await userFlow({ initial: true });
|
|
191
181
|
});
|
|
@@ -223,7 +213,7 @@ onMounted(async () => {
|
|
|
223
213
|
class="flex border border-fv-neutral-300 dark:border-fv-neutral-700 shadow items-center gap-2 justify-start btn neutral defaults w-full mx-auto !font-semibold"
|
|
224
214
|
:style="`background: ${
|
|
225
215
|
field.button['background-color']
|
|
226
|
-
}; color: ${getContrastingTextColor(
|
|
216
|
+
}; color: ${$getContrastingTextColor(
|
|
227
217
|
field.button['background-color'],
|
|
228
218
|
)}`"
|
|
229
219
|
>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useTranslation } from "../../composables/translations";
|
|
3
|
+
import { useEventBus } from "../../composables/event-bus";
|
|
4
|
+
import { useUserStore } from "../../stores/user";
|
|
5
|
+
import { useRest, APIResult } from "../../composables/rest";
|
|
6
|
+
import DefaultModal from "../ui/DefaultModal.vue";
|
|
7
|
+
|
|
8
|
+
import { ref, computed, onMounted } from "vue";
|
|
9
|
+
const rest = useRest();
|
|
10
|
+
const eventBus = useEventBus();
|
|
11
|
+
const userStore = useUserStore();
|
|
12
|
+
const isAuth = computed(() => userStore.isAuth);
|
|
13
|
+
const data = ref();
|
|
14
|
+
const providersData = ref();
|
|
15
|
+
const usedProviders = ref<Record<string, boolean>>({});
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
returnTo: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: false,
|
|
20
|
+
default: "/user/account?tab=user_settings",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const returnTo = ref(props.returnTo);
|
|
24
|
+
if (returnTo.value == "") {
|
|
25
|
+
returnTo.value = "/user/account?tab=user_settings";
|
|
26
|
+
}
|
|
27
|
+
const getOAuth2Providers = async () => {
|
|
28
|
+
eventBus.emit("main-loading", true);
|
|
29
|
+
const d = await rest("User/OAuth2/Providers", "GET");
|
|
30
|
+
if (d && d.result == "success") {
|
|
31
|
+
providersData.value = d.data;
|
|
32
|
+
}
|
|
33
|
+
eventBus.emit("main-loading", false);
|
|
34
|
+
};
|
|
35
|
+
const getOAuth2Redirect = async (providerUUID: string) => {
|
|
36
|
+
eventBus.emit("main-loading", true);
|
|
37
|
+
const d = await rest(`User/OAuth2/Provider/${providerUUID}`, "POST", {
|
|
38
|
+
ReturnTo: returnTo.value,
|
|
39
|
+
});
|
|
40
|
+
if (d && d.result == "success") {
|
|
41
|
+
window.location.href = d.data;
|
|
42
|
+
}
|
|
43
|
+
eventBus.emit("main-loading", false);
|
|
44
|
+
};
|
|
45
|
+
const translate = useTranslation();
|
|
46
|
+
const deleteOAuth2Connection = async (providerUUID: string) => {
|
|
47
|
+
eventBus.emit("showConfirm", {
|
|
48
|
+
title: translate("remove_provider_confirm_title"),
|
|
49
|
+
desc: translate("remove_provider_confirm_desc_warning"),
|
|
50
|
+
onConfirm: async () => {
|
|
51
|
+
eventBus.emit("main-loading", true);
|
|
52
|
+
const d = await rest(`User/OAuth2/Provider/${providerUUID}`, "DELETE");
|
|
53
|
+
if (d && d.result == "success") {
|
|
54
|
+
getOAuth2User();
|
|
55
|
+
}
|
|
56
|
+
eventBus.emit("main-loading", false);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const getOAuth2User = async () => {
|
|
61
|
+
eventBus.emit("main-loading", true);
|
|
62
|
+
if (!isAuth.value) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const d = await rest("User/OAuth2", "GET");
|
|
66
|
+
usedProviders.value = {};
|
|
67
|
+
if (d && d.result == "success") {
|
|
68
|
+
data.value = d.data;
|
|
69
|
+
data.value.forEach((p: any) => {
|
|
70
|
+
usedProviders.value[p.ProviderUUID] = true;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
eventBus.emit("main-loading", false);
|
|
74
|
+
};
|
|
75
|
+
onMounted(() => {
|
|
76
|
+
getOAuth2User();
|
|
77
|
+
getOAuth2Providers();
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<div class="flex flex-col gap-3">
|
|
83
|
+
<DefaultModal id="providers" :title="$t('providers_modal_title')">
|
|
84
|
+
<template v-for="provider in providersData" :key="provider.UUID">
|
|
85
|
+
<div
|
|
86
|
+
class="flex items-center gap-3"
|
|
87
|
+
v-if="!usedProviders[provider.UUID]"
|
|
88
|
+
>
|
|
89
|
+
<button
|
|
90
|
+
@click="
|
|
91
|
+
() => {
|
|
92
|
+
getOAuth2Redirect(provider.UUID);
|
|
93
|
+
}
|
|
94
|
+
"
|
|
95
|
+
class="flex border border-fv-neutral-300 dark:border-fv-neutral-700 shadow items-center gap-2 justify-start btn neutral defaults w-full mx-auto !font-semibold"
|
|
96
|
+
:style="`background: ${
|
|
97
|
+
provider.Data.Button.button['background-color']
|
|
98
|
+
}; color: ${$getContrastingTextColor(
|
|
99
|
+
provider.Data.Button.button['background-color'],
|
|
100
|
+
)}`"
|
|
101
|
+
>
|
|
102
|
+
<img
|
|
103
|
+
:key="`${provider.Data.Button.label}oauth`"
|
|
104
|
+
class="h-12 w-12 block p-2 mr-3"
|
|
105
|
+
:alt="provider.Data.Button.info.Name"
|
|
106
|
+
:src="provider.Data.Button.button.logo"
|
|
107
|
+
/>
|
|
108
|
+
<div>
|
|
109
|
+
{{
|
|
110
|
+
$t("user_flow_signin_with", {
|
|
111
|
+
provider: provider.Data.Button.name,
|
|
112
|
+
})
|
|
113
|
+
}}
|
|
114
|
+
</div>
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
</template>
|
|
118
|
+
</DefaultModal>
|
|
119
|
+
<h2 class="h3 flex items-center justify-between">
|
|
120
|
+
<span>{{ $t("oauth2_providers_title") }}</span>
|
|
121
|
+
<button
|
|
122
|
+
class="btn primary medium !py-1 !px-3"
|
|
123
|
+
@click="
|
|
124
|
+
() => {
|
|
125
|
+
$eventBus.emit('providersModal', true);
|
|
126
|
+
}
|
|
127
|
+
"
|
|
128
|
+
>
|
|
129
|
+
{{ $t("add_oauth2_con_cta") }}
|
|
130
|
+
</button>
|
|
131
|
+
</h2>
|
|
132
|
+
<p
|
|
133
|
+
class="text-red-900 dark:text-red-300 text-sm bg-red-200/[.2] dark:bg-red-900/[.2] p-2 rounded shadow"
|
|
134
|
+
v-if="
|
|
135
|
+
$route.query.error &&
|
|
136
|
+
$route.query.error === 'user_oauth2_connection_exists'
|
|
137
|
+
"
|
|
138
|
+
>
|
|
139
|
+
{{ $t("oauth2_error_user_oauth2_connection_exists") }}
|
|
140
|
+
</p>
|
|
141
|
+
<div v-if="data && data.length == 0">
|
|
142
|
+
<p>{{ $t("providers_empty") }}</p>
|
|
143
|
+
</div>
|
|
144
|
+
<div
|
|
145
|
+
v-for="provider in data"
|
|
146
|
+
class="flex items-center gap-3"
|
|
147
|
+
:key="provider.ProviderUUID"
|
|
148
|
+
>
|
|
149
|
+
<img
|
|
150
|
+
:src="provider.Provider.Button.button.logo"
|
|
151
|
+
class="w-14 h-14 p-1 rounded-full"
|
|
152
|
+
:style="`background-color: ${provider.Provider.Button.button['background-color']}`"
|
|
153
|
+
/>
|
|
154
|
+
<div>
|
|
155
|
+
<h3 class="text-xl">
|
|
156
|
+
{{ provider.Provider.Button.name }}
|
|
157
|
+
<small class="text-xs">({{ provider.ServiceID }})</small>
|
|
158
|
+
</h3>
|
|
159
|
+
<div class="flex gap-2 mt-1">
|
|
160
|
+
<button
|
|
161
|
+
class="btn danger small"
|
|
162
|
+
@click="
|
|
163
|
+
() => {
|
|
164
|
+
deleteOAuth2Connection(provider.ProviderUUID);
|
|
165
|
+
}
|
|
166
|
+
"
|
|
167
|
+
>
|
|
168
|
+
{{ $t("remove_oauth2_con_cta") }}
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</template>
|
|
@@ -26,18 +26,20 @@ const props = withDefaults(
|
|
|
26
26
|
onClose?: Function;
|
|
27
27
|
closeIcon?: Object;
|
|
28
28
|
gridHeight?: number;
|
|
29
|
-
mode: "mason" | "grid" | "button" | "hidden";
|
|
29
|
+
mode: "mason" | "grid" | "button" | "hidden" | "custom";
|
|
30
30
|
paging?: APIPaging | undefined;
|
|
31
31
|
buttonText?: string;
|
|
32
32
|
buttonType?: string;
|
|
33
33
|
modelValue: number;
|
|
34
34
|
borderColor?: Function;
|
|
35
35
|
imageLoader: string;
|
|
36
|
-
videoComponent?: Component;
|
|
36
|
+
videoComponent?: Component | string;
|
|
37
|
+
imageComponent?: Component | string;
|
|
37
38
|
isVideo?: Function;
|
|
38
39
|
}>(),
|
|
39
40
|
{
|
|
40
41
|
modelValue: 0,
|
|
42
|
+
imageComponent: "img",
|
|
41
43
|
mode: "grid",
|
|
42
44
|
gridHeight: 4,
|
|
43
45
|
closeIcon: () => h(XCircleIcon),
|
|
@@ -199,7 +201,7 @@ onUnmounted(() => {
|
|
|
199
201
|
class="flex h-[100vh] relative flex-grow items-center justify-center gap-2"
|
|
200
202
|
>
|
|
201
203
|
<div
|
|
202
|
-
class="hidden lg:relative lg:flex w-10 flex-shrink-0 items-center justify-center"
|
|
204
|
+
class="hidden lg:relative lg:flex w-10 flex-shrink-0 items-center justify-center flex-0"
|
|
203
205
|
>
|
|
204
206
|
<button
|
|
205
207
|
class="btn p-1 rounded-full"
|
|
@@ -232,13 +234,23 @@ onUnmounted(() => {
|
|
|
232
234
|
<img
|
|
233
235
|
class="shadow max-w-full h-auto object-contain max-h-[85vh]"
|
|
234
236
|
:src="modelValueSrc"
|
|
235
|
-
v-if="modelValueSrc"
|
|
237
|
+
v-if="modelValueSrc && imageComponent == 'img'"
|
|
236
238
|
@touchstart="touchStart"
|
|
237
239
|
@touchend="touchEnd"
|
|
238
240
|
/>
|
|
241
|
+
<component
|
|
242
|
+
v-else-if="modelValueSrc && imageComponent"
|
|
243
|
+
:is="imageComponent"
|
|
244
|
+
:image="modelValueSrc.image"
|
|
245
|
+
:variant="modelValueSrc.variant"
|
|
246
|
+
:alt="modelValueSrc.alt"
|
|
247
|
+
class="shadow max-w-full h-auto object-contain max-h-[85vh]"
|
|
248
|
+
/>
|
|
239
249
|
</template>
|
|
240
250
|
</div>
|
|
241
|
-
<div
|
|
251
|
+
<div
|
|
252
|
+
class="flex-0 py-2 flex items-center justify-center max-w-full w-full"
|
|
253
|
+
>
|
|
242
254
|
<slot :value="images[modelValue]"></slot>
|
|
243
255
|
</div>
|
|
244
256
|
</div>
|
|
@@ -299,6 +311,16 @@ onUnmounted(() => {
|
|
|
299
311
|
images[i - 1],
|
|
300
312
|
)}`"
|
|
301
313
|
:src="getThumbnailUrl(images[i - 1])"
|
|
314
|
+
v-if="imageComponent == 'img'"
|
|
315
|
+
/>
|
|
316
|
+
<component
|
|
317
|
+
v-else
|
|
318
|
+
@click="$eventBus.emit(`${id}GalleryImage`, i - 1)"
|
|
319
|
+
:is="imageComponent"
|
|
320
|
+
:image="getThumbnailUrl(images[i - 1]).image"
|
|
321
|
+
:variant="getThumbnailUrl(images[i - 1]).variant"
|
|
322
|
+
:alt="getThumbnailUrl(images[i - 1]).alt"
|
|
323
|
+
class="h-auto max-w-full rounded-lg cursor-pointer shadow"
|
|
302
324
|
/>
|
|
303
325
|
</div>
|
|
304
326
|
</div>
|
|
@@ -307,12 +329,14 @@ onUnmounted(() => {
|
|
|
307
329
|
</DialogPanel>
|
|
308
330
|
</Dialog>
|
|
309
331
|
</TransitionRoot>
|
|
310
|
-
<template v-if="mode == 'grid' || mode == 'mason'">
|
|
332
|
+
<template v-if="mode == 'grid' || mode == 'mason' || mode == 'custom'">
|
|
311
333
|
<div
|
|
312
|
-
class="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-4"
|
|
313
334
|
:class="{
|
|
314
|
-
'
|
|
315
|
-
|
|
335
|
+
'grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-4 items-start':
|
|
336
|
+
mode == 'mason',
|
|
337
|
+
'grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-4 items-center':
|
|
338
|
+
mode == 'grid',
|
|
339
|
+
'custom-grid': mode == 'custom',
|
|
316
340
|
}"
|
|
317
341
|
>
|
|
318
342
|
<template v-for="i in images.length" :key="`g_${id}_${i}`">
|
|
@@ -326,9 +350,18 @@ onUnmounted(() => {
|
|
|
326
350
|
<img
|
|
327
351
|
@click="$eventBus.emit(`${id}GalleryImage`, i + j - 2)"
|
|
328
352
|
class="h-auto max-w-full rounded-lg cursor-pointer"
|
|
329
|
-
v-if="i + j - 2 < images.length"
|
|
353
|
+
v-if="i + j - 2 < images.length && imageComponent == 'img'"
|
|
330
354
|
:src="getThumbnailUrl(images[i + j - 2])"
|
|
331
355
|
/>
|
|
356
|
+
<component
|
|
357
|
+
v-else-if="i + j - 2 < images.length"
|
|
358
|
+
:is="imageComponent"
|
|
359
|
+
:image="getThumbnailUrl(images[i + j - 2]).image"
|
|
360
|
+
:variant="getThumbnailUrl(images[i + j - 2]).variant"
|
|
361
|
+
:alt="getThumbnailUrl(images[i + j - 2]).alt"
|
|
362
|
+
class="h-auto max-w-full rounded-lg cursor-pointer"
|
|
363
|
+
@click="$eventBus.emit(`${id}GalleryImage`, i + j - 2)"
|
|
364
|
+
/>
|
|
332
365
|
</div>
|
|
333
366
|
</template>
|
|
334
367
|
</div>
|
|
@@ -338,6 +371,16 @@ onUnmounted(() => {
|
|
|
338
371
|
@click="$eventBus.emit(`${id}GalleryImage`, i - 1)"
|
|
339
372
|
class="h-auto max-w-full rounded-lg cursor-pointer"
|
|
340
373
|
:src="getThumbnailUrl(images[i - 1])"
|
|
374
|
+
v-if="imageComponent == 'img'"
|
|
375
|
+
/>
|
|
376
|
+
<component
|
|
377
|
+
v-else-if="imageComponent"
|
|
378
|
+
:is="imageComponent"
|
|
379
|
+
:image="getThumbnailUrl(images[i - 1]).image"
|
|
380
|
+
:variant="getThumbnailUrl(images[i - 1]).variant"
|
|
381
|
+
:alt="getThumbnailUrl(images[i - 1]).alt"
|
|
382
|
+
class="h-auto max-w-full rounded-lg cursor-pointer"
|
|
383
|
+
@click="$eventBus.emit(`${id}GalleryImage`, i - 1)"
|
|
341
384
|
/>
|
|
342
385
|
</div>
|
|
343
386
|
</template>
|
|
@@ -145,6 +145,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
145
145
|
}"
|
|
146
146
|
v-model="model"
|
|
147
147
|
:autocomplete="autocomplete"
|
|
148
|
+
:placeholder="placeholder"
|
|
148
149
|
:disabled="disabled"
|
|
149
150
|
:aria-describedby="help ? `${id}-help` : id"
|
|
150
151
|
class="bg-fv-neutral-50 border border-fv-neutral-300 text-fv-neutral-900 text-sm rounded-lg focus:ring-fv-primary-500 focus:border-fv-primary-500 block w-full p-2.5 dark:bg-fv-neutral-700 dark:border-fv-neutral-600 dark:placeholder-fv-neutral-400 dark:text-white dark:focus:ring-fv-primary-500 dark:focus:border-fv-primary-500"
|
|
@@ -71,12 +71,11 @@ onUnmounted(() => {
|
|
|
71
71
|
v-if="title"
|
|
72
72
|
>
|
|
73
73
|
<slot name="before"></slot>
|
|
74
|
-
<
|
|
74
|
+
<h2
|
|
75
75
|
class="text-xl font-semibold text-fv-neutral-900 dark:text-white"
|
|
76
76
|
v-if="title"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
</DialogTitle>
|
|
77
|
+
v-html="title"
|
|
78
|
+
/>
|
|
80
79
|
<button
|
|
81
80
|
@click="setModal(false)"
|
|
82
81
|
class="text-fv-neutral-400 bg-transparent hover:bg-fv-neutral-200 hover:text-fv-neutral-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center dark:hover:bg-fv-neutral-600 dark:hover:text-white"
|
|
@@ -20,8 +20,10 @@ const props = withDefaults(
|
|
|
20
20
|
items: APIPaging;
|
|
21
21
|
id: string;
|
|
22
22
|
hash?: string;
|
|
23
|
+
showLegend?: boolean;
|
|
23
24
|
}>(),
|
|
24
25
|
{
|
|
26
|
+
showLegend: true,
|
|
25
27
|
hash: "",
|
|
26
28
|
},
|
|
27
29
|
);
|
|
@@ -128,88 +130,97 @@ useFyHead({
|
|
|
128
130
|
v-if="items && items.page_max > 1 && items.page_no"
|
|
129
131
|
>
|
|
130
132
|
<div class="paging-container">
|
|
131
|
-
<nav
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
:key="`${i}-md`"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
133
|
+
<nav aria-label="Pagination">
|
|
134
|
+
<ul class="flex items-center -space-x-px h-8 text-sm">
|
|
135
|
+
<li v-if="items.page_no >= 2">
|
|
136
|
+
<a
|
|
137
|
+
href="javascript:void(0);"
|
|
138
|
+
@click="prev()"
|
|
139
|
+
class="flex items-center justify-center px-1.5 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
140
|
+
>
|
|
141
|
+
<span class="sr-only">{{ $t("previous_paging") }}</span>
|
|
142
|
+
<ChevronLeftIcon class="w-4 h-4" />
|
|
143
|
+
</a>
|
|
144
|
+
</li>
|
|
145
|
+
<li v-if="items.page_no - 2 > 1">
|
|
146
|
+
<a
|
|
147
|
+
class="flex items-center justify-center px-3 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
148
|
+
href="javascript:void(0);"
|
|
149
|
+
@click="page(1)"
|
|
150
|
+
>
|
|
151
|
+
1
|
|
152
|
+
</a>
|
|
153
|
+
</li>
|
|
154
|
+
<li v-if="items.page_no - 2 > 2">
|
|
155
|
+
<div
|
|
156
|
+
v-if="items.page_no - 2 > 2"
|
|
157
|
+
class="flex items-center justify-center px-1.5 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
158
|
+
>
|
|
159
|
+
...
|
|
160
|
+
</div>
|
|
161
|
+
</li>
|
|
162
|
+
<template v-for="i in 2">
|
|
163
|
+
<li v-if="items.page_no - (3 - i) >= 1" :key="`${i}-sm`">
|
|
164
|
+
<a
|
|
165
|
+
class="flex items-center justify-center px-3 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
166
|
+
href="javascript:void(0);"
|
|
167
|
+
@click="page(items.page_no - (3 - i))"
|
|
168
|
+
>
|
|
169
|
+
{{ items.page_no - (3 - i) }}
|
|
170
|
+
</a>
|
|
171
|
+
</li>
|
|
172
|
+
</template>
|
|
173
|
+
<li>
|
|
174
|
+
<a
|
|
175
|
+
href="#"
|
|
176
|
+
aria-current="page"
|
|
177
|
+
class="z-10 flex items-center justify-center px-3 h-8 leading-tight text-primary-600 border border-primary-300 bg-primary-50 hover:bg-primary-100 hover:text-primary-700 dark:border-fv-neutral-700 dark:bg-fv-neutral-700 dark:text-white"
|
|
178
|
+
>
|
|
179
|
+
{{ items.page_no }}
|
|
180
|
+
</a>
|
|
181
|
+
</li>
|
|
182
|
+
<template v-for="i in 2">
|
|
183
|
+
<li :key="`${i}-md`" v-if="items.page_no + i <= items.page_max">
|
|
184
|
+
<a
|
|
185
|
+
class="flex items-center justify-center px-3 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
186
|
+
href="javascript:void(0);"
|
|
187
|
+
@click="page(items.page_no + i)"
|
|
188
|
+
>
|
|
189
|
+
{{ items.page_no + i }}
|
|
190
|
+
</a>
|
|
191
|
+
</li>
|
|
192
|
+
</template>
|
|
193
|
+
<li v-if="items.page_no + 2 < items.page_max - 1">
|
|
194
|
+
<div
|
|
195
|
+
class="flex items-center justify-center px-1.5 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
196
|
+
>
|
|
197
|
+
...
|
|
198
|
+
</div>
|
|
199
|
+
</li>
|
|
200
|
+
<li v-if="items.page_no + 2 < items.page_max">
|
|
201
|
+
<a
|
|
202
|
+
class="flex items-center justify-center px-3 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
203
|
+
href="javascript:void(0);"
|
|
204
|
+
@click="page(items.page_max)"
|
|
205
|
+
>
|
|
206
|
+
{{ items.page_max }}
|
|
207
|
+
</a>
|
|
208
|
+
</li>
|
|
209
|
+
<li v-if="items.page_no < items.page_max - 1">
|
|
210
|
+
<a
|
|
211
|
+
href="javascript:void(0);"
|
|
212
|
+
@click="next()"
|
|
213
|
+
class="flex items-center justify-center px-1.5 h-8 leading-tight text-fv-neutral-500 bg-white border border-fv-neutral-300 hover:bg-fv-neutral-100 hover:text-fv-neutral-700 dark:bg-fv-neutral-800 dark:border-fv-neutral-700 dark:text-fv-neutral-400 dark:hover:bg-fv-neutral-700 dark:hover:text-white"
|
|
214
|
+
>
|
|
215
|
+
<span class="sr-only">{{ $t("next_paging") }}</span>
|
|
216
|
+
<ChevronRightIcon class="w-4 h-4" />
|
|
217
|
+
</a>
|
|
218
|
+
</li>
|
|
219
|
+
</ul>
|
|
210
220
|
</nav>
|
|
211
221
|
<p
|
|
212
|
-
class="text-xs text-
|
|
222
|
+
class="text-xs text-fv-neutral-700 dark:text-fv-neutral-400 pt-0.5"
|
|
223
|
+
v-if="showLegend"
|
|
213
224
|
>
|
|
214
225
|
{{
|
|
215
226
|
$t("global_paging", {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@keydown.delete.prevent="removeLastTag"
|
|
6
6
|
@keydown.enter.prevent="addTag"
|
|
7
7
|
>
|
|
8
|
-
<span v-for="(tag, index) in
|
|
8
|
+
<span v-for="(tag, index) in model" :key="index" :class="`tag ${color}`">
|
|
9
9
|
{{ tag }}
|
|
10
10
|
<button type="button" @click.prevent="removeTag(index)">
|
|
11
11
|
<svg
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</template>
|
|
38
38
|
|
|
39
39
|
<script setup lang="ts">
|
|
40
|
-
import { ref,
|
|
40
|
+
import { ref, computed, onMounted } from "vue";
|
|
41
41
|
type colorType = "blue" | "red" | "green" | "purple" | "orange" | "neutral";
|
|
42
42
|
|
|
43
43
|
const props = withDefaults(
|
|
@@ -59,17 +59,15 @@ const props = withDefaults(
|
|
|
59
59
|
},
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
-
const emit = defineEmits(["update:modelValue"]);
|
|
63
|
-
const tags = ref([...props.modelValue]);
|
|
64
62
|
const textInput = ref<HTMLElement>();
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(
|
|
69
|
-
|
|
64
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
65
|
+
const model = computed({
|
|
66
|
+
get: () => props.modelValue,
|
|
67
|
+
set: (items) => {
|
|
68
|
+
emit("update:modelValue", items);
|
|
70
69
|
},
|
|
71
|
-
|
|
72
|
-
);
|
|
70
|
+
});
|
|
73
71
|
|
|
74
72
|
onMounted(() => {
|
|
75
73
|
if (props.autofocus) {
|
|
@@ -92,23 +90,32 @@ const addTag = () => {
|
|
|
92
90
|
.split(separatorsRegex)
|
|
93
91
|
.map((tag: string) => tag.trim())
|
|
94
92
|
.filter((tag: string) => tag.length > 0);
|
|
95
|
-
|
|
93
|
+
model.value.push(...newTags);
|
|
96
94
|
textInput.value.innerText = "";
|
|
97
95
|
};
|
|
98
96
|
|
|
99
97
|
const removeTag = (index: number) => {
|
|
100
|
-
|
|
98
|
+
model.value.splice(index, 1);
|
|
101
99
|
focusInput();
|
|
102
100
|
};
|
|
103
101
|
|
|
104
102
|
const removeLastTag = () => {
|
|
105
103
|
if (!textInput.value) return;
|
|
106
|
-
|
|
107
104
|
if (textInput.value.innerText === "") {
|
|
108
|
-
|
|
105
|
+
model.value.pop();
|
|
106
|
+
} else {
|
|
107
|
+
const currentLength = textInput.value.innerText.length;
|
|
108
|
+
textInput.value.innerText = textInput.value.innerText.slice(0, -1);
|
|
109
|
+
|
|
110
|
+
const range = document.createRange();
|
|
111
|
+
const sel = window.getSelection();
|
|
112
|
+
range.selectNodeContents(textInput.value);
|
|
113
|
+
range.collapse(false);
|
|
114
|
+
if (!sel) return;
|
|
115
|
+
sel.removeAllRanges();
|
|
116
|
+
sel.addRange(range);
|
|
109
117
|
}
|
|
110
118
|
};
|
|
111
|
-
|
|
112
119
|
const focusInput = () => {
|
|
113
120
|
if (!textInput.value) return;
|
|
114
121
|
|
package/composables/rest.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RestMethod, RestParams, getMode, rest, stringHash } from "@fy-/fws-js";
|
|
2
|
-
import {
|
|
2
|
+
import { useServerRouter } from "../stores/serverRouter";
|
|
3
3
|
import { isServerRendered } from "./ssr";
|
|
4
4
|
import { useEventBus } from "./event-bus";
|
|
5
5
|
|
|
@@ -31,7 +31,7 @@ export function useRest(): <ResultType extends APIResult>(
|
|
|
31
31
|
method: RestMethod,
|
|
32
32
|
params?: RestParams,
|
|
33
33
|
) => Promise<ResultType> {
|
|
34
|
-
const
|
|
34
|
+
const serverRouter = useServerRouter();
|
|
35
35
|
const eventBus = useEventBus();
|
|
36
36
|
|
|
37
37
|
return async <ResultType extends APIResult>(
|
|
@@ -39,13 +39,24 @@ export function useRest(): <ResultType extends APIResult>(
|
|
|
39
39
|
method: RestMethod,
|
|
40
40
|
params?: RestParams,
|
|
41
41
|
): Promise<ResultType> => {
|
|
42
|
-
|
|
42
|
+
let urlForHash: string = url;
|
|
43
|
+
try {
|
|
44
|
+
const urlParse = new URL(url);
|
|
45
|
+
urlForHash = urlParse.pathname + urlParse.search;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
urlForHash = url;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const requestHash = stringHash(
|
|
51
|
+
urlForHash + method + JSON.stringify(params),
|
|
52
|
+
);
|
|
43
53
|
if (isServerRendered()) {
|
|
44
|
-
const hasResult =
|
|
54
|
+
const hasResult = serverRouter.getResult(requestHash);
|
|
45
55
|
if (hasResult !== undefined) {
|
|
46
56
|
const result = hasResult as ResultType;
|
|
47
|
-
|
|
57
|
+
serverRouter.removeResult(requestHash);
|
|
48
58
|
if (result.result === "error") {
|
|
59
|
+
eventBus.emit("main-loading", false);
|
|
49
60
|
eventBus.emit("rest-error", result);
|
|
50
61
|
return Promise.reject(result);
|
|
51
62
|
}
|
|
@@ -56,18 +67,23 @@ export function useRest(): <ResultType extends APIResult>(
|
|
|
56
67
|
try {
|
|
57
68
|
const restResult: ResultType = await rest(url, method, params);
|
|
58
69
|
if (getMode() === "ssr") {
|
|
59
|
-
|
|
70
|
+
serverRouter.addResult(
|
|
60
71
|
requestHash,
|
|
61
72
|
JSON.parse(JSON.stringify(restResult)),
|
|
62
73
|
);
|
|
63
74
|
}
|
|
75
|
+
if (restResult.result === "error") {
|
|
76
|
+
eventBus.emit("main-loading", false);
|
|
77
|
+
eventBus.emit("rest-error", restResult);
|
|
78
|
+
return Promise.reject(restResult);
|
|
79
|
+
}
|
|
64
80
|
return Promise.resolve(restResult);
|
|
65
81
|
} catch (error) {
|
|
66
82
|
const restError: ResultType = error as ResultType;
|
|
67
83
|
if (getMode() === "ssr") {
|
|
68
|
-
|
|
84
|
+
serverRouter.addResult(requestHash, restError);
|
|
69
85
|
}
|
|
70
|
-
|
|
86
|
+
eventBus.emit("main-loading", false);
|
|
71
87
|
eventBus.emit("rest-error", restError);
|
|
72
88
|
return Promise.resolve(restError);
|
|
73
89
|
}
|
package/composables/seo.ts
CHANGED
|
@@ -30,15 +30,16 @@ export interface LazyHead {
|
|
|
30
30
|
export const useSeo = (seo: Ref<LazyHead>, initial: boolean = false) => {
|
|
31
31
|
const currentUrl = `${getURL().Scheme}://${getURL().Host}${getURL().Path}`;
|
|
32
32
|
const currentLocale = seo.value.locale || getLocale();
|
|
33
|
-
|
|
33
|
+
const actualCurrentURL = computed(() => seo.value.canonical || currentUrl);
|
|
34
34
|
useFyHead({
|
|
35
35
|
title: computed(() => seo.value.title),
|
|
36
|
+
scripts: seo.value.scripts,
|
|
36
37
|
links: computed(() => {
|
|
37
38
|
const links = [];
|
|
38
39
|
|
|
39
40
|
links.push({
|
|
40
41
|
rel: "canonical",
|
|
41
|
-
href:
|
|
42
|
+
href: actualCurrentURL.value,
|
|
42
43
|
key: "canonical",
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -8,7 +8,15 @@ const cropText = (str: string, ml = 100, end = "...") => {
|
|
|
8
8
|
}
|
|
9
9
|
return str;
|
|
10
10
|
};
|
|
11
|
+
const getContrastingTextColor = (backgroundColor: string) => {
|
|
12
|
+
const r = parseInt(backgroundColor.substring(1, 3), 16);
|
|
13
|
+
const g = parseInt(backgroundColor.substring(3, 5), 16);
|
|
14
|
+
const b = parseInt(backgroundColor.substring(5, 7), 16);
|
|
11
15
|
|
|
16
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
17
|
+
|
|
18
|
+
return luminance > 0.5 ? "#000000" : "#FFFFFF";
|
|
19
|
+
};
|
|
12
20
|
const formatBytes = (bytes: number, decimals = 2) => {
|
|
13
21
|
if (!+bytes) return "0 Bytes";
|
|
14
22
|
|
|
@@ -76,4 +84,11 @@ const formatTimeago = (dt: Date | string | number) => {
|
|
|
76
84
|
return formatDateTimeago(new Date(_dt), getLocale().replace("_", "-"));
|
|
77
85
|
};
|
|
78
86
|
|
|
79
|
-
export {
|
|
87
|
+
export {
|
|
88
|
+
cropText,
|
|
89
|
+
formatBytes,
|
|
90
|
+
formatDate,
|
|
91
|
+
formatDatetime,
|
|
92
|
+
formatTimeago,
|
|
93
|
+
getContrastingTextColor,
|
|
94
|
+
};
|
package/env.d.ts
CHANGED
package/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isServerRendered,
|
|
12
12
|
} from "./composables/ssr";
|
|
13
13
|
import { useSeo } from "./composables/seo";
|
|
14
|
-
import { useUserStore, useUserCheck } from "./stores/user";
|
|
14
|
+
import { useUserStore, useUserCheck, useUserCheckAsync } from "./stores/user";
|
|
15
15
|
import { ClientOnly } from "./components/ssr/ClientOnly";
|
|
16
16
|
import {
|
|
17
17
|
cropText,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
formatDate,
|
|
20
20
|
formatDatetime,
|
|
21
21
|
formatTimeago,
|
|
22
|
+
getContrastingTextColor,
|
|
22
23
|
} from "./composables/templating";
|
|
23
24
|
import { useRest } from "./composables/rest";
|
|
24
25
|
export * from "./stores/serverRouter";
|
|
@@ -48,7 +49,8 @@ import DataTable from "./components/fws/DataTable.vue";
|
|
|
48
49
|
import FilterData from "./components/fws/FilterData.vue";
|
|
49
50
|
import CmsArticleBoxed from "./components/fws/CmsArticleBoxed.vue";
|
|
50
51
|
import CmsArticleSingle from "./components/fws/CmsArticleSingle.vue";
|
|
51
|
-
|
|
52
|
+
import UserOAuth2 from "./components/fws/UserOAuth2.vue";
|
|
53
|
+
import UserData from "./components/fws/UserData.vue";
|
|
52
54
|
// Css
|
|
53
55
|
import "./style.css";
|
|
54
56
|
|
|
@@ -71,6 +73,8 @@ function createFWS(): Plugin {
|
|
|
71
73
|
app.config.globalProperties.$formatTimeago = formatTimeago;
|
|
72
74
|
app.config.globalProperties.$formatDatetime = formatDatetime;
|
|
73
75
|
app.config.globalProperties.$formatDate = formatDate;
|
|
76
|
+
app.config.globalProperties.$getContrastingTextColor =
|
|
77
|
+
getContrastingTextColor;
|
|
74
78
|
|
|
75
79
|
app.component("ClientOnly", ClientOnly);
|
|
76
80
|
}
|
|
@@ -87,6 +91,7 @@ declare module "vue" {
|
|
|
87
91
|
$formatTimeago: typeof formatTimeago;
|
|
88
92
|
$formatDatetime: typeof formatDatetime;
|
|
89
93
|
$formatDate: typeof formatDate;
|
|
94
|
+
$getContrastingTextColor: typeof getContrastingTextColor;
|
|
90
95
|
}
|
|
91
96
|
export interface GlobalComponents {
|
|
92
97
|
ClientOnly: typeof ClientOnly;
|
|
@@ -105,6 +110,7 @@ export {
|
|
|
105
110
|
useSeo,
|
|
106
111
|
useUserStore,
|
|
107
112
|
useUserCheck,
|
|
113
|
+
useUserCheckAsync,
|
|
108
114
|
useRest,
|
|
109
115
|
|
|
110
116
|
// Components
|
|
@@ -130,8 +136,10 @@ export {
|
|
|
130
136
|
|
|
131
137
|
// FWS
|
|
132
138
|
UserFlow,
|
|
139
|
+
UserOAuth2,
|
|
133
140
|
DataTable,
|
|
134
141
|
FilterData,
|
|
135
142
|
CmsArticleBoxed,
|
|
136
143
|
CmsArticleSingle,
|
|
144
|
+
UserData,
|
|
137
145
|
};
|
package/package.json
CHANGED
package/stores/serverRouter.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface ServerRouterState {
|
|
|
5
5
|
_router: any | null;
|
|
6
6
|
status: number;
|
|
7
7
|
redirect?: string;
|
|
8
|
+
results: Record<number, any | undefined>;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export const useServerRouter = defineStore({
|
|
@@ -14,6 +15,7 @@ export const useServerRouter = defineStore({
|
|
|
14
15
|
_router: null,
|
|
15
16
|
status: 200,
|
|
16
17
|
redirect: undefined,
|
|
18
|
+
results: {},
|
|
17
19
|
}) as ServerRouterState,
|
|
18
20
|
getters: {
|
|
19
21
|
currentRoute: (state) => state._router!.currentRoute,
|
|
@@ -45,5 +47,17 @@ export const useServerRouter = defineStore({
|
|
|
45
47
|
forward() {
|
|
46
48
|
this._router?.go(1);
|
|
47
49
|
},
|
|
50
|
+
addResult(id: number, result: any) {
|
|
51
|
+
this.results[id] = result;
|
|
52
|
+
},
|
|
53
|
+
hasResult(id: number) {
|
|
54
|
+
return this.results[id] !== undefined;
|
|
55
|
+
},
|
|
56
|
+
getResult(id: number) {
|
|
57
|
+
return this.results[id];
|
|
58
|
+
},
|
|
59
|
+
removeResult(id: number) {
|
|
60
|
+
delete this.results[id];
|
|
61
|
+
},
|
|
48
62
|
},
|
|
49
63
|
});
|
package/stores/user.ts
CHANGED
|
@@ -23,7 +23,6 @@ export const useUserStore = defineStore({
|
|
|
23
23
|
actions: {
|
|
24
24
|
async refreshUser() {
|
|
25
25
|
const user: APIResult = await rest("User:get", "GET").catch((err) => {
|
|
26
|
-
console.log(err);
|
|
27
26
|
this.setUser(null);
|
|
28
27
|
});
|
|
29
28
|
if (user.result === "success") {
|
|
@@ -48,6 +47,34 @@ export const useUserStore = defineStore({
|
|
|
48
47
|
},
|
|
49
48
|
});
|
|
50
49
|
|
|
50
|
+
export async function useUserCheckAsync(path = "/login", redirectLink = false) {
|
|
51
|
+
const userStore = useUserStore();
|
|
52
|
+
await userStore.refreshUser();
|
|
53
|
+
const isAuth = computed(() => userStore.isAuth);
|
|
54
|
+
const router = useServerRouter();
|
|
55
|
+
|
|
56
|
+
const checkUser = (route: RouteLocation) => {
|
|
57
|
+
if (route.meta.reqLogin) {
|
|
58
|
+
if (!isAuth.value) {
|
|
59
|
+
if (!redirectLink) router.push(path);
|
|
60
|
+
else {
|
|
61
|
+
router.status = 307;
|
|
62
|
+
router.push(`${path}?return_to=${route.path}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
router._router.afterEach(async () => {
|
|
69
|
+
await userStore.refreshUser();
|
|
70
|
+
});
|
|
71
|
+
router._router.beforeEach((to: any) => {
|
|
72
|
+
if (to.fullPath != path) {
|
|
73
|
+
checkUser(to);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
51
78
|
export function useUserCheck(path = "/login", redirectLink = false) {
|
|
52
79
|
const userStore = useUserStore();
|
|
53
80
|
const isAuth = computed(() => userStore.isAuth);
|
package/stores/rest.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { defineStore } from "pinia";
|
|
2
|
-
import { APIResult } from "../composables/rest";
|
|
3
|
-
|
|
4
|
-
type SharedState = {
|
|
5
|
-
results: Record<number, any | undefined>;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const useRestStore = defineStore({
|
|
9
|
-
id: "restStore",
|
|
10
|
-
state: (): SharedState => ({
|
|
11
|
-
results: {},
|
|
12
|
-
}),
|
|
13
|
-
actions: {
|
|
14
|
-
addResult(id: number, result: any) {
|
|
15
|
-
this.results[id] = result;
|
|
16
|
-
},
|
|
17
|
-
hasResult(id: number) {
|
|
18
|
-
return this.results[id] !== undefined;
|
|
19
|
-
},
|
|
20
|
-
getResult(id: number) {
|
|
21
|
-
return this.results[id];
|
|
22
|
-
},
|
|
23
|
-
removeResult(id: number) {
|
|
24
|
-
delete this.results[id];
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
});
|