Skip to content

Commit 6893ab8

Browse files
authored
Update Saved Component UI (#62)
* Break out article-list component * Correctly sync bookmark status
1 parent 9352ca7 commit 6893ab8

11 files changed

Lines changed: 218 additions & 168 deletions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<!-- Article List Component Template -->
2+
<ion-list>
3+
@for (entry of entries; track entry) {
4+
<div>
5+
@if (filter === '' || entry.feedUrl === filter) {
6+
<div>
7+
@if (!settingsService.getSettings().compressedFeed) {
8+
<div>
9+
<ion-card [button]="true" (click)="openPreview(entry.title, entry.content, entry.link)">
10+
@if (settingsService.getSettings().showImages) {
11+
<div class="card-image">
12+
@if (entry.imgLink !== null) {
13+
<img alt="Entry Cover Image" src="{{ entry.imgLink }}" />
14+
}
15+
</div>
16+
}
17+
<ion-card-header>
18+
<ion-card-subtitle>
19+
{{entry.source}} | {{formatDateRelative(entry.isoDate, settingsService.getSettings().locale)}}
20+
</ion-card-subtitle>
21+
<ion-card-title>
22+
<ion-item class="ion-no-padding" style="--ion-item-background: transparent;">
23+
<ion-label>{{entry.title}}</ion-label>
24+
@if (entry.bookmark !== true) {
25+
<ion-button slot="end" fill="clear" (click)="this.addBookmark($event, entry)">
26+
<ion-icon color="medium" name="bookmark-outline"></ion-icon>
27+
</ion-button>
28+
}
29+
@if (entry.bookmark !== false) {
30+
<ion-button slot="end" fill="clear" (click)="this.removeBookmark($event, entry)">
31+
<ion-icon color="medium" name="bookmark"></ion-icon>
32+
</ion-button>
33+
}
34+
</ion-item>
35+
</ion-card-title>
36+
</ion-card-header>
37+
<ion-card-content>
38+
{{entry.contentStripped}}
39+
</ion-card-content>
40+
</ion-card>
41+
</div>
42+
}
43+
@if (settingsService.getSettings().compressedFeed) {
44+
<div>
45+
<ion-item [button]="true" (click)="openPreview(entry.title, entry.content, entry.link)">
46+
@if (settingsService.getSettings().showImages) {
47+
<div>
48+
@if (entry.imgLink !== null) {
49+
<ion-thumbnail slot="start">
50+
<img alt="Entry Cover Image" src="{{ entry.imgLink }}" />
51+
</ion-thumbnail>
52+
}
53+
</div>
54+
}
55+
<ion-label>
56+
<strong>{{entry.title}}</strong><br />
57+
<ion-text>{{entry.source}}</ion-text><br />
58+
<ion-note color="medium" class="ion-text-wrap">{{entry.contentStripped}}</ion-note>
59+
</ion-label>
60+
<div class="metadata-end-wrapper" slot="end">
61+
<ion-note color="medium">{{formatDateAsDay(entry.isoDate, settingsService.getSettings().locale)}}</ion-note>
62+
</div>
63+
</ion-item>
64+
</div>
65+
}
66+
</div>
67+
}
68+
</div>
69+
}
70+
</ion-list>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* Add any custom styles for ArticleListComponent here */
2+
.card-image {
3+
width: 100%;
4+
display: flex;
5+
justify-content: center;
6+
align-items: center;
7+
margin-bottom: 8px;
8+
}
9+
.metadata-end-wrapper {
10+
display: flex;
11+
flex-direction: column;
12+
align-items: flex-end;
13+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Component, Input } from '@angular/core';
2+
import {
3+
IonButton, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle,
4+
IonCardTitle, IonIcon, IonItem, IonItemOption, IonItemOptions, IonLabel,
5+
IonList, IonNote, IonText, IonThumbnail, ModalController
6+
} from '@ionic/angular/standalone';
7+
import { addIcons } from 'ionicons';
8+
import { bookmark, bookmarkOutline } from 'ionicons/icons';
9+
import { PreviewComponent } from 'src/app/preview/preview.component';
10+
import { BookmarkService } from 'src/app/services/bookmark.service';
11+
import { PlatformService } from 'src/app/services/platform.service';
12+
import { SettingsService } from 'src/app/services/settings.service';
13+
import { formatDateAsDay, formatDateRelative } from '../lib/date-utils';
14+
import { FeedService } from '../services/feed.service';
15+
16+
@Component({
17+
selector: 'app-article-list',
18+
templateUrl: './article-list.component.html',
19+
styleUrls: ['./article-list.component.scss'],
20+
standalone: true,
21+
imports: [IonCard, IonCardHeader, IonCardContent, IonCardSubtitle, IonCardTitle,
22+
IonNote, IonItem, IonIcon, IonButton, IonItemOption, IonItemOptions,
23+
IonText, IonLabel, IonThumbnail, IonList
24+
]
25+
})
26+
27+
export class ArticleListComponent {
28+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
29+
@Input() entries: any[] = [];
30+
@Input() filter: string = '';
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
32+
@Input() presentingElementId: any;
33+
34+
public formatDateRelative = formatDateRelative;
35+
public formatDateAsDay = formatDateAsDay;
36+
37+
constructor(public bookmarkService: BookmarkService,
38+
public feedService: FeedService,
39+
public modalController: ModalController,
40+
public platformService: PlatformService,
41+
public settingsService: SettingsService) {
42+
addIcons({ bookmark, bookmarkOutline});
43+
}
44+
45+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
46+
public addBookmark(event: Event, entry: any) {
47+
this.feedService.updateBookmarkStatus(entry, true);
48+
this.bookmarkService.addEntry(entry);
49+
this.feedService.saveEntries();
50+
event.stopPropagation();
51+
}
52+
53+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
54+
public removeBookmark(event: Event, entry: any) {
55+
this.bookmarkService.removeEntry(entry);
56+
this.feedService.updateBookmarkStatus(entry, false);
57+
event.stopPropagation();
58+
}
59+
60+
async openPreview(title: string, content: string, url: string) {
61+
if (this.settingsService.getSettings().preview) {
62+
const preview = await this.modalController.create({
63+
component: PreviewComponent,
64+
componentProps: {
65+
articleTitle: title,
66+
articleContent: content,
67+
articleLink: url
68+
},
69+
presentingElement: this.presentingElementId,
70+
71+
});
72+
73+
preview.present();
74+
}
75+
else {
76+
this.platformService.openUrlInPlatformBrowser(url);
77+
}
78+
}
79+
}

src/app/feed/feed.page.html

Lines changed: 5 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -71,77 +71,11 @@
7171
</ion-refresher>
7272

7373
<!-- Feed list -->
74-
<ion-list>
75-
@for (entry of feedService.entries; track entry) {
76-
<div>
77-
@if (filter === '' || entry.feedUrl === filter) {
78-
<div>
79-
@if (!settingsService.getSettings().compressedFeed) {
80-
<div>
81-
<ion-card [button]="true" (click)="openPreview(entry.title, entry.content, entry.link)">
82-
@if (settingsService.getSettings().showImages) {
83-
<div class="card-image">
84-
@if (entry.imgLink !== null) {
85-
<img alt="Entry Cover Image" src="{{ entry.imgLink }}" />
86-
}
87-
</div>
88-
}
89-
<ion-card-header>
90-
<ion-card-subtitle>
91-
{{entry.source}} | {{formatDateRelative(entry.isoDate, settingsService.getSettings().locale)}}
92-
</ion-card-subtitle>
93-
<ion-card-title>
94-
<!---ToDo: We override the background as the colour is slightly different on iOS-->
95-
<ion-item class="ion-no-padding" style="--ion-item-background: transparent;">
96-
<ion-label>{{entry.title}}</ion-label>
97-
@if (entry.bookmark !== true) {
98-
<ion-button slot="end" fill="clear" (click)="addBookmark($event, entry)">
99-
<ion-icon color="medium" name="bookmark-outline"></ion-icon>
100-
</ion-button>
101-
}
102-
@if (entry.bookmark !== false) {
103-
<ion-button slot="end" fill="clear" (click)="removeBookmark($event, entry)">
104-
<ion-icon color="medium" name="bookmark"></ion-icon>
105-
</ion-button>
106-
}
107-
</ion-item>
108-
</ion-card-title>
109-
</ion-card-header>
110-
<ion-card-content>
111-
{{entry.contentStripped}}
112-
</ion-card-content>
113-
</ion-card>
114-
</div>
115-
}
116-
@if (settingsService.getSettings().compressedFeed) {
117-
<div>
118-
<ion-item [button]="true" (click)="openPreview(entry.title, entry.content, entry.link)">
119-
@if (settingsService.getSettings().showImages) {
120-
<div>
121-
@if (entry.imgLink !== null) {
122-
<ion-thumbnail slot="start">
123-
<img alt="Entry Cover Image" src="{{ entry.imgLink }}" />
124-
</ion-thumbnail>
125-
}
126-
</div>
127-
}
128-
<ion-label>
129-
<strong>{{entry.title}}</strong><br />
130-
<ion-text>{{entry.source}}</ion-text><br />
131-
<ion-note color="medium" class="ion-text-wrap">{{entry.contentStripped}}</ion-note>
132-
</ion-label>
133-
<div class="metadata-end-wrapper" slot="end">
134-
<ion-note color="medium">{{formatDateAsDay(entry.isoDate, settingsService.getSettings().locale)}}</ion-note>
135-
<!-- <ion-icon color="medium" name="chevron-forward"></ion-icon> -->
136-
</div>
137-
</ion-item>
138-
</div>
139-
}
140-
</div>
141-
}
142-
</div>
143-
}
144-
</ion-list>
74+
<app-article-list
75+
[entries]="feedService.entries"
76+
[filter]="filter"
77+
[presentingElementId]="this.elementRef.nativeElement"
78+
></app-article-list>
14579

14680
<!-- Return to top button -->
14781
<ion-fab slot="fixed" vertical="bottom" horizontal="end">

src/app/feed/feed.page.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,3 @@
44
--padding-end: 20%;
55
}
66
}
7-
8-
.card-image {
9-
display: flex;
10-
justify-content: center;
11-
}

src/app/feed/feed.page.ts

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,43 @@ import { Component, ElementRef, ViewChild } from '@angular/core';
22
import { FormsModule } from '@angular/forms';
33
import {
44
IonButton,
5-
IonButtons,
6-
IonCard,
7-
IonCardContent,
8-
IonCardHeader,
9-
IonCardSubtitle, IonCardTitle,
10-
IonContent,
5+
IonButtons, IonContent,
116
IonFab, IonFabButton,
12-
IonHeader, IonIcon, IonInput, IonItem,
13-
IonItemOption, IonItemOptions,
14-
IonLabel, IonList,
7+
IonHeader, IonIcon, IonInput, IonItem, IonLabel, IonList,
158
IonMenu,
169
IonMenuToggle,
17-
IonNote, IonRefresher,
10+
IonRefresher,
1811
IonRefresherContent, IonText,
1912
IonThumbnail,
2013
IonTitle, IonToolbar,
2114
ModalController,
2215
ScrollDetail
2316
} from '@ionic/angular/standalone';
2417
import { addIcons } from 'ionicons';
25-
import { bookmark, bookmarkOutline, chevronForward, chevronUpOutline, ellipsisVertical, filterOutline, shareSocialOutline } from 'ionicons/icons';
26-
import { PreviewComponent } from '../preview/preview.component';
18+
import { chevronForward, chevronUpOutline, ellipsisVertical, filterOutline, shareSocialOutline } from 'ionicons/icons';
19+
import { ArticleListComponent } from '../article-list/article-list.component';
20+
import { formatDateAsDay, formatDateAsLong, formatDateRelative } from '../lib/date-utils';
2721
import { BookmarkService } from '../services/bookmark.service';
2822
import { FeedService } from '../services/feed.service';
2923
import { PlatformService } from '../services/platform.service';
3024
import { SettingsService } from '../services/settings.service';
3125
import { SourcesService } from '../services/sources.service';
3226
import { SettingsComponent } from '../settings/settings.component';
33-
import { formatDateAsDay, formatDateAsLong, formatDateRelative } from '../lib/date-utils';
3427

3528
@Component({
3629
selector: 'app-feed',
3730
templateUrl: 'feed.page.html',
3831
styleUrls: ['feed.page.scss'],
3932
standalone: true,
40-
imports: [FormsModule, IonCard, IonCardHeader, IonCardContent,
41-
IonCardSubtitle, IonCardTitle, IonHeader, IonToolbar, IonTitle, IonNote,
42-
IonContent, IonList, IonInput, IonItem, IonItemOption, IonItemOptions,
33+
imports: [
34+
FormsModule, IonItem,
35+
IonHeader, IonToolbar, IonTitle,
36+
IonContent, IonList, IonInput,
4337
IonIcon, IonButton, IonButtons, IonText, IonMenu, IonThumbnail, IonMenuToggle,
4438
IonLabel, IonRefresher, IonRefresherContent, IonFab, IonFabButton,
45-
SettingsComponent]
39+
SettingsComponent,
40+
ArticleListComponent
41+
]
4642
})
4743

4844
export class FeedPage {
@@ -56,11 +52,11 @@ export class FeedPage {
5652
public formatDateAsLong = formatDateAsLong;
5753
public formatDateRelative = formatDateRelative;
5854

59-
constructor(public sourcesService: SourcesService, public platformService: PlatformService,
55+
constructor(public elementRef: ElementRef,
56+
public sourcesService: SourcesService, public platformService: PlatformService,
6057
public bookmarkService: BookmarkService, public feedService: FeedService,
61-
private modalController: ModalController, public elementRef: ElementRef,
62-
public settingsService: SettingsService) {
63-
addIcons({ bookmark, bookmarkOutline, shareSocialOutline, ellipsisVertical, filterOutline, chevronForward,
58+
private modalController: ModalController, public settingsService: SettingsService) {
59+
addIcons({ shareSocialOutline, ellipsisVertical, filterOutline, chevronForward,
6460
chevronUpOutline });
6561
}
6662

@@ -80,38 +76,6 @@ export class FeedPage {
8076
this.filter = '';
8177
}
8278

83-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
84-
public addBookmark(event: Event, entry: any) {
85-
this.bookmarkService.addEntry(entry);
86-
event.stopPropagation();
87-
}
88-
89-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
90-
public removeBookmark(event: Event, entry: any) {
91-
this.bookmarkService.removeEntry(entry);
92-
event.stopPropagation();
93-
}
94-
95-
async openPreview(title: string, content: string, url: string) {
96-
if (this.settingsService.getSettings().preview) {
97-
const preview = await this.modalController.create({
98-
component: PreviewComponent,
99-
componentProps: {
100-
articleTitle: title,
101-
articleContent: content,
102-
articleLink: url
103-
},
104-
presentingElement: this.elementRef.nativeElement,
105-
106-
});
107-
108-
preview.present();
109-
}
110-
else {
111-
this.platformService.openUrlInPlatformBrowser(url);
112-
}
113-
}
114-
11579
async openSettings() {
11680
const settings = await this.modalController.create({
11781
component: SettingsComponent,

0 commit comments

Comments
 (0)