@fy-/fws-vue 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/fws/CmsArticleBoxed.vue +113 -0
- package/components/fws/CmsArticleSingle.vue +115 -0
- package/components/fws/DataTable.vue +260 -0
- package/components/fws/FilterData.vue +179 -0
- package/components/fws/UserFlow.vue +305 -0
- package/components/ssr/ClientOnly.ts +12 -0
- package/components/ui/DefaultBreadcrumb.vue +75 -0
- package/components/ui/DefaultConfirm.vue +69 -0
- package/components/ui/DefaultDateSelection.vue +56 -0
- package/components/ui/DefaultInput.vue +243 -0
- package/components/ui/DefaultLoader.vue +49 -0
- package/components/ui/DefaultModal.vue +90 -0
- package/components/ui/DefaultPaging.vue +212 -0
- package/components/ui/transitions/CollapseTransition.vue +30 -0
- package/components/ui/transitions/ExpandTransition.vue +32 -0
- package/components/ui/transitions/FadeTransition.vue +17 -0
- package/components/ui/transitions/ScaleTransition.vue +35 -0
- package/components/ui/transitions/SlideTransition.vue +127 -0
- package/components.d.ts +22 -0
- package/env.d.ts +6 -0
- package/event-bus.ts +14 -0
- package/index.ts +121 -0
- package/package.json +43 -0
- package/rest.ts +72 -0
- package/seo.ts +114 -0
- package/ssr.ts +97 -0
- package/stores/rest.ts +24 -0
- package/stores/serverRouter.ts +49 -0
- package/stores/user.ts +81 -0
- package/style.css +42 -0
- package/templating.ts +79 -0
- package/translations.ts +29 -0
- package/types.d.ts +4 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition name="scale">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</Transition>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<style scoped>
|
|
8
|
+
.scale-enter-active {
|
|
9
|
+
transition: all 0.1s ease-out;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.scale-leave-active {
|
|
13
|
+
transition: all 0.075s ease-in;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.scale-enter-from {
|
|
17
|
+
opacity: 0;
|
|
18
|
+
transform: scale(0.95);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.scale-enter-to {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
transform: scale(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.scale-leave-from {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
transform: scale(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.scale-leave-to {
|
|
32
|
+
opacity: 0;
|
|
33
|
+
transform: scale(0.95);
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
animation: string;
|
|
4
|
+
}>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Transition :name="props.animation" mode="out-in">
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</Transition>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<style scoped>
|
|
14
|
+
/* slide left */
|
|
15
|
+
.slide-left-enter-active {
|
|
16
|
+
transition: all 0.2s;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.slide-left-leave-active {
|
|
20
|
+
transition: all 0.2s;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.slide-left-enter-from {
|
|
24
|
+
opacity: 0;
|
|
25
|
+
transform: translateX(30px);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.slide-left-leave-to {
|
|
29
|
+
opacity: 0;
|
|
30
|
+
transform: translateX(-30px);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* slide right */
|
|
34
|
+
.slide-right-enter-active {
|
|
35
|
+
transition: all 0.2s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.slide-right-leave-active {
|
|
39
|
+
transition: all 0.2s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.slide-right-enter-from {
|
|
43
|
+
opacity: 0;
|
|
44
|
+
transform: translateX(-30px);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.slide-right-leave-to {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
transform: translateX(30px);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* slide up */
|
|
53
|
+
.slide-up-enter-active {
|
|
54
|
+
transition: all 0.2s;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.slide-up-leave-active {
|
|
58
|
+
transition: all 0.2s;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.slide-up-enter-from {
|
|
62
|
+
opacity: 0;
|
|
63
|
+
transform: translateY(-30px);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.slide-up-leave-to {
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transform: translateY(-30px);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* slide down */
|
|
72
|
+
.slide-down-enter-active {
|
|
73
|
+
transition: all 0.2s;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.slide-down-leave-active {
|
|
77
|
+
transition: all 0.2s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.slide-down-enter-from {
|
|
81
|
+
opacity: 0;
|
|
82
|
+
transform: translateY(-50px);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.slide-down-leave-to {
|
|
86
|
+
opacity: 0;
|
|
87
|
+
transform: translateY(-50px);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* shelf up */
|
|
91
|
+
.shelf-up-enter-active {
|
|
92
|
+
transition: all 0.2s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.shelf-up-leave-active {
|
|
96
|
+
transition: all 0.2s;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.shelf-up-enter-from {
|
|
100
|
+
opacity: 0;
|
|
101
|
+
transform: translateY(30px);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.shelf-up-leave-to {
|
|
105
|
+
opacity: 0;
|
|
106
|
+
transform: translateY(30px);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* shelf down */
|
|
110
|
+
.shelf-down-enter-active {
|
|
111
|
+
transition: all 0.2s;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.shelf-down-leave-active {
|
|
115
|
+
transition: all 0.2s;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.shelf-down-enter-from {
|
|
119
|
+
opacity: 0;
|
|
120
|
+
transform: translateY(-30px);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.shelf-down-leave-to {
|
|
124
|
+
opacity: 0;
|
|
125
|
+
transform: translateY(-30px);
|
|
126
|
+
}
|
|
127
|
+
</style>
|
package/components.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/* prettier-ignore */
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
// Generated by unplugin-vue-components
|
|
5
|
+
// Read more: https://github.com/vuejs/core/pull/3399
|
|
6
|
+
export {}
|
|
7
|
+
|
|
8
|
+
declare module 'vue' {
|
|
9
|
+
export interface GlobalComponents {
|
|
10
|
+
CollapseTransition: typeof import('./components/ui/transitions/CollapseTransition.vue')['default']
|
|
11
|
+
DefaultConfirm: typeof import('./components/ui/DefaultConfirm.vue')['default']
|
|
12
|
+
DefaultInput: typeof import('./components/ui/DefaultInput.vue')['default']
|
|
13
|
+
DefaultModal: typeof import('./components/ui/DefaultModal.vue')['default']
|
|
14
|
+
ExpandTransition: typeof import('./components/ui/transitions/ExpandTransition.vue')['default']
|
|
15
|
+
FadeTransition: typeof import('./components/ui/transitions/FadeTransition.vue')['default']
|
|
16
|
+
RouterLink: typeof import('vue-router')['RouterLink']
|
|
17
|
+
RouterView: typeof import('vue-router')['RouterView']
|
|
18
|
+
ScaleTransition: typeof import('./components/ui/transitions/ScaleTransition.vue')['default']
|
|
19
|
+
SlideTransition: typeof import('./components/ui/transitions/SlideTransition.vue')['default']
|
|
20
|
+
UserFlow: typeof import('./components/fws/UserFlow.vue')['default']
|
|
21
|
+
}
|
|
22
|
+
}
|
package/env.d.ts
ADDED
package/event-bus.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { inject } from "vue";
|
|
2
|
+
import type { Emitter } from "mitt";
|
|
3
|
+
|
|
4
|
+
export type Events = {
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function useEventBus(): Emitter<Events> {
|
|
9
|
+
const eventBus = inject<Emitter<Events>>("fwsVueEventBus");
|
|
10
|
+
|
|
11
|
+
if (!eventBus) throw new Error("Did you apply app.use(fwsVue)?");
|
|
12
|
+
|
|
13
|
+
return eventBus;
|
|
14
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Plugin, App } from "vue";
|
|
2
|
+
import i18next from "i18next";
|
|
3
|
+
import mitt from "mitt";
|
|
4
|
+
import { Emitter } from "mitt";
|
|
5
|
+
import { useServerRouter } from "./stores/serverRouter";
|
|
6
|
+
import { useEventBus, Events } from "./event-bus";
|
|
7
|
+
import { i18nextPromise, useTranslation } from "./translations";
|
|
8
|
+
import { initVueClient, initVueServer, isServerRendered } from "./ssr";
|
|
9
|
+
import { useSeo } from "./seo";
|
|
10
|
+
import { useUserStore, useUserCheck } from "./stores/user";
|
|
11
|
+
import { ClientOnly } from "./components/ssr/ClientOnly";
|
|
12
|
+
import {
|
|
13
|
+
cropText,
|
|
14
|
+
formatBytes,
|
|
15
|
+
formatDate,
|
|
16
|
+
formatDatetime,
|
|
17
|
+
formatTimeago,
|
|
18
|
+
} from "./templating";
|
|
19
|
+
export * from "./stores/serverRouter";
|
|
20
|
+
|
|
21
|
+
// Components/UI/Transitions
|
|
22
|
+
import SlideTransition from "./components/ui/transitions/SlideTransition.vue";
|
|
23
|
+
import ExpandTransition from "./components/ui/transitions/ExpandTransition.vue";
|
|
24
|
+
import CollapseTransition from "./components/ui/transitions/CollapseTransition.vue";
|
|
25
|
+
import ScaleTransition from "./components/ui/transitions/ScaleTransition.vue";
|
|
26
|
+
import FadeTransition from "./components/ui/transitions/FadeTransition.vue";
|
|
27
|
+
|
|
28
|
+
// Components/UI
|
|
29
|
+
import DefaultInput from "./components/ui/DefaultInput.vue";
|
|
30
|
+
import DefaultModal from "./components/ui/DefaultModal.vue";
|
|
31
|
+
import DefaultConfirm from "./components/ui/DefaultConfirm.vue";
|
|
32
|
+
import DefaultPaging from "./components/ui/DefaultPaging.vue";
|
|
33
|
+
import DefaultLoading from "./components/ui/DefaultLoading.vue";
|
|
34
|
+
import DefaultBreadcrumb from "./components/ui/DefaultBreadcrumb.vue";
|
|
35
|
+
|
|
36
|
+
// Components/FWS
|
|
37
|
+
import UserFlow from "./components/fws/UserFlow.vue";
|
|
38
|
+
import DataTable from "./components/fws/DataTable.vue";
|
|
39
|
+
import FilterData from "./components/fws/FilterData.vue";
|
|
40
|
+
import CmsArticleBoxed from "./components/fws/CmsArticleBoxed.vue";
|
|
41
|
+
import CmsArticleSingle from "./components/fws/CmsArticleSingle.vue";
|
|
42
|
+
|
|
43
|
+
import "./style.css";
|
|
44
|
+
|
|
45
|
+
function createFWS(): Plugin {
|
|
46
|
+
const eventBus: Emitter<Events> = mitt<Events>();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
install(app: App) {
|
|
50
|
+
if (app.config.globalProperties) {
|
|
51
|
+
app.config.globalProperties.$eventBus = eventBus;
|
|
52
|
+
app.provide("fwsVueEventBus", eventBus);
|
|
53
|
+
|
|
54
|
+
// i18next
|
|
55
|
+
app.config.globalProperties.$t = i18next.t;
|
|
56
|
+
app.provide("fwsVueTranslate", i18next.t);
|
|
57
|
+
|
|
58
|
+
// Templating
|
|
59
|
+
app.config.globalProperties.$cropText = cropText;
|
|
60
|
+
app.config.globalProperties.$formatBytes = formatBytes;
|
|
61
|
+
app.config.globalProperties.$formatTimeago = formatTimeago;
|
|
62
|
+
app.config.globalProperties.$formatDatetime = formatDatetime;
|
|
63
|
+
app.config.globalProperties.$formatDate = formatDate;
|
|
64
|
+
|
|
65
|
+
app.component("ClientOnly", ClientOnly);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare module "vue" {
|
|
72
|
+
export interface ComponentCustomProperties {
|
|
73
|
+
$t: typeof i18next.t;
|
|
74
|
+
$eventBus: Emitter<Events>;
|
|
75
|
+
$cropText: typeof cropText;
|
|
76
|
+
$formatBytes: typeof formatBytes;
|
|
77
|
+
$formatTimeago: typeof formatTimeago;
|
|
78
|
+
$formatDatetime: typeof formatDatetime;
|
|
79
|
+
$formatDate: typeof formatDate;
|
|
80
|
+
}
|
|
81
|
+
export interface GlobalComponents {
|
|
82
|
+
ClientOnly: typeof ClientOnly;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
i18nextPromise,
|
|
88
|
+
useTranslation,
|
|
89
|
+
createFWS,
|
|
90
|
+
useServerRouter,
|
|
91
|
+
useEventBus,
|
|
92
|
+
initVueClient,
|
|
93
|
+
initVueServer,
|
|
94
|
+
isServerRendered,
|
|
95
|
+
useSeo,
|
|
96
|
+
useUserStore,
|
|
97
|
+
useUserCheck,
|
|
98
|
+
|
|
99
|
+
// Components
|
|
100
|
+
// UI/Transitions
|
|
101
|
+
SlideTransition,
|
|
102
|
+
ExpandTransition,
|
|
103
|
+
CollapseTransition,
|
|
104
|
+
ScaleTransition,
|
|
105
|
+
FadeTransition,
|
|
106
|
+
|
|
107
|
+
// UI
|
|
108
|
+
DefaultInput,
|
|
109
|
+
DefaultModal,
|
|
110
|
+
DefaultConfirm,
|
|
111
|
+
DefaultPaging,
|
|
112
|
+
DefaultLoading,
|
|
113
|
+
DefaultBreadcrumb,
|
|
114
|
+
|
|
115
|
+
// FWS
|
|
116
|
+
UserFlow,
|
|
117
|
+
DataTable,
|
|
118
|
+
FilterData,
|
|
119
|
+
CmsArticleBoxed,
|
|
120
|
+
CmsArticleSingle,
|
|
121
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fy-/fws-vue",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Florian 'Fy' Gasquez <m@fy.to>",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/fy-to/FWJS.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/fy-to/FWJS/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/fy-to/FWJS#readme",
|
|
14
|
+
"main": "index.ts",
|
|
15
|
+
"module": "index.ts",
|
|
16
|
+
"typings": "index.ts",
|
|
17
|
+
"types": "index.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./index.ts",
|
|
21
|
+
"require": "./index.ts",
|
|
22
|
+
"browser": "./index.ts"
|
|
23
|
+
},
|
|
24
|
+
"./style.css": {
|
|
25
|
+
"import": "./style.css",
|
|
26
|
+
"require": "./style.css"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@fy-/fws-js": "^0.0.x",
|
|
31
|
+
"@fy-/fws-types": "^0.0.x",
|
|
32
|
+
"@fy-/head": "^0.0.x",
|
|
33
|
+
"@vuelidate/core": "^2.0.x",
|
|
34
|
+
"@vuelidate/validators": "^2.0.x",
|
|
35
|
+
"@vueuse/core": "^10.x.x",
|
|
36
|
+
"pinia": "2.x.x",
|
|
37
|
+
"vue": "^3.2.x",
|
|
38
|
+
"vue-router": "^4.1.x"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"timeago.js": "^4.0.2"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/rest.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { RestMethod, RestParams, getMode, rest, stringHash } from "@fy-/fws-js";
|
|
2
|
+
import { useRestStore } from "./stores/rest";
|
|
3
|
+
import { isServerRendered } from "./ssr";
|
|
4
|
+
import { useEventBus } from "./event-bus";
|
|
5
|
+
|
|
6
|
+
export interface APIPaging {
|
|
7
|
+
page_no: number;
|
|
8
|
+
results_per_page: number;
|
|
9
|
+
page_max: number;
|
|
10
|
+
page_max_relation: string;
|
|
11
|
+
count: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface APIResult {
|
|
15
|
+
result: "redirect" | "success" | "error";
|
|
16
|
+
param?: string;
|
|
17
|
+
code?: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
request?: string;
|
|
20
|
+
time?: number;
|
|
21
|
+
token?: string;
|
|
22
|
+
paging?: APIPaging;
|
|
23
|
+
message?: string;
|
|
24
|
+
fvReject?: boolean;
|
|
25
|
+
data?: any;
|
|
26
|
+
status?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useRest(): <ResultType extends APIResult>(
|
|
30
|
+
url: string,
|
|
31
|
+
method: RestMethod,
|
|
32
|
+
params?: RestParams,
|
|
33
|
+
) => Promise<ResultType> {
|
|
34
|
+
const restStore = useRestStore();
|
|
35
|
+
const eventBus = useEventBus();
|
|
36
|
+
|
|
37
|
+
return async <ResultType extends APIResult>(
|
|
38
|
+
url: string,
|
|
39
|
+
method: RestMethod,
|
|
40
|
+
params?: RestParams,
|
|
41
|
+
): Promise<ResultType> => {
|
|
42
|
+
const requestHash = stringHash(url + method + JSON.stringify(params));
|
|
43
|
+
if (isServerRendered()) {
|
|
44
|
+
const hasResult = restStore.getResult(requestHash);
|
|
45
|
+
if (hasResult !== undefined) {
|
|
46
|
+
const result = { ...hasResult } as ResultType;
|
|
47
|
+
restStore.removeResult(requestHash);
|
|
48
|
+
if (result.result === "error") {
|
|
49
|
+
eventBus.emit("rest-error", result);
|
|
50
|
+
return Promise.reject(result);
|
|
51
|
+
}
|
|
52
|
+
return Promise.resolve(result);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const restResult: ResultType = await rest(url, method, params);
|
|
58
|
+
if (getMode() === "ssr") {
|
|
59
|
+
restStore.addResult(requestHash, restResult);
|
|
60
|
+
}
|
|
61
|
+
return Promise.resolve(restResult);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const restError: ResultType = error as ResultType;
|
|
64
|
+
if (getMode() === "ssr") {
|
|
65
|
+
restStore.addResult(requestHash, restError);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
eventBus.emit("rest-error", restError);
|
|
69
|
+
return Promise.resolve(restError);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
package/seo.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Ref } from "vue";
|
|
2
|
+
import { useFyHead } from "@fy-/head";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import { getURL, getLocale } from "@fy-/fws-js";
|
|
5
|
+
|
|
6
|
+
export interface LazyHead {
|
|
7
|
+
name?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
imageType?: string;
|
|
11
|
+
imageWidth?: string;
|
|
12
|
+
imageHeight?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
published?: string;
|
|
15
|
+
modified?: string;
|
|
16
|
+
keywords?: string;
|
|
17
|
+
type?: "blog" | "search" | "article" | "website";
|
|
18
|
+
searchAction?: string;
|
|
19
|
+
next?: string;
|
|
20
|
+
prev?: string;
|
|
21
|
+
canonical?: string;
|
|
22
|
+
locale?: string;
|
|
23
|
+
robots?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
isAdult?: boolean;
|
|
26
|
+
alternateLocales?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const useSeo = (seo: Ref<LazyHead>, initial: boolean = false) => {
|
|
30
|
+
const currentUrl = `${getURL().Scheme}://${getURL().Host}${getURL().Path}`;
|
|
31
|
+
const currentLocale = seo.value.locale || getLocale();
|
|
32
|
+
|
|
33
|
+
useFyHead({
|
|
34
|
+
title: computed(() => seo.value.title),
|
|
35
|
+
links: computed(() => {
|
|
36
|
+
const links = [];
|
|
37
|
+
|
|
38
|
+
links.push({
|
|
39
|
+
rel: "canonical",
|
|
40
|
+
href: seo.value.canonical || currentUrl,
|
|
41
|
+
key: "canonical",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
["prev", "next"].forEach((rel) => {
|
|
45
|
+
if (seo.value[rel as keyof LazyHead]) {
|
|
46
|
+
links.push({
|
|
47
|
+
rel,
|
|
48
|
+
href: seo.value[rel as keyof LazyHead] as string,
|
|
49
|
+
key: rel,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
seo.value.alternateLocales?.forEach((locale) => {
|
|
55
|
+
if (locale !== currentLocale) {
|
|
56
|
+
links.push({
|
|
57
|
+
rel: "alternate",
|
|
58
|
+
hreflang: locale,
|
|
59
|
+
href: `${currentUrl}/l/${locale}${getURL().Path}`,
|
|
60
|
+
key: `alternate-${locale}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return links;
|
|
66
|
+
}),
|
|
67
|
+
metas: computed(() => {
|
|
68
|
+
const metas = [];
|
|
69
|
+
|
|
70
|
+
metas.push(
|
|
71
|
+
{ property: "og:locale", content: currentLocale },
|
|
72
|
+
{ property: "og:url", content: seo.value.url || currentUrl },
|
|
73
|
+
{ property: "og:type", content: seo.value.type || "website" },
|
|
74
|
+
{
|
|
75
|
+
name: "robots",
|
|
76
|
+
content:
|
|
77
|
+
"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (seo.value.isAdult) {
|
|
82
|
+
metas.push(
|
|
83
|
+
{ property: "rating", content: "adult" },
|
|
84
|
+
{ property: "RATING", content: "RTA-5042-1996-1400-1577-RTA" },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const metaPairs = [
|
|
89
|
+
["name", "og:site_name"],
|
|
90
|
+
["title", "og:title", "twitter:title"],
|
|
91
|
+
["description", "og:description", "twitter:description", "description"],
|
|
92
|
+
["published", "article:published_time"],
|
|
93
|
+
["modified", "article:modified_time"],
|
|
94
|
+
["imageWidth", "og:image:width"],
|
|
95
|
+
["imageHeight", "og:image:height"],
|
|
96
|
+
["imageType", "og:image:type", "twitter:image:type"],
|
|
97
|
+
["image", "og:image", "twitter:image"],
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
metaPairs.forEach(([seoKey, ...properties]) => {
|
|
101
|
+
properties.forEach((property) => {
|
|
102
|
+
if (seo.value[seoKey as keyof LazyHead]) {
|
|
103
|
+
metas.push({
|
|
104
|
+
property,
|
|
105
|
+
content: seo.value[seoKey as keyof LazyHead] as string,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return metas;
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
};
|
package/ssr.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Router } from "vue-router";
|
|
2
|
+
import { Pinia } from "pinia";
|
|
3
|
+
import { renderToString } from "@vue/server-renderer";
|
|
4
|
+
import { getInitialState, getPath, getURL, getUUID } from "@fy-/fws-js";
|
|
5
|
+
import { useServerRouter } from "./stores/serverRouter";
|
|
6
|
+
|
|
7
|
+
export interface SSRResult {
|
|
8
|
+
initial: {
|
|
9
|
+
isSSR: boolean;
|
|
10
|
+
pinia?: string;
|
|
11
|
+
};
|
|
12
|
+
uuid?: string;
|
|
13
|
+
meta?: string;
|
|
14
|
+
link?: string;
|
|
15
|
+
bodyAttributes?: string;
|
|
16
|
+
htmlAttributes?: string;
|
|
17
|
+
bodyTags?: string;
|
|
18
|
+
app?: string;
|
|
19
|
+
statusCode?: number;
|
|
20
|
+
redirect?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isServerRendered() {
|
|
24
|
+
return getInitialState().isSSR;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function initVueClient(router: Router, pinia: Pinia) {
|
|
28
|
+
const state = getInitialState();
|
|
29
|
+
if (state.isSSR && state && state.pinia) {
|
|
30
|
+
pinia.state.value = state.pinia;
|
|
31
|
+
}
|
|
32
|
+
useServerRouter(pinia)._setRouter(router);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function initVueServer(
|
|
36
|
+
createApp: Function,
|
|
37
|
+
callback: Function,
|
|
38
|
+
options: { url?: string } = {},
|
|
39
|
+
) {
|
|
40
|
+
const url =
|
|
41
|
+
options.url || `${getPath()}${getURL().Query ? `?${getURL().Query}` : ""}`;
|
|
42
|
+
const { app, router, head, pinia } = await createApp(true);
|
|
43
|
+
await router.push(url);
|
|
44
|
+
await router.isReady();
|
|
45
|
+
const serverRouter = useServerRouter(pinia);
|
|
46
|
+
serverRouter._setRouter(router);
|
|
47
|
+
|
|
48
|
+
const result: SSRResult = {
|
|
49
|
+
uuid: getUUID(),
|
|
50
|
+
initial: {
|
|
51
|
+
isSSR: true,
|
|
52
|
+
pinia: undefined,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (url !== serverRouter.route.fullPath) {
|
|
57
|
+
result.redirect = serverRouter.route.value.fullPath;
|
|
58
|
+
result.statusCode = 307;
|
|
59
|
+
callback(result);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const html = await renderToString(app);
|
|
65
|
+
const { headTags, htmlAttrs, bodyAttrs, bodyTags } =
|
|
66
|
+
head.renderHeadToString();
|
|
67
|
+
|
|
68
|
+
result.meta = headTags;
|
|
69
|
+
result.bodyAttributes = bodyAttrs;
|
|
70
|
+
result.htmlAttributes = htmlAttrs;
|
|
71
|
+
result.bodyTags = bodyTags;
|
|
72
|
+
result.app = html;
|
|
73
|
+
if (serverRouter.status != 200) {
|
|
74
|
+
if ([301, 302, 303, 307].includes(serverRouter.status)) {
|
|
75
|
+
if (serverRouter.redirect) {
|
|
76
|
+
result.statusCode = serverRouter.status;
|
|
77
|
+
result.redirect = serverRouter.redirect;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
result.statusCode = serverRouter.status;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
serverRouter._router = null;
|
|
85
|
+
result.initial.pinia = pinia.state.value;
|
|
86
|
+
callback(result);
|
|
87
|
+
return result;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
//console.error(error);
|
|
90
|
+
result.statusCode = 500;
|
|
91
|
+
const err =
|
|
92
|
+
error instanceof Error ? `${error.message}\n${error.stack}` : error;
|
|
93
|
+
const errorResult = `<div id="app"><h1>500 Internal Server Error</h1><pre>${err}</pre></div>`;
|
|
94
|
+
callback(result);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
}
|
package/stores/rest.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineStore } from "pinia";
|
|
2
|
+
import { APIResult } from "../rest";
|
|
3
|
+
|
|
4
|
+
type SharedState = {
|
|
5
|
+
results: Record<number, APIResult | undefined>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const useRestStore = defineStore({
|
|
9
|
+
id: "restStore",
|
|
10
|
+
state: (): SharedState => ({
|
|
11
|
+
results: {},
|
|
12
|
+
}),
|
|
13
|
+
actions: {
|
|
14
|
+
addResult(id: number, result: APIResult) {
|
|
15
|
+
this.results[id] = result;
|
|
16
|
+
},
|
|
17
|
+
getResult(id: number) {
|
|
18
|
+
return this.results[id];
|
|
19
|
+
},
|
|
20
|
+
removeResult(id: number) {
|
|
21
|
+
delete this.results[id];
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|