@tager/admin-blog 0.3.24 → 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.
Files changed (62) hide show
  1. package/dist/index.esm.js +546 -278
  2. package/dist/index.umd.js +545 -275
  3. package/package.json +7 -5
  4. package/src/constants/scopes.ts +11 -0
  5. package/src/dev.ts +11 -42
  6. package/src/hooks/index.ts +3 -0
  7. package/src/hooks/useModuleConfig.ts +2 -2
  8. package/src/hooks/useStore.ts +8 -0
  9. package/src/hooks/useUserPermission.ts +16 -0
  10. package/src/hooks/useUserProfile.ts +13 -0
  11. package/src/locales/en.ts +10 -0
  12. package/src/locales/ru.ts +10 -0
  13. package/src/router/index.ts +39 -0
  14. package/src/services/requests.ts +17 -1
  15. package/src/store/index.ts +21 -0
  16. package/src/store/user.ts +109 -0
  17. package/src/typings/common.ts +1 -0
  18. package/src/typings/model.ts +23 -0
  19. package/src/utils/common.ts +23 -0
  20. package/src/views/App.vue +10 -2
  21. package/src/views/Categories/CategoryForm/CategoryForm.helpers.ts +19 -4
  22. package/src/views/Categories/CategoryForm/CategoryForm.vue +139 -66
  23. package/src/views/Categories/CategoryList/CategoryList.helpers.ts +30 -28
  24. package/src/views/Categories/CategoryList/CategoryList.vue +26 -11
  25. package/src/views/Categories/CategoryList/hooks/useAdvancedSearch.ts +1 -1
  26. package/src/views/Home.vue +1 -1
  27. package/src/views/Posts/PostForm/PostForm.helpers.ts +2 -5
  28. package/src/views/Posts/PostForm/PostForm.vue +4 -6
  29. package/src/views/Posts/PostList/PostList.helpers.ts +2 -7
  30. package/src/views/Posts/PostList/PostList.vue +84 -24
  31. package/src/views/Posts/PostList/hooks/useAdvancedSearch.ts +6 -5
  32. package/dist/constants/menu.d.ts +0 -5
  33. package/dist/constants/paths.d.ts +0 -14
  34. package/dist/constants/routes.d.ts +0 -5
  35. package/dist/dev.d.ts +0 -3
  36. package/dist/hooks/index.d.ts +0 -3
  37. package/dist/hooks/useBlogCategoryList.d.ts +0 -6
  38. package/dist/hooks/useBlogPostList.d.ts +0 -6
  39. package/dist/hooks/useModuleConfig.d.ts +0 -6
  40. package/dist/locales/en.d.ts +0 -66
  41. package/dist/locales/ru.d.ts +0 -3
  42. package/dist/main.d.ts +0 -4
  43. package/dist/services/requests.d.ts +0 -67
  44. package/dist/typings/model.d.ts +0 -73
  45. package/dist/views/Categories/CategoryForm/CategoryForm.helpers.d.ts +0 -16
  46. package/dist/views/Categories/CategoryForm/CategoryForm.vue.d.ts +0 -86
  47. package/dist/views/Categories/CategoryForm/index.d.ts +0 -1
  48. package/dist/views/Categories/CategoryList/CategoryList.helpers.d.ts +0 -7
  49. package/dist/views/Categories/CategoryList/CategoryList.vue.d.ts +0 -57
  50. package/dist/views/Categories/CategoryList/hooks/index.d.ts +0 -1
  51. package/dist/views/Categories/CategoryList/hooks/useAdvancedSearch.d.ts +0 -16
  52. package/dist/views/Categories/CategoryList/index.d.ts +0 -1
  53. package/dist/views/Posts/PostForm/PostForm.helpers.d.ts +0 -27
  54. package/dist/views/Posts/PostForm/PostForm.vue.d.ts +0 -9380
  55. package/dist/views/Posts/PostForm/components/TabList/TabList.types.d.ts +0 -5
  56. package/dist/views/Posts/PostForm/components/TabList/index.d.ts +0 -2
  57. package/dist/views/Posts/PostForm/index.d.ts +0 -1
  58. package/dist/views/Posts/PostList/PostList.helpers.d.ts +0 -6
  59. package/dist/views/Posts/PostList/PostList.vue.d.ts +0 -63
  60. package/dist/views/Posts/PostList/hooks/index.d.ts +0 -1
  61. package/dist/views/Posts/PostList/hooks/useAdvancedSearch.d.ts +0 -22
  62. package/dist/views/Posts/PostList/index.d.ts +0 -1
@@ -7,63 +7,88 @@
7
7
  >
8
8
  <template v-slot:content>
9
9
  <form novalidate @submit.prevent="submitForm">
10
- <form-field-select
11
- v-if="isCreation && isLangSpecific"
12
- v-model="values.language"
13
- name="language"
14
- :label="$t('blog:language')"
15
- :options="languageOptionList"
16
- :error="errors.language"
17
- />
18
-
19
- <form-field
20
- v-model="values.name"
21
- name="name"
22
- :label="$t('blog:name')"
23
- :error="errors.name"
24
- />
25
-
26
- <form-field-checkbox
27
- v-model="values.isDefault"
28
- name="isDefault"
29
- :label="$t('blog:defaultCategory')"
30
- :error="errors.isDefault"
31
- />
32
-
33
- <form-field-url-alias-input
34
- v-if="!isCreation"
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
-
43
- <form-field
44
- v-model="values.pageTitle"
45
- name="pageTitle"
46
- :label="$t('blog:pageTitle')"
47
- :error="errors.pageTitle"
48
- />
49
-
50
- <form-field
51
- v-model="values.pageDescription"
52
- name="pageDescription"
53
- :label="$t('blog:pageDescription')"
54
- type="textarea"
55
- :error="errors.pageDescription"
56
- />
57
-
58
- <form-field-file-input
59
- v-model="values.openGraphImage"
60
- :label="$t('blog:openGraphImage')"
61
- name="openGraphImage"
62
- file-type="image"
63
- :image-scenario="
64
- moduleConfig ? moduleConfig.fileScenarios.openGraph : null
65
- "
66
- />
10
+ <template v-if="isCreation">
11
+ <form-field-select
12
+ v-if="isLangSpecific"
13
+ v-model="values.language"
14
+ name="language"
15
+ :label="$t('blog:language')"
16
+ :options="languageOptionList"
17
+ :error="errors.language"
18
+ />
19
+
20
+ <form-field
21
+ v-model="values.name"
22
+ name="name"
23
+ :label="$t('blog:name')"
24
+ :error="errors.name"
25
+ />
26
+
27
+ <form-field-checkbox
28
+ v-model="values.isDefault"
29
+ name="isDefault"
30
+ :label="$t('blog:defaultCategory')"
31
+ :error="errors.isDefault"
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>
67
92
  </form>
68
93
  </template>
69
94
 
@@ -90,22 +115,31 @@ import {
90
115
 
91
116
  import {
92
117
  convertRequestErrorToMap,
118
+ getWebsiteOrigin,
119
+ notEmpty,
93
120
  Nullable,
94
121
  useResource,
95
122
  } from '@tager/admin-services';
96
- import { OptionType, FormFooter, TagerFormSubmitEvent } from '@tager/admin-ui';
123
+ import {
124
+ OptionType,
125
+ FormFooter,
126
+ TagerFormSubmitEvent,
127
+ SeoChangeEvent,
128
+ TabType,
129
+ useTranslation,
130
+ } from '@tager/admin-ui';
97
131
 
98
132
  import {
99
133
  createBlogCategory,
100
134
  getBlogCategory,
101
135
  updateBlogCategory,
102
- } from '../../../services/requests';
103
- import { Category, Language } from '../../../typings/model';
136
+ } from '@/services/requests';
137
+ import { Category, FileScenarios, Language } from '@/typings/model';
104
138
  import {
105
139
  getBlogCategoryFormUrl,
106
140
  getBlogCategoryListUrl,
107
- } from '../../../constants/paths';
108
- import { useModuleConfig } from '../../../hooks';
141
+ } from '@/constants/paths';
142
+ import { useModuleConfig } from '@/hooks';
109
143
 
110
144
  import {
111
145
  CategoryFormValues,
@@ -118,6 +152,7 @@ export default defineComponent({
118
152
  name: 'BlogCategoryForm',
119
153
  components: { FormFooter },
120
154
  setup(props, context) {
155
+ const { t } = useTranslation(context);
121
156
  const categoryId = computed<string>(
122
157
  () => context.root.$route.params.categoryId
123
158
  );
@@ -141,6 +176,10 @@ export default defineComponent({
141
176
  () => languageOptionList.value.length > 0
142
177
  );
143
178
 
179
+ const imageScenario = computed<string | null>(
180
+ () => moduleConfig.value?.fileScenarios?.openGraph ?? null
181
+ );
182
+
144
183
  /** Fetch Category */
145
184
 
146
185
  const [
@@ -154,13 +193,17 @@ export default defineComponent({
154
193
  });
155
194
 
156
195
  onMounted(() => {
157
- if (isCreation.value) return;
196
+ if (isCreation.value) {
197
+ return;
198
+ }
158
199
 
159
200
  fetchCategory();
160
201
  });
161
202
 
162
203
  watch(categoryId, () => {
163
- if (isCreation.value) return;
204
+ if (isCreation.value) {
205
+ return;
206
+ }
164
207
 
165
208
  fetchCategory();
166
209
  });
@@ -248,10 +291,32 @@ export default defineComponent({
248
291
  );
249
292
 
250
293
  const categoryPagePath = computed<string>(() => {
251
- const origin = process.env.VUE_APP_WEBSITE_URL || window.location.origin;
252
- return origin + (category.value?.urlTemplate ?? '');
294
+ return `${getWebsiteOrigin()}${category.value?.urlTemplate ?? ''}`;
253
295
  });
254
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
+
255
320
  return {
256
321
  isCreation,
257
322
  isContentLoading: isContentLoading,
@@ -264,6 +329,14 @@ export default defineComponent({
264
329
  submitForm,
265
330
  isSubmitting,
266
331
  moduleConfig,
332
+
333
+ // SEO
334
+ imageScenario,
335
+ handleSeoFieldGroupChange,
336
+
337
+ // Tabs
338
+ tabList,
339
+ selectedTabId,
267
340
  };
268
341
  },
269
342
  });
@@ -1,13 +1,11 @@
1
1
  import { TFunction } from 'i18next';
2
2
 
3
- import { notEmpty, Nullable } from '@tager/admin-services';
3
+ import { getWebsiteOrigin, notEmpty, Nullable } from '@tager/admin-services';
4
4
  import { ColumnDefinition } from '@tager/admin-ui';
5
5
 
6
- import { Category, Language, ModuleConfig } from '../../../typings/model';
7
- import {
8
- getBlogCategoryFormUrl,
9
- getBlogPostListUrl,
10
- } from '../../../constants/paths';
6
+ import { Category, Language, ModuleConfig } from '@/typings/model';
7
+ import { getBlogCategoryFormUrl, getBlogPostListUrl } from '@/constants/paths';
8
+ import { getNameWithDepth } from '@/utils/common';
11
9
 
12
10
  export function convertCategoryList(
13
11
  categoryList: Category[],
@@ -37,42 +35,46 @@ export function getCategoryTableColumnDefs(
37
35
  ? moduleConfig.languages.length > 0
38
36
  : false;
39
37
 
40
- const COLUMN_DEFS: Array<ColumnDefinition<Category> | null> = [
38
+ const COLUMN_DEFS: (ColumnDefinition<Category> | null)[] = [
41
39
  {
42
- id: 2,
40
+ id: 1,
43
41
  name: t('blog:name'),
44
42
  field: 'name',
45
43
  type: 'name',
46
- format: ({ row }) => {
47
- return {
48
- adminLink: {
49
- url: getBlogCategoryFormUrl({ categoryId: row.id }),
50
- text: row.name,
51
- },
52
- websiteLink: {
53
- url: [
54
- process.env.VUE_APP_WEBSITE_URL || window.location.origin,
55
- row.url,
56
- ].join(''),
57
- text: row.url,
58
- },
59
- paramList: row.isDefault
60
- ? [{ name: t('blog:defaultCategory'), value: t('blog:yes') }]
61
- : [],
62
- };
44
+ format: ({ row }) => ({
45
+ adminLink: {
46
+ url: getBlogCategoryFormUrl({ categoryId: row.id }),
47
+ text: getNameWithDepth(row.name, row.depth),
48
+ },
49
+ paramList: row.isDefault
50
+ ? [{ name: t('blog:defaultCategory'), value: t('blog:yes') }]
51
+ : [],
52
+ }),
53
+ },
54
+ {
55
+ id: 2,
56
+ name: t('pages:path'),
57
+ field: 'path',
58
+ type: 'link',
59
+ format: ({ row }) => ({
60
+ url: `${getWebsiteOrigin()}${row.url}`,
61
+ text: row.url,
62
+ }),
63
+ options: {
64
+ shouldOpenNewTab: true,
63
65
  },
64
66
  },
65
67
  isLangSpecific
66
- ? { id: 4, name: t('blog:language'), field: 'language' }
68
+ ? { id: 3, name: t('blog:language'), field: 'language' }
67
69
  : null,
68
70
  {
69
- id: 3,
71
+ id: 4,
70
72
  name: t('blog:posts'),
71
73
  field: 'linkToPosts',
72
74
  style: { whiteSpace: 'nowrap', width: '130px' },
73
75
  },
74
76
  {
75
- id: 4,
77
+ id: 5,
76
78
  name: t('blog:actions'),
77
79
  field: 'actions',
78
80
  style: { whiteSpace: 'nowrap', width: '205px' },
@@ -1,12 +1,16 @@
1
1
  <template>
2
2
  <page
3
3
  :title="$t('blog:blogCategories')"
4
- :header-buttons="[
5
- {
6
- text: $t('blog:newCategory'),
7
- href: getBlogCategoryFormUrl({ categoryId: 'create' }),
8
- },
9
- ]"
4
+ :header-buttons="
5
+ [
6
+ canViewAdministrators
7
+ ? {
8
+ text: $t('blog:newCategory'),
9
+ href: getBlogCategoryFormUrl({ categoryId: 'create' }),
10
+ }
11
+ : null,
12
+ ].filter(Boolean)
13
+ "
10
14
  >
11
15
  <template v-slot:content>
12
16
  <data-table
@@ -51,6 +55,7 @@
51
55
 
52
56
  <template v-slot:cell(actions)="{ row, rowIndex }">
53
57
  <base-button
58
+ v-if="canViewAdministrators"
54
59
  variant="icon"
55
60
  :title="$t('blog:edit')"
56
61
  :disabled="isDeleting(row.id)"
@@ -78,6 +83,7 @@
78
83
  </base-button>
79
84
 
80
85
  <base-button
86
+ v-if="canViewAdministrators"
81
87
  variant="icon"
82
88
  :title="$t('blog:remove')"
83
89
  :disabled="isDeleting(row.id) || row.postsCount > 0"
@@ -98,14 +104,15 @@ import isEqual from 'lodash.isequal';
98
104
  import { useResourceDelete } from '@tager/admin-services';
99
105
  import { useDataTable } from '@tager/admin-ui';
100
106
 
101
- import { Category, Language } from '../../../typings/model';
107
+ import { Scope } from '@/constants/scopes';
108
+ import { Category, Language } from '@/typings/model';
102
109
  import {
103
110
  deleteBlogCategory,
104
111
  getBlogCategoryList,
105
112
  moveBlogCategory,
106
- } from '../../../services/requests';
107
- import { getBlogCategoryFormUrl } from '../../../constants/paths';
108
- import { useModuleConfig } from '../../../hooks';
113
+ } from '@/services/requests';
114
+ import { getBlogCategoryFormUrl } from '@/constants/paths';
115
+ import { useModuleConfig, useUserPermission } from '@/hooks';
109
116
 
110
117
  import {
111
118
  convertCategoryList,
@@ -117,6 +124,11 @@ import { useAdvancedSearch } from './hooks';
117
124
  export default defineComponent({
118
125
  name: 'BlogCategoryList',
119
126
  setup(props, context) {
127
+ const canViewAdministrators = useUserPermission(
128
+ context,
129
+ Scope.AdministratorsView
130
+ );
131
+
120
132
  /** Fetch module config */
121
133
 
122
134
  const {
@@ -185,8 +197,8 @@ export default defineComponent({
185
197
  });
186
198
 
187
199
  const {
188
- handleResourceDelete: handleCategoryDelete,
189
200
  isDeleting,
201
+ handleResourceDelete: handleCategoryDelete,
190
202
  } = useResourceDelete({
191
203
  deleteResource: deleteBlogCategory,
192
204
  resourceName: 'Post',
@@ -248,6 +260,9 @@ export default defineComponent({
248
260
  languageOptionList,
249
261
  tags,
250
262
  tagRemovalHandler,
263
+
264
+ // Permissions
265
+ canViewAdministrators,
251
266
  };
252
267
  },
253
268
  });
@@ -14,7 +14,7 @@ import {
14
14
  OptionType,
15
15
  } from '@tager/admin-ui';
16
16
 
17
- import { Language } from '../../../../typings/model';
17
+ import { Language } from '@/typings/model';
18
18
 
19
19
  interface Params {
20
20
  context: SetupContext;
@@ -7,7 +7,7 @@ import { defineComponent, ref } from '@vue/composition-api';
7
7
 
8
8
  import { NavigationGridItem } from '@tager/admin-ui';
9
9
 
10
- import { getBlogCategoryListUrl, getBlogPostListUrl } from '../constants/paths';
10
+ import { getBlogCategoryListUrl, getBlogPostListUrl } from '@/constants/paths';
11
11
 
12
12
  export default defineComponent({
13
13
  name: 'Home',
@@ -6,11 +6,8 @@ import {
6
6
  universalFieldUtils,
7
7
  } from '@tager/admin-dynamic-field';
8
8
 
9
- import { Category, PostFull } from '../../../typings/model';
10
- import {
11
- PostCreationPayload,
12
- PostUpdatePayload,
13
- } from '../../../services/requests';
9
+ import { Category, PostFull } from '@/typings/model';
10
+ import { PostCreationPayload, PostUpdatePayload } from '@/services/requests';
14
11
 
15
12
  export interface FormValues {
16
13
  title: string;
@@ -199,12 +199,10 @@ import {
199
199
  createBlogPost,
200
200
  getBlogPost,
201
201
  updateBlogPost,
202
- } from '../../../services/requests';
203
- import { Language, PostFull } from '../../../typings/model';
204
- import {
205
- getBlogPostFormUrl,
206
- getBlogPostListUrl,
207
- } from '../../../constants/paths';
202
+ } from '@/services/requests';
203
+ import { Language, PostFull } from '@/typings/model';
204
+ import { getBlogPostFormUrl, getBlogPostListUrl } from '@/constants/paths';
205
+
208
206
  import {
209
207
  useModuleConfig,
210
208
  useBlogCategoryList,
@@ -3,13 +3,8 @@ import { TFunction } from 'i18next';
3
3
  import { notEmpty, Nullable } from '@tager/admin-services';
4
4
  import { ColumnDefinition } from '@tager/admin-ui';
5
5
 
6
- import {
7
- Category,
8
- ModuleConfig,
9
- Language,
10
- PostShort,
11
- } from '../../../typings/model';
12
- import { getBlogPostFormUrl } from '../../../constants/paths';
6
+ import { Category, ModuleConfig, Language, PostShort } from '@/typings/model';
7
+ import { getBlogPostFormUrl } from '@/constants/paths';
13
8
 
14
9
  export function convertPostList(
15
10
  postList: PostShort[],
@@ -1,12 +1,16 @@
1
1
  <template>
2
2
  <page
3
3
  :title="pageTitle"
4
- :header-buttons="[
5
- {
6
- text: $t('blog:newPost'),
7
- href: getBlogPostFormUrl({ postId: 'create' }),
8
- },
9
- ]"
4
+ :header-buttons="
5
+ [
6
+ canViewAdministrators
7
+ ? {
8
+ text: $t('blog:newPost'),
9
+ href: getBlogPostFormUrl({ postId: 'create' }),
10
+ }
11
+ : null,
12
+ ].filter(Boolean)
13
+ "
10
14
  >
11
15
  <template v-slot:content>
12
16
  <data-table
@@ -75,20 +79,32 @@
75
79
 
76
80
  <template v-slot:cell(actions)="{ row }">
77
81
  <base-button
82
+ v-if="canViewAdministrators"
78
83
  variant="icon"
79
84
  :title="$t('blog:edit')"
80
- :disabled="isDeleting(row.id)"
85
+ :disabled="isBusy(row.id)"
81
86
  :href="getBlogPostFormUrl({ postId: row.id })"
82
87
  >
83
- <svg-icon name="edit"></svg-icon>
88
+ <svg-icon name="edit" />
84
89
  </base-button>
90
+
91
+ <base-button
92
+ variant="icon"
93
+ :title="$t('blog:clone')"
94
+ :disabled="isBusy(row.id)"
95
+ @click="handleResourceClone(row.id)"
96
+ >
97
+ <svg-icon name="contentCopy" />
98
+ </base-button>
99
+
85
100
  <base-button
101
+ v-if="canViewAdministrators"
86
102
  variant="icon"
87
103
  :title="$t('blog:remove')"
88
- :disabled="isDeleting(row.id)"
104
+ :disabled="isBusy(row.id)"
89
105
  @click="handleResourceDelete(row.id)"
90
106
  >
91
- <svg-icon name="delete"></svg-icon>
107
+ <svg-icon name="delete" />
92
108
  </base-button>
93
109
  </template>
94
110
  </data-table>
@@ -101,13 +117,30 @@ import { computed, defineComponent, watch } from '@vue/composition-api';
101
117
  import isEqual from 'lodash.isequal';
102
118
  import pick from 'lodash.pick';
103
119
 
104
- import { ColumnDefinition, useDataTable } from '@tager/admin-ui';
105
- import { Nullable, useResourceDelete } from '@tager/admin-services';
106
-
107
- import { Category, Language, PostShort } from '../../../typings/model';
108
- import { deleteBlogPost, getBlogPostList } from '../../../services/requests';
109
- import { getBlogPostFormUrl } from '../../../constants/paths';
110
- import { useModuleConfig, useBlogCategoryList } from '../../../hooks';
120
+ import {
121
+ ColumnDefinition,
122
+ useDataTable,
123
+ useTranslation,
124
+ } from '@tager/admin-ui';
125
+ import {
126
+ Nullable,
127
+ useResourceClone,
128
+ useResourceDelete,
129
+ } from '@tager/admin-services';
130
+
131
+ import { Category, Language, PostFull, PostShort } from '@/typings/model';
132
+ import {
133
+ clonePost,
134
+ deleteBlogPost,
135
+ getBlogPostList,
136
+ } from '@/services/requests';
137
+ import { getBlogPostFormUrl } from '@/constants/paths';
138
+ import {
139
+ useModuleConfig,
140
+ useBlogCategoryList,
141
+ useUserPermission,
142
+ } from '@/hooks';
143
+ import { Scope } from '@/constants/scopes';
111
144
 
112
145
  import { convertPostList, getPostTableColumnDefs } from './PostList.helpers';
113
146
  import { useAdvancedSearch } from './hooks';
@@ -115,6 +148,13 @@ import { useAdvancedSearch } from './hooks';
115
148
  export default defineComponent({
116
149
  name: 'BlogPostList',
117
150
  setup(props, context) {
151
+ const { t } = useTranslation(context);
152
+
153
+ const canViewAdministrators = useUserPermission(
154
+ context,
155
+ Scope.AdministratorsView
156
+ );
157
+
118
158
  const categoryId = computed<string>(() => {
119
159
  return Array.isArray(context.root.$route.query.category)
120
160
  ? context.root.$route.query.category[0] ?? ''
@@ -224,13 +264,6 @@ export default defineComponent({
224
264
  )
225
265
  );
226
266
 
227
- const { handleResourceDelete, isDeleting } = useResourceDelete({
228
- deleteResource: deleteBlogPost,
229
- resourceName: 'Post',
230
- onSuccess: fetchPostList,
231
- context,
232
- });
233
-
234
267
  const isRowDataLoading = computed<boolean>(
235
268
  () =>
236
269
  isCategoryListLoading.value ||
@@ -238,6 +271,26 @@ export default defineComponent({
238
271
  isModuleConfigLoading.value
239
272
  );
240
273
 
274
+ const { isDeleting, handleResourceDelete } = useResourceDelete({
275
+ deleteResource: deleteBlogPost,
276
+ resourceName: 'Post',
277
+ onSuccess: fetchPostList,
278
+ context,
279
+ });
280
+
281
+ const { isCloning, handleResourceClone } = useResourceClone({
282
+ cloneResource: clonePost,
283
+ confirmMessage: t('blog:cloneConfirm'),
284
+ successMessage: t('blog:cloneSuccess'),
285
+ failureMessage: t('blog:cloneFailure'),
286
+ onSuccessRedirectTo: (data: PostFull) => `/posts/${data.id}`,
287
+ context,
288
+ });
289
+
290
+ function isBusy(postId: number): boolean {
291
+ return isDeleting(postId) || isCloning(postId) || isRowDataLoading.value;
292
+ }
293
+
241
294
  const columnDefs = computed<ColumnDefinition<PostShort>[]>(() =>
242
295
  getPostTableColumnDefs(moduleConfig.value, context.root.$t)
243
296
  );
@@ -268,6 +321,13 @@ export default defineComponent({
268
321
  toDateFilter,
269
322
  tags,
270
323
  tagRemovalHandler,
324
+
325
+ // Permissions
326
+ canViewAdministrators,
327
+
328
+ // Clone
329
+ handleResourceClone,
330
+ isBusy,
271
331
  };
272
332
  },
273
333
  });