From 6023d4dc832256db13669c8fd6c450e7fed6c3c5 Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 00:34:01 -0400 Subject: [PATCH 1/9] Added "Unlock" Feature; Made a dynamic dropdown; Moved vote option creation to Static; --- src/Actions.ts | 13 +++++++++++++ src/Poll.ts | 48 +++++++++++++++++++++++++++++------------------- src/Static.ts | 31 ++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/Actions.ts b/src/Actions.ts index 2ac1e3e..f143101 100644 --- a/src/Actions.ts +++ b/src/Actions.ts @@ -51,6 +51,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 +118,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..baccb25 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", @@ -104,6 +89,20 @@ 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") + ] + }; + return [{ type: "actions", elements: [selection] }]; + } + public resetVote(userId: string): void { this.processButtons(this.message.length, button => { const { votes, userIdIndex } = this.getVotesAndUserIndex(button, userId); @@ -131,13 +130,24 @@ 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, 2).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 { + const voteoptions = []; + const results = this.message.slice(this.getDividerId()+2); + for (let i = 0; i < results.length; i++) { + const option = ((results[i] as SectionBlock).text as MrkdwnElement).text; + voteoptions.push(option.substr(option.indexOf("* "), option.indexOf(" »")).slice(2,-2)); + } + const actionBlocks = Static.buildVoteOptions(voteoptions,0); + this.isLocked = false; + this.message = this.message.slice(0,2).concat(actionBlocks).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); @@ -163,7 +173,7 @@ export class Poll { const users: string[] = votes[key].split(","); users.splice(0, 1); // Don"t bother with empty votes - if (users.length === 0) return null; + if (users.length === 0) return Static.buildSectionBlock(`*0* ${key} »`); // When anonymous we don"t display the user"s names const names = !this.anonymous || overrideAnon ? users.map((k: string) => `<@${k}>`).join(",") : "~HIDDEN~"; return Static.buildSectionBlock(`*${users.length}* ${key} » ${names}`); diff --git a/src/Static.ts b/src/Static.ts index 13ace5c..36e2ce1 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,39 @@ 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 + console.log(parameters[i]); + const button: Button = { type: "button", value: " ", text: Static.buildTextElem(parameters[i]) }; + console.log(button); + actionBlocks[actionBlockCount].elements.push(button); + } + return actionBlocks; } } \ No newline at end of file From 1046fed2ff2f5ee18b4e3c835453dd21348755e2 Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 00:40:14 -0400 Subject: [PATCH 2/9] Removed console.log() --- src/Static.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Static.ts b/src/Static.ts index 36e2ce1..4dd077a 100644 --- a/src/Static.ts +++ b/src/Static.ts @@ -36,9 +36,7 @@ export class Static { parameters[i] = parameters[i].replace("&", "+").replace("<", "greater than ") .replace(">", "less than "); // We set value to empty string so that it is always defined - console.log(parameters[i]); const button: Button = { type: "button", value: " ", text: Static.buildTextElem(parameters[i]) }; - console.log(button); actionBlocks[actionBlockCount].elements.push(button); } return actionBlocks; From 64918e241abf6db1811060c0abfcbd67dbac8fe0 Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 07:25:23 -0400 Subject: [PATCH 3/9] Removed unnecessary "if" statement --- src/Poll.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Poll.ts b/src/Poll.ts index baccb25..f20ce11 100644 --- a/src/Poll.ts +++ b/src/Poll.ts @@ -172,8 +172,6 @@ export class Poll { private buildVoteTally(overrideAnon: boolean, votes: any, key: string): SectionBlock | null { const users: string[] = votes[key].split(","); users.splice(0, 1); - // Don"t bother with empty votes - if (users.length === 0) return Static.buildSectionBlock(`*0* ${key} »`); // When anonymous we don"t display the user"s names const names = !this.anonymous || overrideAnon ? users.map((k: string) => `<@${k}>`).join(",") : "~HIDDEN~"; return Static.buildSectionBlock(`*${users.length}* ${key} » ${names}`); From 6bbdcc6932c4d8100fbbd148f227bb63afe7cd0f Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 09:44:35 -0400 Subject: [PATCH 4/9] Prevent removal of vote options when poll is locked; Prevent button press processing when vote is locked; Changed how "isLocked" status is determined Added "Collect" to getDynamicSelect(); Removed vote builder in unlockpoll() since its no longer needed --- src/Poll.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Poll.ts b/src/Poll.ts index f20ce11..af9ce94 100644 --- a/src/Poll.ts +++ b/src/Poll.ts @@ -68,8 +68,8 @@ 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 the is a lock symbol right below the divider, the poll is locked + this.isLocked = (this.message.length-1 === this.getDividerId())? false : ((this.message[this.getDividerId()+1] as SectionBlock).text as MrkdwnElement).text === ":lock:"; } public getBlocks(): KnownBlock[] { @@ -100,6 +100,9 @@ export class Poll { Static.buildSelectOption("Delete poll", "delete") ] }; + if (this.anonymous) { + selection.options!.push(Static.buildSelectOption("Collect Results", "collect")); + } return [{ type: "actions", elements: [selection] }]; } @@ -116,6 +119,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) { @@ -132,20 +136,13 @@ export class Poll { public lockPoll(): void { this.isLocked = true; this.generateVoteResults(); - this.message = this.message.slice(0, 2).concat(this.getDynamicSelect()).concat(this.message.slice(this.getDividerId())); + 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 { - const voteoptions = []; - const results = this.message.slice(this.getDividerId()+2); - for (let i = 0; i < results.length; i++) { - const option = ((results[i] as SectionBlock).text as MrkdwnElement).text; - voteoptions.push(option.substr(option.indexOf("* "), option.indexOf(" »")).slice(2,-2)); - } - const actionBlocks = Static.buildVoteOptions(voteoptions,0); this.isLocked = false; - this.message = this.message.slice(0,2).concat(actionBlocks).concat(this.getDynamicSelect()).concat({ type: "divider" }).concat(this.message.slice(this.getDividerId()+2)); + 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 @@ -191,7 +188,7 @@ export class Poll { } 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)); } From 1b47bf38b7ed76dd298c54c1f869eda2c6451c93 Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 15:48:49 -0400 Subject: [PATCH 5/9] Turned this.isLocked assignment into easier to read if statement --- src/Poll.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Poll.ts b/src/Poll.ts index af9ce94..3516096 100644 --- a/src/Poll.ts +++ b/src/Poll.ts @@ -69,7 +69,9 @@ export class Poll { this.multiple = this.checkIfMsgContains("(Multiple Answers)"); this.anonymous = this.checkIfMsgContains("(Anonymous)"); //if the is a lock symbol right below the divider, the poll is locked - this.isLocked = (this.message.length-1 === this.getDividerId())? false : ((this.message[this.getDividerId()+1] as SectionBlock).text as MrkdwnElement).text === ":lock:"; + if (this.message.length-1 !== this.getDividerId()) { + this.isLocked = ((this.message[this.getDividerId()+1] as SectionBlock).text as MrkdwnElement).text === ":lock:"; + } } public getBlocks(): KnownBlock[] { From 1fdaf348f27d06e82c91da0145ecc8e3c1672586 Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 15:56:19 -0400 Subject: [PATCH 6/9] Add notification to user if their vote was blocked due to it being locked. --- src/Actions.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Actions.ts b/src/Actions.ts index f143101..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 From b3bedd063e38f27a17afd4a6a5b1b4a175a4718e Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 16:19:07 -0400 Subject: [PATCH 7/9] Remove empty votes from results; Remove null from section, because null causes errors when sending the message. --- src/Poll.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Poll.ts b/src/Poll.ts index 3516096..4e504bd 100644 --- a/src/Poll.ts +++ b/src/Poll.ts @@ -171,6 +171,8 @@ export class Poll { private buildVoteTally(overrideAnon: boolean, votes: any, key: string): SectionBlock | null { const users: string[] = votes[key].split(","); users.splice(0, 1); + // Don"t bother with empty votes + if (users.length === 0) return null; // When anonymous we don"t display the user"s names const names = !this.anonymous || overrideAnon ? users.map((k: string) => `<@${k}>`).join(",") : "~HIDDEN~"; return Static.buildSectionBlock(`*${users.length}* ${key} » ${names}`); @@ -186,7 +188,7 @@ 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 { From e36a1cdb26d2816481fd8987a31c9bcfe44c889b Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 16:22:12 -0400 Subject: [PATCH 8/9] Fixed issue where < was replaced by greater than and vice versa. --- src/Static.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Static.ts b/src/Static.ts index 4dd077a..01b9602 100644 --- a/src/Static.ts +++ b/src/Static.ts @@ -33,8 +33,8 @@ export class Static { 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 "); + 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); From 48decf4553c162fb58bf8f592e91b5247029794e Mon Sep 17 00:00:00 2001 From: Zapdos26 Date: Sat, 19 Oct 2019 16:26:21 -0400 Subject: [PATCH 9/9] Fixed spelling issue --- src/Poll.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Poll.ts b/src/Poll.ts index 4e504bd..13e45b3 100644 --- a/src/Poll.ts +++ b/src/Poll.ts @@ -68,7 +68,7 @@ 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 the is a lock symbol right below the divider, the poll is locked + //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:"; }