@tager/admin-blog 0.3.21 → 0.3.25
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/index.esm.js +3452 -612
- package/dist/index.umd.js +3454 -619
- package/package.json +13 -7
- package/src/constants/menu.ts +0 -2
- package/src/constants/paths.ts +0 -5
- package/src/constants/routes.ts +5 -18
- package/src/constants/scopes.ts +11 -0
- package/src/dev.ts +11 -44
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useBlogCategoryList.ts +3 -5
- package/src/hooks/useBlogPostList.ts +2 -4
- package/src/hooks/useModuleConfig.ts +4 -8
- package/src/hooks/useStore.ts +8 -0
- package/src/hooks/useUserPermission.ts +16 -0
- package/src/hooks/useUserProfile.ts +13 -0
- package/src/locales/en.ts +17 -5
- package/src/locales/ru.ts +17 -4
- package/src/router/index.ts +39 -0
- package/src/services/requests.ts +33 -43
- package/src/store/index.ts +21 -0
- package/src/store/user.ts +109 -0
- package/src/typings/common.ts +1 -0
- package/src/typings/model.ts +62 -38
- package/src/utils/common.ts +23 -0
- package/src/views/App.vue +10 -4
- package/src/views/{BlogCategoryForm/BlogCategoryForm.helpers.ts → Categories/CategoryForm/CategoryForm.helpers.ts} +22 -7
- package/src/views/{BlogCategoryForm/BlogCategoryForm.vue → Categories/CategoryForm/CategoryForm.vue} +140 -63
- package/src/views/Categories/CategoryForm/index.ts +1 -0
- package/src/views/Categories/CategoryList/CategoryList.helpers.ts +91 -0
- package/src/views/Categories/CategoryList/CategoryList.vue +269 -0
- package/src/views/Categories/CategoryList/hooks/index.ts +1 -0
- package/src/views/Categories/CategoryList/hooks/useAdvancedSearch.ts +95 -0
- package/src/views/Categories/CategoryList/index.ts +1 -0
- package/src/views/Home.vue +2 -9
- package/src/views/Posts/PostForm/PostForm.helpers.ts +150 -0
- package/src/views/Posts/PostForm/PostForm.vue +550 -0
- package/src/views/{BlogPostForm → Posts/PostForm}/components/TabList/TabList.types.ts +0 -0
- package/src/views/{BlogPostForm → Posts/PostForm}/components/TabList/TabList.vue +1 -1
- package/src/views/{BlogPostForm → Posts/PostForm}/components/TabList/index.ts +0 -0
- package/src/views/Posts/PostForm/index.ts +1 -0
- package/src/views/{BlogPostList/BlogPostList.helpers.ts → Posts/PostList/PostList.helpers.ts} +10 -18
- package/src/views/Posts/PostList/PostList.vue +361 -0
- package/src/views/Posts/PostList/hooks/index.ts +1 -0
- package/src/views/Posts/PostList/hooks/useAdvancedSearch.ts +250 -0
- package/src/views/Posts/PostList/index.ts +1 -0
- package/dist/constants/menu.d.ts +0 -5
- package/dist/constants/paths.d.ts +0 -16
- package/dist/constants/routes.d.ts +0 -6
- package/dist/dev.d.ts +0 -3
- package/dist/hooks/useBlogCategoryList.d.ts +0 -7
- package/dist/hooks/useBlogPostList.d.ts +0 -7
- package/dist/hooks/useModuleConfig.d.ts +0 -7
- package/dist/locales/en.d.ts +0 -63
- package/dist/locales/ru.d.ts +0 -3
- package/dist/main.d.ts +0 -4
- package/dist/services/requests.d.ts +0 -68
- package/dist/typings/model.d.ts +0 -74
- package/dist/views/BlogCategoryForm/BlogCategoryForm.helpers.d.ts +0 -16
- package/dist/views/BlogCategoryForm/BlogCategoryForm.vue.d.ts +0 -86
- package/dist/views/BlogCategoryForm/index.d.ts +0 -1
- package/dist/views/BlogCategoryList/BlogCategoryList.helpers.d.ts +0 -6
- package/dist/views/BlogCategoryList/BlogCategoryList.vue.d.ts +0 -3
- package/dist/views/BlogCategoryList/index.d.ts +0 -1
- package/dist/views/BlogPostForm/BlogPostForm.helpers.d.ts +0 -27
- package/dist/views/BlogPostForm/BlogPostForm.vue.d.ts +0 -9320
- package/dist/views/BlogPostForm/components/TabList/TabList.types.d.ts +0 -5
- package/dist/views/BlogPostForm/components/TabList/index.d.ts +0 -2
- package/dist/views/BlogPostForm/index.d.ts +0 -1
- package/dist/views/BlogPostList/BlogPostList.helpers.d.ts +0 -6
- package/dist/views/BlogPostList/BlogPostList.vue.d.ts +0 -45
- package/dist/views/BlogPostList/index.d.ts +0 -1
- package/dist/views/BlogSettings/BlogSettings.helpers.d.ts +0 -6
- package/dist/views/BlogSettings/BlogSettings.vue.d.ts +0 -9132
- package/dist/views/BlogSettings/index.d.ts +0 -1
- package/src/views/BlogCategoryForm/index.ts +0 -1
- package/src/views/BlogCategoryList/BlogCategoryList.helpers.ts +0 -82
- package/src/views/BlogCategoryList/BlogCategoryList.vue +0 -196
- package/src/views/BlogCategoryList/index.ts +0 -1
- package/src/views/BlogPostForm/BlogPostForm.helpers.ts +0 -152
- package/src/views/BlogPostForm/BlogPostForm.vue +0 -534
- package/src/views/BlogPostForm/index.ts +0 -1
- package/src/views/BlogPostList/BlogPostList.vue +0 -174
- package/src/views/BlogPostList/index.ts +0 -1
- package/src/views/BlogSettings/BlogSettings.helpers.ts +0 -25
- package/src/views/BlogSettings/BlogSettings.vue +0 -110
- package/src/views/BlogSettings/index.ts +0 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { GetterTree, Module } from 'vuex';
|
|
2
|
+
|
|
3
|
+
import { FETCH_STATUSES, FetchStatus } from '@tager/admin-services';
|
|
4
|
+
|
|
5
|
+
import { ScopeType, UserModel } from '@/typings/model';
|
|
6
|
+
import { getUserProfile } from '@/services/requests';
|
|
7
|
+
import { RootState } from '@/store';
|
|
8
|
+
import { Scope } from '@/constants/scopes';
|
|
9
|
+
|
|
10
|
+
export interface UserModuleStateModel {
|
|
11
|
+
profile: UserModel | null;
|
|
12
|
+
status: FetchStatus;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const USER_MODULE_NAMESPACE = 'user';
|
|
16
|
+
|
|
17
|
+
const USER_MUTATION_TYPES = {
|
|
18
|
+
PROFILE_REQUEST_STARTED: 'profileRequestStarted',
|
|
19
|
+
PROFILE_REQUEST_FULFILLED: 'profileRequestFulfilled',
|
|
20
|
+
PROFILE_REQUEST_REJECTED: 'profileRequestRejected',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const USER_ACTION_TYPES = {
|
|
24
|
+
FETCH_USER_PROFILE: 'fetchUserProfile',
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
type ReturnValueMapType<
|
|
28
|
+
T extends { [key: string]: (...args: any[]) => any }
|
|
29
|
+
> = { [key in keyof T]: ReturnType<T[key]> };
|
|
30
|
+
|
|
31
|
+
interface UserGettersType extends GetterTree<UserModuleStateModel, RootState> {
|
|
32
|
+
userScopes: (state: UserModuleStateModel) => Array<ScopeType>;
|
|
33
|
+
userHasPermission: (
|
|
34
|
+
state: UserModuleStateModel,
|
|
35
|
+
getters: ReturnValueMapType<UserGettersType>
|
|
36
|
+
) => (scope: ScopeType) => boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const userGetters: UserGettersType = {
|
|
40
|
+
userProfile(state) {
|
|
41
|
+
return state.profile;
|
|
42
|
+
},
|
|
43
|
+
userScopes(state) {
|
|
44
|
+
const roles = state.profile ? state.profile.roles : [];
|
|
45
|
+
const scopes: Array<ScopeType> = [];
|
|
46
|
+
|
|
47
|
+
roles.forEach((role) => {
|
|
48
|
+
scopes.push(...role.scopes);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return scopes;
|
|
52
|
+
},
|
|
53
|
+
userHasPermission: (state, getters) => (scope: ScopeType) => {
|
|
54
|
+
const scopes = getters.userScopes;
|
|
55
|
+
|
|
56
|
+
if (scopes.includes(Scope.All)) return true;
|
|
57
|
+
|
|
58
|
+
return scopes.includes(scope);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const userModule: Module<UserModuleStateModel, RootState> = {
|
|
63
|
+
namespaced: true,
|
|
64
|
+
state: {
|
|
65
|
+
profile: null,
|
|
66
|
+
status: FETCH_STATUSES.IDLE,
|
|
67
|
+
},
|
|
68
|
+
mutations: {
|
|
69
|
+
[USER_MUTATION_TYPES.PROFILE_REQUEST_STARTED](state) {
|
|
70
|
+
if (state.status === FETCH_STATUSES.IDLE) {
|
|
71
|
+
state.status = FETCH_STATUSES.LOADING;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[USER_MUTATION_TYPES.PROFILE_REQUEST_FULFILLED](state, user: UserModel) {
|
|
75
|
+
if (state.status === FETCH_STATUSES.LOADING) {
|
|
76
|
+
state.status = FETCH_STATUSES.SUCCESS;
|
|
77
|
+
state.profile = user;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
[USER_MUTATION_TYPES.PROFILE_REQUEST_REJECTED](state) {
|
|
81
|
+
if (state.status === FETCH_STATUSES.LOADING) {
|
|
82
|
+
state.status = FETCH_STATUSES.FAILURE;
|
|
83
|
+
state.profile = null;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
actions: {
|
|
88
|
+
[USER_ACTION_TYPES.FETCH_USER_PROFILE](store) {
|
|
89
|
+
if (store.state.status !== FETCH_STATUSES.IDLE) return;
|
|
90
|
+
|
|
91
|
+
store.commit(USER_MUTATION_TYPES.PROFILE_REQUEST_STARTED);
|
|
92
|
+
|
|
93
|
+
getUserProfile()
|
|
94
|
+
.then((response) => {
|
|
95
|
+
store.commit(
|
|
96
|
+
USER_MUTATION_TYPES.PROFILE_REQUEST_FULFILLED,
|
|
97
|
+
response.data
|
|
98
|
+
);
|
|
99
|
+
})
|
|
100
|
+
.catch((error) => {
|
|
101
|
+
console.error(error);
|
|
102
|
+
store.commit(USER_MUTATION_TYPES.PROFILE_REQUEST_REJECTED);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
getters: userGetters,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default userModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Values<T> = T[keyof T];
|
package/src/typings/model.ts
CHANGED
|
@@ -6,13 +6,34 @@ import {
|
|
|
6
6
|
} from '@tager/admin-dynamic-field';
|
|
7
7
|
import { ShortCodeItemType } from '@tager/admin-ui';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { Values } from '@/typings/common';
|
|
10
|
+
import { Scope } from '@/constants/scopes';
|
|
11
|
+
|
|
12
|
+
export type ScopeType = Values<typeof Scope>;
|
|
13
|
+
|
|
14
|
+
export interface RoleModel {
|
|
15
|
+
readonly id: number;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly scopes: Array<ScopeType>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UserModel {
|
|
21
|
+
readonly id: number;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly email: string;
|
|
24
|
+
readonly roles: Array<RoleModel>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SeoInfo {
|
|
10
28
|
readonly pageTitle: Nullable<string>;
|
|
11
29
|
readonly pageDescription: Nullable<string>;
|
|
30
|
+
readonly pageKeywords: Nullable<string>;
|
|
31
|
+
readonly openGraphTitle: Nullable<string>;
|
|
32
|
+
readonly openGraphDescription: Nullable<string>;
|
|
12
33
|
readonly openGraphImage: Nullable<FileType>;
|
|
13
|
-
}
|
|
34
|
+
}
|
|
14
35
|
|
|
15
|
-
export
|
|
36
|
+
export interface Category extends SeoInfo {
|
|
16
37
|
readonly id: number;
|
|
17
38
|
readonly name: string;
|
|
18
39
|
readonly isDefault: boolean;
|
|
@@ -21,21 +42,23 @@ export type BlogCategory = {
|
|
|
21
42
|
readonly urlTemplate: string;
|
|
22
43
|
readonly urlAlias: string;
|
|
23
44
|
readonly postsCount: string;
|
|
24
|
-
|
|
45
|
+
readonly parent: Nullable<Pick<Category, 'id' | 'name'>>;
|
|
46
|
+
readonly depth: number;
|
|
47
|
+
}
|
|
25
48
|
|
|
26
|
-
export
|
|
49
|
+
export interface PostShort {
|
|
27
50
|
readonly id: number;
|
|
28
51
|
readonly title: string;
|
|
29
52
|
readonly url: string;
|
|
30
53
|
readonly excerpt: string;
|
|
31
54
|
readonly status: string;
|
|
32
55
|
readonly language: string;
|
|
33
|
-
readonly categories:
|
|
56
|
+
readonly categories: Category[];
|
|
34
57
|
readonly date: string;
|
|
35
58
|
readonly image: Nullable<FileType>;
|
|
36
|
-
}
|
|
59
|
+
}
|
|
37
60
|
|
|
38
|
-
export
|
|
61
|
+
export interface PostFull extends SeoInfo {
|
|
39
62
|
readonly id: number;
|
|
40
63
|
readonly language: string;
|
|
41
64
|
readonly title: string;
|
|
@@ -47,38 +70,39 @@ export type PostFull = {
|
|
|
47
70
|
readonly excerpt: string;
|
|
48
71
|
readonly image: Nullable<FileType>;
|
|
49
72
|
readonly imageMobile: Nullable<FileType>;
|
|
50
|
-
readonly categories:
|
|
73
|
+
readonly categories: Category[];
|
|
51
74
|
readonly body: string;
|
|
52
75
|
readonly coverImage: Nullable<FileType>;
|
|
53
|
-
readonly relatedPosts:
|
|
54
|
-
readonly tags:
|
|
55
|
-
readonly additionalFields:
|
|
56
|
-
}
|
|
76
|
+
readonly relatedPosts: Pick<PostShort, 'id' | 'title'>[];
|
|
77
|
+
readonly tags: string[];
|
|
78
|
+
readonly additionalFields: FieldShortType<IncomingValueUnion>[];
|
|
79
|
+
}
|
|
57
80
|
|
|
58
|
-
export
|
|
81
|
+
export interface Language {
|
|
82
|
+
readonly id: string;
|
|
83
|
+
readonly name: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface DefaultCategory {
|
|
87
|
+
readonly id: number;
|
|
88
|
+
readonly name: string;
|
|
89
|
+
readonly language: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface FileScenarios {
|
|
93
|
+
readonly cover: Nullable<string>;
|
|
94
|
+
readonly image: Nullable<string>;
|
|
95
|
+
readonly imageMobile: Nullable<string>;
|
|
96
|
+
readonly content: Nullable<string>;
|
|
97
|
+
readonly openGraph: Nullable<string>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ModuleConfig {
|
|
59
101
|
readonly urlPostTemplate: Nullable<string>;
|
|
60
102
|
readonly urlCategoryTemplate: Nullable<string>;
|
|
61
|
-
readonly languages:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
name: string;
|
|
68
|
-
language: string;
|
|
69
|
-
}>;
|
|
70
|
-
readonly fields: Array<FieldConfigUnion>;
|
|
71
|
-
readonly shortcodes: Array<ShortCodeItemType>;
|
|
72
|
-
readonly fileScenarios: {
|
|
73
|
-
cover: Nullable<string>;
|
|
74
|
-
image: Nullable<string>;
|
|
75
|
-
imageMobile: Nullable<string>;
|
|
76
|
-
content: Nullable<string>;
|
|
77
|
-
openGraph: Nullable<string>;
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export type SettingItemType = {
|
|
82
|
-
field: FieldConfigUnion;
|
|
83
|
-
value: IncomingValueUnion;
|
|
84
|
-
};
|
|
103
|
+
readonly languages: Language[];
|
|
104
|
+
readonly defaultCategories: DefaultCategory[];
|
|
105
|
+
readonly fields: FieldConfigUnion[];
|
|
106
|
+
readonly shortcodes: ShortCodeItemType[];
|
|
107
|
+
readonly fileScenarios: FileScenarios;
|
|
108
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { USER_MODULE_NAMESPACE } from '@/store/user';
|
|
2
|
+
|
|
3
|
+
export function namespace(namespace: string, name: string): string {
|
|
4
|
+
return `${namespace}/${name}`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function userNamespace(name: string): string {
|
|
8
|
+
return namespace(USER_MODULE_NAMESPACE, name);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function generateNumberArray(length: number): Array<number> {
|
|
12
|
+
return Array.from({ length }, (_, index) => index);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getNameWithDepth(name: string, depth: number): string {
|
|
16
|
+
return (
|
|
17
|
+
generateNumberArray(depth)
|
|
18
|
+
.map(() => '—')
|
|
19
|
+
.join(' ') +
|
|
20
|
+
' ' +
|
|
21
|
+
name
|
|
22
|
+
);
|
|
23
|
+
}
|
package/src/views/App.vue
CHANGED
|
@@ -5,20 +5,26 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import { defineComponent, SetupContext } from '@vue/composition-api';
|
|
8
|
+
import { defineComponent, onMounted, SetupContext } from '@vue/composition-api';
|
|
9
9
|
|
|
10
|
-
import { getBlogMenuItem } from '
|
|
10
|
+
import { getBlogMenuItem } from '@/constants/menu';
|
|
11
|
+
import { useStore } from '@/hooks';
|
|
12
|
+
import { USER_ACTION_TYPES } from '@/store/user';
|
|
13
|
+
import { userNamespace } from '@/utils/common';
|
|
11
14
|
|
|
12
15
|
export default defineComponent({
|
|
13
16
|
name: 'App',
|
|
14
17
|
setup(props, context: SetupContext) {
|
|
18
|
+
const store = useStore(context);
|
|
15
19
|
const blogMenuItem = getBlogMenuItem({ t: context.root.$t });
|
|
16
20
|
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
store.dispatch(userNamespace(USER_ACTION_TYPES.FETCH_USER_PROFILE));
|
|
23
|
+
});
|
|
24
|
+
|
|
17
25
|
return {
|
|
18
26
|
sidebarMenuList: [blogMenuItem],
|
|
19
27
|
};
|
|
20
28
|
},
|
|
21
29
|
});
|
|
22
30
|
</script>
|
|
23
|
-
|
|
24
|
-
<style scoped lang="scss"></style>
|
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
import { createId, Nullable } from '@tager/admin-services';
|
|
2
2
|
import { OptionType, SingleFileInputValueType } from '@tager/admin-ui';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { Category } from '@/typings/model';
|
|
5
5
|
import {
|
|
6
6
|
BlogCategoryCreationPayload,
|
|
7
7
|
BlogCategoryUpdatePayload,
|
|
8
|
-
} from '
|
|
8
|
+
} from '@/services/requests';
|
|
9
9
|
|
|
10
10
|
export type CategoryFormValues = {
|
|
11
11
|
name: string;
|
|
12
12
|
isDefault: boolean;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
pageTitle: Nullable<string>;
|
|
15
|
+
pageDescription: Nullable<string>;
|
|
16
|
+
pageKeywords: Nullable<string>;
|
|
17
|
+
openGraphTitle: Nullable<string>;
|
|
18
|
+
openGraphDescription: Nullable<string>;
|
|
15
19
|
openGraphImage: Nullable<SingleFileInputValueType>;
|
|
20
|
+
|
|
16
21
|
urlAlias: string;
|
|
17
22
|
language: Nullable<OptionType>;
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
export function convertCategoryToFormValues(
|
|
21
|
-
category: Nullable<
|
|
22
|
-
languageOptionList:
|
|
26
|
+
category: Nullable<Category>,
|
|
27
|
+
languageOptionList: OptionType[]
|
|
23
28
|
): CategoryFormValues {
|
|
24
29
|
const currentLangOption = languageOptionList.find(
|
|
25
|
-
(
|
|
30
|
+
({ value }) => value === category?.language
|
|
26
31
|
);
|
|
27
32
|
|
|
28
33
|
if (!category) {
|
|
29
34
|
return {
|
|
30
35
|
name: '',
|
|
31
36
|
isDefault: false,
|
|
37
|
+
|
|
32
38
|
pageTitle: '',
|
|
33
39
|
pageDescription: '',
|
|
40
|
+
openGraphTitle: '',
|
|
41
|
+
openGraphDescription: '',
|
|
42
|
+
pageKeywords: '',
|
|
34
43
|
openGraphImage: null,
|
|
44
|
+
|
|
35
45
|
urlAlias: '',
|
|
36
46
|
language: currentLangOption ?? null,
|
|
37
47
|
};
|
|
@@ -39,11 +49,16 @@ export function convertCategoryToFormValues(
|
|
|
39
49
|
return {
|
|
40
50
|
name: category.name,
|
|
41
51
|
isDefault: category.isDefault,
|
|
52
|
+
|
|
42
53
|
pageTitle: category.pageTitle ?? '',
|
|
43
54
|
pageDescription: category.pageDescription ?? '',
|
|
55
|
+
pageKeywords: category.pageKeywords ?? '',
|
|
56
|
+
openGraphTitle: category.openGraphTitle ?? '',
|
|
57
|
+
openGraphDescription: category.openGraphDescription ?? '',
|
|
44
58
|
openGraphImage: category.openGraphImage
|
|
45
59
|
? { id: createId(), file: category.openGraphImage }
|
|
46
60
|
: null,
|
|
61
|
+
|
|
47
62
|
urlAlias: category.urlAlias,
|
|
48
63
|
language: currentLangOption ?? null,
|
|
49
64
|
};
|
package/src/views/{BlogCategoryForm/BlogCategoryForm.vue → Categories/CategoryForm/CategoryForm.vue}
RENAMED
|
@@ -7,61 +7,88 @@
|
|
|
7
7
|
>
|
|
8
8
|
<template v-slot:content>
|
|
9
9
|
<form novalidate @submit.prevent="submitForm">
|
|
10
|
-
<
|
|
11
|
-
|
|
10
|
+
<template v-if="isCreation">
|
|
11
|
+
<form-field-select
|
|
12
|
+
v-if="isLangSpecific"
|
|
12
13
|
v-model="values.language"
|
|
13
14
|
name="language"
|
|
14
15
|
:label="$t('blog:language')"
|
|
15
16
|
:options="languageOptionList"
|
|
16
17
|
:error="errors.language"
|
|
17
|
-
|
|
18
|
+
/>
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
<form-field
|
|
20
21
|
v-model="values.name"
|
|
21
22
|
name="name"
|
|
22
23
|
:label="$t('blog:name')"
|
|
23
24
|
:error="errors.name"
|
|
24
|
-
|
|
25
|
+
/>
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
<form-field-checkbox
|
|
27
28
|
v-model="values.isDefault"
|
|
28
29
|
name="isDefault"
|
|
29
30
|
:label="$t('blog:defaultCategory')"
|
|
30
31
|
:error="errors.isDefault"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<form-field-url-alias-input
|
|
35
|
+
id="urlAlias"
|
|
36
|
+
v-model="values.urlAlias"
|
|
37
|
+
name="urlAlias"
|
|
38
|
+
:label="$t('blog:URLAlias')"
|
|
39
|
+
:url-template="pagePath"
|
|
40
|
+
:error="errors.urlAlias"
|
|
41
|
+
/>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<template v-else>
|
|
45
|
+
<tab-list
|
|
46
|
+
:tab-list="tabList"
|
|
47
|
+
:selected-tab-id="selectedTabId"
|
|
48
|
+
@tab:update="selectedTabId = $event.tabId"
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
<template v-if="selectedTabId === 'common'">
|
|
52
|
+
<form-field
|
|
53
|
+
v-model="values.name"
|
|
54
|
+
name="name"
|
|
55
|
+
:label="$t('blog:name')"
|
|
56
|
+
:error="errors.name"
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<form-field-checkbox
|
|
60
|
+
v-model="values.isDefault"
|
|
61
|
+
name="isDefault"
|
|
62
|
+
:label="$t('blog:defaultCategory')"
|
|
63
|
+
:error="errors.isDefault"
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<form-field-url-alias-input
|
|
67
|
+
id="urlAlias"
|
|
68
|
+
v-model="values.urlAlias"
|
|
69
|
+
name="urlAlias"
|
|
70
|
+
:label="$t('blog:URLAlias')"
|
|
71
|
+
:url-template="pagePath"
|
|
72
|
+
:error="errors.urlAlias"
|
|
73
|
+
/>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<template v-if="selectedTabId === 'seo'">
|
|
77
|
+
<seo-field-group
|
|
78
|
+
:title="values.pageTitle"
|
|
79
|
+
:title-error-message="errors.pageTitle"
|
|
80
|
+
:description="values.pageDescription"
|
|
81
|
+
:description-error-message="errors.pageDescription"
|
|
82
|
+
:should-display-keywords="false"
|
|
83
|
+
:keywords="values.pageKeywords"
|
|
84
|
+
:keywords-error-message="errors.pageKeywords"
|
|
85
|
+
:image="values.openGraphImage"
|
|
86
|
+
:image-scenario="imageScenario"
|
|
87
|
+
:image-error-message="errors.openGraphImage"
|
|
88
|
+
@change="handleSeoFieldGroupChange"
|
|
89
|
+
/>
|
|
90
|
+
</template>
|
|
91
|
+
</template>
|
|
65
92
|
</form>
|
|
66
93
|
</template>
|
|
67
94
|
|
|
@@ -88,34 +115,44 @@ import {
|
|
|
88
115
|
|
|
89
116
|
import {
|
|
90
117
|
convertRequestErrorToMap,
|
|
118
|
+
getWebsiteOrigin,
|
|
119
|
+
notEmpty,
|
|
91
120
|
Nullable,
|
|
92
121
|
useResource,
|
|
93
122
|
} from '@tager/admin-services';
|
|
94
|
-
import {
|
|
123
|
+
import {
|
|
124
|
+
OptionType,
|
|
125
|
+
FormFooter,
|
|
126
|
+
TagerFormSubmitEvent,
|
|
127
|
+
SeoChangeEvent,
|
|
128
|
+
TabType,
|
|
129
|
+
useTranslation,
|
|
130
|
+
} from '@tager/admin-ui';
|
|
95
131
|
|
|
96
132
|
import {
|
|
97
133
|
createBlogCategory,
|
|
98
134
|
getBlogCategory,
|
|
99
135
|
updateBlogCategory,
|
|
100
|
-
} from '
|
|
101
|
-
import {
|
|
136
|
+
} from '@/services/requests';
|
|
137
|
+
import { Category, FileScenarios, Language } from '@/typings/model';
|
|
102
138
|
import {
|
|
103
139
|
getBlogCategoryFormUrl,
|
|
104
140
|
getBlogCategoryListUrl,
|
|
105
|
-
} from '
|
|
106
|
-
import useModuleConfig from '
|
|
141
|
+
} from '@/constants/paths';
|
|
142
|
+
import { useModuleConfig } from '@/hooks';
|
|
107
143
|
|
|
108
144
|
import {
|
|
109
145
|
CategoryFormValues,
|
|
110
146
|
convertCategoryFormValuesToCreationPayload,
|
|
111
147
|
convertCategoryFormValuesToUpdatePayload,
|
|
112
148
|
convertCategoryToFormValues,
|
|
113
|
-
} from './
|
|
149
|
+
} from './CategoryForm.helpers';
|
|
114
150
|
|
|
115
151
|
export default defineComponent({
|
|
116
152
|
name: 'BlogCategoryForm',
|
|
117
153
|
components: { FormFooter },
|
|
118
154
|
setup(props, context) {
|
|
155
|
+
const { t } = useTranslation(context);
|
|
119
156
|
const categoryId = computed<string>(
|
|
120
157
|
() => context.root.$route.params.categoryId
|
|
121
158
|
);
|
|
@@ -127,22 +164,28 @@ export default defineComponent({
|
|
|
127
164
|
loading: isModuleConfigLoading,
|
|
128
165
|
} = useModuleConfig({ context });
|
|
129
166
|
|
|
130
|
-
const
|
|
131
|
-
() =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
167
|
+
const languageList = computed<Language[]>(
|
|
168
|
+
() => moduleConfig.value?.languages ?? []
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const languageOptionList = computed<OptionType[]>(() =>
|
|
172
|
+
languageList.value.map(({ id, name }) => ({ value: id, label: name }))
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const isLangSpecific = computed<boolean>(
|
|
176
|
+
() => languageOptionList.value.length > 0
|
|
136
177
|
);
|
|
137
178
|
|
|
138
|
-
const
|
|
179
|
+
const imageScenario = computed<string | null>(
|
|
180
|
+
() => moduleConfig.value?.fileScenarios?.openGraph ?? null
|
|
181
|
+
);
|
|
139
182
|
|
|
140
183
|
/** Fetch Category */
|
|
141
184
|
|
|
142
185
|
const [
|
|
143
186
|
fetchCategory,
|
|
144
187
|
{ data: category, loading: isCategoryLoading },
|
|
145
|
-
] = useResource<Nullable<
|
|
188
|
+
] = useResource<Nullable<Category>>({
|
|
146
189
|
fetchResource: () => getBlogCategory(categoryId.value),
|
|
147
190
|
initialValue: null,
|
|
148
191
|
context,
|
|
@@ -150,13 +193,17 @@ export default defineComponent({
|
|
|
150
193
|
});
|
|
151
194
|
|
|
152
195
|
onMounted(() => {
|
|
153
|
-
if (isCreation.value)
|
|
196
|
+
if (isCreation.value) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
154
199
|
|
|
155
200
|
fetchCategory();
|
|
156
201
|
});
|
|
157
202
|
|
|
158
203
|
watch(categoryId, () => {
|
|
159
|
-
if (isCreation.value)
|
|
204
|
+
if (isCreation.value) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
160
207
|
|
|
161
208
|
fetchCategory();
|
|
162
209
|
});
|
|
@@ -182,8 +229,10 @@ export default defineComponent({
|
|
|
182
229
|
values.value
|
|
183
230
|
);
|
|
184
231
|
const updateBody = convertCategoryFormValuesToUpdatePayload(values.value);
|
|
185
|
-
if(!isCreation.value){
|
|
186
|
-
updateBody.language = category.value?.language
|
|
232
|
+
if (!isCreation.value) {
|
|
233
|
+
updateBody.language = category.value?.language
|
|
234
|
+
? category.value?.language
|
|
235
|
+
: null;
|
|
187
236
|
}
|
|
188
237
|
|
|
189
238
|
const requestPromise = isCreation.value
|
|
@@ -242,10 +291,32 @@ export default defineComponent({
|
|
|
242
291
|
);
|
|
243
292
|
|
|
244
293
|
const categoryPagePath = computed<string>(() => {
|
|
245
|
-
|
|
246
|
-
return origin + (category.value?.urlTemplate ?? '');
|
|
294
|
+
return `${getWebsiteOrigin()}${category.value?.urlTemplate ?? ''}`;
|
|
247
295
|
});
|
|
248
296
|
|
|
297
|
+
// SEO
|
|
298
|
+
|
|
299
|
+
function handleSeoFieldGroupChange({
|
|
300
|
+
title,
|
|
301
|
+
description,
|
|
302
|
+
keywords,
|
|
303
|
+
image,
|
|
304
|
+
}: SeoChangeEvent) {
|
|
305
|
+
values.value.pageTitle = title;
|
|
306
|
+
values.value.pageDescription = description;
|
|
307
|
+
values.value.pageKeywords = keywords;
|
|
308
|
+
values.value.openGraphImage = image;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Tabs */
|
|
312
|
+
|
|
313
|
+
const tabList = computed<TabType[]>(() => [
|
|
314
|
+
{ id: 'common', label: t('blog:tabs.common') },
|
|
315
|
+
{ id: 'seo', label: t('blog:tabs.seo') },
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
const selectedTabId = ref<string>(tabList.value[0].id);
|
|
319
|
+
|
|
249
320
|
return {
|
|
250
321
|
isCreation,
|
|
251
322
|
isContentLoading: isContentLoading,
|
|
@@ -257,10 +328,16 @@ export default defineComponent({
|
|
|
257
328
|
categoryListUrl: getBlogCategoryListUrl(),
|
|
258
329
|
submitForm,
|
|
259
330
|
isSubmitting,
|
|
260
|
-
moduleConfig
|
|
331
|
+
moduleConfig,
|
|
332
|
+
|
|
333
|
+
// SEO
|
|
334
|
+
imageScenario,
|
|
335
|
+
handleSeoFieldGroupChange,
|
|
336
|
+
|
|
337
|
+
// Tabs
|
|
338
|
+
tabList,
|
|
339
|
+
selectedTabId,
|
|
261
340
|
};
|
|
262
341
|
},
|
|
263
342
|
});
|
|
264
343
|
</script>
|
|
265
|
-
|
|
266
|
-
<style scoped lang="scss"></style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './CategoryForm.vue';
|