diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index 06ac5c2..7edd313 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -1,5 +1,5 @@
-
+
-
\ No newline at end of file
+
diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss
index cb1b034..a1fbbc1 100644
--- a/frontend/src/app/app.component.scss
+++ b/frontend/src/app/app.component.scss
@@ -1,14 +1,14 @@
:host {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+
+ app-header {
+ width: 100%;
+ }
+
+ main {
flex: 1;
-
- app-header {
- width: 100%;
- }
-
- main {
- flex: 1;
- padding: 1em 0;
- }
-}
\ No newline at end of file
+ padding: 1em 0;
+ }
+}
diff --git a/frontend/src/app/app.component.spec.ts b/frontend/src/app/app.component.spec.ts
index 0e07b72..82308c0 100644
--- a/frontend/src/app/app.component.spec.ts
+++ b/frontend/src/app/app.component.spec.ts
@@ -1,5 +1,5 @@
-import { TestBed } from '@angular/core/testing';
-import { AppComponent } from './app.component';
+import {TestBed} from '@angular/core/testing';
+import {AppComponent} from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts
index 0c0a0a2..c78ef51 100644
--- a/frontend/src/app/app.component.ts
+++ b/frontend/src/app/app.component.ts
@@ -1,18 +1,17 @@
-
-import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
-import { HeaderComponent } from './components/header/header.component';
-import { FooterComponent } from './components/footer/footer.component';
+import {Component} from '@angular/core';
+import {RouterOutlet} from '@angular/router';
+import {HeaderComponent} from './components/header/header.component';
+import {FooterComponent} from './components/footer/footer.component';
@Component({
- selector: 'app-root',
- imports: [
+ selector: 'app-root',
+ imports: [
RouterOutlet,
HeaderComponent,
FooterComponent
-],
- templateUrl: './app.component.html',
- styleUrl: './app.component.scss'
+ ],
+ templateUrl: './app.component.html',
+ styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'codiki-ng';
diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts
index d003db6..c3568ca 100644
--- a/frontend/src/app/app.config.ts
+++ b/frontend/src/app/app.config.ts
@@ -1,11 +1,11 @@
-import { ApplicationConfig, inject, provideAppInitializer } from '@angular/core';
-import { provideRouter, withRouterConfig } from '@angular/router';
+import {ApplicationConfig, inject, provideAppInitializer} from '@angular/core';
+import {provideRouter, withRouterConfig} from '@angular/router';
-import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
-import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
-import { routes } from './app.routes';
-import { JwtInterceptor } from './core/interceptor/jwt.interceptor';
-import { AuthenticationService } from './core/service/authentication.service';
+import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
+import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
+import {routes} from './app.routes';
+import {JwtInterceptor} from './core/interceptor/jwt.interceptor';
+import {AuthenticationService} from './core/service/authentication.service';
export const appConfig: ApplicationConfig = {
providers: [
@@ -15,13 +15,13 @@ export const appConfig: ApplicationConfig = {
paramsInheritanceStrategy: 'always',
onSameUrlNavigation: 'reload'
})
- ),
+ ),
provideAnimationsAsync(),
provideHttpClient(withInterceptorsFromDi()),
- { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
+ {provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true},
provideAppInitializer(() => {
- const initializerFn = ((authenticationService: AuthenticationService) => () => authenticationService.startAuthenticationCheckingProcess())(inject(AuthenticationService));
- return initializerFn();
- })
+ const initializerFn = ((authenticationService: AuthenticationService) => () => authenticationService.startAuthenticationCheckingProcess())(inject(AuthenticationService));
+ return initializerFn();
+ })
]
};
diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts
index 40eb449..c00ad23 100644
--- a/frontend/src/app/app.routes.ts
+++ b/frontend/src/app/app.routes.ts
@@ -1,43 +1,43 @@
-import { Routes } from '@angular/router';
-import { alreadyAuthenticatedGuard } from './core/guard/already-authenticated.guard';
+import {Routes} from '@angular/router';
+import {alreadyAuthenticatedGuard} from './core/guard/already-authenticated.guard';
export const routes: Routes = [
- {
- path: 'login',
- loadComponent: () => import('./pages/login/login.component').then(module => module.LoginComponent),
- canActivate: [alreadyAuthenticatedGuard]
- },
- {
- path: 'signin',
- loadComponent: () => import('./pages/signin/signin.component').then(module => module.SigninComponent),
- canActivate: [alreadyAuthenticatedGuard]
- },
- {
- path: 'disconnect',
- loadComponent: () => import('./pages/disconnection/disconnection.component').then(module => module.DisconnectionComponent)
- },
- {
- path: 'publications/new',
- loadChildren: () => import('./pages/publication-creation/publication-creation.routes').then(module => module.ROUTES)
- },
- {
- path: 'publications/:publicationId',
- loadComponent: () => import('./pages/publication/publication.component').then(module => module.PublicationComponent)
- },
- {
- path: 'publications/:publicationId/edit',
- loadChildren: () => import('./pages/publication-update/publication-update.routes').then(module => module.ROUTES)
- },
- {
- path: 'publications',
- loadComponent: () => import('./pages/search-publications/search-publications.component').then(module => module.SearchPublicationsComponent)
- },
- {
- path: 'my-publications',
- loadChildren: () => import('./pages/my-publications/my-publications.routes').then(module => module.ROUTES)
- },
- {
- path: '**',
- loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent)
- }
+ {
+ path: 'login',
+ loadComponent: () => import('./pages/login/login.component').then(module => module.LoginComponent),
+ canActivate: [alreadyAuthenticatedGuard]
+ },
+ {
+ path: 'signin',
+ loadComponent: () => import('./pages/signin/signin.component').then(module => module.SigninComponent),
+ canActivate: [alreadyAuthenticatedGuard]
+ },
+ {
+ path: 'disconnect',
+ loadComponent: () => import('./pages/disconnection/disconnection.component').then(module => module.DisconnectionComponent)
+ },
+ {
+ path: 'publications/new',
+ loadChildren: () => import('./pages/publication-creation/publication-creation.routes').then(module => module.ROUTES)
+ },
+ {
+ path: 'publications/:publicationId',
+ loadComponent: () => import('./pages/publication/publication.component').then(module => module.PublicationComponent)
+ },
+ {
+ path: 'publications/:publicationId/edit',
+ loadChildren: () => import('./pages/publication-update/publication-update.routes').then(module => module.ROUTES)
+ },
+ {
+ path: 'publications',
+ loadComponent: () => import('./pages/search-publications/search-publications.component').then(module => module.SearchPublicationsComponent)
+ },
+ {
+ path: 'my-publications',
+ loadChildren: () => import('./pages/my-publications/my-publications.routes').then(module => module.ROUTES)
+ },
+ {
+ path: '**',
+ loadComponent: () => import('./pages/home/home.component').then(module => module.HomeComponent)
+ }
];
diff --git a/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.html b/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.html
index 74b0190..1157116 100644
--- a/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.html
+++ b/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.html
@@ -1,10 +1,10 @@
-
{{title}}
-{{description}}
+{{ title }}
+{{ description }}
\ No newline at end of file
+
+
+
diff --git a/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.scss b/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.scss
index 23e6e87..62d7b50 100644
--- a/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.scss
+++ b/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.scss
@@ -1,12 +1,12 @@
:host {
- display: flex;
- flex-direction: column;
- text-align: center;
- padding: 1em;
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ padding: 1em;
- footer {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- }
-}
\ No newline at end of file
+ footer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ }
+}
diff --git a/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.ts b/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.ts
index 71b3fef..f8bcd56 100644
--- a/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.ts
+++ b/frontend/src/app/components/confirmation-dialog/confirmation-dialog.component.ts
@@ -1,35 +1,35 @@
-import { Component, inject, Input } from "@angular/core";
-import { MatRippleModule } from "@angular/material/core";
-import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
+import {Component, inject} from "@angular/core";
+import {MatRippleModule} from "@angular/material/core";
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
export interface ConfirmationDialogData {
- title: string;
- description: string;
+ title: string;
+ description: string;
}
@Component({
- selector: 'app-confirmation-dialog',
- templateUrl: './confirmation-dialog.component.html',
- styleUrl: './confirmation-dialog.component.scss',
- imports: [MatRippleModule]
+ selector: 'app-confirmation-dialog',
+ templateUrl: './confirmation-dialog.component.html',
+ styleUrl: './confirmation-dialog.component.scss',
+ imports: [MatRippleModule]
})
export class ConfirmationDialog {
- private readonly dialogRef = inject(MatDialogRef);
- data: ConfirmationDialogData = inject(MAT_DIALOG_DATA);
+ private readonly dialogRef = inject(MatDialogRef);
+ data: ConfirmationDialogData = inject(MAT_DIALOG_DATA);
- get title(): string {
- return this.data.title;
- }
+ get title(): string {
+ return this.data.title;
+ }
- get description(): string {
- return this.data.description;
- }
+ get description(): string {
+ return this.data.description;
+ }
- closeAndValidate(): void {
- this.dialogRef.close(true);
- }
+ closeAndValidate(): void {
+ this.dialogRef.close(true);
+ }
- closeDialog(): void {
- this.dialogRef.close(false);
- }
-}
\ No newline at end of file
+ closeDialog(): void {
+ this.dialogRef.close(false);
+ }
+}
diff --git a/frontend/src/app/components/footer/footer.component.html b/frontend/src/app/components/footer/footer.component.html
index f025fd8..65a8b05 100644
--- a/frontend/src/app/components/footer/footer.component.html
+++ b/frontend/src/app/components/footer/footer.component.html
@@ -1,14 +1,14 @@
-
©
- 2016 - 2026 All rights reserved
- -
- 2.2
-
- favorite
-
+
©
+ 2016 - 2026 All rights reserved
+ -
+ 2.2
+
+ favorite
+
- menu_book
- -
- Development realised by Florian THIERRY
+ menu_book
+ -
+ Development realised by Florian THIERRY
diff --git a/frontend/src/app/components/footer/footer.component.scss b/frontend/src/app/components/footer/footer.component.scss
index 1784222..16d10cb 100644
--- a/frontend/src/app/components/footer/footer.component.scss
+++ b/frontend/src/app/components/footer/footer.component.scss
@@ -1,32 +1,33 @@
:host {
- background-color: #3f51b5;
- color: rgba(255,255,255,.6);
+ background-color: #3f51b5;
+ color: rgba(255, 255, 255, .6);
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ align-items: center;
+ padding: .5em;
+ font-size: 1.1em;
+
+ div {
display: flex;
flex-direction: row;
- justify-content: space-around;
align-items: center;
- padding: .5em;
- font-size: 1.1em;
+ gap: .2em;
- div {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: .2em;
-
- .copy-left {
- transform: rotate(180deg);
- }
- a {
- text-decoration: none;
- color: rgba(255,255,255,.6);
- }
-
- mat-icon {
- font-size: 1em;
- display: flex;
- justify-content: center;
- align-items: center;
- }
+ .copy-left {
+ transform: rotate(180deg);
}
+
+ a {
+ text-decoration: none;
+ color: rgba(255, 255, 255, .6);
+ }
+
+ mat-icon {
+ font-size: 1em;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ }
}
diff --git a/frontend/src/app/components/footer/footer.component.ts b/frontend/src/app/components/footer/footer.component.ts
index 0e62bc6..bbf699b 100644
--- a/frontend/src/app/components/footer/footer.component.ts
+++ b/frontend/src/app/components/footer/footer.component.ts
@@ -1,12 +1,13 @@
-import { Component } from '@angular/core';
-import { MatIconModule } from '@angular/material/icon';
-import { MatTooltipModule } from '@angular/material/tooltip';
-import { RouterModule } from '@angular/router';
+import {Component} from '@angular/core';
+import {MatIconModule} from '@angular/material/icon';
+import {MatTooltipModule} from '@angular/material/tooltip';
+import {RouterModule} from '@angular/router';
@Component({
- selector: 'app-footer',
- imports: [MatIconModule, MatTooltipModule, RouterModule],
- templateUrl: './footer.component.html',
- styleUrl: './footer.component.scss'
+ selector: 'app-footer',
+ imports: [MatIconModule, MatTooltipModule, RouterModule],
+ templateUrl: './footer.component.html',
+ styleUrl: './footer.component.scss'
})
-export class FooterComponent {}
+export class FooterComponent {
+}
diff --git a/frontend/src/app/components/header/header.component.html b/frontend/src/app/components/header/header.component.html
index 17a4b46..3f21547 100644
--- a/frontend/src/app/components/header/header.component.html
+++ b/frontend/src/app/components/header/header.component.html
@@ -1,44 +1,44 @@
- @if (isAuthenticated) {
-
-
-
-
- } @else {
-
Login
- }
+ @if (isAuthenticated) {
+
+
+
+
+ } @else {
+
Login
+ }
-
\ No newline at end of file
+
diff --git a/frontend/src/app/components/header/header.component.scss b/frontend/src/app/components/header/header.component.scss
index a17213b..6ef4a16 100644
--- a/frontend/src/app/components/header/header.component.scss
+++ b/frontend/src/app/components/header/header.component.scss
@@ -1,148 +1,148 @@
$headerHeight: 3.5em;
:host {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ background-color: #3f51b5;
+ color: white;
+ position: relative;
+ height: $headerHeight;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
+
+ div {
display: flex;
flex-direction: row;
- justify-content: space-between;
- background-color: #3f51b5;
- color: white;
+ justify-content: center;
position: relative;
height: $headerHeight;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
- div {
+ &.left {
+ position: absolute;
+ top: 0;
+ left: 0;
+ align-items: center;
+ gap: 1em;
+ padding: 0 1em;
+ z-index: 2;
+
+ a {
display: flex;
flex-direction: row;
justify-content: center;
- position: relative;
- height: $headerHeight;
+ align-items: center;
+ color: white;
+ text-decoration: none;
+ gap: .5em;
- &.left {
- position: absolute;
- top: 0;
- left: 0;
- align-items: center;
- gap: 1em;
- padding: 0 1em;
- z-index: 2;
-
- a {
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- color: white;
- text-decoration: none;
- gap: .5em;
-
- img {
- $imageSize: 2em;
- width: $imageSize;
- height: $imageSize;
- }
-
- .title {
- font-size: 1.5em;
- display: none;
-
- @media screen and (min-width: 600px) {
- display: block;
- }
- }
- }
+ img {
+ $imageSize: 2em;
+ width: $imageSize;
+ height: $imageSize;
}
- &.middle {
- flex: 1;
- $borderRadiusValue: 10em;
- position: relative;
- transition: max-width .2s ease-in-out;
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1;
-
- app-publications-search-bar {
- width: 100%;
- max-width: 12em;
+ .title {
+ font-size: 1.5em;
+ display: none;
- @media screen and (min-width: 435px) {
- max-width: 16em;
- }
-
- @media screen and (min-width: 500px) {
- max-width: 20em;
- }
-
- @media screen and (min-width: 700px) {
- max-width: 24em;
- }
-
- @media screen and (min-width: 800px) {
- max-width: 32em;
- }
-
- @media screen and (min-width: 900px) {
- max-width: 38em;
- }
-
- @media screen and (min-width: 1000px) {
- max-width: 45em;
- }
-
- @media screen and (min-width: 1100px) {
- max-width: 50em;
- }
- }
- }
-
- &.right {
- position: absolute;
- top: 0;
- right: 0;
- z-index: 2;
- margin-right: .5em;
-
- a, button {
- margin: .5em;
- }
+ @media screen and (min-width: 600px) {
+ display: block;
+ }
}
+ }
}
+
+ &.middle {
+ flex: 1;
+ $borderRadiusValue: 10em;
+ position: relative;
+ transition: max-width .2s ease-in-out;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1;
+
+ app-publications-search-bar {
+ width: 100%;
+ max-width: 12em;
+
+ @media screen and (min-width: 435px) {
+ max-width: 16em;
+ }
+
+ @media screen and (min-width: 500px) {
+ max-width: 20em;
+ }
+
+ @media screen and (min-width: 700px) {
+ max-width: 24em;
+ }
+
+ @media screen and (min-width: 800px) {
+ max-width: 32em;
+ }
+
+ @media screen and (min-width: 900px) {
+ max-width: 38em;
+ }
+
+ @media screen and (min-width: 1000px) {
+ max-width: 45em;
+ }
+
+ @media screen and (min-width: 1100px) {
+ max-width: 50em;
+ }
+ }
+ }
+
+ &.right {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 2;
+ margin-right: .5em;
+
+ a, button {
+ margin: .5em;
+ }
+ }
+ }
}
app-side-menu {
- height: 100%;
+ height: 100%;
}
.authenticated-user-menu {
+ display: flex;
+ flex-direction: column;
+ padding: 0.2em 0;
+
+ a {
+ flex: 1;
display: flex;
- flex-direction: column;
- padding: 0.2em 0;
+ flex-direction: row;
+ align-items: center;
+ text-decoration: none;
+ background-color: white;
+ color: black;
+ padding: 1em;
+ gap: .5em;
+ transition: background-color .2s ease-in-out, color .2s ease-in-out;
- a {
- flex: 1;
- display: flex;
- flex-direction: row;
- align-items: center;
- text-decoration: none;
- background-color: white;
- color: black;
- padding: 1em;
- gap: .5em;
- transition: background-color .2s ease-in-out, color .2s ease-in-out;
-
- &:hover {
- background-color: #5c6bc0;
- color: white;
- }
-
- &.disconnection {
- color: #D50000;
-
- &:hover {
- background-color: #E53935;
- color: white;
- }
- }
+ &:hover {
+ background-color: #5c6bc0;
+ color: white;
}
-}
\ No newline at end of file
+
+ &.disconnection {
+ color: #D50000;
+
+ &:hover {
+ background-color: #E53935;
+ color: white;
+ }
+ }
+ }
+}
diff --git a/frontend/src/app/components/header/header.component.ts b/frontend/src/app/components/header/header.component.ts
index b8c7df3..3dc4184 100644
--- a/frontend/src/app/components/header/header.component.ts
+++ b/frontend/src/app/components/header/header.component.ts
@@ -1,19 +1,18 @@
-
-import { Component, inject } from '@angular/core';
-import { FormControl, ReactiveFormsModule } from '@angular/forms';
-import { MatButtonModule } from '@angular/material/button';
-import { MatRippleModule } from '@angular/material/core';
-import { MatIconModule } from '@angular/material/icon';
-import { MatMenuModule } from '@angular/material/menu';
-import { MatTooltipModule } from '@angular/material/tooltip';
-import { RouterModule } from '@angular/router';
-import { AuthenticationService } from '../../core/service/authentication.service';
-import { PublicationsSearchBarComponent } from '../publications-search-bar/publications-search-bar.component';
-import { SideMenuComponent } from '../side-menu/side-menu.component';
+import {Component, inject} from '@angular/core';
+import {FormControl, ReactiveFormsModule} from '@angular/forms';
+import {MatButtonModule} from '@angular/material/button';
+import {MatRippleModule} from '@angular/material/core';
+import {MatIconModule} from '@angular/material/icon';
+import {MatMenuModule} from '@angular/material/menu';
+import {MatTooltipModule} from '@angular/material/tooltip';
+import {RouterModule} from '@angular/router';
+import {AuthenticationService} from '../../core/service/authentication.service';
+import {PublicationsSearchBarComponent} from '../publications-search-bar/publications-search-bar.component';
+import {SideMenuComponent} from '../side-menu/side-menu.component';
@Component({
- selector: 'app-header',
- imports: [
+ selector: 'app-header',
+ imports: [
MatButtonModule,
MatIconModule,
MatMenuModule,
@@ -23,9 +22,9 @@ import { SideMenuComponent } from '../side-menu/side-menu.component';
ReactiveFormsModule,
RouterModule,
SideMenuComponent
-],
- templateUrl: './header.component.html',
- styleUrl: './header.component.scss'
+ ],
+ templateUrl: './header.component.html',
+ styleUrl: './header.component.scss'
})
export class HeaderComponent {
private authenticationService = inject(AuthenticationService);
diff --git a/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.html b/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.html
index abd2006..cde3d87 100644
--- a/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.html
+++ b/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.html
@@ -4,34 +4,34 @@
matTooltip="Close"
matRipple
i18n-matTooltip>
- close
+ close
- Add a code block
+ Add a code block
diff --git a/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.scss b/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.scss
index e21d3e8..9f8e59f 100644
--- a/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.scss
+++ b/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.scss
@@ -1,30 +1,30 @@
:host {
+ display: flex;
+ flex-direction: column;
+ padding: 1em;
+ gap: 1em;
+ position: relative;
+ max-height: 90vh;
+
+ header {
+ flex: 1;
display: flex;
- flex-direction: column;
- padding: 1em;
- gap: 1em;
- position: relative;
- max-height: 90vh;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ }
- header {
- flex: 1;
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- }
+ form {
+ div {
+ &.form-content {
+ mat-form-field {
+ width: 100%;
- form {
- div {
- &.form-content {
- mat-form-field {
- width: 100%;
-
- textarea {
- height: 30vh;
- }
- }
- }
+ textarea {
+ height: 30vh;
+ }
}
+ }
}
-}
\ No newline at end of file
+ }
+}
diff --git a/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.ts b/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.ts
index 3453a45..a2d7c8d 100644
--- a/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.ts
+++ b/frontend/src/app/components/publication-edition/code-block-dialog/code-block-dialog.component.ts
@@ -1,119 +1,119 @@
-import { Component, inject } from "@angular/core";
-import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
-import { MatRippleModule } from "@angular/material/core";
-import { MatDialogRef } from "@angular/material/dialog";
-import { MatFormFieldModule } from "@angular/material/form-field";
-import { MatIcon } from "@angular/material/icon";
-import { MatInputModule } from "@angular/material/input";
-import { MatSelectModule } from '@angular/material/select';
-import { MatTooltip } from "@angular/material/tooltip";
+import {Component, inject} from "@angular/core";
+import {FormBuilder, FormControl, ReactiveFormsModule, Validators} from "@angular/forms";
+import {MatRippleModule} from "@angular/material/core";
+import {MatDialogRef} from "@angular/material/dialog";
+import {MatFormFieldModule} from "@angular/material/form-field";
+import {MatIcon} from "@angular/material/icon";
+import {MatInputModule} from "@angular/material/input";
+import {MatSelectModule} from '@angular/material/select';
+import {MatTooltip} from "@angular/material/tooltip";
export interface ProgramingLanguage {
- code: string;
- label: string;
+ code: string;
+ label: string;
}
export const PROGRAMMING_LANGUAGES: ProgramingLanguage[] = [
- {
- code: 'bash',
- label: 'Bash'
- },
- {
- code: 'c',
- label: 'C'
- },
- {
- code: 'cpp',
- label: 'C++'
- },
- {
- code: 'cs',
- label: 'C#'
- },
- {
- code: 'lua',
- label: 'Lua'
- },
- {
- code: 'java',
- label: 'Java'
- },
- {
- code: 'json5',
- label: 'JSON'
- },
- {
- code: 'kt',
- label: 'Kotlin'
- },
- {
- code: 'markup',
- label: 'html/xml'
- },
- {
- code: 'php',
- label: 'PHP'
- },
- {
- code: 'plsql',
- label: 'PL/SQL'
- },
- {
- code: 'python',
- label: 'Python'
- },
- {
- code: 'powershell',
- label: 'PowerShell'
- },
- {
- code: 'rust',
- label: 'Rust'
- },
- {
- code: 'sql',
- label: 'SQL'
- },
- {
- code: 'ts',
- label: 'Typescript'
- },
- {
- code: 'yml',
- label: 'YAML'
- },
+ {
+ code: 'bash',
+ label: 'Bash'
+ },
+ {
+ code: 'c',
+ label: 'C'
+ },
+ {
+ code: 'cpp',
+ label: 'C++'
+ },
+ {
+ code: 'cs',
+ label: 'C#'
+ },
+ {
+ code: 'lua',
+ label: 'Lua'
+ },
+ {
+ code: 'java',
+ label: 'Java'
+ },
+ {
+ code: 'json5',
+ label: 'JSON'
+ },
+ {
+ code: 'kt',
+ label: 'Kotlin'
+ },
+ {
+ code: 'markup',
+ label: 'html/xml'
+ },
+ {
+ code: 'php',
+ label: 'PHP'
+ },
+ {
+ code: 'plsql',
+ label: 'PL/SQL'
+ },
+ {
+ code: 'python',
+ label: 'Python'
+ },
+ {
+ code: 'powershell',
+ label: 'PowerShell'
+ },
+ {
+ code: 'rust',
+ label: 'Rust'
+ },
+ {
+ code: 'sql',
+ label: 'SQL'
+ },
+ {
+ code: 'ts',
+ label: 'Typescript'
+ },
+ {
+ code: 'yml',
+ label: 'YAML'
+ },
];
@Component({
- selector: 'app-code-block-dialog',
- templateUrl: './code-block-dialog.component.html',
- styleUrl: './code-block-dialog.component.scss',
- imports: [
- MatFormFieldModule,
- MatIcon,
- MatInputModule,
- MatRippleModule,
- MatSelectModule,
- MatTooltip,
- ReactiveFormsModule,
- ]
+ selector: 'app-code-block-dialog',
+ templateUrl: './code-block-dialog.component.html',
+ styleUrl: './code-block-dialog.component.scss',
+ imports: [
+ MatFormFieldModule,
+ MatIcon,
+ MatInputModule,
+ MatRippleModule,
+ MatSelectModule,
+ MatTooltip,
+ ReactiveFormsModule,
+ ]
})
export class CodeBlockDialog {
- private readonly dialogRef = inject(MatDialogRef);
- private formBuilder = inject(FormBuilder);
- programmingLanguages = PROGRAMMING_LANGUAGES;
- formGroup = this.formBuilder.group({
- programmingLanguage: new FormControl('', Validators.required),
- codeBlock: new FormControl('', Validators.required)
- });
+ private readonly dialogRef = inject(MatDialogRef);
+ private formBuilder = inject(FormBuilder);
+ programmingLanguages = PROGRAMMING_LANGUAGES;
+ formGroup = this.formBuilder.group({
+ programmingLanguage: new FormControl('', Validators.required),
+ codeBlock: new FormControl('', Validators.required)
+ });
- closeAndValidate(): void {
- if (this.formGroup.valid) {
- this.dialogRef.close(this.formGroup.value);
- }
+ closeAndValidate(): void {
+ if (this.formGroup.valid) {
+ this.dialogRef.close(this.formGroup.value);
}
+ }
- closeDialog(): void {
- this.dialogRef.close();
- }
-}
\ No newline at end of file
+ closeDialog(): void {
+ this.dialogRef.close();
+ }
+}
diff --git a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.html b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.html
index 88dab07..9d4b3c0 100644
--- a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.html
+++ b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.html
@@ -4,40 +4,41 @@
matTooltip="Close"
matRipple
i18n-matTooltip>
- close
+ close
- Select an illustration
+ Select an illustration
- @if (isLoading()) {
-
Pictures loading...
-
+ @if (isLoading()) {
+
Pictures loading...
+
+ } @else {
+ @if (pictures.length) {
+ @for (picture of pictures; track picture) {
+

+ }
} @else {
- @if (pictures.length) {
- @for(picture of pictures; track picture) {
-

- }
- } @else {
-
There is no any picture.
- }
+
There is no any picture.
}
+ }
diff --git a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.scss b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.scss
index d5ead41..0d0d121 100644
--- a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.scss
+++ b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.scss
@@ -1,83 +1,83 @@
:host {
+ display: flex;
+ flex-direction: column;
+ padding: 1em;
+ gap: 1em;
+ position: relative;
+ max-height: 90vh;
+
+ header {
+ flex: 1;
display: flex;
- flex-direction: column;
- padding: 1em;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .picture-container {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
gap: 1em;
- position: relative;
- max-height: 90vh;
+ max-height: 30em;
+ overflow-y: auto;
+ min-height: 10em;
+ padding: .5em 0;
- header {
- flex: 1;
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
+ img {
+ width: 15em;
+ height: 10em;
+ object-fit: cover;
+ border-radius: 1em;
+ opacity: .9;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
+ transition: opacity .2s ease-in-out, box-shadow .2s ease-in-out;
+
+ &:hover {
+ cursor: pointer;
+ opacity: 1;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .32), 0 2px 10px 0 rgba(0, 0, 0, .24);
+ }
+ }
+ }
+
+ footer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+
+ button {
+ padding: .8em 1.2em;
+ border-radius: 10em;
+ border: none;
+ background-color: #3f51b5;
+ color: white;
+ transition: background-color .2s ease-in-out;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ background-color: #5b6ed8;
+ }
+
+ &.secondary {
+ color: #3f51b5;
+ background-color: white;
+
+ &:hover {
+ background-color: #f2f4ff;
+ cursor: pointer;
+ }
+ }
}
- .picture-container {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: center;
- align-items: center;
- gap: 1em;
- max-height: 30em;
- overflow-y: auto;
- min-height: 10em;
- padding: .5em 0;
-
- img {
- width: 15em;
- height: 10em;
- object-fit: cover;
- border-radius: 1em;
- opacity: .9;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
- transition: opacity .2s ease-in-out, box-shadow .2s ease-in-out;
-
- &:hover {
- cursor: pointer;
- opacity: 1;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.32),0 2px 10px 0 rgba(0,0,0,.24);
- }
- }
+ input[type=file] {
+ display: none;
}
-
- footer {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
-
- button {
- padding: .8em 1.2em;
- border-radius: 10em;
- border: none;
- background-color: #3f51b5;
- color: white;
- transition: background-color .2s ease-in-out;
- cursor: pointer;
- display: flex;
- justify-content: center;
- align-items: center;
-
- &:hover {
- background-color: #5b6ed8;
- }
-
- &.secondary {
- color: #3f51b5;
- background-color: white;
-
- &:hover {
- background-color: #f2f4ff;
- cursor: pointer;
- }
- }
- }
-
- input[type=file] {
- display: none;
- }
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.ts b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.ts
index 25ad430..bbf8eac 100644
--- a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.ts
+++ b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.component.ts
@@ -1,74 +1,74 @@
import {Component, inject, OnInit, signal} from "@angular/core";
-import { Picture } from "../../../core/rest-services/picture/model/picture";
+import {Picture} from "../../../core/rest-services/picture/model/picture";
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
-import { MatSnackBar } from "@angular/material/snack-bar";
-import { PictureRestService } from "../../../core/rest-services/picture/picture.rest-service";
-import { MatIcon } from "@angular/material/icon";
-import { MatDialogRef } from "@angular/material/dialog";
+import {MatSnackBar} from "@angular/material/snack-bar";
+import {PictureRestService} from "../../../core/rest-services/picture/picture.rest-service";
+import {MatIcon} from "@angular/material/icon";
+import {MatDialogRef} from "@angular/material/dialog";
import {MatRippleModule} from '@angular/material/core';
-import { MatTooltip } from "@angular/material/tooltip";
+import {MatTooltip} from "@angular/material/tooltip";
@Component({
- selector: 'app-picture-selection',
- templateUrl: './picture-selection-dialog.component.html',
- styleUrl: './picture-selection-dialog.component.scss',
- imports: [
- MatIcon,
- MatRippleModule,
- MatProgressSpinnerModule,
- MatTooltip
- ]
+ selector: 'app-picture-selection',
+ templateUrl: './picture-selection-dialog.component.html',
+ styleUrl: './picture-selection-dialog.component.scss',
+ imports: [
+ MatIcon,
+ MatRippleModule,
+ MatProgressSpinnerModule,
+ MatTooltip
+ ]
})
export class PictureSelectionDialog implements OnInit {
- private readonly pictureRestService = inject(PictureRestService);
- private readonly snackBar = inject(MatSnackBar);
- private readonly dialogRef = inject(MatDialogRef);
+ private readonly pictureRestService = inject(PictureRestService);
+ private readonly snackBar = inject(MatSnackBar);
+ private readonly dialogRef = inject(MatDialogRef);
- isLoading = signal(false);
- isLoaded = signal(false);
- pictures: Picture[] = [];
+ isLoading = signal(false);
+ isLoaded = signal(false);
+ pictures: Picture[] = [];
- ngOnInit(): void {
- this.isLoading.set(true);
- this.pictureRestService.getAllOfCurrentUser()
- .then(pictures => {
- this.pictures = pictures;
- })
- .catch(error => {
- if (error.status === 401) {
- this.dialogRef.close();
- } else {
- const errorMessage = $localize`An error occurred while loading pictures.`;
- console.error(errorMessage, error);
- this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
- }
- })
- .finally(() => {
- this.isLoading.set(false);
- this.isLoaded.set(true);
- });
- }
-
- selectPicture(picture: Picture): void {
- this.dialogRef.close(picture.id);
- }
-
- closeDialog(): void {
- this.dialogRef.close();
- }
-
- uploadPicture(fileSelectionEvent: any): void {
- const pictureFile = fileSelectionEvent.target.files[0];
- if (pictureFile) {
- this.pictureRestService.uploadPicture(pictureFile)
- .then(pictureId => {
- this.dialogRef.close(pictureId);
- })
- .catch(error => {
- const errorMessage = $localize`A technical error occurred while uploading your picture.`;
- console.error(errorMessage, error);
- this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
- });
+ ngOnInit(): void {
+ this.isLoading.set(true);
+ this.pictureRestService.getAllOfCurrentUser()
+ .then(pictures => {
+ this.pictures = pictures;
+ })
+ .catch(error => {
+ if (error.status === 401) {
+ this.dialogRef.close();
+ } else {
+ const errorMessage = $localize`An error occurred while loading pictures.`;
+ console.error(errorMessage, error);
+ this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
}
+ })
+ .finally(() => {
+ this.isLoading.set(false);
+ this.isLoaded.set(true);
+ });
+ }
+
+ selectPicture(picture: Picture): void {
+ this.dialogRef.close(picture.id);
+ }
+
+ closeDialog(): void {
+ this.dialogRef.close();
+ }
+
+ uploadPicture(fileSelectionEvent: any): void {
+ const pictureFile = fileSelectionEvent.target.files[0];
+ if (pictureFile) {
+ this.pictureRestService.uploadPicture(pictureFile)
+ .then(pictureId => {
+ this.dialogRef.close(pictureId);
+ })
+ .catch(error => {
+ const errorMessage = $localize`A technical error occurred while uploading your picture.`;
+ console.error(errorMessage, error);
+ this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
+ });
}
+ }
}
diff --git a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.service.ts b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.service.ts
index f7d5673..29f0567 100644
--- a/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.service.ts
+++ b/frontend/src/app/components/publication-edition/picture-selection-dialog/picture-selection-dialog.service.ts
@@ -1,24 +1,24 @@
-import { inject, Injectable } from "@angular/core";
-import { PictureRestService } from "../../../core/rest-services/picture/picture.rest-service";
-import { MatSnackBar } from "@angular/material/snack-bar";
-import { MatDialogRef } from "@angular/material/dialog";
-import { PictureSelectionDialog } from "./picture-selection-dialog.component";
+import {inject, Injectable} from "@angular/core";
+import {PictureRestService} from "../../../core/rest-services/picture/picture.rest-service";
+import {MatSnackBar} from "@angular/material/snack-bar";
+import {MatDialogRef} from "@angular/material/dialog";
+import {PictureSelectionDialog} from "./picture-selection-dialog.component";
@Injectable()
export class PictureSelectionDialogService {
- private pictureRestService = inject(PictureRestService);
- private snackBar = inject(MatSnackBar);
- private readonly dialogRef = inject(MatDialogRef);
+ private pictureRestService = inject(PictureRestService);
+ private snackBar = inject(MatSnackBar);
+ private readonly dialogRef = inject(MatDialogRef);
- uploadPicture(pictureFile: File): void {
- this.pictureRestService.uploadPicture(pictureFile)
- .then(pictureId => {
- this.dialogRef.close(pictureId);
- })
- .catch(error => {
- const errorMessage = $localize`An error occured while uploading a picture...`;
- console.error(errorMessage, error);
- this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
- });
- }
-}
\ No newline at end of file
+ uploadPicture(pictureFile: File): void {
+ this.pictureRestService.uploadPicture(pictureFile)
+ .then(pictureId => {
+ this.dialogRef.close(pictureId);
+ })
+ .catch(error => {
+ const errorMessage = $localize`An error occured while uploading a picture...`;
+ console.error(errorMessage, error);
+ this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
+ });
+ }
+}
diff --git a/frontend/src/app/components/publication-edition/publication-edition.component.html b/frontend/src/app/components/publication-edition/publication-edition.component.html
index 0f59725..8a2d8c3 100644
--- a/frontend/src/app/components/publication-edition/publication-edition.component.html
+++ b/frontend/src/app/components/publication-edition/publication-edition.component.html
@@ -1,132 +1,133 @@
diff --git a/frontend/src/app/components/publication-edition/publication-edition.component.scss b/frontend/src/app/components/publication-edition/publication-edition.component.scss
index 9f564de..83a06e7 100644
--- a/frontend/src/app/components/publication-edition/publication-edition.component.scss
+++ b/frontend/src/app/components/publication-edition/publication-edition.component.scss
@@ -1,170 +1,170 @@
:host {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
- form {
- margin: 1em;
- max-width: 80em;
- width: 90%;
- border-radius: .5em;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
+ form {
+ margin: 1em;
+ max-width: 80em;
+ width: 90%;
+ border-radius: .5em;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
- & > header {
- padding: 2em;
- background-color: #3f51b5;
- color: white;
- border-radius: .5em .5em 0 0;
+ & > header {
+ padding: 2em;
+ background-color: #3f51b5;
+ color: white;
+ border-radius: .5em .5em 0 0;
- h1 {
- font-size: 2em;
- margin-bottom: .5em;
- }
- }
-
- footer {
- padding: 2em;
- display: flex;
- flex-direction: row-reverse;
- justify-content: space-between;
- align-items: center;
- }
+ h1 {
+ font-size: 2em;
+ margin-bottom: .5em;
+ }
}
+
+ footer {
+ padding: 2em;
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: space-between;
+ align-items: center;
+ }
+ }
}
.form-content {
- padding: 2em;
- padding-bottom: 0;
+ padding: 2em;
+ padding-bottom: 0;
+ display: flex;
+ flex-direction: column;
+ gap: .5em;
+
+ mat-form-field {
+ textarea {
+ height: 20em;
+ }
+ }
+
+ .first-part {
display: flex;
- flex-direction: column;
+ flex-direction: column-reverse;
+ gap: 1em;
+
+ @media screen and (min-width: 600px) {
+ flex-direction: row;
+
+ div {
+ flex: 1 0;
+
+ &.picture-container {
+ max-width: 20em;
+
+ img {
+ max-height: 15em;
+ max-width: 20em;
+ }
+ }
+ }
+ }
+
+ div {
+ flex: 1 0 50%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+
+ &.picture-container {
+ img {
+ flex: 1;
+ object-fit: cover;
+ width: 100%;
+ cursor: pointer;
+ border-radius: 1em;
+ opacity: .9;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
+ transition: opacity .2s ease-in-out, box-shadow .2s ease-in-out;
+
+ &:hover {
+ cursor: pointer;
+ opacity: 1;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .32), 0 2px 10px 0 rgba(0, 0, 0, .24);
+ }
+ }
+ }
+ }
+ }
+
+ .actions {
+ display: flex;
+ flex-direction: row;
gap: .5em;
- mat-form-field {
- textarea {
- height: 20em;
- }
- }
-
- .first-part {
- display: flex;
- flex-direction: column-reverse;
- gap: 1em;
-
- @media screen and (min-width: 600px) {
- flex-direction: row;
-
- div {
- flex: 1 0;
-
- &.picture-container {
- max-width: 20em;
-
- img {
- max-height: 15em;
- max-width: 20em;
- }
- }
- }
- }
-
- div {
- flex: 1 0 50%;
- display: flex;
- flex-direction: column;
- justify-content: center;
-
- &.picture-container {
- img {
- flex: 1;
- object-fit: cover;
- width: 100%;
- cursor: pointer;
- border-radius: 1em;
- opacity: .9;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
- transition: opacity .2s ease-in-out, box-shadow .2s ease-in-out;
-
- &:hover {
- cursor: pointer;
- opacity: 1;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.32),0 2px 10px 0 rgba(0,0,0,.24);
- }
- }
- }
- }
- }
-
- .actions {
- display: flex;
- flex-direction: row;
- gap: .5em;
-
- button {
- padding: 0;
- border-radius: 10em;
- border: none;
- background-color: #3f51b5;
- color: white;
- transition: background-color .2s ease-in-out;
- display: flex;
- justify-content: center;
- align-items: center;
- width: 3em;
- height: 3em;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
- font-weight: bold;
-
- &:hover {
- background-color: #5b6ed8;
- cursor: pointer;
- }
-
- &:disabled {
- background-color: #5f6aa6;
- cursor: not-allowed;
- }
- }
+ button {
+ padding: 0;
+ border-radius: 10em;
+ border: none;
+ background-color: #3f51b5;
+ color: white;
+ transition: background-color .2s ease-in-out;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 3em;
+ height: 3em;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
+ font-weight: bold;
+
+ &:hover {
+ background-color: #5b6ed8;
+ cursor: pointer;
+ }
+
+ &:disabled {
+ background-color: #5f6aa6;
+ cursor: not-allowed;
+ }
}
+ }
}
.preview {
+ display: flex;
+ flex-direction: column;
+ max-height: 80vh;
+ overflow-y: auto;
+
+ .preview-loading {
display: flex;
flex-direction: column;
- max-height: 80vh;
- overflow-y: auto;
+ align-items: center;
+ }
- .preview-loading {
- display: flex;
- flex-direction: column;
- align-items: center;
+ .illustration {
+ flex: 1;
+ height: 12em;
+ object-fit: cover;
+ transition: height .2s ease-in-out;
+
+ @media screen and (min-width: 450px) {
+ height: 15em;
}
- .illustration {
- flex: 1;
- height: 12em;
- object-fit: cover;
- transition: height .2s ease-in-out;
-
- @media screen and (min-width: 450px) {
- height: 15em;
- }
-
- @media screen and (min-width: 600px) {
- height: 20em;
- }
-
- @media screen and (min-width: 750px) {
- height: 25em;
- }
+ @media screen and (min-width: 600px) {
+ height: 20em;
}
- header {
- padding: 2em;
+ @media screen and (min-width: 750px) {
+ height: 25em;
}
+ }
- main {
- padding: 2em;
- text-align: justify;
- }
-}
\ No newline at end of file
+ header {
+ padding: 2em;
+ }
+
+ main {
+ padding: 2em;
+ text-align: justify;
+ }
+}
diff --git a/frontend/src/app/components/publication-edition/publication-edition.component.ts b/frontend/src/app/components/publication-edition/publication-edition.component.ts
index 4eacd02..b266dc6 100644
--- a/frontend/src/app/components/publication-edition/publication-edition.component.ts
+++ b/frontend/src/app/components/publication-edition/publication-edition.component.ts
@@ -1,148 +1,135 @@
-import { CommonModule, Location } from "@angular/common";
-import { Component, EventEmitter, inject, Input, OnChanges, OnDestroy, Output } from "@angular/core";
-import { FormGroup, ReactiveFormsModule } from "@angular/forms";
-import { MatDialogModule } from "@angular/material/dialog";
-import { MatIconModule } from "@angular/material/icon";
-import { MatInputModule } from "@angular/material/input";
-import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
-import { MatSelectModule } from "@angular/material/select";
-import { MatTabsModule } from "@angular/material/tabs";
-import { MatTooltipModule } from "@angular/material/tooltip";
-import { map, Observable, of, Subscription } from "rxjs";
-import { Category } from "../../core/rest-services/category/model/category";
-import { Publication } from "../../core/rest-services/publications/model/publication";
-import { CategoryService } from "../../core/service/category.service";
-import { SubmitButtonComponent } from "../submit-button/submit-button.component";
-import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component";
-import { PublicationEditionService } from "./publication-edition.service";
-import { MatRippleModule } from "@angular/material/core";
+import {CommonModule, Location} from "@angular/common";
+import {Component, effect, inject, input, output, signal} from "@angular/core";
+import {FormGroup, ReactiveFormsModule} from "@angular/forms";
+import {MatDialogModule} from "@angular/material/dialog";
+import {MatIconModule} from "@angular/material/icon";
+import {MatInputModule} from "@angular/material/input";
+import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
+import {MatSelectModule} from "@angular/material/select";
+import {MatTabsModule} from "@angular/material/tabs";
+import {MatTooltipModule} from "@angular/material/tooltip";
+import {map, Observable} from "rxjs";
+import {Category} from "../../core/rest-services/category/model/category";
+import {DEFAULT_PUBLICATION, Publication} from "../../core/rest-services/publications/model/publication";
+import {CategoryService} from "../../core/service/category.service";
+import {SubmitButtonComponent} from "../submit-button/submit-button.component";
+import {PublicationEditionService} from "./publication-edition.service";
+import {MatRippleModule} from "@angular/material/core";
@Component({
- selector: 'app-publication-edition',
- templateUrl: './publication-edition.component.html',
- styleUrl: './publication-edition.component.scss',
- imports: [
- CommonModule,
- MatDialogModule,
- MatIconModule,
- MatInputModule,
- MatRippleModule,
- MatProgressSpinnerModule,
- MatSelectModule,
- MatTabsModule,
- MatTooltipModule,
- ReactiveFormsModule,
- SubmitButtonComponent
- ],
- providers: [PublicationEditionService]
+ selector: 'app-publication-edition',
+ templateUrl: './publication-edition.component.html',
+ styleUrl: './publication-edition.component.scss',
+ imports: [
+ CommonModule,
+ MatDialogModule,
+ MatIconModule,
+ MatInputModule,
+ MatRippleModule,
+ MatProgressSpinnerModule,
+ MatSelectModule,
+ MatTabsModule,
+ MatTooltipModule,
+ ReactiveFormsModule,
+ SubmitButtonComponent
+ ],
+ providers: [PublicationEditionService]
})
-export class PublicationEditionComponent implements OnChanges, OnDestroy {
- @Input()
- publication!: Publication;
- @Input()
- title!: string;
- @Input()
- isSaving$: Observable = of(false);
- @Output()
- publicationSave = new EventEmitter();
+export class PublicationEditionComponent {
+ readonly #categoryService = inject(CategoryService);
+ readonly #location = inject(Location);
+ readonly #publicationEditionService = inject(PublicationEditionService);
- publicationInEdition!: Publication;
- private readonly categoryService = inject(CategoryService);
- private readonly location = inject(Location);
- private readonly publicationEditionService = inject(PublicationEditionService);
- private subscriptions: Subscription[] = [];
+ publication = input.required();
+ title = input.required();
+ isSaving = input.required();
+ publicationSave = output();
- get publicationEditionForm(): FormGroup {
- return this.publicationEditionService.publicationEditionForm;
+ isLoading = this.#publicationEditionService.isLoading;
+ isPreviewing = this.#publicationEditionService.isPreviewing;
+ publicationInEdition = signal(DEFAULT_PUBLICATION);
+
+ constructor() {
+ effect(() => {
+ let publication = this.publication();
+ const publicationInEdition = this.publicationInEdition();
+ if (!publicationInEdition || publicationInEdition !== publication) {
+ this.publicationInEdition.set(publication);
+ this.#publicationEditionService.init(publicationInEdition);
+ }
+ });
+ }
+
+ get publicationEditionForm(): FormGroup {
+ return this.#publicationEditionService.publicationEditionForm;
+ }
+
+ get categories$(): Observable {
+ return this.#categoryService.categories$
+ .pipe(
+ map(categories =>
+ categories.filter(category => category.subCategories.length == 0)
+ .sort(this.byNameAscComparator())
+ )
+ );
+ }
+
+ private byNameAscComparator(): (categoryA: Category, categoryB: Category) => number {
+ return (categoryA, categoryB) => this.compareStrings(categoryA.name, categoryB.name);
+ }
+
+ private compareStrings(stringA: string, stringB: string): number {
+ if (stringA < stringB) {
+ return -1;
}
-
- get isLoading$(): Observable {
- return this.publicationEditionService.isLoading$;
+ if (stringA > stringB) {
+ return 1;
}
+ return 0;
+ }
- get isPreviewing$(): Observable {
- return this.publicationEditionService.isPreviewing$;
+ goPreviousLocation(): void {
+ this.#location.back();
+ }
+
+ insertTitle(titleNumber: number): void {
+ this.#publicationEditionService.insertTitle(titleNumber);
+ }
+
+ selectAPicture(): void {
+ this.#publicationEditionService.selectAPicture();
+ }
+
+ insertLink(): void {
+ this.#publicationEditionService.insertLink();
+ }
+
+ displayCodeBlockDialog(): void {
+ this.#publicationEditionService.displayCodeBlockDialog();
+ }
+
+ displayPictureSectionDialog(): void {
+ this.#publicationEditionService.displayPictureSectionDialog();
+ }
+
+ updateCursorPosition(event: KeyboardEvent | MouseEvent): void {
+ if (event.target) {
+ const textarea = event.target as HTMLTextAreaElement;
+
+ const positionStart = textarea.selectionStart;
+ const positionEnd = textarea.selectionEnd;
+
+ this.#publicationEditionService.editCursorPosition(positionStart, positionEnd);
}
+ }
- get categories$(): Observable {
- return this.categoryService.categories$
- .pipe(
- map(categories =>
- categories.filter(category => category.subCategories.length == 0)
- .sort(this.byNameAscComparator())
- )
- );
- }
-
- private byNameAscComparator(): (categoryA: Category, categoryB: Category) => number {
- return (categoryA, categoryB) => this.compareStrings(categoryA.name, categoryB.name);
- }
-
- private compareStrings(stringA: string, stringB: string): number {
- if (stringA < stringB) {
- return -1;
- }
- if (stringA > stringB) {
- return 1;
- }
- return 0;
- }
-
- ngOnChanges(): void {
- this.ngOnDestroy();
-
- if (!this.publicationInEdition || this.publicationInEdition !== this.publication) {
- this.publicationInEdition = this.publication;
- this.publicationEditionService.init(this.publicationInEdition);
- }
- }
-
- ngOnDestroy(): void {
- this.subscriptions.forEach(subscription => subscription?.unsubscribe());
- }
-
- goPreviousLocation(): void {
- this.location.back();
- }
-
- insertTitle(titleNumber: number): void {
- this.publicationEditionService.insertTitle(titleNumber);
- }
-
- selectAPicture(): void {
- this.publicationEditionService.selectAPicture();
- }
-
- insertLink(): void {
- this.publicationEditionService.insertLink();
- }
-
- displayCodeBlockDialog(): void {
- this.publicationEditionService.displayCodeBlockDialog();
- }
-
- displayPictureSectionDialog(): void {
- this.publicationEditionService.displayPictureSectionDialog();
- }
-
- updateCursorPosition(event: KeyboardEvent | MouseEvent): void {
- if (event.target) {
- const textarea = event.target as HTMLTextAreaElement;
-
- const positionStart = textarea.selectionStart;
- const positionEnd = textarea.selectionEnd;
-
- this.publicationEditionService.editCursorPosition(positionStart, positionEnd);
- }
- }
-
- save(): void {
- this.publicationSave.emit(this.publicationEditionService.editedPublication);
- }
-
- onTabChange(tabSelectedIndex: number): void {
- if (tabSelectedIndex === 1) {
- this.publicationEditionService.loadPreview();
- }
+ save(): void {
+ this.publicationSave.emit(this.#publicationEditionService.editedPublication);
+ }
+
+ onTabChange(tabSelectedIndex: number): void {
+ if (tabSelectedIndex === 1) {
+ this.#publicationEditionService.loadPreview();
}
+ }
}
diff --git a/frontend/src/app/components/publication-edition/publication-edition.service.ts b/frontend/src/app/components/publication-edition/publication-edition.service.ts
index 36ccd58..0d01f46 100644
--- a/frontend/src/app/components/publication-edition/publication-edition.service.ts
+++ b/frontend/src/app/components/publication-edition/publication-edition.service.ts
@@ -1,312 +1,287 @@
-import { Location } from "@angular/common";
-import { inject, Injectable, OnDestroy } from "@angular/core";
-import { MatDialog } from "@angular/material/dialog";
-import { MatSnackBar } from "@angular/material/snack-bar";
-import { ActivatedRoute } from "@angular/router";
-import { BehaviorSubject, debounceTime, distinctUntilChanged, Observable, Subscription } from "rxjs";
-import { Publication } from "../../core/rest-services/publications/model/publication";
-import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
-import { copy } from "../../core/utils/ObjectUtils";
-import { CodeBlockDialog } from "./code-block-dialog/code-block-dialog.component";
-import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component";
-import { PreviewContentRequest } from "../../core/rest-services/publications/model/preview";
-import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
+import {Location} from "@angular/common";
+import {inject, Injectable, OnDestroy, Signal, signal} from "@angular/core";
+import {MatDialog} from "@angular/material/dialog";
+import {MatSnackBar} from "@angular/material/snack-bar";
+import {ActivatedRoute} from "@angular/router";
+import {debounceTime, distinctUntilChanged, Subscription} from "rxjs";
+import {DEFAULT_PUBLICATION, Publication} from "../../core/rest-services/publications/model/publication";
+import {PublicationRestService} from "../../core/rest-services/publications/publication.rest-service";
+import {copy} from "../../core/utils/ObjectUtils";
+import {CodeBlockDialog} from "./code-block-dialog/code-block-dialog.component";
+import {PictureSelectionDialog} from "./picture-selection-dialog/picture-selection-dialog.component";
+import {PreviewContentRequest} from "../../core/rest-services/publications/model/preview";
+import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
declare let Prism: any;
export class CursorPosition {
- start: number;
- end: number;
- selectedCharacters: number;
+ start: number;
+ end: number;
+ selectedCharacters: number;
- constructor(start: number, end: number) {
- this.start = start;
- this.end = end;
- this.selectedCharacters = end - start;
- }
+ constructor(start: number, end: number) {
+ this.start = start;
+ this.end = end;
+ this.selectedCharacters = end - start;
+ }
}
export interface PublicationEditionState {
- publication: Publication;
- cursorPosition: CursorPosition;
+ publication: Publication;
+ cursorPosition: CursorPosition;
}
-const DEFAULT_PUBLICATION: Publication = {
- id: '',
- key: '',
- title: '',
- text: '',
- parsedText: '',
- description: '',
- creationDate: new Date(),
- illustrationId: '',
- categoryId: '',
- author: {
- id: '',
- name: '',
- image: ''
- }
-};
-
const DEFAULT_CURSOR_POSITION = new CursorPosition(0, 0);
const DEFAULT_STATE: PublicationEditionState = {
- publication: DEFAULT_PUBLICATION,
- cursorPosition: DEFAULT_CURSOR_POSITION
+ publication: DEFAULT_PUBLICATION,
+ cursorPosition: DEFAULT_CURSOR_POSITION
};
@Injectable()
export class PublicationEditionService implements OnDestroy {
- private readonly activatedRoute = inject(ActivatedRoute);
- private readonly dialog = inject(MatDialog);
- private readonly formBuilder = inject(FormBuilder);
- private readonly location = inject(Location);
- private readonly publicationRestService = inject(PublicationRestService);
- private readonly snackBar = inject(MatSnackBar);
+ readonly #activatedRoute = inject(ActivatedRoute);
+ readonly #dialog = inject(MatDialog);
+ readonly #formBuilder = inject(FormBuilder);
+ readonly #location = inject(Location);
+ readonly #publicationRestService = inject(PublicationRestService);
+ readonly #snackBar = inject(MatSnackBar);
- private isLoadingSubject = new BehaviorSubject(false);
- private stateSubject = new BehaviorSubject(copy(DEFAULT_STATE));
- private subscriptions: Subscription[] = [];
- private isSavingSubject = new BehaviorSubject(false);
- private isPreviewingSubject = new BehaviorSubject(false);
+ #isLoading = signal(false);
+ #state = signal(copy(DEFAULT_STATE));
+ #isSaving = signal(false);
+ #isPreviewing = signal(false);
+ #subscriptions: Subscription[] = [];
- publicationEditionForm: FormGroup = this.formBuilder.group({
- title: new FormControl('', [Validators.required]),
- description: new FormControl('', [Validators.required]),
- text: new FormControl('', [Validators.required]),
- illustrationId: new FormControl('', [Validators.required]),
- categoryId: new FormControl('', [Validators.required])
+ publicationEditionForm: FormGroup = this.#formBuilder.group({
+ title: new FormControl('', [Validators.required]),
+ description: new FormControl('', [Validators.required]),
+ text: new FormControl('', [Validators.required]),
+ illustrationId: new FormControl('', [Validators.required]),
+ categoryId: new FormControl('', [Validators.required])
+ });
+
+ ngOnDestroy(): void {
+ this.#subscriptions.forEach(subscription => subscription.unsubscribe());
+ }
+
+ #updateForm(): void {
+ const state = this.#state();
+ const publication = state.publication;
+
+ this.publicationEditionForm.controls['title'].setValue(publication.title);
+ this.publicationEditionForm.controls['description'].setValue(publication.description);
+ this.publicationEditionForm.controls['text'].setValue(publication.text);
+ this.publicationEditionForm.controls['illustrationId'].setValue(publication.illustrationId);
+ this.publicationEditionForm.controls['categoryId'].setValue(publication.categoryId);
+ }
+
+ get isLoading(): Signal {
+ return this.#isLoading.asReadonly();
+ }
+
+ get isSaving(): Signal {
+ return this.#isSaving.asReadonly();
+ }
+
+ get isPreviewing(): Signal {
+ return this.#isPreviewing.asReadonly();
+ }
+
+ get state(): Signal {
+ return this.#state.asReadonly();
+ }
+
+ get editedPublication(): Publication {
+ return this.#state().publication;
+ }
+
+ loadPublication(): void {
+ this.#isLoading.set(true);
+
+ this.#activatedRoute.paramMap.subscribe(params => {
+ const publicationId = params.get('publicationId');
+ if (publicationId == undefined) {
+ this.#snackBar.open($localize`A technical error occurred while loading publication data.`, $localize`Close`, {duration: 5000});
+ this.#location.back();
+ } else {
+ this.#publicationRestService.getById(publicationId)
+ .then(publication => {
+ const state = this.#state();
+ state.publication = publication;
+ this.#state.set(state);
+ })
+ .catch(error => {
+ const errorMessage = $localize`A technical error occurred while loading publication data.`;
+ this.#snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
+ console.error(errorMessage, error)
+ })
+ .finally(() => this.#isLoading.set(false));
+ }
});
+ }
- ngOnDestroy(): void {
- this.subscriptions.forEach(subscription => subscription.unsubscribe());
- }
+ init(publication: Publication): void {
+ const state = this.#state();
+ state.publication = publication;
+ this.#state.set(state);
+ this.#updateForm();
- private get _state(): PublicationEditionState {
- return this.stateSubject.value;
- }
-
- private _save(state: PublicationEditionState): void {
- this.stateSubject.next(state);
- }
-
- private _updateForm(): void {
- const state = this._state;
+ const formValueChangesSubscription = this.publicationEditionForm.valueChanges
+ .pipe(
+ debounceTime(200),
+ distinctUntilChanged()
+ )
+ .subscribe(formValue => {
+ const state = this.#state();
const publication = state.publication;
- this.publicationEditionForm.controls['title'].setValue(publication.title);
- this.publicationEditionForm.controls['description'].setValue(publication.description);
- this.publicationEditionForm.controls['text'].setValue(publication.text);
- this.publicationEditionForm.controls['illustrationId'].setValue(publication.illustrationId);
- this.publicationEditionForm.controls['categoryId'].setValue(publication.categoryId);
- }
+ publication.title = formValue.title;
+ publication.description = formValue.description;
+ publication.categoryId = formValue.categoryId;
+ publication.text = formValue.text;
- get isLoading$(): Observable {
- return this.isLoadingSubject.asObservable();
- }
+ this.#state.set(state);
+ });
+ this.#subscriptions.push(formValueChangesSubscription);
+ }
- get isSaving$(): Observable {
- return this.isSavingSubject.asObservable();
- }
+ private editIllustrationId(pictureId: string): void {
+ const state = this.#state();
+ state.publication.illustrationId = pictureId
+ this.#state.set(state);
+ }
- get isPreviewing$(): Observable {
- return this.isPreviewingSubject.asObservable();
- }
+ displayPictureSectionDialog(): void {
+ const dialogRef = this.#dialog.open(PictureSelectionDialog);
- get state$(): Observable {
- return this.stateSubject.asObservable();
- }
-
- get editedPublication(): Publication {
- return this._state.publication;
- }
-
- loadPublication(): void {
- this.isLoadingSubject.next(true);
-
- this.activatedRoute.paramMap.subscribe(params => {
- const publicationId = params.get('publicationId');
- if (publicationId == undefined) {
- this.snackBar.open($localize`A technical error occurred while loading publication data.`, $localize`Close`, { duration: 5000 });
- this.location.back();
- } else {
- this.publicationRestService.getById(publicationId)
- .then(publication => {
- const state = this._state;
- state.publication = publication;
- this.stateSubject.next(state);
- })
- .catch(error => {
- const errorMessage = $localize`A technical error occurred while loading publication data.`;
- this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
- console.error(errorMessage, error)
- })
- .finally(() => this.isLoadingSubject.next(false));
- }
- });
- }
-
- init(publication: Publication): void {
- const state = this._state;
- state.publication = publication;
- this.stateSubject.next(state);
- this._updateForm();
-
- const formValueChangesSubscription = this.publicationEditionForm.valueChanges
- .pipe(
- debounceTime(200),
- distinctUntilChanged()
- )
- .subscribe(formValue => {
- const state = this._state;
- const publication = state.publication;
-
- publication.title = formValue.title;
- publication.description = formValue.description;
- publication.categoryId = formValue.categoryId;
- publication.text = formValue.text;
-
- this._save(state);
- })
- this.subscriptions.push(formValueChangesSubscription);
- }
-
- private editIllustrationId(pictureId: string): void {
- const state = this._state;
- state.publication.illustrationId = pictureId
- this._save(state);
- }
-
- displayPictureSectionDialog(): void {
- const dialogRef = this.dialog.open(PictureSelectionDialog);
-
- const afterDialogCloseSubscription = dialogRef.afterClosed()
- .subscribe(newPictureId => {
- if (newPictureId) {
- this.editIllustrationId(newPictureId);
- }
- });
- this.subscriptions.push(afterDialogCloseSubscription);
- }
-
- displayCodeBlockDialog(): void {
- const dialogRef = this.dialog.open(CodeBlockDialog, { width: '60em' });
-
- const afterDialogCloseSubscription = dialogRef.afterClosed()
- .subscribe(codeBlockWithLanguage => {
- if (codeBlockWithLanguage) {
- this.insertCodeBlock(codeBlockWithLanguage.programmingLanguage, codeBlockWithLanguage.codeBlock);
- }
- });
- this.subscriptions.push(afterDialogCloseSubscription);
- }
-
- editCursorPosition(positionStart: number, positionEnd: number): void {
- const state = this._state;
-
- state.cursorPosition.start = positionStart;
- state.cursorPosition.end = positionEnd;
-
- this._save(state);
- }
-
- insertTitle(titleNumber: number): void {
- if (titleNumber >= 1 && titleNumber <= 3) {
- const state = this._state;
-
- const publication = state.publication;
-
- const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
- const publicationTextMiddlePart = publication.text.substring(state.cursorPosition.start, state.cursorPosition.end);
- const publicationTextRightPart = publication.text.substring(state.cursorPosition.end);
- const textWithTags = `${publicationTextLeftPart}[h${titleNumber}]${publicationTextMiddlePart}[/h${titleNumber}]${publicationTextRightPart}`;
-
- publication.text = textWithTags;
-
- this._save(state);
- this._updateForm();
- } else {
- console.error(`Bad value for parameter of function 'insertTitle': '${titleNumber}'.`);
+ const afterDialogCloseSubscription = dialogRef.afterClosed()
+ .subscribe(newPictureId => {
+ if (newPictureId) {
+ this.editIllustrationId(newPictureId);
}
+ });
+ this.#subscriptions.push(afterDialogCloseSubscription);
+ }
+
+ displayCodeBlockDialog(): void {
+ const dialogRef = this.#dialog.open(CodeBlockDialog, {width: '60em'});
+
+ const afterDialogCloseSubscription = dialogRef.afterClosed()
+ .subscribe(codeBlockWithLanguage => {
+ if (codeBlockWithLanguage) {
+ this.insertCodeBlock(codeBlockWithLanguage.programmingLanguage, codeBlockWithLanguage.codeBlock);
+ }
+ });
+ this.#subscriptions.push(afterDialogCloseSubscription);
+ }
+
+ editCursorPosition(positionStart: number, positionEnd: number): void {
+ const state = this.#state();
+
+ state.cursorPosition.start = positionStart;
+ state.cursorPosition.end = positionEnd;
+
+ this.#state.set(state);
+ }
+
+ insertTitle(titleNumber: number): void {
+ if (titleNumber >= 1 && titleNumber <= 3) {
+ const state = this.#state();
+
+ const publication = state.publication;
+
+ const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
+ const publicationTextMiddlePart = publication.text.substring(state.cursorPosition.start, state.cursorPosition.end);
+ const publicationTextRightPart = publication.text.substring(state.cursorPosition.end);
+ const textWithTags = `${publicationTextLeftPart}[h${titleNumber}]${publicationTextMiddlePart}[/h${titleNumber}]${publicationTextRightPart}`;
+
+ publication.text = textWithTags;
+
+ this.#state.set(state);
+ this.#updateForm();
+ } else {
+ console.error(`Bad value for parameter of function 'insertTitle': '${titleNumber}'.`);
}
+ }
- selectAPicture(): void {
- const dialogRef = this.dialog.open(PictureSelectionDialog);
+ selectAPicture(): void {
+ const dialogRef = this.#dialog.open(PictureSelectionDialog);
- const afterDialogCloseSubscription = dialogRef.afterClosed()
- .subscribe(newPictureId => {
- if (newPictureId) {
- this.insertPicture(newPictureId);
- }
- });
- this.subscriptions.push(afterDialogCloseSubscription);
- }
+ const afterDialogCloseSubscription = dialogRef.afterClosed()
+ .subscribe(newPictureId => {
+ if (newPictureId) {
+ this.insertPicture(newPictureId);
+ }
+ });
+ this.#subscriptions.push(afterDialogCloseSubscription);
+ }
- insertPicture(pictureId: string): void {
- const state = this._state;
+ insertPicture(pictureId: string): void {
+ const state = this.#state();
- const publication = state.publication;
+ const publication = state.publication;
- const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
- const publicationTextRightPart = publication.text.substring(state.cursorPosition.start);
- const textWithTags = `${publicationTextLeftPart}[img src="/api/pictures/${pictureId}" /]${publicationTextRightPart}`;
+ const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
+ const publicationTextRightPart = publication.text.substring(state.cursorPosition.start);
+ const textWithTags = `${publicationTextLeftPart}[img src="/api/pictures/${pictureId}" /]${publicationTextRightPart}`;
- publication.text = textWithTags;
+ publication.text = textWithTags;
- this._save(state);
- this._updateForm();
- }
+ this.#state.set(state);
+ this.#updateForm();
+ }
- insertLink(): void {
- const state = this._state;
+ insertLink(): void {
+ const state = this.#state();
- const publication = state.publication;
+ const publication = state.publication;
- const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
- const publicationTextMiddlePart = publication.text.substring(state.cursorPosition.start, state.cursorPosition.end);
- const publicationTextRightPart = publication.text.substring(state.cursorPosition.end);
- const textWithTags = `${publicationTextLeftPart}[link href="" txt="${publicationTextMiddlePart}" /]${publicationTextRightPart}`;
+ const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
+ const publicationTextMiddlePart = publication.text.substring(state.cursorPosition.start, state.cursorPosition.end);
+ const publicationTextRightPart = publication.text.substring(state.cursorPosition.end);
+ const textWithTags = `${publicationTextLeftPart}[link href="" txt="${publicationTextMiddlePart}" /]${publicationTextRightPart}`;
- publication.text = textWithTags;
+ publication.text = textWithTags;
- this._save(state);
- this._updateForm();
- }
+ this.#state.set(state);
+ this.#updateForm();
+ }
- private insertCodeBlock(programmingLanguage: string, codeBlock: string): void {
- const state = this._state;
+ private insertCodeBlock(programmingLanguage: string, codeBlock: string): void {
+ const state = this.#state();
- const publication = state.publication;
+ const publication = state.publication;
- const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
- const publicationTextRightPart = publication.text.substring(state.cursorPosition.start);
- const codeBlockInstruction = `\n[code lg="${programmingLanguage}"]\n${codeBlock}\n[/code]\n\n`;
- const textWithTags = `${publicationTextLeftPart}${codeBlockInstruction}${publicationTextRightPart}`;
+ const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
+ const publicationTextRightPart = publication.text.substring(state.cursorPosition.start);
+ const codeBlockInstruction = `\n[code lg="${programmingLanguage}"]\n${codeBlock}\n[/code]\n\n`;
+ const textWithTags = `${publicationTextLeftPart}${codeBlockInstruction}${publicationTextRightPart}`;
- publication.text = textWithTags;
+ publication.text = textWithTags;
- this._save(state);
- this._updateForm();
- }
+ this.#state.set(state);
+ this.#updateForm();
+ }
- loadPreview(): void {
- const state = this._state;
+ loadPreview(): void {
+ const state = this.#state();
- this.isPreviewingSubject.next(true);
- const request: PreviewContentRequest = {
- text: state.publication.text
- };
- this.publicationRestService.preview(request)
- .then(response => {
- state.publication.parsedText = response.text;
- this._save(state);
- setTimeout(() => Prism.highlightAll(), 1000);
- })
- .catch(error => {
- console.error(error);
- })
- .finally(() => {
- this.isPreviewingSubject.next(false);
- });
- }
-}
\ No newline at end of file
+ this.#isPreviewing.set(true);
+ const request: PreviewContentRequest = {
+ text: state.publication.text
+ };
+ this.#publicationRestService.preview(request)
+ .then(response => {
+ state.publication.parsedText = response.text;
+ this.#state.set(state);
+ setTimeout(() => Prism.highlightAll(), 1000);
+ })
+ .catch(error => {
+ console.error(error);
+ })
+ .finally(() => {
+ this.#isPreviewing.set(false);
+ });
+ }
+}
diff --git a/frontend/src/app/components/publication-list/publication-list.component.html b/frontend/src/app/components/publication-list/publication-list.component.html
index 7986926..084981d 100644
--- a/frontend/src/app/components/publication-list/publication-list.component.html
+++ b/frontend/src/app/components/publication-list/publication-list.component.html
@@ -1,16 +1,16 @@
-@for(publication of publications(); track publication.id) {
-
-
-
-
{{publication.title}}
- {{publication.description}}
-
-
+
}
diff --git a/frontend/src/app/components/publication-list/publication-list.component.scss b/frontend/src/app/components/publication-list/publication-list.component.scss
index a43901b..45670db 100644
--- a/frontend/src/app/components/publication-list/publication-list.component.scss
+++ b/frontend/src/app/components/publication-list/publication-list.component.scss
@@ -1,87 +1,87 @@
$cardBorderRadius: .5em;
:host {
+ display: flex;
+ flex-direction: column;
+ gap: 2em;
+ max-width: 50em;
+ width: 90%;
+ margin: auto;
+
+ .publication {
display: flex;
flex-direction: column;
- gap: 2em;
- max-width: 50em;
- width: 90%;
- margin: auto;
+ border-radius: $cardBorderRadius;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
+ transition: box-shadow .2s ease-in-out;
+ text-decoration: none;
+ color: black;
+ background-color: #ffffff;
- .publication {
- display: flex;
- flex-direction: column;
- border-radius: $cardBorderRadius;
- box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
- transition: box-shadow .2s ease-in-out;
- text-decoration: none;
- color: black;
- background-color: #ffffff;
-
- &:hover {
- box-shadow: 0 4px 8px 0 rgba(0,0,0,.24),0 4px 14px 0 rgba(0,0,0,.16);
- }
-
- img {
- object-fit: cover;
- height: 15em;
- border-radius: $cardBorderRadius $cardBorderRadius 0 0;
- transition: height .2s ease-in-out;
-
- @media screen and (min-width: 450px) {
- height: 20em;
- }
-
- @media screen and (min-width: 600px) {
- height: 25em;
- }
-
- @media screen and (min-width: 750px) {
- height: 32em;
- }
- }
-
- .body {
- display: flex;
- flex-direction: column;
- padding: 1.5em 2em;
-
- h1 {
- font-size: 1.8em;
- margin-bottom: .5em;
- }
-
- h2 {
- font-size: 1em;
- line-height: 1.4em;
- margin: 0;
- color: #747373;
- font-weight: 400;
- }
- }
-
- .footer {
- display: flex;
- flex-direction: row;
- align-items: center;
- background-color: #f0f0f0;
- border-radius: 0 0 $cardBorderRadius $cardBorderRadius;
- padding: 1em 2em;
- gap: 1em;
- color: #6c757d;
-
- img {
- $imageSize: 4em;
- border-radius: 10em;
- width: $imageSize;
- height: $imageSize;
- object-fit: cover;
- }
-
- .publication-date {
- font-style: italic;
- color: #bdbdbd;
- }
- }
+ &:hover {
+ box-shadow: 0 4px 8px 0 rgba(0, 0, 0, .24), 0 4px 14px 0 rgba(0, 0, 0, .16);
}
-}
\ No newline at end of file
+
+ img {
+ object-fit: cover;
+ height: 15em;
+ border-radius: $cardBorderRadius $cardBorderRadius 0 0;
+ transition: height .2s ease-in-out;
+
+ @media screen and (min-width: 450px) {
+ height: 20em;
+ }
+
+ @media screen and (min-width: 600px) {
+ height: 25em;
+ }
+
+ @media screen and (min-width: 750px) {
+ height: 32em;
+ }
+ }
+
+ .body {
+ display: flex;
+ flex-direction: column;
+ padding: 1.5em 2em;
+
+ h1 {
+ font-size: 1.8em;
+ margin-bottom: .5em;
+ }
+
+ h2 {
+ font-size: 1em;
+ line-height: 1.4em;
+ margin: 0;
+ color: #747373;
+ font-weight: 400;
+ }
+ }
+
+ .footer {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ background-color: #f0f0f0;
+ border-radius: 0 0 $cardBorderRadius $cardBorderRadius;
+ padding: 1em 2em;
+ gap: 1em;
+ color: #6c757d;
+
+ img {
+ $imageSize: 4em;
+ border-radius: 10em;
+ width: $imageSize;
+ height: $imageSize;
+ object-fit: cover;
+ }
+
+ .publication-date {
+ font-style: italic;
+ color: #bdbdbd;
+ }
+ }
+ }
+}
diff --git a/frontend/src/app/components/publication-list/publication-list.component.ts b/frontend/src/app/components/publication-list/publication-list.component.ts
index 43b2d91..b18cdda 100644
--- a/frontend/src/app/components/publication-list/publication-list.component.ts
+++ b/frontend/src/app/components/publication-list/publication-list.component.ts
@@ -1,16 +1,15 @@
-import {Component, input, Input} from "@angular/core";
-import { Publication } from "../../core/rest-services/publications/model/publication";
-import { Observable } from "rxjs";
-import { CommonModule } from "@angular/common";
-import { RouterModule } from "@angular/router";
-import { MatTooltipModule } from "@angular/material/tooltip";
+import {Component, input} from "@angular/core";
+import {Publication} from "../../core/rest-services/publications/model/publication";
+import {CommonModule} from "@angular/common";
+import {RouterModule} from "@angular/router";
+import {MatTooltipModule} from "@angular/material/tooltip";
@Component({
- selector: 'app-publication-list',
- templateUrl: './publication-list.component.html',
- styleUrl: './publication-list.component.scss',
- imports: [CommonModule, RouterModule, MatTooltipModule]
+ selector: 'app-publication-list',
+ templateUrl: './publication-list.component.html',
+ styleUrl: './publication-list.component.scss',
+ imports: [CommonModule, RouterModule, MatTooltipModule]
})
export class PublicationListComponent {
- publications = input.required();
+ publications = input.required();
}
diff --git a/frontend/src/app/components/publications-search-bar/publications-search-bar.component.html b/frontend/src/app/components/publications-search-bar/publications-search-bar.component.html
index 67c8ec2..0bf8ff4 100644
--- a/frontend/src/app/components/publications-search-bar/publications-search-bar.component.html
+++ b/frontend/src/app/components/publications-search-bar/publications-search-bar.component.html
@@ -1,6 +1,6 @@
\ No newline at end of file
+
+
+
diff --git a/frontend/src/app/components/publications-search-bar/publications-search-bar.component.scss b/frontend/src/app/components/publications-search-bar/publications-search-bar.component.scss
index fb10d3f..72b683d 100644
--- a/frontend/src/app/components/publications-search-bar/publications-search-bar.component.scss
+++ b/frontend/src/app/components/publications-search-bar/publications-search-bar.component.scss
@@ -1,38 +1,38 @@
:host {
- $borderRadiusValue: 10em;
- position: relative;
- flex-direction: row;
- align-items: center;
+ $borderRadiusValue: 10em;
+ position: relative;
+ flex-direction: row;
+ align-items: center;
- form {
- display: flex;
+ form {
+ display: flex;
- input {
- flex: 1;
- border-radius: $borderRadiusValue;
- background-color: white;
- border: solid 1px #ddd;
- padding: .2em 2.7em .2em 1em;
- height: 2em;
- width: 100%;
- }
-
- button {
- position: absolute;
- display: flex;
- align-items: center;
- border-radius: $borderRadiusValue;
- background-color: white;
- border: none;
- top: 0;
- right: 0;
- color: #aaaaaa;
- padding: .3em;
-
- &:hover {
- background-color: #eee;
- cursor: pointer;
- }
- }
+ input {
+ flex: 1;
+ border-radius: $borderRadiusValue;
+ background-color: white;
+ border: solid 1px #ddd;
+ padding: .2em 2.7em .2em 1em;
+ height: 2em;
+ width: 100%;
}
-}
\ No newline at end of file
+
+ button {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ border-radius: $borderRadiusValue;
+ background-color: white;
+ border: none;
+ top: 0;
+ right: 0;
+ color: #aaaaaa;
+ padding: .3em;
+
+ &:hover {
+ background-color: #eee;
+ cursor: pointer;
+ }
+ }
+ }
+}
diff --git a/frontend/src/app/components/publications-search-bar/publications-search-bar.component.ts b/frontend/src/app/components/publications-search-bar/publications-search-bar.component.ts
index 7880844..58ad511 100644
--- a/frontend/src/app/components/publications-search-bar/publications-search-bar.component.ts
+++ b/frontend/src/app/components/publications-search-bar/publications-search-bar.component.ts
@@ -1,36 +1,36 @@
-import { Component, inject } from "@angular/core";
-import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
-import { MatRippleModule } from "@angular/material/core";
-import { MatIconModule } from "@angular/material/icon";
-import { Router } from "@angular/router";
+import {Component, inject} from "@angular/core";
+import {FormBuilder, FormControl, ReactiveFormsModule, Validators} from "@angular/forms";
+import {MatRippleModule} from "@angular/material/core";
+import {MatIconModule} from "@angular/material/icon";
+import {Router} from "@angular/router";
@Component({
- selector: 'app-publications-search-bar',
- templateUrl: './publications-search-bar.component.html',
- styleUrl: './publications-search-bar.component.scss',
- imports: [
- MatIconModule,
- MatRippleModule,
- ReactiveFormsModule
- ],
- providers: []
+ selector: 'app-publications-search-bar',
+ templateUrl: './publications-search-bar.component.html',
+ styleUrl: './publications-search-bar.component.scss',
+ imports: [
+ MatIconModule,
+ MatRippleModule,
+ ReactiveFormsModule
+ ],
+ providers: []
})
export class PublicationsSearchBarComponent {
- private formBuilder = inject(FormBuilder);
- private router = inject(Router);
- formGroup = this.formBuilder.group({
- criteria: new FormControl('', [Validators.required])
- });
+ private formBuilder = inject(FormBuilder);
+ private router = inject(Router);
+ formGroup = this.formBuilder.group({
+ criteria: new FormControl('', [Validators.required])
+ });
- searchPublications(): void {
- const query = this.formGroup.controls.criteria.value
+ searchPublications(): void {
+ const query = this.formGroup.controls.criteria.value
- if (query?.trim()) {
- const queryParams = { 'query' : this.formGroup.controls.criteria.value ?? '' }
- this.router.navigate(['/publications'], { queryParams });
- } else {
- this.router.navigate(['/home']);
- }
+ if (query?.trim()) {
+ const queryParams = {'query': this.formGroup.controls.criteria.value ?? ''}
+ this.router.navigate(['/publications'], {queryParams});
+ } else {
+ this.router.navigate(['/home']);
}
-}
\ No newline at end of file
+ }
+}
diff --git a/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.html b/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.html
index 08fe4e9..8a83dab 100644
--- a/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.html
+++ b/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.html
@@ -1,18 +1,18 @@
-@for(category of categories$ | async; track category) {
-
-
-
+@for (category of categories$ | async; track category) {
+
+
-}
\ No newline at end of file
+
+
+}
diff --git a/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.scss b/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.scss
index bcd6a8f..df29967 100644
--- a/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.scss
+++ b/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.scss
@@ -1,57 +1,57 @@
:host {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
- .category {
+ .category {
+ transition: background-color .2s ease-in-out;
+
+ &:hover {
+ cursor: pointer;
+ background-color: #5c6bc0;
+ }
+
+ .category-header {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: .5em 1em;
+
+ mat-icon {
+ transition: transform .2s ease-in-out;
+ }
+ }
+
+ &.openned {
+ .category-header {
+ mat-icon {
+ transform: rotate(90deg);
+ }
+ }
+
+ .sub-category-container {
+ max-height: none;
+ }
+ }
+
+ .sub-category-container {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ max-height: 0;
+ transition: max-height .2s ease-in-out;
+ background-color: #303f9f;
+
+ .sub-category {
+ padding: .5em 1em .5em 2em;
+ text-decoration: none;
+ color: inherit;
transition: background-color .2s ease-in-out;
&:hover {
- cursor: pointer;
- background-color: #5c6bc0;
- }
-
- .category-header {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding: .5em 1em;
-
- mat-icon {
- transition: transform .2s ease-in-out;
- }
- }
-
- &.openned {
- .category-header {
- mat-icon {
- transform: rotate(90deg);
- }
- }
-
- .sub-category-container {
- max-height: none;
- }
- }
-
- .sub-category-container {
- display: flex;
- flex-direction: column;
- overflow: hidden;
- max-height: 0;
- transition: max-height .2s ease-in-out;
- background-color: #303f9f;
-
- .sub-category {
- padding: .5em 1em .5em 2em;
- text-decoration: none;
- color: inherit;
- transition: background-color .2s ease-in-out;
-
- &:hover {
- background-color: #5c6bc0;
- }
- }
+ background-color: #5c6bc0;
}
+ }
}
+ }
}
diff --git a/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.ts b/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.ts
index b0e8603..226a9c2 100644
--- a/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.ts
+++ b/frontend/src/app/components/side-menu/categories-menu/categories-menu.component.ts
@@ -1,19 +1,19 @@
-import { CommonModule } from "@angular/common";
-import { Component, EventEmitter, inject, OnInit, Output } from "@angular/core";
-import { MatIconModule } from "@angular/material/icon";
-import { DisplayableCategory, SideMenuService } from "../side-menu.service";
-import { Observable } from "rxjs";
-import { RouterModule } from "@angular/router";
+import {CommonModule} from "@angular/common";
+import {Component, EventEmitter, inject, OnInit, Output} from "@angular/core";
+import {MatIconModule} from "@angular/material/icon";
+import {DisplayableCategory, SideMenuService} from "../side-menu.service";
+import {Observable} from "rxjs";
+import {RouterModule} from "@angular/router";
@Component({
- selector: 'app-categories-menu',
- templateUrl: './categories-menu.component.html',
- imports: [
- CommonModule,
- RouterModule,
- MatIconModule
- ],
- styleUrl: './categories-menu.component.scss'
+ selector: 'app-categories-menu',
+ templateUrl: './categories-menu.component.html',
+ imports: [
+ CommonModule,
+ RouterModule,
+ MatIconModule
+ ],
+ styleUrl: './categories-menu.component.scss'
})
export class CategoriesMenuComponent implements OnInit {
private sideMenuService = inject(SideMenuService);
@@ -37,10 +37,10 @@ export class CategoriesMenuComponent implements OnInit {
} else {
const categoriesDivs = document.getElementsByClassName('category-header');
Array.from(categoriesDivs)
- .map(category => category as HTMLElement)
+ .map(category => category as HTMLElement)
.forEach(categoryDiv => this.closeAccordion(categoryDiv));
-
- const categoryDiv = document.getElementById(`category-${category.id}`);
+
+ const categoryDiv = document.getElementById(`category-${category.id}`);
if (categoryDiv) {
this.openAccordion(categoryDiv);
}
@@ -58,4 +58,4 @@ export class CategoriesMenuComponent implements OnInit {
const divContent = categoryDiv?.nextElementSibling as HTMLElement;
divContent.style.maxHeight = `${divContent.scrollHeight}px`;
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/app/components/side-menu/side-menu.component.html b/frontend/src/app/components/side-menu/side-menu.component.html
index 34cf202..583647e 100644
--- a/frontend/src/app/components/side-menu/side-menu.component.html
+++ b/frontend/src/app/components/side-menu/side-menu.component.html
@@ -1,19 +1,19 @@
-