Skip to content

Commit a2a454c

Browse files
authored
Merge pull request moodlehq#4840 from crazyserver/MOBILE-5003
Mobile 5003 Forum mark as read
2 parents 7deddb5 + 39afdb2 commit a2a454c

14 files changed

Lines changed: 595 additions & 243 deletions

File tree

scripts/langindex.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,12 +681,17 @@
681681
"addon.mod_forum.lastpost": "forum",
682682
"addon.mod_forum.lockdiscussion": "forum",
683683
"addon.mod_forum.lockupdated": "forum",
684+
"addon.mod_forum.markread": "forum",
685+
"addon.mod_forum.markunread": "forum",
684686
"addon.mod_forum.message": "forum",
685687
"addon.mod_forum.modeflatnewestfirst": "forum",
686688
"addon.mod_forum.modeflatoldestfirst": "forum",
687689
"addon.mod_forum.modenested": "forum",
688690
"addon.mod_forum.modulenameplural": "forum",
691+
"addon.mod_forum.noactionsavailable": "local_moodlemobileapp",
692+
"addon.mod_forum.nopermissiontoact": "local_moodlemobileapp",
689693
"addon.mod_forum.numreplies": "local_moodlemobileapp",
694+
"addon.mod_forum.offlinereconnectactions": "local_moodlemobileapp",
690695
"addon.mod_forum.pindiscussion": "forum",
691696
"addon.mod_forum.pinupdated": "forum",
692697
"addon.mod_forum.postaddedsuccess": "forum",
@@ -697,6 +702,7 @@
697702
"addon.mod_forum.privatereply": "forum",
698703
"addon.mod_forum.qandanotify": "forum",
699704
"addon.mod_forum.re": "forum",
705+
"addon.mod_forum.reconnecttomanage": "local_moodlemobileapp",
700706
"addon.mod_forum.refreshposts": "local_moodlemobileapp",
701707
"addon.mod_forum.removefromfavourites": "forum",
702708
"addon.mod_forum.reply": "forum",
Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,68 @@
11
<ion-content>
2-
<core-loading [hideUntil]="loaded" [fullscreen]="false">
2+
<core-loading [hideUntil]="loaded()" [fullscreen]="false">
33
<ion-list>
4-
@if (offlinePost || canEdit) {
5-
<ion-item button class="ion-text-wrap" (click)="editPost()" [detail]="false">
6-
<ion-icon name="fas-pen" slot="start" aria-hidden="true" />
4+
@if (canMarkAsRead()) {
5+
<ion-item button class="ion-text-wrap" (click)="toggleReadState()" [detail]="false" [disabled]="markAsReadDisabled()">
6+
<ion-icon name="fas-bookmark" slot="start" aria-hidden="true" />
77
<ion-label>
8-
<p class="item-heading">{{ 'addon.mod_forum.edit' | translate }}</p>
8+
<p class="item-heading">
9+
@if (postCalculated().unread) {
10+
{{ 'addon.mod_forum.markread' | translate }}
11+
} @else {
12+
{{ 'addon.mod_forum.markunread' | translate }}
13+
}
14+
</p>
915
</ion-label>
1016
</ion-item>
1117
}
12-
@if (offlinePost || canDelete) {
13-
<ion-item button class="ion-text-wrap" (click)="deletePost()" [detail]="false">
14-
<ion-icon name="fas-trash" slot="start" aria-hidden="true" />
18+
@if (canEdit()) {
19+
<ion-item button class="ion-text-wrap" (click)="editPost()" [detail]="false" [disabled]="editDeleteDisabled()">
20+
<ion-icon name="fas-pen" slot="start" aria-hidden="true" />
1521
<ion-label>
16-
@if (!offlinePost) {
17-
<p class="item-heading">{{ 'addon.mod_forum.delete' | translate }}</p>
18-
} @else {
19-
<p class="item-heading">{{ 'core.discard' | translate }}</p>
20-
}
22+
<p class="item-heading">{{ 'addon.mod_forum.edit' | translate }}</p>
2123
</ion-label>
2224
</ion-item>
2325
}
24-
@if (url) {
25-
<ion-item class="ion-text-wrap" [href]="url" core-link [capture]="false" button [detail]="false"
26-
[showBrowserWarning]="false">
27-
<ion-icon name="fas-up-right-from-square" slot="start" aria-hidden="true" />
26+
@if (canDelete()) {
27+
<ion-item button class="ion-text-wrap" (click)="deletePost()" [detail]="false" [disabled]="editDeleteDisabled()">
28+
<ion-icon name="fas-trash" slot="start" aria-hidden="true" />
2829
<ion-label>
29-
<p class="item-heading">{{ 'core.openinbrowser' | translate }}</p>
30+
<p class="item-heading">
31+
@if (!isOfflinePost()) {
32+
{{ 'addon.mod_forum.delete' | translate }}
33+
} @else {
34+
{{ 'core.discard' | translate }}
35+
}
36+
</p>
3037
</ion-label>
3138
</ion-item>
3239
}
40+
41+
@if (!isOnline()) {
42+
@if (unknownCapabilities()) {
43+
<ion-item lines="none" class="ion-text-wrap">
44+
<ion-icon name="fas-lock" slot="start" aria-hidden="true" />
45+
<ion-label>
46+
<p class="item-heading">{{ 'core.youreoffline' | translate }}</p>
47+
<p>{{ 'addon.mod_forum.reconnecttomanage' | translate }}</p>
48+
</ion-label>
49+
</ion-item>
50+
} @else if (!isOfflinePost()) {
51+
<ion-item lines="none" class="ion-text-wrap item-lines-top">
52+
<ion-label>
53+
<p>{{ 'addon.mod_forum.offlinereconnectactions' | translate }}</p>
54+
</ion-label>
55+
</ion-item>
56+
}
57+
} @else if (!canMarkAsRead() && !canEdit() && !canDelete()) {
58+
<ion-item lines="none" class="ion-text-wrap">
59+
<ion-icon name="fas-lock" slot="start" aria-hidden="true" />
60+
<ion-label>
61+
<p class="item-heading">{{ 'addon.mod_forum.noactionsavailable' | translate }}</p>
62+
<p>{{ 'addon.mod_forum.nopermissiontoact' | translate }}</p>
63+
</ion-label>
64+
</ion-item>
65+
}
3366
</ion-list>
3467
</core-loading>
3568
</ion-content>

src/addons/mod/forum/components/post-options-menu/post-options-menu.ts

Lines changed: 88 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { Component, Input, OnInit } from '@angular/core';
16-
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
15+
import { Component, computed, effect, input, linkedSignal, signal, untracked } from '@angular/core';
16+
import { CoreSitesReadingStrategy } from '@services/sites';
1717
import { CoreNetwork } from '@services/network';
1818
import { AddonModForum, AddonModForumPost } from '@addons/mod/forum/services/forum';
1919
import { PopoverController } from '@singletons';
20-
import { CoreNetworkError } from '@classes/errors/network-error';
2120
import { CoreSharedModule } from '@/core/shared.module';
2221
import { CoreAlerts } from '@services/overlays/alerts';
22+
import { AddonModForumHelper } from '../../services/forum-helper';
2323

2424
/**
2525
* This component is meant to display a popover with the post options.
@@ -32,69 +32,83 @@ import { CoreAlerts } from '@services/overlays/alerts';
3232
CoreSharedModule,
3333
],
3434
})
35-
export class AddonModForumPostOptionsMenuComponent implements OnInit {
35+
export class AddonModForumPostOptionsMenuComponent {
3636

37-
@Input({ required: true }) post!: AddonModForumPost; // The post.
38-
@Input({ required: true }) cmId!: number;
39-
@Input({ required: true }) forumId!: number; // The forum Id.
37+
readonly post = input.required<AddonModForumPost>(); // The post.
38+
readonly cmId = input.required<number>();
39+
readonly forumId = input.required<number>(); // The forum Id.
4040

41-
canEdit = false;
42-
canDelete = false;
43-
loaded = false;
44-
url?: string;
45-
offlinePost = false;
41+
protected readonly postCalculated = linkedSignal(() => this.post());
42+
readonly canEdit = computed(() => this.isOfflinePost() || AddonModForumHelper.canUpdatePost(this.postCalculated()));
43+
44+
readonly canDelete = computed(() => this.isOfflinePost() || AddonModForumHelper.canDeletePost(this.postCalculated()));
45+
readonly editDeleteDisabled = computed(() => this.unknownCapabilities() || (!this.isOfflinePost() && !this.isOnline()));
46+
47+
readonly canMarkAsRead = computed(() =>
48+
!this.isOfflinePost() && AddonModForumHelper.canSetReadState(this.postCalculated()));
49+
50+
readonly markAsReadDisabled = computed(() => this.unknownCapabilities() || !this.isOnline());
51+
52+
readonly loaded = signal(false);
53+
54+
readonly isOfflinePost = computed(() => this.postCalculated().id < 0);
55+
protected readonly isOnline = CoreNetwork.onlineSignal;
56+
57+
protected readonly unknownCapabilities = computed(() => {
58+
if (this.isOfflinePost()) {
59+
return false;
60+
}
61+
62+
const post = this.postCalculated();
63+
64+
return post.capabilities.delete === undefined;
65+
});
4666

4767
/**
4868
* @inheritdoc
4969
*/
50-
async ngOnInit(): Promise<void> {
51-
this.offlinePost = this.post.id < 0;
52-
if (this.offlinePost) {
53-
this.loaded = true;
54-
55-
return;
56-
}
70+
constructor() {
71+
effect(async () => {
72+
const unknownCapabilities = this.unknownCapabilities();
73+
const isOnline = this.isOnline();
5774

58-
if (this.post.capabilities.delete === undefined) {
59-
if (this.forumId) {
60-
try {
61-
this.post =
62-
await AddonModForum.getDiscussionPost(this.forumId, this.post.discussionid, this.post.id, {
63-
cmId: this.cmId,
64-
readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
65-
});
66-
} catch (error) {
67-
CoreAlerts.showError(error, { default: 'Error getting discussion post.' });
68-
}
69-
} else {
70-
this.loaded = true;
71-
// Display the open in browser button to prevent having an empty menu.
72-
this.setOpenInBrowserUrl();
75+
if (!unknownCapabilities) {
76+
this.loaded.set(true);
7377

7478
return;
7579
}
76-
}
7780

78-
this.canDelete = !!this.post.capabilities.delete && AddonModForum.isDeletePostAvailable();
79-
this.canEdit = !!this.post.capabilities.edit && AddonModForum.isUpdatePostAvailable();
80-
if (!this.canDelete && !this.canEdit) {
81-
// Display the open in browser button to prevent having an empty menu.
82-
this.setOpenInBrowserUrl();
83-
}
81+
// Only check this one because the others are always available if this one is.
82+
const wsAvailable = AddonModForum.isDeletePostAvailable();
83+
if (!wsAvailable) {
84+
// If the WS is not available, we cannot do anything.
85+
this.loaded.set(true);
8486

85-
this.loaded = true;
86-
}
87+
return;
88+
}
8789

88-
/**
89-
* Set the URL to open in browser.
90-
*/
91-
protected setOpenInBrowserUrl(): void {
92-
const site = CoreSites.getRequiredCurrentSite();
93-
if (!site.shouldDisplayInformativeLinks()) {
94-
return;
95-
}
90+
const forumId = this.forumId();
91+
if (forumId && isOnline) {
92+
this.loaded.set(false);
93+
94+
await untracked(async () => {
95+
let post = this.postCalculated();
96+
97+
try {
98+
post =
99+
await AddonModForum.getDiscussionPost(forumId, post.discussionid, post.id, {
100+
cmId: this.cmId(),
101+
readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
102+
});
103+
this.postCalculated.set(post);
104+
} catch (error) {
105+
CoreAlerts.showError(error, { default: 'Error getting discussion post.' });
106+
}
107+
});
108+
}
96109

97-
this.url = site.createSiteUrl('/mod/forum/discuss.php', { d: this.post.discussionid.toString() }, `p${this.post.id}`);
110+
this.loaded.set(true);
111+
});
98112
}
99113

100114
/**
@@ -108,30 +122,37 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit {
108122
* Delete a post.
109123
*/
110124
deletePost(): void {
111-
if (!this.offlinePost) {
112-
if (!CoreNetwork.isOnline()) {
113-
CoreAlerts.showError(new CoreNetworkError());
114-
115-
return;
116-
}
117-
118-
PopoverController.dismiss({ action: 'delete' });
125+
if (!this.isOfflinePost()) {
126+
PopoverController.dismiss({ action: AddonModForumPostOptionsMenuAction.DELETE });
119127
} else {
120-
PopoverController.dismiss({ action: 'deleteoffline' });
128+
PopoverController.dismiss({ action: AddonModForumPostOptionsMenuAction.DELETE_OFFLINE });
121129
}
122130
}
123131

124132
/**
125133
* Edit a post.
126134
*/
127135
editPost(): void {
128-
if (!this.offlinePost && !CoreNetwork.isOnline()) {
129-
CoreAlerts.showError(new CoreNetworkError());
130-
131-
return;
132-
}
136+
PopoverController.dismiss({ action: AddonModForumPostOptionsMenuAction.EDIT });
137+
}
133138

134-
PopoverController.dismiss({ action: 'edit' });
139+
/**
140+
* Toggle the read state of a post.
141+
*/
142+
toggleReadState(): void {
143+
PopoverController.dismiss({
144+
action: this.postCalculated().unread
145+
? AddonModForumPostOptionsMenuAction.MARKREAD
146+
: AddonModForumPostOptionsMenuAction.MARKUNREAD,
147+
});
135148
}
136149

137150
}
151+
152+
export enum AddonModForumPostOptionsMenuAction {
153+
DELETE = 'delete',
154+
DELETE_OFFLINE = 'deleteoffline',
155+
EDIT = 'edit',
156+
MARKREAD = 'markread',
157+
MARKUNREAD = 'markunread',
158+
};

src/addons/mod/forum/components/post/post.html

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,6 @@ <h2 class="ion-text-wrap">
1818
<core-format-text [text]="post.subject" contextLevel="module" [contextInstanceId]="forum && forum.cmid"
1919
[courseId]="courseId" [sanitize]="!post.id || post.id < 0" />
2020
</h2>
21-
@if ((trackPosts && post.unread) || optionsMenuEnabled) {
22-
<div class="addon-mod-forum-post-title-options">
23-
@if (trackPosts && post.unread) {
24-
<ion-note [attr.aria-label]="'addon.mod_forum.unread' | translate">
25-
<ion-icon name="fas-circle" color="primary" aria-hidden="true" />
26-
</ion-note>
27-
}
28-
@if (optionsMenuEnabled) {
29-
<ion-button fill="clear" [ariaLabel]="('core.displayoptions' | translate)"
30-
(click)="showOptionsMenu($event)">
31-
<ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true" />
32-
</ion-button>
33-
}
34-
</div>
35-
}
3621
</div>
3722
}
3823
<div class="addon-mod-forum-post-info">
@@ -63,22 +48,18 @@ <h2 class="ion-text-wrap">
6348
}
6449
</div>
6550

66-
@if (!displaySubject) {
67-
@if ((trackPosts && post.unread) || optionsMenuEnabled) {
68-
<div class="addon-mod-forum-post-title-options">
69-
@if (trackPosts && post.unread) {
70-
<ion-note [attr.aria-label]="'addon.mod_forum.unread' | translate">
71-
<ion-icon name="fas-circle" color="primary" aria-hidden="true" />
72-
</ion-note>
73-
}
74-
@if (optionsMenuEnabled) {
75-
<ion-button fill="clear" [ariaLabel]="('core.displayoptions' | translate)"
76-
(click)="showOptionsMenu($event)">
77-
<ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true" />
78-
</ion-button>
79-
}
80-
</div>
81-
}
51+
@if ((trackPosts && post.unread) || optionsMenuEnabled) {
52+
<div class="addon-mod-forum-post-title-options">
53+
@if (trackPosts && post.unread) {
54+
<ion-icon name="fas-circle" color="primary" [attr.aria-label]="'addon.mod_forum.unread' | translate" />
55+
}
56+
@if (optionsMenuEnabled) {
57+
<ion-button fill="clear" [ariaLabel]="('core.displayoptions' | translate)"
58+
(click)="showOptionsMenu($event)">
59+
<ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true" />
60+
</ion-button>
61+
}
62+
</div>
8263
}
8364
</div>
8465
</ion-label>

src/addons/mod/forum/components/post/post.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@
6767
display: flex;
6868
align-items: center;
6969
gap: 4px;
70-
flex-direction: column;
70+
flex-direction: row;
71+
ion-button {
72+
margin: 0px;
73+
}
7174
}
7275

7376
}

0 commit comments

Comments
 (0)