Remove SCSS files related to components, layouts, and Bootstrap variables no longer in use
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
||||
<!-- <mat-nav-list>-->
|
||||
@for (fileInfo of fileInfos; track fileInfo){
|
||||
<a mat-list-item (click)="select(fileInfo)">
|
||||
<img style="width: 120px; height: 120px" src="{{fileInfo.url}}" alt="">
|
||||
<img style="width: 179px; height: 100px" src="{{fileInfo.url}}" alt="">
|
||||
<span class="entry">
|
||||
|
||||
<!-- @if (!isCollapsed) {-->
|
||||
|
||||
+41
-52
@@ -1,26 +1,26 @@
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Add tutorial</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content >
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form" novalidate>
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label class="label-style">Title</mat-label>
|
||||
<input matInput required
|
||||
[(ngModel)]="tutorial.title">
|
||||
<input matInput
|
||||
formControlName="title"
|
||||
>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-label class="label-style">Title image</mat-label>
|
||||
<mat-divider></mat-divider>
|
||||
<table class="example-full-width">
|
||||
<tr>
|
||||
<td style="width: 120px;">
|
||||
<img style="width: 120px; height: 120px" src="{{tutorial.titleimage}}" alt="">
|
||||
</td>
|
||||
<td>
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Title image</mat-label>
|
||||
<input matInput disabled
|
||||
[(ngModel)]="tutorial.titleimage">
|
||||
<input matInput
|
||||
|
||||
|
||||
formControlName="titleimage"
|
||||
[disabled]="true"
|
||||
>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-raised-button color="primary" (click)="openSelectImageDialog('100ms', '5ms')">Select image</button>
|
||||
@@ -30,50 +30,27 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="example-full-width">
|
||||
<mat-label class="label-style">Description</mat-label>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text | Result">
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Editor">
|
||||
<div class="markdown-editor-container">
|
||||
<div class="markdown-editor">
|
||||
<form novalidate>
|
||||
<angular-markdown-editor
|
||||
textareaId="editor1"
|
||||
[options]="editorOptions"
|
||||
name="markdownText"
|
||||
[(ngModel)]="tutorial.description"
|
||||
(onFullscreenExit)="hidePreview()"
|
||||
>
|
||||
</angular-markdown-editor>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Result">
|
||||
<markdown class="preview" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
|
||||
@if(tutorial.titleimage){
|
||||
<div class="example-full-width content-center">
|
||||
<img style="width: 716px; height: 400px" src="{{tutorial.titleimage}}" alt="">
|
||||
</div>
|
||||
}
|
||||
<div class="example-full-width">
|
||||
<mat-label class="label-style">Body</mat-label>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text | Result">
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.body"></textarea>
|
||||
<textarea class="variable-textarea"
|
||||
|
||||
formControlName = "body"
|
||||
|
||||
(input)="onBodyChange($event)"
|
||||
>
|
||||
|
||||
</textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Editor">
|
||||
<!--<mat-tab label="Editor">
|
||||
<form novalidate>
|
||||
<angular-markdown-editor style="color: #1a1a1a"
|
||||
textareaId="editor2"
|
||||
@@ -84,26 +61,38 @@
|
||||
>
|
||||
</angular-markdown-editor>
|
||||
</form>
|
||||
</mat-tab>
|
||||
</mat-tab>-->
|
||||
<mat-tab label="Result">
|
||||
<markdown class="preview" [data]="tutorial.body"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<textarea class="variable-textarea" [value]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-card-actions>
|
||||
@if (hasError){
|
||||
<mat-error>
|
||||
{{errorMessage}}
|
||||
</mat-error>
|
||||
}
|
||||
|
||||
@if (submitted){
|
||||
<div >
|
||||
<h4>Tutorial was submitted successfully!</h4>
|
||||
<button matButton (click)="newTutorial()">Add new tutorial</button>
|
||||
<button matButton routerLink="../tutorials-list">Back to list</button>
|
||||
</div>
|
||||
} @else {
|
||||
<button matButton (click)="saveTutorial()" >Save</button>
|
||||
<button matButton (click)="saveTutorial()"
|
||||
[disabled]="form.invalid"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
}
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
+4
-1
@@ -24,6 +24,9 @@ mat-card-title{
|
||||
.example-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
.content-center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.variable-binding,
|
||||
@@ -55,7 +58,7 @@ mat-card-title{
|
||||
}
|
||||
|
||||
.label-style{
|
||||
font-size: -webkit-xxx-large;
|
||||
font-size: xx-large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
+114
-103
@@ -2,21 +2,44 @@ import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||
import {UserApiService} from '../user-api.service';
|
||||
import {TokenStorageService} from '../../../services/token-storage.service';
|
||||
import {EventBusService} from '../../../_shared/event-bus.service';
|
||||
import {Router} from '@angular/router';
|
||||
import {Router, RouterLink} from '@angular/router';
|
||||
import {AuthService} from '../../../services/auth.service';
|
||||
import {Tutorial} from '../../../models/tutorial.model';
|
||||
import {MarkdownComponent, MarkdownService} from 'ngx-markdown';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||
import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
FormsModule,
|
||||
ReactiveFormsModule, ValidationErrors,
|
||||
ValidatorFn,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import {AngularMarkdownEditorModule, EditorInstance, EditorOption} from 'angular-markdown-editor';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component';
|
||||
import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component';
|
||||
import {FileUploadComponent} from '../../../components/file-upload.component/file-upload.component';
|
||||
import {MatDivider} from '@angular/material/divider';
|
||||
import {GlobalConstants} from '../../../global-constants';
|
||||
|
||||
|
||||
const nonBlank = (): ValidatorFn => (c: AbstractControl): ValidationErrors | null =>
|
||||
c.value && /\S/.test(c.value) ? null : { nonBlank: true };
|
||||
|
||||
type TutorialFormValue = {
|
||||
title: string;
|
||||
titleimage: string;
|
||||
body: string;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial-add.component',
|
||||
@@ -35,140 +58,122 @@ import {MatDivider} from '@angular/material/divider';
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
AngularMarkdownEditorModule,
|
||||
MatDivider
|
||||
MatDivider,
|
||||
MatError,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './tutorial-add.component.html',
|
||||
styleUrl: './tutorial-add.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class TutorialAddComponent implements OnInit{
|
||||
export class TutorialAddComponent{
|
||||
|
||||
tutorial: Tutorial = new Tutorial();
|
||||
submitted = false;
|
||||
markdownText = '';
|
||||
showEditor = true;
|
||||
bsEditorInstance!: EditorInstance;
|
||||
tutorialForm!: FormGroup;
|
||||
editorOptions!: EditorOption;
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
|
||||
markdown = GlobalConstants.MARKDOWN_EXAMPLE;
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
markdown = `## Markdown __rulez__!
|
||||
---
|
||||
form = this.fb.nonNullable.group({
|
||||
|
||||
### Syntax highlight
|
||||
\`\`\`typescript
|
||||
const language = 'typescript';
|
||||
\`\`\`
|
||||
title: ['', [Validators.required, nonBlank()] ],
|
||||
body: ['', [Validators.required, nonBlank()] ],
|
||||
|
||||
### Lists
|
||||
1. Ordered list
|
||||
2. Another bullet point
|
||||
- Unordered list
|
||||
- Another unordered bullet
|
||||
titleimage: this.fb.nonNullable.control({ value: '', disabled: true }),
|
||||
|
||||
### Blockquote
|
||||
> Blockquote to the max`;
|
||||
});
|
||||
|
||||
hasError = false;
|
||||
errorMessage = '';
|
||||
|
||||
constructor(private fb: FormBuilder,
|
||||
constructor(
|
||||
private markdownService: MarkdownService,
|
||||
private userApiService: UserApiService,
|
||||
) {
|
||||
|
||||
this.tutorial.description = this.markdown;
|
||||
this.tutorial.title="";
|
||||
this.tutorial.titleimage="";
|
||||
this.tutorial.body = this.markdown;
|
||||
|
||||
this.form.patchValue(this.fromModel(this.tutorial));
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.editorOptions = {
|
||||
autofocus: false,
|
||||
iconlibrary: 'fa',
|
||||
height: 300,
|
||||
savable: false,
|
||||
onFullscreenExit: (e) => this.hidePreview(),
|
||||
onShow: (e) => this.bsEditorInstance = e,
|
||||
parser: (val) => this.parse(val)
|
||||
// Map Model -> Form value
|
||||
private fromModel(m: Tutorial): Partial<TutorialFormValue> {
|
||||
return {
|
||||
title: m.title ?? '',
|
||||
body: m.body ?? '',
|
||||
titleimage: m.titleimage ?? '',
|
||||
|
||||
};
|
||||
this.buildForm(this.tutorial.description);
|
||||
|
||||
}
|
||||
|
||||
buildForm(markdownText: string | undefined) {
|
||||
this.tutorialForm = this.fb.group({
|
||||
body: [markdownText],
|
||||
isPreview: [true]
|
||||
});
|
||||
}
|
||||
|
||||
/** highlight all code found, needs to be wrapped in timer to work properly */
|
||||
highlight() {
|
||||
setTimeout(() => {
|
||||
this.markdownService.highlight();
|
||||
});
|
||||
}
|
||||
|
||||
hidePreview() {
|
||||
if (this.bsEditorInstance && this.bsEditorInstance.hidePreview) {
|
||||
this.bsEditorInstance.hidePreview();
|
||||
}
|
||||
}
|
||||
|
||||
showFullScreen(isFullScreen: boolean) {
|
||||
if (this.bsEditorInstance && this.bsEditorInstance.setFullscreen) {
|
||||
this.bsEditorInstance.showPreview();
|
||||
this.bsEditorInstance.setFullscreen(isFullScreen);
|
||||
}
|
||||
}
|
||||
|
||||
parse(inputValue: string) {
|
||||
const markedOutput = this.markdownService.parse(inputValue.trim());
|
||||
this.highlight();
|
||||
|
||||
return markedOutput;
|
||||
}
|
||||
|
||||
onFormChanges(): void {
|
||||
this.tutorialForm.valueChanges.subscribe(formData => {
|
||||
if (formData) {
|
||||
this.markdownText = formData.body;
|
||||
}
|
||||
});
|
||||
// Map Form value -> Model (compose with existing model if you need to keep id, etc.)
|
||||
private toModel(): Tutorial {
|
||||
const v = this.form.getRawValue(); // TutorialFormValue
|
||||
return {
|
||||
...this.tutorial, // keep immutable fields like id
|
||||
title: v.title.trim(),
|
||||
body: v.body.trim(),
|
||||
//
|
||||
titleimage: v.titleimage,
|
||||
created: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
saveTutorial(): void {
|
||||
const data = {
|
||||
title: this.tutorial.title,
|
||||
description: this.tutorial.description,
|
||||
body: this.tutorial.body,
|
||||
published: this.tutorial.published,
|
||||
titleimage: this.tutorial.titleimage,
|
||||
created: new Date(),
|
||||
modified: new Date(),
|
||||
tobepublished: false,
|
||||
isEdit: false,
|
||||
isAdmin: false,
|
||||
isSuperAdmin: false,
|
||||
isUser: false,
|
||||
};
|
||||
if (this.form.invalid) {
|
||||
this.form.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
this.tutorial = this.toModel();
|
||||
// send `updated` to your API here
|
||||
this.userApiService.create(this.tutorial)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.submitted = true;
|
||||
},
|
||||
)
|
||||
|
||||
const data = {
|
||||
title: this.tutorial.title,
|
||||
description: this.tutorial.description,
|
||||
body: this.tutorial.body,
|
||||
published: this.tutorial.published,
|
||||
titleimage: this.tutorial.titleimage,
|
||||
created: new Date(),
|
||||
modified: new Date(),
|
||||
tobepublished: false,
|
||||
isEdit: false,
|
||||
isAdmin: false,
|
||||
isSuperAdmin: false,
|
||||
isUser: false,
|
||||
}
|
||||
|
||||
this.userApiService.create(data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.submitted = true;
|
||||
this.hasError = false;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.errorMessage = error.error.message;
|
||||
|
||||
this.hasError = true;
|
||||
});
|
||||
|
||||
this.userApiService.create(data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.submitted = true;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
newTutorial(): void {
|
||||
this.submitted = false;
|
||||
this.tutorial = {
|
||||
title: '',
|
||||
description: '',
|
||||
body:"",
|
||||
published: false
|
||||
};
|
||||
}
|
||||
@@ -195,6 +200,7 @@ const language = 'typescript';
|
||||
return;
|
||||
}
|
||||
this.tutorial.titleimage = result.url;
|
||||
this.form.patchValue({ titleimage: result.url });
|
||||
|
||||
});
|
||||
}
|
||||
@@ -220,7 +226,12 @@ const language = 'typescript';
|
||||
return;
|
||||
}
|
||||
this.tutorial.titleimage = result.url;
|
||||
|
||||
this.form.patchValue({ titleimage: result.url });
|
||||
});
|
||||
}
|
||||
|
||||
onBodyChange($event: Event) {
|
||||
this.tutorial.body = this.form.get('body')?.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+64
-90
@@ -1,104 +1,78 @@
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Edit tutorial</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content >
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label class="label-style">Title</mat-label>
|
||||
<input matInput required
|
||||
[(ngModel)]="tutorial.title">
|
||||
</mat-form-field>
|
||||
<form [formGroup]="form" novalidate>
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label class="label-style">Title</mat-label>
|
||||
<input matInput
|
||||
formControlName="title"
|
||||
>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-label class="label-style">Title image</mat-label>
|
||||
<mat-divider></mat-divider>
|
||||
<table class="example-full-width">
|
||||
<tr>
|
||||
<td style="width: 120px;">
|
||||
<img style="width: 120px; height: 120px" src="{{tutorial.titleimage}}" alt="">
|
||||
</td>
|
||||
<td>
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Title image</mat-label>
|
||||
<input matInput disabled
|
||||
[(ngModel)]="tutorial.titleimage">
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-raised-button color="primary" (click)="openSelectImageDialog('100ms', '5ms')">Select image</button>
|
||||
<button mat-raised-button color="primary" (click)="openUploadImageDialog('100ms', '5ms')">Upload image</button>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="example-full-width">
|
||||
<mat-label class="label-style">Description</mat-label>
|
||||
<mat-label class="label-style">Title image</mat-label>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text | Result">
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Editor">
|
||||
<div class="markdown-editor-container">
|
||||
<div class="markdown-editor">
|
||||
<form novalidate>
|
||||
<angular-markdown-editor
|
||||
textareaId="editor1"
|
||||
[options]="editorOptions"
|
||||
name="markdownText"
|
||||
[(ngModel)]="tutorial.description"
|
||||
(onFullscreenExit)="hidePreview()"
|
||||
>
|
||||
</angular-markdown-editor>
|
||||
</form>
|
||||
<table class="example-full-width">
|
||||
<tr>
|
||||
<td>
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Title image</mat-label>
|
||||
<input matInput
|
||||
formControlName="titleimage"
|
||||
[disabled]="true"
|
||||
>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-raised-button color="primary" (click)="openSelectImageDialog('100ms', '5ms')">Select image</button>
|
||||
<button mat-raised-button color="primary" (click)="openUploadImageDialog('100ms', '5ms')">Upload image</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Result">
|
||||
<markdown class="preview" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@if(tutorial.titleimage){
|
||||
<div class="example-full-width content-center">
|
||||
<img style="width: 716px; height: 400px" src="{{tutorial.titleimage}}" alt="">
|
||||
</div>
|
||||
}
|
||||
<div class="example-full-width">
|
||||
<mat-label class="label-style">Body</mat-label>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text | Result">
|
||||
<textarea class="variable-textarea"
|
||||
|
||||
<div class="example-full-width">
|
||||
<mat-label class="label-style">Body</mat-label>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text | Result">
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.body"></textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Editor">
|
||||
<form novalidate>
|
||||
<angular-markdown-editor style="color: #1a1a1a"
|
||||
textareaId="editor2"
|
||||
[options]="editorOptions"
|
||||
name="markdownText"
|
||||
[(ngModel)]="tutorial.body"
|
||||
(onFullscreenExit)="hidePreview()"
|
||||
formControlName = "body"
|
||||
(input)="onBodyChange($event)"
|
||||
>
|
||||
</angular-markdown-editor>
|
||||
</form>
|
||||
</mat-tab>
|
||||
<mat-tab label="Result">
|
||||
<markdown class="preview" [data]="tutorial.body"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
|
||||
</textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
||||
</mat-tab>
|
||||
<!--<mat-tab label="Editor">
|
||||
<form novalidate>
|
||||
<angular-markdown-editor style="color: #1a1a1a"
|
||||
textareaId="editor2"
|
||||
[options]="editorOptions"
|
||||
name="markdownText"
|
||||
[(ngModel)]="tutorial.body"
|
||||
(onFullscreenExit)="hidePreview()"
|
||||
>
|
||||
</angular-markdown-editor>
|
||||
</form>
|
||||
</mat-tab>-->
|
||||
<mat-tab label="Result">
|
||||
<markdown class="preview" [data]="tutorial.body"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [value]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-card-actions>
|
||||
|
||||
<button matButton (click)="updateTutorial()">Save</button>
|
||||
<button matButton (click)="updateTutorial()" [disabled]="form.invalid">Save</button>
|
||||
@if (hasError){
|
||||
<mat-error>
|
||||
{{errorMessage}}
|
||||
|
||||
+11
@@ -19,6 +19,11 @@ mat-card-title{
|
||||
|
||||
.example-full-width {
|
||||
width: 100%;
|
||||
margin-bottom: inherit;
|
||||
|
||||
}
|
||||
.content-center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,3 +62,9 @@ mat-card-title{
|
||||
border-color: var(--mat-card-outlined-outline-color, var(--mat-sys-outline-variant));
|
||||
box-shadow: var(--mat-card-outlined-container-elevation, var(--mat-sys-level0));
|
||||
}
|
||||
|
||||
|
||||
.label-style{
|
||||
font-size: xx-large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
+66
-73
@@ -4,7 +4,15 @@ import {MatButton} from '@angular/material/button';
|
||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||
import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormGroup,
|
||||
FormsModule,
|
||||
ReactiveFormsModule, ValidationErrors,
|
||||
ValidatorFn,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import {Tutorial} from '../../../models/tutorial.model';
|
||||
import {UserApiService} from '../user-api.service';
|
||||
import {ActivatedRoute, RouterLink} from '@angular/router';
|
||||
@@ -13,6 +21,18 @@ import {MatDivider} from '@angular/material/divider';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component';
|
||||
import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component';
|
||||
import {GlobalConstants} from '../../../global-constants';
|
||||
|
||||
const nonBlank = (): ValidatorFn => (c: AbstractControl): ValidationErrors | null =>
|
||||
c.value && /\S/.test(c.value) ? null : { nonBlank: true };
|
||||
|
||||
type TutorialFormValue = {
|
||||
title: string;
|
||||
titleimage: string;
|
||||
body: string;
|
||||
|
||||
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial-edit.component',
|
||||
@@ -40,41 +60,34 @@ import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialo
|
||||
styleUrl: './tutorial-edit.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class TutorialEditComponent implements OnInit{
|
||||
export class TutorialEditComponent{
|
||||
|
||||
tutorial: Tutorial = new Tutorial();
|
||||
submitted = false;
|
||||
|
||||
hasError = false;
|
||||
errorMessage = '';
|
||||
markdownText="";
|
||||
bsEditorInstance!: EditorInstance;
|
||||
tutorialForm!: FormGroup;
|
||||
editorOptions!: EditorOption;
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
|
||||
|
||||
markdown = `## Markdown __rulez__!
|
||||
---
|
||||
markdown = GlobalConstants.MARKDOWN_EXAMPLE;
|
||||
|
||||
### Syntax highlight
|
||||
\`\`\`typescript
|
||||
const language = 'typescript';
|
||||
\`\`\`
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
### Lists
|
||||
1. Ordered list
|
||||
2. Another bullet point
|
||||
- Unordered list
|
||||
- Another unordered bullet
|
||||
form = this.fb.nonNullable.group({
|
||||
|
||||
title: ['', [Validators.required, nonBlank()] ],
|
||||
body: ['', [Validators.required, nonBlank()] ],
|
||||
|
||||
titleimage: this.fb.nonNullable.control({ value: '', disabled: true }),
|
||||
|
||||
});
|
||||
|
||||
### Blockquote
|
||||
> Blockquote to the max`;
|
||||
private id: string | null | undefined;
|
||||
|
||||
constructor(private userApiService: UserApiService,
|
||||
private route: ActivatedRoute,
|
||||
private fb: FormBuilder,
|
||||
private markdownService: MarkdownService
|
||||
) {
|
||||
|
||||
@@ -88,66 +101,42 @@ const language = 'typescript';
|
||||
this.userApiService.getTutorial(this.id).subscribe(
|
||||
data=>{
|
||||
this.tutorial = data;
|
||||
this.form.patchValue(this.fromModel(this.tutorial));
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.editorOptions = {
|
||||
autofocus: false,
|
||||
iconlibrary: 'fa',
|
||||
height: 300,
|
||||
savable: false,
|
||||
onFullscreenExit: (e) => this.hidePreview(),
|
||||
onShow: (e) => this.bsEditorInstance = e,
|
||||
parser: (val) => this.parse(val)
|
||||
// Map Model -> Form value
|
||||
private fromModel(m: Tutorial): Partial<TutorialFormValue> {
|
||||
return {
|
||||
title: m.title ?? '',
|
||||
body: m.body ?? '',
|
||||
titleimage: m.titleimage ?? '',
|
||||
|
||||
};
|
||||
this.buildForm(this.tutorial.description);
|
||||
|
||||
}
|
||||
|
||||
buildForm(markdownText: string | undefined) {
|
||||
this.tutorialForm = this.fb.group({
|
||||
body: [markdownText],
|
||||
isPreview: [true]
|
||||
});
|
||||
}
|
||||
/** highlight all code found, needs to be wrapped in timer to work properly */
|
||||
highlight() {
|
||||
setTimeout(() => {
|
||||
this.markdownService.highlight();
|
||||
});
|
||||
}
|
||||
|
||||
hidePreview() {
|
||||
if (this.bsEditorInstance && this.bsEditorInstance.hidePreview) {
|
||||
this.bsEditorInstance.hidePreview();
|
||||
}
|
||||
}
|
||||
|
||||
showFullScreen(isFullScreen: boolean) {
|
||||
if (this.bsEditorInstance && this.bsEditorInstance.setFullscreen) {
|
||||
this.bsEditorInstance.showPreview();
|
||||
this.bsEditorInstance.setFullscreen(isFullScreen);
|
||||
}
|
||||
}
|
||||
|
||||
parse(inputValue: string) {
|
||||
const markedOutput = this.markdownService.parse(inputValue.trim());
|
||||
this.highlight();
|
||||
|
||||
return markedOutput;
|
||||
}
|
||||
|
||||
onFormChanges(): void {
|
||||
this.tutorialForm.valueChanges.subscribe(formData => {
|
||||
if (formData) {
|
||||
this.markdownText = formData.body;
|
||||
}
|
||||
});
|
||||
// Map Form value -> Model (compose with existing model if you need to keep id, etc.)
|
||||
private toModel(): Tutorial {
|
||||
const v = this.form.getRawValue(); // TutorialFormValue
|
||||
return {
|
||||
...this.tutorial, // keep immutable fields like id
|
||||
title: v.title.trim(),
|
||||
body: v.body.trim(),
|
||||
//
|
||||
titleimage: v.titleimage,
|
||||
modified: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
updateTutorial(): void {
|
||||
if (this.form.invalid) {
|
||||
this.form.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
this.tutorial = this.toModel();
|
||||
// send `updated` to your API here
|
||||
this.userApiService.update(this.id, this.tutorial)
|
||||
.subscribe(
|
||||
response => {
|
||||
@@ -186,7 +175,7 @@ const language = 'typescript';
|
||||
return;
|
||||
}
|
||||
this.tutorial.titleimage = result.url;
|
||||
|
||||
this.form.patchValue({ titleimage: result.url });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -211,7 +200,11 @@ const language = 'typescript';
|
||||
return;
|
||||
}
|
||||
this.tutorial.titleimage = result.url;
|
||||
|
||||
this.form.patchValue({ titleimage: result.url });
|
||||
});
|
||||
}
|
||||
|
||||
onBodyChange($event: Event) {
|
||||
this.tutorial.body = this.form.get('body')?.value;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,4 +1,6 @@
|
||||
|
||||
<article class="table-header">
|
||||
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
|
||||
<button
|
||||
class="button-remove-rows"
|
||||
mat-button
|
||||
@@ -6,7 +8,7 @@
|
||||
>
|
||||
Remove Rows
|
||||
</button>
|
||||
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
|
||||
|
||||
|
||||
</article>
|
||||
<table mat-table [dataSource]="dataSource">
|
||||
|
||||
+3
-1
@@ -22,6 +22,7 @@ import {
|
||||
import {DatePipe} from '@angular/common';
|
||||
import {MatCheckbox} from '@angular/material/checkbox';
|
||||
import {MatSlideToggle} from '@angular/material/slide-toggle';
|
||||
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -43,7 +44,8 @@ import {MatSlideToggle} from '@angular/material/slide-toggle';
|
||||
MatCheckbox,
|
||||
DatePipe,
|
||||
MatCell,
|
||||
MatSlideToggle
|
||||
MatSlideToggle,
|
||||
BreadcrumbsComponent
|
||||
],
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA],
|
||||
templateUrl: './tutorials-list.component.html',
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
</app-side-bar-user>
|
||||
|
||||
<div class="pc-container">
|
||||
<app-breadcrumbs></app-breadcrumbs>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//---------------
|
||||
.pc-sidebar{
|
||||
top: 65px;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(153, 153, 153, 0.16);
|
||||
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.pc-container{
|
||||
top: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import {RouterOutlet} from '@angular/router';
|
||||
import {MatSidenav} from '@angular/material/sidenav';
|
||||
import {BreakpointObserver} from '@angular/cdk/layout';
|
||||
import {SideBarUserComponent} from '../side-bar-user.component/side-bar-user.component';
|
||||
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user.component',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
SideBarUserComponent
|
||||
SideBarUserComponent,
|
||||
BreadcrumbsComponent
|
||||
],
|
||||
templateUrl: './user.component.html',
|
||||
styleUrl: './user.component.scss',
|
||||
|
||||
@@ -7,30 +7,39 @@ const USER_ROUTES: Routes = [
|
||||
path: '',
|
||||
component: UserComponent,
|
||||
|
||||
data:{breadcrumb: 'User'},
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'user-welcome' }, // default redirect
|
||||
|
||||
{
|
||||
path: 'user-welcome',
|
||||
loadComponent: () => import('../user-module/user-welcome.component/user-welcome.component').then((c) => c.UserWelcomeComponent),
|
||||
data:{breadcrumb: 'Welcome'}
|
||||
},
|
||||
{
|
||||
path: 'tutorials-list',
|
||||
loadComponent: () => import('../user-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent)
|
||||
loadComponent: () => import('../user-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent),
|
||||
data:{breadcrumb: 'Tutorials List'}
|
||||
},
|
||||
{
|
||||
path: 'tutorial-add',
|
||||
loadComponent: () => import('../user-module/tutorial-add.component/tutorial-add.component').then((c) => c.TutorialAddComponent)
|
||||
loadComponent: () => import('../user-module/tutorial-add.component/tutorial-add.component').then((c) => c.TutorialAddComponent),
|
||||
data:{breadcrumb: 'Add Tutorial'}
|
||||
},
|
||||
{
|
||||
path: 'tutorial-edit',
|
||||
loadComponent: () => import('../user-module/tutorial-edit.component/tutorial-edit.component').then((c) => c.TutorialEditComponent)
|
||||
loadComponent: () => import('../user-module/tutorial-edit.component/tutorial-edit.component').then((c) => c.TutorialEditComponent),
|
||||
data:{breadcrumb: 'Edit Tutorial'}
|
||||
},
|
||||
{
|
||||
path: 'ai-models',
|
||||
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent)
|
||||
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent),
|
||||
data:{breadcrumb: 'AI'}
|
||||
},
|
||||
{
|
||||
path: 'images',
|
||||
loadComponent: () => import('../user-module/images.component/images.component').then((c) => c.ImagesComponent)
|
||||
loadComponent: () => import('../user-module/images.component/images.component').then((c) => c.ImagesComponent),
|
||||
data:{breadcrumb: 'Images'}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user