@tager/admin-blog 0.3.39 → 0.3.43

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { request, useResource, createId, notEmpty, isNotNullish, useResourceDelete, useResourceClone, convertRequestErrorToMap, urlTranslit, getWebsiteOrigin, i18n } from '@tager/admin-services';
2
2
  import { onMounted, watch, computed, ref, defineComponent } from '@vue/composition-api';
3
- import { getFilterParamAsStringArray, getFilterParamAsString, getFilterParams, useTranslation, useDataTable, ShortCodeConstructor, FormFooter, createTabErrorFinder } from '@tager/admin-ui';
3
+ import { getFilterParamAsStringArray, getFilterParamAsString, getFilterParams, useTranslation, useDataTable, formatDateTime, ShortCodeConstructor, FormFooter, createTabErrorFinder } from '@tager/admin-ui';
4
4
  import { compile } from 'path-to-regexp';
5
5
  import { universalFieldUtils, DynamicField } from '@tager/admin-dynamic-field';
6
6
 
@@ -82,6 +82,12 @@ const EN = {
82
82
  statusPublished: 'Published',
83
83
  statusDraft: 'Draft',
84
84
  statusArchived: 'Archived',
85
+ scheduleArchive: 'Schedule archive',
86
+ schedulePublish: 'Schedule publish',
87
+ archiveAt: 'Archive at',
88
+ publishAt: 'Publish at',
89
+ archiveAtLabel: 'Archive at',
90
+ publishAtLabel: 'Publish at',
85
91
  };
86
92
 
87
93
  const RU = {
@@ -162,6 +168,12 @@ const RU = {
162
168
  statusPublished: 'Опубликован',
163
169
  statusDraft: 'Черновик',
164
170
  statusArchived: 'В архиве',
171
+ scheduleArchive: 'Запланировать перенос в архив',
172
+ schedulePublish: 'Запланировать публикацию',
173
+ archiveAt: 'Перенести в архив в',
174
+ publishAt: 'Опубликовать в',
175
+ archiveAtLabel: 'Перенос в архив',
176
+ publishAtLabel: 'Публикация',
165
177
  };
166
178
 
167
179
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -2685,7 +2697,7 @@ function getNameWithDepth(name, depth) {
2685
2697
  const getStatusOptions = (t) => [
2686
2698
  { label: t('blog:statusPublished'), value: 'PUBLISHED' },
2687
2699
  { label: t('blog:statusDraft'), value: 'DRAFT' },
2688
- { label: t('blog:statusArchived'), value: 'ARCHIVED' },
2700
+ { label: t('blog:statusArchived'), value: 'ARCHIVED' }
2689
2701
  ];
2690
2702
  function convertPostToFormValues(post, languageOptionList, postOptionList, additionalFieldList, statusOptions) {
2691
2703
  var _a, _b, _c;
@@ -2708,6 +2720,8 @@ function convertPostToFormValues(post, languageOptionList, postOptionList, addit
2708
2720
  tags: '',
2709
2721
  additionalFields: additionalFieldList.map((fieldConfig) => universalFieldUtils.createFormField(fieldConfig, null)),
2710
2722
  status: statusOptions[0],
2723
+ statusDateEnabled: false,
2724
+ statusDate: null
2711
2725
  };
2712
2726
  }
2713
2727
  const currentLangOption = languageOptionList.find((option) => option.value === post.language);
@@ -2733,7 +2747,7 @@ function convertPostToFormValues(post, languageOptionList, postOptionList, addit
2733
2747
  urlAlias: post.urlAlias,
2734
2748
  categories: post.categories.map((category) => ({
2735
2749
  value: category.id,
2736
- label: category.name,
2750
+ label: category.name
2737
2751
  })),
2738
2752
  language: currentLangOption !== null && currentLangOption !== void 0 ? currentLangOption : null,
2739
2753
  relatedPosts: selectedPostOptionList,
@@ -2743,6 +2757,8 @@ function convertPostToFormValues(post, languageOptionList, postOptionList, addit
2743
2757
  return universalFieldUtils.createFormField(fieldConfig, postAdditionalField === null || postAdditionalField === void 0 ? void 0 : postAdditionalField.value);
2744
2758
  }),
2745
2759
  status: foundStatus,
2760
+ statusDateEnabled: post.status === 'PUBLISHED' ? !!post.archiveAt : (post.status === 'DRAFT' ? !!post.publishAt : false),
2761
+ statusDate: post.status === 'PUBLISHED' && post.archiveAt ? post.archiveAt.substr(0, 10) : (post.status === 'DRAFT' && post.publishAt ? post.publishAt.substr(0, 10) : null)
2746
2762
  };
2747
2763
  }
2748
2764
  function convertFormValuesToCreationPayload(values) {
@@ -2765,9 +2781,11 @@ function convertFormValuesToCreationPayload(values) {
2765
2781
  tags: values.tags.split(',').filter((tag) => tag.trim().length > 0),
2766
2782
  additionalFields: values.additionalFields.map((field) => ({
2767
2783
  name: field.config.name,
2768
- value: universalFieldUtils.getOutgoingValue(field),
2784
+ value: universalFieldUtils.getOutgoingValue(field)
2769
2785
  })),
2770
2786
  status: values.status.value,
2787
+ archiveAt: values.status.value === 'PUBLISHED' && values.statusDateEnabled && values.statusDate ? values.statusDate : null,
2788
+ publishAt: values.status.value === 'DRAFT' && values.statusDateEnabled && values.statusDate ? values.statusDate : null
2771
2789
  };
2772
2790
  }
2773
2791
  function convertFormValuesToUpdatePayload(values) {
@@ -2778,7 +2796,7 @@ function convertCategoryListToOptions(categoryList, languageId) {
2778
2796
  .filter(({ language }) => (languageId ? language === languageId : true))
2779
2797
  .map(({ id, name, depth }) => ({
2780
2798
  value: id,
2781
- label: getNameWithDepth(name, depth),
2799
+ label: getNameWithDepth(name, depth)
2782
2800
  }));
2783
2801
  }
2784
2802
 
@@ -2818,12 +2836,11 @@ function getPostTableColumnDefs(moduleConfig, t) {
2818
2836
  id: 2,
2819
2837
  name: t('blog:status'),
2820
2838
  field: 'status',
2821
- style: { width: '120px' },
2822
- headStyle: { width: '120px' },
2823
- format: ({ row }) => getStatuses(t)[row.status],
2839
+ style: { width: '140px' },
2840
+ headStyle: { width: '140px' },
2824
2841
  },
2825
2842
  {
2826
- id: 2,
2843
+ id: 3,
2827
2844
  name: t('blog:name'),
2828
2845
  field: 'name',
2829
2846
  type: 'name',
@@ -2842,7 +2859,7 @@ function getPostTableColumnDefs(moduleConfig, t) {
2842
2859
  }),
2843
2860
  },
2844
2861
  {
2845
- id: 3,
2862
+ id: 4,
2846
2863
  name: t('blog:image'),
2847
2864
  field: 'image',
2848
2865
  type: 'image',
@@ -2850,23 +2867,23 @@ function getPostTableColumnDefs(moduleConfig, t) {
2850
2867
  headStyle: { width: '250px' },
2851
2868
  },
2852
2869
  isLangSpecific
2853
- ? { id: 4, name: t('blog:language'), field: 'language' }
2870
+ ? { id: 5, name: t('blog:language'), field: 'language' }
2854
2871
  : null,
2855
2872
  {
2856
- id: 5,
2873
+ id: 6,
2857
2874
  name: t('blog:date'),
2858
2875
  field: 'datetime',
2859
2876
  type: 'datetime',
2860
2877
  options: { ignoreStartOfDayTime: true },
2861
2878
  },
2862
2879
  {
2863
- id: 6,
2880
+ id: 7,
2864
2881
  name: t('blog:categories'),
2865
2882
  field: 'categories',
2866
2883
  format: ({ row }) => row.categories.map((category) => category.name).join(', '),
2867
2884
  },
2868
2885
  {
2869
- id: 7,
2886
+ id: 8,
2870
2887
  name: t('blog:actions'),
2871
2888
  field: 'actions',
2872
2889
  style: { whiteSpace: 'nowrap', width: '120px' },
@@ -3041,10 +3058,10 @@ var script = defineComponent({
3041
3058
  });
3042
3059
  const statusOptionList = computed(() => getStatusOptions(t));
3043
3060
  /** Fetch module config **/
3044
- const { data: moduleConfig, loading: isModuleConfigLoading, } = useFetchModuleConfig({ context });
3061
+ const { data: moduleConfig, loading: isModuleConfigLoading } = useFetchModuleConfig({ context });
3045
3062
  const languageList = computed(() => { var _a, _b; return (_b = (_a = moduleConfig.value) === null || _a === void 0 ? void 0 : _a.languages) !== null && _b !== void 0 ? _b : []; });
3046
3063
  /** Fetch category list **/
3047
- const { data: categoryList, loading: isCategoryListLoading, } = useFetchCategories({ context });
3064
+ const { data: categoryList, loading: isCategoryListLoading } = useFetchCategories({ context });
3048
3065
  const selectedCategory = computed(() => {
3049
3066
  var _a;
3050
3067
  return categoryId.value
@@ -3056,20 +3073,20 @@ var script = defineComponent({
3056
3073
  : context.root.$t('blog:posts');
3057
3074
  });
3058
3075
  /** Advanced search **/
3059
- const { categoryFilter, categoryOptionList, languageFilter, languageOptionList, fromDateFilter, toDateFilter, filterParams, tags, tagRemovalHandler, statusFilter, } = useAdvancedSearch({
3076
+ const { categoryFilter, categoryOptionList, languageFilter, languageOptionList, fromDateFilter, toDateFilter, filterParams, tags, tagRemovalHandler, statusFilter } = useAdvancedSearch({
3060
3077
  context,
3061
3078
  categoryList,
3062
3079
  languageList,
3063
- statusOptionList,
3080
+ statusOptionList
3064
3081
  });
3065
3082
  const isLangSpecific = computed(() => languageOptionList.value.length > 1);
3066
3083
  /** Fetch Post list **/
3067
- const { fetchEntityList: fetchPostList, isLoading: isPostLoading, rowData: postList, errorMessage, searchQuery, handleChange, pageNumber, pageCount, pageSize, } = useDataTable({
3084
+ const { fetchEntityList: fetchPostList, isLoading: isPostLoading, rowData: postList, errorMessage, searchQuery, handleChange, pageNumber, pageCount, pageSize } = useDataTable({
3068
3085
  fetchEntityList: (params) => getPosts(Object.assign({ query: params.searchQuery, pageNumber: params.pageNumber, pageSize: params.pageSize }, filterParams.value)),
3069
3086
  initialValue: [],
3070
3087
  context,
3071
3088
  resourceName: 'Blog post list',
3072
- pageSize: 100,
3089
+ pageSize: 100
3073
3090
  });
3074
3091
  watch(filterParams, () => {
3075
3092
  if (isModuleConfigLoading.value || isCategoryListLoading.value) {
@@ -3092,7 +3109,7 @@ var script = defineComponent({
3092
3109
  deleteResource: deleteBlogPost,
3093
3110
  resourceName: 'Post',
3094
3111
  onSuccess: fetchPostList,
3095
- context,
3112
+ context
3096
3113
  });
3097
3114
  const { isCloning, handleResourceClone } = useResourceClone({
3098
3115
  cloneResource: clonePost,
@@ -3100,13 +3117,15 @@ var script = defineComponent({
3100
3117
  successMessage: t('blog:cloneSuccess'),
3101
3118
  failureMessage: t('blog:cloneFailure'),
3102
3119
  onSuccessRedirectTo: (data) => `/blog/posts/${data.id}`,
3103
- context,
3120
+ context
3104
3121
  });
3105
3122
  function isBusy(postId) {
3106
3123
  return isDeleting(postId) || isCloning(postId) || isRowDataLoading.value;
3107
3124
  }
3108
3125
  const columnDefs = computed(() => getPostTableColumnDefs(moduleConfig.value, context.root.$t));
3109
3126
  return {
3127
+ t,
3128
+ formatDateTime,
3110
3129
  columnDefs,
3111
3130
  rowData: displayedPostList,
3112
3131
  handleResourceDelete,
@@ -3133,11 +3152,12 @@ var script = defineComponent({
3133
3152
  statusOptionList,
3134
3153
  tags,
3135
3154
  tagRemovalHandler,
3155
+ getStatuses,
3136
3156
  // Clone
3137
3157
  handleResourceClone,
3138
- isBusy,
3158
+ isBusy
3139
3159
  };
3140
- },
3160
+ }
3141
3161
  });
3142
3162
 
3143
3163
  function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
@@ -3433,6 +3453,59 @@ var __vue_render__ = function () {
3433
3453
  },
3434
3454
  proxy: true,
3435
3455
  },
3456
+ {
3457
+ key: "cell(status)",
3458
+ fn: function (ref) {
3459
+ var row = ref.row;
3460
+ return [
3461
+ _c("div", { staticClass: "status" }, [
3462
+ _c("span", [
3463
+ _vm._v(_vm._s(_vm.getStatuses(_vm.t)[row.status])),
3464
+ ]),
3465
+ _vm._v(" "),
3466
+ row.status === "PUBLISHED" && row.archiveAt
3467
+ ? _c("span", [
3468
+ _vm._v(
3469
+ "\n " +
3470
+ _vm._s(_vm.t("blog:archiveAtLabel")) +
3471
+ ": "
3472
+ ),
3473
+ _c("i", [
3474
+ _vm._v(
3475
+ _vm._s(
3476
+ _vm.formatDateTime(
3477
+ new Date(row.archiveAt),
3478
+ true
3479
+ )
3480
+ )
3481
+ ),
3482
+ ]),
3483
+ ])
3484
+ : _vm._e(),
3485
+ _vm._v(" "),
3486
+ row.status === "DRAFT" && row.publishAt
3487
+ ? _c("span", [
3488
+ _vm._v(
3489
+ "\n " +
3490
+ _vm._s(_vm.t("blog:publishAtLabel")) +
3491
+ ": "
3492
+ ),
3493
+ _c("i", [
3494
+ _vm._v(
3495
+ _vm._s(
3496
+ _vm.formatDateTime(
3497
+ new Date(row.publishAt),
3498
+ true
3499
+ )
3500
+ )
3501
+ ),
3502
+ ]),
3503
+ ])
3504
+ : _vm._e(),
3505
+ ]),
3506
+ ]
3507
+ },
3508
+ },
3436
3509
  {
3437
3510
  key: "cell(actions)",
3438
3511
  fn: function (ref) {
@@ -3505,11 +3578,11 @@ __vue_render__._withStripped = true;
3505
3578
  /* style */
3506
3579
  const __vue_inject_styles__ = function (inject) {
3507
3580
  if (!inject) return
3508
- inject("data-v-075b6310_0", { source: ".filters[data-v-075b6310] {\n display: flex;\n margin: 0 -10px;\n}\n.filters[data-v-075b6310]:not(:first-child) {\n margin-top: 10px;\n}\n.filters .filter[data-v-075b6310] {\n padding: 10px 10px 0;\n width: 50%;\n margin: 0;\n}\n.date-label[data-v-075b6310] {\n margin-bottom: 0.5rem;\n}\n.date-content[data-v-075b6310] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n column-gap: 10px;\n}\n\n/*# sourceMappingURL=PostList.vue.map */", map: {"version":3,"sources":["/home/runner/work/tager-admin-blog/tager-admin-blog/src/views/Posts/PostList/PostList.vue","PostList.vue"],"names":[],"mappings":"AAsVA;EACA,aAAA;EACA,eAAA;ACrVA;ADuVA;EACA,gBAAA;ACrVA;ADwVA;EACA,oBAAA;EACA,UAAA;EACA,SAAA;ACtVA;AD0VA;EACA,qBAAA;ACvVA;AD0VA;EACA,aAAA;EACA,qCAAA;EACA,gBAAA;ACvVA;;AAEA,uCAAuC","file":"PostList.vue","sourcesContent":["<template>\n <page\n :title=\"pageTitle\"\n :header-buttons=\"[\n {\n text: $t('blog:newPost'),\n href: getBlogPostFormUrl({ postId: 'create' }),\n },\n ]\"\n >\n <template v-slot:content>\n <data-table\n :column-defs=\"columnDefs\"\n :row-data=\"rowData\"\n :loading=\"isRowDataLoading\"\n :error-message=\"errorMessage\"\n :search-query=\"searchQuery\"\n :pagination=\"{\n pageNumber,\n pageCount,\n pageSize,\n disabled: isRowDataLoading,\n }\"\n data-table=\"blog-post\"\n @change=\"handleChange\"\n >\n <template v-slot:filters>\n <advanced-search :tags=\"tags\" @click:tag=\"tagRemovalHandler\">\n <div class=\"filters\">\n <form-field-multi-select\n v-model=\"categoryFilter\"\n :options=\"categoryOptionList\"\n name=\"categoryFilter\"\n :searchable=\"true\"\n :label=\"$t('blog:category')\"\n class=\"filter\"\n />\n\n <form-field-multi-select\n v-if=\"isLangSpecific\"\n v-model=\"languageFilter\"\n :options=\"languageOptionList\"\n name=\"languageFilter\"\n :searchable=\"true\"\n :label=\"$t('blog:language')\"\n class=\"filter\"\n />\n\n <div class=\"filter\">\n <div class=\"date-label\">\n {{ $t('blog:dateOfPublication') }}\n </div>\n\n <div class=\"date-content\">\n <form-field\n v-model=\"fromDateFilter\"\n :label=\"$t('blog:From')\"\n name=\"fromDateFilter\"\n type=\"date\"\n :max=\"toDateFilter\"\n />\n\n <form-field\n v-model=\"toDateFilter\"\n :label=\"$t('blog:To')\"\n name=\"toDateFilter\"\n type=\"date\"\n :min=\"fromDateFilter\"\n />\n </div>\n </div>\n\n <form-field-multi-select\n v-model=\"statusFilter\"\n :options=\"statusOptionList\"\n name=\"statusFilter\"\n :searchable=\"true\"\n :label=\"$t('blog:status')\"\n class=\"filter\"\n />\n </div>\n </advanced-search>\n </template>\n\n <template v-slot:cell(actions)=\"{ row }\">\n <base-button\n variant=\"icon\"\n :title=\"$t('blog:edit')\"\n :disabled=\"isBusy(row.id)\"\n :href=\"getBlogPostFormUrl({ postId: row.id })\"\n >\n <svg-icon name=\"edit\" />\n </base-button>\n\n <base-button\n variant=\"icon\"\n :title=\"$t('blog:clone')\"\n :disabled=\"isBusy(row.id)\"\n @click=\"handleResourceClone(row.id)\"\n >\n <svg-icon name=\"contentCopy\" />\n </base-button>\n\n <base-button\n variant=\"icon\"\n :title=\"$t('blog:remove')\"\n :disabled=\"isBusy(row.id)\"\n @click=\"handleResourceDelete(row.id)\"\n >\n <svg-icon name=\"delete\" />\n </base-button>\n </template>\n </data-table>\n </template>\n </page>\n</template>\n\n<script lang=\"ts\">\nimport { computed, defineComponent, watch } from '@vue/composition-api';\nimport isEqual from 'lodash.isequal';\nimport pick from 'lodash.pick';\n\nimport {\n ColumnDefinition,\n OptionType,\n useDataTable,\n useTranslation,\n} from '@tager/admin-ui';\nimport {\n Nullable,\n useResourceClone,\n useResourceDelete,\n} from '@tager/admin-services';\n\nimport { useFetchModuleConfig, useFetchCategories } from '../../../hooks';\nimport {\n clonePost,\n deleteBlogPost,\n getPosts,\n} from '../../../services/requests';\nimport { getBlogPostFormUrl } from '../../../constants/paths';\nimport {\n Category,\n Language,\n PostFull,\n PostShort,\n} from '../../../typings/model';\nimport { getStatusOptions } from '../PostForm/PostForm.helpers';\n\nimport { convertPostList, getPostTableColumnDefs } from './PostList.helpers';\nimport { useAdvancedSearch } from './hooks';\n\nexport default defineComponent({\n name: 'Posts',\n setup(props, context) {\n const { t } = useTranslation(context);\n\n const categoryId = computed<string>(() => {\n return Array.isArray(context.root.$route.query.category)\n ? context.root.$route.query.category[0] ?? ''\n : context.root.$route.query.category;\n });\n\n const statusOptionList = computed<OptionType[]>(() => getStatusOptions(t));\n\n /** Fetch module config **/\n\n const {\n data: moduleConfig,\n loading: isModuleConfigLoading,\n } = useFetchModuleConfig({ context });\n\n const languageList = computed<Language[]>(\n () => moduleConfig.value?.languages ?? []\n );\n\n /** Fetch category list **/\n\n const {\n data: categoryList,\n loading: isCategoryListLoading,\n } = useFetchCategories({ context });\n\n const selectedCategory = computed<Nullable<Category>>(() =>\n categoryId.value\n ? categoryList.value.find(\n ({ id }) => String(id) === String(categoryId.value)\n ) ?? null\n : null\n );\n\n const pageTitle = computed<string>(() => {\n return selectedCategory.value\n ? `${context.root.$t('blog:postsByCategory')} \"${\n selectedCategory.value.name\n }\"`\n : context.root.$t('blog:posts');\n });\n\n /** Advanced search **/\n\n const {\n categoryFilter,\n categoryOptionList,\n languageFilter,\n languageOptionList,\n fromDateFilter,\n toDateFilter,\n filterParams,\n tags,\n tagRemovalHandler,\n statusFilter,\n } = useAdvancedSearch({\n context,\n categoryList,\n languageList,\n statusOptionList,\n });\n\n const isLangSpecific = computed<boolean>(\n () => languageOptionList.value.length > 1\n );\n\n /** Fetch Post list **/\n\n const {\n fetchEntityList: fetchPostList,\n isLoading: isPostLoading,\n rowData: postList,\n errorMessage,\n searchQuery,\n handleChange,\n pageNumber,\n pageCount,\n pageSize,\n } = useDataTable<PostShort>({\n fetchEntityList: (params) =>\n getPosts({\n query: params.searchQuery,\n pageNumber: params.pageNumber,\n pageSize: params.pageSize,\n ...filterParams.value,\n }),\n initialValue: [],\n context,\n resourceName: 'Blog post list',\n pageSize: 100,\n });\n\n watch(filterParams, () => {\n if (isModuleConfigLoading.value || isCategoryListLoading.value) {\n return;\n }\n\n const newQuery = {\n ...pick(context.root.$route.query, ['query', 'pageNumber']),\n ...filterParams.value,\n };\n\n if (!isEqual(context.root.$route.query, newQuery)) {\n context.root.$router.replace({ query: newQuery });\n fetchPostList();\n }\n });\n\n const displayedPostList = computed<PostShort[]>(() =>\n convertPostList(\n postList.value,\n selectedCategory.value,\n moduleConfig.value?.languages ?? []\n )\n );\n\n const isRowDataLoading = computed<boolean>(\n () =>\n isCategoryListLoading.value ||\n isPostLoading.value ||\n isModuleConfigLoading.value\n );\n\n const { isDeleting, handleResourceDelete } = useResourceDelete({\n deleteResource: deleteBlogPost,\n resourceName: 'Post',\n onSuccess: fetchPostList,\n context,\n });\n\n const { isCloning, handleResourceClone } = useResourceClone({\n cloneResource: clonePost,\n confirmMessage: t('blog:cloneConfirm'),\n successMessage: t('blog:cloneSuccess'),\n failureMessage: t('blog:cloneFailure'),\n onSuccessRedirectTo: (data: PostFull) => `/blog/posts/${data.id}`,\n context,\n });\n\n function isBusy(postId: number): boolean {\n return isDeleting(postId) || isCloning(postId) || isRowDataLoading.value;\n }\n\n const columnDefs = computed<ColumnDefinition<PostShort>[]>(() =>\n getPostTableColumnDefs(moduleConfig.value, context.root.$t)\n );\n\n return {\n columnDefs,\n rowData: displayedPostList,\n handleResourceDelete,\n isDeleting,\n categoryList,\n isRowDataLoading,\n errorMessage,\n getBlogPostFormUrl,\n pageTitle,\n searchQuery,\n handleChange,\n pageNumber,\n pageCount,\n pageSize,\n\n // Advanced search\n categoryFilter,\n categoryOptionList,\n isLangSpecific,\n languageFilter,\n languageOptionList,\n fromDateFilter,\n toDateFilter,\n statusFilter,\n statusOptionList,\n tags,\n tagRemovalHandler,\n\n // Clone\n handleResourceClone,\n isBusy,\n };\n },\n});\n</script>\n\n<style scoped lang=\"scss\">\n.filters {\n display: flex;\n margin: 0 -10px;\n\n &:not(:first-child) {\n margin-top: 10px;\n }\n\n .filter {\n padding: 10px 10px 0;\n width: 50%;\n margin: 0;\n }\n}\n\n.date-label {\n margin-bottom: 0.5rem;\n}\n\n.date-content {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n column-gap: 10px;\n}\n</style>\n",".filters {\n display: flex;\n margin: 0 -10px;\n}\n.filters:not(:first-child) {\n margin-top: 10px;\n}\n.filters .filter {\n padding: 10px 10px 0;\n width: 50%;\n margin: 0;\n}\n\n.date-label {\n margin-bottom: 0.5rem;\n}\n\n.date-content {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n column-gap: 10px;\n}\n\n/*# sourceMappingURL=PostList.vue.map */"]}, media: undefined });
3581
+ inject("data-v-8fa877aa_0", { source: ".filters[data-v-8fa877aa] {\n display: flex;\n margin: 0 -10px;\n}\n.filters[data-v-8fa877aa]:not(:first-child) {\n margin-top: 10px;\n}\n.filters .filter[data-v-8fa877aa] {\n padding: 10px 10px 0;\n width: 50%;\n margin: 0;\n}\n.date-label[data-v-8fa877aa] {\n margin-bottom: 0.5rem;\n}\n.date-content[data-v-8fa877aa] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n column-gap: 10px;\n}\n.status span[data-v-8fa877aa]:first-child {\n display: block;\n}\n.status span[data-v-8fa877aa]:nth-child(2) {\n display: block;\n margin-top: 5px;\n font-size: 0.8rem;\n}\n.status span:nth-child(2) i[data-v-8fa877aa] {\n display: block;\n}\n\n/*# sourceMappingURL=PostList.vue.map */", map: {"version":3,"sources":["/home/runner/work/tager-admin-blog/tager-admin-blog/src/views/Posts/PostList/PostList.vue","PostList.vue"],"names":[],"mappings":"AAsWA;EACA,aAAA;EACA,eAAA;ACrWA;ADuWA;EACA,gBAAA;ACrWA;ADwWA;EACA,oBAAA;EACA,UAAA;EACA,SAAA;ACtWA;AD0WA;EACA,qBAAA;ACvWA;AD0WA;EACA,aAAA;EACA,qCAAA;EACA,gBAAA;ACvWA;AD2WA;EACA,cAAA;ACxWA;AD2WA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;ACzWA;AD2WA;EACA,cAAA;ACzWA;;AAEA,uCAAuC","file":"PostList.vue","sourcesContent":["<template>\n <page\n :title='pageTitle'\n :header-buttons=\"[\n {\n text: $t('blog:newPost'),\n href: getBlogPostFormUrl({ postId: 'create' }),\n },\n ]\"\n >\n <template v-slot:content>\n <data-table\n :column-defs='columnDefs'\n :row-data='rowData'\n :loading='isRowDataLoading'\n :error-message='errorMessage'\n :search-query='searchQuery'\n :pagination='{\n pageNumber,\n pageCount,\n pageSize,\n disabled: isRowDataLoading,\n }'\n data-table='blog-post'\n @change='handleChange'\n >\n <template v-slot:filters>\n <advanced-search :tags='tags' @click:tag='tagRemovalHandler'>\n <div class='filters'>\n <form-field-multi-select\n v-model='categoryFilter'\n :options='categoryOptionList'\n name='categoryFilter'\n :searchable='true'\n :label=\"$t('blog:category')\"\n class='filter'\n />\n\n <form-field-multi-select\n v-if='isLangSpecific'\n v-model='languageFilter'\n :options='languageOptionList'\n name='languageFilter'\n :searchable='true'\n :label=\"$t('blog:language')\"\n class='filter'\n />\n\n <div class='filter'>\n <div class='date-label'>\n {{ $t('blog:dateOfPublication') }}\n </div>\n\n <div class='date-content'>\n <form-field\n v-model='fromDateFilter'\n :label=\"$t('blog:From')\"\n name='fromDateFilter'\n type='date'\n :max='toDateFilter'\n />\n\n <form-field\n v-model='toDateFilter'\n :label=\"$t('blog:To')\"\n name='toDateFilter'\n type='date'\n :min='fromDateFilter'\n />\n </div>\n </div>\n\n <form-field-multi-select\n v-model='statusFilter'\n :options='statusOptionList'\n name='statusFilter'\n :searchable='true'\n :label=\"$t('blog:status')\"\n class='filter'\n />\n </div>\n </advanced-search>\n </template>\n\n <template v-slot:cell(status)='{ row }'>\n <div class='status'>\n <span>{{ getStatuses(t)[row.status] }}</span>\n <span v-if='row.status === \"PUBLISHED\" && row.archiveAt'>\n {{ t('blog:archiveAtLabel') }}: <i>{{ formatDateTime(new Date(row.archiveAt), true) }}</i>\n </span>\n <span v-if='row.status === \"DRAFT\" && row.publishAt'>\n {{ t('blog:publishAtLabel') }}: <i>{{ formatDateTime(new Date(row.publishAt), true) }}</i>\n </span>\n </div>\n </template>\n\n <template v-slot:cell(actions)='{ row }'>\n <base-button\n variant='icon'\n :title=\"$t('blog:edit')\"\n :disabled='isBusy(row.id)'\n :href='getBlogPostFormUrl({ postId: row.id })'\n >\n <svg-icon name='edit' />\n </base-button>\n\n <base-button\n variant='icon'\n :title=\"$t('blog:clone')\"\n :disabled='isBusy(row.id)'\n @click='handleResourceClone(row.id)'\n >\n <svg-icon name='contentCopy' />\n </base-button>\n\n <base-button\n variant='icon'\n :title=\"$t('blog:remove')\"\n :disabled='isBusy(row.id)'\n @click='handleResourceDelete(row.id)'\n >\n <svg-icon name='delete' />\n </base-button>\n </template>\n </data-table>\n </template>\n </page>\n</template>\n\n<script lang='ts'>\nimport { computed, defineComponent, watch } from '@vue/composition-api';\nimport isEqual from 'lodash.isequal';\nimport pick from 'lodash.pick';\n\nimport {\n ColumnDefinition,\n OptionType,\n useDataTable,\n useTranslation,\n formatDateTime\n} from '@tager/admin-ui';\nimport {\n Nullable,\n useResourceClone,\n useResourceDelete\n} from '@tager/admin-services';\n\nimport { useFetchModuleConfig, useFetchCategories } from '../../../hooks';\nimport {\n clonePost,\n deleteBlogPost,\n getPosts\n} from '../../../services/requests';\nimport { getBlogPostFormUrl } from '../../../constants/paths';\nimport {\n Category,\n Language,\n PostFull,\n PostShort\n} from '../../../typings/model';\nimport { getStatusOptions } from '../PostForm/PostForm.helpers';\n\nimport { convertPostList, getPostTableColumnDefs, getStatuses } from './PostList.helpers';\nimport { useAdvancedSearch } from './hooks';\n\nexport default defineComponent({\n name: 'Posts',\n setup(props, context) {\n const { t } = useTranslation(context);\n\n const categoryId = computed<string>(() => {\n return Array.isArray(context.root.$route.query.category)\n ? context.root.$route.query.category[0] ?? ''\n : context.root.$route.query.category;\n });\n\n const statusOptionList = computed<OptionType[]>(() => getStatusOptions(t));\n\n /** Fetch module config **/\n\n const {\n data: moduleConfig,\n loading: isModuleConfigLoading\n } = useFetchModuleConfig({ context });\n\n const languageList = computed<Language[]>(\n () => moduleConfig.value?.languages ?? []\n );\n\n /** Fetch category list **/\n\n const {\n data: categoryList,\n loading: isCategoryListLoading\n } = useFetchCategories({ context });\n\n const selectedCategory = computed<Nullable<Category>>(() =>\n categoryId.value\n ? categoryList.value.find(\n ({ id }) => String(id) === String(categoryId.value)\n ) ?? null\n : null\n );\n\n const pageTitle = computed<string>(() => {\n return selectedCategory.value\n ? `${context.root.$t('blog:postsByCategory')} \"${\n selectedCategory.value.name\n }\"`\n : context.root.$t('blog:posts');\n });\n\n /** Advanced search **/\n\n const {\n categoryFilter,\n categoryOptionList,\n languageFilter,\n languageOptionList,\n fromDateFilter,\n toDateFilter,\n filterParams,\n tags,\n tagRemovalHandler,\n statusFilter\n } = useAdvancedSearch({\n context,\n categoryList,\n languageList,\n statusOptionList\n });\n\n const isLangSpecific = computed<boolean>(\n () => languageOptionList.value.length > 1\n );\n\n /** Fetch Post list **/\n\n const {\n fetchEntityList: fetchPostList,\n isLoading: isPostLoading,\n rowData: postList,\n errorMessage,\n searchQuery,\n handleChange,\n pageNumber,\n pageCount,\n pageSize\n } = useDataTable<PostShort>({\n fetchEntityList: (params) =>\n getPosts({\n query: params.searchQuery,\n pageNumber: params.pageNumber,\n pageSize: params.pageSize,\n ...filterParams.value\n }),\n initialValue: [],\n context,\n resourceName: 'Blog post list',\n pageSize: 100\n });\n\n watch(filterParams, () => {\n if (isModuleConfigLoading.value || isCategoryListLoading.value) {\n return;\n }\n\n const newQuery = {\n ...pick(context.root.$route.query, ['query', 'pageNumber']),\n ...filterParams.value\n };\n\n if (!isEqual(context.root.$route.query, newQuery)) {\n context.root.$router.replace({ query: newQuery });\n fetchPostList();\n }\n });\n\n const displayedPostList = computed<PostShort[]>(() =>\n convertPostList(\n postList.value,\n selectedCategory.value,\n moduleConfig.value?.languages ?? []\n )\n );\n\n const isRowDataLoading = computed<boolean>(\n () =>\n isCategoryListLoading.value ||\n isPostLoading.value ||\n isModuleConfigLoading.value\n );\n\n const { isDeleting, handleResourceDelete } = useResourceDelete({\n deleteResource: deleteBlogPost,\n resourceName: 'Post',\n onSuccess: fetchPostList,\n context\n });\n\n const { isCloning, handleResourceClone } = useResourceClone({\n cloneResource: clonePost,\n confirmMessage: t('blog:cloneConfirm'),\n successMessage: t('blog:cloneSuccess'),\n failureMessage: t('blog:cloneFailure'),\n onSuccessRedirectTo: (data: PostFull) => `/blog/posts/${data.id}`,\n context\n });\n\n function isBusy(postId: number): boolean {\n return isDeleting(postId) || isCloning(postId) || isRowDataLoading.value;\n }\n\n const columnDefs = computed<ColumnDefinition<PostShort>[]>(() =>\n getPostTableColumnDefs(moduleConfig.value, context.root.$t)\n );\n\n return {\n t,\n formatDateTime,\n columnDefs,\n rowData: displayedPostList,\n handleResourceDelete,\n isDeleting,\n categoryList,\n isRowDataLoading,\n errorMessage,\n getBlogPostFormUrl,\n pageTitle,\n searchQuery,\n handleChange,\n pageNumber,\n pageCount,\n pageSize,\n\n // Advanced search\n categoryFilter,\n categoryOptionList,\n isLangSpecific,\n languageFilter,\n languageOptionList,\n fromDateFilter,\n toDateFilter,\n statusFilter,\n statusOptionList,\n tags,\n tagRemovalHandler,\n getStatuses,\n\n // Clone\n handleResourceClone,\n isBusy\n };\n }\n});\n</script>\n\n<style scoped lang='scss'>\n.filters {\n display: flex;\n margin: 0 -10px;\n\n &:not(:first-child) {\n margin-top: 10px;\n }\n\n .filter {\n padding: 10px 10px 0;\n width: 50%;\n margin: 0;\n }\n}\n\n.date-label {\n margin-bottom: 0.5rem;\n}\n\n.date-content {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n column-gap: 10px;\n}\n\n.status {\n span:first-child {\n display: block;\n }\n\n span:nth-child(2) {\n display: block;\n margin-top: 5px;\n font-size: 0.8rem;\n\n i {\n display: block;\n }\n }\n;\n}\n</style>\n",".filters {\n display: flex;\n margin: 0 -10px;\n}\n.filters:not(:first-child) {\n margin-top: 10px;\n}\n.filters .filter {\n padding: 10px 10px 0;\n width: 50%;\n margin: 0;\n}\n\n.date-label {\n margin-bottom: 0.5rem;\n}\n\n.date-content {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n column-gap: 10px;\n}\n\n.status span:first-child {\n display: block;\n}\n.status span:nth-child(2) {\n display: block;\n margin-top: 5px;\n font-size: 0.8rem;\n}\n.status span:nth-child(2) i {\n display: block;\n}\n\n/*# sourceMappingURL=PostList.vue.map */"]}, media: undefined });
3509
3582
 
3510
3583
  };
3511
3584
  /* scoped */
3512
- const __vue_scope_id__ = "data-v-075b6310";
3585
+ const __vue_scope_id__ = "data-v-8fa877aa";
3513
3586
  /* module identifier */
3514
3587
  const __vue_module_identifier__ = undefined;
3515
3588
  /* functional template */
@@ -3543,21 +3616,21 @@ var script$1 = defineComponent({
3543
3616
  const urlAliasChanged = ref(false);
3544
3617
  const statusOptions = computed(() => getStatusOptions(context.root.$t));
3545
3618
  /** Fetch module config **/
3546
- const { data: moduleConfig, loading: isModuleConfigLoading, } = useFetchModuleConfig({ context });
3619
+ const { data: moduleConfig, loading: isModuleConfigLoading } = useFetchModuleConfig({ context });
3547
3620
  const languageList = computed(() => { var _a, _b; return (_b = (_a = moduleConfig.value) === null || _a === void 0 ? void 0 : _a.languages) !== null && _b !== void 0 ? _b : []; });
3548
3621
  const languageOptionList = computed(() => languageList.value.map(({ id, name }) => ({
3549
3622
  value: id,
3550
- label: name,
3623
+ label: name
3551
3624
  })));
3552
3625
  const isLangSpecific = computed(() => languageOptionList.value.length > 0);
3553
3626
  /** Fetch category list */
3554
- const { data: categoryList, loading: isCategoryListLoading, } = useFetchCategories({ context });
3627
+ const { data: categoryList, loading: isCategoryListLoading } = useFetchCategories({ context });
3555
3628
  /** Fetch Post */
3556
3629
  const [fetchPost, { data: post, loading: isPostLoading }] = useResource({
3557
3630
  fetchResource: () => getPost(postId.value),
3558
3631
  initialValue: null,
3559
3632
  context,
3560
- resourceName: 'Post',
3633
+ resourceName: 'Post'
3561
3634
  });
3562
3635
  onMounted(() => {
3563
3636
  if (isCreation.value)
@@ -3571,13 +3644,13 @@ var script$1 = defineComponent({
3571
3644
  });
3572
3645
  /** Fetch post list */
3573
3646
  const { data: postList, loading: isPostListLoading } = useFetchPosts({
3574
- context,
3647
+ context
3575
3648
  });
3576
3649
  const postOptionList = computed(() => postList.value
3577
3650
  .filter((relatedPost) => { var _a; return relatedPost.id !== ((_a = post.value) === null || _a === void 0 ? void 0 : _a.id); })
3578
3651
  .map((post) => ({
3579
3652
  value: post.id,
3580
- label: post.title,
3653
+ label: post.title
3581
3654
  })));
3582
3655
  /** Form State **/
3583
3656
  const isSubmitting = ref(false);
@@ -3617,7 +3690,7 @@ var script$1 = defineComponent({
3617
3690
  title: context.root.$t('blog:success'),
3618
3691
  body: isCreation.value
3619
3692
  ? context.root.$t('blog:blogPostWasSuccessfullyCreated')
3620
- : context.root.$t('blog:blogPostWasSuccessfullyUpdated'),
3693
+ : context.root.$t('blog:blogPostWasSuccessfullyUpdated')
3621
3694
  });
3622
3695
  })
3623
3696
  .catch((error) => {
@@ -3628,7 +3701,7 @@ var script$1 = defineComponent({
3628
3701
  title: context.root.$t('blog:error'),
3629
3702
  body: isCreation.value
3630
3703
  ? context.root.$t('blog:blogPostCreationWasFailed')
3631
- : context.root.$t('blog:blogPostUpdateWasFailed'),
3704
+ : context.root.$t('blog:blogPostUpdateWasFailed')
3632
3705
  });
3633
3706
  })
3634
3707
  .finally(() => {
@@ -3681,24 +3754,24 @@ var script$1 = defineComponent({
3681
3754
  'urlAlias',
3682
3755
  'excerpt',
3683
3756
  'body',
3684
- 'date',
3685
- ]),
3757
+ 'date'
3758
+ ])
3686
3759
  },
3687
3760
  {
3688
3761
  id: 'images',
3689
3762
  label: context.root.$t('blog:images'),
3690
- hasErrors: hasErrors(['coverImage', 'image']),
3763
+ hasErrors: hasErrors(['coverImage', 'image'])
3691
3764
  },
3692
3765
  {
3693
3766
  id: 'relations',
3694
3767
  label: context.root.$t('blog:relations'),
3695
- hasErrors: hasErrors(['categories', 'relatedPosts', 'tags']),
3768
+ hasErrors: hasErrors(['categories', 'relatedPosts', 'tags'])
3696
3769
  },
3697
3770
  values.value.additionalFields.length > 0
3698
3771
  ? {
3699
3772
  id: 'additional',
3700
3773
  label: context.root.$t('blog:additionalFields'),
3701
- hasErrors: hasErrors([]),
3774
+ hasErrors: hasErrors([])
3702
3775
  }
3703
3776
  : null,
3704
3777
  {
@@ -3707,9 +3780,9 @@ var script$1 = defineComponent({
3707
3780
  hasErrors: hasErrors([
3708
3781
  'pageTitle',
3709
3782
  'pageDescription',
3710
- 'openGraphImage',
3711
- ]),
3712
- },
3783
+ 'openGraphImage'
3784
+ ])
3785
+ }
3713
3786
  ].filter(isNotNullish);
3714
3787
  });
3715
3788
  const selectedTabId = ref(tabList.value[0].id);
@@ -3718,17 +3791,17 @@ var script$1 = defineComponent({
3718
3791
  ? {
3719
3792
  text: context.root.$t('blog:viewPost'),
3720
3793
  href: process.env.VUE_APP_WEBSITE_URL + post.value.url,
3721
- target: '_blank',
3794
+ target: '_blank'
3722
3795
  }
3723
- : null,
3796
+ : null
3724
3797
  ].filter(isNotNullish));
3725
- function handleSeoFieldGroupChange({ title, description, image, }) {
3798
+ function handleSeoFieldGroupChange({ title, description, image }) {
3726
3799
  values.value.pageTitle = title;
3727
3800
  values.value.pageDescription = description;
3728
3801
  values.value.openGraphImage = image;
3729
3802
  }
3730
3803
  function handleTitleChange(value) {
3731
- if (urlAliasChanged.value === false) {
3804
+ if (isCreation.value && urlAliasChanged.value === false) {
3732
3805
  values.value.urlAlias = urlTranslit(value);
3733
3806
  }
3734
3807
  }
@@ -3758,7 +3831,7 @@ var script$1 = defineComponent({
3758
3831
  }
3759
3832
  const newSelectedOption = {
3760
3833
  value: foundSelectedCategory.id,
3761
- label: foundSelectedCategory.name,
3834
+ label: foundSelectedCategory.name
3762
3835
  };
3763
3836
  if (!newSelectedOptions.some(({ value }) => value === newSelectedOption.value)) {
3764
3837
  newSelectedOptions.push(newSelectedOption);
@@ -3795,9 +3868,9 @@ var script$1 = defineComponent({
3795
3868
  handleAliasChange,
3796
3869
  urlAliasTemplate,
3797
3870
  handleCategoriesChange,
3798
- statusOptions,
3871
+ statusOptions
3799
3872
  };
3800
- },
3873
+ }
3801
3874
  });
3802
3875
 
3803
3876
  /* script */
@@ -3852,23 +3925,6 @@ var __vue_render__$1 = function () {
3852
3925
  _vm._v(" "),
3853
3926
  _vm.selectedTabId === "common"
3854
3927
  ? [
3855
- _c("form-field-select", {
3856
- attrs: {
3857
- name: "status",
3858
- error: _vm.errors.status,
3859
- label: _vm.$t("blog:status"),
3860
- "no-options-message": _vm.$t("blog:noTemplates"),
3861
- options: _vm.statusOptions,
3862
- },
3863
- model: {
3864
- value: _vm.values.status,
3865
- callback: function ($$v) {
3866
- _vm.$set(_vm.values, "status", $$v);
3867
- },
3868
- expression: "values.status",
3869
- },
3870
- }),
3871
- _vm._v(" "),
3872
3928
  _vm.isCreation && _vm.isLangSpecific
3873
3929
  ? _c("form-field-select", {
3874
3930
  attrs: {
@@ -3921,6 +3977,114 @@ var __vue_render__$1 = function () {
3921
3977
  },
3922
3978
  }),
3923
3979
  _vm._v(" "),
3980
+ _c("div", { staticClass: "status" }, [
3981
+ _c(
3982
+ "div",
3983
+ { staticClass: "status-value" },
3984
+ [
3985
+ _c("form-field-select", {
3986
+ attrs: {
3987
+ name: "status",
3988
+ error: _vm.errors.status,
3989
+ label: _vm.$t("blog:status"),
3990
+ "no-options-message": _vm.$t(
3991
+ "blog:noTemplates"
3992
+ ),
3993
+ options: _vm.statusOptions,
3994
+ },
3995
+ model: {
3996
+ value: _vm.values.status,
3997
+ callback: function ($$v) {
3998
+ _vm.$set(_vm.values, "status", $$v);
3999
+ },
4000
+ expression: "values.status",
4001
+ },
4002
+ }),
4003
+ ],
4004
+ 1
4005
+ ),
4006
+ _vm._v(" "),
4007
+ _vm.values.status.value === "PUBLISHED" ||
4008
+ _vm.values.status.value === "DRAFT"
4009
+ ? _c(
4010
+ "div",
4011
+ { staticClass: "status-checkbox" },
4012
+ [
4013
+ _c("form-field-checkbox", {
4014
+ attrs: {
4015
+ name: "statusDateEnabled",
4016
+ label: _vm.$t(
4017
+ _vm.values.status.value === "PUBLISHED"
4018
+ ? "blog:scheduleArchive"
4019
+ : "blog:schedulePublish"
4020
+ ),
4021
+ error: _vm.errors.status,
4022
+ },
4023
+ model: {
4024
+ value: _vm.values.statusDateEnabled,
4025
+ callback: function ($$v) {
4026
+ _vm.$set(
4027
+ _vm.values,
4028
+ "statusDateEnabled",
4029
+ $$v
4030
+ );
4031
+ },
4032
+ expression: "values.statusDateEnabled",
4033
+ },
4034
+ }),
4035
+ ],
4036
+ 1
4037
+ )
4038
+ : _vm._e(),
4039
+ _vm._v(" "),
4040
+ _vm.values.statusDateEnabled &&
4041
+ (_vm.values.status.value === "PUBLISHED" ||
4042
+ _vm.values.status.value === "DRAFT")
4043
+ ? _c(
4044
+ "div",
4045
+ { staticClass: "status-date" },
4046
+ [
4047
+ _c("form-field", {
4048
+ attrs: {
4049
+ name: "statusDate",
4050
+ type: "date",
4051
+ label: _vm.$t(
4052
+ _vm.values.status.value === "PUBLISHED"
4053
+ ? "blog:archiveAt"
4054
+ : "blog:publishAt"
4055
+ ),
4056
+ error: _vm.errors.statusDate,
4057
+ },
4058
+ model: {
4059
+ value: _vm.values.statusDate,
4060
+ callback: function ($$v) {
4061
+ _vm.$set(_vm.values, "statusDate", $$v);
4062
+ },
4063
+ expression: "values.statusDate",
4064
+ },
4065
+ }),
4066
+ ],
4067
+ 1
4068
+ )
4069
+ : _vm._e(),
4070
+ ]),
4071
+ _vm._v(" "),
4072
+ _c("form-field", {
4073
+ attrs: {
4074
+ name: "datetime",
4075
+ label: _vm.$t("blog:date"),
4076
+ type: "date",
4077
+ error: _vm.errors.datetime,
4078
+ },
4079
+ model: {
4080
+ value: _vm.values.datetime,
4081
+ callback: function ($$v) {
4082
+ _vm.$set(_vm.values, "datetime", $$v);
4083
+ },
4084
+ expression: "values.datetime",
4085
+ },
4086
+ }),
4087
+ _vm._v(" "),
3924
4088
  _c("form-field", {
3925
4089
  attrs: {
3926
4090
  name: "excerpt",
@@ -3961,22 +4125,6 @@ var __vue_render__$1 = function () {
3961
4125
  : [],
3962
4126
  },
3963
4127
  }),
3964
- _vm._v(" "),
3965
- _c("form-field", {
3966
- attrs: {
3967
- name: "datetime",
3968
- label: _vm.$t("blog:date"),
3969
- type: "date",
3970
- error: _vm.errors.datetime,
3971
- },
3972
- model: {
3973
- value: _vm.values.datetime,
3974
- callback: function ($$v) {
3975
- _vm.$set(_vm.values, "datetime", $$v);
3976
- },
3977
- expression: "values.datetime",
3978
- },
3979
- }),
3980
4128
  ]
3981
4129
  : _vm._e(),
3982
4130
  _vm._v(" "),
@@ -4147,15 +4295,17 @@ var __vue_staticRenderFns__$1 = [];
4147
4295
  __vue_render__$1._withStripped = true;
4148
4296
 
4149
4297
  /* style */
4150
- const __vue_inject_styles__$1 = undefined;
4298
+ const __vue_inject_styles__$1 = function (inject) {
4299
+ if (!inject) return
4300
+ inject("data-v-50f3750d_0", { source: ".status[data-v-50f3750d] {\n display: flex;\n align-items: flex-end;\n}\n.status-value[data-v-50f3750d] {\n flex-basis: 50%;\n}\n.status-checkbox[data-v-50f3750d] {\n margin-left: 20px;\n padding-bottom: 8px;\n}\n.status-date[data-v-50f3750d] {\n margin-left: 20px;\n flex: 1 1 1px;\n}\n\n/*# sourceMappingURL=PostForm.vue.map */", map: {"version":3,"sources":["/home/runner/work/tager-admin-blog/tager-admin-blog/src/views/Posts/PostForm/PostForm.vue","PostForm.vue"],"names":[],"mappings":"AA2nBA;EACA,aAAA;EACA,qBAAA;AC1nBA;AD6nBA;EACA,eAAA;AC1nBA;AD6nBA;EACA,iBAAA;EACA,mBAAA;AC1nBA;AD6nBA;EACA,iBAAA;EACA,aAAA;AC1nBA;;AAEA,uCAAuC","file":"PostForm.vue","sourcesContent":["<template>\n <page\n :title=\"isCreation ? $t('blog:createPost') : $t('blog:updatePost')\"\n :is-content-loading='isContentLoading'\n :header-buttons='headerButtonList'\n :footer='{\n backHref: postListUrl,\n onSubmit: submitForm,\n isSubmitting: isSubmitting,\n }'\n >\n <template v-slot:content>\n <form novalidate @submit.prevent='submitForm'>\n <tab-list\n :tab-list='tabList'\n :selected-tab-id='selectedTabId'\n @tab:update='selectedTabId = $event.tabId'\n />\n\n <template v-if=\"selectedTabId === 'common'\">\n\n <form-field-select\n v-if='isCreation && isLangSpecific'\n v-model='values.language'\n name='language'\n :label=\"$t('blog:language')\"\n :options='languageOptionList'\n :error='errors.language'\n />\n\n <form-field\n v-model='values.title'\n name='title'\n :label=\"$t('blog:title')\"\n :error='errors.title'\n @input='handleTitleChange'\n />\n\n <form-field-url-alias-input\n id='urlAlias'\n v-model='values.urlAlias'\n name='urlAlias'\n :label=\"$t('blog:link')\"\n :url-template='urlAliasTemplate'\n :error='errors.urlAlias'\n @change='handleAliasChange'\n />\n\n <div class='status'>\n <div class='status-value'>\n <form-field-select\n v-model='values.status'\n name='status'\n :error='errors.status'\n :label=\"$t('blog:status')\"\n :no-options-message=\"$t('blog:noTemplates')\"\n :options='statusOptions'\n />\n </div>\n <div class='status-checkbox' v-if='values.status.value === \"PUBLISHED\" || values.status.value === \"DRAFT\"'>\n <form-field-checkbox\n v-model='values.statusDateEnabled'\n name='statusDateEnabled'\n :label=\"$t(values.status.value === 'PUBLISHED' ? 'blog:scheduleArchive' : 'blog:schedulePublish')\"\n :error='errors.status'\n />\n </div>\n <div class='status-date'\n v-if='values.statusDateEnabled && (values.status.value === \"PUBLISHED\" || values.status.value === \"DRAFT\")'>\n <form-field\n v-model='values.statusDate'\n name='statusDate'\n type='date'\n :label=\"$t(values.status.value === 'PUBLISHED' ? 'blog:archiveAt' : 'blog:publishAt')\"\n :error='errors.statusDate'\n />\n </div>\n </div>\n\n <form-field\n v-model='values.datetime'\n name='datetime'\n :label=\"$t('blog:date')\"\n type='date'\n :error='errors.datetime'\n />\n\n <form-field\n v-model='values.excerpt'\n name='excerpt'\n :label=\"$t('blog:excerpt')\"\n type='textarea'\n :error='errors.excerpt'\n />\n\n <form-field-rich-text-input\n v-model='values.body'\n name='body'\n :label=\"$t('blog:body')\"\n :error='errors.body'\n :get-upload-adapter-options='getUploadAdapterOptions'\n />\n\n <ShortCodeConstructor\n :short-code-config-list='\n moduleConfig ? moduleConfig.shortcodes : []\n '\n />\n </template>\n\n <template v-if=\"selectedTabId === 'images'\">\n <form-field-file-input\n v-model='values.coverImage'\n :label=\"$t('blog:coverImage')\"\n name='coverImage'\n file-type='image'\n :scenario='moduleConfig ? moduleConfig.fileScenarios.cover : null'\n />\n\n <form-field-file-input\n v-model='values.image'\n name='image'\n :label=\"$t('blog:innerImage')\"\n file-type='image'\n :scenario='moduleConfig ? moduleConfig.fileScenarios.image : null'\n />\n\n <form-field-file-input\n v-model='values.imageMobile'\n name='imageMobile'\n :label=\"$t('blog:innerImageMobile')\"\n file-type='image'\n :scenario='\n moduleConfig ? moduleConfig.fileScenarios.imageMobile : null\n '\n />\n </template>\n\n <template v-if=\"selectedTabId === 'relations'\">\n <form-field\n v-model='values.tags'\n name='tags'\n :label=\"$t('blog:tags')\"\n :error='errors.tags'\n />\n\n <form-field-multi-select\n :selected-options='values.categories'\n name='categories'\n :label=\"$t('blog:categories')\"\n :options='categoryOptionList'\n :error='errors.categories'\n @change='handleCategoriesChange'\n />\n\n <form-field-multi-select\n v-model='values.relatedPosts'\n name='relatedPosts'\n :label=\"$t('blog:relatedPosts')\"\n :options='postOptionList'\n :error='errors.relatedPosts'\n :searchable='true'\n />\n </template>\n\n <template v-if=\"selectedTabId === 'additional'\">\n <DynamicField\n v-for='field of values.additionalFields'\n :key='field.name'\n :field='field'\n />\n </template>\n\n <template v-if=\"selectedTabId === 'seo'\">\n <seo-field-group\n :title='values.pageTitle'\n :title-error-message='errors.pageTitle'\n :title-label=\"$t('blog:pageTitle')\"\n :description='values.pageDescription'\n :description-error-message='errors.pageDescription'\n :description-label=\"$t('blog:pageDescription')\"\n :image='values.openGraphImage'\n :image-error-message='errors.openGraphImage'\n :image-label=\"$t('blog:openGraphImage')\"\n :image-scenario='\n moduleConfig ? moduleConfig.fileScenarios.openGraph : null\n '\n @change='handleSeoFieldGroupChange'\n />\n </template>\n </form>\n </template>\n\n <template v-slot:footer>\n <FormFooter\n :back-href='postListUrl'\n :on-submit='submitForm'\n :is-submitting='isSubmitting'\n :is-creation='isCreation'\n :can-create-another='isCreation'\n />\n </template>\n </page>\n</template>\n\n<script lang='ts'>\nimport {\n computed,\n defineComponent,\n onMounted,\n ref,\n watch\n} from '@vue/composition-api';\n\nimport {\n convertRequestErrorToMap,\n isNotNullish,\n Nullable,\n useResource,\n urlTranslit\n} from '@tager/admin-services';\nimport {\n createTabErrorFinder,\n OptionType,\n TabType,\n ShortCodeConstructor,\n SeoChangeEvent,\n FormFooter,\n TagerFormSubmitEvent\n} from '@tager/admin-ui';\nimport { DynamicField } from '@tager/admin-dynamic-field';\n\nimport {\n getBlogPostFormUrl,\n getBlogPostListUrl\n} from '../../../constants/paths';\nimport {\n createBlogPost,\n getPost,\n updateBlogPost\n} from '../../../services/requests';\nimport { Language, PostFull } from '../../../typings/model';\nimport {\n useFetchModuleConfig,\n useFetchCategories,\n useFetchPosts\n} from '../../../hooks';\n\nimport {\n convertCategoryListToOptions,\n convertFormValuesToCreationPayload,\n convertFormValuesToUpdatePayload,\n convertPostToFormValues,\n FormValues,\n getStatusOptions\n} from './PostForm.helpers';\n\nexport default defineComponent({\n name: 'BlogPostForm',\n components: { DynamicField, ShortCodeConstructor, FormFooter },\n setup(props, context) {\n const postId = computed<string>(() => context.root.$route.params.postId);\n const isCreation = computed<boolean>(() => postId.value === 'create');\n\n const urlAliasChanged = ref<boolean>(false);\n\n const statusOptions = computed<OptionType[]>(() =>\n getStatusOptions(context.root.$t)\n );\n\n /** Fetch module config **/\n\n const {\n data: moduleConfig,\n loading: isModuleConfigLoading\n } = useFetchModuleConfig({ context });\n\n const languageList = computed<Language[]>(\n () => moduleConfig.value?.languages ?? []\n );\n\n const languageOptionList = computed<OptionType[]>(() =>\n languageList.value.map(({ id, name }) => ({\n value: id,\n label: name\n }))\n );\n\n const isLangSpecific = computed(() => languageOptionList.value.length > 0);\n\n /** Fetch category list */\n\n const {\n data: categoryList,\n loading: isCategoryListLoading\n } = useFetchCategories({ context });\n\n /** Fetch Post */\n\n const [fetchPost, { data: post, loading: isPostLoading }] = useResource<Nullable<PostFull>>({\n fetchResource: () => getPost(postId.value),\n initialValue: null,\n context,\n resourceName: 'Post'\n });\n\n onMounted(() => {\n if (isCreation.value) return;\n\n fetchPost();\n });\n\n watch(postId, () => {\n if (isCreation.value) return;\n\n fetchPost();\n });\n\n /** Fetch post list */\n const { data: postList, loading: isPostListLoading } = useFetchPosts({\n context\n });\n\n const postOptionList = computed<OptionType<number>[]>(() =>\n postList.value\n .filter((relatedPost) => relatedPost.id !== post.value?.id)\n .map((post) => ({\n value: post.id,\n label: post.title\n }))\n );\n\n /** Form State **/\n const isSubmitting = ref<boolean>(false);\n const values = ref<FormValues>(\n convertPostToFormValues(\n post.value,\n languageOptionList.value,\n postOptionList.value,\n moduleConfig.value?.fields ?? [],\n statusOptions.value\n )\n );\n const errors = ref<Record<string, string>>({});\n\n watch([post, languageOptionList, postOptionList, moduleConfig], () => {\n values.value = convertPostToFormValues(\n post.value,\n languageOptionList.value,\n postOptionList.value,\n moduleConfig.value?.fields ?? [],\n statusOptions.value\n );\n });\n\n function submitForm(event: TagerFormSubmitEvent) {\n isSubmitting.value = true;\n\n const creationBody = convertFormValuesToCreationPayload(values.value);\n const updateBody = convertFormValuesToUpdatePayload(values.value);\n\n const requestPromise = isCreation.value\n ? createBlogPost(creationBody)\n : updateBlogPost(postId.value, updateBody);\n\n requestPromise\n .then(({ data }) => {\n errors.value = {};\n\n if (event.type === 'create') {\n context.root.$router.push(\n getBlogPostFormUrl({ postId: String(data.id) })\n );\n }\n\n if (event.type === 'create_exit' || event.type === 'save_exit') {\n if (context.root.$previousRoute) {\n context.root.$router.back();\n } else {\n context.root.$router.push(getBlogPostListUrl());\n }\n }\n\n if (event.type === 'create_create-another') {\n values.value = convertPostToFormValues(\n null,\n languageOptionList.value,\n postOptionList.value,\n moduleConfig.value?.fields ?? [],\n statusOptions.value\n );\n }\n\n context.root.$toast({\n variant: 'success',\n title: context.root.$t('blog:success'),\n body: isCreation.value\n ? context.root.$t('blog:blogPostWasSuccessfullyCreated')\n : context.root.$t('blog:blogPostWasSuccessfullyUpdated')\n });\n })\n .catch((error) => {\n console.error(error);\n errors.value = convertRequestErrorToMap(error);\n context.root.$toast({\n variant: 'danger',\n title: context.root.$t('blog:error'),\n body: isCreation.value\n ? context.root.$t('blog:blogPostCreationWasFailed')\n : context.root.$t('blog:blogPostUpdateWasFailed')\n });\n })\n .finally(() => {\n isSubmitting.value = false;\n });\n }\n\n /** Category Options */\n\n const categoryOptionList = computed<OptionType<number>[]>(() =>\n convertCategoryListToOptions(\n categoryList.value,\n values.value.language?.value ?? null\n )\n );\n\n watch([categoryOptionList, moduleConfig], () => {\n function isOptionExist(\n selectedCategoryOption: OptionType<number>\n ): boolean {\n return categoryOptionList.value.some(\n (option) => option.value === selectedCategoryOption.value\n );\n }\n\n if (isCreation.value) {\n if (moduleConfig?.value?.defaultCategories) {\n const defaultIds = moduleConfig.value.defaultCategories.map(\n (item: { id: number }) => item.id\n );\n values.value.categories = categoryOptionList.value.filter(\n (item: OptionType<number>) => {\n return defaultIds.indexOf(item.value) !== -1;\n }\n );\n }\n }\n\n values.value.categories = values.value.categories.filter(isOptionExist);\n });\n\n /** Misc */\n\n const isContentLoading = computed<boolean>(\n () =>\n isPostLoading.value ||\n isCategoryListLoading.value ||\n isPostListLoading.value ||\n isModuleConfigLoading.value\n );\n\n function getUploadAdapterOptions() {\n return { uploadScenario: moduleConfig.value?.fileScenarios.content };\n }\n\n const blogPagePath = computed<string>(() => {\n const origin = process.env.VUE_APP_WEBSITE_URL || window.location.origin;\n return origin + (post.value?.urlTemplate ?? '');\n });\n\n const tabList = computed<TabType[]>(() => {\n const hasErrors = createTabErrorFinder(errors.value);\n return [\n {\n id: 'common',\n label: context.root.$t('blog:common'),\n hasErrors: hasErrors([\n 'title',\n 'language',\n 'urlAlias',\n 'excerpt',\n 'body',\n 'date'\n ])\n },\n {\n id: 'images',\n label: context.root.$t('blog:images'),\n hasErrors: hasErrors(['coverImage', 'image'])\n },\n {\n id: 'relations',\n label: context.root.$t('blog:relations'),\n hasErrors: hasErrors(['categories', 'relatedPosts', 'tags'])\n },\n values.value.additionalFields.length > 0\n ? {\n id: 'additional',\n label: context.root.$t('blog:additionalFields'),\n hasErrors: hasErrors([])\n }\n : null,\n {\n id: 'seo',\n label: 'SEO',\n hasErrors: hasErrors([\n 'pageTitle',\n 'pageDescription',\n 'openGraphImage'\n ])\n }\n ].filter(isNotNullish);\n });\n\n const selectedTabId = ref<string>(tabList.value[0].id);\n\n const headerButtonList = computed<{ text: string; href: string; target?: string }[]>(() =>\n [\n post.value\n ? {\n text: context.root.$t('blog:viewPost'),\n href: process.env.VUE_APP_WEBSITE_URL + post.value.url,\n target: '_blank'\n }\n : null\n ].filter(isNotNullish)\n );\n\n function handleSeoFieldGroupChange({\n title,\n description,\n image\n }: SeoChangeEvent) {\n values.value.pageTitle = title;\n values.value.pageDescription = description;\n values.value.openGraphImage = image;\n }\n\n function handleTitleChange(value: string) {\n if (isCreation.value && urlAliasChanged.value === false) {\n values.value.urlAlias = urlTranslit(value);\n }\n }\n\n function handleAliasChange() {\n urlAliasChanged.value = true;\n }\n\n const urlAliasTemplate = computed<string>(() => {\n if (!moduleConfig.value || !moduleConfig.value.urlPostTemplate) {\n return '';\n }\n\n let result = moduleConfig.value.urlPostTemplate;\n\n if (values.value.language) {\n result = result.replace('{language}', values.value.language.value);\n }\n\n if (postId.value !== 'create') {\n result = result.replace('{id}', postId.value);\n }\n\n return result;\n });\n\n const handleCategoriesChange = (selectedOptions: OptionType<number>[]) => {\n const newSelectedOptions: OptionType<number>[] = [];\n\n selectedOptions.forEach((selectedOption) => {\n const findTreeSelectedCategory = (categoryId: Nullable<number>) => {\n const foundSelectedCategory = categoryList.value.find(\n ({ id }) => id === categoryId\n );\n\n if (!foundSelectedCategory) {\n return;\n }\n\n const newSelectedOption: OptionType<number> = {\n value: foundSelectedCategory.id,\n label: foundSelectedCategory.name\n };\n\n if (\n !newSelectedOptions.some(\n ({ value }) => value === newSelectedOption.value\n )\n ) {\n newSelectedOptions.push(newSelectedOption);\n }\n\n if (foundSelectedCategory.parent) {\n findTreeSelectedCategory(foundSelectedCategory.parent.id);\n }\n };\n\n findTreeSelectedCategory(selectedOption.value);\n });\n\n values.value.categories = newSelectedOptions;\n };\n\n return {\n isCreation,\n postId,\n pagePath: blogPagePath,\n categoryOptionList,\n values,\n errors,\n isContentLoading,\n postListUrl: getBlogPostListUrl(),\n submitForm,\n isLangSpecific,\n languageOptionList,\n isSubmitting,\n moduleConfig,\n postOptionList,\n getUploadAdapterOptions,\n tabList,\n selectedTabId,\n headerButtonList,\n handleSeoFieldGroupChange,\n handleTitleChange,\n handleAliasChange,\n urlAliasTemplate,\n handleCategoriesChange,\n\n statusOptions\n };\n }\n});\n</script>\n\n<style scoped lang='scss'>\n.status {\n display: flex;\n align-items: flex-end;\n}\n\n.status-value {\n flex-basis: 50%;\n}\n\n.status-checkbox {\n margin-left: 20px;\n padding-bottom: 8px;\n}\n\n.status-date {\n margin-left: 20px;\n flex: 1 1 1px;\n}\n</style>",".status {\n display: flex;\n align-items: flex-end;\n}\n\n.status-value {\n flex-basis: 50%;\n}\n\n.status-checkbox {\n margin-left: 20px;\n padding-bottom: 8px;\n}\n\n.status-date {\n margin-left: 20px;\n flex: 1 1 1px;\n}\n\n/*# sourceMappingURL=PostForm.vue.map */"]}, media: undefined });
4301
+
4302
+ };
4151
4303
  /* scoped */
4152
- const __vue_scope_id__$1 = undefined;
4304
+ const __vue_scope_id__$1 = "data-v-50f3750d";
4153
4305
  /* module identifier */
4154
4306
  const __vue_module_identifier__$1 = undefined;
4155
4307
  /* functional template */
4156
4308
  const __vue_is_functional_template__$1 = false;
4157
- /* style inject */
4158
-
4159
4309
  /* style inject SSR */
4160
4310
 
4161
4311
  /* style inject shadow dom */
@@ -4170,7 +4320,7 @@ __vue_render__$1._withStripped = true;
4170
4320
  __vue_is_functional_template__$1,
4171
4321
  __vue_module_identifier__$1,
4172
4322
  false,
4173
- undefined,
4323
+ createInjector,
4174
4324
  undefined,
4175
4325
  undefined
4176
4326
  );
@@ -4603,6 +4753,7 @@ function convertCategoryToFormValues(category, languageOptionList, t) {
4603
4753
  name: '',
4604
4754
  parent: { value: null, label: t('blog:noParent') },
4605
4755
  urlAlias: '',
4756
+ isDefault: false,
4606
4757
  pageTitle: '',
4607
4758
  pageDescription: '',
4608
4759
  pageKeywords: '',
@@ -4613,6 +4764,7 @@ function convertCategoryToFormValues(category, languageOptionList, t) {
4613
4764
  }
4614
4765
  return {
4615
4766
  language: currentLangOption !== null && currentLangOption !== void 0 ? currentLangOption : null,
4767
+ isDefault: category.isDefault,
4616
4768
  name: category.name,
4617
4769
  parent: category.parent
4618
4770
  ? { value: category.parent.id, label: category.parent.name }
@@ -4635,7 +4787,7 @@ function convertCategoryFormValuesToPayload(values) {
4635
4787
  name: values.name,
4636
4788
  parent: values.parent.value,
4637
4789
  urlAlias: values.urlAlias,
4638
- isDefault: false,
4790
+ isDefault: values.isDefault,
4639
4791
  pageTitle: values.pageTitle,
4640
4792
  pageDescription: values.pageDescription,
4641
4793
  openGraphImage: (_d = (_c = values.openGraphImage) === null || _c === void 0 ? void 0 : _c.file.id) !== null && _d !== void 0 ? _d : null,
@@ -4920,6 +5072,21 @@ var __vue_render__$3 = function () {
4920
5072
  })
4921
5073
  : _vm._e(),
4922
5074
  _vm._v(" "),
5075
+ _c("form-field-checkbox", {
5076
+ attrs: {
5077
+ name: "isDefault",
5078
+ label: _vm.$t("blog:defaultCategory"),
5079
+ error: _vm.errors.isDefault,
5080
+ },
5081
+ model: {
5082
+ value: _vm.values.isDefault,
5083
+ callback: function ($$v) {
5084
+ _vm.$set(_vm.values, "isDefault", $$v);
5085
+ },
5086
+ expression: "values.isDefault",
5087
+ },
5088
+ }),
5089
+ _vm._v(" "),
4923
5090
  _c("form-field", {
4924
5091
  attrs: {
4925
5092
  name: "name",