- Unnamed' | safeHtml"> |
+
+ {{key.name}}
+ Unnamed
+ |
{{ key.generatedOn | shortdate }} |
- Doesn\'t expire')"> |
+
+ {{key.expiresOn | shortdate}}
+ Doesn't expire
+ |
Delete
diff --git a/projects/gameboard-ui/src/app/core/core.module.ts b/projects/gameboard-ui/src/app/core/core.module.ts
index 0bf52685..3b98cd14 100644
--- a/projects/gameboard-ui/src/app/core/core.module.ts
+++ b/projects/gameboard-ui/src/app/core/core.module.ts
@@ -11,7 +11,7 @@ import { PlayerAvatarListComponent } from './components/player-avatar-list/playe
import { ModalConfirmComponent } from './components/modal/modal-confirm.component';
import { ModalConfirmDirective } from './directives/modal-confirm.directive';
import { PlayerStatusComponent } from './components/player-status/player-status.component';
-import { SafeHtmlPipe } from './pipes/safe-html.pipe';
+import { SanitizeHtmlPipe } from './pipes/sanitize-html.pipe';
import { YamlBlockComponent } from './components/yaml-block/yaml-block.component';
import { YamlPipe } from './pipes/yaml.pipe';
import { CustomInputComponent } from './components/custom-input/custom-input.component';
@@ -25,7 +25,7 @@ const PUBLIC_DECLARATIONS = [
PlayerAvatarListComponent,
PlayerStatusComponent,
RelativeUrlsPipe,
- SafeHtmlPipe,
+ SanitizeHtmlPipe,
UrlRewritePipe,
YamlBlockComponent,
YamlPipe
diff --git a/projects/gameboard-ui/src/app/core/pipes/linkify-html.pipe.ts b/projects/gameboard-ui/src/app/core/pipes/linkify-html.pipe.ts
index d54ab07c..42942026 100644
--- a/projects/gameboard-ui/src/app/core/pipes/linkify-html.pipe.ts
+++ b/projects/gameboard-ui/src/app/core/pipes/linkify-html.pipe.ts
@@ -1,4 +1,4 @@
-import { Pipe, PipeTransform } from '@angular/core';
+import { Pipe, PipeTransform, SecurityContext } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import linkifyHtml from 'linkify-html';
@@ -6,7 +6,7 @@ import linkifyHtml from 'linkify-html';
export class LinkifyHtmlPipe implements PipeTransform {
constructor(private domSanitizer: DomSanitizer) { }
- transform(value: string, ...args: unknown[]): SafeHtml {
- return this.domSanitizer.bypassSecurityTrustHtml(linkifyHtml(value, { nl2br: true }));
+ transform(value: string, ...args: unknown[]): string {
+ return this.domSanitizer.sanitize(SecurityContext.HTML, linkifyHtml(value, { nl2br: true })) || "";
}
}
diff --git a/projects/gameboard-ui/src/app/core/pipes/safe-html.pipe.ts b/projects/gameboard-ui/src/app/core/pipes/safe-html.pipe.ts
deleted file mode 100644
index 58065c87..00000000
--- a/projects/gameboard-ui/src/app/core/pipes/safe-html.pipe.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Pipe, PipeTransform } from '@angular/core';
-import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
-
-@Pipe({ name: 'safeHtml' })
-export class SafeHtmlPipe implements PipeTransform {
- constructor(private domSanitizer: DomSanitizer) { }
-
- transform(value: string): SafeHtml {
- return this.domSanitizer.bypassSecurityTrustHtml(value);
- }
-}
diff --git a/projects/gameboard-ui/src/app/core/pipes/sanitize-html.pipe.ts b/projects/gameboard-ui/src/app/core/pipes/sanitize-html.pipe.ts
new file mode 100644
index 00000000..3e00d2c5
--- /dev/null
+++ b/projects/gameboard-ui/src/app/core/pipes/sanitize-html.pipe.ts
@@ -0,0 +1,11 @@
+import { Pipe, PipeTransform, SecurityContext } from '@angular/core';
+import { DomSanitizer } from '@angular/platform-browser';
+
+@Pipe({ name: 'sanitizeHtml' })
+export class SanitizeHtmlPipe implements PipeTransform {
+ constructor(private domSanitizer: DomSanitizer) { }
+
+ transform(value: string): string {
+ return this.domSanitizer.sanitize(SecurityContext.HTML, value) || '';
+ }
+}
diff --git a/projects/gameboard-ui/src/app/game/certificate/certificate.component.ts b/projects/gameboard-ui/src/app/game/certificate/certificate.component.ts
index cae6140f..d0ce92a0 100644
--- a/projects/gameboard-ui/src/app/game/certificate/certificate.component.ts
+++ b/projects/gameboard-ui/src/app/game/certificate/certificate.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input, OnInit, SecurityContext } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { faPrint } from '@fortawesome/free-solid-svg-icons';
import { Observable } from 'rxjs';
@@ -13,7 +13,7 @@ import { PlayerService } from '../../api/player.service';
})
export class CertificateComponent implements OnInit {
@Input() ctx!: GameContext;
- cert$!: Observable;
+ cert$!: Observable;
toPrint: string = "";
faPrint = faPrint;
@@ -26,7 +26,7 @@ export class CertificateComponent implements OnInit {
this.cert$ = this.apiPlayer.getCertificate(this.ctx.player.id).pipe(
tap(g => this.toPrint = g.html),
// sanitize html to render in iframe binding and add wrapper div to center certificate
- map(g => this.sanitizer.bypassSecurityTrustHtml(` ${g.html} `)),
+ map(g => this.sanitizer.sanitize(SecurityContext.HTML, `${g.html} `) || ''),
);
}
diff --git a/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.html b/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.html
index ee51eee6..ea4fab51 100644
--- a/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.html
+++ b/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.html
@@ -14,7 +14,8 @@ Certificates
- {{cert.player.approvedName}}
+
+ {{cert.player.approvedName}}
-
-
+
No certificates yet
diff --git a/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.ts b/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.ts
index 4109baae..aa4a3b9c 100644
--- a/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.ts
+++ b/projects/gameboard-ui/src/app/home/certificate-list/certificate-list.component.ts
@@ -1,8 +1,7 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, SecurityContext } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { faArrowLeft, faAward, faPrint, faMedal, faUser, faUsers } from '@fortawesome/free-solid-svg-icons';
import { Observable } from 'rxjs';
-import { map } from 'rxjs/operators';
import { PlayerCertificate } from '../../api/player-models';
import { PlayerService } from '../../api/player.service';
@@ -19,29 +18,28 @@ export class CertificateListComponent implements OnInit {
faUser = faUser;
faUsers = faUsers;
certs$: Observable;
+
constructor(
- private apiPlayer: PlayerService,
+ apiPlayer: PlayerService,
private sanitizer: DomSanitizer
- ) {
- this.certs$ = apiPlayer.getUserCertificates().pipe(
- map(c => c.map(a => ({...a, safeHtml: sanitizer.bypassSecurityTrustHtml(a.html)}))
- ));
+ ) {
+ this.certs$ = apiPlayer.getUserCertificates();
}
ngOnInit(): void {
}
- print(html: string): void {
+ print(cert: PlayerCertificate): void {
let printWindow = window.open('', '', '');
// make sure background is always there and no margins to print to pdf as is
printWindow?.document?.write(``);
printWindow?.document?.write(``);
printWindow?.document?.write(``);
- printWindow?.document.write(html);
+ printWindow?.document.write(this.sanitizer.sanitize(SecurityContext.HTML, cert.html) || "");
printWindow?.document.close();
printWindow?.focus();
printWindow?.addEventListener('load', printWindow?.print, true); // wait until all content loads before printing
// don't close new tab automatically in case want to keep open for some reason [ printWindow?.close(); ]
- }
+ }
}
diff --git a/projects/gameboard-ui/src/app/services/router.service.ts b/projects/gameboard-ui/src/app/services/router.service.ts
index 13725ccd..8b24fa5f 100644
--- a/projects/gameboard-ui/src/app/services/router.service.ts
+++ b/projects/gameboard-ui/src/app/services/router.service.ts
@@ -3,13 +3,16 @@ import { Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class RouterService {
-
constructor(public router: Router) { }
public goHome(): void {
this.router.navigateByUrl("/");
}
+ public toSupportTickets(highlightTicketKey: string) {
+ return this.router.navigateByUrl(this.router.parseUrl(`/support/tickets/${highlightTicketKey}`));
+ }
+
public tryGoBack() {
const prevUrl = this.router.getCurrentNavigation()?.previousNavigation?.initialUrl;
if (prevUrl) {
diff --git a/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.html b/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.html
index 2e8243c9..cecd73c2 100644
--- a/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.html
+++ b/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.html
@@ -7,7 +7,7 @@
Back
-
+
{{ctx.ticket.fullKey}}
{{ctx.ticket.summary}}
@@ -22,8 +22,9 @@
Save
-
-
+
+ {{ctx.ticket.status}}
@@ -69,7 +70,7 @@
-
-
\ No newline at end of file
+
diff --git a/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts b/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts
index c06c0f6e..689c9b79 100644
--- a/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts
+++ b/projects/gameboard-ui/src/app/support/ticket-details/ticket-details.component.ts
@@ -14,7 +14,6 @@ import { UserService } from '../../api/user.service';
import { EditData, SuggestionOption } from '../../utility/components/inplace-editor/inplace-editor.component';
import { UserService as LocalUserService } from '../../utility/user.service';
import { NotificationService } from '../../services/notification.service';
-import linkifyHtml from 'linkify-html';
import { ClipboardService } from "../../utility/services/clipboard.service";
import { ToastService } from '../../utility/services/toast.service';
import { FontAwesomeService } from '../../services/font-awesome.service';
@@ -55,6 +54,7 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy {
editingContent = false;
savingContent = false;
editingCommentId = null;
+ errors: string[] = [];
currentLabels = new Set();
@@ -105,7 +105,6 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy {
const ticket$ = combineLatest([
route.params,
this.refresh$,
- // timer(0, 30_000) // refresh-causing line - runs every 30 seconds
]).pipe(
map(([p, r]) => p),
filter(p => !!p.id && (!this.editingContent || this.savingContent)), // don't refresh data if editing and not saving yet
@@ -127,12 +126,16 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy {
tap(a => {
// Initialize ticket attachment URL object
this.attachmentObjectUrls = new Array(a.attachmentFiles.length);
+
// Set the selected object urls
this.selectedObjectUrls = this.attachmentObjectUrls;
+
// Fetch each original ticket's attachment file
a.attachmentFiles.forEach((f, i) => this.fetchFile(f, i));
+
// Initialize comment attachment URL object - store it in a map to account for multiple comments
a.activity.forEach((g, i) => this.commentAttachmentMap.set(g.id, new Array(g.attachmentFiles.length)));
+
// Fetch each comment's attachments
a.activity.forEach((g, i) => g.attachmentFiles.forEach((f, j) => this.fetchFile(f, j, g)));
a.selfCreated = a.creatorId == a.requesterId;
@@ -174,9 +177,11 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy {
// Get the image element on the screen, abort if it or the blob is null
let img = activity ? document.getElementById(`comment-attachment-${activity.id}-${imgId}`) : document.getElementById(`attachment-${imgId}`);
if (img == null || a.body == null) return;
+
// Create a new object URL from the blob, then set the src to reference that
- let url: string = URL.createObjectURL(a.body);
+ const url = URL.createObjectURL(a.body);
img.setAttribute("src", url);
+
// Set the appropriate storage object based on whether this is being called on a comment or not
if (activity) this.commentAttachmentMap.get(activity.id)![imgId] = this.sanitizer.bypassSecurityTrustResourceUrl(url);
else this.attachmentObjectUrls[imgId] = this.sanitizer.bypassSecurityTrustResourceUrl(url);
@@ -201,6 +206,7 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy {
}
addComment() {
+ this.errors = [];
if (!!this.newCommentText || !!this.newCommentAttachments) {
let newComment = {
ticketId: this.id,
@@ -216,7 +222,7 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy {
this.resetAttachments$.next(true);
},
(err) => {
- alert(err);
+ this.errors.push(err);
}
);
}
@@ -476,9 +482,4 @@ export class TicketDetailsComponent implements AfterViewInit, OnDestroy {
});
});
}
-
- public detectLinks(body: string, id: string): void {
- var elem = document.getElementById(id);
- if (elem && body) elem.innerHTML = linkifyHtml(body, { nl2br: true });
- }
}
diff --git a/projects/gameboard-ui/src/app/support/ticket-form/ticket-form.component.html b/projects/gameboard-ui/src/app/support/ticket-form/ticket-form.component.html
index 04d9ecdd..f450f20f 100644
--- a/projects/gameboard-ui/src/app/support/ticket-form/ticket-form.component.html
+++ b/projects/gameboard-ui/src/app/support/ticket-form/ticket-form.component.html
@@ -7,40 +7,43 @@
|