diff --git a/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.html b/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.html
index 4b121e5114..2f8a80d721 100644
--- a/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.html
+++ b/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.html
@@ -3,6 +3,30 @@
@if (form && (!isEditMode || blogPost)) {
+
+
+ @if (isTagsEnabled) {
+
+
+
+
+
{{ 'CmsKit::TagsHelpText' | abpLocalization }}
+
+ }
} @else {
diff --git a/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.ts b/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.ts
index 4af9bc8d88..2f6e77620b 100644
--- a/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.ts
+++ b/npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.ts
@@ -1,9 +1,12 @@
import { Component, OnInit, inject, Injector, DestroyRef } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';
+import { ReactiveFormsModule, FormsModule, FormGroup, FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { NgxValidateCoreModule } from '@ngx-validate/core';
-import { LocalizationPipe } from '@abp/ng.core';
+import { forkJoin, of } from 'rxjs';
+import { switchMap, tap } from 'rxjs/operators';
+import { LocalizationPipe, RestService } from '@abp/ng.core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
ExtensibleFormComponent,
FormPropData,
@@ -13,7 +16,15 @@ import {
import { PageComponent } from '@abp/ng.components/page';
import { ButtonComponent } from '@abp/ng.theme.shared';
import { ToastuiEditorComponent, prepareSlugFromControl } from '@abp/ng.cms-kit';
-import { BlogPostAdminService, BlogPostDto } from '@abp/ng.cms-kit/proxy';
+import {
+ BlogPostAdminService,
+ BlogPostDto,
+ MediaDescriptorAdminService,
+ EntityTagAdminService,
+ CreateMediaInputWithStream,
+ TagDto,
+ BlogFeatureDto,
+} from '@abp/ng.cms-kit/proxy';
import { eCmsKitAdminComponents } from '../../../enums';
import { BlogPostFormService } from '../../../services';
@@ -33,12 +44,16 @@ import { BlogPostFormService } from '../../../services';
ToastuiEditorComponent,
LocalizationPipe,
ReactiveFormsModule,
+ FormsModule,
CommonModule,
NgxValidateCoreModule,
],
})
export class BlogPostFormComponent implements OnInit {
private blogPostService = inject(BlogPostAdminService);
+ private mediaService = inject(MediaDescriptorAdminService);
+ private entityTagService = inject(EntityTagAdminService);
+ private restService = inject(RestService);
private injector = inject(Injector);
private blogPostFormService = inject(BlogPostFormService);
private route = inject(ActivatedRoute);
@@ -48,6 +63,11 @@ export class BlogPostFormComponent implements OnInit {
blogPost: BlogPostDto | null = null;
blogPostId: string | null = null;
isEditMode = false;
+ coverImageFile: File | null = null;
+ coverImagePreview: string | null = null;
+ tags: string = '';
+ isTagsEnabled = true;
+ readonly BLOG_POST_ENTITY_TYPE = 'BlogPost';
ngOnInit() {
const id = this.route.snapshot.params['id'];
@@ -64,43 +84,160 @@ export class BlogPostFormComponent implements OnInit {
private loadBlogPost(id: string) {
this.blogPostService.get(id).subscribe(blogPost => {
this.blogPost = blogPost;
+ if (blogPost.coverImageMediaId) {
+ this.coverImagePreview = `/api/cms-kit/media/${blogPost.coverImageMediaId}`;
+ }
this.buildForm();
+ this.loadTags(id);
});
}
+ private loadTags(blogPostId: string) {
+ this.restService
+ .request({
+ method: 'GET',
+ url: `/api/cms-kit-public/tags/${this.BLOG_POST_ENTITY_TYPE}/${blogPostId}`,
+ })
+ .subscribe(tags => {
+ if (tags && tags.length > 0) {
+ this.tags = tags.map(t => t.name || '').join(', ');
+ }
+ });
+ }
+
private buildForm() {
const data = new FormPropData(this.injector, this.blogPost || {});
const baseForm = generateFormFromProps(data);
this.form = new FormGroup({
...baseForm.controls,
content: new FormControl(this.blogPost?.content || ''),
+ coverImageMediaId: new FormControl(this.blogPost?.coverImageMediaId || null),
});
prepareSlugFromControl(this.form, 'title', 'slug', this.destroyRef);
+
+ // Check if tags feature is enabled for the blog
+ const blogId = this.form.get('blogId')?.value || this.blogPost?.blogId;
+ if (blogId) {
+ this.checkTagsFeature(blogId);
+ }
+
+ // Listen for blog selection changes
+ this.form
+ .get('blogId')
+ ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(blogId => {
+ if (blogId) {
+ this.checkTagsFeature(blogId);
+ }
+ });
}
- private executeSaveOperation(operation: 'save' | 'draft' | 'publish' | 'sendToReview') {
- if (this.isEditMode) {
- if (!this.blogPost || !this.blogPostId) {
- return;
- }
- this.blogPostFormService.update(this.blogPostId, this.form, this.blogPost).subscribe();
- return;
+ private checkTagsFeature(blogId: string) {
+ this.restService
+ .request({
+ method: 'GET',
+ url: `/api/cms-kit/blogs/${blogId}/features/CmsKit.Tags`,
+ })
+ .subscribe(feature => {
+ this.isTagsEnabled = feature?.isEnabled === true;
+ });
+ }
+
+ onCoverImageChange(event: Event) {
+ const input = event.target as HTMLInputElement;
+ if (input.files && input.files[0]) {
+ this.coverImageFile = input.files[0];
+ const reader = new FileReader();
+ reader.onload = (e: any) => {
+ this.coverImagePreview = e.target.result;
+ };
+ reader.readAsDataURL(this.coverImageFile);
+ }
+ }
+
+ removeCoverImage() {
+ this.coverImageFile = null;
+ this.coverImagePreview = null;
+ this.form.patchValue({ coverImageMediaId: null });
+ }
+
+ private uploadCoverImage() {
+ if (!this.coverImageFile) {
+ return of(this.form.value.coverImageMediaId || null);
+ }
+
+ const input: CreateMediaInputWithStream = {
+ name: this.coverImageFile.name,
+ file: this.coverImageFile as any,
+ };
+
+ return this.mediaService.create('blogpost', input).pipe(
+ tap(result => {
+ this.form.patchValue({ coverImageMediaId: result.id });
+ }),
+ switchMap(result => of(result.id || null)),
+ );
+ }
+
+ private setTags(blogPostId: string) {
+ if (!this.tags || !this.tags.trim()) {
+ return of(null);
}
- switch (operation) {
- case 'save':
- this.blogPostFormService.createAsDraft(this.form).subscribe();
- break;
- case 'draft':
- this.blogPostFormService.createAsDraft(this.form).subscribe();
- break;
- case 'publish':
- this.blogPostFormService.createAndPublish(this.form).subscribe();
- break;
- case 'sendToReview':
- this.blogPostFormService.createAndSendToReview(this.form).subscribe();
- break;
+ const tagArray = this.tags
+ .split(',')
+ .map(t => t.trim())
+ .filter(t => t.length > 0);
+
+ if (tagArray.length === 0) {
+ return of(null);
}
+
+ return this.entityTagService.setEntityTags({
+ entityType: this.BLOG_POST_ENTITY_TYPE,
+ entityId: blogPostId,
+ tags: tagArray,
+ });
+ }
+
+ private executeSaveOperation(operation: 'save' | 'draft' | 'publish' | 'sendToReview') {
+ // First upload cover image if selected
+ this.uploadCoverImage()
+ .pipe(
+ tap(coverImageMediaId => {
+ if (coverImageMediaId) {
+ this.form.patchValue({ coverImageMediaId });
+ }
+ }),
+ switchMap(() => {
+ if (this.isEditMode) {
+ if (!this.blogPost || !this.blogPostId) {
+ return of(null);
+ }
+ return this.blogPostFormService.update(this.blogPostId, this.form, this.blogPost);
+ }
+
+ switch (operation) {
+ case 'save':
+ case 'draft':
+ return this.blogPostFormService.createAsDraft(this.form);
+ case 'publish':
+ return this.blogPostFormService.createAndPublish(this.form);
+ case 'sendToReview':
+ return this.blogPostFormService.createAndSendToReview(this.form);
+ default:
+ return of(null);
+ }
+ }),
+ switchMap(result => {
+ if (!result || !result.id) {
+ return of(null);
+ }
+ // Set tags after blog post is created/updated
+ return forkJoin([of(result), this.setTags(result.id)]);
+ }),
+ )
+ .subscribe();
}
saveAsDraft() {