From 807d109d4f205f740d12b2892a35ffe2be8770e8 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 5 Jun 2026 12:07:06 -0700 Subject: [PATCH 01/19] Created admin latest news component --- .../admin-latest-news.component.html | 1 + .../admin-latest-news.component.scss | 0 .../admin-latest-news.component.spec.ts | 23 +++++++++++++++++++ .../admin-latest-news.component.ts | 11 +++++++++ src/app/home/home.component.html | 4 ++++ 5 files changed, 39 insertions(+) create mode 100644 src/app/home/admin-latest-news/admin-latest-news.component.html create mode 100644 src/app/home/admin-latest-news/admin-latest-news.component.scss create mode 100644 src/app/home/admin-latest-news/admin-latest-news.component.spec.ts create mode 100644 src/app/home/admin-latest-news/admin-latest-news.component.ts diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.html b/src/app/home/admin-latest-news/admin-latest-news.component.html new file mode 100644 index 00000000000..5375a7cf669 --- /dev/null +++ b/src/app/home/admin-latest-news/admin-latest-news.component.html @@ -0,0 +1 @@ +

admin-latest-news works!

diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.scss b/src/app/home/admin-latest-news/admin-latest-news.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts b/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts new file mode 100644 index 00000000000..54ea2b82a4a --- /dev/null +++ b/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminLatestNewsComponent } from './admin-latest-news.component'; + +describe('AdminLatestNewsComponent', () => { + let component: AdminLatestNewsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AdminLatestNewsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AdminLatestNewsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.ts b/src/app/home/admin-latest-news/admin-latest-news.component.ts new file mode 100644 index 00000000000..b4a3ceaf3ff --- /dev/null +++ b/src/app/home/admin-latest-news/admin-latest-news.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'admin-latest-news', + imports: [], + templateUrl: './admin-latest-news.component.html', + styleUrl: './admin-latest-news.component.scss' +}) +export class AdminLatestNewsComponent { + +} diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index ed9027b891e..8a386998c7d 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -46,6 +46,10 @@ queryString="order=latest" /> + } @else { + + + }
From 3cea440846297394f65f5d75ba2f2e64f55d2fd1 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Thu, 11 Jun 2026 23:25:17 -0700 Subject: [PATCH 02/19] Moved content of DiscourseLatestNews to new generic LatestNews component used by discourse and admin news --- .../admin-latest-news.component.html | 2 +- .../admin-latest-news.component.ts | 15 ++++- .../discourse-latest-news.component.html | 33 +++------- .../discourse-latest-news.component.scss | 61 ------------------- .../discourse-latest-news.component.ts | 27 +------- src/app/home/home.component.html | 12 ++-- src/app/home/home.component.ts | 2 + .../latest-news/latest-news.component.html | 32 ++++++++++ .../latest-news/latest-news.component.scss | 61 +++++++++++++++++++ .../latest-news/latest-news.component.spec.ts | 22 +++++++ .../home/latest-news/latest-news.component.ts | 36 +++++++++++ 11 files changed, 183 insertions(+), 120 deletions(-) create mode 100644 src/app/home/latest-news/latest-news.component.html create mode 100644 src/app/home/latest-news/latest-news.component.scss create mode 100644 src/app/home/latest-news/latest-news.component.spec.ts create mode 100644 src/app/home/latest-news/latest-news.component.ts diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.html b/src/app/home/admin-latest-news/admin-latest-news.component.html index 5375a7cf669..17cf1da79c2 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.html +++ b/src/app/home/admin-latest-news/admin-latest-news.component.html @@ -1 +1 @@ -

admin-latest-news works!

+ diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.ts b/src/app/home/admin-latest-news/admin-latest-news.component.ts index b4a3ceaf3ff..59b30c5dae8 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.ts @@ -1,11 +1,24 @@ import { Component } from '@angular/core'; +import { News } from '../../domain/news'; +import { NewsService } from '../../services/news.service'; +import { LatestNewsComponent } from '../latest-news/latest-news.component'; @Component({ selector: 'admin-latest-news', - imports: [], + imports: [LatestNewsComponent], templateUrl: './admin-latest-news.component.html', styleUrl: './admin-latest-news.component.scss' }) export class AdminLatestNewsComponent { + protected topics: News[] = []; + protected isLoaded: boolean = false; + constructor(private newsService: NewsService) {} + + ngOnInit(): void { + this.newsService.getAllNews().subscribe((news) => { + this.topics = news; + this.isLoaded = true; + }); + } } diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.html b/src/app/home/discourse-latest-news/discourse-latest-news.component.html index a5ca6d0ad7b..5d23fd353ff 100644 --- a/src/app/home/discourse-latest-news/discourse-latest-news.component.html +++ b/src/app/home/discourse-latest-news/discourse-latest-news.component.html @@ -1,26 +1,7 @@ -
-

- rss_feed - What's New?  -

-
    - @for (topic of topics; track topic; let index = $index) { - @if (!xsScreen || index === 0) { -
  • - - {{ topic.title }} - - @if (smallScreen) { - • - } -
  • - } - } -
-
- More news -
-
+ diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.scss b/src/app/home/discourse-latest-news/discourse-latest-news.component.scss index d96420df049..e69de29bb2d 100644 --- a/src/app/home/discourse-latest-news/discourse-latest-news.component.scss +++ b/src/app/home/discourse-latest-news/discourse-latest-news.component.scss @@ -1,61 +0,0 @@ -@use 'style/abstracts/functions'; - -.latest-news { - padding: 8px; - background-color: rgba(0,0,0,0.8); - border-style: solid; - border-width: 0 0 0 8px; - display: none; - - [dir=rtl] & { - border-width: 0 8px 0 0; - } - - @media (min-width: functions.breakpoint('md.min')) { - padding: 24px; - } - - &.loaded { - display: block; - } - - > * { - @media (max-width: functions.breakpoint('sm.max')) { - display: inline; - } - } -} - -.mat-icon { - vertical-align: sub; -} - -ul { - padding: 0; - - @media (min-width: functions.breakpoint('md.min')) { - padding: 0 0 0 16px; - } -} - -li { - &:not(:last-of-type) { - margin-bottom: 4px; - } - - @media (max-width: functions.breakpoint('sm.max')) { - display: inline; - list-style-type: none; - } - - @media (min-width: functions.breakpoint('md.min')) { - a { - max-width: 100%; - vertical-align: middle; - display: inline-block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } -} diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.ts b/src/app/home/discourse-latest-news/discourse-latest-news.component.ts index fdaf5e4a325..ae089214d2f 100644 --- a/src/app/home/discourse-latest-news/discourse-latest-news.component.ts +++ b/src/app/home/discourse-latest-news/discourse-latest-news.component.ts @@ -1,32 +1,11 @@ -import { HttpClient } from '@angular/common/http'; import { Component } from '@angular/core'; -import { BreakpointObserver } from '@angular/cdk/layout'; import { DiscourseFeedComponent } from '../../discourse-feed/discourse-feed.component'; -import { CommonModule } from '@angular/common'; -import { MatIconModule } from '@angular/material/icon'; +import { LatestNewsComponent } from '../latest-news/latest-news.component'; @Component({ - imports: [CommonModule, MatIconModule], + imports: [LatestNewsComponent], selector: 'discourse-latest-news', styleUrl: 'discourse-latest-news.component.scss', templateUrl: 'discourse-latest-news.component.html' }) -export class DiscourseLatestNewsComponent extends DiscourseFeedComponent { - protected smallScreen: boolean; - protected xsScreen: boolean; - - constructor( - protected http: HttpClient, - private breakpointObserver: BreakpointObserver - ) { - super(http); - this.breakpointObserver - .observe(['(max-width: 40rem)', '(max-width: 48rem)']) - .subscribe((result) => { - this.smallScreen = result.matches; - }); - this.breakpointObserver.observe(['(max-width: 40rem)']).subscribe((result) => { - this.xsScreen = result.matches; - }); - } -} +export class DiscourseLatestNewsComponent extends DiscourseFeedComponent {} diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 8a386998c7d..6d3c9a71090 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -37,20 +37,18 @@ Integrated science learning and teaching with technology - @if (isDiscourseNewsAvailable) { - + + @if (isDiscourseNewsAvailable) { - - } @else { - + } @else { - - } + } +
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 839479af8a7..605ee733898 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -13,12 +13,14 @@ import { HomePageProjectLibraryComponent } from '../modules/library/home-page-pr import { MatButton } from '@angular/material/button'; import { RouterLink } from '@angular/router'; import { CallToActionComponent } from '../modules/shared/call-to-action/call-to-action.component'; +import { AdminLatestNewsComponent } from './admin-latest-news/admin-latest-news.component'; @Component({ animations: [bounceIn, flipInX, flipInY, jackInTheBox, rotateIn, zoomIn], imports: [ HeroSectionComponent, MatIcon, + AdminLatestNewsComponent, DiscourseLatestNewsComponent, NgClass, BlurbComponent, diff --git a/src/app/home/latest-news/latest-news.component.html b/src/app/home/latest-news/latest-news.component.html new file mode 100644 index 00000000000..9ef6b151ed7 --- /dev/null +++ b/src/app/home/latest-news/latest-news.component.html @@ -0,0 +1,32 @@ +
+

+ rss_feed + What's New?  +

+
    + @for (topic of topics; track topic; let index = $index) { + @if (!xsScreen || index === 0) { +
  • + @if (isDiscourseNewsAvailable) { + + {{ topic.title }} + + } @else { + {{ topic.title }} + } + @if (smallScreen) { + • + } +
  • + } + } +
+ @if (isDiscourseNewsAvailable) { +
+ More news +
+ } +
diff --git a/src/app/home/latest-news/latest-news.component.scss b/src/app/home/latest-news/latest-news.component.scss new file mode 100644 index 00000000000..ecd13331ab9 --- /dev/null +++ b/src/app/home/latest-news/latest-news.component.scss @@ -0,0 +1,61 @@ +@use 'style/abstracts/functions'; + +.latest-news { + padding: 8px; + background-color: rgba(0,0,0,0.8); + border-style: solid; + border-width: 0 0 0 8px; + display: none; + + [dir=rtl] & { + border-width: 0 8px 0 0; + } + + @media (min-width: functions.breakpoint('md.min')) { + padding: 24px; + } + + &.loaded { + display: block; + } + + > * { + @media (max-width: functions.breakpoint('sm.max')) { + display: inline; + } + } +} + +.mat-icon { + vertical-align: sub; +} + +ul { + padding: 0; + + @media (min-width: functions.breakpoint('md.min')) { + padding: 0 0 0 16px; + } +} + +li { + &:not(:last-of-type) { + margin-bottom: 4px; + } + + @media (max-width: functions.breakpoint('sm.max')) { + display: inline; + list-style-type: none; + } + + @media (min-width: functions.breakpoint('md.min')) { + a { + max-width: 100%; + vertical-align: middle; + display: inline-block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } +} diff --git a/src/app/home/latest-news/latest-news.component.spec.ts b/src/app/home/latest-news/latest-news.component.spec.ts new file mode 100644 index 00000000000..cd84cd4a7e4 --- /dev/null +++ b/src/app/home/latest-news/latest-news.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LatestNewsComponent } from './latest-news.component'; + +describe('LatestNewsComponent', () => { + let component: LatestNewsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LatestNewsComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(LatestNewsComponent); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home/latest-news/latest-news.component.ts b/src/app/home/latest-news/latest-news.component.ts new file mode 100644 index 00000000000..491113e06fc --- /dev/null +++ b/src/app/home/latest-news/latest-news.component.ts @@ -0,0 +1,36 @@ +import { BreakpointObserver } from '@angular/cdk/layout'; +import { HttpClient } from '@angular/common/http'; +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatIconModule } from '@angular/material/icon'; +import { News } from '../../domain/news'; + +@Component({ + selector: 'latest-news', + imports: [CommonModule, MatIconModule], + templateUrl: './latest-news.component.html', + styleUrl: './latest-news.component.scss' +}) +export class LatestNewsComponent { + protected smallScreen: boolean; + protected xsScreen: boolean; + @Input() isDiscourseNewsAvailable: boolean; + @Input() topics: News[] | { slug: string; id: number; title: string }[]; + @Input() baseUrl?: string; + @Input() category?: string; + @Input() isLoaded: boolean; + + constructor( + protected http: HttpClient, + private breakpointObserver: BreakpointObserver + ) { + this.breakpointObserver + .observe(['(max-width: 40rem)', '(max-width: 48rem)']) + .subscribe((result) => { + this.smallScreen = result.matches; + }); + this.breakpointObserver.observe(['(max-width: 40rem)']).subscribe((result) => { + this.xsScreen = result.matches; + }); + } +} From 58eb109eb52758dea07e29d0548645559608c3c2 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Thu, 11 Jun 2026 23:43:18 -0700 Subject: [PATCH 03/19] Fixed isDiscourseNewsAvailable always truthy bug --- src/app/home/admin-latest-news/admin-latest-news.component.html | 2 +- .../discourse-latest-news/discourse-latest-news.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.html b/src/app/home/admin-latest-news/admin-latest-news.component.html index 17cf1da79c2..39f8f79f8ec 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.html +++ b/src/app/home/admin-latest-news/admin-latest-news.component.html @@ -1 +1 @@ - + diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.html b/src/app/home/discourse-latest-news/discourse-latest-news.component.html index 5d23fd353ff..4beb7eb73c6 100644 --- a/src/app/home/discourse-latest-news/discourse-latest-news.component.html +++ b/src/app/home/discourse-latest-news/discourse-latest-news.component.html @@ -1,5 +1,5 @@ Date: Fri, 12 Jun 2026 11:11:00 -0700 Subject: [PATCH 04/19] Max 3 admin news --- src/app/home/admin-latest-news/admin-latest-news.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.ts b/src/app/home/admin-latest-news/admin-latest-news.component.ts index 59b30c5dae8..7f7851406aa 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.ts @@ -17,7 +17,7 @@ export class AdminLatestNewsComponent { ngOnInit(): void { this.newsService.getAllNews().subscribe((news) => { - this.topics = news; + this.topics = news.slice(0, 3); this.isLoaded = true; }); } From fd192559a136c4171e584657668c8d3e61515cc3 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 12 Jun 2026 11:18:15 -0700 Subject: [PATCH 05/19] Only show news if there are news topics and fixed bug with dot dividers not showing up on medium screens --- src/app/home/latest-news/latest-news.component.html | 7 ++----- src/app/home/latest-news/latest-news.component.scss | 6 ++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/home/latest-news/latest-news.component.html b/src/app/home/latest-news/latest-news.component.html index 9ef6b151ed7..e02b7c82d93 100644 --- a/src/app/home/latest-news/latest-news.component.html +++ b/src/app/home/latest-news/latest-news.component.html @@ -1,3 +1,4 @@ +@if (topics) {

• } } - @if (isDiscourseNewsAvailable) { } -

diff --git a/src/app/home/latest-news/latest-news.component.scss b/src/app/home/latest-news/latest-news.component.scss index ecd13331ab9..64220f3f1e1 100644 --- a/src/app/home/latest-news/latest-news.component.scss +++ b/src/app/home/latest-news/latest-news.component.scss @@ -30,6 +30,12 @@ vertical-align: sub; } +.divider { + @media (min-width: functions.breakpoint('sm.max')) { + display: none; + } +} + ul { padding: 0; From bdfbb3428ff2ed8a25dbb777771a3e207fc5d45c Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 12 Jun 2026 12:18:13 -0700 Subject: [PATCH 06/19] Dialog for more admin news --- .../latest-news/latest-news.component.html | 51 +++++++++-------- .../home/latest-news/latest-news.component.ts | 15 ++++- .../more-news-dialog.component.html | 43 ++++++++++++++ .../more-news-dialog.component.scss | 57 +++++++++++++++++++ .../more-news-dialog.component.spec.ts | 22 +++++++ .../more-news-dialog.component.ts | 17 ++++++ 6 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.html create mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.scss create mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.spec.ts create mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.ts diff --git a/src/app/home/latest-news/latest-news.component.html b/src/app/home/latest-news/latest-news.component.html index e02b7c82d93..93f21fc1589 100644 --- a/src/app/home/latest-news/latest-news.component.html +++ b/src/app/home/latest-news/latest-news.component.html @@ -1,29 +1,34 @@ @if (topics) { -
-

- rss_feed - What's New?  -

-
- More news + @if (isDiscourseNewsAvailable) { + More news + } @else { + More news + }
- } +
+} diff --git a/src/app/home/latest-news/latest-news.component.ts b/src/app/home/latest-news/latest-news.component.ts index 491113e06fc..1b1db6d9634 100644 --- a/src/app/home/latest-news/latest-news.component.ts +++ b/src/app/home/latest-news/latest-news.component.ts @@ -4,6 +4,8 @@ import { Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; import { News } from '../../domain/news'; +import { MatDialog } from '@angular/material/dialog'; +import { MoreNewsDialogComponent } from '../more-news-dialog/more-news-dialog.component'; @Component({ selector: 'latest-news', @@ -21,8 +23,9 @@ export class LatestNewsComponent { @Input() isLoaded: boolean; constructor( - protected http: HttpClient, - private breakpointObserver: BreakpointObserver + private breakpointObserver: BreakpointObserver, + protected dialog: MatDialog, + protected http: HttpClient ) { this.breakpointObserver .observe(['(max-width: 40rem)', '(max-width: 48rem)']) @@ -33,4 +36,12 @@ export class LatestNewsComponent { this.xsScreen = result.matches; }); } + + openNewsDialog(event: Event): void { + event.preventDefault(); + this.dialog.open(MoreNewsDialogComponent, { + panelClass: 'dialog-sm', + data: { topics: this.topics } + }); + } } diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.html b/src/app/home/more-news-dialog/more-news-dialog.component.html new file mode 100644 index 00000000000..89e9aef08e3 --- /dev/null +++ b/src/app/home/more-news-dialog/more-news-dialog.component.html @@ -0,0 +1,43 @@ +
+

More News

+ +
+ + + + @for (topic of data.topics; track topic.id) { + + + + @if (topic.news && accordionItem.expanded) { +
+ {{ topic.news }} +
+ } +
+ } +
+
diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.scss b/src/app/home/more-news-dialog/more-news-dialog.component.scss new file mode 100644 index 00000000000..46eb1152f3c --- /dev/null +++ b/src/app/home/more-news-dialog/more-news-dialog.component.scss @@ -0,0 +1,57 @@ +.dialog-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.close-button { + margin-right: 10px; + :hover { + cursor: pointer; + } +} + +.news-accordion { + display: block; +} + +.news-item { + display: block; + border: solid 1px #ccc; +} + +.news-item + .news-item { + border-top: none; +} + +.news-item-header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + background: none; + border: none; + padding: 16px; + text-align: left; + +} + +.news-item-body { + padding: 16px; + margin-left: 16px; +} + +.has-news:hover { + cursor: pointer; + background-color: #eee; +} + +.news-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +.news-item:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} \ No newline at end of file diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts b/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts new file mode 100644 index 00000000000..c0f43616bad --- /dev/null +++ b/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MoreNewsDialogComponent } from './more-news-dialog.component'; + +describe('MoreNewsDialogComponent', () => { + let component: MoreNewsDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MoreNewsDialogComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(MoreNewsDialogComponent); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.ts b/src/app/home/more-news-dialog/more-news-dialog.component.ts new file mode 100644 index 00000000000..000790f1688 --- /dev/null +++ b/src/app/home/more-news-dialog/more-news-dialog.component.ts @@ -0,0 +1,17 @@ +import { CdkAccordionModule } from '@angular/cdk/accordion'; +import { Component, inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { News } from '../../domain/news'; + +@Component({ + selector: 'more-news-dialog', + imports: [CdkAccordionModule, MatDialogModule, MatDividerModule, MatIconModule], + templateUrl: './more-news-dialog.component.html', + styleUrl: './more-news-dialog.component.scss' +}) +export class MoreNewsDialogComponent { + readonly dialogRef = inject(MatDialogRef); + readonly data = inject<{ topics: News[] }>(MAT_DIALOG_DATA); +} From 92154a7729cd0f0bb0f9fb5608e06c593d10b203 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 12 Jun 2026 12:31:22 -0700 Subject: [PATCH 07/19] Sort admin news descending by date and input all to LatestNews instead of just top 3 --- .../admin-latest-news/admin-latest-news.component.ts | 2 +- src/app/home/latest-news/latest-news.component.html | 2 +- src/app/home/latest-news/latest-news.component.ts | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.ts b/src/app/home/admin-latest-news/admin-latest-news.component.ts index 7f7851406aa..33d56562ab1 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.ts @@ -17,7 +17,7 @@ export class AdminLatestNewsComponent { ngOnInit(): void { this.newsService.getAllNews().subscribe((news) => { - this.topics = news.slice(0, 3); + this.topics = news.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); this.isLoaded = true; }); } diff --git a/src/app/home/latest-news/latest-news.component.html b/src/app/home/latest-news/latest-news.component.html index 93f21fc1589..c3f6cb5222b 100644 --- a/src/app/home/latest-news/latest-news.component.html +++ b/src/app/home/latest-news/latest-news.component.html @@ -8,7 +8,7 @@ What's New? 
    - @for (topic of topics; track topic; let index = $index) { + @for (topic of threeTopics; track topic; let index = $index) { @if (!xsScreen || index === 0) {
  • @if (isDiscourseNewsAvailable) { diff --git a/src/app/home/latest-news/latest-news.component.ts b/src/app/home/latest-news/latest-news.component.ts index 1b1db6d9634..beed6f5e8dc 100644 --- a/src/app/home/latest-news/latest-news.component.ts +++ b/src/app/home/latest-news/latest-news.component.ts @@ -7,6 +7,8 @@ import { News } from '../../domain/news'; import { MatDialog } from '@angular/material/dialog'; import { MoreNewsDialogComponent } from '../more-news-dialog/more-news-dialog.component'; +type Topic = News | { slug: string; id: number; title: string }; + @Component({ selector: 'latest-news', imports: [CommonModule, MatIconModule], @@ -17,7 +19,8 @@ export class LatestNewsComponent { protected smallScreen: boolean; protected xsScreen: boolean; @Input() isDiscourseNewsAvailable: boolean; - @Input() topics: News[] | { slug: string; id: number; title: string }[]; + @Input() topics: Topic[]; + protected threeTopics: Topic[]; @Input() baseUrl?: string; @Input() category?: string; @Input() isLoaded: boolean; @@ -37,6 +40,10 @@ export class LatestNewsComponent { }); } + ngOnChanges(): void { + this.threeTopics = this.topics.slice(0, 3); + } + openNewsDialog(event: Event): void { event.preventDefault(); this.dialog.open(MoreNewsDialogComponent, { From b06b3383ee2e7b832ba81b048610a12d08e68e22 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 13 Jun 2026 12:38:43 -0700 Subject: [PATCH 08/19] Dialog for individual news items --- .../latest-news/latest-news.component.html | 4 +++- .../home/latest-news/latest-news.component.ts | 11 +++++++++- .../more-news-dialog.component.html | 2 +- .../news-item-dialog.component.html | 12 ++++++++++ .../news-item-dialog.component.scss | 12 ++++++++++ .../news-item-dialog.component.spec.ts | 22 +++++++++++++++++++ .../news-item-dialog.component.ts | 16 ++++++++++++++ 7 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.html create mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.scss create mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.spec.ts create mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.ts diff --git a/src/app/home/latest-news/latest-news.component.html b/src/app/home/latest-news/latest-news.component.html index c3f6cb5222b..eb1ca9cdcc3 100644 --- a/src/app/home/latest-news/latest-news.component.html +++ b/src/app/home/latest-news/latest-news.component.html @@ -16,7 +16,9 @@ {{ topic.title }} } @else { - {{ topic.title }} + + {{ topic.title }} + }
  • diff --git a/src/app/home/latest-news/latest-news.component.ts b/src/app/home/latest-news/latest-news.component.ts index beed6f5e8dc..f134d0a73e1 100644 --- a/src/app/home/latest-news/latest-news.component.ts +++ b/src/app/home/latest-news/latest-news.component.ts @@ -6,6 +6,7 @@ import { MatIconModule } from '@angular/material/icon'; import { News } from '../../domain/news'; import { MatDialog } from '@angular/material/dialog'; import { MoreNewsDialogComponent } from '../more-news-dialog/more-news-dialog.component'; +import { NewsItemDialogComponent } from '../news-item-dialog/news-item-dialog.component'; type Topic = News | { slug: string; id: number; title: string }; @@ -44,11 +45,19 @@ export class LatestNewsComponent { this.threeTopics = this.topics.slice(0, 3); } - openNewsDialog(event: Event): void { + protected openNewsDialog(event: Event): void { event.preventDefault(); this.dialog.open(MoreNewsDialogComponent, { panelClass: 'dialog-sm', data: { topics: this.topics } }); } + + protected openNewsItemDialog(event: Event, newsItem: News): void { + event.preventDefault(); + this.dialog.open(NewsItemDialogComponent, { + panelClass: 'dialog-sm', + data: { newsItem: newsItem } + }); + } } diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.html b/src/app/home/more-news-dialog/more-news-dialog.component.html index 89e9aef08e3..47791471a30 100644 --- a/src/app/home/more-news-dialog/more-news-dialog.component.html +++ b/src/app/home/more-news-dialog/more-news-dialog.component.html @@ -1,5 +1,5 @@
    -

    More News

    +

    Latest News

    diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.html b/src/app/home/news-item-dialog/news-item-dialog.component.html new file mode 100644 index 00000000000..e9320b8fab3 --- /dev/null +++ b/src/app/home/news-item-dialog/news-item-dialog.component.html @@ -0,0 +1,12 @@ +
    +

    {{ newsItem.title }}

    + +
    +@if (newsItem.news) { + + +

    {{ newsItem.news }}

    +
    +} diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.scss b/src/app/home/news-item-dialog/news-item-dialog.component.scss new file mode 100644 index 00000000000..ceae7a560f8 --- /dev/null +++ b/src/app/home/news-item-dialog/news-item-dialog.component.scss @@ -0,0 +1,12 @@ +.dialog-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.close-button { + margin-right: 10px; + :hover { + cursor: pointer; + } +} \ No newline at end of file diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts b/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts new file mode 100644 index 00000000000..120f80a5caf --- /dev/null +++ b/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewsItemDialogComponent } from './news-item-dialog.component'; + +describe('NewsItemDialogComponent', () => { + let component: NewsItemDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NewsItemDialogComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(NewsItemDialogComponent); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.ts b/src/app/home/news-item-dialog/news-item-dialog.component.ts new file mode 100644 index 00000000000..19dc018c247 --- /dev/null +++ b/src/app/home/news-item-dialog/news-item-dialog.component.ts @@ -0,0 +1,16 @@ +import { Component, inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { News } from '../../domain/news'; + +@Component({ + selector: 'news-item-dialog', + imports: [MatDialogModule, MatDividerModule, MatIconModule], + templateUrl: './news-item-dialog.component.html', + styleUrl: './news-item-dialog.component.scss' +}) +export class NewsItemDialogComponent { + readonly dialogRef = inject(MatDialogRef); + readonly newsItem = inject<{ newsItem: News }>(MAT_DIALOG_DATA).newsItem; +} From 252103094e17376f660f2603a8632551a0d26f43 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 13 Jun 2026 12:39:23 -0700 Subject: [PATCH 09/19] Only show public news --- src/app/home/admin-latest-news/admin-latest-news.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.ts b/src/app/home/admin-latest-news/admin-latest-news.component.ts index 33d56562ab1..af5b3275259 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.ts @@ -17,7 +17,9 @@ export class AdminLatestNewsComponent { ngOnInit(): void { this.newsService.getAllNews().subscribe((news) => { - this.topics = news.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + this.topics = news + .filter((news) => news.type === 'public') + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); this.isLoaded = true; }); } From f2357c651a6d308b33e0324b9321f8d174ce88d3 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 13 Jun 2026 15:30:44 -0700 Subject: [PATCH 10/19] Added date posted to news dialogs --- .../more-news-dialog/more-news-dialog.component.html | 10 +--------- .../more-news-dialog/more-news-dialog.component.ts | 2 ++ .../news-item-dialog/news-item-dialog.component.html | 4 +--- .../news-item-dialog/news-item-dialog.component.scss | 4 ++++ .../news-item-dialog/news-item-dialog.component.ts | 2 ++ src/app/services/news.service.ts | 10 ++++++++++ 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.html b/src/app/home/more-news-dialog/more-news-dialog.component.html index 47791471a30..f3c7676959e 100644 --- a/src/app/home/more-news-dialog/more-news-dialog.component.html +++ b/src/app/home/more-news-dialog/more-news-dialog.component.html @@ -16,15 +16,7 @@

    Latest News

    [attr.id]="'news-item-header-' + topic.id" > {{ topic.title }} - @if (topic.news) { - - @if (accordionItem.expanded) { - remove - } @else { - add - } - - } + {{ NewsService.getNewsDateMDY(topic) }} @if (topic.news && accordionItem.expanded) { diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.ts b/src/app/home/more-news-dialog/more-news-dialog.component.ts index 000790f1688..da57dde616f 100644 --- a/src/app/home/more-news-dialog/more-news-dialog.component.ts +++ b/src/app/home/more-news-dialog/more-news-dialog.component.ts @@ -4,6 +4,7 @@ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/materia import { MatDividerModule } from '@angular/material/divider'; import { MatIconModule } from '@angular/material/icon'; import { News } from '../../domain/news'; +import { NewsService } from '../../services/news.service'; @Component({ selector: 'more-news-dialog', @@ -14,4 +15,5 @@ import { News } from '../../domain/news'; export class MoreNewsDialogComponent { readonly dialogRef = inject(MatDialogRef); readonly data = inject<{ topics: News[] }>(MAT_DIALOG_DATA); + readonly NewsService = inject(NewsService); } diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.html b/src/app/home/news-item-dialog/news-item-dialog.component.html index e9320b8fab3..442e633673c 100644 --- a/src/app/home/news-item-dialog/news-item-dialog.component.html +++ b/src/app/home/news-item-dialog/news-item-dialog.component.html @@ -1,8 +1,6 @@

    {{ newsItem.title }}

    - + {{ NewsService.getNewsDateMDY(newsItem) }}
    @if (newsItem.news) { diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.scss b/src/app/home/news-item-dialog/news-item-dialog.component.scss index ceae7a560f8..1c8e7381bf7 100644 --- a/src/app/home/news-item-dialog/news-item-dialog.component.scss +++ b/src/app/home/news-item-dialog/news-item-dialog.component.scss @@ -9,4 +9,8 @@ :hover { cursor: pointer; } +} + +.news-item-date { + margin-right: 16px; } \ No newline at end of file diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.ts b/src/app/home/news-item-dialog/news-item-dialog.component.ts index 19dc018c247..94e817d290e 100644 --- a/src/app/home/news-item-dialog/news-item-dialog.component.ts +++ b/src/app/home/news-item-dialog/news-item-dialog.component.ts @@ -3,6 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/materia import { MatDividerModule } from '@angular/material/divider'; import { MatIconModule } from '@angular/material/icon'; import { News } from '../../domain/news'; +import { NewsService } from '../../services/news.service'; @Component({ selector: 'news-item-dialog', @@ -13,4 +14,5 @@ import { News } from '../../domain/news'; export class NewsItemDialogComponent { readonly dialogRef = inject(MatDialogRef); readonly newsItem = inject<{ newsItem: News }>(MAT_DIALOG_DATA).newsItem; + readonly NewsService = inject(NewsService); } diff --git a/src/app/services/news.service.ts b/src/app/services/news.service.ts index 5c984fe7b65..66a65bab619 100644 --- a/src/app/services/news.service.ts +++ b/src/app/services/news.service.ts @@ -15,4 +15,14 @@ export class NewsService { const headers = new HttpHeaders({ 'Cache-Control': 'no-cache' }); return this.http.get(this.newsUrl, { headers: headers }) as Observable; } + + getNewsDateMDY(news: News): string { + const removeLeadingZero = (str: string) => str.replace(/^0+/, ''); + + const month = removeLeadingZero(news.date.slice(5, 7)); + const day = removeLeadingZero(news.date.slice(8, 10)); + const year = news.date.slice(2, 4); + + return `${month}/${day}/${year}`; + } } From 0ca44c9d91e1ef596a610d87a5eee16c303dd386 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 13 Jun 2026 15:57:34 -0700 Subject: [PATCH 11/19] Dialog styling --- .../admin-latest-news.component.ts | 2 +- .../home/latest-news/latest-news.component.ts | 14 +++++++------- .../more-news-dialog.component.html | 6 +++--- .../more-news-dialog.component.scss | 13 +++++++++++-- .../news-item-dialog.component.html | 6 +++--- .../news-item-dialog.component.scss | 18 +++++++++++++----- 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.ts b/src/app/home/admin-latest-news/admin-latest-news.component.ts index af5b3275259..ee4f6e8341f 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; +import { LatestNewsComponent } from '../latest-news/latest-news.component'; import { News } from '../../domain/news'; import { NewsService } from '../../services/news.service'; -import { LatestNewsComponent } from '../latest-news/latest-news.component'; @Component({ selector: 'admin-latest-news', diff --git a/src/app/home/latest-news/latest-news.component.ts b/src/app/home/latest-news/latest-news.component.ts index f134d0a73e1..8cf35ebbd83 100644 --- a/src/app/home/latest-news/latest-news.component.ts +++ b/src/app/home/latest-news/latest-news.component.ts @@ -1,11 +1,11 @@ import { BreakpointObserver } from '@angular/cdk/layout'; -import { HttpClient } from '@angular/common/http'; import { Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; import { MatIconModule } from '@angular/material/icon'; -import { News } from '../../domain/news'; import { MatDialog } from '@angular/material/dialog'; import { MoreNewsDialogComponent } from '../more-news-dialog/more-news-dialog.component'; +import { News } from '../../domain/news'; import { NewsItemDialogComponent } from '../news-item-dialog/news-item-dialog.component'; type Topic = News | { slug: string; id: number; title: string }; @@ -17,14 +17,14 @@ type Topic = News | { slug: string; id: number; title: string }; styleUrl: './latest-news.component.scss' }) export class LatestNewsComponent { - protected smallScreen: boolean; - protected xsScreen: boolean; - @Input() isDiscourseNewsAvailable: boolean; - @Input() topics: Topic[]; - protected threeTopics: Topic[]; @Input() baseUrl?: string; @Input() category?: string; + @Input() isDiscourseNewsAvailable: boolean; @Input() isLoaded: boolean; + @Input() topics: Topic[]; + protected threeTopics: Topic[]; + protected smallScreen: boolean; + protected xsScreen: boolean; constructor( private breakpointObserver: BreakpointObserver, diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.html b/src/app/home/more-news-dialog/more-news-dialog.component.html index f3c7676959e..406428402c7 100644 --- a/src/app/home/more-news-dialog/more-news-dialog.component.html +++ b/src/app/home/more-news-dialog/more-news-dialog.component.html @@ -1,11 +1,11 @@
    -

    Latest News

    +

    Latest News

    - - + + @for (topic of data.topics; track topic.id) { diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.scss b/src/app/home/more-news-dialog/more-news-dialog.component.scss index 46eb1152f3c..99270bcdf0e 100644 --- a/src/app/home/more-news-dialog/more-news-dialog.component.scss +++ b/src/app/home/more-news-dialog/more-news-dialog.component.scss @@ -2,15 +2,25 @@ display: flex; justify-content: space-between; align-items: center; + background-color: #147997; + color: white; +} + +.dialog-title { + color: white; } .close-button { - margin-right: 10px; + margin-right: 16px; :hover { cursor: pointer; } } +.dialog-divider { + margin-top: 0; +} + .news-accordion { display: block; } @@ -33,7 +43,6 @@ border: none; padding: 16px; text-align: left; - } .news-item-body { diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.html b/src/app/home/news-item-dialog/news-item-dialog.component.html index 442e633673c..b21ff1bb6a4 100644 --- a/src/app/home/news-item-dialog/news-item-dialog.component.html +++ b/src/app/home/news-item-dialog/news-item-dialog.component.html @@ -1,10 +1,10 @@
    -

    {{ newsItem.title }}

    +

    {{ newsItem.title }}

    {{ NewsService.getNewsDateMDY(newsItem) }}
    @if (newsItem.news) { - - + +

    {{ newsItem.news }}

    } diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.scss b/src/app/home/news-item-dialog/news-item-dialog.component.scss index 1c8e7381bf7..a07f964a027 100644 --- a/src/app/home/news-item-dialog/news-item-dialog.component.scss +++ b/src/app/home/news-item-dialog/news-item-dialog.component.scss @@ -2,15 +2,23 @@ display: flex; justify-content: space-between; align-items: center; + background-color: #147997; + color: white; } -.close-button { - margin-right: 10px; - :hover { - cursor: pointer; - } +.dialog-title { + color: white; } .news-item-date { margin-right: 16px; +} + +.dialog-divider { + margin: 0; + padding-bottom: 16px; +} + +.dialog-content { + padding: 16px; } \ No newline at end of file From bec328a582352766ac9ab8056ad0ea1331936186 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sun, 14 Jun 2026 22:17:07 -0700 Subject: [PATCH 12/19] Wrote tests --- .../admin-latest-news.component.spec.ts | 47 +++++++++++++++++-- .../discourse-latest-news.component.spec.ts | 10 ++-- .../home/latest-news/latest-news.component.ts | 2 +- .../more-news-dialog.component.html | 2 +- .../more-news-dialog.component.spec.ts | 9 +++- .../news-item-dialog.component.html | 2 +- .../news-item-dialog.component.spec.ts | 11 ++++- src/app/services/news.service.spec.ts | 27 ++++++++--- src/app/services/news.service.ts | 2 +- 9 files changed, 93 insertions(+), 19 deletions(-) diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts b/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts index 54ea2b82a4a..29d46195bd3 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts @@ -1,16 +1,47 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { AdminLatestNewsComponent } from './admin-latest-news.component'; +import { NewsService } from '../../services/news.service'; +import { News } from '../../domain/news'; +import { of } from 'rxjs'; describe('AdminLatestNewsComponent', () => { let component: AdminLatestNewsComponent; let fixture: ComponentFixture; beforeEach(async () => { + const newsServiceSpy = jasmine.createSpyObj(['getAllNews']); + const news1 = new News({ + id: 1, + date: '2026-02-01 19:14:23.0', + type: 'public', + title: 'Test News', + news: 'news content', + owner: undefined + }); + const news2 = new News({ + id: 2, + date: '2026-01-02 19:14:23.0', + type: 'teacherOnly', + title: 'Test News 2', + news: 'news content 2', + owner: undefined + }); + const news3 = new News({ + id: 3, + date: '2026-02-01 19:15:00.0', + type: 'public', + title: 'Test News 3', + news: 'news content 3', + owner: undefined + }); + + newsServiceSpy.getAllNews.and.callFake(() => of([news1, news2, news3])); + await TestBed.configureTestingModule({ - imports: [AdminLatestNewsComponent] - }) - .compileComponents(); + imports: [AdminLatestNewsComponent], + providers: [{ provide: NewsService, useValue: newsServiceSpy }] + }).compileComponents(); fixture = TestBed.createComponent(AdminLatestNewsComponent); component = fixture.componentInstance; @@ -20,4 +51,12 @@ describe('AdminLatestNewsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should filter and sort topics', fakeAsync(() => { + component.ngOnInit(); + tick(); + expect(component['topics']).toBeTruthy(); + expect(component['topics'].length).toBe(2); + expect(component['topics'][0].title).toBe('Test News 3'); + })); }); diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.spec.ts b/src/app/home/discourse-latest-news/discourse-latest-news.component.spec.ts index 50494108be0..e5570e98934 100644 --- a/src/app/home/discourse-latest-news/discourse-latest-news.component.spec.ts +++ b/src/app/home/discourse-latest-news/discourse-latest-news.component.spec.ts @@ -17,9 +17,13 @@ describe('DiscourseLatestNewsComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [], - providers: [DiscourseLatestNewsComponent, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}); + imports: [], + providers: [ + DiscourseLatestNewsComponent, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting() + ] + }); http = TestBed.inject(HttpTestingController); fixture = TestBed.createComponent(DiscourseLatestNewsComponent); component = fixture.componentInstance; diff --git a/src/app/home/latest-news/latest-news.component.ts b/src/app/home/latest-news/latest-news.component.ts index 8cf35ebbd83..d6ac2914096 100644 --- a/src/app/home/latest-news/latest-news.component.ts +++ b/src/app/home/latest-news/latest-news.component.ts @@ -42,7 +42,7 @@ export class LatestNewsComponent { } ngOnChanges(): void { - this.threeTopics = this.topics.slice(0, 3); + this.threeTopics = this.topics?.slice(0, 3) ?? []; } protected openNewsDialog(event: Event): void { diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.html b/src/app/home/more-news-dialog/more-news-dialog.component.html index 406428402c7..0acd7b4cec2 100644 --- a/src/app/home/more-news-dialog/more-news-dialog.component.html +++ b/src/app/home/more-news-dialog/more-news-dialog.component.html @@ -16,7 +16,7 @@

    Latest News

    [attr.id]="'news-item-header-' + topic.id" > {{ topic.title }} - {{ NewsService.getNewsDateMDY(topic) }} + {{ NewsService.formatNewsDate(topic) }} @if (topic.news && accordionItem.expanded) { diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts b/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts index c0f43616bad..bbccab41df7 100644 --- a/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts +++ b/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts @@ -1,14 +1,21 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MoreNewsDialogComponent } from './more-news-dialog.component'; +import { News } from '../../domain/news'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +const topics = [new News({ id: 1 }), new News({ id: 2 }), new News({ id: 3 })]; describe('MoreNewsDialogComponent', () => { let component: MoreNewsDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [MoreNewsDialogComponent] + imports: [MoreNewsDialogComponent], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: topics }, + { provide: MatDialogRef, useValue: {} } + ] }).compileComponents(); fixture = TestBed.createComponent(MoreNewsDialogComponent); diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.html b/src/app/home/news-item-dialog/news-item-dialog.component.html index b21ff1bb6a4..2d410396c91 100644 --- a/src/app/home/news-item-dialog/news-item-dialog.component.html +++ b/src/app/home/news-item-dialog/news-item-dialog.component.html @@ -1,6 +1,6 @@

    {{ newsItem.title }}

    - {{ NewsService.getNewsDateMDY(newsItem) }} + {{ NewsService.formatNewsDate(newsItem) }}
    @if (newsItem.news) { diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts b/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts index 120f80a5caf..664bf365794 100644 --- a/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts +++ b/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts @@ -1,14 +1,23 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NewsItemDialogComponent } from './news-item-dialog.component'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { News } from '../../domain/news'; +const newsItem = { + newsItem: new News({ id: 1 }) +}; describe('NewsItemDialogComponent', () => { let component: NewsItemDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [NewsItemDialogComponent] + imports: [NewsItemDialogComponent], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: newsItem }, + { provide: MatDialogRef, useValue: {} } + ] }).compileComponents(); fixture = TestBed.createComponent(NewsItemDialogComponent); diff --git a/src/app/services/news.service.spec.ts b/src/app/services/news.service.spec.ts index 50ed53a3f7f..c518e207f56 100644 --- a/src/app/services/news.service.spec.ts +++ b/src/app/services/news.service.spec.ts @@ -3,17 +3,32 @@ import { TestBed } from '@angular/core/testing'; import { NewsService } from './news.service'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { News } from '../domain/news'; +let service: NewsService; describe('NewsService', () => { - beforeEach(() => + beforeEach(() => { TestBed.configureTestingModule({ - imports: [], - providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}) - ); + imports: [], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] + }); + service = TestBed.inject(NewsService); + }); it('should be created', () => { - const service: NewsService = TestBed.inject(NewsService); expect(service).toBeTruthy(); }); + + it('should properly format News date', () => { + const news = new News({ + id: 1, + date: '2026-10-01 19:14:23.0', + type: 'public', + title: 'Test News', + news: 'news content', + owner: undefined + }); + const date = service.formatNewsDate(news); + expect(date).toBe('10/1/26'); + }); }); diff --git a/src/app/services/news.service.ts b/src/app/services/news.service.ts index 66a65bab619..c7f84a1453d 100644 --- a/src/app/services/news.service.ts +++ b/src/app/services/news.service.ts @@ -16,7 +16,7 @@ export class NewsService { return this.http.get(this.newsUrl, { headers: headers }) as Observable; } - getNewsDateMDY(news: News): string { + formatNewsDate(news: News): string { const removeLeadingZero = (str: string) => str.replace(/^0+/, ''); const month = removeLeadingZero(news.date.slice(5, 7)); From 247cc57fc6e3d7781b0588ff50bb8fdc03ed07f6 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 24 Jun 2026 19:16:54 -0700 Subject: [PATCH 13/19] Switched to using existing /news page instead of dialogs --- .../latest-news/latest-news.component.html | 4 +- .../home/latest-news/latest-news.component.ts | 21 +----- .../more-news-dialog.component.html | 35 ---------- .../more-news-dialog.component.scss | 66 ------------------- .../more-news-dialog.component.spec.ts | 29 -------- .../more-news-dialog.component.ts | 19 ------ .../news-item-dialog.component.html | 10 --- .../news-item-dialog.component.scss | 24 ------- .../news-item-dialog.component.spec.ts | 31 --------- .../news-item-dialog.component.ts | 18 ----- src/app/news/news.component.html | 14 ++-- src/app/news/news.component.ts | 56 ++++++++++++++-- 12 files changed, 65 insertions(+), 262 deletions(-) delete mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.html delete mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.scss delete mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.spec.ts delete mode 100644 src/app/home/more-news-dialog/more-news-dialog.component.ts delete mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.html delete mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.scss delete mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.spec.ts delete mode 100644 src/app/home/news-item-dialog/news-item-dialog.component.ts diff --git a/src/app/home/latest-news/latest-news.component.html b/src/app/home/latest-news/latest-news.component.html index eb1ca9cdcc3..c923b1edc06 100644 --- a/src/app/home/latest-news/latest-news.component.html +++ b/src/app/home/latest-news/latest-news.component.html @@ -16,7 +16,7 @@ {{ topic.title }} } @else { - + {{ topic.title }} } @@ -29,7 +29,7 @@ @if (isDiscourseNewsAvailable) { More news } @else { - More news + More news }
diff --git a/src/app/home/latest-news/latest-news.component.ts b/src/app/home/latest-news/latest-news.component.ts index d6ac2914096..437a87c87f4 100644 --- a/src/app/home/latest-news/latest-news.component.ts +++ b/src/app/home/latest-news/latest-news.component.ts @@ -4,15 +4,14 @@ import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { MatIconModule } from '@angular/material/icon'; import { MatDialog } from '@angular/material/dialog'; -import { MoreNewsDialogComponent } from '../more-news-dialog/more-news-dialog.component'; import { News } from '../../domain/news'; -import { NewsItemDialogComponent } from '../news-item-dialog/news-item-dialog.component'; +import { RouterLink } from '@angular/router'; type Topic = News | { slug: string; id: number; title: string }; @Component({ selector: 'latest-news', - imports: [CommonModule, MatIconModule], + imports: [CommonModule, MatIconModule, RouterLink], templateUrl: './latest-news.component.html', styleUrl: './latest-news.component.scss' }) @@ -44,20 +43,4 @@ export class LatestNewsComponent { ngOnChanges(): void { this.threeTopics = this.topics?.slice(0, 3) ?? []; } - - protected openNewsDialog(event: Event): void { - event.preventDefault(); - this.dialog.open(MoreNewsDialogComponent, { - panelClass: 'dialog-sm', - data: { topics: this.topics } - }); - } - - protected openNewsItemDialog(event: Event, newsItem: News): void { - event.preventDefault(); - this.dialog.open(NewsItemDialogComponent, { - panelClass: 'dialog-sm', - data: { newsItem: newsItem } - }); - } } diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.html b/src/app/home/more-news-dialog/more-news-dialog.component.html deleted file mode 100644 index 0acd7b4cec2..00000000000 --- a/src/app/home/more-news-dialog/more-news-dialog.component.html +++ /dev/null @@ -1,35 +0,0 @@ -
-

Latest News

- -
- - - - @for (topic of data.topics; track topic.id) { - - - - @if (topic.news && accordionItem.expanded) { -
- {{ topic.news }} -
- } -
- } -
-
diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.scss b/src/app/home/more-news-dialog/more-news-dialog.component.scss deleted file mode 100644 index 99270bcdf0e..00000000000 --- a/src/app/home/more-news-dialog/more-news-dialog.component.scss +++ /dev/null @@ -1,66 +0,0 @@ -.dialog-header { - display: flex; - justify-content: space-between; - align-items: center; - background-color: #147997; - color: white; -} - -.dialog-title { - color: white; -} - -.close-button { - margin-right: 16px; - :hover { - cursor: pointer; - } -} - -.dialog-divider { - margin-top: 0; -} - -.news-accordion { - display: block; -} - -.news-item { - display: block; - border: solid 1px #ccc; -} - -.news-item + .news-item { - border-top: none; -} - -.news-item-header { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - background: none; - border: none; - padding: 16px; - text-align: left; -} - -.news-item-body { - padding: 16px; - margin-left: 16px; -} - -.has-news:hover { - cursor: pointer; - background-color: #eee; -} - -.news-item:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} - -.news-item:last-child { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; -} \ No newline at end of file diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts b/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts deleted file mode 100644 index bbccab41df7..00000000000 --- a/src/app/home/more-news-dialog/more-news-dialog.component.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MoreNewsDialogComponent } from './more-news-dialog.component'; -import { News } from '../../domain/news'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; - -const topics = [new News({ id: 1 }), new News({ id: 2 }), new News({ id: 3 })]; -describe('MoreNewsDialogComponent', () => { - let component: MoreNewsDialogComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [MoreNewsDialogComponent], - providers: [ - { provide: MAT_DIALOG_DATA, useValue: topics }, - { provide: MatDialogRef, useValue: {} } - ] - }).compileComponents(); - - fixture = TestBed.createComponent(MoreNewsDialogComponent); - component = fixture.componentInstance; - await fixture.whenStable(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/home/more-news-dialog/more-news-dialog.component.ts b/src/app/home/more-news-dialog/more-news-dialog.component.ts deleted file mode 100644 index da57dde616f..00000000000 --- a/src/app/home/more-news-dialog/more-news-dialog.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CdkAccordionModule } from '@angular/cdk/accordion'; -import { Component, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatIconModule } from '@angular/material/icon'; -import { News } from '../../domain/news'; -import { NewsService } from '../../services/news.service'; - -@Component({ - selector: 'more-news-dialog', - imports: [CdkAccordionModule, MatDialogModule, MatDividerModule, MatIconModule], - templateUrl: './more-news-dialog.component.html', - styleUrl: './more-news-dialog.component.scss' -}) -export class MoreNewsDialogComponent { - readonly dialogRef = inject(MatDialogRef); - readonly data = inject<{ topics: News[] }>(MAT_DIALOG_DATA); - readonly NewsService = inject(NewsService); -} diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.html b/src/app/home/news-item-dialog/news-item-dialog.component.html deleted file mode 100644 index 2d410396c91..00000000000 --- a/src/app/home/news-item-dialog/news-item-dialog.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
-

{{ newsItem.title }}

- {{ NewsService.formatNewsDate(newsItem) }} -
-@if (newsItem.news) { - - -

{{ newsItem.news }}

-
-} diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.scss b/src/app/home/news-item-dialog/news-item-dialog.component.scss deleted file mode 100644 index a07f964a027..00000000000 --- a/src/app/home/news-item-dialog/news-item-dialog.component.scss +++ /dev/null @@ -1,24 +0,0 @@ -.dialog-header { - display: flex; - justify-content: space-between; - align-items: center; - background-color: #147997; - color: white; -} - -.dialog-title { - color: white; -} - -.news-item-date { - margin-right: 16px; -} - -.dialog-divider { - margin: 0; - padding-bottom: 16px; -} - -.dialog-content { - padding: 16px; -} \ No newline at end of file diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts b/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts deleted file mode 100644 index 664bf365794..00000000000 --- a/src/app/home/news-item-dialog/news-item-dialog.component.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { NewsItemDialogComponent } from './news-item-dialog.component'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { News } from '../../domain/news'; - -const newsItem = { - newsItem: new News({ id: 1 }) -}; -describe('NewsItemDialogComponent', () => { - let component: NewsItemDialogComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [NewsItemDialogComponent], - providers: [ - { provide: MAT_DIALOG_DATA, useValue: newsItem }, - { provide: MatDialogRef, useValue: {} } - ] - }).compileComponents(); - - fixture = TestBed.createComponent(NewsItemDialogComponent); - component = fixture.componentInstance; - await fixture.whenStable(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/home/news-item-dialog/news-item-dialog.component.ts b/src/app/home/news-item-dialog/news-item-dialog.component.ts deleted file mode 100644 index 94e817d290e..00000000000 --- a/src/app/home/news-item-dialog/news-item-dialog.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatIconModule } from '@angular/material/icon'; -import { News } from '../../domain/news'; -import { NewsService } from '../../services/news.service'; - -@Component({ - selector: 'news-item-dialog', - imports: [MatDialogModule, MatDividerModule, MatIconModule], - templateUrl: './news-item-dialog.component.html', - styleUrl: './news-item-dialog.component.scss' -}) -export class NewsItemDialogComponent { - readonly dialogRef = inject(MatDialogRef); - readonly newsItem = inject<{ newsItem: News }>(MAT_DIALOG_DATA).newsItem; - readonly NewsService = inject(NewsService); -} diff --git a/src/app/news/news.component.html b/src/app/news/news.component.html index f1fac0eb13f..9c4c8c439f1 100644 --- a/src/app/news/news.component.html +++ b/src/app/news/news.component.html @@ -10,7 +10,7 @@

@for (newsItem of allNewsItems; track newsItem; let i = $index) { @if (i < 10 || showAll) { - + {{ newsItem.date | date: 'mediumDate' }} @@ -18,10 +18,14 @@

{{ newsItem.title }}

-
+
+ @if (newsOverLengthLimit(newsItem) && !newsShowMore[i]) { +

{{ abbreviateNews(newsItem) }}

+ Show More + } @else { +

{{ newsItem.news }}

+ } +
diff --git a/src/app/news/news.component.ts b/src/app/news/news.component.ts index 8e1f8733627..04417b0c938 100644 --- a/src/app/news/news.component.ts +++ b/src/app/news/news.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { NewsService } from '../services/news.service'; import { News } from '../domain/news'; -import { DomSanitizer } from '@angular/platform-browser'; +// import { DomSanitizer } from '@angular/platform-browser'; import { MatIcon } from '@angular/material/icon'; import { TimelineComponent } from '../modules/timeline/timeline/timeline.component'; import { @@ -12,6 +12,8 @@ import { import { MatCard, MatCardContent } from '@angular/material/card'; import { MatButton } from '@angular/material/button'; import { DatePipe } from '@angular/common'; +import { UserService } from '../services/user.service'; +import { ActivatedRoute } from '@angular/router'; @Component({ imports: [ @@ -30,16 +32,62 @@ import { DatePipe } from '@angular/common'; }) export class NewsComponent implements OnInit { allNewsItems: any = []; + newsShowMore: boolean[] = []; showAll: boolean = false; + showTeacherNews: boolean = false; constructor( + private route: ActivatedRoute, private newsService: NewsService, - protected sanitizer: DomSanitizer + private userService: UserService + // protected sanitizer: DomSanitizer ) {} ngOnInit() { + this.showTeacherNewsIfLoggedIn(); + this.retrieveNews(); + } + + private showTeacherNewsIfLoggedIn(): void { + this.userService.getUser().subscribe((user) => { + this.showTeacherNews = user && user.roles?.length > 0; + }); + } + + private retrieveNews(): void { this.newsService.getAllNews().subscribe((allNewsItems: News[]) => { - this.allNewsItems = allNewsItems; + this.prepareNewsItems(allNewsItems); + this.newsShowMore = new Array(this.allNewsItems.length).fill(false); + this.scrollToFragmentNewsItem(); + }); + } + + private prepareNewsItems(allNewsItems: News[]) { + this.allNewsItems = allNewsItems + .filter((newsItem) => this.showTeacherNews || newsItem.type === 'public') + .reverse(); + } + + private scrollToFragmentNewsItem() { + setTimeout(() => { + const fragment = this.route.snapshot.fragment; + if (fragment) { + document.getElementById(fragment)?.scrollIntoView(); + } }); } + + protected newsOverLengthLimit(news: News): boolean { + return news.news.split(' ').length > 75; + } + + protected abbreviateNews(news: News): string { + const words = news.news.split(' '); + return words.slice(0, 75).join(' ') + '...'; + } + + protected expandNews(event: Event, index: number): void { + event.preventDefault(); + this.newsShowMore[index] = true; + } } From a39d18160c402be1c0ca48321bc3e2d8abd59a3f Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 24 Jun 2026 19:39:02 -0700 Subject: [PATCH 14/19] Removed method no longer used --- src/app/news/news.component.ts | 2 -- src/app/services/news.service.spec.ts | 13 ------------- src/app/services/news.service.ts | 10 ---------- 3 files changed, 25 deletions(-) diff --git a/src/app/news/news.component.ts b/src/app/news/news.component.ts index 04417b0c938..8ed031e9b34 100644 --- a/src/app/news/news.component.ts +++ b/src/app/news/news.component.ts @@ -1,7 +1,6 @@ import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { NewsService } from '../services/news.service'; import { News } from '../domain/news'; -// import { DomSanitizer } from '@angular/platform-browser'; import { MatIcon } from '@angular/material/icon'; import { TimelineComponent } from '../modules/timeline/timeline/timeline.component'; import { @@ -40,7 +39,6 @@ export class NewsComponent implements OnInit { private route: ActivatedRoute, private newsService: NewsService, private userService: UserService - // protected sanitizer: DomSanitizer ) {} ngOnInit() { diff --git a/src/app/services/news.service.spec.ts b/src/app/services/news.service.spec.ts index c518e207f56..88439495f33 100644 --- a/src/app/services/news.service.spec.ts +++ b/src/app/services/news.service.spec.ts @@ -18,17 +18,4 @@ describe('NewsService', () => { it('should be created', () => { expect(service).toBeTruthy(); }); - - it('should properly format News date', () => { - const news = new News({ - id: 1, - date: '2026-10-01 19:14:23.0', - type: 'public', - title: 'Test News', - news: 'news content', - owner: undefined - }); - const date = service.formatNewsDate(news); - expect(date).toBe('10/1/26'); - }); }); diff --git a/src/app/services/news.service.ts b/src/app/services/news.service.ts index c7f84a1453d..5c984fe7b65 100644 --- a/src/app/services/news.service.ts +++ b/src/app/services/news.service.ts @@ -15,14 +15,4 @@ export class NewsService { const headers = new HttpHeaders({ 'Cache-Control': 'no-cache' }); return this.http.get(this.newsUrl, { headers: headers }) as Observable; } - - formatNewsDate(news: News): string { - const removeLeadingZero = (str: string) => str.replace(/^0+/, ''); - - const month = removeLeadingZero(news.date.slice(5, 7)); - const day = removeLeadingZero(news.date.slice(8, 10)); - const year = news.date.slice(2, 4); - - return `${month}/${day}/${year}`; - } } From e8d71bfb9b4154fa37348d6c268740746f6517b8 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 24 Jun 2026 19:41:55 -0700 Subject: [PATCH 15/19] Revert changes in NewsService test file --- src/app/services/news.service.spec.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/services/news.service.spec.ts b/src/app/services/news.service.spec.ts index 88439495f33..cb221a8056b 100644 --- a/src/app/services/news.service.spec.ts +++ b/src/app/services/news.service.spec.ts @@ -3,19 +3,17 @@ import { TestBed } from '@angular/core/testing'; import { NewsService } from './news.service'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { News } from '../domain/news'; -let service: NewsService; describe('NewsService', () => { - beforeEach(() => { + beforeEach(() => TestBed.configureTestingModule({ imports: [], providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] - }); - service = TestBed.inject(NewsService); - }); + }) + ); it('should be created', () => { + const service: NewsService = TestBed.inject(NewsService); expect(service).toBeTruthy(); }); }); From 5aad158dbe3d96b2d54cfedec8b6d554dc0e4463 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Thu, 25 Jun 2026 02:20:44 -0700 Subject: [PATCH 16/19] Fixed tests --- .../admin-latest-news.component.spec.ts | 4 ++- src/app/home/home.component.spec.ts | 10 +++++-- src/app/news/news.component.spec.ts | 29 ++++++++++++++----- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts b/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts index 29d46195bd3..49d99ef606f 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.spec.ts @@ -4,6 +4,8 @@ import { AdminLatestNewsComponent } from './admin-latest-news.component'; import { NewsService } from '../../services/news.service'; import { News } from '../../domain/news'; import { of } from 'rxjs'; +import { MockProvider } from 'ng-mocks'; +import { ActivatedRoute } from '@angular/router'; describe('AdminLatestNewsComponent', () => { let component: AdminLatestNewsComponent; @@ -40,7 +42,7 @@ describe('AdminLatestNewsComponent', () => { await TestBed.configureTestingModule({ imports: [AdminLatestNewsComponent], - providers: [{ provide: NewsService, useValue: newsServiceSpy }] + providers: [{ provide: NewsService, useValue: newsServiceSpy }, MockProvider(ActivatedRoute)] }).compileComponents(); fixture = TestBed.createComponent(AdminLatestNewsComponent); diff --git a/src/app/home/home.component.spec.ts b/src/app/home/home.component.spec.ts index 71a3b81af4e..707a544f6fb 100644 --- a/src/app/home/home.component.spec.ts +++ b/src/app/home/home.component.spec.ts @@ -2,9 +2,10 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { HomeComponent } from './home.component'; import { ConfigService } from '../services/config.service'; import { provideHttpClient } from '@angular/common/http'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockProvider } from 'ng-mocks'; import { CallToActionComponent } from '../modules/shared/call-to-action/call-to-action.component'; import { provideAnimations } from '@angular/platform-browser/animations'; +import { ActivatedRoute } from '@angular/router'; describe('HomeComponent', () => { let component: HomeComponent; @@ -13,7 +14,12 @@ describe('HomeComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [HomeComponent, MockComponent(CallToActionComponent)], - providers: [ConfigService, provideAnimations(), provideHttpClient()] + providers: [ + ConfigService, + provideAnimations(), + provideHttpClient(), + MockProvider(ActivatedRoute) + ] }).compileComponents(); })); diff --git a/src/app/news/news.component.spec.ts b/src/app/news/news.component.spec.ts index 47a4dd55762..49969701669 100644 --- a/src/app/news/news.component.spec.ts +++ b/src/app/news/news.component.spec.ts @@ -2,8 +2,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NewsComponent } from './news.component'; import { NewsService } from '../services/news.service'; import { News } from '../domain/news'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { User } from '../domain/user'; +import { ActivatedRoute } from '@angular/router'; +import { MockProvider, MockProviders } from 'ng-mocks'; +import { UserService } from '../services/user.service'; const createNewsItem = (id, date, type, title, news, owner) => { return new News({ @@ -73,9 +76,19 @@ describe('NewsComponent', () => { }; beforeEach(() => { + const userServiceSpy = jasmine.createSpyObj(['getUser']); + const user = new User({ roles: ['teacher'] }); + userServiceSpy.getUser.and.callFake(() => { + return new BehaviorSubject(user); + }); + TestBed.configureTestingModule({ imports: [NewsComponent], - providers: [{ provide: NewsService, useClass: MockNewsService }] + providers: [ + { provide: NewsService, useClass: MockNewsService }, + { provide: UserService, useValue: userServiceSpy }, + MockProvider(ActivatedRoute) + ] }); fixture = TestBed.createComponent(NewsComponent); component = fixture.componentInstance; @@ -92,28 +105,28 @@ describe('NewsComponent', () => { }); it('should display the news date', () => { - const newsItem1 = getNewsItem(0); + const newsItem1 = getNewsItem(1); const date1 = getNewsDate(newsItem1); expect(date1).toContain('Oct 16, 2018'); - const newsItem2 = getNewsItem(1); + const newsItem2 = getNewsItem(0); const date2 = getNewsDate(newsItem2); expect(date2).toContain('Sep 21, 2018'); }); it('should display the news title', () => { - const newsItem1 = getNewsItem(0); + const newsItem1 = getNewsItem(1); const title1 = getNewsTitle(newsItem1); expect(title1).toContain(news1Title); - const newsItem2 = getNewsItem(1); + const newsItem2 = getNewsItem(0); const title2 = getNewsTitle(newsItem2); expect(title2).toContain(news2Title); }); it('should display the news text', () => { - const newsItem1 = getNewsItem(0); + const newsItem1 = getNewsItem(1); const text1 = getNewsText(newsItem1); expect(text1).toContain(news1Text); - const newsItem2 = getNewsItem(1); + const newsItem2 = getNewsItem(0); const text2 = getNewsText(newsItem2); expect(text2).toContain(news2Text); }); From e05456771d09d0899f16a9bc7953de34886136d9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 25 Jun 2026 09:25:48 +0000 Subject: [PATCH 17/19] Updated messages --- src/messages.xlf | 110 ++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/src/messages.xlf b/src/messages.xlf index decfcd7aa38..7279ca34629 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -5391,24 +5391,6 @@ 357,359 - - What's New? - - src/app/home/discourse-latest-news/discourse-latest-news.component.html - 7,10 - - - src/app/news/news.component.html - 5,9 - - - - More news - - src/app/home/discourse-latest-news/discourse-latest-news.component.html - 24,27 - - Add Ideas @@ -5420,56 +5402,56 @@ Integrated science learning and teaching with technology src/app/home/home.component.html - 37,40 + 37,41 The WISE Advantage src/app/home/home.component.html - 59,62 + 61,64 Curriculum Offerings src/app/home/home.component.html - 94,97 + 96,99 Curriculum Offerings src/app/home/home.component.html - 99,103 + 101,105 Ready to try WISE in the Classroom? src/app/home/home.component.html - 105,106 + 107,108 Sign up for free! src/app/home/home.component.html - 106,112 + 108,114 Connect src/app/home/home.component.html - 119,122 + 121,124 WISE on Facebook src/app/home/home.component.html - 125,128 + 127,130 src/app/modules/footer/footer.component.html @@ -5480,7 +5462,7 @@ WISE on Twitter src/app/home/home.component.html - 134,137 + 136,139 src/app/modules/footer/footer.component.html @@ -5491,7 +5473,7 @@ WISE on Github src/app/home/home.component.html - 143,146 + 145,148 src/app/modules/footer/footer.component.html @@ -5502,7 +5484,7 @@ Community src/app/home/home.component.html - 153,155 + 155,157 src/app/modules/footer/footer.component.html @@ -5521,14 +5503,14 @@ Join us! Discuss all things WISE. src/app/home/home.component.html - 155,157 + 157,159 Help + FAQs src/app/home/home.component.html - 164,166 + 166,168 src/app/modules/footer/footer.component.html @@ -5539,14 +5521,14 @@ View tutorials and common questions. src/app/home/home.component.html - 166,169 + 168,171 Contact Us src/app/home/home.component.html - 173,175 + 175,177 src/app/modules/footer/footer.component.html @@ -5557,56 +5539,89 @@ Want to get in touch? Send us a message. src/app/home/home.component.html - 175,179 + 177,181 WISE students in classroom src/app/home/home.component.ts - 41 + 43 WISE students building src/app/home/home.component.ts - 67 + 69 Free, standards-aligned, and research-based inquiry curricula that address NGSS 3D proficiency src/app/home/home.component.ts - 81 + 83 WISE unit on laptop src/app/home/home.component.ts - 86 + 88 Interactive scientific models plus hands-on activities, personalized guidance, and rich embedded assessments src/app/home/home.component.ts - 96 + 98 WISE students and teacher src/app/home/home.component.ts - 100 + 102 Robust teacher grading and management tools supporting individualized and customized learning src/app/home/home.component.ts - 112 + 114 + + + + What's New? + + src/app/home/latest-news/latest-news.component.html + 8,11 + + + src/app/news/news.component.html + 5,9 + + + + + + src/app/home/latest-news/latest-news.component.html + 20,21 + + + src/assets/wise5/components/outsideURL/outside-url-authoring/outside-url-authoring.component.html + 50,51 + + + + More news + + src/app/home/latest-news/latest-news.component.html + 30,33 + + + src/app/home/latest-news/latest-news.component.html + 32,37 @@ -7049,7 +7064,11 @@ Show More src/app/news/news.component.html - 34,40 + 24,26 + + + src/app/news/news.component.html + 38,44 src/app/student/student-run-list/student-run-list.component.html @@ -22059,13 +22078,6 @@ If this problem continues, let your teacher know and move on to the next activit 36,40 - - - - src/assets/wise5/components/outsideURL/outside-url-authoring/outside-url-authoring.component.html - 50,51 - - Remove Filters From 687a5e9cde56a6b6a54b96ee81e508c4e86ecf57 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Thu, 25 Jun 2026 22:06:18 -0700 Subject: [PATCH 18/19] Clean code and return DomSanitizer --- .../discourse-feed.component.ts | 6 ++-- .../admin-latest-news.component.html | 2 +- .../admin-latest-news.component.scss | 0 .../admin-latest-news.component.ts | 9 +++--- .../discourse-latest-news.component.html | 2 +- .../discourse-latest-news.component.scss | 0 .../discourse-latest-news.component.ts | 1 - src/app/home/home.component.ts | 32 +++++++++---------- .../latest-news/latest-news.component.html | 2 +- .../home/latest-news/latest-news.component.ts | 16 +++++----- src/app/news/news.component.html | 8 +++-- src/app/news/news.component.ts | 28 ++++++++-------- 12 files changed, 55 insertions(+), 51 deletions(-) delete mode 100644 src/app/home/admin-latest-news/admin-latest-news.component.scss delete mode 100644 src/app/home/discourse-latest-news/discourse-latest-news.component.scss diff --git a/src/app/discourse-feed/discourse-feed.component.ts b/src/app/discourse-feed/discourse-feed.component.ts index c43f051a3a3..07bf53dea9e 100644 --- a/src/app/discourse-feed/discourse-feed.component.ts +++ b/src/app/discourse-feed/discourse-feed.component.ts @@ -1,11 +1,11 @@ -import { HttpClient } from '@angular/common/http'; import { Directive, Input } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; @Directive() export abstract class DiscourseFeedComponent { @Input() baseUrl: string; @Input() category: string; - protected isLoaded: boolean; + protected loaded: boolean; @Input() queryString: string; protected topics: any; @@ -18,7 +18,7 @@ export abstract class DiscourseFeedComponent { return !topic.pinned_globally; }) .slice(0, 3); - this.isLoaded = true; + this.loaded = true; }); } diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.html b/src/app/home/admin-latest-news/admin-latest-news.component.html index 39f8f79f8ec..1ef84f45543 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.html +++ b/src/app/home/admin-latest-news/admin-latest-news.component.html @@ -1 +1 @@ - + diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.scss b/src/app/home/admin-latest-news/admin-latest-news.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/app/home/admin-latest-news/admin-latest-news.component.ts b/src/app/home/admin-latest-news/admin-latest-news.component.ts index ee4f6e8341f..6a4b98776df 100644 --- a/src/app/home/admin-latest-news/admin-latest-news.component.ts +++ b/src/app/home/admin-latest-news/admin-latest-news.component.ts @@ -4,14 +4,13 @@ import { News } from '../../domain/news'; import { NewsService } from '../../services/news.service'; @Component({ - selector: 'admin-latest-news', imports: [LatestNewsComponent], - templateUrl: './admin-latest-news.component.html', - styleUrl: './admin-latest-news.component.scss' + selector: 'admin-latest-news', + templateUrl: './admin-latest-news.component.html' }) export class AdminLatestNewsComponent { + protected loaded: boolean = false; protected topics: News[] = []; - protected isLoaded: boolean = false; constructor(private newsService: NewsService) {} @@ -20,7 +19,7 @@ export class AdminLatestNewsComponent { this.topics = news .filter((news) => news.type === 'public') .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - this.isLoaded = true; + this.loaded = true; }); } } diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.html b/src/app/home/discourse-latest-news/discourse-latest-news.component.html index 4beb7eb73c6..54fe2c1eefc 100644 --- a/src/app/home/discourse-latest-news/discourse-latest-news.component.html +++ b/src/app/home/discourse-latest-news/discourse-latest-news.component.html @@ -3,5 +3,5 @@ [topics]="topics" [baseUrl]="baseUrl" [category]="category" - [isLoaded]="isLoaded" + [loaded]="loaded" /> diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.scss b/src/app/home/discourse-latest-news/discourse-latest-news.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/app/home/discourse-latest-news/discourse-latest-news.component.ts b/src/app/home/discourse-latest-news/discourse-latest-news.component.ts index ae089214d2f..6e4a784552e 100644 --- a/src/app/home/discourse-latest-news/discourse-latest-news.component.ts +++ b/src/app/home/discourse-latest-news/discourse-latest-news.component.ts @@ -5,7 +5,6 @@ import { LatestNewsComponent } from '../latest-news/latest-news.component'; @Component({ imports: [LatestNewsComponent], selector: 'discourse-latest-news', - styleUrl: 'discourse-latest-news.component.scss', templateUrl: 'discourse-latest-news.component.html' }) export class DiscourseLatestNewsComponent extends DiscourseFeedComponent {} diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 605ee733898..73da1e21fb8 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -1,33 +1,33 @@ -import { Component, OnInit, SecurityContext } from '@angular/core'; +import { AdminLatestNewsComponent } from './admin-latest-news/admin-latest-news.component'; +import { BlurbComponent } from '../modules/shared/blurb/blurb.component'; import { bounceIn, flipInX, flipInY, jackInTheBox, rotateIn, zoomIn } from '../animations'; -import { DomSanitizer } from '@angular/platform-browser'; -import { ConfigService } from '../services/config.service'; -import { Config } from '../domain/config'; import { BreakpointObserver } from '@angular/cdk/layout'; -import { HeroSectionComponent } from '../modules/shared/hero-section/hero-section.component'; -import { MatIcon } from '@angular/material/icon'; +import { CallToActionComponent } from '../modules/shared/call-to-action/call-to-action.component'; +import { Component, OnInit, SecurityContext } from '@angular/core'; +import { Config } from '../domain/config'; +import { ConfigService } from '../services/config.service'; import { DiscourseLatestNewsComponent } from './discourse-latest-news/discourse-latest-news.component'; -import { NgClass } from '@angular/common'; -import { BlurbComponent } from '../modules/shared/blurb/blurb.component'; +import { DomSanitizer } from '@angular/platform-browser'; +import { HeroSectionComponent } from '../modules/shared/hero-section/hero-section.component'; import { HomePageProjectLibraryComponent } from '../modules/library/home-page-project-library/home-page-project-library.component'; import { MatButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { NgClass } from '@angular/common'; import { RouterLink } from '@angular/router'; -import { CallToActionComponent } from '../modules/shared/call-to-action/call-to-action.component'; -import { AdminLatestNewsComponent } from './admin-latest-news/admin-latest-news.component'; @Component({ animations: [bounceIn, flipInX, flipInY, jackInTheBox, rotateIn, zoomIn], imports: [ - HeroSectionComponent, - MatIcon, AdminLatestNewsComponent, - DiscourseLatestNewsComponent, - NgClass, BlurbComponent, + CallToActionComponent, + DiscourseLatestNewsComponent, + HeroSectionComponent, HomePageProjectLibraryComponent, MatButton, - RouterLink, - CallToActionComponent + MatIcon, + NgClass, + RouterLink ], selector: 'app-home', styleUrl: './home.component.scss', diff --git a/src/app/home/latest-news/latest-news.component.html b/src/app/home/latest-news/latest-news.component.html index c923b1edc06..bf135cfde06 100644 --- a/src/app/home/latest-news/latest-news.component.html +++ b/src/app/home/latest-news/latest-news.component.html @@ -1,5 +1,5 @@ @if (topics) { -
+

{{ newsItem.title }}

@if (newsOverLengthLimit(newsItem) && !newsShowMore[i]) { -

{{ abbreviateNews(newsItem) }}

+

Show More } @else { -

{{ newsItem.news }}

+

}
diff --git a/src/app/news/news.component.ts b/src/app/news/news.component.ts index 8ed031e9b34..270d6086877 100644 --- a/src/app/news/news.component.ts +++ b/src/app/news/news.component.ts @@ -1,30 +1,31 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { NewsService } from '../services/news.service'; -import { News } from '../domain/news'; +import { ActivatedRoute } from '@angular/router'; +import { Component, OnInit } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { DomSanitizer } from '@angular/platform-browser'; +import { MatButton } from '@angular/material/button'; +import { MatCard, MatCardContent } from '@angular/material/card'; import { MatIcon } from '@angular/material/icon'; +import { News } from '../domain/news'; +import { NewsService } from '../services/news.service'; import { TimelineComponent } from '../modules/timeline/timeline/timeline.component'; import { TimelineItemComponent, TimelineItemLabel, TimelineItemContent } from '../modules/timeline/timeline-item/timeline-item.component'; -import { MatCard, MatCardContent } from '@angular/material/card'; -import { MatButton } from '@angular/material/button'; -import { DatePipe } from '@angular/common'; import { UserService } from '../services/user.service'; -import { ActivatedRoute } from '@angular/router'; @Component({ imports: [ + DatePipe, + MatButton, + MatCard, + MatCardContent, MatIcon, TimelineComponent, TimelineItemComponent, - TimelineItemLabel, TimelineItemContent, - MatCard, - MatCardContent, - MatButton, - DatePipe + TimelineItemLabel ], selector: 'app-news', templateUrl: './news.component.html' @@ -36,8 +37,9 @@ export class NewsComponent implements OnInit { showTeacherNews: boolean = false; constructor( - private route: ActivatedRoute, private newsService: NewsService, + private route: ActivatedRoute, + protected sanitizer: DomSanitizer, private userService: UserService ) {} From cbea455ffbcbf1eae826113078257f7ad57e85be Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Jun 2026 05:15:10 +0000 Subject: [PATCH 19/19] Updated messages --- src/messages.xlf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/messages.xlf b/src/messages.xlf index 7279ca34629..2a0b384bb89 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -7064,11 +7064,11 @@ Show More src/app/news/news.component.html - 24,26 + 28,30 src/app/news/news.component.html - 38,44 + 42,48 src/app/student/student-run-list/student-run-list.component.html