Add User Guide Editor (ref #239)
This commit is contained in:
parent
85689db9aa
commit
9018795e6e
|
@ -1,5 +1,8 @@
|
|||
package eu.eudat.controllers;
|
||||
|
||||
import eu.eudat.models.data.helpers.responses.ResponseItem;
|
||||
import eu.eudat.models.data.userguide.UserGuide;
|
||||
import eu.eudat.types.ApiMessageCode;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
@ -8,9 +11,7 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
@ -39,10 +40,13 @@ public class UserGuideController {
|
|||
String fileName = result.get(0);
|
||||
InputStream is = new FileInputStream(fileName);
|
||||
|
||||
String[] filepath = fileName.split("\\.")[0].split("\\\\");
|
||||
String simplename = filepath[filepath.length - 1];
|
||||
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.setContentLength(is.available());
|
||||
responseHeaders.setContentType(MediaType.TEXT_HTML);
|
||||
responseHeaders.set("Content-Disposition", "attachment;filename=" + fileName);
|
||||
responseHeaders.set("Content-Disposition", "attachment;filename=" + simplename);
|
||||
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
|
||||
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
|
||||
|
||||
|
@ -53,4 +57,14 @@ public class UserGuideController {
|
|||
return new ResponseEntity<>(content, responseHeaders, HttpStatus.OK);
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping(value = "current", method = RequestMethod.POST)
|
||||
public @ResponseBody
|
||||
ResponseEntity<ResponseItem<String>> updateGuide(@RequestBody UserGuide guide) throws Exception {
|
||||
String fileName = this.environment.getProperty("userguide.path") + guide.getName() + ".html";
|
||||
OutputStream os = new FileOutputStream(fileName);
|
||||
os.write(guide.getHtml().getBytes());
|
||||
os.close();
|
||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<String>().status(ApiMessageCode.SUCCESS_MESSAGE).message("Updated").payload("Updated"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package eu.eudat.models.data.userguide;
|
||||
|
||||
public class UserGuide {
|
||||
|
||||
private String name;
|
||||
private String html;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getHtml() {
|
||||
return html;
|
||||
}
|
||||
|
||||
public void setHtml(String html) {
|
||||
this.html = html;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,10 @@
|
|||
"polyfills": "src/polyfills.ts",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
"src/favicon.ico",
|
||||
{ "glob": "**/*", "input": "node_modules/tinymce/skins", "output": "/tinymce/skins/" },
|
||||
{ "glob": "**/*", "input": "node_modules/tinymce/themes", "output": "/tinymce/themes/" },
|
||||
{ "glob": "**/*", "input": "node_modules/tinymce/plugins", "output": "/tinymce/plugins/" }
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
|
@ -27,7 +30,9 @@
|
|||
"node_modules/cookieconsent/build/cookieconsent.min.css"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/cookieconsent/build/cookieconsent.min.js"
|
||||
"node_modules/cookieconsent/build/cookieconsent.min.js",
|
||||
"node_modules/tinymce/tinymce.min.js"
|
||||
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
|
@ -95,11 +100,15 @@
|
|||
"styles": [
|
||||
"src/styles.scss",
|
||||
"src/assets/scss/material-dashboard.scss",
|
||||
"src/assets/css/demo.css"
|
||||
"src/assets/css/demo.css",
|
||||
"node_modules/tinymce/tinymce.min.js"
|
||||
],
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
"src/favicon.ico",
|
||||
{ "glob": "**/*", "input": "node_modules/tinymce/skins", "output": "/tinymce/skins/" },
|
||||
{ "glob": "**/*", "input": "node_modules/tinymce/themes", "output": "/tinymce/themes/" },
|
||||
{ "glob": "**/*", "input": "node_modules/tinymce/plugins", "output": "/tinymce/plugins/" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"@ngx-translate/core": "^11.0.1",
|
||||
"@ngx-translate/http-loader": "^4.0.0",
|
||||
"@swimlane/ngx-datatable": "^16.0.2",
|
||||
"@tinymce/tinymce-angular": "^3.4.0",
|
||||
"@w11k/angular-sticky-things": "^1.1.2",
|
||||
"bootstrap": "^4.3.1",
|
||||
"cookieconsent": "^3.1.1",
|
||||
|
@ -32,6 +33,7 @@
|
|||
"ngx-cookie-service": "^2.2.0",
|
||||
"ngx-cookieconsent": "^2.2.3",
|
||||
"rxjs": "^6.3.2",
|
||||
"tinymce": "^5.1.6",
|
||||
"tslib": "^1.10.0",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"zone.js": "~0.9.1"
|
||||
|
|
|
@ -180,6 +180,14 @@ const appRoutes: Routes = [
|
|||
title: 'GENERAL.TITLES.LANGUAGE-EDITOR'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'user-guide-editor',
|
||||
loadChildren: () => import('./ui/user-guide-editor/user-guide-editor.module').then(m => m.UserGuideEditorModule),
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
title: 'GENERAL.TITLES.GUIDE-EDITOR'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'login/admin',
|
||||
loadChildren: () => import('./ui/auth/admin-login/admin-login.module').then(m => m.AdminLoginModule),
|
||||
|
|
|
@ -22,4 +22,8 @@ export class UserGuideService {
|
|||
'Access-Control-Allow-Credentials': 'true'} });
|
||||
}
|
||||
|
||||
public updateUserGuide(data: any): Observable<String> {
|
||||
return this.http.post<string>(`${this.userGuideUrl}/current`, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,7 +51,8 @@ export const ADMIN_ROUTES: RouteInfo[] = [
|
|||
{ path: '/dmp-profiles', title: 'SIDE-BAR.DMP-TEMPLATES', icon: 'library_books' },
|
||||
{ path: '/dataset-profiles', title: 'SIDE-BAR.DATASET-TEMPLATES', icon: 'library_books' },
|
||||
{ path: '/users', title: 'SIDE-BAR.USERS', icon: 'people' },
|
||||
{ path: '/language-editor', title: 'SIDE-BAR.LANGUAGE-EDITOR', icon: 'language'}
|
||||
{ path: '/language-editor', title: 'SIDE-BAR.LANGUAGE-EDITOR', icon: 'language'},
|
||||
{ path: '/user-guide-editor', title: 'SIDE-BAR.GUIDE-EDITOR', icon: 'import_contacts'}
|
||||
];
|
||||
// export const HISTORY_ROUTES: RouteInfo[] = [
|
||||
// { path: '/typography', title: 'SIDE-BAR.HISTORY-VISITED', icon: 'visibility'},
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<div class="user-guide-editor">
|
||||
<form (ngSubmit)="submit()" [formGroup]="formGroup">
|
||||
<div class="row">
|
||||
<mat-card class="col-md-8 offset-md-2">
|
||||
<mat-card-title><div>{{'GUIDE.TITLE' | translate}}</div></mat-card-title>
|
||||
<mat-card-content>
|
||||
<editor [init]="{
|
||||
base_url: '/tinymce',
|
||||
suffix: '.min',
|
||||
height: 800,
|
||||
menubar: true,
|
||||
plugins: [
|
||||
'advlist autolink lists link image charmap print preview anchor',
|
||||
'searchreplace visualblocks code fullscreen fullpage',
|
||||
'insertdatetime media table paste code help wordcount importcss'
|
||||
],
|
||||
toolbar:
|
||||
'undo redo | formatselect | bold italic backcolor | \
|
||||
alignleft aligncenter alignright alignjustify | \
|
||||
bullist numlist outdent indent | code | preview | removeformat | help'
|
||||
}" formControlName="html"></editor>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<button mat-fab class="mat-fab-bottom-right save-btn" type="submit">
|
||||
<mat-icon class="mat-24" title="save">save</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
.user-guide-editor {
|
||||
padding-top: 5em;
|
||||
padding-bottom: 2em;
|
||||
|
||||
.save-btn {
|
||||
padding-top: inherit !important;
|
||||
top: auto !important;
|
||||
width: 56px !important;
|
||||
bottom: 10px;
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { BaseComponent } from '@common/base/base.component';
|
||||
import { UserGuideService } from '@app/core/services/user-guide/user-guide.service';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { UiNotificationService, SnackBarNotificationLevel } from '@app/core/services/notification/ui-notification-service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { environment } from 'environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-guide-editor',
|
||||
templateUrl: './user-guide-editor.component.html',
|
||||
styleUrls: ['./user-guide-editor.component.scss']
|
||||
})
|
||||
export class UserGuideEditorComponent extends BaseComponent implements OnInit {
|
||||
public formGroup: FormGroup;
|
||||
private formBuilder: FormBuilder;
|
||||
|
||||
constructor(private userGuideService: UserGuideService,
|
||||
private uiNotificationService: UiNotificationService,
|
||||
private translate: TranslateService,
|
||||
private router: Router,
|
||||
) { super(); }
|
||||
|
||||
ngOnInit() {
|
||||
this.formBuilder = new FormBuilder();
|
||||
this.formGroup = this.formBuilder.group({
|
||||
name: [''],
|
||||
html: ['']
|
||||
});
|
||||
this.userGuideService.getUserGuide().pipe(takeUntil(this._destroyed)).subscribe(data => {
|
||||
const contentDispositionHeader = data.headers.get('Content-Disposition');
|
||||
const filename = contentDispositionHeader.split(';')[1].trim().split('=')[1].replace(/"/g, '');
|
||||
|
||||
this.formGroup.get('name').patchValue(filename);
|
||||
this.loadFile(data.body);
|
||||
});
|
||||
}
|
||||
|
||||
private parseText(source: string): string {
|
||||
source = source.replace(/src="images/g, `src="${environment.guideAssets}`);
|
||||
source = source.replace(/\r\n +>/g, '>\r\n');
|
||||
const brokenElements = Array.from(new Set(source.match(/<\/\w+\d* >/g)));
|
||||
if (!isNullOrUndefined(brokenElements)) {
|
||||
brokenElements.forEach((brokenElement) => {
|
||||
const tempvalue = brokenElement.match(/\/\w+\d*/)[0];
|
||||
source = source.replace(new RegExp(brokenElement, 'g'), `<${tempvalue}>\r\n`);
|
||||
});
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
loadFile(data: Blob) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => {
|
||||
let result = this.parseText(reader.result as string);
|
||||
result = result.replace(/class="href" path="/g, 'href="#');
|
||||
this.formGroup.get('html').patchValue(result);
|
||||
}, false);
|
||||
reader.readAsText(data);
|
||||
}
|
||||
|
||||
submit() {
|
||||
let result = this.parseText(this.formGroup.get('html').value);
|
||||
result = result.replace(/href="#/g, 'class="href" path="');
|
||||
this.formGroup.get('html').patchValue(result);
|
||||
this.userGuideService.updateUserGuide(this.formGroup.value).pipe(takeUntil(this._destroyed))
|
||||
.subscribe(
|
||||
complete => {
|
||||
this.onCallbackSuccess(complete);
|
||||
},
|
||||
error => {
|
||||
this.onCallbackError(error);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
onCallbackSuccess(id?: String): void {
|
||||
this.uiNotificationService.snackBarNotification( this.translate.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
|
||||
this.router.navigate(['/reload']).then(() => this.router.navigate(['/user-guide-editor']));
|
||||
}
|
||||
|
||||
onCallbackError(error: any) {
|
||||
this.uiNotificationService.snackBarNotification( error, SnackBarNotificationLevel.Error);
|
||||
//this.validateAllFormFields(this.formGroup);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { EditorModule } from '@tinymce/tinymce-angular';
|
||||
import { UserGuideEditorRoutingModule } from './user-guide-editor.routing';
|
||||
import { UserGuideEditorComponent } from './user-guide-editor.component';
|
||||
import { CommonUiModule } from '@common/ui/common-ui.module';
|
||||
import { CommonFormsModule } from '@common/forms/common-forms.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [UserGuideEditorComponent],
|
||||
imports: [
|
||||
CommonUiModule,
|
||||
CommonFormsModule,
|
||||
UserGuideEditorRoutingModule,
|
||||
EditorModule
|
||||
]
|
||||
})
|
||||
export class UserGuideEditorModule { }
|
|
@ -0,0 +1,15 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { UserGuideEditorComponent } from './user-guide-editor.component';
|
||||
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: UserGuideEditorComponent, canActivate: [AdminAuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class UserGuideEditorRoutingModule { }
|
|
@ -120,7 +120,8 @@
|
|||
"DATASET-UPDATE": "Update Dataset Description",
|
||||
"DATASET-PROFILES-NEW-VERSION": "New Version of Dataset Description Template",
|
||||
"DATASET-PROFILES-CLONE": "New Clone of Dataset Description Template",
|
||||
"LANGUAGE-EDITOR": "Language Editor"
|
||||
"LANGUAGE-EDITOR": "Language Editor",
|
||||
"GUIDE-EDITOR": "User Guide Editor"
|
||||
},
|
||||
"FILE-TYPES": {
|
||||
"PDF": "PDF",
|
||||
|
@ -215,7 +216,8 @@
|
|||
"DATASET-TEMPLATES": "Dataset Description Templates",
|
||||
"DMP-TEMPLATES": "DMP Templates",
|
||||
"USERS": "Users",
|
||||
"LANGUAGE-EDITOR":"Language Editor"
|
||||
"LANGUAGE-EDITOR":"Language Editor",
|
||||
"GUIDE-EDITOR": "User Guide Editor"
|
||||
},
|
||||
"DATASET-PROFILE-EDITOR": {
|
||||
"TITLE": {
|
||||
|
|
|
@ -120,7 +120,8 @@
|
|||
"DATASET-UPDATE": "Update Dataset Description",
|
||||
"DATASET-PROFILES-NEW-VERSION": "New Version of Dataset Description Template",
|
||||
"DATASET-PROFILES-CLONE": "New Clone of Dataset Description Template",
|
||||
"LANGUAGE-EDITOR": "Language Editor"
|
||||
"LANGUAGE-EDITOR": "Language Editor",
|
||||
"GUIDE-EDITOR": "User Guide Editor"
|
||||
},
|
||||
"FILE-TYPES": {
|
||||
"PDF": "PDF",
|
||||
|
@ -215,7 +216,8 @@
|
|||
"DATASET-TEMPLATES": "Dataset Description Templates",
|
||||
"DMP-TEMPLATES": "DMP Templates",
|
||||
"USERS": "Users",
|
||||
"LANGUAGE-EDITOR":"Language Editor"
|
||||
"LANGUAGE-EDITOR":"Language Editor",
|
||||
"GUIDE-EDITOR": "User Guide Editor"
|
||||
},
|
||||
"DATASET-PROFILE-EDITOR": {
|
||||
"TITLE": {
|
||||
|
|
|
@ -44,4 +44,5 @@ export const environment = {
|
|||
logLevels: ["debug", "info", "warning", "error"]
|
||||
},
|
||||
lockInterval: 60000,
|
||||
guideAssets: "assets/images/guide"
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue