Browse Source

update: add cover image and tags

pull/24234/head
sumeyye 9 months ago
parent
commit
363fdf400f
  1. 40
      npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.html
  2. 183
      npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.ts

40
npm/ng-packs/packages/cms-kit/admin/src/components/blog-posts/blog-post-form/blog-post-form.component.html

@ -3,6 +3,30 @@
<div class="card-body"> <div class="card-body">
@if (form && (!isEditMode || blogPost)) { @if (form && (!isEditMode || blogPost)) {
<form [formGroup]="form" (ngSubmit)="saveAsDraft()" validateOnSubmit> <form [formGroup]="form" (ngSubmit)="saveAsDraft()" validateOnSubmit>
<!-- Cover Image -->
<div class="mb-3">
@if (coverImagePreview) {
<div class="mb-2">
<img [src]="coverImagePreview" height="120" alt="Cover Image" />
<br />
<button type="button" class="btn btn-link p-0" (click)="removeCoverImage()">
{{ 'CmsKit::RemoveCoverImage' | abpLocalization }}
</button>
<br />
</div>
}
<label class="form-label"
>{{ 'CmsKit::CoverImage' | abpLocalization }}
<span class="badge text-bg-light">16:9</span></label
>
<input
type="file"
class="form-control"
accept="image/*"
(change)="onCoverImageChange($event)"
/>
</div>
<!-- Basic fields --> <!-- Basic fields -->
<abp-extensible-form [selectedRecord]="blogPost || {}" /> <abp-extensible-form [selectedRecord]="blogPost || {}" />
@ -12,6 +36,22 @@
<abp-toastui-editor formControlName="content" /> <abp-toastui-editor formControlName="content" />
</div> </div>
</form> </form>
<!-- Tags Editor -->
@if (isTagsEnabled) {
<hr />
<div class="mb-3">
<label class="form-label">{{ 'CmsKit::Tags' | abpLocalization }}</label>
<input
type="text"
class="form-control"
[(ngModel)]="tags"
[ngModelOptions]="{ standalone: true }"
[placeholder]="'CmsKit::TagsHelpText' | abpLocalization"
/>
<div class="form-text">{{ 'CmsKit::TagsHelpText' | abpLocalization }}</div>
</div>
}
} @else { } @else {
<div class="text-center"> <div class="text-center">
<i class="fa fa-pulse fa-spinner" aria-hidden="true"></i> <i class="fa fa-pulse fa-spinner" aria-hidden="true"></i>

183
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 { Component, OnInit, inject, Injector, DestroyRef } from '@angular/core';
import { CommonModule } from '@angular/common'; 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 { ActivatedRoute } from '@angular/router';
import { NgxValidateCoreModule } from '@ngx-validate/core'; 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 { import {
ExtensibleFormComponent, ExtensibleFormComponent,
FormPropData, FormPropData,
@ -13,7 +16,15 @@ import {
import { PageComponent } from '@abp/ng.components/page'; import { PageComponent } from '@abp/ng.components/page';
import { ButtonComponent } from '@abp/ng.theme.shared'; import { ButtonComponent } from '@abp/ng.theme.shared';
import { ToastuiEditorComponent, prepareSlugFromControl } from '@abp/ng.cms-kit'; 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 { eCmsKitAdminComponents } from '../../../enums';
import { BlogPostFormService } from '../../../services'; import { BlogPostFormService } from '../../../services';
@ -33,12 +44,16 @@ import { BlogPostFormService } from '../../../services';
ToastuiEditorComponent, ToastuiEditorComponent,
LocalizationPipe, LocalizationPipe,
ReactiveFormsModule, ReactiveFormsModule,
FormsModule,
CommonModule, CommonModule,
NgxValidateCoreModule, NgxValidateCoreModule,
], ],
}) })
export class BlogPostFormComponent implements OnInit { export class BlogPostFormComponent implements OnInit {
private blogPostService = inject(BlogPostAdminService); private blogPostService = inject(BlogPostAdminService);
private mediaService = inject(MediaDescriptorAdminService);
private entityTagService = inject(EntityTagAdminService);
private restService = inject(RestService);
private injector = inject(Injector); private injector = inject(Injector);
private blogPostFormService = inject(BlogPostFormService); private blogPostFormService = inject(BlogPostFormService);
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
@ -48,6 +63,11 @@ export class BlogPostFormComponent implements OnInit {
blogPost: BlogPostDto | null = null; blogPost: BlogPostDto | null = null;
blogPostId: string | null = null; blogPostId: string | null = null;
isEditMode = false; isEditMode = false;
coverImageFile: File | null = null;
coverImagePreview: string | null = null;
tags: string = '';
isTagsEnabled = true;
readonly BLOG_POST_ENTITY_TYPE = 'BlogPost';
ngOnInit() { ngOnInit() {
const id = this.route.snapshot.params['id']; const id = this.route.snapshot.params['id'];
@ -64,43 +84,160 @@ export class BlogPostFormComponent implements OnInit {
private loadBlogPost(id: string) { private loadBlogPost(id: string) {
this.blogPostService.get(id).subscribe(blogPost => { this.blogPostService.get(id).subscribe(blogPost => {
this.blogPost = blogPost; this.blogPost = blogPost;
if (blogPost.coverImageMediaId) {
this.coverImagePreview = `/api/cms-kit/media/${blogPost.coverImageMediaId}`;
}
this.buildForm(); this.buildForm();
this.loadTags(id);
}); });
} }
private loadTags(blogPostId: string) {
this.restService
.request<void, TagDto[]>({
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() { private buildForm() {
const data = new FormPropData(this.injector, this.blogPost || {}); const data = new FormPropData(this.injector, this.blogPost || {});
const baseForm = generateFormFromProps(data); const baseForm = generateFormFromProps(data);
this.form = new FormGroup({ this.form = new FormGroup({
...baseForm.controls, ...baseForm.controls,
content: new FormControl(this.blogPost?.content || ''), content: new FormControl(this.blogPost?.content || ''),
coverImageMediaId: new FormControl(this.blogPost?.coverImageMediaId || null),
}); });
prepareSlugFromControl(this.form, 'title', 'slug', this.destroyRef); 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') { private checkTagsFeature(blogId: string) {
if (this.isEditMode) { this.restService
if (!this.blogPost || !this.blogPostId) { .request<void, BlogFeatureDto>({
return; method: 'GET',
} url: `/api/cms-kit/blogs/${blogId}/features/CmsKit.Tags`,
this.blogPostFormService.update(this.blogPostId, this.form, this.blogPost).subscribe(); })
return; .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) { const tagArray = this.tags
case 'save': .split(',')
this.blogPostFormService.createAsDraft(this.form).subscribe(); .map(t => t.trim())
break; .filter(t => t.length > 0);
case 'draft':
this.blogPostFormService.createAsDraft(this.form).subscribe(); if (tagArray.length === 0) {
break; return of(null);
case 'publish':
this.blogPostFormService.createAndPublish(this.form).subscribe();
break;
case 'sendToReview':
this.blogPostFormService.createAndSendToReview(this.form).subscribe();
break;
} }
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() { saveAsDraft() {

Loading…
Cancel
Save