FEATURE: allow user to set preferred sidebar list destination (#18594)
User can choose between latest or new/unread and that preference will affect behavior of sidebar links.
This commit is contained in:
parent
daa8aedccf
commit
243efa8931
|
@ -20,6 +20,7 @@ export default class SidebarCommonCategoriesSection extends Component {
|
|||
new CategorySectionLink({
|
||||
category,
|
||||
topicTrackingState: this.topicTrackingState,
|
||||
currentUser: this.currentUser,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import I18n from "I18n";
|
||||
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export const DEFAULT_LIST_DESTINATION = "default";
|
||||
export const UNREAD_LIST_DESTINATION = "unread_new";
|
||||
|
||||
export default class extends Controller {
|
||||
@tracked saved = false;
|
||||
@tracked selectedSidebarCategories = [];
|
||||
@tracked selectedSidebarTagNames = [];
|
||||
|
||||
sidebarListDestinations = [
|
||||
{
|
||||
name: I18n.t("user.experimental_sidebar.list_destination_default"),
|
||||
value: DEFAULT_LIST_DESTINATION,
|
||||
},
|
||||
{
|
||||
name: I18n.t("user.experimental_sidebar.list_destination_unread_new"),
|
||||
value: UNREAD_LIST_DESTINATION,
|
||||
},
|
||||
];
|
||||
|
||||
@action
|
||||
save() {
|
||||
const initialSidebarCategoryIds = this.model.sidebarCategoryIds;
|
||||
|
@ -20,6 +35,11 @@ export default class extends Controller {
|
|||
|
||||
this.model.set("sidebar_tag_names", this.selectedSidebarTagNames);
|
||||
|
||||
this.model.set(
|
||||
"user_option.sidebar_list_destination",
|
||||
this.newSidebarListDestination
|
||||
);
|
||||
|
||||
this.model
|
||||
.save()
|
||||
.then((result) => {
|
||||
|
|
|
@ -2,6 +2,7 @@ import I18n from "I18n";
|
|||
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import BaseSectionLink from "discourse/lib/sidebar/base-community-section-link";
|
||||
import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sidebar";
|
||||
|
||||
export default class EverythingSectionLink extends BaseSectionLink {
|
||||
@tracked totalUnread = 0;
|
||||
|
@ -63,6 +64,14 @@ export default class EverythingSectionLink extends BaseSectionLink {
|
|||
}
|
||||
|
||||
get route() {
|
||||
if (this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION) {
|
||||
if (this.totalUnread > 0) {
|
||||
return "discovery.unread";
|
||||
}
|
||||
if (this.totalNew > 0) {
|
||||
return "discovery.new";
|
||||
}
|
||||
}
|
||||
return "discovery.latest";
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,16 @@ import { tracked } from "@glimmer/tracking";
|
|||
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import Category from "discourse/models/category";
|
||||
import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sidebar";
|
||||
|
||||
export default class CategorySectionLink {
|
||||
@tracked totalUnread = 0;
|
||||
@tracked totalNew = 0;
|
||||
|
||||
constructor({ category, topicTrackingState }) {
|
||||
constructor({ category, topicTrackingState, currentUser }) {
|
||||
this.category = category;
|
||||
this.topicTrackingState = topicTrackingState;
|
||||
this.currentUser = currentUser;
|
||||
this.refreshCounts();
|
||||
}
|
||||
|
||||
|
@ -79,6 +81,14 @@ export default class CategorySectionLink {
|
|||
}
|
||||
|
||||
get route() {
|
||||
if (this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION) {
|
||||
if (this.totalUnread > 0) {
|
||||
return "discovery.unreadCategory";
|
||||
}
|
||||
if (this.totalNew > 0) {
|
||||
return "discovery.newCategory";
|
||||
}
|
||||
}
|
||||
return "discovery.category";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import EmberObject, { computed, get, getProperties } from "@ember/object";
|
||||
import cookie, { removeCookie } from "discourse/lib/cookie";
|
||||
import { defaultHomepage, escapeExpression } from "discourse/lib/utilities";
|
||||
import { alias, equal, filterBy, gt, mapBy, or } from "@ember/object/computed";
|
||||
import {
|
||||
alias,
|
||||
equal,
|
||||
filterBy,
|
||||
gt,
|
||||
mapBy,
|
||||
or,
|
||||
readOnly,
|
||||
} from "@ember/object/computed";
|
||||
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
|
||||
import { A } from "@ember/array";
|
||||
import Badge from "discourse/models/badge";
|
||||
|
@ -109,6 +117,7 @@ let userOptionFields = [
|
|||
"seen_popups",
|
||||
"default_calendar",
|
||||
"bookmark_auto_delete_preference",
|
||||
"sidebar_list_destination",
|
||||
];
|
||||
|
||||
export function addSaveableUserOptionField(fieldName) {
|
||||
|
@ -341,6 +350,8 @@ const User = RestModel.extend({
|
|||
);
|
||||
},
|
||||
|
||||
sidebarListDestination: readOnly("user_option.sidebar_list_destination"),
|
||||
|
||||
changeUsername(new_username) {
|
||||
return ajax(userPath(`${this.username_lower}/preferences/username`), {
|
||||
type: "PUT",
|
||||
|
|
|
@ -12,6 +12,7 @@ export default RestrictedUserRoute.extend({
|
|||
if (this.siteSettings.tagging_enabled) {
|
||||
props.selectedSidebarTagNames = user.sidebarTagNames;
|
||||
}
|
||||
props.newSidebarListDestination = user.sidebarListDestination;
|
||||
|
||||
controller.setProperties(props);
|
||||
},
|
||||
|
|
|
@ -35,4 +35,13 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="control-group preferences-sidebar-navigation">
|
||||
<legend class="control-label">{{i18n "user.experimental_sidebar.navigation_section"}}</legend>
|
||||
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n "user.experimental_sidebar.list_destination_instruction"}}</label>
|
||||
<ComboBox @valueProperty="value" @content={{this.sidebarListDestinations}} @value={{this.newSidebarListDestination}} @onChange={{action (mut this.newSidebarListDestination)}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SaveControls @model={{this.model}} @action={{action "save"}} @saved={{this.saved}} />
|
||||
|
|
|
@ -15,6 +15,7 @@ import Site from "discourse/models/site";
|
|||
import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures";
|
||||
import categoryFixture from "discourse/tests/fixtures/category-fixtures";
|
||||
import { cloneJSON } from "discourse-common/lib/object";
|
||||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||
|
||||
acceptance(
|
||||
"Sidebar - Logged on user - Categories Section - allow_uncategorized_topics disabled",
|
||||
|
@ -292,6 +293,124 @@ acceptance("Sidebar - Logged on user - Categories Section", function (needs) {
|
|||
);
|
||||
});
|
||||
|
||||
test("clicking section links - sidebar_list_destination set to unread/new and no unread or new topics", async function (assert) {
|
||||
updateCurrentUser({
|
||||
user_option: {
|
||||
sidebar_list_destination: "unread_new",
|
||||
},
|
||||
});
|
||||
const { category1 } = setupUserSidebarCategories();
|
||||
|
||||
await visit("/");
|
||||
|
||||
await click(`.sidebar-section-link-${category1.slug}`);
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/c/${category1.slug}/${category1.id}`,
|
||||
"it should transition to the category1 default view page"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
count(".sidebar-section-categories .sidebar-section-link.active"),
|
||||
1,
|
||||
"only one link is marked as active"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(`.sidebar-section-link-${category1.slug}.active`),
|
||||
"the category1 section link is marked as active"
|
||||
);
|
||||
});
|
||||
|
||||
test("clicking section links - sidebar_list_destination set to unread/new with new topics", async function (assert) {
|
||||
const { category1 } = setupUserSidebarCategories();
|
||||
const topicTrackingState = this.container.lookup(
|
||||
"service:topic-tracking-state"
|
||||
);
|
||||
topicTrackingState.states.set("t112", {
|
||||
last_read_post_number: null,
|
||||
id: 112,
|
||||
notification_level: NotificationLevels.TRACKING,
|
||||
category_id: category1.id,
|
||||
created_in_new_period: true,
|
||||
});
|
||||
updateCurrentUser({
|
||||
user_option: {
|
||||
sidebar_list_destination: "unread_new",
|
||||
},
|
||||
});
|
||||
|
||||
await visit("/");
|
||||
|
||||
await click(`.sidebar-section-link-${category1.slug}`);
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/c/${category1.slug}/${category1.id}/l/new`,
|
||||
"it should transition to the category1 new page"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
count(".sidebar-section-categories .sidebar-section-link.active"),
|
||||
1,
|
||||
"only one link is marked as active"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(`.sidebar-section-link-${category1.slug}.active`),
|
||||
"the category1 section link is marked as active"
|
||||
);
|
||||
});
|
||||
|
||||
test("clicking section links - sidebar_list_destination set to unread/new with new and unread topics", async function (assert) {
|
||||
const { category1 } = setupUserSidebarCategories();
|
||||
const topicTrackingState = this.container.lookup(
|
||||
"service:topic-tracking-state"
|
||||
);
|
||||
topicTrackingState.states.set("t112", {
|
||||
last_read_post_number: null,
|
||||
id: 112,
|
||||
notification_level: NotificationLevels.TRACKING,
|
||||
category_id: category1.id,
|
||||
created_in_new_period: true,
|
||||
});
|
||||
topicTrackingState.states.set("t113", {
|
||||
last_read_post_number: 1,
|
||||
highest_post_number: 2,
|
||||
id: 113,
|
||||
notification_level: NotificationLevels.TRACKING,
|
||||
category_id: category1.id,
|
||||
created_in_new_period: true,
|
||||
});
|
||||
updateCurrentUser({
|
||||
user_option: {
|
||||
sidebar_list_destination: "unread_new",
|
||||
},
|
||||
});
|
||||
|
||||
await visit("/");
|
||||
|
||||
await click(`.sidebar-section-link-${category1.slug}`);
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/c/${category1.slug}/${category1.id}/l/unread`,
|
||||
"it should transition to the category1 unread page"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
count(".sidebar-section-categories .sidebar-section-link.active"),
|
||||
1,
|
||||
"only one link is marked as active"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(`.sidebar-section-link-${category1.slug}.active`),
|
||||
"the category1 section link is marked as active"
|
||||
);
|
||||
});
|
||||
|
||||
test("category section link for category with 3-digit hex code for color", async function (assert) {
|
||||
const { category1 } = setupUserSidebarCategories();
|
||||
category1.set("color", "888");
|
||||
|
|
|
@ -160,6 +160,121 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) {
|
|||
);
|
||||
});
|
||||
|
||||
test("clicking on everything link - sidebar_list_destination set to unread/new and no unread or new topics", async function (assert) {
|
||||
updateCurrentUser({
|
||||
user_option: {
|
||||
sidebar_list_destination: "unread_new",
|
||||
},
|
||||
});
|
||||
|
||||
await visit("/t/280");
|
||||
await click(".sidebar-section-community .sidebar-section-link-everything");
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
"/latest",
|
||||
"it should transition to the latest page"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
count(".sidebar-section-community .sidebar-section-link.active"),
|
||||
1,
|
||||
"only one link is marked as active"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(
|
||||
".sidebar-section-community .sidebar-section-link-everything.active"
|
||||
),
|
||||
"the everything link is marked as active"
|
||||
);
|
||||
});
|
||||
|
||||
test("clicking on everything link - sidebar_list_destination set to unread/new with new topics", async function (assert) {
|
||||
const topicTrackingState = this.container.lookup(
|
||||
"service:topic-tracking-state"
|
||||
);
|
||||
topicTrackingState.states.set("t112", {
|
||||
last_read_post_number: null,
|
||||
id: 112,
|
||||
notification_level: NotificationLevels.TRACKING,
|
||||
category_id: 2,
|
||||
created_in_new_period: true,
|
||||
});
|
||||
updateCurrentUser({
|
||||
user_option: {
|
||||
sidebar_list_destination: "unread_new",
|
||||
},
|
||||
});
|
||||
await visit("/t/280");
|
||||
await click(".sidebar-section-community .sidebar-section-link-everything");
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
"/new",
|
||||
"it should transition to the new page"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
count(".sidebar-section-community .sidebar-section-link.active"),
|
||||
1,
|
||||
"only one link is marked as active"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(
|
||||
".sidebar-section-community .sidebar-section-link-everything.active"
|
||||
),
|
||||
"the everything link is marked as active"
|
||||
);
|
||||
});
|
||||
|
||||
test("clicking on everything link - sidebar_list_destination set to unread/new with new and unread topics", async function (assert) {
|
||||
const topicTrackingState = this.container.lookup(
|
||||
"service:topic-tracking-state"
|
||||
);
|
||||
topicTrackingState.states.set("t112", {
|
||||
last_read_post_number: null,
|
||||
id: 112,
|
||||
notification_level: NotificationLevels.TRACKING,
|
||||
category_id: 2,
|
||||
created_in_new_period: true,
|
||||
});
|
||||
topicTrackingState.states.set("t113", {
|
||||
last_read_post_number: 1,
|
||||
highest_post_number: 2,
|
||||
id: 113,
|
||||
notification_level: NotificationLevels.TRACKING,
|
||||
category_id: 2,
|
||||
created_in_new_period: true,
|
||||
});
|
||||
updateCurrentUser({
|
||||
user_option: {
|
||||
sidebar_list_destination: "unread_new",
|
||||
},
|
||||
});
|
||||
await visit("/t/280");
|
||||
await click(".sidebar-section-community .sidebar-section-link-everything");
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
"/unread",
|
||||
"it should transition to the unread page"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
count(".sidebar-section-community .sidebar-section-link.active"),
|
||||
1,
|
||||
"only one link is marked as active"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(
|
||||
".sidebar-section-community .sidebar-section-link-everything.active"
|
||||
),
|
||||
"the everything link is marked as active"
|
||||
);
|
||||
});
|
||||
|
||||
test("clicking on tracked link", async function (assert) {
|
||||
await visit("/t/280");
|
||||
await click(".sidebar-section-community .sidebar-section-link-tracked");
|
||||
|
|
|
@ -46,6 +46,9 @@ acceptance("User Preferences - Sidebar", function (needs) {
|
|||
{ name: "monkey", pm_only: false },
|
||||
{ name: "gazelle", pm_only: false },
|
||||
],
|
||||
user_option: {
|
||||
sidebar_list_destination: "unread_new",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ class UserOption < ActiveRecord::Base
|
|||
scope :human_users, -> { where('user_id > 0') }
|
||||
|
||||
enum default_calendar: { none_selected: 0, ics: 1, google: 2 }, _scopes: false
|
||||
enum sidebar_list_destination: { none_selected: 0, default: 0, unread_new: 1 }, _prefix: "sidebar_list"
|
||||
|
||||
def self.ensure_consistency!
|
||||
sql = <<~SQL
|
||||
|
@ -269,6 +270,7 @@ end
|
|||
# bookmark_auto_delete_preference :integer default(3), not null
|
||||
# enable_experimental_sidebar :boolean default(FALSE)
|
||||
# seen_popups :integer is an Array
|
||||
# sidebar_list_destination :integer default("none_selected"), not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -34,7 +34,8 @@ class UserOptionSerializer < ApplicationSerializer
|
|||
:timezone,
|
||||
:skip_new_user_tips,
|
||||
:default_calendar,
|
||||
:oldest_search_log_date
|
||||
:oldest_search_log_date,
|
||||
:sidebar_list_destination
|
||||
|
||||
def auto_track_topics_after_msecs
|
||||
object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs
|
||||
|
@ -51,4 +52,8 @@ class UserOptionSerializer < ApplicationSerializer
|
|||
def theme_ids
|
||||
object.theme_ids.presence || [SiteSetting.default_theme_id]
|
||||
end
|
||||
|
||||
def sidebar_list_destination
|
||||
object.sidebar_list_none_selected? ? SiteSetting.default_sidebar_list_destination : object.sidebar_list_destination
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,7 +48,8 @@ class UserUpdater
|
|||
:timezone,
|
||||
:skip_new_user_tips,
|
||||
:seen_popups,
|
||||
:default_calendar
|
||||
:default_calendar,
|
||||
:sidebar_list_destination
|
||||
]
|
||||
|
||||
NOTIFICATION_SCHEDULE_ATTRS = -> {
|
||||
|
|
|
@ -1183,6 +1183,10 @@ en:
|
|||
categories_section_instruction: "Selected categories will be displayed under Sidebar's categories section."
|
||||
tags_section: "Tags Section"
|
||||
tags_section_instruction: "Selected tags will be displayed under Sidebar's tags section."
|
||||
navigation_section: "Navigation"
|
||||
list_destination_instruction: "When I click a topic list link in the sidebar with new or unread topics, take me to"
|
||||
list_destination_default: "Default"
|
||||
list_destination_unread_new: "New/Unread"
|
||||
change: "change"
|
||||
featured_topic: "Featured Topic"
|
||||
moderator: "%{user} is a moderator"
|
||||
|
|
|
@ -2056,6 +2056,13 @@ sidebar:
|
|||
type: tag_list
|
||||
default: ""
|
||||
client: true
|
||||
default_sidebar_list_destination:
|
||||
hidden: true
|
||||
default: "default"
|
||||
type: "list"
|
||||
choices:
|
||||
- "default"
|
||||
- "unread_new"
|
||||
|
||||
embedding:
|
||||
embed_by_username:
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSidebarListDestinationToUserOption < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :user_options, :sidebar_list_destination, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
|
@ -767,6 +767,9 @@
|
|||
},
|
||||
"oldest_search_log_date": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"sidebar_list_destination": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
@ -426,4 +426,13 @@ RSpec.describe UserSerializer do
|
|||
end
|
||||
|
||||
include_examples "#display_sidebar_tags", UserSerializer
|
||||
|
||||
describe "#sidebar_list_destination" do
|
||||
it "returns choosen value or default" do
|
||||
expect(serializer.as_json[:user_option][:sidebar_list_destination]).to eq(SiteSetting.default_sidebar_list_destination)
|
||||
|
||||
user.user_option.update!(sidebar_list_destination: "unread_new")
|
||||
expect(serializer.as_json[:user_option][:sidebar_list_destination]).to eq("unread_new")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue