mirror of
https://github.com/discourse/discourse.git
synced 2025-01-16 04:02:40 +08:00
FEATURE: add drafts dropdown menu (#30277)
This change adds a new dropdown trigger next to the "New Topic" button. When clicked a menu will display a list of topic/post drafts that can be clicked to resume the draft within the composer. The "New Topic" button will no longer change text to show "Open Draft" when a draft topic exists, it will still attempt to load the existing draft if one exists (this will change later when we support multiple drafts in a separate PR). The "My Posts" link in desktop sidebar will now be "My Drafts" and only appear when the current user has existing drafts.
This commit is contained in:
parent
0b3663a16a
commit
47c8197ea1
|
@ -16,4 +16,8 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</:tooltip>
|
</:tooltip>
|
||||||
</DButtonTooltip>
|
</DButtonTooltip>
|
||||||
|
|
||||||
|
{{#if @showDrafts}}
|
||||||
|
<TopicDraftsDropdown />
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
|
@ -102,6 +102,7 @@
|
||||||
@label={{this.createTopicLabel}}
|
@label={{this.createTopicLabel}}
|
||||||
@btnClass={{this.createTopicClass}}
|
@btnClass={{this.createTopicClass}}
|
||||||
@canCreateTopicOnTag={{this.canCreateTopicOnTag}}
|
@canCreateTopicOnTag={{this.canCreateTopicOnTag}}
|
||||||
|
@showDrafts={{if (gt this.draftCount 0) true false}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
|
|
|
@ -20,6 +20,8 @@ export default class DNavigation extends Component {
|
||||||
|
|
||||||
@setting("fixed_category_positions") fixedCategoryPositions;
|
@setting("fixed_category_positions") fixedCategoryPositions;
|
||||||
|
|
||||||
|
createTopicLabel = "topic.create";
|
||||||
|
|
||||||
@dependentKeyCompat
|
@dependentKeyCompat
|
||||||
get filterType() {
|
get filterType() {
|
||||||
return filterTypeForMode(this.filterMode);
|
return filterTypeForMode(this.filterMode);
|
||||||
|
@ -111,11 +113,6 @@ export default class DNavigation extends Component {
|
||||||
return classNames.join(" ");
|
return classNames.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed("hasDraft")
|
|
||||||
createTopicLabel(hasDraft) {
|
|
||||||
return hasDraft ? "topic.open_draft" : "topic.create";
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("category.can_edit")
|
@discourseComputed("category.can_edit")
|
||||||
showCategoryEdit(canEdit) {
|
showCategoryEdit(canEdit) {
|
||||||
return canEdit;
|
return canEdit;
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
@createTopic={{@createTopic}}
|
@createTopic={{@createTopic}}
|
||||||
@createTopicDisabled={{@createTopicDisabled}}
|
@createTopicDisabled={{@createTopicDisabled}}
|
||||||
@hasDraft={{this.currentUser.has_topic_draft}}
|
@hasDraft={{this.currentUser.has_topic_draft}}
|
||||||
|
@draftCount={{this.currentUser.draft_count}}
|
||||||
@editCategory={{this.editCategory}}
|
@editCategory={{this.editCategory}}
|
||||||
@showCategoryAdmin={{@showCategoryAdmin}}
|
@showCategoryAdmin={{@showCategoryAdmin}}
|
||||||
@createCategory={{this.createCategory}}
|
@createCategory={{this.createCategory}}
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { fn } from "@ember/helper";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||||
|
import DiscourseURL from "discourse/lib/url";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
import DMenu from "float-kit/components/d-menu";
|
||||||
|
|
||||||
|
const DRAFTS_LIMIT = 4;
|
||||||
|
|
||||||
|
export default class TopicDraftsDropdown extends Component {
|
||||||
|
@service currentUser;
|
||||||
|
@service composer;
|
||||||
|
|
||||||
|
@tracked drafts = [];
|
||||||
|
|
||||||
|
get draftCount() {
|
||||||
|
return this.currentUser.draft_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
get otherDraftsCount() {
|
||||||
|
return this.draftCount > DRAFTS_LIMIT ? this.draftCount - DRAFTS_LIMIT : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get otherDraftsText() {
|
||||||
|
return this.otherDraftsCount > 0
|
||||||
|
? i18n("drafts.dropdown.other_drafts", {
|
||||||
|
count: this.otherDraftsCount,
|
||||||
|
})
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onRegisterApi(api) {
|
||||||
|
this.dMenu = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async onShowMenu() {
|
||||||
|
try {
|
||||||
|
const draftsStream = this.currentUser.userDraftsStream;
|
||||||
|
draftsStream.reset();
|
||||||
|
|
||||||
|
await draftsStream.findItems(this.site);
|
||||||
|
this.drafts = draftsStream.content.slice(0, DRAFTS_LIMIT);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Failed to fetch drafts with error:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async resumeDraft(draft) {
|
||||||
|
await this.dMenu.close();
|
||||||
|
|
||||||
|
if (draft.postUrl) {
|
||||||
|
DiscourseURL.routeTo(draft.postUrl);
|
||||||
|
} else {
|
||||||
|
this.composer.open({
|
||||||
|
draft,
|
||||||
|
draftKey: draft.draft_key,
|
||||||
|
draftSequence: draft.sequence,
|
||||||
|
...draft.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DMenu
|
||||||
|
@identifier="topic-drafts-menu"
|
||||||
|
@title={{i18n "drafts.dropdown.title"}}
|
||||||
|
@icon="chevron-down"
|
||||||
|
@onShow={{this.onShowMenu}}
|
||||||
|
@onRegisterApi={{this.onRegisterApi}}
|
||||||
|
@modalForMobile={{true}}
|
||||||
|
class="btn-small"
|
||||||
|
>
|
||||||
|
<:content>
|
||||||
|
<DropdownMenu as |dropdown|>
|
||||||
|
{{#each this.drafts as |draft|}}
|
||||||
|
<dropdown.item class="topic-drafts-item">
|
||||||
|
<DButton
|
||||||
|
@action={{fn this.resumeDraft draft}}
|
||||||
|
@icon={{if draft.topic_id "reply" "layer-group"}}
|
||||||
|
@translatedLabel={{draft.title}}
|
||||||
|
class="btn-secondary"
|
||||||
|
/>
|
||||||
|
</dropdown.item>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<dropdown.divider />
|
||||||
|
|
||||||
|
<dropdown.item>
|
||||||
|
<DButton
|
||||||
|
@href="/my/activity/drafts"
|
||||||
|
@model={{this.currentUser}}
|
||||||
|
class="btn-link view-all-drafts"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
data-other-drafts={{this.otherDraftsCount}}
|
||||||
|
>{{this.otherDraftsText}}</span>
|
||||||
|
<span>{{i18n "drafts.dropdown.view_all"}}</span>
|
||||||
|
</DButton>
|
||||||
|
</dropdown.item>
|
||||||
|
</DropdownMenu>
|
||||||
|
</:content>
|
||||||
|
</DMenu>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import {
|
||||||
import SectionLink from "discourse/lib/sidebar/section-link";
|
import SectionLink from "discourse/lib/sidebar/section-link";
|
||||||
import AdminSectionLink from "discourse/lib/sidebar/user/community-section/admin-section-link";
|
import AdminSectionLink from "discourse/lib/sidebar/user/community-section/admin-section-link";
|
||||||
import InviteSectionLink from "discourse/lib/sidebar/user/community-section/invite-section-link";
|
import InviteSectionLink from "discourse/lib/sidebar/user/community-section/invite-section-link";
|
||||||
import MyPostsSectionLink from "discourse/lib/sidebar/user/community-section/my-posts-section-link";
|
import MyDraftsSectionLink from "discourse/lib/sidebar/user/community-section/my-drafts-section-link";
|
||||||
import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link";
|
import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link";
|
||||||
|
|
||||||
const SPECIAL_LINKS_MAP = {
|
const SPECIAL_LINKS_MAP = {
|
||||||
|
@ -22,7 +22,7 @@ const SPECIAL_LINKS_MAP = {
|
||||||
"/about": AboutSectionLink,
|
"/about": AboutSectionLink,
|
||||||
"/u": UsersSectionLink,
|
"/u": UsersSectionLink,
|
||||||
"/faq": FAQSectionLink,
|
"/faq": FAQSectionLink,
|
||||||
"/my/activity": MyPostsSectionLink,
|
"/my/activity": MyDraftsSectionLink,
|
||||||
"/review": ReviewSectionLink,
|
"/review": ReviewSectionLink,
|
||||||
"/badges": BadgesSectionLink,
|
"/badges": BadgesSectionLink,
|
||||||
"/admin": AdminSectionLink,
|
"/admin": AdminSectionLink,
|
||||||
|
|
|
@ -4,13 +4,13 @@ import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
const USER_DRAFTS_CHANGED_EVENT = "user-drafts:changed";
|
const USER_DRAFTS_CHANGED_EVENT = "user-drafts:changed";
|
||||||
|
|
||||||
export default class MyPostsSectionLink extends BaseSectionLink {
|
export default class MyDraftsSectionLink extends BaseSectionLink {
|
||||||
@tracked draftCount = this.currentUser?.draft_count;
|
@tracked draftCount = this.currentUser?.draft_count;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
|
|
||||||
if (this.shouldDisplay) {
|
if (this.currentUser) {
|
||||||
this.appEvents.on(
|
this.appEvents.on(
|
||||||
USER_DRAFTS_CHANGED_EVENT,
|
USER_DRAFTS_CHANGED_EVENT,
|
||||||
this,
|
this,
|
||||||
|
@ -20,7 +20,7 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown() {
|
teardown() {
|
||||||
if (this.shouldDisplay) {
|
if (this.currentUser) {
|
||||||
this.appEvents.off(
|
this.appEvents.off(
|
||||||
USER_DRAFTS_CHANGED_EVENT,
|
USER_DRAFTS_CHANGED_EVENT,
|
||||||
this,
|
this,
|
||||||
|
@ -38,21 +38,11 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return "my-posts";
|
return "my-drafts";
|
||||||
}
|
}
|
||||||
|
|
||||||
get route() {
|
get route() {
|
||||||
if (this._hasDraft) {
|
return "userActivity.drafts";
|
||||||
return "userActivity.drafts";
|
|
||||||
} else {
|
|
||||||
return "userActivity.index";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentWhen() {
|
|
||||||
if (this._hasDraft) {
|
|
||||||
return "userActivity.index userActivity.drafts";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get model() {
|
get model() {
|
||||||
|
@ -60,24 +50,11 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
if (this._hasDraft) {
|
return i18n("sidebar.sections.community.links.my_drafts.title");
|
||||||
return i18n("sidebar.sections.community.links.my_posts.title_drafts");
|
|
||||||
} else {
|
|
||||||
return i18n("sidebar.sections.community.links.my_posts.title");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
if (this._hasDraft && this.currentUser?.new_new_view_enabled) {
|
return i18n("sidebar.sections.community.links.my_drafts.content");
|
||||||
return i18n("sidebar.sections.community.links.my_posts.content_drafts");
|
|
||||||
} else {
|
|
||||||
return i18n(
|
|
||||||
`sidebar.sections.community.links.${this.overridenName
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(" ", "_")}.content`,
|
|
||||||
{ defaultValue: this.overridenName }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get badgeText() {
|
get badgeText() {
|
||||||
|
@ -88,7 +65,7 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
||||||
if (this.currentUser.new_new_view_enabled) {
|
if (this.currentUser.new_new_view_enabled) {
|
||||||
return this.draftCount.toString();
|
return this.draftCount.toString();
|
||||||
} else {
|
} else {
|
||||||
return i18n("sidebar.sections.community.links.my_posts.draft_count", {
|
return i18n("sidebar.sections.community.links.my_drafts.draft_count", {
|
||||||
count: this.draftCount,
|
count: this.draftCount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -98,13 +75,6 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
||||||
return this.draftCount > 0;
|
return this.draftCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultPrefixValue() {
|
|
||||||
if (this._hasDraft && this.currentUser?.new_new_view_enabled) {
|
|
||||||
return "pencil";
|
|
||||||
}
|
|
||||||
return "user";
|
|
||||||
}
|
|
||||||
|
|
||||||
get suffixCSSClass() {
|
get suffixCSSClass() {
|
||||||
return "unread";
|
return "unread";
|
||||||
}
|
}
|
||||||
|
@ -120,6 +90,10 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
return this.currentUser;
|
return this.currentUser && this._hasDraft;
|
||||||
|
}
|
||||||
|
|
||||||
|
get prefixValue() {
|
||||||
|
return "far-pen-to-square";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -60,10 +60,6 @@ async function loadDraft(store, opts = {}) {
|
||||||
Draft.clear(draftKey, draftSequence);
|
Draft.clear(draftKey, draftSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!draft?.title && !draft?.reply) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let attrs = {
|
let attrs = {
|
||||||
draftKey,
|
draftKey,
|
||||||
draftSequence,
|
draftSequence,
|
||||||
|
|
|
@ -908,7 +908,7 @@ acceptance("Composer", function (needs) {
|
||||||
});
|
});
|
||||||
|
|
||||||
await visit("/latest");
|
await visit("/latest");
|
||||||
assert.dom("#create-topic").hasText(i18n("topic.open_draft"));
|
assert.dom("#create-topic").hasText(i18n("topic.create"));
|
||||||
|
|
||||||
await click("#create-topic");
|
await click("#create-topic");
|
||||||
assert.strictEqual(selectKit(".category-chooser").header().value(), "2");
|
assert.strictEqual(selectKit(".category-chooser").header().value(), "2");
|
||||||
|
|
|
@ -503,50 +503,13 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) {
|
||||||
.doesNotExist();
|
.doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("clicking on my posts link", async function (assert) {
|
test("clicking on my drafts link", async function (assert) {
|
||||||
await visit("/t/280");
|
updateCurrentUser({ draft_count: 1 });
|
||||||
await click(
|
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-posts']"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(
|
|
||||||
currentURL(),
|
|
||||||
`/u/${loggedInUser().username}/activity`,
|
|
||||||
"should transition to the user's activity url"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-section-link.active"
|
|
||||||
)
|
|
||||||
.exists({ count: 1 }, "only one link is marked as active");
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-posts'].active"
|
|
||||||
)
|
|
||||||
.exists("the my posts link is marked as active");
|
|
||||||
|
|
||||||
await visit(`/u/${loggedInUser().username}/activity/drafts`);
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-posts'].active"
|
|
||||||
)
|
|
||||||
.doesNotExist(
|
|
||||||
"the my posts link is not marked as active when user has no drafts and visiting the user activity drafts URL"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("clicking on my posts link when user has a draft", async function (assert) {
|
|
||||||
await visit("/t/280");
|
await visit("/t/280");
|
||||||
|
|
||||||
await publishToMessageBus(`/user-drafts/${loggedInUser().id}`, {
|
|
||||||
draft_count: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
await click(
|
await click(
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-posts']"
|
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-drafts']"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
|
@ -563,45 +526,24 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) {
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(
|
.dom(
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-posts'].active"
|
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-drafts'].active"
|
||||||
)
|
)
|
||||||
.exists("the my posts link is marked as active");
|
.exists("the my drafts link is marked as active");
|
||||||
|
|
||||||
await visit(`/u/${loggedInUser().username}/activity`);
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".sidebar-section[data-section-name='community'] .sidebar-section-link[data-link-name='my-posts'].active"
|
|
||||||
)
|
|
||||||
.exists("the my posts link is marked as active");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("my posts title changes when drafts are present", async function (assert) {
|
test("my drafts link is visible when user has drafts", async function (assert) {
|
||||||
|
updateCurrentUser({ draft_count: 1 });
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(".sidebar-section-link[data-link-name='my-posts']")
|
.dom(".sidebar-section-link[data-link-name='my-drafts']")
|
||||||
.hasAttribute(
|
.exists("my drafts link is displayed when drafts are present");
|
||||||
"title",
|
|
||||||
i18n("sidebar.sections.community.links.my_posts.title"),
|
|
||||||
"displays the default title when no drafts are present"
|
|
||||||
);
|
|
||||||
|
|
||||||
await publishToMessageBus(`/user-drafts/${loggedInUser().id}`, {
|
|
||||||
draft_count: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(".sidebar-section-link[data-link-name='my-posts']")
|
|
||||||
.hasAttribute(
|
|
||||||
"title",
|
|
||||||
i18n("sidebar.sections.community.links.my_posts.title_drafts"),
|
|
||||||
"displays the draft title when drafts are present"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("my posts changes its text when drafts are present and new new view experiment is enabled", async function (assert) {
|
test("my posts changes its text when drafts are present and new new view experiment is enabled", async function (assert) {
|
||||||
updateCurrentUser({
|
updateCurrentUser({
|
||||||
|
draft_count: 1,
|
||||||
user_option: {
|
user_option: {
|
||||||
sidebar_show_count_of_new_items: true,
|
sidebar_show_count_of_new_items: true,
|
||||||
},
|
},
|
||||||
|
@ -609,28 +551,17 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) {
|
||||||
});
|
});
|
||||||
await visit("/");
|
await visit("/");
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(".sidebar-section-link[data-link-name='my-posts']")
|
|
||||||
.hasText(
|
|
||||||
i18n("sidebar.sections.community.links.my_posts.content"),
|
|
||||||
"displays the default text when no drafts are present"
|
|
||||||
);
|
|
||||||
|
|
||||||
await publishToMessageBus(`/user-drafts/${loggedInUser().id}`, {
|
|
||||||
draft_count: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(
|
.dom(
|
||||||
".sidebar-section-link[data-link-name='my-posts'] .sidebar-section-link-content-text"
|
".sidebar-section-link[data-link-name='my-drafts'] .sidebar-section-link-content-text"
|
||||||
)
|
)
|
||||||
.hasText(
|
.hasText(
|
||||||
i18n("sidebar.sections.community.links.my_posts.content_drafts"),
|
i18n("sidebar.sections.community.links.my_drafts.content"),
|
||||||
"displays the text that's appropriate for when drafts are present"
|
"displays the text that's appropriate for when drafts are present"
|
||||||
);
|
);
|
||||||
assert
|
assert
|
||||||
.dom(
|
.dom(
|
||||||
".sidebar-section-link[data-link-name='my-posts'] .sidebar-section-link-content-badge"
|
".sidebar-section-link[data-link-name='my-drafts'] .sidebar-section-link-content-badge"
|
||||||
)
|
)
|
||||||
.hasText("1", "displays the draft count with no text");
|
.hasText("1", "displays the draft count with no text");
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
@import "date-picker";
|
@import "date-picker";
|
||||||
@import "date-time-input-range";
|
@import "date-time-input-range";
|
||||||
@import "date-time-input";
|
@import "date-time-input";
|
||||||
|
@import "drafts-dropdown-menu";
|
||||||
@import "file-size-input";
|
@import "file-size-input";
|
||||||
@import "footer-nav";
|
@import "footer-nav";
|
||||||
@import "form-template-field";
|
@import "form-template-field";
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Styles for the drafts dropdown menu
|
||||||
|
|
||||||
|
.topic-drafts-menu-trigger {
|
||||||
|
margin-left: -0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-drafts-menu-content {
|
||||||
|
margin-top: -0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-drafts-menu-content .dropdown-menu {
|
||||||
|
.btn .d-button-label {
|
||||||
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-all-drafts {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
span:first-child {
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
@import "drafts-dropdown-menu";
|
||||||
@import "more-topics";
|
@import "more-topics";
|
||||||
@import "sidebar/edit-navigation-menu/tags-modal";
|
@import "sidebar/edit-navigation-menu/tags-modal";
|
||||||
@import "sidebar/sidebar-section";
|
@import "sidebar/sidebar-section";
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.topic-drafts-menu-content .dropdown-menu {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
|
@ -14,9 +14,9 @@ class SidebarUrl < ActiveRecord::Base
|
||||||
segment: SidebarUrl.segments["primary"],
|
segment: SidebarUrl.segments["primary"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "My Posts",
|
name: "My Drafts",
|
||||||
path: "/my/activity",
|
path: "/my/activity",
|
||||||
icon: "user",
|
icon: "far-pen-to-square",
|
||||||
segment: SidebarUrl.segments["primary"],
|
segment: SidebarUrl.segments["primary"],
|
||||||
},
|
},
|
||||||
{ name: "Review", path: "/review", icon: "flag", segment: SidebarUrl.segments["primary"] },
|
{ name: "Review", path: "/review", icon: "flag", segment: SidebarUrl.segments["primary"] },
|
||||||
|
|
|
@ -497,6 +497,12 @@ en:
|
||||||
confirm: "You already have a draft in progress. What would you like to do with it?"
|
confirm: "You already have a draft in progress. What would you like to do with it?"
|
||||||
yes_value: "Discard"
|
yes_value: "Discard"
|
||||||
no_value: "Resume editing"
|
no_value: "Resume editing"
|
||||||
|
dropdown:
|
||||||
|
title: "Open the latest drafts menu"
|
||||||
|
view_all: "view all"
|
||||||
|
other_drafts:
|
||||||
|
one: "+%{count} other draft"
|
||||||
|
other: "+%{count} other drafts"
|
||||||
|
|
||||||
topic_count_all:
|
topic_count_all:
|
||||||
one: "See %{count} new topic"
|
one: "See %{count} new topic"
|
||||||
|
@ -4973,11 +4979,9 @@ en:
|
||||||
users:
|
users:
|
||||||
content: "Users"
|
content: "Users"
|
||||||
title: "List of all users"
|
title: "List of all users"
|
||||||
my_posts:
|
my_drafts:
|
||||||
content: "My Posts"
|
content: "My Drafts"
|
||||||
content_drafts: "My Drafts"
|
title: "My unposted drafts"
|
||||||
title: "My recent topic activity"
|
|
||||||
title_drafts: "My unposted drafts"
|
|
||||||
draft_count:
|
draft_count:
|
||||||
one: "%{count} draft"
|
one: "%{count} draft"
|
||||||
other: "%{count} drafts"
|
other: "%{count} drafts"
|
||||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe SidebarSection do
|
||||||
expect(community_section.sidebar_section_links.all.map { |link| link.linkable.name }).to eq(
|
expect(community_section.sidebar_section_links.all.map { |link| link.linkable.name }).to eq(
|
||||||
[
|
[
|
||||||
"Topics",
|
"Topics",
|
||||||
"My Posts",
|
"My Drafts",
|
||||||
"Review",
|
"Review",
|
||||||
"Admin",
|
"Admin",
|
||||||
"Invite",
|
"Invite",
|
||||||
|
|
86
spec/system/drafts_dropdown_spec.rb
Normal file
86
spec/system/drafts_dropdown_spec.rb
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe "Drafts dropdown", type: :system do
|
||||||
|
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
||||||
|
let(:composer) { PageObjects::Components::Composer.new }
|
||||||
|
let(:drafts_dropdown) { PageObjects::Components::DraftsMenu.new }
|
||||||
|
let(:discard_draft_modal) { PageObjects::Modals::DiscardDraft.new }
|
||||||
|
|
||||||
|
before { sign_in(user) }
|
||||||
|
|
||||||
|
describe "with no drafts" do
|
||||||
|
it "does not display drafts dropdown" do
|
||||||
|
page.visit "/"
|
||||||
|
expect(drafts_dropdown).to be_hidden
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not have a my drafts link in sidebar" do
|
||||||
|
page.visit "/"
|
||||||
|
expect(page).to have_no_css(".sidebar-section-link[data-link-name='my-drafts']")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds a draft dropdown menu when a draft is available" do
|
||||||
|
page.visit "/new-topic"
|
||||||
|
composer.fill_content("This is a draft")
|
||||||
|
|
||||||
|
expect(drafts_dropdown).to be_visible
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows a my drafts link in sidebar when a draft is saved" do
|
||||||
|
page.visit "/new-topic"
|
||||||
|
|
||||||
|
composer.fill_content("This is a draft")
|
||||||
|
composer.close
|
||||||
|
|
||||||
|
expect(discard_draft_modal).to be_open
|
||||||
|
discard_draft_modal.click_save
|
||||||
|
|
||||||
|
visit "/"
|
||||||
|
expect(page).to have_css(".sidebar-section-link[data-link-name='my-drafts']")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with multiple drafts" do
|
||||||
|
before do
|
||||||
|
Draft.set(
|
||||||
|
user,
|
||||||
|
Draft::NEW_TOPIC,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
title: "This is a test topic",
|
||||||
|
reply: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||||
|
}.to_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
5.times do |i|
|
||||||
|
topic = Fabricate(:topic, user: user)
|
||||||
|
Draft.set(user, topic.draft_key, 0, { reply: "test reply #{i}" }.to_json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "displays the correct draft count" do
|
||||||
|
page.visit "/"
|
||||||
|
drafts_dropdown.open
|
||||||
|
|
||||||
|
expect(drafts_dropdown).to be_open
|
||||||
|
|
||||||
|
expect(drafts_dropdown.draft_item_count).to eq(4)
|
||||||
|
expect(drafts_dropdown.other_drafts_count).to eq(2)
|
||||||
|
|
||||||
|
drafts_dropdown.find(".topic-drafts-item:first-child").click
|
||||||
|
|
||||||
|
expect(drafts_dropdown).to be_closed
|
||||||
|
|
||||||
|
expect(composer).to be_opened
|
||||||
|
composer.create
|
||||||
|
|
||||||
|
wait_for { Draft.count == 5 }
|
||||||
|
|
||||||
|
page.visit "/"
|
||||||
|
drafts_dropdown.open
|
||||||
|
|
||||||
|
expect(drafts_dropdown.draft_item_count).to eq(4)
|
||||||
|
expect(drafts_dropdown.other_drafts_count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,7 +23,7 @@ RSpec.describe "Editing Sidebar Community Section", type: :system do
|
||||||
visit("/latest")
|
visit("/latest")
|
||||||
|
|
||||||
expect(sidebar.primary_section_icons("community")).to eq(
|
expect(sidebar.primary_section_icons("community")).to eq(
|
||||||
%w[layer-group user flag wrench paper-plane ellipsis-vertical],
|
%w[layer-group flag wrench paper-plane ellipsis-vertical],
|
||||||
)
|
)
|
||||||
|
|
||||||
modal = sidebar.click_community_section_more_button.click_customize_community_section_button
|
modal = sidebar.click_community_section_more_button.click_customize_community_section_button
|
||||||
|
@ -32,12 +32,10 @@ RSpec.describe "Editing Sidebar Community Section", type: :system do
|
||||||
modal.save
|
modal.save
|
||||||
modal.confirm_update
|
modal.confirm_update
|
||||||
|
|
||||||
expect(sidebar.primary_section_links("community")).to eq(
|
expect(sidebar.primary_section_links("community")).to eq(%w[Topics Review Admin Invite More])
|
||||||
["My Posts", "Topics", "Review", "Admin", "Invite", "More"],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(sidebar.primary_section_icons("community")).to eq(
|
expect(sidebar.primary_section_icons("community")).to eq(
|
||||||
%w[user paper-plane flag wrench paper-plane ellipsis-vertical],
|
%w[paper-plane flag wrench paper-plane ellipsis-vertical],
|
||||||
)
|
)
|
||||||
|
|
||||||
modal = sidebar.click_community_section_more_button.click_customize_community_section_button
|
modal = sidebar.click_community_section_more_button.click_customize_community_section_button
|
||||||
|
@ -45,12 +43,10 @@ RSpec.describe "Editing Sidebar Community Section", type: :system do
|
||||||
|
|
||||||
expect(sidebar).to have_section("Community")
|
expect(sidebar).to have_section("Community")
|
||||||
|
|
||||||
expect(sidebar.primary_section_links("community")).to eq(
|
expect(sidebar.primary_section_links("community")).to eq(%w[Topics Review Admin Invite More])
|
||||||
["Topics", "My Posts", "Review", "Admin", "Invite", "More"],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(sidebar.primary_section_icons("community")).to eq(
|
expect(sidebar.primary_section_icons("community")).to eq(
|
||||||
%w[layer-group user flag wrench paper-plane ellipsis-vertical],
|
%w[layer-group flag wrench paper-plane ellipsis-vertical],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
37
spec/system/page_objects/components/drafts_menu.rb
Normal file
37
spec/system/page_objects/components/drafts_menu.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Components
|
||||||
|
class DraftsMenu < PageObjects::Components::Base
|
||||||
|
MENU_SELECTOR = ".topic-drafts-menu"
|
||||||
|
|
||||||
|
def visible?
|
||||||
|
has_css?(MENU_SELECTOR + "-trigger")
|
||||||
|
end
|
||||||
|
|
||||||
|
def hidden?
|
||||||
|
has_no_css?(MENU_SELECTOR + "-trigger")
|
||||||
|
end
|
||||||
|
|
||||||
|
def open?
|
||||||
|
has_css?(MENU_SELECTOR + "-content")
|
||||||
|
end
|
||||||
|
|
||||||
|
def closed?
|
||||||
|
has_no_css?(MENU_SELECTOR + "-content")
|
||||||
|
end
|
||||||
|
|
||||||
|
def open
|
||||||
|
find(MENU_SELECTOR + "-trigger").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def draft_item_count
|
||||||
|
all(MENU_SELECTOR + "-content .topic-drafts-item").size
|
||||||
|
end
|
||||||
|
|
||||||
|
def other_drafts_count
|
||||||
|
find(MENU_SELECTOR + "-content .view-all-drafts span:first-child")["data-other-drafts"].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,6 +15,10 @@ module PageObjects
|
||||||
def click_save
|
def click_save
|
||||||
footer.find("button.save-draft").click
|
footer.find("button.save-draft").click
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def click_discard
|
||||||
|
footer.find("button.discard-draft").click
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,10 +56,8 @@ describe "Viewing sidebar as logged in user", type: :system do
|
||||||
sign_in user
|
sign_in user
|
||||||
visit("/latest")
|
visit("/latest")
|
||||||
links = page.all("#sidebar-section-content-community .sidebar-section-link-wrapper a")
|
links = page.all("#sidebar-section-content-community .sidebar-section-link-wrapper a")
|
||||||
expect(links.map(&:text)).to eq(%w[Tematy Wysłane])
|
expect(links.map(&:text)).to eq(%w[Tematy])
|
||||||
expect(links.map { |link| link[:title] }).to eq(
|
expect(links.map { |link| link[:title] }).to eq(["Wszystkie tematy"])
|
||||||
["Wszystkie tematy", "Moja ostatnia aktywność w temacie"],
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user