diff --git a/src/Actions.ts b/src/Actions.ts index 2ac1e3e..fde35b9 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -32,7 +32,12 @@ export class Actions { const poll = new Poll(payload.message.blocks); poll.vote(payload.actions[0].text.text, payload.user.id); payload.message.blocks = poll.getBlocks(); - payload.message.text = "Vote changed!"; + // Sends user message if their vote was changed or blocked due to the poll being locked + const vote_text = poll.getLockedStatus() ? "You cannot vote after the poll has been locked!" : "Vote changed!"; + this.wc.chat.postEphemeral({ + channel: payload.channel.id, + text: vote_text, user: payload.user.id + }); // We respond with the new payload res(payload.message); // In case it is being slow users will see this message @@ -51,6 +56,9 @@ export class Actions { case "lock": this.onLockSelected(payload, poll); break; + case "unlock": + this.onUnlockSelected(payload,poll); + break; case "delete": this.onDeleteSelected(payload, poll); break; @@ -115,6 +123,16 @@ export class Actions { this.postEphemeralOnlyAuthor("lock", "poll", payload.channel.id, payload.user.id); } } + + private onUnlockSelected(payload: any, poll: Poll): void { + payload.message.text = "Poll unlocked!"; + if (Actions.isPollAuthor(payload,poll)) { + poll.unlockpoll(); + payload.message.blocks = poll.getBlocks(); + } else{ + this.postEphemeralOnlyAuthor("unlock","poll",payload.channel.id, payload.user.id); + } + } private onDeleteSelected(payload: any, poll: Poll): void { if (Actions.isPollAuthor(payload, poll)) { diff --git a/src/Poll.ts b/src/Poll.ts index f2e73a4..13e45b3 100644 --- a/src/Poll.ts +++ b/src/Poll.ts @@ -34,23 +34,8 @@ export class Poll { const titleBlock = Static.buildSectionBlock(mrkdwnValue); message.push(titleBlock, Static.buildContextBlock(`Asked by: ${author}`)); - const actionBlocks: ActionsBlock[] = [{ type: "actions", elements: [] }]; - let actionBlockCount = 0; - // Construct all the buttons const start = titleBlock.text!.text === parameters[0] ? 1 : 2; - for (let i = start; i < parameters.length; i++) { - if (i % 5 === 0) { - const newActionBlock: ActionsBlock = { type: "actions", elements: [] }; - actionBlocks.push(newActionBlock); - actionBlockCount++; - } - // Remove special characters, should be able to remove this once slack figures itself out - parameters[i] = parameters[i].replace("&", "+").replace("<", "greater than ") - .replace(">", "less than "); - // We set value to empty string so that it is always defined - const button: Button = { type: "button", value: " ", text: Static.buildTextElem(parameters[i]) }; - actionBlocks[actionBlockCount].elements.push(button); - } + const actionBlocks = Static.buildVoteOptions(parameters,start); // The various poll options const selection: StaticSelect = { type: "static_select", @@ -83,8 +68,10 @@ export class Poll { // Since its databaseless the way we know if it is anonymous or multiple is by parsing the title this.multiple = this.checkIfMsgContains("(Multiple Answers)"); this.anonymous = this.checkIfMsgContains("(Anonymous)"); - // If there's no buttons then the poll is locked - this.isLocked = this.message[3].type === "divider"; + //if there is a lock symbol right below the divider, the poll is locked + if (this.message.length-1 !== this.getDividerId()) { + this.isLocked = ((this.message[this.getDividerId()+1] as SectionBlock).text as MrkdwnElement).text === ":lock:"; + } } public getBlocks(): KnownBlock[] { @@ -104,6 +91,23 @@ export class Poll { return { votes, userIdIndex: votes.indexOf(userId) }; } + private getDynamicSelect(): ActionsBlock[] { + const selection: StaticSelect = { + type: "static_select", + placeholder: Static.buildTextElem("Poll Options"), + options: [ + Static.buildSelectOption("Reset your vote", "reset"), + this.isLocked ? Static.buildSelectOption(":unlock: Unlock poll", "unlock") : Static.buildSelectOption(":lock: Lock poll", "lock"), + Static.buildSelectOption("Move to bottom", "bottom"), + Static.buildSelectOption("Delete poll", "delete") + ] + }; + if (this.anonymous) { + selection.options!.push(Static.buildSelectOption("Collect Results", "collect")); + } + return [{ type: "actions", elements: [selection] }]; + } + public resetVote(userId: string): void { this.processButtons(this.message.length, button => { const { votes, userIdIndex } = this.getVotesAndUserIndex(button, userId); @@ -117,6 +121,7 @@ export class Poll { } public vote(buttonText: string, userId: string): void { + if (this.isLocked) return; this.processButtons(this.message.length, button => { const { votes, userIdIndex } = this.getVotesAndUserIndex(button, userId); if (!this.multiple && userIdIndex > -1 && button.text.text !== buttonText) { @@ -131,13 +136,17 @@ export class Poll { } public lockPoll(): void { - if (this.isLocked) return; this.isLocked = true; this.generateVoteResults(); - this.message = this.message.slice(0, 2).concat(this.message.slice(this.getDividerId() - 1)); + this.message = this.message.slice(0, this.getDividerId()-1).concat(this.getDynamicSelect()).concat(this.message.slice(this.getDividerId())); // ((this.message[2] as ActionsBlock).elements[0] as StaticSelect).options!.splice(0, 2); } + public unlockpoll(): void { + this.isLocked = false; + this.message = this.message.slice(0,this.getDividerId()-1).concat(this.getDynamicSelect()).concat({ type: "divider" }).concat(this.message.slice(this.getDividerId()+2)); + } + // Creates the message that will be sent to the poll author with the final results public collectResults(): KnownBlock[] { const results = this.generateResults(true); @@ -179,11 +188,11 @@ export class Poll { }); const sections = Object.keys(votes).map(key => this.buildVoteTally(overrideAnon, votes, key) as SectionBlock); if (this.isLocked) sections.unshift(Static.buildSectionBlock(":lock:")); - return sections; + return sections.filter(f=>f !== null); } private generateVoteResults(): void { - // We throw out the old vote response and construct them again + // We throw out the old vote response and construct them again this.message = this.message.slice(0, this.getDividerId() + 1).concat(this.generateResults(false)); } diff --git a/src/Static.ts b/src/Static.ts index 13ace5c..01b9602 100644 --- a/src/Static.ts +++ b/src/Static.ts @@ -1,5 +1,5 @@ import { - SectionBlock, ContextBlock, PlainTextElement, Option + SectionBlock, ContextBlock, PlainTextElement, Option, ActionsBlock, Button } from "@slack/types"; export class Static { @@ -8,18 +8,37 @@ export class Static { } public static buildSectionBlock(mrkdwnValue: string): SectionBlock { - return { type: "section", text: { type: "mrkdwn", text: mrkdwnValue } }; + return {type: "section", text: {type: "mrkdwn", text: mrkdwnValue}}; } public static buildContextBlock(mrkdwnValue: string): ContextBlock { - return { type: "context", elements: [ { type: "mrkdwn", text: mrkdwnValue } ] }; + return {type: "context", elements: [{type: "mrkdwn", text: mrkdwnValue}]}; } public static buildSelectOption(text: string, value: string): Option { - return { text: this.buildTextElem(text), value: value }; + return {text: this.buildTextElem(text), value: value}; } public static buildTextElem(text: string): PlainTextElement { - return { type: "plain_text", text, emoji: true }; + return {type: "plain_text", text, emoji: true}; + } + public static buildVoteOptions(parameters: string [], start: number ): ActionsBlock[] { + const actionBlocks: ActionsBlock[] = [{ type: "actions", elements: [] }]; + let actionBlockCount = 0; + // Construct all the buttons + for (let i = start; i < parameters.length; i++) { + if (i % 5 === 0 && i != 0) { + const newActionBlock: ActionsBlock = { type: "actions", elements: [] }; + actionBlocks.push(newActionBlock); + actionBlockCount++; + } + // Remove special characters, should be able to remove this once slack figures itself out + parameters[i] = parameters[i].replace("&", "+").replace(">", "greater than ") + .replace("<", "less than "); + // We set value to empty string so that it is always defined + const button: Button = { type: "button", value: " ", text: Static.buildTextElem(parameters[i]) }; + actionBlocks[actionBlockCount].elements.push(button); + } + return actionBlocks; } } \ No newline at end of file