feat: ability to create polls (#1235)
* feat: ability to create polls fixes #1130 * fix adds and deletes * fix tests * fix tests again
This commit is contained in:
parent
2c1de66592
commit
0878275ab9
|
@ -42,6 +42,7 @@ module.exports = [
|
||||||
{ id: 'fa-circle-o', src: 'src/thirdparty/font-awesome-svg-png/white/svg/circle-o.svg' },
|
{ 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-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-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-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-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' },
|
{ 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-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-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-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' }
|
||||||
]
|
]
|
||||||
|
|
|
@ -22,7 +22,7 @@ export async function insertHandleForReply (statusId) {
|
||||||
|
|
||||||
export async function postStatus (realm, text, inReplyToId, mediaIds,
|
export async function postStatus (realm, text, inReplyToId, mediaIds,
|
||||||
sensitive, spoilerText, visibility,
|
sensitive, spoilerText, visibility,
|
||||||
mediaDescriptions, inReplyToUuid) {
|
mediaDescriptions, inReplyToUuid, poll) {
|
||||||
let { currentInstance, accessToken, online } = store.get()
|
let { currentInstance, accessToken, online } = store.get()
|
||||||
|
|
||||||
if (!online) {
|
if (!online) {
|
||||||
|
@ -41,7 +41,7 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
|
||||||
return description && putMediaDescription(currentInstance, accessToken, mediaIds[i], description)
|
return description && putMediaDescription(currentInstance, accessToken, mediaIds[i], description)
|
||||||
}))
|
}))
|
||||||
let status = await postStatusToServer(currentInstance, accessToken, text,
|
let status = await postStatusToServer(currentInstance, accessToken, text,
|
||||||
inReplyToId, mediaIds, sensitive, spoilerText, visibility)
|
inReplyToId, mediaIds, sensitive, spoilerText, visibility, poll)
|
||||||
addStatusOrNotification(currentInstance, 'home', status)
|
addStatusOrNotification(currentInstance, 'home', status)
|
||||||
store.clearComposeData(realm)
|
store.clearComposeData(realm)
|
||||||
emit('postedStatus', realm, inReplyToUuid)
|
emit('postedStatus', realm, inReplyToUuid)
|
||||||
|
|
18
src/routes/_actions/composePoll.js
Normal file
18
src/routes/_actions/composePoll.js
Normal file
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { auth, basename } from './utils'
|
||||||
import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax'
|
import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax'
|
||||||
|
|
||||||
export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
|
export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
|
||||||
sensitive, spoilerText, visibility) {
|
sensitive, spoilerText, visibility, poll) {
|
||||||
let url = `${basename(instanceName)}/api/v1/statuses`
|
let url = `${basename(instanceName)}/api/v1/statuses`
|
||||||
|
|
||||||
let body = {
|
let body = {
|
||||||
|
@ -11,7 +11,8 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId,
|
||||||
media_ids: mediaIds,
|
media_ids: mediaIds,
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
spoiler_text: spoilerText,
|
spoiler_text: spoilerText,
|
||||||
visibility: visibility
|
visibility: visibility,
|
||||||
|
poll: poll
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let key of Object.keys(body)) {
|
for (let key of Object.keys(body)) {
|
||||||
|
|
76
src/routes/_components/Select.html
Normal file
76
src/routes/_components/Select.html
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<div class="select-wrapper {className || ''}">
|
||||||
|
<select on:change>
|
||||||
|
{#each options as option (option.value)}
|
||||||
|
<option value="{option.value}" selected="{option.value === defaultValue ? 'selected' : ''}">
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
<div class="select-dropdown-icon-wrapper">
|
||||||
|
<SvgIcon href="#fa-angle-down" className="select-dropdown-icon"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.select-dropdown-icon-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
:global(.select-dropdown-icon) {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
min-width: 18px;
|
||||||
|
fill: var(--action-button-deemphasized-fill-color);
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 35px 5px 15px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.3em;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
line-height: 1.1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid var(--main-border);
|
||||||
|
border-radius: 10px;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
select:hover {
|
||||||
|
background-color: var(--button-bg-hover);
|
||||||
|
}
|
||||||
|
select:active {
|
||||||
|
background-color: var(--button-bg-active);
|
||||||
|
}
|
||||||
|
select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
select:-moz-focusring {
|
||||||
|
color: transparent;
|
||||||
|
text-shadow: 0 0 0 var(--body-text-color);
|
||||||
|
}
|
||||||
|
select option {
|
||||||
|
font-weight:normal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import SvgIcon from './SvgIcon.html'
|
||||||
|
export default {
|
||||||
|
data: () => ({
|
||||||
|
defaultValue: '',
|
||||||
|
className: ''
|
||||||
|
}),
|
||||||
|
components: {
|
||||||
|
SvgIcon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -13,7 +13,13 @@
|
||||||
<ComposeInput {realm} {text} {autoFocus} on:postAction="doPostStatus()" />
|
<ComposeInput {realm} {text} {autoFocus} on:postAction="doPostStatus()" />
|
||||||
<ComposeLengthGauge {length} {overLimit} />
|
<ComposeLengthGauge {length} {overLimit} />
|
||||||
<ComposeAutosuggest {realm} {text} />
|
<ComposeAutosuggest {realm} {text} />
|
||||||
<ComposeToolbar {realm} {postPrivacy} {media} {contentWarningShown} {text} />
|
{#if poll && poll.options && poll.options.length}
|
||||||
|
<div class="compose-poll-wrapper"
|
||||||
|
transition:slide="{duration: 333}">
|
||||||
|
<ComposePoll {realm} {poll} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<ComposeToolbar {realm} {postPrivacy} {media} {contentWarningShown} {text} {poll} />
|
||||||
<ComposeLengthIndicator {length} {overLimit} />
|
<ComposeLengthIndicator {length} {overLimit} />
|
||||||
<ComposeMedia {realm} {media} />
|
<ComposeMedia {realm} {media} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,6 +44,7 @@
|
||||||
"avatar input input input"
|
"avatar input input input"
|
||||||
"avatar gauge gauge gauge"
|
"avatar gauge gauge gauge"
|
||||||
"avatar autosuggest autosuggest autosuggest"
|
"avatar autosuggest autosuggest autosuggest"
|
||||||
|
"avatar poll poll poll"
|
||||||
"avatar toolbar toolbar length"
|
"avatar toolbar toolbar length"
|
||||||
"avatar media media media";
|
"avatar media media media";
|
||||||
grid-template-columns: min-content minmax(0, max-content) 1fr 1fr;
|
grid-template-columns: min-content minmax(0, max-content) 1fr 1fr;
|
||||||
|
@ -62,6 +69,10 @@
|
||||||
grid-area: cw;
|
grid-area: cw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compose-poll-wrapper {
|
||||||
|
grid-area: poll;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.compose-box {
|
.compose-box {
|
||||||
padding: 10px 10px 0 10px;
|
padding: 10px 10px 0 10px;
|
||||||
|
@ -83,12 +94,14 @@
|
||||||
import ComposeContentWarning from './ComposeContentWarning.html'
|
import ComposeContentWarning from './ComposeContentWarning.html'
|
||||||
import ComposeFileDrop from './ComposeFileDrop.html'
|
import ComposeFileDrop from './ComposeFileDrop.html'
|
||||||
import ComposeAutosuggest from './ComposeAutosuggest.html'
|
import ComposeAutosuggest from './ComposeAutosuggest.html'
|
||||||
|
import ComposePoll from './ComposePoll.html'
|
||||||
import { measureText } from '../../_utils/measureText'
|
import { measureText } from '../../_utils/measureText'
|
||||||
import { POST_PRIVACY_OPTIONS } from '../../_static/statuses'
|
import { POST_PRIVACY_OPTIONS } from '../../_static/statuses'
|
||||||
import { store } from '../../_store/store'
|
import { store } from '../../_store/store'
|
||||||
import { slide } from 'svelte-transitions'
|
import { slide } from 'svelte-transitions'
|
||||||
import { postStatus, insertHandleForReply, setReplySpoiler, setReplyVisibility } from '../../_actions/compose'
|
import { postStatus, insertHandleForReply, setReplySpoiler, setReplyVisibility } from '../../_actions/compose'
|
||||||
import { classname } from '../../_utils/classname'
|
import { classname } from '../../_utils/classname'
|
||||||
|
import { POLL_EXPIRY_DEFAULT } from '../../_static/polls'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
|
@ -118,7 +131,8 @@
|
||||||
ComposeMedia,
|
ComposeMedia,
|
||||||
ComposeContentWarning,
|
ComposeContentWarning,
|
||||||
ComposeFileDrop,
|
ComposeFileDrop,
|
||||||
ComposeAutosuggest
|
ComposeAutosuggest,
|
||||||
|
ComposePoll
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
size: void 0,
|
size: void 0,
|
||||||
|
@ -144,6 +158,7 @@
|
||||||
composeData: ({ $currentComposeData, realm }) => $currentComposeData[realm] || {},
|
composeData: ({ $currentComposeData, realm }) => $currentComposeData[realm] || {},
|
||||||
text: ({ composeData }) => composeData.text || '',
|
text: ({ composeData }) => composeData.text || '',
|
||||||
media: ({ composeData }) => composeData.media || [],
|
media: ({ composeData }) => composeData.media || [],
|
||||||
|
poll: ({ composeData }) => composeData.poll,
|
||||||
inReplyToId: ({ composeData }) => composeData.inReplyToId,
|
inReplyToId: ({ composeData }) => composeData.inReplyToId,
|
||||||
postPrivacy: ({ postPrivacyKey }) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey),
|
postPrivacy: ({ postPrivacyKey }) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey),
|
||||||
defaultPostPrivacyKey: ({ $currentVerifyCredentials }) => (
|
defaultPostPrivacyKey: ({ $currentVerifyCredentials }) => (
|
||||||
|
@ -172,7 +187,8 @@
|
||||||
realm,
|
realm,
|
||||||
overLimit,
|
overLimit,
|
||||||
inReplyToUuid, // typical replies, using Pinafore-specific uuid
|
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()
|
} = this.get()
|
||||||
let sensitive = media.length && !!contentWarning
|
let sensitive = media.length && !!contentWarning
|
||||||
let mediaIds = media.map(_ => _.data.id)
|
let mediaIds = media.map(_ => _.data.id)
|
||||||
|
@ -183,10 +199,25 @@
|
||||||
return // do nothing if invalid
|
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 */
|
/* no await */
|
||||||
postStatus(realm, text, inReplyTo, mediaIds,
|
postStatus(realm, text, inReplyTo, mediaIds,
|
||||||
sensitive, contentWarning, postPrivacyKey,
|
sensitive, contentWarning, postPrivacyKey,
|
||||||
mediaDescriptions, inReplyToUuid)
|
mediaDescriptions, inReplyToUuid, pollToPost)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
157
src/routes/_components/compose/ComposePoll.html
Normal file
157
src/routes/_components/compose/ComposePoll.html
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
<section class="compose-poll" aria-label="Create poll">
|
||||||
|
{#each poll.options as option, i}
|
||||||
|
<input id="poll-option-{realm}-{i}"
|
||||||
|
type="text"
|
||||||
|
maxlength="25"
|
||||||
|
on:change="onChange(i)"
|
||||||
|
placeholder="Choice {i + 1}"
|
||||||
|
aria-labelledby="poll-option-label-{realm}-{i}"
|
||||||
|
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
label="Remove choice {i + 1}"
|
||||||
|
href="#fa-times"
|
||||||
|
muted={true}
|
||||||
|
on:click="onDeleteClick(i)"
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<div>
|
||||||
|
<input type="checkbox"
|
||||||
|
id="poll-option-multiple-{realm}"
|
||||||
|
on:change="onMultipleChange()"
|
||||||
|
>
|
||||||
|
<label class="multiple-choice-label"
|
||||||
|
for="poll-option-multiple-{realm}">
|
||||||
|
Multiple choice
|
||||||
|
</label>
|
||||||
|
<Select className="poll-expiry-select"
|
||||||
|
options={pollExpiryOptions}
|
||||||
|
defaultValue={pollExpiryDefaultValue}
|
||||||
|
on:change="onExpiryChange(event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
className="add-poll-choice-button"
|
||||||
|
label="Add choice"
|
||||||
|
href="#fa-plus"
|
||||||
|
muted={true}
|
||||||
|
disabled={poll.options.length === 4}
|
||||||
|
on:click="onAddClick()"
|
||||||
|
/>
|
||||||
|
{#each poll.options as option, i}
|
||||||
|
<label id="poll-option-label-{realm}-{i}"
|
||||||
|
class="sr-only"
|
||||||
|
for="poll-option-{realm}-{i}">
|
||||||
|
Choice {i + 1}
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
<style>
|
||||||
|
.compose-poll {
|
||||||
|
margin: 10px 0 10px 5px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, max-content) max-content;
|
||||||
|
grid-row-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.poll-expiry-select) {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiple-choice-label {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
:global(.poll-expiry-select) {
|
||||||
|
display: block;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
:global(.add-poll-choice-button) {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import IconButton from '../IconButton.html'
|
||||||
|
import Select from '../Select.html'
|
||||||
|
import { store } from '../../_store/store'
|
||||||
|
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
|
||||||
|
import { POLL_EXPIRY_DEFAULT, POLL_EXPIRY_OPTIONS } from '../../_static/polls'
|
||||||
|
|
||||||
|
function flushPollOptionsToDom (poll, realm) {
|
||||||
|
for (let i = 0; i < poll.options.length; i++) {
|
||||||
|
let element = document.getElementById(`poll-option-${realm}-${i}`)
|
||||||
|
element.value = poll.options[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
oncreate () {
|
||||||
|
let { realm } = this.get()
|
||||||
|
let poll = this.store.getComposeData(realm, 'poll')
|
||||||
|
flushPollOptionsToDom(poll, realm)
|
||||||
|
document.getElementById(`poll-option-multiple-${realm}`).checked = !!poll.multiple
|
||||||
|
this.set({ pollExpiryDefaultValue: poll.expiry || POLL_EXPIRY_DEFAULT })
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
pollExpiryOptions: POLL_EXPIRY_OPTIONS,
|
||||||
|
pollExpiryDefaultValue: POLL_EXPIRY_DEFAULT
|
||||||
|
}),
|
||||||
|
store: () => store,
|
||||||
|
methods: {
|
||||||
|
onChange (i) {
|
||||||
|
scheduleIdleTask(() => {
|
||||||
|
let { realm } = this.get()
|
||||||
|
let element = document.getElementById(`poll-option-${realm}-${i}`)
|
||||||
|
let poll = this.store.getComposeData(realm, 'poll')
|
||||||
|
poll.options[i] = element.value
|
||||||
|
this.store.setComposeData(realm, { poll })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onMultipleChange () {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let { realm } = this.get()
|
||||||
|
let element = document.getElementById(`poll-option-multiple-${realm}`)
|
||||||
|
let poll = this.store.getComposeData(realm, 'poll')
|
||||||
|
poll.multiple = !!element.checked
|
||||||
|
this.store.setComposeData(realm, { poll })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDeleteClick (i) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let { realm } = this.get()
|
||||||
|
let poll = this.store.getComposeData(realm, 'poll')
|
||||||
|
poll.options.splice(i, 1)
|
||||||
|
this.store.setComposeData(realm, { poll })
|
||||||
|
flushPollOptionsToDom(poll, realm)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onAddClick () {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let { realm } = this.get()
|
||||||
|
let poll = this.store.getComposeData(realm, 'poll')
|
||||||
|
if (!poll.options.length !== 4) {
|
||||||
|
poll.options.push('')
|
||||||
|
}
|
||||||
|
this.store.setComposeData(realm, { poll })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onExpiryChange (e) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let { realm } = this.get()
|
||||||
|
let { value } = e.target
|
||||||
|
let poll = this.store.getComposeData(realm, 'poll')
|
||||||
|
poll.expiry = parseInt(value, 10)
|
||||||
|
this.store.setComposeData(realm, { poll })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
IconButton,
|
||||||
|
Select
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -12,6 +12,13 @@
|
||||||
on:click="onMediaClick()"
|
on:click="onMediaClick()"
|
||||||
disabled={$uploadingMedia || (media.length === 4)}
|
disabled={$uploadingMedia || (media.length === 4)}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
label="{poll && poll.options && poll.options.length ? 'Add poll' : 'Remove poll'}"
|
||||||
|
href="#fa-bar-chart"
|
||||||
|
on:click="onPollClick()"
|
||||||
|
pressable="true"
|
||||||
|
pressed={poll && poll.options && poll.options.length}
|
||||||
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Adjust privacy (currently {postPrivacy.label})"
|
label="Adjust privacy (currently {postPrivacy.label})"
|
||||||
href={postPrivacy.icon}
|
href={postPrivacy.icon}
|
||||||
|
@ -48,6 +55,7 @@
|
||||||
import { doMediaUpload } from '../../_actions/media'
|
import { doMediaUpload } from '../../_actions/media'
|
||||||
import { toggleContentWarningShown } from '../../_actions/contentWarnings'
|
import { toggleContentWarningShown } from '../../_actions/contentWarnings'
|
||||||
import { mediaAccept } from '../../_static/media'
|
import { mediaAccept } from '../../_static/media'
|
||||||
|
import { enablePoll, disablePoll } from '../../_actions/composePoll'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -79,6 +87,14 @@
|
||||||
onContentWarningClick () {
|
onContentWarningClick () {
|
||||||
let { realm } = this.get()
|
let { realm } = this.get()
|
||||||
toggleContentWarningShown(realm)
|
toggleContentWarningShown(realm)
|
||||||
|
},
|
||||||
|
onPollClick () {
|
||||||
|
let { poll, realm } = this.get()
|
||||||
|
if (poll && poll.options && poll.options.length) {
|
||||||
|
disablePoll(realm)
|
||||||
|
} else {
|
||||||
|
enablePoll(realm)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
src/routes/_static/polls.js
Normal file
32
src/routes/_static/polls.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
export const POLL_EXPIRY_OPTIONS = [
|
||||||
|
{
|
||||||
|
'value': 300,
|
||||||
|
'label': '5 minutes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 1800,
|
||||||
|
'label': '30 minutes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 3600,
|
||||||
|
'label': '1 hour'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 21600,
|
||||||
|
'label': '6 hours'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 86400,
|
||||||
|
'label': '1 day'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 259200,
|
||||||
|
'label': '3 days'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': 604800,
|
||||||
|
'label': '7 days'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const POLL_EXPIRY_DEFAULT = 86400
|
|
@ -22,8 +22,8 @@ export const composeButton = $('.compose-box-button')
|
||||||
export const composeLengthIndicator = $('.compose-box-length')
|
export const composeLengthIndicator = $('.compose-box-length')
|
||||||
export const emojiButton = $('.compose-box-toolbar button:first-child')
|
export const emojiButton = $('.compose-box-toolbar button:first-child')
|
||||||
export const mediaButton = $('.compose-box-toolbar button:nth-child(2)')
|
export const mediaButton = $('.compose-box-toolbar button:nth-child(2)')
|
||||||
export const postPrivacyButton = $('.compose-box-toolbar button:nth-child(3)')
|
export const postPrivacyButton = $('.compose-box-toolbar button:nth-child(4)')
|
||||||
export const contentWarningButton = $('.compose-box-toolbar button:nth-child(4)')
|
export const contentWarningButton = $('.compose-box-toolbar button:nth-child(5)')
|
||||||
export const emailInput = $('input#user_email')
|
export const emailInput = $('input#user_email')
|
||||||
export const passwordInput = $('input#user_password')
|
export const passwordInput = $('input#user_password')
|
||||||
export const authorizeInput = $('button[type=submit]:not(.negative)')
|
export const authorizeInput = $('button[type=submit]:not(.negative)')
|
||||||
|
@ -56,7 +56,7 @@ export const composeModalInput = $('.modal-dialog .compose-box-input')
|
||||||
export const composeModalComposeButton = $('.modal-dialog .compose-box-button')
|
export const composeModalComposeButton = $('.modal-dialog .compose-box-button')
|
||||||
export const composeModalContentWarningInput = $('.modal-dialog .content-warning-input')
|
export const composeModalContentWarningInput = $('.modal-dialog .content-warning-input')
|
||||||
export const composeModalEmojiButton = $('.modal-dialog .compose-box-toolbar button:nth-child(1)')
|
export const composeModalEmojiButton = $('.modal-dialog .compose-box-toolbar button:nth-child(1)')
|
||||||
export const composeModalPostPrivacyButton = $('.modal-dialog .compose-box-toolbar button:nth-child(3)')
|
export const composeModalPostPrivacyButton = $('.modal-dialog .compose-box-toolbar button:nth-child(4)')
|
||||||
|
|
||||||
export const postPrivacyDialogButtonUnlisted = $('[aria-label="Post privacy dialog"] li:nth-child(2) button')
|
export const postPrivacyDialogButtonUnlisted = $('[aria-label="Post privacy dialog"] li:nth-child(2) button')
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ export function getNthComposeReplyButton (n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNthPostPrivacyButton (n) {
|
export function getNthPostPrivacyButton (n) {
|
||||||
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(3)`)
|
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(4)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNthAutosuggestionResult (n) {
|
export function getNthAutosuggestionResult (n) {
|
||||||
|
@ -301,11 +301,11 @@ export function getNthReplyContentWarningInput (n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNthReplyContentWarningButton (n) {
|
export function getNthReplyContentWarningButton (n) {
|
||||||
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(4)`)
|
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(5)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNthReplyPostPrivacyButton (n) {
|
export function getNthReplyPostPrivacyButton (n) {
|
||||||
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(3)`)
|
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(4)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNthPostPrivacyOptionInDialog (n) {
|
export function getNthPostPrivacyOptionInDialog (n) {
|
||||||
|
|
Loading…
Reference in a new issue