From 0878275ab91c0e14a15d8f3a45ce7e7c59233f32 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 27 May 2019 00:24:47 -0700 Subject: [PATCH] feat: ability to create polls (#1235) * feat: ability to create polls fixes #1130 * fix adds and deletes * fix tests * fix tests again --- bin/svgs.js | 4 +- src/routes/_actions/compose.js | 4 +- src/routes/_actions/composePoll.js | 18 ++ src/routes/_api/statuses.js | 5 +- src/routes/_components/Select.html | 76 +++++++++ .../_components/compose/ComposeBox.html | 39 ++++- .../_components/compose/ComposePoll.html | 157 ++++++++++++++++++ .../_components/compose/ComposeToolbar.html | 16 ++ src/routes/_static/polls.js | 32 ++++ tests/utils.js | 12 +- 10 files changed, 348 insertions(+), 15 deletions(-) create mode 100644 src/routes/_actions/composePoll.js create mode 100644 src/routes/_components/Select.html create mode 100644 src/routes/_components/compose/ComposePoll.html create mode 100644 src/routes/_static/polls.js diff --git a/bin/svgs.js b/bin/svgs.js index 45703483..04a78d7c 100644 --- a/bin/svgs.js +++ b/bin/svgs.js @@ -42,6 +42,7 @@ module.exports = [ { id: 'fa-circle-o', src: 'src/thirdparty/font-awesome-svg-png/white/svg/circle-o.svg' }, { id: 'fa-angle-left', src: 'src/thirdparty/font-awesome-svg-png/white/svg/angle-left.svg' }, { id: 'fa-angle-right', src: 'src/thirdparty/font-awesome-svg-png/white/svg/angle-right.svg' }, + { id: 'fa-angle-down', src: 'src/thirdparty/font-awesome-svg-png/white/svg/angle-down.svg' }, { id: 'fa-search-minus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/search-minus.svg' }, { id: 'fa-search-plus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/search-plus.svg' }, { id: 'fa-share-square-o', src: 'src/thirdparty/font-awesome-svg-png/white/svg/share-square-o.svg' }, @@ -49,5 +50,6 @@ module.exports = [ { id: 'fa-suitcase', src: 'src/thirdparty/font-awesome-svg-png/white/svg/suitcase.svg' }, { id: 'fa-bar-chart', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bar-chart.svg' }, { id: 'fa-clock', src: 'src/thirdparty/font-awesome-svg-png/white/svg/clock-o.svg' }, - { id: 'fa-refresh', src: 'src/thirdparty/font-awesome-svg-png/white/svg/refresh.svg' } + { id: 'fa-refresh', src: 'src/thirdparty/font-awesome-svg-png/white/svg/refresh.svg' }, + { id: 'fa-plus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/plus.svg' } ] diff --git a/src/routes/_actions/compose.js b/src/routes/_actions/compose.js index 6074e5c9..a97b8745 100644 --- a/src/routes/_actions/compose.js +++ b/src/routes/_actions/compose.js @@ -22,7 +22,7 @@ export async function insertHandleForReply (statusId) { export async function postStatus (realm, text, inReplyToId, mediaIds, sensitive, spoilerText, visibility, - mediaDescriptions, inReplyToUuid) { + mediaDescriptions, inReplyToUuid, poll) { let { currentInstance, accessToken, online } = store.get() if (!online) { @@ -41,7 +41,7 @@ export async function postStatus (realm, text, inReplyToId, mediaIds, return description && putMediaDescription(currentInstance, accessToken, mediaIds[i], description) })) let status = await postStatusToServer(currentInstance, accessToken, text, - inReplyToId, mediaIds, sensitive, spoilerText, visibility) + inReplyToId, mediaIds, sensitive, spoilerText, visibility, poll) addStatusOrNotification(currentInstance, 'home', status) store.clearComposeData(realm) emit('postedStatus', realm, inReplyToUuid) diff --git a/src/routes/_actions/composePoll.js b/src/routes/_actions/composePoll.js new file mode 100644 index 00000000..020fe5a0 --- /dev/null +++ b/src/routes/_actions/composePoll.js @@ -0,0 +1,18 @@ +import { store } from '../_store/store' + +export function enablePoll (realm) { + store.setComposeData(realm, { + poll: { + options: [ + '', + '' + ] + } + }) +} + +export function disablePoll (realm) { + store.setComposeData(realm, { + poll: null + }) +} diff --git a/src/routes/_api/statuses.js b/src/routes/_api/statuses.js index 506ca135..13a5649c 100644 --- a/src/routes/_api/statuses.js +++ b/src/routes/_api/statuses.js @@ -2,7 +2,7 @@ import { auth, basename } from './utils' import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax' export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds, - sensitive, spoilerText, visibility) { + sensitive, spoilerText, visibility, poll) { let url = `${basename(instanceName)}/api/v1/statuses` let body = { @@ -11,7 +11,8 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId, media_ids: mediaIds, sensitive: sensitive, spoiler_text: spoilerText, - visibility: visibility + visibility: visibility, + poll: poll } for (let key of Object.keys(body)) { diff --git a/src/routes/_components/Select.html b/src/routes/_components/Select.html new file mode 100644 index 00000000..14d5a873 --- /dev/null +++ b/src/routes/_components/Select.html @@ -0,0 +1,76 @@ +
+ +
+ +
+
+ + diff --git a/src/routes/_components/compose/ComposeBox.html b/src/routes/_components/compose/ComposeBox.html index 167e0bf8..b1a3c050 100644 --- a/src/routes/_components/compose/ComposeBox.html +++ b/src/routes/_components/compose/ComposeBox.html @@ -13,7 +13,13 @@ - + {#if poll && poll.options && poll.options.length} +
+ +
+ {/if} + @@ -38,6 +44,7 @@ "avatar input input input" "avatar gauge gauge gauge" "avatar autosuggest autosuggest autosuggest" + "avatar poll poll poll" "avatar toolbar toolbar length" "avatar media media media"; grid-template-columns: min-content minmax(0, max-content) 1fr 1fr; @@ -62,6 +69,10 @@ grid-area: cw; } + .compose-poll-wrapper { + grid-area: poll; + } + @media (max-width: 767px) { .compose-box { padding: 10px 10px 0 10px; @@ -83,12 +94,14 @@ import ComposeContentWarning from './ComposeContentWarning.html' import ComposeFileDrop from './ComposeFileDrop.html' import ComposeAutosuggest from './ComposeAutosuggest.html' + import ComposePoll from './ComposePoll.html' import { measureText } from '../../_utils/measureText' import { POST_PRIVACY_OPTIONS } from '../../_static/statuses' import { store } from '../../_store/store' import { slide } from 'svelte-transitions' import { postStatus, insertHandleForReply, setReplySpoiler, setReplyVisibility } from '../../_actions/compose' import { classname } from '../../_utils/classname' + import { POLL_EXPIRY_DEFAULT } from '../../_static/polls' export default { oncreate () { @@ -118,7 +131,8 @@ ComposeMedia, ComposeContentWarning, ComposeFileDrop, - ComposeAutosuggest + ComposeAutosuggest, + ComposePoll }, data: () => ({ size: void 0, @@ -144,6 +158,7 @@ composeData: ({ $currentComposeData, realm }) => $currentComposeData[realm] || {}, text: ({ composeData }) => composeData.text || '', media: ({ composeData }) => composeData.media || [], + poll: ({ composeData }) => composeData.poll, inReplyToId: ({ composeData }) => composeData.inReplyToId, postPrivacy: ({ postPrivacyKey }) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey), defaultPostPrivacyKey: ({ $currentVerifyCredentials }) => ( @@ -172,7 +187,8 @@ realm, overLimit, inReplyToUuid, // typical replies, using Pinafore-specific uuid - inReplyToId // delete-and-redraft replies, using standard id + inReplyToId, // delete-and-redraft replies, using standard id + poll } = this.get() let sensitive = media.length && !!contentWarning let mediaIds = media.map(_ => _.data.id) @@ -183,10 +199,25 @@ return // do nothing if invalid } + let hasPoll = poll && poll.options && poll.options.length + if (hasPoll) { + // validate poll + if (poll.options.length < 2 || !poll.options.every(Boolean)) { + return + } + } + + // convert internal poll format to the format Mastodon's REST API uses + let pollToPost = hasPoll && { + expires_in: (poll.expiry || POLL_EXPIRY_DEFAULT).toString(), + multiple: !!poll.multiple, + options: poll.options + } + /* no await */ postStatus(realm, text, inReplyTo, mediaIds, sensitive, contentWarning, postPrivacyKey, - mediaDescriptions, inReplyToUuid) + mediaDescriptions, inReplyToUuid, pollToPost) } } } diff --git a/src/routes/_components/compose/ComposePoll.html b/src/routes/_components/compose/ComposePoll.html new file mode 100644 index 00000000..75f6684b --- /dev/null +++ b/src/routes/_components/compose/ComposePoll.html @@ -0,0 +1,157 @@ +
+ {#each poll.options as option, i} + + + {/each} +
+ + +