FEATURE: improve blank page syndrome on the user activity pages (#14311)

This improves blank page syndrome on the next pages:
* activity
* activity/replies
* activity/drafts
* activity/likes-given
This commit is contained in:
Andrei Prigorshnev 2021-09-16 21:35:34 +04:00 committed by GitHub
parent 119bdc12ea
commit 477bbc372e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 219 additions and 37 deletions

View File

@ -60,10 +60,6 @@ export default RestModel.extend({
return;
}
if (result.no_results_help) {
this.set("noContentHelp", result.no_results_help);
}
if (!result.drafts) {
return;
}

View File

@ -81,9 +81,6 @@ export default RestModel.extend({
if (this.filterParam) {
findUrl += `&filter=${this.filterParam}`;
}
if (this.noContentHelpKey) {
findUrl += `&no_results_help_key=${this.noContentHelpKey}`;
}
if (this.actingUsername) {
findUrl += `&acting_username=${this.actingUsername}`;
@ -102,9 +99,6 @@ export default RestModel.extend({
this.set("loading", true);
return ajax(findUrl)
.then((result) => {
if (result && result.no_results_help) {
this.set("noContentHelp", result.no_results_help);
}
if (result && result.user_actions) {
const copy = A();
result.user_actions.forEach((action) => {

View File

@ -74,6 +74,14 @@ const DiscourseRoute = Route.extend({
}
},
isAnotherUsersPage(user) {
if (!this.currentUser) {
return true;
}
return user.username !== this.currentUser.username;
},
isPoppedState(transition) {
return !transition._discourse_intercepted && !!transition.intent.url;
},

View File

@ -1,8 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
noContentHelpKey: "user_activity.no_bookmarks",
queryParams: {
acting_username: { refreshModel: true },
},

View File

@ -1,10 +1,19 @@
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
export default DiscourseRoute.extend({
model() {
const model = this.modelFor("user").get("userDraftsStream");
model.reset();
return model.findItems(this.site).then(() => model);
const user = this.modelFor("user");
const draftsStream = user.get("userDraftsStream");
draftsStream.reset();
return draftsStream.findItems(this.site).then(() => {
return {
stream: draftsStream,
isAnotherUsersPage: this.isAnotherUsersPage(user),
emptyState: this.emptyState(),
};
});
},
renderTemplate() {
@ -15,6 +24,12 @@ export default DiscourseRoute.extend({
controller.set("model", model);
},
emptyState() {
const title = I18n.t("user_activity.no_drafts_title");
const body = I18n.t("user_activity.no_drafts_body");
return { title, body };
},
activate() {
this.appEvents.on("draft:destroyed", this, this.refresh);
},

View File

@ -1,5 +1,20 @@
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import { iconHTML } from "discourse-common/lib/icon-library";
import getURL from "discourse-common/lib/get-url";
import I18n from "I18n";
export default UserActivityStreamRoute.extend({
userActionType: null,
emptyState() {
const title = I18n.t("user_activity.no_activity_title");
const body = I18n.t("user_activity.no_activity_body", {
topUrl: getURL("/top"),
categoriesUrl: getURL("/categories"),
preferencesUrl: getURL("/my/preferences"),
heartIcon: iconHTML("heart"),
}).htmlSafe();
return { title, body };
},
});

View File

@ -1,9 +1,20 @@
import UserAction from "discourse/models/user-action";
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import { iconHTML } from "discourse-common/lib/icon-library";
import I18n from "I18n";
export default UserActivityStreamRoute.extend({
userActionType: UserAction.TYPES["likes_given"],
noContentHelpKey: "user_activity.no_likes_given",
emptyStateOthers: I18n.t("user_activity.no_likes_others"),
emptyState() {
const title = I18n.t("user_activity.no_likes_title");
const body = I18n.t("user_activity.no_likes_body", {
heartIcon: iconHTML("heart"),
}).htmlSafe();
return { title, body };
},
actions: {
didTransition() {

View File

@ -1,9 +1,16 @@
import UserAction from "discourse/models/user-action";
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import I18n from "I18n";
export default UserActivityStreamRoute.extend({
userActionType: UserAction.TYPES["posts"],
noContentHelpKey: "user_activity.no_replies",
emptyStateOthers: I18n.t("user_activity.no_replies_others"),
emptyState() {
const title = I18n.t("user_activity.no_replies_title");
const body = "";
return { title, body };
},
actions: {
didTransition() {

View File

@ -1,19 +1,30 @@
import DiscourseRoute from "discourse/routes/discourse";
import ViewingActionType from "discourse/mixins/viewing-action-type";
import { action } from "@ember/object";
import I18n from "I18n";
export default DiscourseRoute.extend(ViewingActionType, {
queryParams: {
acting_username: { refreshModel: true },
},
emptyStateOthers: I18n.t("user_activity.no_activity_others"),
model() {
return this.modelFor("user").get("stream");
const user = this.modelFor("user");
const stream = user.get("stream");
return {
stream,
isAnotherUsersPage: this.isAnotherUsersPage(user),
emptyState: this.emptyState(),
emptyStateOthers: this.emptyStateOthers,
};
},
afterModel(model, transition) {
return model.filterBy({
return model.stream.filterBy({
filter: this.userActionType,
noContentHelpKey: this.noContentHelpKey || "user_activity.no_default",
actingUsername: transition.to.queryParams.acting_username,
});
},
@ -27,10 +38,15 @@ export default DiscourseRoute.extend(ViewingActionType, {
this.viewingActionType(this.userActionType);
},
actions: {
didTransition() {
this.controllerFor("user-activity")._showFooter();
return true;
},
emptyState() {
const title = I18n.t("user_activity.no_activity_title");
const body = "";
return { title, body };
},
@action
didTransition() {
this.controllerFor("user-activity")._showFooter();
return true;
},
});

View File

@ -1,4 +1,13 @@
{{#if model.noContent}}
<div class="alert alert-info">{{html-safe model.noContentHelp}}</div>
{{#if model.stream.noContent}}
{{#if model.isAnotherUsersPage}}
<div class="alert alert-info">{{model.emptyStateOthers}}</div>
{{else}}
<div class="empty-state">
<span class="empty-state-title">{{model.emptyState.title}}</span>
<div class="empty-state-body">
<p>{{model.emptyState.body}}</p>
</div>
</div>
{{/if}}
{{/if}}
{{user-stream stream=model}}
{{user-stream stream=model.stream}}

View File

@ -0,0 +1,30 @@
import { acceptance, exists, query } from "../helpers/qunit-helpers";
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
import I18n from "I18n";
acceptance("User Activity / All - empty state", function (needs) {
needs.user();
needs.pretender((server, helper) => {
const emptyResponse = { user_actions: [] };
server.get("/user_actions.json", () => {
return helper.response(emptyResponse);
});
});
test("When looking at own activity it renders the empty state panel", async function (assert) {
await visit("/u/eviltrout/activity");
assert.ok(exists("div.empty-state"));
});
test("When looking at another user activity it renders the 'No activity' message", async function (assert) {
await visit("/u/charlie/activity");
assert.ok(exists("div.alert-info"));
assert.equal(
query("div.alert-info").innerText.trim(),
I18n.t("user_activity.no_activity_others")
);
});
});

View File

@ -0,0 +1,20 @@
import { acceptance, exists } from "../helpers/qunit-helpers";
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
acceptance("User Activity / Drafts - empty state", function (needs) {
needs.user();
needs.pretender((server, helper) => {
const emptyResponse = { drafts: [] };
server.get("/drafts.json", () => {
return helper.response(emptyResponse);
});
});
test("It renders the empty state panel", async function (assert) {
await visit("/u/eviltrout/activity/drafts");
assert.ok(exists("div.empty-state"));
});
});

View File

@ -0,0 +1,30 @@
import { acceptance, exists, query } from "../helpers/qunit-helpers";
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
import I18n from "I18n";
acceptance("User Activity / Likes - empty state", function (needs) {
needs.user();
needs.pretender((server, helper) => {
const emptyResponse = { user_actions: [] };
server.get("/user_actions.json", () => {
return helper.response(emptyResponse);
});
});
test("When looking at own activity it renders the empty state panel", async function (assert) {
await visit("/u/eviltrout/activity/likes-given");
assert.ok(exists("div.empty-state"));
});
test("When looking at another user activity it renders the 'No activity' message", async function (assert) {
await visit("/u/charlie/activity/likes-given");
assert.ok(exists("div.alert-info"));
assert.equal(
query("div.alert-info").innerText.trim(),
I18n.t("user_activity.no_likes_others")
);
});
});

View File

@ -0,0 +1,30 @@
import { acceptance, exists, query } from "../helpers/qunit-helpers";
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
import I18n from "I18n";
acceptance("User Activity / Replies - empty state", function (needs) {
needs.user();
needs.pretender((server, helper) => {
const emptyResponse = { user_actions: [] };
server.get("/user_actions.json", () => {
return helper.response(emptyResponse);
});
});
test("When looking at own activity it renders the empty state panel", async function (assert) {
await visit("/u/eviltrout/activity/replies");
assert.ok(exists("div.empty-state"));
});
test("When looking at another user activity it renders the 'No activity' message", async function (assert) {
await visit("/u/charlie/activity/replies");
assert.ok(exists("div.alert-info"));
assert.equal(
query("div.alert-info").innerText.trim(),
I18n.t("user_activity.no_replies_others")
);
});
});

View File

@ -3855,6 +3855,18 @@ en:
pick_files_button:
unsupported_file_picked: "You have picked an unsupported file. Supported file types %{types}."
user_activity:
no_activity_title: "No activity yet"
no_activity_body: "Welcome to our community! You are brand new here and have not yet contributed to discussions. As a first step, visit <a href='%{topUrl}'>Top</a> or <a href='%{categoriesUrl}'>Categories</a> and just start reading! Select %{heartIcon} on posts that you like or want to learn more about. If you have not already done so, help others get to know you by adding a picture and bio in your <a href='%{preferencesUrl}'>user preferences</a>."
no_activity_others: "No activity."
no_replies_title: "You have not replied to any topics yet"
no_replies_others: "No replies."
no_drafts_title: "You havent started any drafts"
no_drafts_body: "Not quite ready to post? Well automatically save a new draft and list it here whenever you start composing a topic, reply, or personal message. Select the cancel button to discard or save your draft to continue later."
no_likes_title: "You havent liked any topics yet"
no_likes_body: "A great way to jump in and start contributing is to start reading conversations that have already taken place, and select the %{heartIcon} on posts that you like!"
no_likes_others: "No liked posts."
# This section is exported to the javascript for i18n in the admin section
admin_js:
type_to_filter: "type to filter..."

View File

@ -958,19 +958,10 @@ en:
pm_title: "Backup Drafts from ongoing topics"
pm_body: "Topic containing backup drafts"
user_activity:
no_default:
self: "You have no activity yet."
others: "No activity."
no_bookmarks:
self: "You have no bookmarked posts; bookmarks allow you to quickly refer to specific posts."
search: "No bookmarks found with the provided search query."
others: "No bookmarks."
no_likes_given:
self: "You have not liked any posts."
others: "No liked posts."
no_replies:
self: "You have not replied to any posts."
others: "No replies."
no_drafts:
self: "You have no drafts; begin composing a reply in any topic and it will be auto-saved as a new draft."