DEV: Reimplement widget UI as glimmer components (#243)
This aims to be a 1:1 port of functionality and HTML structure. In future, this plugin may benefit from a refactor to use float-kit. Also unskips the system spec and makes improvements to prevent flakiness.
This commit is contained in:
parent
983027da53
commit
d9af83fd7b
|
@ -0,0 +1,125 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import routeAction from "discourse/helpers/route-action";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import VoteButton from "./vote-button";
|
||||
import VoteCount from "./vote-count";
|
||||
import VoteOptions from "./vote-options";
|
||||
|
||||
export default class VoteBox extends Component {
|
||||
@service siteSettings;
|
||||
@service currentUser;
|
||||
|
||||
@tracked votesAlert;
|
||||
@tracked allowClick = true;
|
||||
@tracked initialVote = false;
|
||||
@tracked showOptions = false;
|
||||
|
||||
@action
|
||||
addVote() {
|
||||
let topic = this.args.topic;
|
||||
return ajax("/voting/vote", {
|
||||
type: "POST",
|
||||
data: {
|
||||
topic_id: topic.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
topic.vote_count = result.vote_count;
|
||||
topic.user_voted = true;
|
||||
this.currentUser.votes_exceeded = !result.can_vote;
|
||||
this.currentUser.votes_left = result.votes_left;
|
||||
if (result.alert) {
|
||||
this.votesAlert = result.votes_left;
|
||||
}
|
||||
this.allowClick = true;
|
||||
this.showOptions = false;
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
@action
|
||||
removeVote() {
|
||||
const topic = this.args.topic;
|
||||
|
||||
return ajax("/voting/unvote", {
|
||||
type: "POST",
|
||||
data: {
|
||||
topic_id: topic.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
topic.vote_count = result.vote_count;
|
||||
topic.user_voted = false;
|
||||
this.currentUser.votes_exceeded = !result.can_vote;
|
||||
this.currentUser.votes_left = result.votes_left;
|
||||
this.allowClick = true;
|
||||
this.showOptions = false;
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
@action
|
||||
showVoteOptions() {
|
||||
this.showOptions = true;
|
||||
}
|
||||
|
||||
@action
|
||||
closeVoteOptions() {
|
||||
this.showOptions = false;
|
||||
}
|
||||
|
||||
@action
|
||||
closeVotesAlert() {
|
||||
this.votesAlert = null;
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
class={{concatClass
|
||||
"voting-wrapper"
|
||||
(if this.siteSettings.topic_voting_show_who_voted "show-pointer")
|
||||
}}
|
||||
>
|
||||
<VoteCount @topic={{@topic}} @showLogin={{routeAction "showLogin"}} />
|
||||
<VoteButton
|
||||
@topic={{@topic}}
|
||||
@allowClick={{this.allowClick}}
|
||||
@showVoteOptions={{this.showVoteOptions}}
|
||||
@addVote={{this.addVote}}
|
||||
@showLogin={{routeAction "showLogin"}}
|
||||
/>
|
||||
|
||||
{{#if this.showOptions}}
|
||||
<VoteOptions
|
||||
@topic={{@topic}}
|
||||
@removeVote={{this.removeVote}}
|
||||
{{closeOnClickOutside this.closeVoteOptions (hash)}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.votesAlert}}
|
||||
<div
|
||||
class="voting-popup-menu vote-options popup-menu"
|
||||
{{closeOnClickOutside this.closeVotesAlert (hash)}}
|
||||
>
|
||||
{{htmlSafe
|
||||
(i18n
|
||||
"topic_voting.votes_left"
|
||||
count=this.votesAlert
|
||||
path="/my/activity/votes"
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { applyBehaviorTransformer } from "discourse/lib/transformer";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class VoteBox extends Component {
|
||||
@service siteSettings;
|
||||
@service currentUser;
|
||||
|
||||
get wrapperClasses() {
|
||||
const classes = [];
|
||||
const { topic } = this.args;
|
||||
if (topic.closed) {
|
||||
classes.push("voting-closed");
|
||||
} else {
|
||||
if (!topic.user_voted) {
|
||||
classes.push("nonvote");
|
||||
} else {
|
||||
if (this.currentUser && this.currentUser.votes_exceeded) {
|
||||
classes.push("vote-limited nonvote");
|
||||
} else {
|
||||
classes.push("vote");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.siteSettings.topic_voting_show_who_voted) {
|
||||
classes.push("show-pointer");
|
||||
}
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
get buttonContent() {
|
||||
const { topic } = this.args;
|
||||
if (this.currentUser) {
|
||||
if (topic.closed) {
|
||||
return i18n("topic_voting.voting_closed_title");
|
||||
}
|
||||
|
||||
if (topic.user_voted) {
|
||||
return i18n("topic_voting.voted_title");
|
||||
}
|
||||
|
||||
if (this.currentUser.votes_exceeded) {
|
||||
return i18n("topic_voting.voting_limit");
|
||||
}
|
||||
|
||||
return i18n("topic_voting.vote_title");
|
||||
}
|
||||
|
||||
if (topic.vote_count) {
|
||||
return i18n("topic_voting.anonymous_button", {
|
||||
count: topic.vote_count,
|
||||
});
|
||||
}
|
||||
|
||||
return i18n("topic_voting.anonymous_button", { count: 1 });
|
||||
}
|
||||
|
||||
@action
|
||||
click() {
|
||||
applyBehaviorTransformer("topic-vote-button-click", () => {
|
||||
if (!this.currentUser) {
|
||||
cookie("destination_url", window.location.href, { path: "/" });
|
||||
this.args.showLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
const { topic } = this.args;
|
||||
|
||||
if (
|
||||
!topic.closed &&
|
||||
!topic.user_voted &&
|
||||
!this.currentUser.votes_exceeded
|
||||
) {
|
||||
this.args.addVote();
|
||||
}
|
||||
|
||||
if (topic.user_voted || this.currentUser.votes_exceeded) {
|
||||
this.args.showVoteOptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class={{this.wrapperClasses}}>
|
||||
<DButton
|
||||
@translatedTitle={{if
|
||||
this.currentUser
|
||||
(i18n
|
||||
"topic_voting.votes_left_button_title"
|
||||
count=this.currentUser.votes_left
|
||||
)
|
||||
""
|
||||
}}
|
||||
@translatedLabel={{this.buttonContent}}
|
||||
class="btn-primary vote-button"
|
||||
@action={{this.click}}
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { eq } from "truth-helpers";
|
||||
import AsyncContent from "discourse/components/async-content";
|
||||
import SmallUserList from "discourse/components/small-user-list";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { bind } from "discourse/lib/decorators";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
|
||||
|
||||
export default class VoteBox extends Component {
|
||||
@service siteSettings;
|
||||
@service currentUser;
|
||||
|
||||
@tracked showWhoVoted = false;
|
||||
|
||||
@bind
|
||||
async loadWhoVoted() {
|
||||
return ajax("/voting/who", {
|
||||
type: "GET",
|
||||
data: {
|
||||
topic_id: this.args.topic.id,
|
||||
},
|
||||
}).then((users) =>
|
||||
users.map((user) => {
|
||||
return {
|
||||
template: user.avatar_template,
|
||||
username: user.username,
|
||||
post_url: user.post_url,
|
||||
url: getURL("/u/") + user.username.toLowerCase(),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
click(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!this.currentUser) {
|
||||
cookie("destination_url", window.location.href, { path: "/" });
|
||||
this.args.showLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.showWhoVoted) {
|
||||
this.showWhoVoted = false;
|
||||
} else if (this.siteSettings.topic_voting_show_who_voted) {
|
||||
this.showWhoVoted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
clickOutside() {
|
||||
this.showWhoVoted = false;
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
class={{concatClass
|
||||
"vote-count-wrapper"
|
||||
(if (eq @topic.vote_count 0) "no-votes")
|
||||
}}
|
||||
{{on "click" this.click}}
|
||||
role="button"
|
||||
>
|
||||
<div class="vote-count">
|
||||
{{@topic.vote_count}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.showWhoVoted}}
|
||||
<div
|
||||
class="who-voted popup-menu voting-popup-menu"
|
||||
{{closeOnClickOutside
|
||||
this.clickOutside
|
||||
(hash secondaryTargetSelector=".vote-count-wrapper")
|
||||
}}
|
||||
>
|
||||
<AsyncContent @asyncData={{this.loadWhoVoted}}>
|
||||
<:content as |voters|>
|
||||
<SmallUserList @users={{voters}} class="regular-votes" />
|
||||
</:content>
|
||||
</AsyncContent>
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { service } from "@ember/service";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class VoteBox extends Component {
|
||||
@service currentUser;
|
||||
|
||||
<template>
|
||||
<div class="vote-options voting-popup-menu popup-menu" ...attributes>
|
||||
{{#if @topic.user_voted}}
|
||||
<div
|
||||
role="button"
|
||||
class="remove-vote vote-option"
|
||||
{{on "click" @removeVote}}
|
||||
>
|
||||
{{icon "xmark"}}
|
||||
{{i18n "topic_voting.remove_vote"}}
|
||||
</div>
|
||||
{{else if this.currentUser.votes_exceeded}}
|
||||
<div>{{i18n "topic_voting.reached_limit"}}</div>
|
||||
<p>
|
||||
<a href="/my/activity/votes">{{i18n "topic_voting.list_votes"}}</a>
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import Component from "@ember/component";
|
||||
import { classNames, tagName } from "@ember-decorators/component";
|
||||
import MountWidget from "discourse/components/mount-widget";
|
||||
import routeAction from "discourse/helpers/route-action";
|
||||
import VoteBox from "../../components/vote-box";
|
||||
|
||||
@tagName("div")
|
||||
@classNames("topic-above-post-stream-outlet", "topic-title-voting")
|
||||
|
@ -11,10 +11,8 @@ export default class TopicTitleVoting extends Component {
|
|||
{{#if this.model.postStream.loaded}}
|
||||
{{#if this.model.postStream.firstPostPresent}}
|
||||
<div class="voting title-voting">
|
||||
{{! template-lint-disable no-capital-arguments }}
|
||||
<MountWidget
|
||||
@widget="vote-box"
|
||||
@args={{this.model}}
|
||||
<VoteBox
|
||||
@topic={{this.model}}
|
||||
@showLogin={{routeAction "showLogin"}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import Component from "@ember/component";
|
||||
import Component from "@glimmer/component";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import { classNames, tagName } from "@ember-decorators/component";
|
||||
import { service } from "@ember/service";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
@tagName("")
|
||||
@classNames("user-activity-bottom-outlet", "user-voted-topics")
|
||||
export default class UserVotedTopics extends Component {
|
||||
@service siteSettings;
|
||||
|
||||
<template>
|
||||
{{#if this.siteSettings.topic_voting_show_votes_on_profile}}
|
||||
<LinkTo @route="userActivity.votes">
|
||||
{{icon "heart"}}
|
||||
{{i18n "topic_voting.vote_title_plural"}}
|
||||
</LinkTo>
|
||||
<li class="user-nav__activity-votes">
|
||||
|
||||
<LinkTo @route="userActivity.votes">
|
||||
{{icon "heart"}}
|
||||
{{i18n "topic_voting.vote_title_plural"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ export default {
|
|||
name: "discourse-topic-voting",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("0.8.32", (api) => {
|
||||
withPluginApi((api) => {
|
||||
const siteSettings = api.container.lookup("service:site-settings");
|
||||
if (siteSettings.topic_voting_enabled) {
|
||||
const pageSearchController = api.container.lookup(
|
||||
|
@ -57,16 +57,11 @@ export default {
|
|||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
withPluginApi("0.11.7", (api) => {
|
||||
const siteSettings = api.container.lookup("service:site-settings");
|
||||
if (siteSettings.topic_voting_enabled) {
|
||||
api.addSearchSuggestion("order:votes");
|
||||
}
|
||||
});
|
||||
|
||||
withPluginApi("2.1.0", (api) => {
|
||||
api.registerValueTransformer(
|
||||
"category-available-views",
|
||||
({ value, context }) => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
|
@ -35,6 +36,23 @@ function initialize(api) {
|
|||
},
|
||||
{ priority: -100 }
|
||||
);
|
||||
|
||||
api.modifyClass(
|
||||
"model:topic",
|
||||
(Superclass) =>
|
||||
class extends Superclass {
|
||||
@tracked vote_count;
|
||||
@tracked user_voted;
|
||||
}
|
||||
);
|
||||
api.modifyClass(
|
||||
"model:user",
|
||||
(Superclass) =>
|
||||
class extends Superclass {
|
||||
@tracked votes_exceeded;
|
||||
@tracked votes_left;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { iconNode } from "discourse/lib/icon-library";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default createWidget("remove-vote", {
|
||||
tagName: "div.remove-vote",
|
||||
|
||||
buildClasses() {
|
||||
return "vote-option";
|
||||
},
|
||||
|
||||
html() {
|
||||
return [iconNode("xmark"), i18n("topic_voting.remove_vote")];
|
||||
},
|
||||
|
||||
click() {
|
||||
this.sendWidgetAction("removeVote");
|
||||
},
|
||||
});
|
|
@ -1,104 +0,0 @@
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import RawHtml from "discourse/widgets/raw-html";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default createWidget("vote-box", {
|
||||
tagName: "div.voting-wrapper",
|
||||
buildKey: () => "vote-box",
|
||||
|
||||
buildClasses() {
|
||||
if (this.siteSettings.topic_voting_show_who_voted) {
|
||||
return "show-pointer";
|
||||
}
|
||||
},
|
||||
|
||||
defaultState() {
|
||||
return { allowClick: true, initialVote: false };
|
||||
},
|
||||
|
||||
html(attrs, state) {
|
||||
let voteCount = this.attach("vote-count", attrs);
|
||||
let voteButton = this.attach("vote-button", attrs);
|
||||
let voteOptions = this.attach("vote-options", attrs);
|
||||
let contents = [voteCount, voteButton, voteOptions];
|
||||
|
||||
if (state.votesAlert > 0) {
|
||||
const html =
|
||||
"<div class='voting-popup-menu vote-options popup-menu'>" +
|
||||
i18n("topic_voting.votes_left", {
|
||||
count: state.votesAlert,
|
||||
path: this.currentUser.get("path") + "/activity/votes",
|
||||
}) +
|
||||
"</div>";
|
||||
contents.push(new RawHtml({ html }));
|
||||
}
|
||||
|
||||
return contents;
|
||||
},
|
||||
|
||||
hideVotesAlert() {
|
||||
if (this.state.votesAlert) {
|
||||
this.state.votesAlert = null;
|
||||
this.scheduleRerender();
|
||||
}
|
||||
},
|
||||
|
||||
click() {
|
||||
this.hideVotesAlert();
|
||||
},
|
||||
|
||||
clickOutside() {
|
||||
this.hideVotesAlert();
|
||||
},
|
||||
|
||||
addVote() {
|
||||
let topic = this.attrs;
|
||||
let state = this.state;
|
||||
return ajax("/voting/vote", {
|
||||
type: "POST",
|
||||
data: {
|
||||
topic_id: topic.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
topic.set("vote_count", result.vote_count);
|
||||
topic.set("user_voted", true);
|
||||
this.currentUser.setProperties({
|
||||
votes_exceeded: !result.can_vote,
|
||||
votes_left: result.votes_left,
|
||||
});
|
||||
if (result.alert) {
|
||||
state.votesAlert = result.votes_left;
|
||||
}
|
||||
topic.set("who_voted", result.who_voted);
|
||||
state.allowClick = true;
|
||||
this.scheduleRerender();
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
removeVote() {
|
||||
let topic = this.attrs;
|
||||
let state = this.state;
|
||||
return ajax("/voting/unvote", {
|
||||
type: "POST",
|
||||
data: {
|
||||
topic_id: topic.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
topic.set("vote_count", result.vote_count);
|
||||
topic.set("user_voted", false);
|
||||
this.currentUser.setProperties({
|
||||
votes_exceeded: !result.can_vote,
|
||||
votes_left: result.votes_left,
|
||||
});
|
||||
topic.set("who_voted", result.who_voted);
|
||||
state.allowClick = true;
|
||||
this.scheduleRerender();
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
});
|
|
@ -1,101 +0,0 @@
|
|||
import { h } from "virtual-dom";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { applyBehaviorTransformer } from "discourse/lib/transformer";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default createWidget("vote-button", {
|
||||
tagName: "div",
|
||||
|
||||
buildClasses(attrs) {
|
||||
let buttonClass = "";
|
||||
if (attrs.closed) {
|
||||
buttonClass = "voting-closed";
|
||||
} else {
|
||||
if (!attrs.user_voted) {
|
||||
buttonClass = "nonvote";
|
||||
} else {
|
||||
if (this.currentUser && this.currentUser.votes_exceeded) {
|
||||
buttonClass = "vote-limited nonvote";
|
||||
} else {
|
||||
buttonClass = "vote";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.siteSettings.topic_voting_show_who_voted) {
|
||||
buttonClass += " show-pointer";
|
||||
}
|
||||
return buttonClass;
|
||||
},
|
||||
|
||||
buildButtonTitle(attrs) {
|
||||
if (this.currentUser) {
|
||||
if (attrs.closed) {
|
||||
return i18n("topic_voting.voting_closed_title");
|
||||
}
|
||||
|
||||
if (attrs.user_voted) {
|
||||
return i18n("topic_voting.voted_title");
|
||||
}
|
||||
|
||||
if (this.currentUser.votes_exceeded) {
|
||||
return i18n("topic_voting.voting_limit");
|
||||
}
|
||||
|
||||
return i18n("topic_voting.vote_title");
|
||||
}
|
||||
|
||||
if (attrs.vote_count) {
|
||||
return i18n("topic_voting.anonymous_button", {
|
||||
count: attrs.vote_count,
|
||||
});
|
||||
}
|
||||
|
||||
return i18n("topic_voting.anonymous_button", { count: 1 });
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
return h(
|
||||
"button",
|
||||
{
|
||||
attributes: {
|
||||
title: this.currentUser
|
||||
? i18n("topic_voting.votes_left_button_title", {
|
||||
count: this.currentUser.votes_left,
|
||||
})
|
||||
: "",
|
||||
},
|
||||
className: "btn btn-primary vote-button",
|
||||
},
|
||||
this.buildButtonTitle(attrs)
|
||||
);
|
||||
},
|
||||
|
||||
click() {
|
||||
applyBehaviorTransformer("topic-vote-button-click", () => {
|
||||
if (!this.currentUser) {
|
||||
this.sendWidgetAction("showLogin");
|
||||
cookie("destination_url", window.location.href, { path: "/" });
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.attrs.closed &&
|
||||
this.parentWidget.state.allowClick &&
|
||||
!this.attrs.user_voted &&
|
||||
!this.currentUser.votes_exceeded
|
||||
) {
|
||||
this.parentWidget.state.allowClick = false;
|
||||
this.parentWidget.state.initialVote = true;
|
||||
this.sendWidgetAction("addVote");
|
||||
}
|
||||
if (this.attrs.user_voted || this.currentUser.votes_exceeded) {
|
||||
document.querySelector(".vote-options").classList.toggle("hidden");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
clickOutside() {
|
||||
document.querySelector(".vote-options").classList.add("hidden");
|
||||
this.parentWidget.state.initialVote = false;
|
||||
},
|
||||
});
|
|
@ -1,90 +0,0 @@
|
|||
import { h } from "virtual-dom";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
|
||||
export default createWidget("vote-count", {
|
||||
tagName: "div.vote-count-wrapper",
|
||||
buildKey: () => "vote-count",
|
||||
|
||||
buildClasses() {
|
||||
if (this.attrs.vote_count === 0) {
|
||||
return "no-votes";
|
||||
}
|
||||
},
|
||||
|
||||
defaultState() {
|
||||
return { whoVotedUsers: null };
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
let voteCount = h("div.vote-count", attrs.vote_count.toString());
|
||||
let whoVoted = null;
|
||||
if (
|
||||
this.siteSettings.topic_voting_show_who_voted &&
|
||||
this.state.whoVotedUsers &&
|
||||
this.state.whoVotedUsers.length > 0
|
||||
) {
|
||||
whoVoted = this.attach("small-user-list", {
|
||||
users: this.state.whoVotedUsers,
|
||||
addSelf: attrs.liked,
|
||||
listClassName: "regular-votes",
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = [voteCount];
|
||||
if (whoVoted) {
|
||||
buffer.push(h("div.who-voted.popup-menu.voting-popup-menu", [whoVoted]));
|
||||
}
|
||||
return buffer;
|
||||
},
|
||||
|
||||
click() {
|
||||
if (!this.currentUser) {
|
||||
this.sendWidgetAction("showLogin");
|
||||
cookie("destination_url", window.location.href, { path: "/" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.siteSettings.topic_voting_show_who_voted &&
|
||||
this.attrs.vote_count > 0
|
||||
) {
|
||||
if (this.state.whoVotedUsers === null) {
|
||||
return this.getWhoVoted();
|
||||
} else {
|
||||
const whoVotedElement = document.querySelector(".who-voted");
|
||||
whoVotedElement.style.display =
|
||||
whoVotedElement.style.display === "none" ? "block" : "none";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clickOutside() {
|
||||
const whoVotedElement = document.querySelector(".who-voted");
|
||||
if (whoVotedElement) {
|
||||
whoVotedElement.style.display = "none";
|
||||
}
|
||||
},
|
||||
|
||||
getWhoVoted() {
|
||||
return ajax("/voting/who", {
|
||||
type: "GET",
|
||||
data: {
|
||||
topic_id: this.attrs.id,
|
||||
},
|
||||
}).then((users) => {
|
||||
this.state.whoVotedUsers = users.map(whoVotedAvatars);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function whoVotedAvatars(user) {
|
||||
return {
|
||||
template: user.avatar_template,
|
||||
username: user.username,
|
||||
post_url: user.post_url,
|
||||
url: getURL("/u/") + user.username.toLowerCase(),
|
||||
};
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
import { h } from "virtual-dom";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default createWidget("vote-options", {
|
||||
tagName: "div.vote-options",
|
||||
|
||||
buildClasses() {
|
||||
return "voting-popup-menu popup-menu hidden";
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
let contents = [];
|
||||
|
||||
if (attrs.user_voted) {
|
||||
contents.push(this.attach("remove-vote", attrs));
|
||||
} else if (
|
||||
this.currentUser &&
|
||||
this.currentUser.votes_exceeded &&
|
||||
!attrs.user_voted
|
||||
) {
|
||||
contents.push([
|
||||
h("div", i18n("topic_voting.reached_limit")),
|
||||
h(
|
||||
"p",
|
||||
h(
|
||||
"a",
|
||||
{ href: this.currentUser.get("path") + "/activity/votes" },
|
||||
i18n("topic_voting.list_votes")
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
return contents;
|
||||
},
|
||||
});
|
|
@ -16,31 +16,23 @@ RSpec.describe "Topic voting", type: :system, js: true do
|
|||
fab!(:admin_page) { PageObjects::Pages::AdminSiteSettings.new }
|
||||
|
||||
before do
|
||||
SiteSetting.topic_voting_enabled = false
|
||||
|
||||
admin.activate
|
||||
user.activate
|
||||
|
||||
SiteSetting.topic_voting_enabled = true
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
skip "enables voting in category topics and votes" do
|
||||
it "enables voting in category topics and votes" do
|
||||
category_page.visit(category1)
|
||||
expect(category_page).to have_no_css(category_page.votes)
|
||||
|
||||
# enables voting
|
||||
admin_page.visit_filtered_plugin_setting("topic%20voting%20enabled").toggle_setting(
|
||||
"topic_voting_enabled",
|
||||
"Allow users to vote on topics?",
|
||||
)
|
||||
|
||||
# enable voting in category
|
||||
category_page
|
||||
.visit_settings(category1)
|
||||
.toggle_setting("enable-topic-voting", "Allow users to vote on topics in this category")
|
||||
.save_settings
|
||||
.back_to_category
|
||||
|
||||
# voting
|
||||
try_until_success { expect(Category.can_vote?(category1.id)).to eq(true) }
|
||||
|
||||
# make a vote
|
||||
category_page.visit(category1)
|
||||
expect(category_page).to have_css(category_page.votes)
|
||||
expect(category_page).to have_css(category_page.topic_with_vote_count(0), count: 2)
|
||||
|
@ -50,14 +42,12 @@ RSpec.describe "Topic voting", type: :system, js: true do
|
|||
topic_page.vote
|
||||
expect(topic_page.vote_popup).to have_text("You have 9 votes left, see your votes")
|
||||
expect(topic_page.vote_count).to have_text("1")
|
||||
topic_page.click_vote_popup_activity
|
||||
|
||||
# visit user activity page
|
||||
topic_page.click_vote_popup_activity
|
||||
expect(user_page.active_user_primary_navigation).to have_text("Activity")
|
||||
expect(user_page.active_user_secondary_navigation).to have_text("Votes")
|
||||
expect(page).to have_css(".topic-list-body tr[data-topic-id=\"#{topic1.id}\"]")
|
||||
expect(find(".topic-list-body tr[data-topic-id=\"#{topic1.id}\"] a.voted")).to have_text(
|
||||
"1 vote",
|
||||
)
|
||||
expect(page).to have_css(".topic-list-body tr[data-topic-id=\"#{topic1.id}\"]", text: "1 vote")
|
||||
find(".topic-list-body tr[data-topic-id=\"#{topic1.id}\"] a.raw-link").click
|
||||
|
||||
# unvoting
|
||||
|
|
Loading…
Reference in New Issue