DEV: Remove widget wrapper from poll plugin (#28666)
- Uses `helper.renderGlimmer` with GJS to render the `<Poll` component without any widgets - Moves some logic into component, so that only `@post`, `@poll` and `@titleHTML` need to be passed into the component (no more 'attrs') - Updates `modifyClass` calls to modern syntax - Replaces observer in `Post` model with a native setter & tracked property - Replaced Poll EmberObject instances with TrackedObject - Updated component tests with new arguments - Updated some tests to qunit-dom - Fixed up core `repliesBelow` and `repliesAbove` logic to create post models properly. Previously it was passing 'transformed' versions of posts into the model, which doesn't make sense.
This commit is contained in:
parent
a2f625a0ef
commit
e6edd52047
|
@ -618,16 +618,8 @@ createWidget("post-contents", {
|
|||
this.state.repliesBelow = posts.map((p) => {
|
||||
let result = transformWithCallbacks(p);
|
||||
|
||||
// these would conflict with computed properties with identical names
|
||||
// in the post model if we kept them.
|
||||
delete result.new_user;
|
||||
delete result.deleted;
|
||||
delete result.shareUrl;
|
||||
delete result.firstPost;
|
||||
delete result.usernameUrl;
|
||||
|
||||
result.customShare = `${topicUrl}/${p.post_number}`;
|
||||
result.asPost = this.store.createRecord("post", result);
|
||||
result.asPost = this.store.createRecord("post", p);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
@ -882,18 +874,8 @@ createWidget("post-article", {
|
|||
this.state.repliesAbove = posts.map((p) => {
|
||||
let result = transformWithCallbacks(p);
|
||||
|
||||
// We don't want to overwrite CPs - we are doing something a bit weird
|
||||
// here by creating a post object from a transformed post. They aren't
|
||||
// 100% the same.
|
||||
delete result.new_user;
|
||||
delete result.deleted;
|
||||
delete result.shareUrl;
|
||||
delete result.firstPost;
|
||||
delete result.usernameUrl;
|
||||
delete result.topicNotificationLevel;
|
||||
|
||||
result.customShare = `${topicUrl}/${p.post_number}`;
|
||||
result.asPost = this.store.createRecord("post", result);
|
||||
result.asPost = this.store.createRecord("post", p);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import I18n from "discourse-i18n";
|
|||
|
||||
export default class PollBreakdownModal extends Component {
|
||||
@service dialog;
|
||||
@service siteSettings;
|
||||
|
||||
model = null;
|
||||
charts = null;
|
||||
|
@ -19,7 +20,7 @@ export default class PollBreakdownModal extends Component {
|
|||
displayMode = "percentage";
|
||||
|
||||
init() {
|
||||
this.set("groupedBy", this.model.groupableUserFields[0]);
|
||||
this.set("groupedBy", this.groupableUserFields[0]?.id);
|
||||
loadScript("/javascripts/Chart.min.js")
|
||||
.then(() => loadScript("/javascripts/chartjs-plugin-datalabels.min.js"))
|
||||
.then(() => {
|
||||
|
@ -33,17 +34,19 @@ export default class PollBreakdownModal extends Component {
|
|||
return pollTitle ? htmlSafe(pollTitle) : topicTitle;
|
||||
}
|
||||
|
||||
@discourseComputed("model.groupableUserFields")
|
||||
groupableUserFields(fields) {
|
||||
return fields.map((field) => {
|
||||
const transformed = field.split("_").filter(Boolean);
|
||||
get groupableUserFields() {
|
||||
return this.siteSettings.poll_groupable_user_fields
|
||||
.split("|")
|
||||
.filter(Boolean)
|
||||
.map((field) => {
|
||||
const transformed = field.split("_").filter(Boolean);
|
||||
|
||||
if (transformed.length > 1) {
|
||||
transformed[0] = classify(transformed[0]);
|
||||
}
|
||||
if (transformed.length > 1) {
|
||||
transformed[0] = classify(transformed[0]);
|
||||
}
|
||||
|
||||
return { id: field, label: transformed.join(" ") };
|
||||
});
|
||||
return { id: field, label: transformed.join(" ") };
|
||||
});
|
||||
}
|
||||
|
||||
@discourseComputed("model.poll.options")
|
||||
|
|
|
@ -42,11 +42,10 @@ export default class PollComponent extends Component {
|
|||
@service dialog;
|
||||
@service modal;
|
||||
|
||||
@tracked vote = this.args.attrs.vote || [];
|
||||
@tracked poll = this.args.attrs.poll;
|
||||
@tracked vote = this.args.post.polls_votes?.[this.args.poll.name] || [];
|
||||
@tracked preloadedVoters = this.defaultPreloadedVoters();
|
||||
@tracked voterListExpanded = false;
|
||||
@tracked hasSavedVote = this.args.attrs.hasSavedVote;
|
||||
@tracked hasSavedVote = this.vote.length > 0;
|
||||
|
||||
@tracked
|
||||
showResults =
|
||||
|
@ -123,13 +122,17 @@ export default class PollComponent extends Component {
|
|||
this.vote = [...this.vote];
|
||||
};
|
||||
|
||||
get poll() {
|
||||
return this.args.poll;
|
||||
}
|
||||
|
||||
defaultPreloadedVoters() {
|
||||
const preloadedVoters = {};
|
||||
|
||||
if (this.poll.public && this.args.preloadedVoters) {
|
||||
Object.keys(this.args.preloadedVoters).forEach((key) => {
|
||||
if (this.poll.public && this.poll.preloaded_voters) {
|
||||
Object.keys(this.poll.preloaded_voters).forEach((key) => {
|
||||
preloadedVoters[key] = {
|
||||
voters: this.args.preloadedVoters[key],
|
||||
voters: this.poll.preloaded_voters[key],
|
||||
loading: false,
|
||||
};
|
||||
});
|
||||
|
@ -148,15 +151,17 @@ export default class PollComponent extends Component {
|
|||
}
|
||||
|
||||
get id() {
|
||||
return this.args.attrs.id;
|
||||
return `${this.args.poll.name}-${this.args.post.id}`;
|
||||
}
|
||||
|
||||
get post() {
|
||||
return this.args.attrs.post;
|
||||
return this.args.post;
|
||||
}
|
||||
|
||||
get groupableUserFields() {
|
||||
return this.args.attrs.groupableUserFields;
|
||||
return this.siteSettings.poll_groupable_user_fields
|
||||
.split("|")
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
get isStaff() {
|
||||
|
@ -164,7 +169,7 @@ export default class PollComponent extends Component {
|
|||
}
|
||||
|
||||
get titleHTML() {
|
||||
return htmlSafe(this.args.attrs.titleHTML);
|
||||
return htmlSafe(this.args.titleHTML);
|
||||
}
|
||||
|
||||
get topicArchived() {
|
||||
|
@ -192,7 +197,7 @@ export default class PollComponent extends Component {
|
|||
}
|
||||
|
||||
get status() {
|
||||
return this.poll.get("status");
|
||||
return this.poll.status;
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -206,7 +211,7 @@ export default class PollComponent extends Component {
|
|||
}
|
||||
|
||||
try {
|
||||
const poll = await ajax("/polls/vote", {
|
||||
const { poll } = await ajax("/polls/vote", {
|
||||
type: "PUT",
|
||||
data: {
|
||||
post_id: this.post.id,
|
||||
|
@ -216,7 +221,8 @@ export default class PollComponent extends Component {
|
|||
});
|
||||
|
||||
this.hasSavedVote = true;
|
||||
this.poll.setProperties(poll);
|
||||
Object.assign(this.poll, poll);
|
||||
|
||||
this.appEvents.trigger("poll:voted", poll, this.post, this.vote);
|
||||
|
||||
if (this.poll.results !== ON_CLOSE) {
|
||||
|
@ -243,7 +249,7 @@ export default class PollComponent extends Component {
|
|||
}
|
||||
|
||||
get options() {
|
||||
let enrichedOptions = this.poll.get("options");
|
||||
let enrichedOptions = this.poll.options;
|
||||
|
||||
if (this.isRankedChoice) {
|
||||
enrichedOptions.forEach((candidate) => {
|
||||
|
@ -262,11 +268,11 @@ export default class PollComponent extends Component {
|
|||
}
|
||||
|
||||
get voters() {
|
||||
return this.poll.get("voters");
|
||||
return this.poll.voters;
|
||||
}
|
||||
|
||||
get rankedChoiceOutcome() {
|
||||
return this.poll.get("ranked_choice_outcome") || [];
|
||||
return this.poll.ranked_choice_outcome || [];
|
||||
}
|
||||
|
||||
get min() {
|
||||
|
@ -574,7 +580,7 @@ export default class PollComponent extends Component {
|
|||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.poll.set("status", status);
|
||||
this.poll.status = status;
|
||||
|
||||
if (
|
||||
this.poll.results === ON_CLOSE ||
|
||||
|
@ -606,7 +612,10 @@ export default class PollComponent extends Component {
|
|||
@action
|
||||
showBreakdown() {
|
||||
this.modal.show(PollBreakdownModal, {
|
||||
model: this.args.attrs,
|
||||
model: {
|
||||
poll: this.poll,
|
||||
post: this.post,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -660,121 +669,129 @@ export default class PollComponent extends Component {
|
|||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
{{didUpdate this.updatedVoters @preloadedVoters}}
|
||||
class="poll-container"
|
||||
>
|
||||
{{this.titleHTML}}
|
||||
{{#if this.notInVotingGroup}}
|
||||
<div class="alert alert-danger">{{this.pollGroups}}</div>
|
||||
{{/if}}
|
||||
{{#if this.showResults}}
|
||||
<div class={{this.resultsWidgetTypeClass}}>
|
||||
{{#if this.isNumber}}
|
||||
<span>{{this.averageRating}}</span>
|
||||
{{else}}
|
||||
{{#if this.resultsPie}}
|
||||
<PollResultsPie @id={{this.id}} @options={{this.options}} />
|
||||
<div class="poll">
|
||||
<div
|
||||
{{didUpdate this.updatedVoters this.poll.preloaded_voters}}
|
||||
class="poll-container"
|
||||
>
|
||||
{{this.titleHTML}}
|
||||
{{#if this.notInVotingGroup}}
|
||||
<div class="alert alert-danger">{{this.pollGroups}}</div>
|
||||
{{/if}}
|
||||
{{#if this.showResults}}
|
||||
<div class={{this.resultsWidgetTypeClass}}>
|
||||
{{#if this.isNumber}}
|
||||
<span>{{this.averageRating}}</span>
|
||||
{{else}}
|
||||
<PollResultsTabs
|
||||
@options={{this.options}}
|
||||
@pollName={{this.poll.name}}
|
||||
@pollType={{this.poll.type}}
|
||||
@isRankedChoice={{this.isRankedChoice}}
|
||||
@isPublic={{this.poll.public}}
|
||||
@postId={{this.post.id}}
|
||||
@vote={{this.vote}}
|
||||
@voters={{this.preloadedVoters}}
|
||||
@votersCount={{this.poll.voters}}
|
||||
@fetchVoters={{this.fetchVoters}}
|
||||
@rankedChoiceOutcome={{this.rankedChoiceOutcome}}
|
||||
@showTally={{this.showTally}}
|
||||
/>
|
||||
{{#if this.resultsPie}}
|
||||
<PollResultsPie @id={{this.id}} @options={{this.options}} />
|
||||
{{else}}
|
||||
<PollResultsTabs
|
||||
@options={{this.options}}
|
||||
@pollName={{this.poll.name}}
|
||||
@pollType={{this.poll.type}}
|
||||
@isRankedChoice={{this.isRankedChoice}}
|
||||
@isPublic={{this.poll.public}}
|
||||
@postId={{this.post.id}}
|
||||
@vote={{this.vote}}
|
||||
@voters={{this.preloadedVoters}}
|
||||
@votersCount={{this.poll.voters}}
|
||||
@fetchVoters={{this.fetchVoters}}
|
||||
@rankedChoiceOutcome={{this.rankedChoiceOutcome}}
|
||||
@showTally={{this.showTally}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<PollOptions
|
||||
@isCheckbox={{this.isCheckbox}}
|
||||
@isRankedChoice={{this.isRankedChoice}}
|
||||
@options={{this.options}}
|
||||
@votes={{this.vote}}
|
||||
@sendOptionSelect={{this.toggleOption}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
<PollInfo
|
||||
@options={{this.options}}
|
||||
@min={{this.min}}
|
||||
@max={{this.max}}
|
||||
@isMultiple={{this.isMultiple}}
|
||||
@close={{this.close}}
|
||||
@closed={{this.closed}}
|
||||
@results={{this.poll.results}}
|
||||
@showResults={{this.showResults}}
|
||||
@postUserId={{this.poll.post.user_id}}
|
||||
@isPublic={{this.poll.public}}
|
||||
@hasVoted={{this.hasVoted}}
|
||||
@voters={{this.voters}}
|
||||
/>
|
||||
<div class="poll-buttons">
|
||||
{{#if this.showCastVotesButton}}
|
||||
<button
|
||||
class={{this.castVotesButtonClass}}
|
||||
title="poll.cast-votes.title"
|
||||
disabled={{this.castVotesDisabled}}
|
||||
{{on "click" this.castVotes}}
|
||||
>
|
||||
{{icon this.castVotesButtonIcon}}
|
||||
<span class="d-button-label">{{i18n "poll.cast-votes.label"}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showHideResultsButton}}
|
||||
<button
|
||||
class="btn btn-default toggle-results"
|
||||
title="poll.hide-results.title"
|
||||
{{on "click" this.toggleResults}}
|
||||
>
|
||||
{{icon "chevron-left"}}
|
||||
<span class="d-button-label">{{i18n "poll.hide-results.label"}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showShowResultsButton}}
|
||||
<button
|
||||
class="btn btn-default toggle-results"
|
||||
title="poll.show-results.title"
|
||||
{{on "click" this.toggleResults}}
|
||||
>
|
||||
{{icon "chart-bar"}}
|
||||
<span class="d-button-label">{{i18n "poll.show-results.label"}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showRemoveVoteButton}}
|
||||
<button
|
||||
class="btn btn-default remove-vote"
|
||||
title="poll.remove-vote.title"
|
||||
{{on "click" this.removeVote}}
|
||||
>
|
||||
{{icon "undo"}}
|
||||
<span class="d-button-label">{{i18n "poll.remove-vote.label"}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
<PollButtonsDropdown
|
||||
</div>
|
||||
{{else}}
|
||||
<PollOptions
|
||||
@isCheckbox={{this.isCheckbox}}
|
||||
@isRankedChoice={{this.isRankedChoice}}
|
||||
@options={{this.options}}
|
||||
@votes={{this.vote}}
|
||||
@sendOptionSelect={{this.toggleOption}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
<PollInfo
|
||||
@options={{this.options}}
|
||||
@min={{this.min}}
|
||||
@max={{this.max}}
|
||||
@isMultiple={{this.isMultiple}}
|
||||
@close={{this.close}}
|
||||
@closed={{this.closed}}
|
||||
@results={{this.poll.results}}
|
||||
@showResults={{this.showResults}}
|
||||
@postUserId={{this.poll.post.user_id}}
|
||||
@isPublic={{this.poll.public}}
|
||||
@hasVoted={{this.hasVoted}}
|
||||
@voters={{this.voters}}
|
||||
@isStaff={{this.isStaff}}
|
||||
@isMe={{this.isMe}}
|
||||
@isRankedChoice={{this.isRankedChoice}}
|
||||
@topicArchived={{this.topicArchived}}
|
||||
@groupableUserFields={{this.groupableUserFields}}
|
||||
@isAutomaticallyClosed={{this.isAutomaticallyClosed}}
|
||||
@dropDownClick={{this.dropDownClick}}
|
||||
@availableDisplayMode={{this.availableDisplayMode}}
|
||||
/>
|
||||
<div class="poll-buttons">
|
||||
{{#if this.showCastVotesButton}}
|
||||
<button
|
||||
class={{this.castVotesButtonClass}}
|
||||
title="poll.cast-votes.title"
|
||||
disabled={{this.castVotesDisabled}}
|
||||
{{on "click" this.castVotes}}
|
||||
>
|
||||
{{icon this.castVotesButtonIcon}}
|
||||
<span class="d-button-label">{{i18n "poll.cast-votes.label"}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showHideResultsButton}}
|
||||
<button
|
||||
class="btn btn-default toggle-results"
|
||||
title="poll.hide-results.title"
|
||||
{{on "click" this.toggleResults}}
|
||||
>
|
||||
{{icon "chevron-left"}}
|
||||
<span class="d-button-label">{{i18n
|
||||
"poll.hide-results.label"
|
||||
}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showShowResultsButton}}
|
||||
<button
|
||||
class="btn btn-default toggle-results"
|
||||
title="poll.show-results.title"
|
||||
{{on "click" this.toggleResults}}
|
||||
>
|
||||
{{icon "chart-bar"}}
|
||||
<span class="d-button-label">{{i18n
|
||||
"poll.show-results.label"
|
||||
}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showRemoveVoteButton}}
|
||||
<button
|
||||
class="btn btn-default remove-vote"
|
||||
title="poll.remove-vote.title"
|
||||
{{on "click" this.removeVote}}
|
||||
>
|
||||
{{icon "undo"}}
|
||||
<span class="d-button-label">{{i18n
|
||||
"poll.remove-vote.label"
|
||||
}}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
<PollButtonsDropdown
|
||||
@closed={{this.closed}}
|
||||
@voters={{this.voters}}
|
||||
@isStaff={{this.isStaff}}
|
||||
@isMe={{this.isMe}}
|
||||
@isRankedChoice={{this.isRankedChoice}}
|
||||
@topicArchived={{this.topicArchived}}
|
||||
@groupableUserFields={{this.groupableUserFields}}
|
||||
@isAutomaticallyClosed={{this.isAutomaticallyClosed}}
|
||||
@dropDownClick={{this.dropDownClick}}
|
||||
@availableDisplayMode={{this.availableDisplayMode}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import EmberObject from "@ember/object";
|
||||
import { TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { PIE_CHART_TYPE } from "../components/modal/poll-ui-builder";
|
||||
import Poll from "../components/poll";
|
||||
|
||||
function attachPolls(elem, helper) {
|
||||
let pollNodes = [...elem.querySelectorAll(".poll")];
|
||||
pollNodes = pollNodes.filter(
|
||||
(node) => node.parentNode.tagName !== "BLOCKQUOTE"
|
||||
);
|
||||
if (!pollNodes.length || !helper) {
|
||||
return;
|
||||
}
|
||||
|
||||
const post = helper.getModel();
|
||||
const polls = post.pollsObject;
|
||||
|
||||
pollNodes.forEach((pollNode) => {
|
||||
const pollName = pollNode.dataset.pollName;
|
||||
let poll = polls[pollName];
|
||||
let pollPost = post;
|
||||
|
||||
const quotedId = pollNode.closest(".expanded-quote")?.dataset.postId;
|
||||
if (quotedId && post.quoted[quotedId]) {
|
||||
pollPost = EmberObject.create(post.quoted[quotedId]);
|
||||
poll = new TrackedObject(pollPost.polls.find((p) => p.name === pollName));
|
||||
}
|
||||
|
||||
if (poll) {
|
||||
const titleHTML = pollNode.querySelector(".poll-title")?.outerHTML;
|
||||
|
||||
const newPollNode = document.createElement("div");
|
||||
Object.assign(newPollNode.dataset, {
|
||||
pollName: poll.name,
|
||||
pollType: poll.type,
|
||||
});
|
||||
newPollNode.classList.add("poll-outer");
|
||||
if (poll.chart_type === PIE_CHART_TYPE) {
|
||||
newPollNode.classList.add("pie");
|
||||
}
|
||||
|
||||
pollNode.replaceWith(newPollNode);
|
||||
helper.renderGlimmer(newPollNode, <template>
|
||||
<Poll @poll={{poll}} @post={{post}} @titleHTML={{titleHTML}} />
|
||||
</template>);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializePolls(api) {
|
||||
api.modifyClass(
|
||||
"controller:topic",
|
||||
(Superclass) =>
|
||||
class extends Superclass {
|
||||
subscribe() {
|
||||
super.subscribe(...arguments);
|
||||
this.messageBus.subscribe(
|
||||
`/polls/${this.model.id}`,
|
||||
this._onPollMessage
|
||||
);
|
||||
}
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/polls/*", this._onPollMessage);
|
||||
super.unsubscribe(...arguments);
|
||||
}
|
||||
|
||||
@bind
|
||||
_onPollMessage(msg) {
|
||||
const post = this.get("model.postStream").findLoadedPost(msg.post_id);
|
||||
if (post) {
|
||||
post.polls = msg.polls;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
api.modifyClass(
|
||||
"model:post",
|
||||
(Superclass) =>
|
||||
class extends Superclass {
|
||||
@tracked pollsObject = new TrackedObject();
|
||||
@tracked _polls;
|
||||
|
||||
get polls() {
|
||||
return this._polls;
|
||||
}
|
||||
|
||||
set polls(value) {
|
||||
this._polls = value;
|
||||
this._refreshPollsObject();
|
||||
}
|
||||
|
||||
_refreshPollsObject() {
|
||||
for (const rawPoll of this.polls) {
|
||||
const name = rawPoll.name;
|
||||
this.pollsObject[name] ||= new TrackedObject();
|
||||
Object.assign(this.pollsObject[name], rawPoll);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
api.decorateCookedElement(attachPolls, { onlyStream: true });
|
||||
|
||||
const siteSettings = api.container.lookup("service:site-settings");
|
||||
if (siteSettings.poll_enabled) {
|
||||
api.addSearchSuggestion("in:polls");
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "extend-for-poll",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("0.8.7", initializePolls);
|
||||
},
|
||||
};
|
|
@ -1,150 +0,0 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import WidgetGlue from "discourse/widgets/glue";
|
||||
import { getRegister } from "discourse-common/lib/get-owner";
|
||||
import { bind, observes } from "discourse-common/utils/decorators";
|
||||
|
||||
const PLUGIN_ID = "discourse-poll";
|
||||
let _glued = [];
|
||||
let _interval = null;
|
||||
|
||||
function rerender() {
|
||||
_glued.forEach((g) => g.queueRerender());
|
||||
}
|
||||
|
||||
function cleanUpPolls() {
|
||||
if (_interval) {
|
||||
clearInterval(_interval);
|
||||
_interval = null;
|
||||
}
|
||||
|
||||
_glued.forEach((g) => g.cleanUp());
|
||||
_glued = [];
|
||||
}
|
||||
|
||||
function initializePolls(api) {
|
||||
const register = getRegister(api),
|
||||
pollGroupableUserFields = api.container.lookup(
|
||||
"service:site-settings"
|
||||
).poll_groupable_user_fields;
|
||||
cleanUpPolls();
|
||||
|
||||
api.modifyClass("controller:topic", {
|
||||
pluginId: PLUGIN_ID,
|
||||
|
||||
subscribe() {
|
||||
this._super(...arguments);
|
||||
this.messageBus.subscribe(`/polls/${this.model.id}`, this._onPollMessage);
|
||||
},
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/polls/*", this._onPollMessage);
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
@bind
|
||||
_onPollMessage(msg) {
|
||||
const post = this.get("model.postStream").findLoadedPost(msg.post_id);
|
||||
post?.set("polls", msg.polls);
|
||||
},
|
||||
});
|
||||
|
||||
api.modifyClass("model:post", {
|
||||
pluginId: PLUGIN_ID,
|
||||
_polls: null,
|
||||
pollsObject: null,
|
||||
|
||||
// we need a proper ember object so it is bindable
|
||||
@observes("polls")
|
||||
pollsChanged() {
|
||||
const polls = this.polls;
|
||||
if (polls) {
|
||||
this._polls = this._polls || {};
|
||||
polls.forEach((p) => {
|
||||
const existing = this._polls[p.name];
|
||||
if (existing) {
|
||||
this._polls[p.name].setProperties(p);
|
||||
} else {
|
||||
this._polls[p.name] = EmberObject.create(p);
|
||||
}
|
||||
});
|
||||
this.set("pollsObject", this._polls);
|
||||
rerender();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function attachPolls(elem, helper) {
|
||||
let pollNodes = [...elem.querySelectorAll(".poll")];
|
||||
pollNodes = pollNodes.filter(
|
||||
(node) => node.parentNode.tagName !== "BLOCKQUOTE"
|
||||
);
|
||||
if (!pollNodes.length || !helper) {
|
||||
return;
|
||||
}
|
||||
|
||||
const post = helper.getModel();
|
||||
api.preventCloak(post.id);
|
||||
post.pollsChanged();
|
||||
|
||||
const polls = post.pollsObject || {};
|
||||
const votes = post.polls_votes || {};
|
||||
|
||||
_interval = _interval || setInterval(rerender, 30000);
|
||||
|
||||
pollNodes.forEach((pollNode) => {
|
||||
const pollName = pollNode.dataset.pollName;
|
||||
let poll = polls[pollName];
|
||||
let pollPost = post;
|
||||
let vote = votes[pollName] || [];
|
||||
|
||||
const quotedId = pollNode.closest(".expanded-quote")?.dataset.postId;
|
||||
if (quotedId && post.quoted[quotedId]) {
|
||||
pollPost = post.quoted[quotedId];
|
||||
pollPost = EmberObject.create(pollPost);
|
||||
poll = EmberObject.create(pollPost.polls.findBy("name", pollName));
|
||||
vote = pollPost.polls_votes || {};
|
||||
vote = vote[pollName] || [];
|
||||
}
|
||||
|
||||
if (poll) {
|
||||
const titleElement = pollNode.querySelector(".poll-title");
|
||||
|
||||
const attrs = {
|
||||
id: `${pollName}-${pollPost.id}`,
|
||||
post: pollPost,
|
||||
poll,
|
||||
vote,
|
||||
hasSavedVote: vote.length > 0,
|
||||
titleHTML: titleElement?.outerHTML,
|
||||
groupableUserFields: (pollGroupableUserFields || "")
|
||||
.split("|")
|
||||
.filter(Boolean),
|
||||
_postCookedWidget: helper.widget,
|
||||
};
|
||||
const glue = new WidgetGlue("discourse-poll", register, attrs);
|
||||
glue.appendTo(pollNode);
|
||||
_glued.push(glue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
api.includePostAttributes("polls", "polls_votes");
|
||||
api.decorateCookedElement(attachPolls, {
|
||||
onlyStream: true,
|
||||
});
|
||||
api.cleanupStream(cleanUpPolls);
|
||||
|
||||
const siteSettings = api.container.lookup("service:site-settings");
|
||||
if (siteSettings.poll_enabled) {
|
||||
api.addSearchSuggestion("in:polls");
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "extend-for-poll",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("0.8.7", initializePolls);
|
||||
},
|
||||
};
|
|
@ -1,36 +0,0 @@
|
|||
import { hbs } from "ember-cli-htmlbars";
|
||||
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
import { PIE_CHART_TYPE } from "../components/modal/poll-ui-builder";
|
||||
|
||||
export default createWidget("discourse-poll", {
|
||||
tagName: "div",
|
||||
buildKey: (attrs) => `poll-${attrs.id}`,
|
||||
services: ["dialog"],
|
||||
|
||||
buildAttributes(attrs) {
|
||||
let cssClasses = "poll-outer";
|
||||
if (attrs.poll.chart_type === PIE_CHART_TYPE) {
|
||||
cssClasses += " pie";
|
||||
}
|
||||
return {
|
||||
class: cssClasses,
|
||||
"data-poll-name": attrs.poll.name,
|
||||
"data-poll-type": attrs.poll.type,
|
||||
};
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
return [
|
||||
new RenderGlimmer(
|
||||
this,
|
||||
"div.poll",
|
||||
hbs`<Poll @attrs={{@data.attrs}} @preloadedVoters={{@data.preloadedVoters}} />`,
|
||||
{
|
||||
attrs,
|
||||
preloadedVoters: attrs.poll.preloaded_voters,
|
||||
}
|
||||
),
|
||||
];
|
||||
},
|
||||
});
|
|
@ -1,15 +1,10 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import { click, render } from "@ember/test-helpers";
|
||||
import { TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
import hbs from "htmlbars-inline-precompile";
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import pretender, { response } from "discourse/tests/helpers/create-pretender";
|
||||
import {
|
||||
count,
|
||||
exists,
|
||||
query,
|
||||
queryAll,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
let requests = 0;
|
||||
|
@ -48,112 +43,86 @@ module("Poll | Component | poll", function (hooks) {
|
|||
|
||||
test("shows vote", async function (assert) {
|
||||
this.setProperties({
|
||||
attributes: EmberObject.create({
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: EmberObject.create({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "closed",
|
||||
results: "always",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 1,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
vote: [],
|
||||
groupableUserFields: [],
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: new TrackedObject({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "closed",
|
||||
results: "always",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 1,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
preloadedVoters: [],
|
||||
});
|
||||
|
||||
await render(
|
||||
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
|
||||
);
|
||||
await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
|
||||
|
||||
assert.deepEqual(
|
||||
Array.from(queryAll(".results li .option p")).map(
|
||||
(span) => span.innerText
|
||||
),
|
||||
["100% yes", "0% no"]
|
||||
);
|
||||
assert.dom(".results li:nth-of-type(1) .option p").hasText("100% yes");
|
||||
assert.dom(".results li:nth-of-type(2) .option p").hasText("0% no");
|
||||
});
|
||||
|
||||
test("does not show results after voting when results are to be shown only on closed", async function (assert) {
|
||||
this.setProperties({
|
||||
attributes: EmberObject.create({
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
hasSavedVote: true,
|
||||
poll: EmberObject.create({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "open",
|
||||
results: "on_close",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes" },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no" },
|
||||
],
|
||||
voters: 1,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
vote: ["1f972d1df351de3ce35a787c89faad29"],
|
||||
groupableUserFields: [],
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: new TrackedObject({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "open",
|
||||
results: "on_close",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes" },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no" },
|
||||
],
|
||||
voters: 1,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
preloadedVoters: [],
|
||||
});
|
||||
|
||||
await render(
|
||||
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
|
||||
);
|
||||
await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
|
||||
|
||||
assert.ok(exists("ul.options"), "options are shown");
|
||||
assert.ok(!exists("ul.results"), "results are not shown");
|
||||
assert.dom("ul.options").exists("options are shown");
|
||||
assert.dom("ul.results").doesNotExist("results are not shown");
|
||||
});
|
||||
|
||||
test("can vote", async function (assert) {
|
||||
this.setProperties({
|
||||
attributes: EmberObject.create({
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: EmberObject.create({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "open",
|
||||
results: "always",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 0,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
vote: [],
|
||||
groupableUserFields: [],
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: new TrackedObject({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "open",
|
||||
results: "always",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 0,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
preloadedVoters: [],
|
||||
});
|
||||
|
||||
await render(
|
||||
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
|
||||
);
|
||||
await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
|
||||
|
||||
requests = 0;
|
||||
|
||||
|
@ -161,100 +130,84 @@ module("Poll | Component | poll", function (hooks) {
|
|||
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button"
|
||||
);
|
||||
assert.strictEqual(requests, 1);
|
||||
assert.strictEqual(count(".chosen"), 1);
|
||||
assert.dom(".chosen").exists({ count: 1 });
|
||||
|
||||
await click(".toggle-results");
|
||||
assert.strictEqual(
|
||||
count("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']"),
|
||||
1
|
||||
);
|
||||
assert
|
||||
.dom("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']")
|
||||
.exists({ count: 1 });
|
||||
});
|
||||
|
||||
test("cannot vote if not member of the right group", async function (assert) {
|
||||
this.setProperties({
|
||||
attributes: EmberObject.create({
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: EmberObject.create({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "open",
|
||||
results: "always",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 0,
|
||||
chart_type: "bar",
|
||||
groups: "foo",
|
||||
}),
|
||||
vote: [],
|
||||
groupableUserFields: [],
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: new TrackedObject({
|
||||
name: "poll",
|
||||
type: "regular",
|
||||
status: "open",
|
||||
results: "always",
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 0,
|
||||
chart_type: "bar",
|
||||
groups: "foo",
|
||||
}),
|
||||
preloadedVoters: [],
|
||||
});
|
||||
|
||||
await render(
|
||||
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
|
||||
);
|
||||
await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
|
||||
|
||||
requests = 0;
|
||||
|
||||
await click(
|
||||
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".poll-container .alert").innerText,
|
||||
I18n.t("poll.results.groups.title", { groups: "foo" })
|
||||
);
|
||||
assert
|
||||
.dom(".poll-container .alert")
|
||||
.hasText(I18n.t("poll.results.groups.title", { groups: "foo" }));
|
||||
assert.strictEqual(requests, 0);
|
||||
assert.ok(!exists(".chosen"));
|
||||
assert.dom(".chosen").doesNotExist();
|
||||
});
|
||||
|
||||
test("voting on a multiple poll with no min attribute", async function (assert) {
|
||||
this.setProperties({
|
||||
attributes: EmberObject.create({
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: EmberObject.create({
|
||||
name: "poll",
|
||||
type: "multiple",
|
||||
status: "open",
|
||||
results: "always",
|
||||
max: 2,
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 0,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
vote: [],
|
||||
groupableUserFields: [],
|
||||
post: EmberObject.create({
|
||||
id: 42,
|
||||
topic: {
|
||||
archived: false,
|
||||
},
|
||||
user_id: 29,
|
||||
}),
|
||||
poll: EmberObject.create({
|
||||
name: "poll",
|
||||
type: "multiple",
|
||||
status: "open",
|
||||
results: "always",
|
||||
max: 2,
|
||||
options: [
|
||||
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
|
||||
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
|
||||
],
|
||||
voters: 0,
|
||||
chart_type: "bar",
|
||||
}),
|
||||
preloadedVoters: [],
|
||||
});
|
||||
await render(
|
||||
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
|
||||
);
|
||||
await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
|
||||
|
||||
assert.ok(exists(".poll-buttons .cast-votes:disabled"));
|
||||
assert.dom(".poll-buttons .cast-votes:disabled").exists();
|
||||
|
||||
await click(
|
||||
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button"
|
||||
);
|
||||
|
||||
await click(".poll-buttons .cast-votes");
|
||||
assert.ok(exists(".chosen"));
|
||||
assert.dom(".chosen").exists();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue